installation and launching of vanilla minecraft working

This commit is contained in:
Samuel Walker 2024-10-30 15:39:11 -06:00
parent ab0bfebe87
commit 2bbb4b8cf0
13 changed files with 584 additions and 17 deletions

View File

@ -7,17 +7,23 @@ import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
)
type Instance struct {
InstanceName string
ModpackId string
ModpackVersion string
MinecraftVersion string
ForgeVersion string
NeoForgeVersion string
FabricVersion string
QuiltVersion string
JavaVersion int
}
type InstanceManager struct {
@ -36,7 +42,8 @@ type component struct {
func (i *InstanceManager)SearchInstances() {
i.instances = []Instance{}
dir := i.app.PrismLauncher.GetInstanceDir()
dir, _ := os.UserConfigDir()
dir = filepath.Join(dir, "FCLauncher", "instances")
if _, err := os.Stat(dir); err != nil {
return
}
@ -148,13 +155,59 @@ func (i *InstanceManager)InstallModpack(modpack Modpack, instanceName string){
}
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"))
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 obtain metadata: %s\n", err)
} else {
fmt.Printf("Version Metadata: %+v", metadata)
fmt.Printf("unable to pull metadata: %s\n", err)
}
time.Sleep(time.Second * 1)
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}
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
}
@ -163,6 +216,7 @@ func (i *InstanceManager)GetInstances() []Instance{
}
func (i *InstanceManager)CheckUpdate(instance Instance){
return
i.app.Status("Checking for Updates")
i.app.Modpacks.QuerryModpacks()
pack := i.app.Modpacks.GetModpack(instance.ModpackId)
@ -185,3 +239,37 @@ func (i *InstanceManager)CheckUpdate(instance Instance){
i.SearchInstances()
}
func (i *InstanceManager)LaunchInstance(instance string) {
dir, err := os.UserConfigDir()
if err != nil {
fmt.Printf("unable to get config directory\n")
}
instanceObject := Instance{}
found := false
for _, inst := range i.instances {
if inst.InstanceName == instance {
instanceObject = inst
found = true
break
}
}
if !found {
fmt.Printf("unable to find instance %s\n", instance)
}
execName := "java"
suffix := "lin"
if runtime.GOOS == "windows" {
execName = "Java.exe"
suffix = "win"
}
dir = filepath.Join(dir, "FCLauncher")
args, err := GetOfflineLaunchArgs(instanceObject.MinecraftVersion, filepath.Join(dir, "lib"), filepath.Join(dir, "bin"), filepath.Join(dir, "assets"), filepath.Join(dir, "instances", instance, "minecraft"), "Player")
if err != nil {
fmt.Printf("unable to get launch args: %s\n", err)
}
fmt.Printf("Args: %+v", args)
child := exec.Command(filepath.Join(dir, "java", fmt.Sprintf("java-%d-%s", instanceObject.JavaVersion, suffix), "bin", execName), args...)
data, err := child.CombinedOutput()
fmt.Printf("Command Output: %s\n", data)
}

View File

@ -7,6 +7,9 @@ import (
"github.com/wailsapp/wails/v2/pkg/runtime"
)
const client_id string = "9305aeb8-5ecb-4e7a-b28f-c33aefcfbd8d"
// App struct
type App struct {
Ctx context.Context

View File

@ -1,7 +1,6 @@
<script lang="ts">
import {GetModpacks, GetModpack} from '../wailsjs/go/main/ModpackManager.js'
import {InstallVanilla, GetInstances, CheckUpdate} from '../wailsjs/go/main/InstanceManager.js'
import {LaunchInstance} from '../wailsjs/go/main/Prism.js'
import {InstallVanilla, LaunchInstance, GetInstances, CheckUpdate} from '../wailsjs/go/main/InstanceManager.js'
import {GetVersions} from '../wailsjs/go/main/App.js'
import {onMount} from 'svelte'
import {loading} from './global.ts'
@ -10,7 +9,7 @@
let modpacks: string[] = []
let pack: string
let instances: Instance[] = []
let instance: Instance
let instance: string
let addingInstance: boolean = false
let name: string = "New Modpack"
@ -22,7 +21,7 @@
})
GetInstances().then((result) => {
instances = result
instance = instances[0]
instance = instances[0].InstanceName
})
}
@ -32,12 +31,10 @@
function onclick(event) {
$loading = true
CheckUpdate(instance).then(() => {
LaunchInstance(instance).then(() => {
$loading = false
window.runtime.Quit()
})
})
}
function install(){
@ -59,7 +56,7 @@
<main>
<select bind:value={instance} name="pack">Select a Modpack:
{#each instances as instance}
<option value={instance}>{instance.InstanceName}</option>
<option value={instance.InstanceName}>{instance.InstanceName}</option>
{/each}
</select>
<button on:click={onclick}>Launch</button>

View File

@ -10,4 +10,6 @@ export function InstallModpack(arg1:main.Modpack,arg2:string):Promise<void>;
export function InstallVanilla(arg1:string,arg2:string):Promise<void>;
export function LaunchInstance(arg1:string):Promise<void>;
export function SearchInstances():Promise<void>;

View File

@ -18,6 +18,10 @@ export function InstallVanilla(arg1, arg2) {
return window['go']['main']['InstanceManager']['InstallVanilla'](arg1, arg2);
}
export function LaunchInstance(arg1) {
return window['go']['main']['InstanceManager']['LaunchInstance'](arg1);
}
export function SearchInstances() {
return window['go']['main']['InstanceManager']['SearchInstances']();
}

View File

@ -4,6 +4,12 @@ export namespace main {
InstanceName: string;
ModpackId: string;
ModpackVersion: string;
MinecraftVersion: string;
ForgeVersion: string;
NeoForgeVersion: string;
FabricVersion: string;
QuiltVersion: string;
JavaVersion: number;
static createFrom(source: any = {}) {
return new Instance(source);
@ -14,6 +20,12 @@ export namespace main {
this.InstanceName = source["InstanceName"];
this.ModpackId = source["ModpackId"];
this.ModpackVersion = source["ModpackVersion"];
this.MinecraftVersion = source["MinecraftVersion"];
this.ForgeVersion = source["ForgeVersion"];
this.NeoForgeVersion = source["NeoForgeVersion"];
this.FabricVersion = source["FabricVersion"];
this.QuiltVersion = source["QuiltVersion"];
this.JavaVersion = source["JavaVersion"];
}
}
export class Version {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
package main
import (
"bytes"
"crypto/sha1"
"encoding/hex"
"encoding/json"
@ -9,7 +10,12 @@ import (
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"time"
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
"github.com/zhyee/zipstream"
)
type McVersionManifestEntry struct {
@ -59,6 +65,7 @@ type McLibraryArtifact struct {
type McLibraryDownload struct {
Artifact McLibraryArtifact
Classifiers map[string]interface{}
}
type McRuleOs struct {
@ -77,6 +84,7 @@ type McLibrary struct {
Downloads McLibraryDownload
Name string
Rules []McRule
Natives map[string]string
}
type McDownload struct {
@ -100,6 +108,7 @@ type McMetadata struct {
ReleaseTime time.Time
Time time.Time
Type string
MinecraftArguments string
}
func GetVersionManifest() (McVersionManifest, error) {
@ -176,7 +185,6 @@ func GetVersionMetadata(wantedVersion string) (McMetadata, error) {
if hex.EncodeToString(sha[:20]) == version.Sha1{
metadata := McMetadata{}
json.Unmarshal(data, &metadata)
fmt.Printf("Found cache!\n")
return metadata, nil
}
}
@ -214,3 +222,379 @@ func GetVersionMetadata(wantedVersion string) (McMetadata, error) {
}
return McMetadata{}, fmt.Errorf("Unable to find version %s\n", wantedVersion)
}
func GetAssetIndex(mcVersion string) ([]string, error) {
found := false
var data []byte
metadata, err := GetVersionMetadata(mcVersion)
if err != nil {
return []string{}, fmt.Errorf("Unable to pull manifest: %e\n", err)
}
dir, _ := os.UserConfigDir()
path := filepath.Join(dir, "FCLauncher", "assets", "indexes")
if _, err := os.Stat(filepath.Join(path, metadata.Assets+".json")); err == nil {
//cache file exists
if f, err := os.OpenFile(filepath.Join(path, metadata.Assets+".json"), os.O_RDONLY, 0755); err == nil {
defer f.Close()
if data, err = io.ReadAll(f); err == nil {
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) == metadata.AssetIndex.Sha1 {
found = true
}
}
}
}
if !found {
//no cache file
data = []byte{}
resp, err := http.Get(metadata.AssetIndex.Url)
if err != nil {
return []string{}, fmt.Errorf("Unable to pull asset index: %e\n", err)
}
defer resp.Body.Close()
data, err = io.ReadAll(resp.Body)
if err != nil {
return []string{}, fmt.Errorf("Unable to pull asset index: %e\n", err)
}
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) != metadata.AssetIndex.Sha1 {
return []string{}, fmt.Errorf("Sha mismatch!\n")
}
os.MkdirAll(path, 0755)
f, _ := os.OpenFile(filepath.Join(path, metadata.Assets+".json"), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
f.Write(data)
}
//at this point data is populated
var index map[string]interface{}
json.Unmarshal(data, &index)
index = index["objects"].(map[string]interface{})
hashes := []string{}
for _, val := range index {
index_map := val.(map[string]interface{})
hashes = append(hashes, index_map["hash"].(string))
}
return hashes, nil
}
func DownloadAssets(mcVersion string, assetPath string, a App) error {
a.Status("Downloading Minecraft Assets")
assets, err := GetAssetIndex(mcVersion)
if err != nil {
return fmt.Errorf("Unable to get asset index: %e\n", err)
}
metadata, err := GetVersionMetadata(mcVersion)
if err != nil {
return fmt.Errorf("Unable to get version metadata: %e\n", err)
}
total := metadata.AssetIndex.TotalSize
downloaded := 0
wruntime.EventsEmit(a.Ctx, "download", downloaded, total)
for _, hash := range assets {
if _, err := os.Stat(filepath.Join(assetPath, "objects", hash[:2], hash)); err == nil {
f, _ := os.OpenFile(filepath.Join(assetPath, "objects", hash[:2], hash), os.O_RDONLY, 0755)
defer f.Close()
data, _ := io.ReadAll(f)
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) == hash {
downloaded += len(data)
wruntime.EventsEmit(a.Ctx, "download", downloaded, total)
continue
}
}
resp, err := http.Get(fmt.Sprintf("https://resources.download.minecraft.net/%s/%s", hash[:2], hash))
if err != nil {
return fmt.Errorf("unable to download assets: %e\n", err)
}
defer resp.Body.Close()
buff := new(bytes.Buffer)
for {
count, err := io.CopyN(buff, resp.Body, BlockSize)
if err == io.EOF {
downloaded += int(count)
break
}
if err != nil {
return fmt.Errorf("Error Downloading assets: %e\n", err)
}
downloaded += int(count)
wruntime.EventsEmit(a.Ctx, "download", downloaded, total)
}
data := buff.Bytes()
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) != hash {
return fmt.Errorf("unable to download assets: Sha1 Mismatch\n")
}
err = os.MkdirAll(filepath.Join(assetPath, "objects", hash[:2]), 0755)
if err != nil {
return fmt.Errorf("unable to download assets: Unable to create directory\n")
}
f, err := os.OpenFile(filepath.Join(assetPath, "objects", hash[:2], hash), os.O_CREATE|os.O_RDWR, 0755)
if err != nil {
return fmt.Errorf("unable to download assets: Unable to open file\n")
}
defer f.Close()
f.Write(data)
wruntime.EventsEmit(a.Ctx, "download", downloaded, total)
}
wruntime.EventsEmit(a.Ctx, "download_complete")
return nil
}
func DownloadLibraries(mcVersion string, libPath string, a App) error {
metadata, err := GetVersionMetadata(mcVersion)
if err != nil {
return fmt.Errorf("unable to pull version metadata: %e\n", err)
}
for _, lib := range metadata.Libraries {
a.Status(fmt.Sprintf("Checking %s\n", lib.Name))
if _, err := os.Stat(filepath.Join(libPath, lib.Downloads.Artifact.Path)); err == nil {
f, _ := os.OpenFile(filepath.Join(libPath, lib.Downloads.Artifact.Path), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
data, _ := io.ReadAll(f)
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) == lib.Downloads.Artifact.Sha1 {
continue
}
}
a.Status(fmt.Sprintf("Downloading %s\n", lib.Name))
resp, err := http.Get(lib.Downloads.Artifact.Url)
if err != nil {
return fmt.Errorf("unable to download libs: %e\n", err)
}
defer resp.Body.Close()
buff := new(bytes.Buffer)
downloaded := 0
for {
count, err := io.CopyN(buff, resp.Body, BlockSize)
if err == io.EOF {
downloaded += int(count)
break
}
if err != nil {
return fmt.Errorf("Error Downloading libs: %e\n", err)
}
downloaded += int(count)
wruntime.EventsEmit(a.Ctx, "download", downloaded, resp.ContentLength)
}
data := buff.Bytes()
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) != lib.Downloads.Artifact.Sha1 {
return fmt.Errorf("unable to download libs: Sha1 Mismatch\n")
}
path := ""
tokens := strings.Split(lib.Downloads.Artifact.Path, "/")
for ind, token := range tokens {
if ind != len(tokens)-1 {
path = filepath.Join(path, token)
}
}
err = os.MkdirAll(filepath.Join(libPath, path), 0755)
if err != nil {
return fmt.Errorf("unable to download libs: Unable to create directory\n")
}
f, err := os.OpenFile(filepath.Join(libPath, lib.Downloads.Artifact.Path), os.O_CREATE|os.O_RDWR, 0755)
if err != nil {
return fmt.Errorf("unable to download libs: Unable to open file\n")
}
defer f.Close()
f.Write(data)
wruntime.EventsEmit(a.Ctx, "download_complete")
}
return nil
}
func InstallNatives(mcVersion string, nativesDir string){
metadata, _ := GetVersionMetadata(mcVersion)
for _, lib := range metadata.Libraries {
if lib.Natives != nil {
glob := lib.Natives[runtime.GOOS]
fmt.Printf("glob is: %s\n", glob)
if lib.Downloads.Classifiers[glob] != nil {
artifact := lib.Downloads.Classifiers[glob].(map[string]interface{})
resp, _ := http.Get(artifact["url"].(string))
defer resp.Body.Close()
zr := zipstream.NewReader(resp.Body)
for {
e, err := zr.GetNextEntry()
if err == io.EOF {
break
}
if e.IsDir() {
os.MkdirAll(filepath.Join(nativesDir, e.Name), 0755)
} else {
zc, _ := e.Open()
f, _ := os.OpenFile(filepath.Join(nativesDir, e.Name), os.O_CREATE|os.O_RDWR, 0755)
defer zc.Close()
defer f.Close()
io.Copy(f, zc)
}
}
}
}
}
}
func DownloadExecutable(mcVersion string, binDir string, a App) error {
metadata, err := GetVersionMetadata(mcVersion)
if err != nil {
return fmt.Errorf("unable to pull version metadata: %e\n", err)
}
a.Status(fmt.Sprintf("Checking Minecraft %s Executable\n", mcVersion))
if _, err := os.Stat(filepath.Join(binDir, mcVersion, "client.jar")); err == nil {
f, _ := os.OpenFile(filepath.Join(binDir, mcVersion, "client.jar"), os.O_CREATE|os.O_RDWR, 0755)
defer f.Close()
data, _ := io.ReadAll(f)
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) == metadata.Downloads["client"].Sha1 {
return nil
}
}
a.Status(fmt.Sprintf("Downloading Minecraft %s Executable\n", mcVersion))
resp, err := http.Get(metadata.Downloads["client"].Url)
if err != nil {
return fmt.Errorf("unable to download executable: %e\n", err)
}
defer resp.Body.Close()
buff := new(bytes.Buffer)
downloaded := 0
for {
count, err := io.CopyN(buff, resp.Body, BlockSize)
if err == io.EOF {
downloaded += int(count)
break
}
if err != nil {
return fmt.Errorf("Error Downloading executable: %e\n", err)
}
downloaded += int(count)
wruntime.EventsEmit(a.Ctx, "download", downloaded, resp.ContentLength)
}
data := buff.Bytes()
sha := sha1.Sum(data)
if hex.EncodeToString(sha[:20]) != metadata.Downloads["client"].Sha1 {
return fmt.Errorf("unable to download executable: Sha1 Mismatch\n")
}
err = os.MkdirAll(filepath.Join(binDir, mcVersion), 0755)
if err != nil {
return fmt.Errorf("unable to download executable: Unable to create directory\n")
}
f, err := os.OpenFile(filepath.Join(binDir, mcVersion, "client.jar"), os.O_CREATE|os.O_RDWR, 0755)
if err != nil {
return fmt.Errorf("unable to download executable: Unable to open file\n")
}
defer f.Close()
f.Write(data)
wruntime.EventsEmit(a.Ctx, "download_complete")
return nil
}
func GetBaseLaunchArgs(mcVersion string, libDir string, binDir string, assetDir string, gameDir string) ([]string, error) {
args := []string{}
metadata, err := GetVersionMetadata(mcVersion)
if err != nil {
return args, fmt.Errorf("GetLaunchArgs: %e\n", err)
}
searchArgs := []string{}
if metadata.MinecraftArguments != "" {
searchArgs = strings.Split(metadata.MinecraftArguments, " ")
} else {
searchArgs = metadata.Arguments.Game
}
args = append(args, "-Djava.library.path="+filepath.Join(gameDir, "natives"))
args = append(args, "-Djna.tmpdir="+filepath.Join(gameDir, "natives"))
args = append(args, "-Dorg.lwjgl.system.SharedLibraryExtractpath="+filepath.Join(gameDir, "natives"))
args = append(args, "-Dio.netty.native.workdir="+filepath.Join(gameDir, "natives"))
args = append(args, "-Xms512m")
args = append(args, "-Xmx1024m")
args = append(args, "-cp")
arg := ""
for _, lib := range metadata.Libraries {
arg += filepath.Join(libDir, lib.Downloads.Artifact.Path) + ":"
}
arg += filepath.Join(binDir, mcVersion, "client.jar")
args = append(args, arg)
args = append(args, metadata.MainClass)
for _, val := range searchArgs {
switch val {
case "${version_name}":
args = append(args, mcVersion)
case "${game_directory}":
args = append(args, gameDir)
case "${assets_root}":
args = append(args, assetDir)
case "${clientid}":
args = append(args, client_id)
case "${version_type}":
args = append(args, metadata.Type)
case "${user_type}":
args = append(args, "mojang")
case "${assets_index_name}":
args = append(args, metadata.Assets)
case "${user_properties}":
args = append(args, "null")
default:
args = append(args, val)
}
}
return args, nil
}
func GetOfflineLaunchArgs(mcVersion string, libDir string, binDir string, assetDir string, gameDir string, playerName string) ([]string, error) {
args, err := GetBaseLaunchArgs(mcVersion, libDir, binDir, assetDir, gameDir)
if err != nil {
return []string{}, fmt.Errorf("GatOfflineLaunchArgs: %e\n", err)
}
for ind, val := range args {
switch val{
case "${auth_player_name}":
args[ind] = playerName
case "${auth_uuid}":
args[ind] = "null"
case "${auth_access_token}":
args[ind] = "null"
case "${auth_xuid}":
args[ind] = "null"
default:
}
}
return args, nil
}
func GetOnlineLaunchArgs(mcVersion string, libDir string, binDir string, assetDir string, gameDir string, playerName string) ([]string, error) {
args, err := GetBaseLaunchArgs(mcVersion, libDir, binDir, assetDir, gameDir)
if err != nil {
return []string{}, fmt.Errorf("GatOfflineLaunchArgs: %e\n", err)
}
for ind, val := range args {
switch val{
case "${auth_player_name}":
args[ind] = val
case "${auth_uuid}":
args[ind] = val
case "${auth_access_token}":
args[ind] = val
case "${auth_xuid}":
args[ind] = val
default:
}
}
return args, nil
}