Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
View Source
var Account = &cli.Command{ Name: "account", Usage: "Manage Minecraft accounts", Commands: []*cli.Command{ AccountLogin, AccountLogout, AccountLs, }, }
View Source
var AccountLogin = &cli.Command{ Name: "login", Usage: "Add a new account", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "local", Usage: "create an offline account", }, &cli.StringFlag{ Name: "username", Usage: "username for offline account", }, }, Action: func(ctx context.Context, cmd *cli.Command) error { l := GetLauncher(ctx) localFlag := cmd.Bool("local") if localFlag { usernameFlag := cmd.String("username") if usernameFlag == "" { return fmt.Errorf("--local set but --username not set or empty") } if _, err := l.CreateOfflineAccount(usernameFlag); err != nil { return err } return nil } cb := &UILoginCallbacks{ Writer: cmd.ErrWriter, IsTerm: false, } if f, ok := cmd.ErrWriter.(interface{ Fd() uintptr }); ok { cb.IsTerm = term.IsTerminal(int(f.Fd())) } ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) defer cancel() mcAuth, err := authentication.AuthenticateMSA(ctx, l.HttpClient, cb) if err != nil { if errors.Is(err, context.Canceled) { return ErrExit(1) } return err } auth := launcher.Auth{ Name: mcAuth.Profile.Name, Uuid: mcAuth.Profile.Id, Xuid: mcAuth.Xuid, RefreshToken: mcAuth.RefreshToken, } authConfig := launcher.AccountConfig{} if len(auth.Uuid) == 32 { parts := []string{ auth.Uuid[0:8], auth.Uuid[8:12], auth.Uuid[12:16], auth.Uuid[16:20], auth.Uuid[20:32], } auth.Uuid = strings.Join(parts, "-") } account, err := l.CreateOnlineAccount(auth, &authConfig) if err != nil { return err } fmt.Fprintf(cmd.Writer, "Username: %s\n", account.Username) fmt.Fprintf(cmd.Writer, "UUID : %s\n", account.Uuid) return nil }, }
View Source
var AccountLogout = &cli.Command{ Name: "logout", Usage: "Remove an account", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "force", Aliases: []string{"f"}, }, &cli.BoolFlag{ Name: "verbose", Aliases: []string{"v"}, }, }, Action: func(ctx context.Context, cmd *cli.Command) error { l := GetLauncher(ctx) if cmd.Args().Len() == 0 { return cli.ShowSubcommandHelp(cmd) } forceFlag := cmd.Bool("force") verboseFlag := cmd.Bool("verbose") for _, accountName := range cmd.Args().Slice() { if err := l.DeleteAccount(accountName); err != nil { if forceFlag { continue } return err } if verboseFlag { fmt.Fprintf(cmd.Writer, "removed account %q\n", accountName) } } return nil }, }
View Source
var AccountLs = &cli.Command{ Name: "ls", Usage: "List accounts", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "quiet", Aliases: []string{"q"}, }, }, Action: func(ctx context.Context, cmd *cli.Command) error { l := GetLauncher(ctx) quietFlag := cmd.Bool("quiet") results, err := l.GetAccounts() if err != nil { return err } w := tabwriter.NewWriter(cmd.Writer, 1, 4, 3, ' ', 0) if !quietFlag { fmt.Fprintf(w, "USERNAME\tUUID\tTYPE\tENCRYPTION\tEXPIRES\tDEFAULT\n") } for _, res := range results { if res.Error != nil { path, err := filepath.Rel(l.BaseDir, res.Path) if err != nil { path = res.Path } fmt.Fprintf(cmd.ErrWriter, "warning: failed to load %q: %v\n", path, res.Error) } else { account := &res.Account if quietFlag { fmt.Fprintln(w, account.Username) } else { isDefault := "" fmt.Fprintf(w, "%s\t%s\toffline\tnone\tnever\t%s\n", account.Username, account.Uuid, isDefault) } } } w.Flush() return nil }, }
View Source
var Instance = &cli.Command{ Name: "instance", Usage: "Manage game intances", Commands: []*cli.Command{ InstanceCreate, InstanceList, InstanceMod, InstanceRemove, InstanceRename, InstanceSetAccount, InstanceShow, InstanceStart, }, }
View Source
var InstanceCreate = &cli.Command{ Name: "create", Usage: "Create a new instance", ArgsUsage: "INSTANCE", Flags: []cli.Flag{ &cli.StringFlag{ Name: "version", Value: "latest", Usage: "choose Minecraft version", }, &cli.StringFlag{ Name: "loader", Usage: "choose mod loader", }, &cli.StringFlag{ Name: "java", Usage: "choose alternative Java path", }, &cli.StringFlag{ Name: "account", Usage: "choose alternative account", }, &cli.BoolFlag{ Name: "fetch", Usage: "fetch assets and libraries", }, }, Action: func(ctx context.Context, cmd *cli.Command) error { if cmd.Args().Len() != 1 { return cli.ShowSubcommandHelp(cmd) } instanceArg := cmd.Args().Get(0) loaderFlag := cmd.String("loader") versionFlag := cmd.String("version") javaFlag := cmd.String("java") loaderTypeString, loaderVersion, _ := strings.Cut(loaderFlag, "@") var loaderType launcher.ModLoaderType if err := loaderType.UnmarshalText([]byte(loaderTypeString)); err != nil { return err } loader := &launcher.ModLoader{ Type: loaderType, Version: loaderVersion, } l := GetLauncher(ctx) ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() manifest, err := l.GetManifest() if err != nil { return err } var versionId string if versionFlag == "latest" { ver, ok := manifest.GetLatestRelease() if !ok { return fmt.Errorf("no latest release available") } versionId = ver.Id } else { versionId = versionFlag } version, err := l.GetVersion(ctx, versionId) if err != nil { return err } instance, err := l.CreateInstanceEx(instanceArg, loaderFlag, version, func(config *launcher.InstanceConfig) error { config.JavaPath = javaFlag config.AdditionalClasspath = make([]string, 0) config.ModLoader = loader config.Mods = make(map[string]launcher.InstanceConfigMod) return nil }) if err != nil { return err } if cmd.Bool("fetch") { var progress ProgressBarReporter if err := l.DownloadLibraries(ctx, instance, &progress); err != nil { return err } if err := l.DownloadAssets(ctx, instance, &progress); err != nil { return err } } return nil }, }
View Source
var InstanceList = &cli.Command{ Name: "ls", Aliases: []string{"list"}, Usage: "List available instances", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "quiet", Aliases: []string{"q"}, Usage: "only print instance names", }, }, Action: func(ctx context.Context, cmd *cli.Command) error { l := GetLauncher(ctx) results, err := l.GetInstances() if err != nil { return err } quietFlag := cmd.Bool("quiet") w := tabwriter.NewWriter(cmd.Writer, 1, 4, 3, ' ', 0) if !quietFlag { fmt.Fprintf(w, "INSTANCE\tVERSION\tLOADER\tACCOUNT\n") } for _, res := range results { instanceName := filepath.Base(res.Path) if res.Error != nil { path, err := filepath.Rel(l.BaseDir, res.Path) if err != nil { path = res.Path } fmt.Fprintf(cmd.ErrWriter, "warning: failed to load %q: %v\n", path, res.Error) } else { config := &res.Instance if quietFlag { fmt.Fprintln(w, instanceName) } else { fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", instanceName, config.Version, "vanilla", config.Account) } } } w.Flush() return nil }, }
View Source
var InstanceMod = &cli.Command{ Name: "mod", Usage: "Manage instance mods", Commands: []*cli.Command{ InstanceModAdd, InstanceModLs, InstanceModPin, InstanceModRm, }, }
View Source
var InstanceModAdd = &cli.Command{ Name: "add", Usage: "Install mods", ArgsUsage: "INSTANCE MOD[@<VERSION> [MOD[@<VERSION>]...]", Action: func(ctx context.Context, cmd *cli.Command) error { panic("") }, }
View Source
var InstanceModLs = &cli.Command{ Name: "ls", Aliases: []string{"list"}, Usage: "List installed mods", ArgsUsage: "INSTANCE", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "quiet", Aliases: []string{"q"}, Usage: "only print mod \"id@version\"", }, }, Action: func(ctx context.Context, cmd *cli.Command) error { if cmd.Args().Len() != 1 { return cli.ShowSubcommandHelp(cmd) } instanceArg := cmd.Args().Get(0) l := GetLauncher(ctx) instance, err := l.GetInstance(instanceArg) if err != nil { return err } mods, err := l.GetMods(instance, instance.Config.ModLoader.Type) if err != nil { return err } w := tabwriter.NewWriter(cmd.Writer, 1, 4, 3, ' ', 0) fmt.Fprintln(w, "ID\tVERSION\tLOADER") for _, mod := range mods { fmt.Fprintf(w, "%s\t%s\t%s\n", mod.ModID(), mod.ModVersion(), mod.ModLoader()) } w.Flush() return nil }, }
View Source
var InstanceModPin = &cli.Command{ Name: "pin", Usage: "Assign specific mod versions", ArgsUsage: "INSTANCE MOD[@<VERSION> [MOD[@<VERSION>]...]", Action: func(ctx context.Context, cmd *cli.Command) error { return nil }, }
View Source
var InstanceModRm = &cli.Command{ Name: "rm", Aliases: []string{"remove"}, Usage: "Uninstall mods", ArgsUsage: "INSTANCE MOD [MOD...]", Action: func(ctx context.Context, cmd *cli.Command) error { return nil }, }
View Source
var InstanceRemove = &cli.Command{ Name: "rm", Aliases: []string{"remove"}, Usage: "Delete an existing instance", ArgsUsage: "INSTANCE [INSTANCES...]", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "force", Aliases: []string{"f"}, Usage: "ignore non-existing instances", }, &cli.BoolFlag{ Name: "verbose", Aliases: []string{"v"}, Usage: "print extra messages", }, }, Action: func(ctx context.Context, cmd *cli.Command) error { if cmd.Args().Len() < 1 { return cli.ShowSubcommandHelp(cmd) } l := GetLauncher(ctx) hasErr := false forceFlag := cmd.Bool("force") verboseFlag := cmd.Bool("verbose") for _, instance := range cmd.Args().Slice() { err := l.DeleteInstance(instance) if err != nil { if errors.Is(err, os.ErrNotExist) || !forceFlag { hasErr = true fmt.Fprintf(cmd.ErrWriter, "cannot delete %q: %v\n", instance, err) } } else { if verboseFlag { fmt.Printf("removed instance %q\n", instance) } } } if hasErr { return ErrExit(1) } return nil }, }
View Source
var InstanceRename = &cli.Command{ Name: "rename", Usage: "Rename an existing instance", ArgsUsage: "OLD NEW", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "verbose", Aliases: []string{"v"}, Usage: "", }, }, Action: func(ctx context.Context, cmd *cli.Command) error { l := GetLauncher(ctx) if cmd.Args().Len() != 2 { return cli.ShowSubcommandHelp(cmd) } verboseFlag := cmd.Bool("verbose") oldArg := cmd.Args().Get(0) newArg := cmd.Args().Get(1) if err := l.RenameInstance(oldArg, newArg); err != nil { if errors.Is(err, os.ErrNotExist) { return fmt.Errorf("%s: no such instance", oldArg) } return err } if verboseFlag { fmt.Printf("%q -> %q\n", oldArg, newArg) } return nil }, }
View Source
var InstanceSetAccount = &cli.Command{ Name: "set-account", Usage: "Set account for an instance", ArgsUsage: "INSTANCE ACCOUNT", Action: func(ctx context.Context, cmd *cli.Command) error { if cmd.Args().Len() != 2 { return cli.ShowSubcommandHelp(cmd) } instanceArg := cmd.Args().Get(0) accountArg := cmd.Args().Get(1) l := GetLauncher(ctx) ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) defer cancel() instance, err := l.LoadInstance(ctx, instanceArg) if err != nil { if errors.Is(err, context.Canceled) { return ErrExit(1) } return err } if _, err := l.GetAccount(accountArg); err != nil { return err } err = l.EditInstance(instance, func(conf *launcher.InstanceConfig) error { conf.Account = accountArg return nil }) if err != nil { return err } return nil }, }
View Source
var InstanceShow = &cli.Command{ Name: "show", Usage: "Print information about an instance", ArgsUsage: "INSTANCE", Action: func(ctx context.Context, cmd *cli.Command) error { if cmd.Args().Len() != 1 { return cli.ShowSubcommandHelp(cmd) } l := GetLauncher(ctx) ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) defer cancel() instanceArg := cmd.Args().Get(0) instance, err := l.LoadInstance(ctx, instanceArg) if err != nil { return err } fmt.Printf("Path: %s\n", instance.Dir) fmt.Printf("Version: %s\n", instance.Config.Version) return nil }, }
View Source
var InstanceStart = &cli.Command{ Name: "start", Usage: "Start an instance", ArgsUsage: "INSTANCE", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "no-libs", Usage: "don't attempt to fetch assets", }, &cli.BoolFlag{ Name: "no-assets", Usage: "don't attempt to fetch libraries", }, &cli.StringFlag{ Name: "account", Usage: "use alternative account", }, }, Action: func(ctx context.Context, cmd *cli.Command) error { if cmd.Args().Len() != 1 { return cli.ShowSubcommandHelp(cmd) } instanceArg := cmd.Args().Get(0) accountFlag := cmd.String("account") noAssetsFlag := cmd.Bool("no-assets") noLibsFlag := cmd.Bool("no-libs") l := GetLauncher(ctx) ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) defer cancel() instance, err := l.LoadInstance(ctx, instanceArg) if err != nil { return err } var account *launcher.Account if accountFlag != "" { account, err = l.GetAccount(accountFlag) } else if instance.Config.Account != "" { account, err = l.GetAccount(instance.Config.Account) } else { account, err = l.GetDefaultAccount() } if err != nil { return err } var auth *launcher.Auth if l.Offline || account.IsOffline() { auth = &launcher.Auth{ Name: account.Username, Uuid: account.Uuid, Xuid: account.Xuid, } } else { auth, err = l.AuthenticateAccount(account) if err != nil { return err } } var reporter ProgressBarReporter if !l.Offline && !noLibsFlag { if err := l.DownloadLibraries(ctx, instance, &reporter); err != nil { return fmt.Errorf("failed to download libraries for instance %q: %w", instanceArg, err) } } if !l.Offline && !noAssetsFlag { if err := l.DownloadAssets(ctx, instance, &reporter); err != nil { return fmt.Errorf("failed to download assets for instance %q: %w", instanceArg, err) } } command, err := l.CreateCommand(ctx, instance, auth) if err != nil { return fmt.Errorf("failed to create command for instance %q: %w\n", instanceArg, err) } if err := command.Start(); err != nil { return fmt.Errorf("failed to start process for instance %q: %w\n", instanceArg, err) } if err := command.Wait(); err != nil { if exitErr, ok := err.(*exec.ExitError); ok { return exitErr } else { return fmt.Errorf("failed to wait process for instance %q: %w", instanceArg, err) } } return nil }, }
View Source
var Java = &cli.Command{ Name: "java", Usage: "Manage Java installations", Commands: []*cli.Command{ JavaLs, }, }
View Source
var JavaLs = &cli.Command{ Name: "ls", Usage: "List available Java installation paths", Action: func(_ context.Context, cmd *cli.Command) error { javaPaths := java.GetJavaPaths() w := tabwriter.NewWriter(cmd.Writer, 1, 4, 3, ' ', 0) fmt.Fprintf(w, "SOURCE\tVERSION\tVENDOR\n") for _, path := range javaPaths { value := path.Value version := path.Version vendor := path.Vendor if version == "" { version = "unknown" } if vendor == "" { vendor = "unknown" } fmt.Fprintf(w, "%s\t%s\t%s\n", value, version, vendor) } w.Flush() return nil }, }
View Source
var Minecraft = &cli.Command{ Name: "minecraft", Commands: []*cli.Command{ MinecraftLs, MinecraftUpdate, }, }
View Source
var MinecraftLs = &cli.Command{ Name: "ls", Flags: []cli.Flag{ &cli.StringSliceFlag{ Name: "type", Value: []string{"release"}, }, &cli.BoolFlag{ Name: "quiet", Aliases: []string{"q"}, Usage: "only print version ids", }, }, Action: func(ctx context.Context, cmd *cli.Command) error { l := GetLauncher(ctx) quietFlag := cmd.Bool("quiet") typeFlag := cmd.StringSlice("type") versionTypes := make(map[string]struct{}, 4) all := slices.Contains(typeFlag, "all") if !all { for _, versionType := range typeFlag { switch versionType { case "release": versionTypes["release"] = struct{}{} case "snapshot": versionTypes["snapshot"] = struct{}{} case "beta": versionTypes["old_beta"] = struct{}{} case "alpha": versionTypes["old_alpha"] = struct{}{} } } } manifest, err := l.GetManifest() if err != nil { if errors.Is(err, os.ErrNotExist) { return fmt.Errorf("index does not exist, try running `muxi minecraft update`") } return err } output := cmd.Writer if f, ok := output.(interface{ Fd() uintptr }); ok { if term.IsTerminal(int(f.Fd())) { pager, err := ui.SpawnPager(context.Background()) if err != nil { fmt.Fprintf(cmd.ErrWriter, "warning: failed to spawn pager: %v\n", err) } else { defer pager.Close() output = pager } } } w := tabwriter.NewWriter(output, 1, 4, 3, ' ', 0) defer w.Flush() if !quietFlag { fmt.Fprintln(w, "ID\tTYPE\tRELEASED\tHASH") } for _, version := range manifest.Versions { if !all { if _, ok := versionTypes[version.Type]; !ok { continue } } if quietFlag { fmt.Println(version.Id) } else { timeOffset := timeOffsetConfig.Format(version.ReleaseTime) var versionType string switch version.Type { case "release", "snapshot": versionType = version.Type case "old_beta": versionType = "beta" case "old_alpha": versionType = "alpha" } fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", version.Id, versionType, timeOffset, version.Sha1) } } return nil }, }
View Source
var MinecraftUpdate = &cli.Command{ Name: "update", Usage: "update version index", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "force", Aliases: []string{"f"}, }, }, Action: func(ctx context.Context, cmd *cli.Command) error { l := GetLauncher(ctx) if l.Offline { return fmt.Errorf("offline mode enabled") } forceFlag := cmd.Bool("force") ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) defer cancel() currentManifest, err := l.GetManifest() if err != nil { currentManifest = new(vanilla.Manifest) } newManifest, err := l.UpdateManifest(ctx) if err != nil { return err } type summary struct { added uint modified uint } type versionInfo struct { hash string summary *summary } versionMap := make(map[string]string, len(newManifest.Versions)) var releases summary var snapshots summary var other summary if !forceFlag { for _, version := range currentManifest.Versions { versionMap[version.Id] = version.Sha1 } } for _, version := range newManifest.Versions { var sum *summary switch version.Type { case "release": sum = &releases case "snapshot": sum = &snapshots default: sum = &other } hash, ok := versionMap[version.Id] switch { case !ok: sum.added += 1 case hash != version.Sha1: sum.modified += 1 } delete(versionMap, version.Id) } changed := false if releases.added != 0 || releases.modified != 0 { fmt.Printf("releases +%d ~%d\n", releases.added, releases.modified) changed = true } if snapshots.added != 0 || snapshots.modified != 0 { fmt.Printf("snapshots +%d ~%d\n", snapshots.added, snapshots.modified) changed = true } if other.added != 0 || other.modified != 0 { fmt.Printf("other +%d ~%d\n", other.added, other.modified) changed = true } if !changed { fmt.Println("index up to date") } return nil }, }
View Source
var Root = &cli.Command{ Name: "muxi", Usage: "Unofficial launcher for Minecraft: Java Edition", Flags: []cli.Flag{ &cli.StringFlag{ Name: "home", }, &cli.DurationFlag{ Name: "http-timeout", Usage: "timeout for HTTP requests in seconds", Value: 30 * time.Second, }, &cli.UintFlag{ Name: "asset-jobs", Usage: "concurrent jobs for asset downloads", Value: 64, }, &cli.UintFlag{ Name: "library-jobs", Usage: "concurrent jobs for library downloads", Value: 32, }, &cli.BoolFlag{ Name: "offline", Usage: "never try accessing network", }, &cli.BoolFlag{ Name: "staging", Usage: "use staging APIs if available (FOR TESTING ONLY)", Hidden: true, }, &cli.BoolFlag{ Name: "version", Usage: "displays version and exit", }, }, EnableShellCompletion: true, Commands: []*cli.Command{ Instance, Account, Java, Minecraft, Version, }, Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) { if cmd.Bool("version") { if err := Version.Action(ctx, Version); err != nil { return ctx, err } return ctx, ErrExit(0) } hc := &http.Client{ Timeout: cmd.Duration("http-timeout"), } launcherHome := cmd.String("home") if launcherHome == "" { home, err := getLauncherHome() if err != nil { return ctx, err } launcherHome = home } l := &launcher.Launcher{ BaseDir: launcherHome, HttpClient: hc, LibraryJobs: cmd.Uint("library-jobs"), AssetJobs: cmd.Uint("asset-jobs"), Modrinth: modrinth.Client{ HttpClient: hc, Staging: cmd.Bool("staging"), }, Offline: cmd.Bool("offline"), } return context.WithValue(ctx, launcherKey{}, l), nil }, }
Functions ¶
Types ¶
type ExitNoMessage ¶
type ExitNoMessage struct {
Code int
}
func (*ExitNoMessage) Error ¶
func (err *ExitNoMessage) Error() string
type ProgressBarReporter ¶
type ProgressBarReporter struct {
// contains filtered or unexported fields
}
func (*ProgressBarReporter) End ¶
func (r *ProgressBarReporter) End()
func (*ProgressBarReporter) Increment ¶
func (r *ProgressBarReporter) Increment()
func (*ProgressBarReporter) Start ¶
func (r *ProgressBarReporter) Start(stage string, max int)
type UILoginCallbacks ¶
func (*UILoginCallbacks) DeviceCode ¶
func (cb *UILoginCallbacks) DeviceCode(url string, code string, expiryMinutes int)
func (*UILoginCallbacks) Init ¶
func (cb *UILoginCallbacks) Init(steps int)
func (*UILoginCallbacks) Message ¶
func (cb *UILoginCallbacks) Message(text string)
func (*UILoginCallbacks) Step ¶
func (cb *UILoginCallbacks) Step(name string)
Click to show internal directories.
Click to hide internal directories.