Skip to content

Commit

Permalink
resource/pack.go: Download resource packs from a URL, rename Compile …
Browse files Browse the repository at this point in the history
…methods (#202)
  • Loading branch information
TwistedAsylumMC authored Sep 21, 2023
1 parent 729e794 commit 62b14e8
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 18 deletions.
9 changes: 8 additions & 1 deletion minecraft/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,13 @@ func (conn *Conn) handleClientToServerHandshake() error {
}
pk := &packet.ResourcePacksInfo{TexturePackRequired: conn.texturePacksRequired}
for _, pack := range conn.resourcePacks {
if pack.DownloadURL() != "" {
pk.PackURLs = append(pk.PackURLs, protocol.PackURL{
UUIDVersion: fmt.Sprintf("%s_%s", pack.UUID(), pack.Version()),
URL: pack.DownloadURL(),
})
}

// If it has behaviours, add it to the behaviour pack list. If not, we add it to the texture packs
// list.
if pack.HasBehaviours() {
Expand Down Expand Up @@ -1123,7 +1130,7 @@ func (conn *Conn) handleResourcePackDataInfo(pk *packet.ResourcePackDataInfo) er
return
}
// First parse the resource pack from the total byte buffer we obtained.
newPack, err := resource.FromBytes(pack.buf.Bytes())
newPack, err := resource.Read(pack.buf)
if err != nil {
conn.log.Printf("invalid full resource pack data for UUID %v: %v\n", id, err)
return
Expand Down
10 changes: 5 additions & 5 deletions minecraft/protocol/resource_pack.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,16 @@ func (x *StackResourcePack) Marshal(r IO) {
// PackURL represents a resource pack that is being served from a HTTP server rather than being sent over
// the Minecraft protocol.
type PackURL struct {
// UUID is the UUID of the resource pack. Each resource pack downloaded must have a different UUID in
// order for the client to be able to handle them properly.
UUID string
// UUIDVersion is a combination of the UUID and version of the resource pack in the format UUID_Version.
// The client will only attempt to download the resource pack if it does not already have it cached.
UUIDVersion string
// URL is the URL from which the resource pack is downloaded. This URL must serve a zip file containing
// a manifest.json file inside of another folder. The manifest cannot be in the root of the zip file.
// a manifest.json file inside another folder. The manifest cannot be in the root of the zip file.
URL string
}

// Marshal encodes/decodes a CDNURL.
func (x *PackURL) Marshal(r IO) {
r.String(&x.UUID)
r.String(&x.UUIDVersion)
r.String(&x.URL)
}
66 changes: 54 additions & 12 deletions minecraft/resource/pack.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"github.com/muhammadmuzzammil1998/jsonc"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
Expand All @@ -20,6 +21,9 @@ type Pack struct {
// version and description.
manifest *Manifest

// downloadURL is the URL that the resource pack can be downloaded from. If the string is empty, then the
// resource pack will be downloaded over RakNet rather than HTTP.
downloadURL string
// content is a bytes.Reader that contains the full content of the zip file. It is used to send the full
// data to a client.
content *bytes.Reader
Expand All @@ -32,42 +36,74 @@ type Pack struct {
checksum [32]byte
}

// Compile compiles a resource pack found at the path passed. The resource pack must either be a zip archive
// ReadPath compiles a resource pack found at the path passed. The resource pack must either be a zip archive
// (extension does not matter, could be .zip or .mcpack), or a directory containing a resource pack. In the
// case of a directory, the directory is compiled into an archive and the pack is parsed from that.
// Compile operates assuming the resource pack has a 'manifest.json' file in it. If it does not, the function
// ReadPath operates assuming the resource pack has a 'manifest.json' file in it. If it does not, the function
// will fail and return an error.
func Compile(path string) (*Pack, error) {
func ReadPath(path string) (*Pack, error) {
return compile(path)
}

// MustCompile compiles a resource pack found at the path passed. The resource pack must either be a zip
// ReadURL downloads a resource pack found at the URL passed and compiles it. The resource pack must be a valid
// zip archive where the manifest.json file is inside a subdirectory rather than the root itself. If the resource
// pack is not a valid zip or there is no manifest.json file, an error is returned.
func ReadURL(url string) (*Pack, error) {
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("error downloading resource pack: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("error downloading resource pack: %v (%d)", resp.Status, resp.StatusCode)
}
pack, err := Read(resp.Body)
if err != nil {
return nil, err
}
pack.downloadURL = url
return pack, nil
}

// MustReadPath compiles a resource pack found at the path passed. The resource pack must either be a zip
// archive (extension does not matter, could be .zip or .mcpack), or a directory containing a resource pack.
// In the case of a directory, the directory is compiled into an archive and the pack is parsed from that.
// Compile operates assuming the resource pack has a 'manifest.json' file in it. If it does not, the function
// ReadPath operates assuming the resource pack has a 'manifest.json' file in it. If it does not, the function
// will fail and return an error.
// Unlike Compile, MustCompile does not return an error and panics if an error occurs instead.
func MustCompile(path string) *Pack {
// Unlike ReadPath, MustReadPath does not return an error and panics if an error occurs instead.
func MustReadPath(path string) *Pack {
pack, err := compile(path)
if err != nil {
panic(err)
}
return pack
}

// FromBytes parses an archived resource pack written to a raw byte slice passed. The data must be a valid
// MustReadURL downloads a resource pack found at the URL passed and compiles it. The resource pack must be a valid
// zip archive where the manifest.json file is inside a subdirectory rather than the root itself. If the resource
// pack is not a valid zip or there is no manifest.json file, an error is returned.
// Unlike ReadURL, MustReadURL does not return an error and panics if an error occurs instead.
func MustReadURL(url string) *Pack {
pack, err := ReadURL(url)
if err != nil {
panic(err)
}
return pack
}

// Read parses an archived resource pack written to a raw byte slice passed. The data must be a valid
// zip archive and contain a pack manifest in order for the function to succeed.
// FromBytes saves the data to a temporary archive.
func FromBytes(data []byte) (*Pack, error) {
// Read saves the data to a temporary archive.
func Read(r io.Reader) (*Pack, error) {
temp, err := createTempFile()
if err != nil {
return nil, fmt.Errorf("error creating temp zip archive: %v", err)
}
_, _ = temp.Write(data)
_, _ = io.Copy(temp, r)
if err := temp.Close(); err != nil {
return nil, fmt.Errorf("error closing temp zip archive: %v", err)
}
pack, parseErr := Compile(temp.Name())
pack, parseErr := ReadPath(temp.Name())
if err := os.Remove(temp.Name()); err != nil {
return nil, fmt.Errorf("error removing temp zip archive: %v", err)
}
Expand Down Expand Up @@ -149,6 +185,12 @@ func (pack *Pack) HasWorldTemplate() bool {
return pack.manifest.worldTemplate
}

// DownloadURL returns the URL that the resource pack can be downloaded from. If the string is empty, then the
// resource pack will be downloaded over RakNet rather than HTTP.
func (pack *Pack) DownloadURL() string {
return pack.downloadURL
}

// Checksum returns the SHA256 checksum made from the full, compressed content of the resource pack archive.
// It is transmitted as a string over network.
func (pack *Pack) Checksum() [32]byte {
Expand Down

0 comments on commit 62b14e8

Please sign in to comment.