package main import ( "bytes" "crypto/sha1" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "net/url" "os" "os/exec" "path/filepath" "runtime" "strings" "time" ) const client_id string = "9305aeb8-5ecb-4e7a-b28f-c33aefcfbd8d" type McProfile struct { Id string Name string } type Authentication struct { Access authenticationResp XboxAuth xboxAuthResponse XboxAPI xboxAuthResponse McAPI xboxAuthResponse McAuth McAuthResponse Profile McProfile } 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 int token_type string } type McVersionManifestEntry struct { Id string Type string Url string Time time.Time ReleaseTime time.Time Sha1 string ComplianceLevel int } type McLatestEntry struct { Release string Snapshot string } type McVersionManifest struct { Latest McLatestEntry Versions []McVersionManifestEntry } type McArguments struct { Game []string Jvm []string } type McAssetIndex struct { Id string Sha1 string Size int TotalSize int Url string } type McDownload struct { Sha1 string Size int Url string } type McDownloads struct { Client McDownload Client_mappings McDownload Server McDownload Server_mappings McDownload } type McJavaVersion struct { Component string MajorVersion int } type McLibraryArtifact struct { Path string Sha1 string Size int Url string } type McLibraryDownload struct { Artifact McLibraryArtifact } type McRuleOs struct { Name string Version string Arch string } type McRule struct { Action string Features map[string]bool Os McRuleOs } type McLibrary struct { Downloads McLibraryDownload Name string Rules []McRule } type McMetadata struct { Arguments McArguments AssetIndex McAssetIndex Assets string complianceLevel int Downloads McDownloads Id string JavaVersion McJavaVersion Libraries []McLibrary Logging interface{} MainClass string MinimumLauncherVersion int ReleaseTime time.Time Time time.Time Type string } func main() { auth := Authentication{} dir, _ := os.UserConfigDir() if _, err := os.Stat(filepath.Join(dir, "minecraft_test", "authentication.json")); err != nil { 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 { fmt.Printf("Device Auth Step: %s\n", err) return } defer resp.Body.Close() if resp.StatusCode != 200 { fmt.Printf("Device Auth Step: %v\n", resp.Status) return } data, _ := io.ReadAll(resp.Body) codeResp := devCodeResp{} json.Unmarshal(data, &codeResp) fmt.Println(codeResp.Message) ticker := time.NewTicker(time.Second * time.Duration(codeResp.Interval)) defer ticker.Stop() 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 { fmt.Printf("Authentication Request Error: %s\n", err) } defer resp.Body.Close() //if resp.StatusCode != 200 { // fmt.Printf("Authentication Request Error: %s\n", resp.Status) //} data, _ := io.ReadAll(resp.Body) authResp := authenticationResp{} json.Unmarshal(data, &authResp) if authResp.Error == "" { fmt.Printf("Authenticated!\n") auth.Access = authResp break } } } else { f, _ := os.OpenFile(filepath.Join(dir, "minecraft_test", "authentication.json"), os.O_CREATE|os.O_RDWR, 0755) data, _ := io.ReadAll(f) json.Unmarshal(data, &auth.Access) 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.Access.Refresh_token}, "scope": {"XboxLive.SignIn XboxLive.offline_access"}, }) if err != nil { fmt.Printf("Authentication Request Error: %s\n", err) } defer resp.Body.Close() //if resp.StatusCode != 200 { // fmt.Printf("Authentication Request Error: %s\n", resp.Status) //} data, _ = io.ReadAll(resp.Body) authResp := authenticationResp{} json.Unmarshal(data, &authResp) if authResp.Error == "" { fmt.Printf("Authenticated!\n") auth.Access = authResp } } os.MkdirAll(filepath.Join(dir, "minecraft_test"), 0755) f, _ := os.OpenFile(filepath.Join(dir, "minecraft_test", "authentication.json"), os.O_CREATE|os.O_RDWR, 0755) defer f.Close() data, _ := json.Marshal(auth.Access) f.Write(data) req, _ := json.Marshal(xboxAuthRequest{Properties: xboxAuthProperties{AuthMethod: "RPS", SiteName: "user.auth.xboxlive.com", RpsTicket: "d=" + auth.Access.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 { fmt.Printf("XboxLive Error: %s\n", err) } defer httpResp.Body.Close() if httpResp.StatusCode != 200 { fmt.Printf("XboxLive Error: %s\n", httpResp.Status) } d, _ := io.ReadAll(httpResp.Body) json.Unmarshal(d, &auth.XboxAuth) xstsData, _ := json.Marshal(XSTSRequest{Properties: XSTSProperties{SandboxId: "RETAIL", UserTokens: []string{auth.XboxAuth.Token}}, RelyingParty: "http://xboxlive.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 { fmt.Printf("XboxLive STS error: %s\n", err) } defer httpResp.Body.Close() if httpResp.StatusCode != 200 { fmt.Printf("XboxLive STS error: %s\n", httpResp.Status) } d, _ = io.ReadAll(httpResp.Body) json.Unmarshal(d, &auth.XboxAPI) xstsData, _ = json.Marshal(XSTSRequest{Properties: XSTSProperties{SandboxId: "RETAIL", UserTokens: []string{auth.XboxAuth.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 { fmt.Printf("Minecraft STS error: %s\n", err) } defer httpResp.Body.Close() if httpResp.StatusCode != 200 { fmt.Printf("Minecraft STS error: %s\n", httpResp.Status) } d, _ = io.ReadAll(httpResp.Body) json.Unmarshal(d, &auth.McAPI) if auth.McAPI.DisplayClaims.Xui[0].Uhs != auth.XboxAPI.DisplayClaims.Xui[0].Uhs { fmt.Printf("Warning: Inconsistant user hash!") } mcAuthData, _ := json.Marshal(McAuthRequest{Xtoken: "XBL3.0 x=" + auth.McAPI.DisplayClaims.Xui[0].Uhs + ";" + auth.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 { fmt.Printf("MC Auth Error: %s\n", err) } defer resp.Body.Close() if resp.StatusCode != 200 { fmt.Printf("MC Auth Error: %s\n", resp.Status) } d, _ = io.ReadAll(resp.Body) json.Unmarshal(d, &auth.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 "+auth.McAuth.Access_token) resp, _ = client.Do(httpReq) if err != nil { fmt.Printf("Error obtaining mc profile information: %s\n", err) } defer resp.Body.Close() data, _ = io.ReadAll(resp.Body) json.Unmarshal(data, &auth.Profile) resp, err = http.Get("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json") if err != nil { fmt.Printf("manifest error") } defer resp.Body.Close() data, _ = io.ReadAll(resp.Body) versionManifest := McVersionManifest{} json.Unmarshal(data, &versionManifest) resp, err = http.Get(versionManifest.Versions[0].Url) if err != nil { fmt.Printf("metadata error") } defer resp.Body.Close() data, _ = io.ReadAll(resp.Body) var metadata McMetadata json.Unmarshal(data, &metadata) os.MkdirAll(filepath.Join(dir, "minecraft_test", "assets", "indexes"), 0755) os.MkdirAll(filepath.Join(dir, "minecraft_test", "assets", "objects"), 0755) download_index := false if _, err := os.Stat(filepath.Join(dir, "minecraft_test", "assets", "indexes", metadata.Assets+".json")); err == nil { f, _ := os.OpenFile(filepath.Join(dir, "minecraft_test", "assets", "indexes", metadata.Assets+".json"), os.O_RDONLY, 0755) defer f.Close() data, _ = io.ReadAll(f) sha := sha1.Sum(data) if hex.EncodeToString(sha[:20]) != metadata.AssetIndex.Sha1 { download_index = true } } else { download_index = true } if download_index { resp, err = http.Get(metadata.AssetIndex.Url) if err != nil { fmt.Printf("asset index error") } defer resp.Body.Close() data, _ = io.ReadAll(resp.Body) sha := sha1.Sum(data) if hex.EncodeToString(sha[:20]) != metadata.AssetIndex.Sha1 { fmt.Printf("Error downloading asset index") return } f, _ = os.OpenFile(filepath.Join(dir, "minecraft_test", "assets", "indexes", metadata.Assets + ".json"), os.O_CREATE|os.O_RDWR, 0755) defer f.Close() f.Write(data) } var index map[string]interface{} json.Unmarshal(data, &index) index = index["objects"].(map[string]interface{}) for name, asset := range index { asset_map := asset.(map[string]interface{}) path := asset_map["hash"].(string)[:2] + "/" + asset_map["hash"].(string) if _, err = os.Stat(filepath.Join(dir, "minecraft_test", "assets", "objects", path)); err == nil { f, _ := os.OpenFile(filepath.Join(dir, "minecraft_test", "assets", "objects", path), os.O_RDONLY, 0755) data, _ := io.ReadAll(f) sha := sha1.Sum(data) if hex.EncodeToString(sha[:20]) == asset_map["hash"].(string) { continue } } fmt.Printf("Downloading %s: ", name) resp, err = http.Get(fmt.Sprintf("https://resources.download.minecraft.net/%s/%s", asset_map["hash"].(string)[:2], asset_map["hash"].(string))) if err != nil { fmt.Printf("Error\n") return } defer resp.Body.Close() data, _ = io.ReadAll(resp.Body) sha := sha1.Sum(data) if hex.EncodeToString(sha[:20]) != asset_map["hash"].(string) { fmt.Printf("Sha1 Mismatch\n") return } fmt.Printf("Ok\n") os.MkdirAll(filepath.Join(dir, "minecraft_test", "assets", "objects", asset_map["hash"].(string)[:2]), 0755) f, _ := os.OpenFile(filepath.Join(dir, "minecraft_test", "assets", "objects", path), os.O_CREATE|os.O_RDWR, 0755) defer f.Close() f.Write(data) } for _, lib := range metadata.Libraries { if _, err = os.Stat(filepath.Join(dir, "minecraft_test", "lib", lib.Downloads.Artifact.Path)); err == nil { f, _ := os.OpenFile(filepath.Join(dir, "minecraft_test", "lib", lib.Downloads.Artifact.Path), os.O_RDONLY, 0755) defer f.Close() data, _ := io.ReadAll(f) sha := sha1.Sum(data) if hex.EncodeToString(sha[:20]) == lib.Downloads.Artifact.Sha1 { continue } } fmt.Printf("Downloading %s: ", lib.Name) resp, err = http.Get(lib.Downloads.Artifact.Url) if err != nil { fmt.Printf("Error\n") return } defer resp.Body.Close() data, _ = io.ReadAll(resp.Body) sha := sha1.Sum(data) if hex.EncodeToString(sha[:20]) != lib.Downloads.Artifact.Sha1 { fmt.Printf("Sha1 Mismatch\n") return } path := "" tokens := strings.Split(lib.Downloads.Artifact.Path, "/") for ind, token := range tokens { if ind != len(tokens)-1 { path = filepath.Join(path, token) } } os.MkdirAll(filepath.Join(dir, "minecraft_test", "lib", path), 0755) f, _ := os.OpenFile(filepath.Join(dir, "minecraft_test", "lib", lib.Downloads.Artifact.Path), os.O_CREATE|os.O_RDWR, 0755) defer f.Close() f.Write(data) fmt.Printf("OK\n") } download := false if os.Stat(filepath.Join(dir, "minecraft_test", "bin", metadata.Id, "client.jar")); err == nil { f, _ := os.OpenFile(filepath.Join(dir, "minecraft_test", "bin", metadata.Id, "client.jar"), os.O_RDONLY, 0755) defer f.Close() data, _ := io.ReadAll(f) sha := sha1.Sum(data) if hex.EncodeToString(sha[:20]) != metadata.Downloads.Client.Sha1 { download = true } } else { download = true } if download { fmt.Printf("Downloading minecraft.jar: ") req, err := http.Get(metadata.Downloads.Client.Url) if err != nil { fmt.Printf("ERROR\n") return } defer req.Body.Close() data, _ := io.ReadAll(req.Body) sha := sha1.Sum(data) if hex.EncodeToString(sha[:20]) != metadata.Downloads.Client.Sha1 { fmt.Printf("Sha Mismatch\n") return } fmt.Printf("OK\n") os.MkdirAll(filepath.Join(dir, "minecraft_test", "bin", metadata.Id), 0755) f, _ := os.OpenFile(filepath.Join(dir, "minecraft_test", "bin", metadata.Id, "client.jar"), os.O_CREATE|os.O_RDWR, 0755) defer f.Close() f.Write(data) } os.MkdirAll(filepath.Join(dir, "minecraft_test", "minecraft"), 0755) args := []string{} args = append(args, "-Xms512m") args = append(args, "-Xmx1024m") for _, val := range metadata.Arguments.Jvm { val = strings.ReplaceAll(val, "${natives_directory}", filepath.Join(dir, "minecraft_test", "minecraft", "natives")) switch val { case "-cp": args = append(args, val) case "${classpath}": arg := "" for _, lib := range metadata.Libraries { if lib.Rules != nil && lib.Rules[0].Os.Name != runtime.GOOS { continue } arg += filepath.Join(dir, "minecraft_test", "lib", lib.Downloads.Artifact.Path) + ":" } arg += filepath.Join(dir, "minecraft_test", "bin", metadata.Id, "client.jar") args = append(args, arg) default: //args = append(args, val) } } args = append(args, "net.minecraft.client.main.Main") for _, val := range metadata.Arguments.Game { switch val{ case "${auth_player_name}": args = append(args, auth.Profile.Name) case "${version_name}": args = append(args, metadata.Id) case "${game_directory}": args = append(args, filepath.Join(dir, "minecraft_test", "minecraft")) case "${assets_root}": args = append(args, filepath.Join(dir, "minecraft_test", "assets")) case "${auth_uuid}": args = append(args, auth.Profile.Id) case "${auth_access_token}": args = append(args, auth.McAuth.Access_token) case "${clientid}": args = append(args, client_id) case "${auth_xuid}": args = append(args, auth.Profile.Id) case "${version_type}": args = append(args, "release") case "${user_type}": args = append(args, "mojang") case "${assets_index_name}": args = append(args, metadata.Assets) default: args = append(args, val) } } fmt.Printf("Args: %+v", args) cmd := exec.Command("/usr/lib/jvm/java-21-openjdk/bin/java", args...) out, err := cmd.CombinedOutput() if err != nil { fmt.Printf("Error: %s\n", err) } fmt.Printf("Output: %s\n", out) /* fmt.Println("Requesting Oauth") token, err := auth.RequestLiveToken() if err != nil { fmt.Println(err) return } else { ts := auth.RefreshTokenSource(token) fmt.Println("Generating Key") key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { fmt.Println(err) return } ctx := context.Background() fmt.Println("Requesting XBL Token") token, err = ts.Token() if err != nil { fmt.Println(err) return } xbl, err := auth.RequestXBLToken(ctx, token, "rp://api.minecraftservices.com/") if err != nil { fmt.Println(err) return } fmt.Println("Requesting Minecraft Chain") ctx = context.Background() _, err = auth.RequestMinecraftChain(ctx, xbl, key) if err != nil { fmt.Println(err) return } }*/ }