diff --git a/vendor/git.itzana.me/itzaname/go-roblox/.gitignore b/vendor/git.itzana.me/itzaname/go-roblox/.gitignore new file mode 100644 index 0000000..c28c40c --- /dev/null +++ b/vendor/git.itzana.me/itzaname/go-roblox/.gitignore @@ -0,0 +1,2 @@ +.vscode/launch.json +debug \ No newline at end of file diff --git a/vendor/git.itzana.me/itzaname/go-roblox/README.md b/vendor/git.itzana.me/itzaname/go-roblox/README.md new file mode 100644 index 0000000..fbe2b21 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/go-roblox/README.md @@ -0,0 +1 @@ +Copyright © Stuart Livingston diff --git a/vendor/git.itzana.me/itzaname/go-roblox/asset.go b/vendor/git.itzana.me/itzaname/go-roblox/asset.go new file mode 100644 index 0000000..866a8e5 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/go-roblox/asset.go @@ -0,0 +1,110 @@ +package roblox + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "strings" + "encoding/json" +) + +type AssetUploadOptions struct { + Name string + AssetID int + Description string + Public bool + Comments bool + Group int +} + +type AssetUploadResponse struct { + AssetID int64 `json:"AssetId"` + AssetVersionID int64 `json:"AssetVersionId"` +} + +func (s *Session) CreateAsset(options *AssetUploadOptions, f io.Reader) (AssetUploadResponse, error) { + var assetInfo AssetUploadResponse + endpoint, err := url.Parse("https://data.roblox.com/Data/Upload.ashx?json=1&type=Model&genreTypeId=1") + if err != nil { + return assetInfo, err + } + + query := endpoint.Query() + query.Set("name", options.Name) + query.Set("description", options.Description) + query.Set("assetid", strconv.Itoa(options.AssetID)) + + // Comments + if options.Comments { + query.Set("allowComments", "true") + } else { + query.Set("allowComments", "false") + } + + // Public + if options.Public { + query.Set("ispublic", "true") + } else { + query.Set("ispublic", "false") + } + + // Group + if options.Group > 0 { + query.Set("groupId", strconv.Itoa(options.Group)) + } + + endpoint.RawQuery = query.Encode() + + req, err := http.NewRequest("POST", endpoint.String(), nil) + req.Header.Set("user-agent", "Roblox") + + // Perform request + resp, err := s.client.Do(req) + if err != nil { + return assetInfo, err + } + defer resp.Body.Close() + + if resp.StatusCode == 403 && resp.Header.Get("X-Csrf-Token") != "" { + req, err := http.NewRequest("POST", endpoint.String(), f) + req.Header.Set("user-agent", "Roblox") + req.Header.Set("x-csrf-token", strings.Trim(resp.Header["X-Csrf-Token"][0], " ")) + // Perform request + resp, err = s.client.Do(req) + if err != nil { + return assetInfo, err + } + defer resp.Body.Close() + } + + if resp.StatusCode != 200 { + return assetInfo, fmt.Errorf(resp.Status) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return assetInfo, err + } + + if err := json.Unmarshal(body, &assetInfo); err != nil { + return assetInfo, err + } + + return assetInfo, nil +} + +func (s *Session) Download(id int) (io.Reader, error) { + resp, err := s.client.Get("https://assetgame.roblox.com/Asset/?id=" + strconv.Itoa(id)) + if err != nil { + return nil, err + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf(resp.Status) + } + + return resp.Body, nil +} diff --git a/vendor/git.itzana.me/itzaname/go-roblox/go.mod b/vendor/git.itzana.me/itzaname/go-roblox/go.mod new file mode 100644 index 0000000..ecadb1c --- /dev/null +++ b/vendor/git.itzana.me/itzaname/go-roblox/go.mod @@ -0,0 +1,3 @@ +module git.itzana.me/itzaname/go-roblox + +go 1.15 diff --git a/vendor/git.itzana.me/itzaname/go-roblox/item.go b/vendor/git.itzana.me/itzaname/go-roblox/item.go new file mode 100644 index 0000000..5d33d3a --- /dev/null +++ b/vendor/git.itzana.me/itzaname/go-roblox/item.go @@ -0,0 +1,156 @@ +package roblox + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strconv" + "strings" +) + +// Item struct containing data on retrieved items +type Item struct { + ProductID int + ItemID int + AssetID int + UserID int + UserName string + Type string + Name string + CopyLocked bool + Owned bool +} + +// HasItem will return if you own the item +func (s *Session) HasItem(id int) (bool, error) { + resp, err := s.client.Get(fmt.Sprintf("http://api.roblox.com/Ownership/HasAsset?userId=%d&assetId=%d", s.ID, id)) + if err != nil { + return false, err + } + defer resp.Body.Close() + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return false, err + } + + return string(data) == "true", nil +} + +// AddItem add item by id to inventory +func (s *Session) AddItem(id int) error { + product, err := s.GetProduct(id) + if err != nil { + return err + } + + payload := struct { + ExpectedCurrency int `json:"expectedCurrency"` + ExpectedPrice int `json:"expectedPrice"` + ExpectedSellerID int `json:"expectedSellerId"` + }{ + ExpectedCurrency: 1, + ExpectedPrice: 0, + ExpectedSellerID: product.Creator.ID, + } + // Gen json body + data, err := json.Marshal(&payload) + if err != nil { + return err + } + + req, err := http.NewRequest("POST", fmt.Sprintf("https://economy.roblox.com/v1/purchases/products/%d", product.ProductID), bytes.NewBuffer(data)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + + resp, err := s.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode == 403 { + req, err := http.NewRequest("POST", fmt.Sprintf("https://economy.roblox.com/v1/purchases/products/%d", product.ProductID), bytes.NewBuffer(data)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Csrf-Token", resp.Header["X-Csrf-Token"][0]) + + resp, err := s.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return fmt.Errorf("Failed to add item. Status %d", resp.StatusCode) + } + } + + return nil +} + +// RemoveItem will remove item from inventory +func (s *Session) RemoveItem(id int) error { + v := url.Values{} + v.Set("assetId", strconv.Itoa(id)) + resp, err := s.client.Post("https://www.roblox.com/asset/delete-from-inventory", "application/x-www-form-urlencoded", bytes.NewBufferString(v.Encode())) + if err != nil { + return err + } + + defer resp.Body.Close() + + if resp.StatusCode == 403 { + req, err := http.NewRequest("POST", "https://www.roblox.com/asset/delete-from-inventory", bytes.NewBufferString(v.Encode())) + req.Header.Set("x-csrf-token", strings.Trim(resp.Header["X-Csrf-Token"][0], " ")) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + resp, err := s.client.Do(req) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return fmt.Errorf("Failed to remove item. Status %d", resp.StatusCode) + } + } + + return nil +} + +// GetModels will list models in inventory +func (s *Session) GetModels(user int) ([]Item, error) { + var Data []Item + + resp, err := s.client.Get("https://www.roblox.com/users/inventory/list-json?assetTypeId=10&userId=" + strconv.Itoa(user)) + if err != nil { + return Data, fmt.Errorf("Could not get list: %s", err) + } + + if resp.StatusCode == 200 { + var dat map[string]interface{} + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + + if err := json.Unmarshal(body, &dat); err != nil { + panic(err) + } + + set := dat["Data"].(map[string]interface{}) + ilist := set["Items"].([]interface{}) + for _, obj := range ilist { + itm := obj.(map[string]interface{}) + iInfo := itm["Item"].(map[string]interface{}) + iCreator := itm["Creator"].(map[string]interface{}) + Data = append(Data, Item{0, int(iInfo["AssetId"].(float64)), 0, int(iCreator["Id"].(float64)), iCreator["Name"].(string), "Model", iInfo["Name"].(string), false, true}) + } + + return Data, nil + } + return Data, fmt.Errorf("Could not get models. Status: %d", resp.StatusCode) +} diff --git a/vendor/git.itzana.me/itzaname/go-roblox/product.go b/vendor/git.itzana.me/itzaname/go-roblox/product.go new file mode 100644 index 0000000..5b79d4b --- /dev/null +++ b/vendor/git.itzana.me/itzaname/go-roblox/product.go @@ -0,0 +1,61 @@ +package roblox + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "time" +) + +// Product contains info about a product +type Product struct { + TargetID int `json:"TargetId"` + ProductType string `json:"ProductType"` + AssetID int `json:"AssetId"` + ProductID int `json:"ProductId"` + Name string `json:"Name"` + Description string `json:"Description"` + AssetTypeID int `json:"AssetTypeId"` + Creator struct { + ID int `json:"Id"` + Name string `json:"Name"` + CreatorType string `json:"CreatorType"` + CreatorTargetID int `json:"CreatorTargetId"` + } `json:"Creator"` + IconImageAssetID int `json:"IconImageAssetId"` + Created time.Time `json:"Created"` + Updated time.Time `json:"Updated"` + PriceInRobux int `json:"PriceInRobux"` + PriceInTickets int `json:"PriceInTickets"` + Sales int `json:"Sales"` + IsNew bool `json:"IsNew"` + IsForSale bool `json:"IsForSale"` + IsPublicDomain bool `json:"IsPublicDomain"` + IsLimited bool `json:"IsLimited"` + IsLimitedUnique bool `json:"IsLimitedUnique"` + Remaining int `json:"Remaining"` + MinimumMembershipLevel int `json:"MinimumMembershipLevel"` + ContentRatingTypeID int `json:"ContentRatingTypeId"` +} + +// GetProduct will retrieve store information on a product +func (s *Session) GetProduct(id int) (*Product, error) { + resp, err := s.client.Get(fmt.Sprintf("http://api.roblox.com/marketplace/productinfo?assetId=%d", id)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var info Product + err = json.Unmarshal(data, &info) + if err != nil { + return nil, err + } + + return &info, nil +} diff --git a/vendor/git.itzana.me/itzaname/go-roblox/session.go b/vendor/git.itzana.me/itzaname/go-roblox/session.go new file mode 100644 index 0000000..c81bfe5 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/go-roblox/session.go @@ -0,0 +1,89 @@ +package roblox + +import ( + "fmt" + "net/http" + "net/http/cookiejar" + "net/url" +) + +// Session struct for roblox login session data and members +type Session struct { + ID int + Username string + client *http.Client +} + +// New create a new session and logs in with provided data +func New(cookie string) (*Session, error) { + cookieJar, _ := cookiejar.New(nil) + rbxCookie := []*http.Cookie{&http.Cookie{ + Name: ".ROBLOSECURITY", + Value: cookie, + }} + // url.Parse("http://www.roblox.com") // http://api.roblox.com + cookieJar.SetCookies(&url.URL{Scheme: "http", Host: "www.roblox.com"}, rbxCookie) + cookieJar.SetCookies(&url.URL{Scheme: "http", Host: "api.roblox.com"}, rbxCookie) + cookieJar.SetCookies(&url.URL{Scheme: "https", Host: "economy.roblox.com"}, rbxCookie) + cookieJar.SetCookies(&url.URL{Scheme: "https", Host: "data.roblox.com"}, rbxCookie) + client := &http.Client{ + Jar: cookieJar, + } + + session := Session{0, "", client} + + /*err := session.Login(username, password) + if err != nil { + return nil, fmt.Errorf("Failed to login: %s", err) + }*/ + + info, err := session.GetUserInfo() + if err != nil { + return nil, fmt.Errorf("Failed to retrieve user information: %s", err) + } + + session.ID = info.UserID + session.Username = info.UserName + + return &session, err +} + +/*func (s *Session) Login(username, password string) error { + details := struct { + Ctype string `json:"ctype"` + Cvalue string `json:"cvalue"` + Password string `json:"password"` + }{ + "Username", + username, + password, + } + payload, err := json.Marshal(&details) + if err != nil { + return err + } + + resp, err := s.client.Post("https://auth.roblox.com/v2/login", "application/json", bytes.NewBuffer(payload)) + if err != nil { + return err + } + resp.Body.Close() + + if resp.StatusCode == 403 { + req, err := http.NewRequest("POST", "https://auth.roblox.com/v2/login", bytes.NewBuffer(payload)) + req.Header.Set("X-Csrf-Token", resp.Header["X-Csrf-Token"][0]) + req.Header.Set("Content-Type", "application/json") + + resp, err := s.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return fmt.Errorf("Status %d", resp.StatusCode) + } + } + + return nil +}*/ diff --git a/vendor/git.itzana.me/itzaname/go-roblox/user.go b/vendor/git.itzana.me/itzaname/go-roblox/user.go new file mode 100644 index 0000000..71935e1 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/go-roblox/user.go @@ -0,0 +1,37 @@ +package roblox + +import ( + "encoding/json" + "io/ioutil" +) + +type UserInfo struct { + UserID int `json:"UserID"` + UserName string `json:"UserName"` + RobuxBalance int `json:"RobuxBalance"` + TicketsBalance int `json:"TicketsBalance"` + ThumbnailURL string `json:"ThumbnailUrl"` + IsAnyBuildersClubMember bool `json:"IsAnyBuildersClubMember"` +} + +// GetUserInfo will retrieve local user information +func (s *Session) GetUserInfo() (*UserInfo, error) { + resp, err := s.client.Get("http://www.roblox.com/mobileapi/userinfo") + if err != nil { + return nil, err + } + defer resp.Body.Close() + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var info UserInfo + err = json.Unmarshal(data, &info) + if err != nil { + return nil, err + } + + return &info, nil +} diff --git a/vendor/git.itzana.me/itzaname/rbxapi/LICENSE b/vendor/git.itzana.me/itzaname/rbxapi/LICENSE new file mode 100644 index 0000000..3002e7b --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxapi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Anaminus + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/git.itzana.me/itzaname/rbxapi/README.md b/vendor/git.itzana.me/itzaname/rbxapi/README.md new file mode 100644 index 0000000..8894d9d --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxapi/README.md @@ -0,0 +1,14 @@ +[![GoDoc](https://godoc.org/git.itzana.me/itzaname/rbxapi?status.png)](https://godoc.org/git.itzana.me/itzaname/rbxapi) + +# rbxapi + +The rbxapi package is a Go package used to represent information about the +Roblox Lua API. + +## API References + +- [rbxapi](https://godoc.org/git.itzana.me/itzaname/rbxapi) + - [patch](https://godoc.org/git.itzana.me/itzaname/rbxapi/patch): Used to represent information about differences between Roblox Lua API structures. + - [diff](https://godoc.org/git.itzana.me/itzaname/rbxapi/diff): Provides an implementation of the patch package for the generic rbxapi types. +- [rbxapidump](https://godoc.org/git.itzana.me/itzaname/rbxapi/rbxapidump): Implements the rbxapi interface as a codec for the Roblox API dump format. +- [rbxapijson](https://godoc.org/git.itzana.me/itzaname/rbxapi/rbxapijson): Implements the rbxapi package as a codec for the Roblox API dump in JSON format. diff --git a/vendor/git.itzana.me/itzaname/rbxapi/go.mod b/vendor/git.itzana.me/itzaname/rbxapi/go.mod new file mode 100644 index 0000000..8d95538 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxapi/go.mod @@ -0,0 +1,3 @@ +module git.itzana.me/itzaname/rbxapi + +go 1.16 diff --git a/vendor/git.itzana.me/itzaname/rbxapi/rbxapi.go b/vendor/git.itzana.me/itzaname/rbxapi/rbxapi.go new file mode 100644 index 0000000..509db23 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxapi/rbxapi.go @@ -0,0 +1,219 @@ +// The rbxapi package is used to represent information about the Roblox Lua +// API. +// +// This package provides a common interface for multiple implementations of +// the Roblox Lua API. The rbxapi interface may not be able to expose all +// information available from a particular implementation. If such information +// is required, it would be more suitable to use that implementation directly. +// +// The rbxapidump and rbxapijson subpackages provide implementations of the +// rbxapi interface. +package rbxapi + +// Root represents the top-level structure of an API. +type Root interface { + // GetClasses returns a list of class descriptors present in the API. + // Items in the list must have a consistent order. + GetClasses() []Class + + // GetClass returns the first class descriptor of the given name, or nil + // if no class of the given name is present. + GetClass(name string) Class + + // GetEnums returns a list of enum descriptors present in the API. Items + // in the list must have a consistent order. + GetEnums() []Enum + + // GetEnum returns the first enum descriptor of the given name, or nil if + // no enum of the given name is present. + GetEnum(name string) Enum + + // Copy returns a deep copy of the API structure. + Copy() Root +} + +// Class represents a class descriptor. +type Class interface { + // GetName returns the class name. + GetName() string + + // GetSuperclass returns the name of the class that this class inherits + // from. + GetSuperclass() string + + // GetMembers returns a list of member descriptors belonging to the class. + // Items in the list must have a consistent order. + GetMembers() []Member + + // GetMember returns the first member descriptor of the given name, or nil + // if no member of the given name is present. + GetMember(name string) Member + + // Copy returns a deep copy of the class descriptor. + Copy() Class + + Taggable +} + +// Member represents a class member descriptor. A Member can be asserted to a +// more specific type. These are Property, Function, Event, and Callback. +type Member interface { + // GetMemberType returns a string indicating the the type of member. + GetMemberType() string + + // GetName returns the name of the member. + GetName() string + + // Copy returns a deep copy of the member descriptor. + Copy() Member + + Taggable +} + +// Property represents a class member of the Property member type. +type Property interface { + Member + + // GetSecurity returns the security context associated with the property's + // read and write access. + GetSecurity() (read, write string) + + // GetValueType returns the type of value stored in the property. + GetValueType() Type +} + +// Function represents a class member of the Function member type. +type Function interface { + Member + + // GetSecurity returns the security context of the member's access. + GetSecurity() string + + // GetParameters returns the list of parameters describing the arguments + // passed to the function. These parameters may have default values. + GetParameters() Parameters + + // GetReturnType returns the type of value returned by the function. + GetReturnType() Type +} + +// Event represents a class member of the Event member type. +type Event interface { + Member + + // GetSecurity returns the security context of the member's access. + GetSecurity() string + + // GetParameters returns the list of parameters describing the arguments + // received from the event. These parameters cannot have default values. + GetParameters() Parameters +} + +// Callback represents a class member of the Callback member type. +type Callback interface { + Member + + // GetSecurity returns the security context of the member's access. + GetSecurity() string + + // GetParameters returns the list of parameters describing the arguments + // passed to the callback. These parameters cannot have default values. + GetParameters() Parameters + + // GetReturnType returns the type of value that is returned by the + // callback. + GetReturnType() Type +} + +// Parameters represents a list of parameters of a function, event, or +// callback member. +type Parameters interface { + // GetLength returns the number of parameters in the list. + GetLength() int + + // GetParameter returns the parameter indicated by the given index. + GetParameter(int) Parameter + + // GetParameters returns a copy of the list as a slice. + GetParameters() []Parameter + + // Copy returns a deep copy of the parameter list. + Copy() Parameters +} + +// Parameter represents a single parameter of a function, event, or callback +// member. +type Parameter interface { + // GetType returns the type of the parameter value. + GetType() Type + + // GetName returns the name describing the parameter. + GetName() string + + // GetDefault returns a string representing the default value of the + // parameter, and whether a default value is present. + GetDefault() (value string, ok bool) + + // Copy returns a deep copy of the parameter. + Copy() Parameter +} + +// Enum represents an enum descriptor. +type Enum interface { + // GetName returns the name of the enum. + GetName() string + + // GetEnumItems returns a list of items of the enum. Items in the list + // must have a consistent order. + GetEnumItems() []EnumItem + + // GetEnumItem returns the first item of the given name, or nil if no item + // of the given name is present. + GetEnumItem(name string) EnumItem + + // Copy returns a deep copy of the enum descriptor. + Copy() Enum + + Taggable +} + +// EnumItem represents an enum item descriptor. +type EnumItem interface { + // GetName returns the name of the enum item. + GetName() string + + // GetValue returns the value of the enum item. + GetValue() int + + // Copy returns a deep copy of the enum item descriptor. + Copy() EnumItem + + Taggable +} + +// Taggable indicates a descriptor that is capable of having tags. +type Taggable interface { + // GetTag returns whether the given tag is present in the descriptor. + GetTag(tag string) bool + + // GetTags returns a list of all tags present in the descriptor. Items in + // the list must have a consistent order. + GetTags() []string +} + +// Type represents a value type. +type Type interface { + // GetName returns the name of the type. + GetName() string + + // GetCategory returns the category of the type. This may be empty when a + // type category is inapplicable or unavailable. + GetCategory() string + + // String returns a string representation of the entire type. The format + // of this string is implementation-dependent. + String() string + + // Copy returns a deep copy of the type. + Copy() Type +} diff --git a/vendor/git.itzana.me/itzaname/rbxfile/LICENSE b/vendor/git.itzana.me/itzaname/rbxfile/LICENSE new file mode 100644 index 0000000..7774d18 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxfile/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2014 Anaminus + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/git.itzana.me/itzaname/rbxfile/README.md b/vendor/git.itzana.me/itzaname/rbxfile/README.md new file mode 100644 index 0000000..67d8856 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxfile/README.md @@ -0,0 +1,49 @@ +[![GoDoc](https://godoc.org/git.itzana.me/itzaname/rbxfile?status.png)](https://godoc.org/git.itzana.me/itzaname/rbxfile) + +# rbxfile + +The rbxfile package handles the decoding, encoding, and manipulation of Roblox +instance data structures. + +This package can be used to manipulate Roblox instance trees outside of the +Roblox client. Such data structures begin with a [Root][root] struct. A Root +contains a list of child [Instances][inst], which in turn contain more child +Instances, and so on, forming a tree of Instances. These Instances can be +accessed and manipulated using an API similar to that of Roblox. + +Each Instance also has a set of "properties". Each property has a specific +value of a certain [type][type]. Every available type implements the +[Value][value] interface, and is prefixed with "Value". + +Root structures can be decoded from and encoded to various formats, including +Roblox's native file formats. The two sub-packages [bin][bin] and [xml][xml] +provide formats for Roblox's binary and XML formats. Root structures can also +be encoded and decoded with the [json][json] package. + +Besides decoding from a format, root structures can also be created manually. +The best way to do this is through the [declare][declare] sub-package, which +provides an easy way to generate root structures. + +[root]: https://godoc.org/git.itzana.me/itzaname/rbxfile#Root +[inst]: https://godoc.org/git.itzana.me/itzaname/rbxfile#Instance +[type]: https://godoc.org/git.itzana.me/itzaname/rbxfile#Type +[value]: https://godoc.org/git.itzana.me/itzaname/rbxfile#Value +[bin]: https://godoc.org/git.itzana.me/itzaname/rbxfile/bin +[xml]: https://godoc.org/git.itzana.me/itzaname/rbxfile/xml +[json]: https://godoc.org/encoding/json +[declare]: https://godoc.org/git.itzana.me/itzaname/rbxfile/declare + +## Related +The implementation of the binary file format is based largely on the +[RobloxFileSpec][spec] document, a reverse-engineered specification by Gregory +Comer. + +Other projects that involve decoding and encoding Roblox files: + +- [rbx-fmt](https://github.com/stravant/rbx-fmt): An implementation in C. +- [LibRbxl](https://github.com/GregoryComer/LibRbxl): An implementation in C#. +- [rbx-dom](https://github.com/LPGhatguy/rbx-dom): An implementation in Rust. +- [Roblox-File-Format](https://github.com/CloneTrooper1019/Roblox-File-Format): + An implementation in C#. + +[spec]: https://www.classy-studios.com/Downloads/RobloxFileSpec.pdf diff --git a/vendor/git.itzana.me/itzaname/rbxfile/file.go b/vendor/git.itzana.me/itzaname/rbxfile/file.go new file mode 100644 index 0000000..8051ffa --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxfile/file.go @@ -0,0 +1,431 @@ +// The rbxfile package handles the decoding, encoding, and manipulation of +// Roblox instance data structures. +// +// This package can be used to manipulate Roblox instance trees outside of the +// Roblox client. Such data structures begin with a Root struct. A Root +// contains a list of child Instances, which in turn contain more child +// Instances, and so on, forming a tree of Instances. These Instances can be +// accessed and manipulated using an API similar to that of Roblox. +// +// Each Instance also has a set of "properties". Each property has a specific +// value of a certain type. Every available type implements the Value +// interface, and is prefixed with "Value". +// +// Root structures can be decoded from and encoded to various formats, +// including Roblox's native file formats. The two sub-packages "bin" and +// "xml" provide formats for Roblox's binary and XML formats. Root structures +// can also be encoded and decoded with the "json" package. +// +// Besides decoding from a format, root structures can also be created +// manually. The best way to do this is through the "declare" sub-package, +// which provides an easy way to generate root structures. +package rbxfile + +import ( + "errors" +) + +//////////////////////////////////////////////////////////////// + +// Root represents the root of an instance tree. Root is not itself an +// instance, but a container for multiple root instances. +type Root struct { + // Instances contains root instances contained in the tree. + Instances []*Instance + + // Metadata contains metadata about the tree. + Metadata map[string]string +} + +// NewRoot returns a new initialized Root. +func NewRoot() *Root { + return &Root{ + Instances: []*Instance{}, + Metadata: map[string]string{}, + } +} + +// Copy creates a copy of the root and its contents. +// +// A copied reference within the tree is resolved so that it points to the +// corresponding copy of the original referent. Copied references that point +// to an instance which isn't being copied will still point to the same +// instance. +func (root *Root) Copy() *Root { + clone := &Root{ + Instances: make([]*Instance, len(root.Instances)), + } + + refs := make(References) + crefs := make(References) + propRefs := make([]PropRef, 0, 8) + for i, inst := range root.Instances { + clone.Instances[i] = inst.clone(refs, crefs, &propRefs) + } + for _, propRef := range propRefs { + if !crefs.Resolve(propRef) { + // Refers to an instance outside the tree, try getting the + // original referent. + refs.Resolve(propRef) + } + } + return clone +} + +// Instance represents a single Roblox instance. +type Instance struct { + // ClassName indicates the instance's type. + ClassName string + + // Reference is a unique string used to refer to the instance from + // elsewhere in the tree. + Reference string + + // IsService indicates whether the instance should be treated as a + // service. + IsService bool + + // Properties is a map of properties of the instance. It maps the name of + // the property to its current value. + Properties map[string]Value + + // Children contains instances that are the children of the current + // instance. If this field is set directly, then FixTree should be called + // afterwards to ensure the correctness of the tree. + Children []*Instance + + // The parent of the instance. Can be nil. + parent *Instance +} + +// NewInstance creates a new Instance of a given class, and an optional +// parent. +func NewInstance(className string, parent *Instance) *Instance { + inst := &Instance{ + ClassName: className, + Reference: GenerateReference(), + Properties: make(map[string]Value, 0), + } + if parent != nil { + parent.Children = append(parent.Children, inst) + inst.parent = parent + } + return inst +} + +// assertLoop returns an error if an instance being the child of a parent would +// create a circular reference. +func assertLoop(child, parent *Instance) error { + if parent == child { + return errors.New("attempt to set instance as its own parent") + } + if parent != nil && parent.IsDescendantOf(child) { + return errors.New("attempt to set parent would result in circular reference") + } + return nil +} + +// addChild appends a child to the instance, and sets its parent. If the child +// is already the child of another instance, it is first removed. +func (inst *Instance) addChild(child *Instance) { + if child.parent != nil { + child.parent.RemoveChild(child) + } + inst.Children = append(inst.Children, child) + child.parent = inst +} + +// AddChild appends a child instance to the instance's list of children. If +// the child has a parent, it is first removed. The parent of the child is set +// to the instance. An error is returned if the instance is a descendant of +// the child, or if the child is the instance itself. +func (inst *Instance) AddChild(child *Instance) error { + if err := assertLoop(child, inst); err != nil { + return err + } + inst.addChild(child) + return nil +} + +// AddChildAt inserts a child instance into the instance's list of children at +// a given position. If the child has a parent, it is first removed. The +// parent of the child is set to the instance. If the index is outside the +// bounds of the list, then it is constrained. An error is returned if the +// instance is a descendant of the child, or if the child is the instance +// itself. +func (inst *Instance) AddChildAt(index int, child *Instance) error { + if err := assertLoop(child, inst); err != nil { + return err + } + if index < 0 { + index = 0 + } else if index >= len(inst.Children) { + inst.addChild(child) + return nil + } + if child.parent != nil { + child.parent.RemoveChild(child) + } + inst.Children = append(inst.Children, nil) + copy(inst.Children[index+1:], inst.Children[index:]) + inst.Children[index] = child + child.parent = inst + return nil +} + +// removeChildAt removes the child at the given index, which is assumed to be +// within bounds. +func (inst *Instance) removeChildAt(index int) (child *Instance) { + child = inst.Children[index] + child.parent = nil + copy(inst.Children[index:], inst.Children[index+1:]) + inst.Children[len(inst.Children)-1] = nil + inst.Children = inst.Children[:len(inst.Children)-1] + return child +} + +// RemoveChild removes a child instance from the instance's list of children. +// The parent of the child is set to nil. Returns the removed child. +func (inst *Instance) RemoveChild(child *Instance) *Instance { + for index, c := range inst.Children { + if c == child { + return inst.removeChildAt(index) + } + } + return nil +} + +// RemoveChildAt removes the child at a given position from the instance's +// list of children. The parent of the child is set to nil. If the index is +// outside the bounds of the list, then no children are removed. Returns the +// removed child. +func (inst *Instance) RemoveChildAt(index int) *Instance { + if index < 0 || index >= len(inst.Children) { + return nil + } + return inst.removeChildAt(index) +} + +// RemoveAll remove every child from the instance. The parent of each child is +// set to nil. +func (inst *Instance) RemoveAll() { + for i, child := range inst.Children { + child.parent = nil + inst.Children[i] = nil + } + inst.Children = inst.Children[:0] +} + +// Parent returns the parent of the instance. Can return nil if the instance +// has no parent. +func (inst *Instance) Parent() *Instance { + return inst.parent +} + +// SetParent sets the parent of the instance, removing itself from the +// children of the old parent, and adding itself as a child of the new parent. +// The parent can be set to nil. An error is returned if the parent is a +// descendant of the instance, or if the parent is the instance itself. If the +// new parent is the same as the old parent, then the position of the instance +// in the parent's children is unchanged. +func (inst *Instance) SetParent(parent *Instance) error { + if inst.parent == parent { + return nil + } + if err := assertLoop(inst, parent); err != nil { + return err + } + if inst.parent != nil { + inst.parent.RemoveChild(inst) + } + if parent != nil { + parent.addChild(inst) + } + return nil +} + +// FixTree walks through the descendants of the instance tree top-down and +// ensures that the parental references are correct. Any descendants that cause +// a circular reference are removed and not traversed. +func (inst *Instance) FixTree() { + for i := 0; i < len(inst.Children); { + child := inst.Children[i] + if err := assertLoop(child, inst); err != nil { + inst.removeChildAt(i) + continue + } + child.parent = inst + child.FixTree() + i++ + } +} + +// clone returns a deep copy of the instance while managing references. +func (inst *Instance) clone(refs, crefs References, propRefs *[]PropRef) *Instance { + clone := &Instance{ + ClassName: inst.ClassName, + Reference: refs.Get(inst), + IsService: inst.IsService, + Children: make([]*Instance, len(inst.Children)), + Properties: make(map[string]Value, len(inst.Properties)), + } + crefs[clone.Reference] = clone + for name, value := range inst.Properties { + if value, ok := value.(ValueReference); ok { + *propRefs = append(*propRefs, PropRef{ + Instance: clone, + Property: name, + Reference: refs.Get(value.Instance), + }) + continue + } + clone.Properties[name] = value.Copy() + } + for i, child := range inst.Children { + c := child.clone(refs, crefs, propRefs) + clone.Children[i] = c + c.parent = clone + } + return clone +} + +// Clone returns a copy of the instance. Each property and all descendants are +// copied as well. Unlike Roblox's implementation, the Archivable property is +// ignored. +// +// A copied reference within the tree is resolved so that it points to the +// corresponding copy of the original referent. Copied references that point +// to an instance which isn't being copied will still point to the same +// instance. +func (inst *Instance) Clone() *Instance { + refs := make(References) + crefs := make(References) + propRefs := make([]PropRef, 0, 8) + clone := inst.clone(refs, crefs, &propRefs) + for _, propRef := range propRefs { + if !crefs.Resolve(propRef) { + // Refers to an instance outside the tree, try getting the + // original referent. + refs.Resolve(propRef) + } + } + return clone +} + +// FindFirstChild returns the first found child whose Name property matches +// the given name. Returns nil if no child was found. If recursive is true, +// then FindFirstChild will be called on descendants as well. +func (inst *Instance) FindFirstChild(name string, recursive bool) *Instance { + for _, child := range inst.Children { + if child.Name() == name { + return child + } + } + + if recursive { + for _, child := range inst.Children { + if desc := child.FindFirstChild(name, true); desc != nil { + return desc + } + } + } + + return nil +} + +// GetFullName returns the "full" name of the instance, which is the combined +// names of the instance and every ancestor, separated by a `.` character. +func (inst *Instance) GetFullName() string { + // Note: Roblox's GetFullName stops at the first ancestor that is a + // ServiceProvider. Since recreating this behavior would require + // information about the class hierarchy, this implementation simply + // includes every ancestor. + + names := make([]string, 0, 8) + + object := inst + for object != nil { + names = append(names, object.Name()) + object = object.Parent() + } + + full := make([]byte, 0, 64) + for i := len(names) - 1; i > 0; i-- { + full = append(full, []byte(names[i])...) + full = append(full, '.') + } + full = append(full, []byte(names[0])...) + + return string(full) +} + +// IsAncestorOf returns whether the instance is the ancestor of another +// instance. +func (inst *Instance) IsAncestorOf(descendant *Instance) bool { + if descendant != nil { + return descendant.IsDescendantOf(inst) + } + return false +} + +// IsDescendantOf returns whether the instance is the descendant of another +// instance. +func (inst *Instance) IsDescendantOf(ancestor *Instance) bool { + parent := inst.Parent() + for parent != nil { + if parent == ancestor { + return true + } + parent = parent.Parent() + } + return false +} + +// Name returns the Name property of the instance, or an empty string if it is +// invalid or not defined. +func (inst *Instance) Name() string { + iname, ok := inst.Properties["Name"] + if !ok { + return "" + } + + name, _ := iname.(ValueString) + return string(name) +} + +// String implements the fmt.Stringer interface by returning the Name of the +// instance, or the ClassName if Name isn't defined. +func (inst *Instance) String() string { + iname, ok := inst.Properties["Name"] + if !ok { + return inst.ClassName + } + + name, _ := iname.(ValueString) + if string(name) == "" { + return inst.ClassName + } + + return string(name) +} + +// SetName sets the Name property of the instance. +func (inst *Instance) SetName(name string) { + inst.Properties["Name"] = ValueString(name) +} + +// Get returns the value of a property in the instance. The value will be nil +// if the property is not defined. +func (inst *Instance) Get(property string) (value Value) { + return inst.Properties[property] +} + +// Set sets the value of a property in the instance. If value is nil, then the +// value will be deleted from the Properties map. +func (inst *Instance) Set(property string, value Value) { + if value == nil { + delete(inst.Properties, property) + } else { + inst.Properties[property] = value + } +} diff --git a/vendor/git.itzana.me/itzaname/rbxfile/go.mod b/vendor/git.itzana.me/itzaname/rbxfile/go.mod new file mode 100644 index 0000000..d4153c3 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxfile/go.mod @@ -0,0 +1,9 @@ +module git.itzana.me/itzaname/rbxfile + +go 1.15 + +require ( + git.itzana.me/itzaname/rbxapi v0.1.0 + github.com/anaminus/but v0.2.0 + github.com/bkaradzic/go-lz4 v1.0.0 +) diff --git a/vendor/git.itzana.me/itzaname/rbxfile/go.sum b/vendor/git.itzana.me/itzaname/rbxfile/go.sum new file mode 100644 index 0000000..dc6a195 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxfile/go.sum @@ -0,0 +1,6 @@ +git.itzana.me/itzaname/rbxapi v0.1.0 h1:eBKdz34TJ+U6boV97E/NWncnS6LYtlV2sz34v/Sd1X4= +git.itzana.me/itzaname/rbxapi v0.1.0/go.mod h1:QrL2P6VPj7gin7/KhM+86HsZ4TqoPUJ9t5FjoduEYfY= +github.com/anaminus/but v0.2.0 h1:UPKY6UtvTZH8seod0rfVRsQxP8qssz+P6VE9a2AYeNY= +github.com/anaminus/but v0.2.0/go.mod h1:44z5qYo/3MWnZDi6ifH3IgrFWa1VFfdTttL3IYN/9R4= +github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk= +github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= diff --git a/vendor/git.itzana.me/itzaname/rbxfile/ref.go b/vendor/git.itzana.me/itzaname/rbxfile/ref.go new file mode 100644 index 0000000..2c02556 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxfile/ref.go @@ -0,0 +1,102 @@ +package rbxfile + +import ( + "crypto/rand" + "io" +) + +// PropRef specifies the property of an instance that is a reference, which is +// to be resolved into its referent at a later time. +type PropRef struct { + Instance *Instance + Property string + Reference string +} + +// References is a mapping of reference strings to Instances. +type References map[string]*Instance + +// Resolve resolves a PropRef and sets the value of the property using +// References. If the referent does not exist, and the reference is not empty, +// then false is returned. True is returned otherwise. +func (refs References) Resolve(propRef PropRef) bool { + if refs == nil { + return false + } + if propRef.Instance == nil { + return false + } + referent := refs[propRef.Reference] + propRef.Instance.Properties[propRef.Property] = ValueReference{ + Instance: referent, + } + return referent != nil && !IsEmptyReference(propRef.Reference) +} + +// Get gets a reference from an Instance, using References to check for +// duplicates. If the instance's reference already exists in References, then +// a new reference is generated and applied to the instance. The instance's +// reference is then added to References. +func (refs References) Get(instance *Instance) (ref string) { + if instance == nil { + return "" + } + + ref = instance.Reference + if refs == nil { + return ref + } + // If the reference is not empty, or if the reference is not marked, or + // the marked reference already refers to the current instance, then do + // nothing. + if IsEmptyReference(ref) || refs[ref] != nil && refs[ref] != instance { + // Otherwise, regenerate the reference until it is not a duplicate. + for { + // If a generated reference matches a reference that was not yet + // traversed, then the latter reference will be regenerated, which + // may not match Roblox's implementation. It is difficult to + // discern whether this is correct because it is extremely + // unlikely that a duplicate will be generated. + ref = GenerateReference() + if _, ok := refs[ref]; !ok { + instance.Reference = ref + break + } + } + } + // Mark reference as taken. + refs[ref] = instance + return ref +} + +// IsEmptyReference returns whether a reference string is considered "empty", +// and therefore does not have a referent. +func IsEmptyReference(ref string) bool { + switch ref { + case "", "null", "nil": + return true + default: + return false + } +} + +func generateUUID() string { + var buf [32]byte + if _, err := io.ReadFull(rand.Reader, buf[:16]); err != nil { + panic(err) + } + buf[6] = (buf[6] & 0x0F) | 0x40 // Version 4 ; 0100XXXX + buf[8] = (buf[8] & 0x3F) | 0x80 // Variant RFC4122 ; 10XXXXXX + const hextable = "0123456789ABCDEF" + for i := len(buf)/2 - 1; i >= 0; i-- { + buf[i*2+1] = hextable[buf[i]&0x0f] + buf[i*2] = hextable[buf[i]>>4] + } + return string(buf[:]) +} + +// GenerateReference generates a unique string that can be used as a reference +// to an Instance. +func GenerateReference() string { + return "RBX" + generateUUID() +} diff --git a/vendor/git.itzana.me/itzaname/rbxfile/value_checklist.md b/vendor/git.itzana.me/itzaname/rbxfile/value_checklist.md new file mode 100644 index 0000000..c429f25 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxfile/value_checklist.md @@ -0,0 +1,89 @@ +To add value type `Foobar`: + +- rbxfile + - `values.go` + - [ ] Add `TypeFoobar` to Type constants. + - [ ] In `typeStrings`, map `TypeFoobar` to string `"Foobar"`. + - [ ] In `valueGenerators`, map `TypeFoobar` to function + `newValueFoobar`. + - [ ] Create `ValueFoobar` type. + - [ ] Add `ValueFoobar` type with appropriate underlying type. + - [ ] Implement `newValueFoobar` function (`func() Value`) + - [ ] Implement `Type() Type` method. + - Return `TypeFoobar`. + - [ ] Implement `String() string` method. + - Return string representation of value that is similar to the + results of Roblox's `tostring` function. + - [ ] Implement `Copy() Value` method. + - Must return a deep copy of the underlying value. + - `values_test.go` + - ... +- declare + - `declare/type.go` + - [ ] Add `Foobar` to type constants. + - Ensure `Foobar` does not conflict with existing identifiers. + - [ ] In `typeStrings`, map `Foobar` to string `"Foobar"`. + - [ ] In function `assertValue`, add case `Foobar`. + - Assert `v` as `rbxfile.ValueFoobar`. + - [ ] In method `Type.value`, add case `Foobar`. + - Convert slice of arbitrary values to a `rbxfile.ValueFoobar`. + - `declare/declare.go` + - [ ] In function `Property`, document behavior of `Foobar` case in + `Type.value` method. + - `declare/declare_test.go` + - ... +- json + - `json/json.go` + - [ ] In function `ValueToJSONInterface`, add case + `rbxfile.ValueFoobar`. + - Convert `rbxfile.ValueFoobar` to generic JSON interface. + - [ ] In function `ValueFromJSONInterface`, add case + `rbxfile.TypeFoobar`. + - Convert generic JSON interface to `rbxfile.ValueFoobar`. +- xml + - `xml/codec.go` + - [ ] In function `GetCanonType` add case `"foobar"` (lowercase). + - Returns `"Foobar"` + - [ ] In method `rdecoder.getValue`, add case `"Foobar"`. + - Receives `tag *Tag`, must return `rbxfile.ValueFoobar`. + - `components` can be used to map subtags to value fields. + - [ ] In method `rencoder.encodeProperty`, add case + `rbxfile.ValueFoobar`. + - Returns `*Tag` that is decodable by `rdecoder.getValue`. + - [ ] In function `isCanonType`, add case `rbxfile.ValueFoobar`. +- bin + - `bin/values.go` + - [ ] Add `TypeFoobar` to type constants. + - [ ] In `typeStrings`, map `TypeFoobar` to `"Foobar"`. + - [ ] In `valueGenerators`, map `TypeFoobar` to function + `newValueFoobar`. + - [ ] Create `ValueFoobar` type. + - [ ] Add `ValueFoobar` with appropriate underlying type. + - [ ] Implement `newValueFoobar` function (`func() Value`). + - [ ] Implement `Type() Type` method. + - Returns `TypeFoobar`. + - [ ] Implement `ArrayBytes`. + - Converts a slice of `ValueFoobar` to a slice of bytes. + - If fields `ValueFoobar` must be interleaved, use + `interleaveFields`. + - [ ] Implement `FromArrayBytes`. + - Converts a slice of bytes to a slice of `ValueFoobar`. + - If fields of ValueFoobar` are interleaved, use + `deinterleaveFields`. + - [ ] Implement `Bytes`. + - Converts a single `ValueFoobar` to a slice of bytes. + - [ ] Implement `FromBytes`. + - Converts a slice of bytes to a single `ValueFoobar`. + - [ ] If fields of `ValueFoobar` must be interleaved, implement + `fielder` interface. + - [ ] Implement `fieldLen`. + - Returns the byte size of each field. + - [ ] Implement `fieldSet`. + - Sets field number `i` using bytes from `b`. + - [ ] Implement `fieldGet`. + - Returns field number `i` as a slice of bytes. + - `bin/codec.go` + - [ ] In function `decodeValue`, add case `*ValueFoobar`. + - Converts `*ValueFoobar` to `rbxfile.ValueFoobar`. + - [ ] In function `encodeValue`, add case `rbxfile.ValueFoobar`. + - Converts `rbxfile.ValueFoobar` to `*ValueFoobar`. diff --git a/vendor/git.itzana.me/itzaname/rbxfile/values.go b/vendor/git.itzana.me/itzaname/rbxfile/values.go new file mode 100644 index 0000000..c30ed06 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxfile/values.go @@ -0,0 +1,950 @@ +package rbxfile + +import ( + "git.itzana.me/itzaname/rbxapi" + "strconv" + "strings" +) + +// Type represents a Roblox type. +type Type byte + +// String returns a string representation of the type. If the type is not +// valid, then the returned value will be "Invalid". +func (t Type) String() string { + s, ok := typeStrings[t] + if !ok { + return "Invalid" + } + return s +} + +const ( + TypeInvalid Type = iota + TypeString + TypeBinaryString + TypeProtectedString + TypeContent + TypeBool + TypeInt + TypeFloat + TypeDouble + TypeUDim + TypeUDim2 + TypeRay + TypeFaces + TypeAxes + TypeBrickColor + TypeColor3 + TypeVector2 + TypeVector3 + TypeCFrame + TypeToken + TypeReference + TypeVector3int16 + TypeVector2int16 + TypeNumberSequence + TypeColorSequence + TypeNumberRange + TypeRect2D + TypePhysicalProperties + TypeColor3uint8 + TypeInt64 + TypeSharedString +) + +// TypeFromString returns a Type from its string representation. TypeInvalid +// is returned if the string does not represent an existing Type. +func TypeFromString(s string) Type { + for typ, str := range typeStrings { + if s == str { + return typ + } + } + return TypeInvalid +} + +// TypeFromAPIString returns a Type from a string, using a rbxapi.Root if +// needed. Valid strings are compatible with type strings typically found in a +// rbxapi.Root. +func TypeFromAPIString(api rbxapi.Root, s string) Type { + if api != nil && api.GetEnum(s) != nil { + return TypeToken + } + s = strings.ToLower(s) + switch s { + case "coordinateframe": + return TypeCFrame + case "object": + return TypeReference + } + for typ, str := range typeStrings { + if s == strings.ToLower(str) { + return typ + } + } + return TypeInvalid + +} + +var typeStrings = map[Type]string{ + TypeString: "String", + TypeBinaryString: "BinaryString", + TypeProtectedString: "ProtectedString", + TypeContent: "Content", + TypeBool: "Bool", + TypeInt: "Int", + TypeFloat: "Float", + TypeDouble: "Double", + TypeUDim: "UDim", + TypeUDim2: "UDim2", + TypeRay: "Ray", + TypeFaces: "Faces", + TypeAxes: "Axes", + TypeBrickColor: "BrickColor", + TypeColor3: "Color3", + TypeVector2: "Vector2", + TypeVector3: "Vector3", + TypeCFrame: "CFrame", + TypeToken: "Token", + TypeReference: "Reference", + TypeVector3int16: "Vector3int16", + TypeVector2int16: "Vector2int16", + TypeNumberSequence: "NumberSequence", + TypeColorSequence: "ColorSequence", + TypeNumberRange: "NumberRange", + TypeRect2D: "Rect2D", + TypePhysicalProperties: "PhysicalProperties", + TypeColor3uint8: "Color3uint8", + TypeInt64: "Int64", + TypeSharedString: "SharedString", +} + +// Value holds a value of a particular Type. +type Value interface { + // Type returns an identifier indicating the type. + Type() Type + + // String returns a string representation of the current value. + String() string + + // Copy returns a copy of the value, which can be safely modified. + Copy() Value +} + +// NewValue returns new Value of the given Type. The initial value will not +// necessarily be the zero for the type. If the given type is invalid, then a +// nil value is returned. +func NewValue(typ Type) Value { + newValue, ok := valueGenerators[typ] + if !ok { + return nil + } + return newValue() +} + +type valueGenerator func() Value + +var valueGenerators = map[Type]valueGenerator{ + TypeString: newValueString, + TypeBinaryString: newValueBinaryString, + TypeProtectedString: newValueProtectedString, + TypeContent: newValueContent, + TypeBool: newValueBool, + TypeInt: newValueInt, + TypeFloat: newValueFloat, + TypeDouble: newValueDouble, + TypeUDim: newValueUDim, + TypeUDim2: newValueUDim2, + TypeRay: newValueRay, + TypeFaces: newValueFaces, + TypeAxes: newValueAxes, + TypeBrickColor: newValueBrickColor, + TypeColor3: newValueColor3, + TypeVector2: newValueVector2, + TypeVector3: newValueVector3, + TypeCFrame: newValueCFrame, + TypeToken: newValueToken, + TypeReference: newValueReference, + TypeVector3int16: newValueVector3int16, + TypeVector2int16: newValueVector2int16, + TypeNumberSequence: newValueNumberSequence, + TypeColorSequence: newValueColorSequence, + TypeNumberRange: newValueNumberRange, + TypeRect2D: newValueRect2D, + TypePhysicalProperties: newValuePhysicalProperties, + TypeColor3uint8: newValueColor3uint8, + TypeInt64: newValueInt64, + TypeSharedString: newValueSharedString, +} + +func joinstr(a ...string) string { + n := 0 + for i := 0; i < len(a); i++ { + n += len(a[i]) + } + + b := make([]byte, n) + bp := 0 + for _, s := range a { + bp += copy(b[bp:], s) + } + return string(b) +} + +//////////////////////////////////////////////////////////////// +// Values + +type ValueString []byte + +func newValueString() Value { + return make(ValueString, 0) +} + +func (ValueString) Type() Type { + return TypeString +} +func (t ValueString) String() string { + return string(t) +} +func (t ValueString) Copy() Value { + c := make(ValueString, len(t)) + copy(c, t) + return c +} + +//////////////// + +type ValueBinaryString []byte + +func newValueBinaryString() Value { + return make(ValueBinaryString, 0) +} + +func (ValueBinaryString) Type() Type { + return TypeBinaryString +} +func (t ValueBinaryString) String() string { + return string(t) +} +func (t ValueBinaryString) Copy() Value { + c := make(ValueBinaryString, len(t)) + copy(c, t) + return c +} + +//////////////// + +type ValueProtectedString []byte + +func newValueProtectedString() Value { + return make(ValueProtectedString, 0) +} + +func (ValueProtectedString) Type() Type { + return TypeProtectedString +} +func (t ValueProtectedString) String() string { + return string(t) +} +func (t ValueProtectedString) Copy() Value { + c := make(ValueProtectedString, len(t)) + copy(c, t) + return c +} + +//////////////// + +type ValueContent []byte + +func newValueContent() Value { + return make(ValueContent, 0) +} + +func (ValueContent) Type() Type { + return TypeContent +} +func (t ValueContent) String() string { + return string(t) +} +func (t ValueContent) Copy() Value { + c := make(ValueContent, len(t)) + copy(c, t) + return c +} + +//////////////// + +type ValueBool bool + +func newValueBool() Value { + return *new(ValueBool) +} + +func (ValueBool) Type() Type { + return TypeBool +} +func (t ValueBool) String() string { + if t { + return "true" + } else { + return "false" + } +} +func (t ValueBool) Copy() Value { + return t +} + +//////////////// + +type ValueInt int32 + +func newValueInt() Value { + return *new(ValueInt) +} + +func (ValueInt) Type() Type { + return TypeInt +} +func (t ValueInt) String() string { + return strconv.FormatInt(int64(t), 10) +} +func (t ValueInt) Copy() Value { + return t +} + +//////////////// + +type ValueFloat float32 + +func newValueFloat() Value { + return *new(ValueFloat) +} + +func (ValueFloat) Type() Type { + return TypeFloat +} +func (t ValueFloat) String() string { + return strconv.FormatFloat(float64(t), 'f', -1, 32) +} +func (t ValueFloat) Copy() Value { + return t +} + +//////////////// + +type ValueDouble float64 + +func newValueDouble() Value { + return *new(ValueDouble) +} + +func (ValueDouble) Type() Type { + return TypeDouble +} +func (t ValueDouble) String() string { + return strconv.FormatFloat(float64(t), 'f', -1, 64) +} +func (t ValueDouble) Copy() Value { + return t +} + +//////////////// + +type ValueUDim struct { + Scale float32 + Offset int32 +} + +func newValueUDim() Value { + return *new(ValueUDim) +} + +func (ValueUDim) Type() Type { + return TypeUDim +} +func (t ValueUDim) String() string { + return joinstr( + strconv.FormatFloat(float64(t.Scale), 'f', -1, 32), + ", ", + strconv.FormatInt(int64(t.Offset), 10), + ) +} +func (t ValueUDim) Copy() Value { + return t +} + +//////////////// + +type ValueUDim2 struct { + X, Y ValueUDim +} + +func newValueUDim2() Value { + return *new(ValueUDim2) +} + +func (ValueUDim2) Type() Type { + return TypeUDim2 +} +func (t ValueUDim2) String() string { + return joinstr( + "{", + t.X.String(), + "}, {", + t.Y.String(), + "}", + ) +} +func (t ValueUDim2) Copy() Value { + return t +} + +//////////////// + +type ValueRay struct { + Origin, Direction ValueVector3 +} + +func newValueRay() Value { + return *new(ValueRay) +} + +func (ValueRay) Type() Type { + return TypeRay +} +func (t ValueRay) String() string { + return joinstr( + "{", + t.Origin.String(), + "}, {", + t.Direction.String(), + "}", + ) +} +func (t ValueRay) Copy() Value { + return t +} + +//////////////// + +type ValueFaces struct { + Right, Top, Back, Left, Bottom, Front bool +} + +func newValueFaces() Value { + return *new(ValueFaces) +} + +func (ValueFaces) Type() Type { + return TypeFaces +} +func (t ValueFaces) String() string { + s := make([]string, 0, 6) + if t.Front { + s = append(s, "Front") + } + if t.Bottom { + s = append(s, "Bottom") + } + if t.Left { + s = append(s, "Left") + } + if t.Back { + s = append(s, "Back") + } + if t.Top { + s = append(s, "Top") + } + if t.Right { + s = append(s, "Right") + } + + return strings.Join(s, ", ") +} +func (t ValueFaces) Copy() Value { + return t +} + +//////////////// + +type ValueAxes struct { + X, Y, Z bool +} + +func newValueAxes() Value { + return *new(ValueAxes) +} + +func (ValueAxes) Type() Type { + return TypeAxes +} +func (t ValueAxes) String() string { + s := make([]string, 0, 3) + if t.X { + s = append(s, "X") + } + if t.Y { + s = append(s, "Y") + } + if t.Z { + s = append(s, "Z") + } + + return strings.Join(s, ", ") +} +func (t ValueAxes) Copy() Value { + return t +} + +//////////////// + +type ValueBrickColor uint32 + +func newValueBrickColor() Value { + return *new(ValueBrickColor) +} + +func (ValueBrickColor) Type() Type { + return TypeBrickColor +} +func (t ValueBrickColor) String() string { + return strconv.FormatUint(uint64(t), 10) +} +func (t ValueBrickColor) Copy() Value { + return t +} + +//////////////// + +type ValueColor3 struct { + R, G, B float32 +} + +func newValueColor3() Value { + return *new(ValueColor3) +} + +func (ValueColor3) Type() Type { + return TypeColor3 +} +func (t ValueColor3) String() string { + return joinstr( + strconv.FormatFloat(float64(t.R), 'f', -1, 32), + ", ", + strconv.FormatFloat(float64(t.G), 'f', -1, 32), + ", ", + strconv.FormatFloat(float64(t.B), 'f', -1, 32), + ) +} +func (t ValueColor3) Copy() Value { + return t +} + +//////////////// + +type ValueVector2 struct { + X, Y float32 +} + +func newValueVector2() Value { + return *new(ValueVector2) +} + +func (ValueVector2) Type() Type { + return TypeVector2 +} +func (t ValueVector2) String() string { + return joinstr( + strconv.FormatFloat(float64(t.X), 'f', -1, 32), + ", ", + strconv.FormatFloat(float64(t.Y), 'f', -1, 32), + ) +} +func (t ValueVector2) Copy() Value { + return t +} + +//////////////// + +type ValueVector3 struct { + X, Y, Z float32 +} + +func newValueVector3() Value { + return *new(ValueVector3) +} + +func (ValueVector3) Type() Type { + return TypeVector3 +} +func (t ValueVector3) String() string { + return joinstr( + strconv.FormatFloat(float64(t.X), 'f', -1, 32), + ", ", + strconv.FormatFloat(float64(t.Y), 'f', -1, 32), + ", ", + strconv.FormatFloat(float64(t.Z), 'f', -1, 32), + ) +} +func (t ValueVector3) Copy() Value { + return t +} + +//////////////// + +type ValueCFrame struct { + Position ValueVector3 + Rotation [9]float32 +} + +func newValueCFrame() Value { + return ValueCFrame{ + Position: ValueVector3{0, 0, 0}, + Rotation: [9]float32{1, 0, 0, 0, 1, 0, 0, 0, 1}, + } +} + +func (ValueCFrame) Type() Type { + return TypeCFrame +} +func (t ValueCFrame) String() string { + s := make([]string, 12) + s[0] = strconv.FormatFloat(float64(t.Position.X), 'f', -1, 32) + s[1] = strconv.FormatFloat(float64(t.Position.Y), 'f', -1, 32) + s[2] = strconv.FormatFloat(float64(t.Position.Z), 'f', -1, 32) + for i, f := range t.Rotation { + s[i+3] = strconv.FormatFloat(float64(f), 'f', -1, 32) + } + return strings.Join(s, ", ") +} +func (t ValueCFrame) Copy() Value { + return t +} + +//////////////// + +type ValueToken uint32 + +func newValueToken() Value { + return *new(ValueToken) +} + +func (ValueToken) Type() Type { + return TypeToken +} +func (t ValueToken) String() string { + return strconv.FormatInt(int64(t), 10) +} +func (t ValueToken) Copy() Value { + return t +} + +//////////////// + +type ValueReference struct { + *Instance +} + +func newValueReference() Value { + return *new(ValueReference) +} + +func (ValueReference) Type() Type { + return TypeReference +} +func (t ValueReference) String() string { + if t.Instance == nil { + return "" + } + return t.Name() +} +func (t ValueReference) Copy() Value { + return t +} + +//////////////// + +type ValueVector3int16 struct { + X, Y, Z int16 +} + +func newValueVector3int16() Value { + return *new(ValueVector3int16) +} + +func (ValueVector3int16) Type() Type { + return TypeVector3int16 +} +func (t ValueVector3int16) String() string { + return joinstr( + strconv.FormatInt(int64(t.X), 10), + ", ", + strconv.FormatInt(int64(t.Y), 10), + ", ", + strconv.FormatInt(int64(t.Z), 10), + ) +} +func (t ValueVector3int16) Copy() Value { + return t +} + +//////////////// + +type ValueVector2int16 struct { + X, Y int16 +} + +func newValueVector2int16() Value { + return *new(ValueVector2int16) +} + +func (ValueVector2int16) Type() Type { + return TypeVector2int16 +} +func (t ValueVector2int16) String() string { + return joinstr( + strconv.FormatInt(int64(t.X), 10), + ", ", + strconv.FormatInt(int64(t.Y), 10), + ) +} +func (t ValueVector2int16) Copy() Value { + return t +} + +//////////////// + +type ValueNumberSequenceKeypoint struct { + Time, Value, Envelope float32 +} + +func (t ValueNumberSequenceKeypoint) String() string { + return joinstr( + strconv.FormatFloat(float64(t.Time), 'f', -1, 32), + " ", + strconv.FormatFloat(float64(t.Value), 'f', -1, 32), + " ", + strconv.FormatFloat(float64(t.Envelope), 'f', -1, 32), + ) +} + +type ValueNumberSequence []ValueNumberSequenceKeypoint + +func newValueNumberSequence() Value { + return make(ValueNumberSequence, 0, 8) +} + +func (ValueNumberSequence) Type() Type { + return TypeNumberSequence +} +func (t ValueNumberSequence) String() string { + b := make([]byte, 0, 64) + for _, v := range t { + b = append(b, []byte(v.String())...) + b = append(b, ' ') + } + return string(b) +} +func (t ValueNumberSequence) Copy() Value { + c := make(ValueNumberSequence, len(t)) + copy(c, t) + return c +} + +//////////////// + +type ValueColorSequenceKeypoint struct { + Time float32 + Value ValueColor3 + Envelope float32 +} + +func (t ValueColorSequenceKeypoint) String() string { + return joinstr( + strconv.FormatFloat(float64(t.Time), 'f', -1, 32), + " ", + strconv.FormatFloat(float64(t.Value.R), 'f', -1, 32), + " ", + strconv.FormatFloat(float64(t.Value.G), 'f', -1, 32), + " ", + strconv.FormatFloat(float64(t.Value.B), 'f', -1, 32), + " ", + strconv.FormatFloat(float64(t.Envelope), 'f', -1, 32), + ) +} + +type ValueColorSequence []ValueColorSequenceKeypoint + +func newValueColorSequence() Value { + return make(ValueColorSequence, 0, 8) +} + +func (ValueColorSequence) Type() Type { + return TypeColorSequence +} +func (t ValueColorSequence) String() string { + b := make([]byte, 0, 64) + for _, v := range t { + b = append(b, []byte(v.String())...) + b = append(b, ' ') + } + return string(b) +} +func (t ValueColorSequence) Copy() Value { + c := make(ValueColorSequence, len(t)) + copy(c, t) + return c +} + +//////////////// + +type ValueNumberRange struct { + Min, Max float32 +} + +func newValueNumberRange() Value { + return *new(ValueNumberRange) +} + +func (ValueNumberRange) Type() Type { + return TypeNumberRange +} +func (t ValueNumberRange) String() string { + return joinstr( + strconv.FormatFloat(float64(t.Min), 'f', -1, 32), + " ", + strconv.FormatFloat(float64(t.Max), 'f', -1, 32), + ) +} +func (t ValueNumberRange) Copy() Value { + return t +} + +//////////////// + +type ValueRect2D struct { + Min, Max ValueVector2 +} + +func newValueRect2D() Value { + return *new(ValueRect2D) +} + +func (ValueRect2D) Type() Type { + return TypeRect2D +} +func (t ValueRect2D) String() string { + return joinstr( + strconv.FormatFloat(float64(t.Min.X), 'f', -1, 32), + ", ", + strconv.FormatFloat(float64(t.Min.Y), 'f', -1, 32), + ", ", + strconv.FormatFloat(float64(t.Max.X), 'f', -1, 32), + ", ", + strconv.FormatFloat(float64(t.Max.Y), 'f', -1, 32), + ) +} +func (t ValueRect2D) Copy() Value { + return t +} + +//////////////// + +type ValuePhysicalProperties struct { + CustomPhysics bool + Density float32 + Friction float32 + Elasticity float32 + FrictionWeight float32 + ElasticityWeight float32 +} + +func newValuePhysicalProperties() Value { + return *new(ValuePhysicalProperties) +} + +func (ValuePhysicalProperties) Type() Type { + return TypePhysicalProperties +} +func (t ValuePhysicalProperties) String() string { + if t.CustomPhysics { + return joinstr( + strconv.FormatFloat(float64(t.Density), 'f', -1, 32), ", ", + strconv.FormatFloat(float64(t.Friction), 'f', -1, 32), ", ", + strconv.FormatFloat(float64(t.Elasticity), 'f', -1, 32), ", ", + strconv.FormatFloat(float64(t.FrictionWeight), 'f', -1, 32), ", ", + strconv.FormatFloat(float64(t.ElasticityWeight), 'f', -1, 32), + ) + } + return "nil" +} +func (t ValuePhysicalProperties) Copy() Value { + return t +} + +//////////////// + +type ValueColor3uint8 struct { + R, G, B byte +} + +func newValueColor3uint8() Value { + return *new(ValueColor3uint8) +} + +func (ValueColor3uint8) Type() Type { + return TypeColor3uint8 +} +func (t ValueColor3uint8) String() string { + return joinstr( + strconv.FormatUint(uint64(t.R), 10), + ", ", + strconv.FormatUint(uint64(t.G), 10), + ", ", + strconv.FormatUint(uint64(t.B), 10), + ) +} +func (t ValueColor3uint8) Copy() Value { + return t +} + +//////////////// + +type ValueInt64 int64 + +func newValueInt64() Value { + return *new(ValueInt64) +} + +func (ValueInt64) Type() Type { + return TypeInt64 +} + +func (t ValueInt64) String() string { + return strconv.FormatInt(int64(t), 10) +} + +func (t ValueInt64) Copy() Value { + return t +} + +//////////////// + +type ValueSharedString []byte + +func newValueSharedString() Value { + return make(ValueSharedString, 0) +} + +func (ValueSharedString) Type() Type { + return TypeSharedString +} +func (t ValueSharedString) String() string { + return string(t) +} +func (t ValueSharedString) Copy() Value { + c := make(ValueSharedString, len(t)) + copy(c, t) + return c +} diff --git a/vendor/git.itzana.me/itzaname/rbxfile/xml/codec.go b/vendor/git.itzana.me/itzaname/rbxfile/xml/codec.go new file mode 100644 index 0000000..0b306e9 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxfile/xml/codec.go @@ -0,0 +1,1597 @@ +package xml + +import ( + "bytes" + "encoding/base64" + "errors" + "fmt" + "git.itzana.me/itzaname/rbxapi" + "git.itzana.me/itzaname/rbxfile" + "io" + "io/ioutil" + "sort" + "strconv" + "strings" +) + +// RobloxCodec implements Decoder and Encoder to emulate Roblox's internal +// codec as closely as possible. +type RobloxCodec struct { + // API can be set to yield a more correct encoding or decoding by + // providing information about each class. If API is nil, the codec will + // try to use other available information, but may not be fully accurate. + API rbxapi.Root + + // ExcludeReferent determines whether the "referent" attribute should be + // added to Item tags when encoding. + ExcludeReferent bool + + // ExcludeExternal determines whether standard tags should be + // added to the root tag when encoding. + ExcludeExternal bool + + // ExcludeInvalidAPI determines whether invalid items are excluded when + // encoding or decoding. An invalid item is an instance or property that + // does not exist or has incorrect information, according to a provided + // rbxapi.API. + // + // If true, then warnings will be emitted for invalid items, and the items + // will not be included in the output. If false, then warnings are still + // emitted, but invalid items are handled as if they were valid. This + // applies when decoding from a Document, and when encoding from a + // rbxfile.Root. + // + // Since an API may exclude some items even though they're correct, it is + // generally preferred to set ExcludeInvalidAPI to false, so that false + // negatives do not lead to lost data. + ExcludeInvalidAPI bool + + // ExcludeMetadata determines whether tags should be included while + // encoding. + ExcludeMetadata bool +} + +func (c RobloxCodec) Decode(document *Document) (root *rbxfile.Root, err error) { + if document == nil { + return nil, fmt.Errorf("document is nil") + } + + dec := &rdecoder{ + document: document, + codec: c, + root: new(rbxfile.Root), + instLookup: make(rbxfile.References), + } + + dec.decode() + return dec.root, dec.err +} + +func generateClassMembers(api rbxapi.Root, className string) map[string]rbxapi.Property { + if api == nil { + return nil + } + + props := map[string]rbxapi.Property{} + class := api.GetClass(className) + for class != nil { + for _, member := range class.GetMembers() { + prop, ok := member.(rbxapi.Property) + if !ok { + continue + } + if _, ok := props[prop.GetName()]; !ok { + props[prop.GetName()] = prop + } + } + class = api.GetClass(class.GetSuperclass()) + } + return props +} + +type rdecoder struct { + document *Document + codec RobloxCodec + root *rbxfile.Root + err error + instLookup rbxfile.References + propRefs []rbxfile.PropRef + stringRefs []rbxfile.PropRef +} + +func (dec *rdecoder) decode() error { + if dec.err != nil { + return dec.err + } + + dec.root = new(rbxfile.Root) + dec.root.Instances, _ = dec.getItems(nil, dec.document.Root.Tags, nil) + + for _, tag := range dec.document.Root.Tags { + switch tag.StartName { + case "Meta": + key, ok := tag.AttrValue("name") + if !ok { + continue + } + if dec.root.Metadata == nil { + dec.root.Metadata = make(map[string]string) + } + dec.root.Metadata[key] = tag.Text + case "SharedStrings": + for _, tag := range tag.Tags { + if tag.StartName != "SharedString" { + continue + } + hash, ok := tag.AttrValue("md5") + if !ok { + continue + } + key, err := ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, strings.NewReader(hash))) + if err != nil { + continue + } + value, err := ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, strings.NewReader(getContent(tag)))) + if err != nil { + continue + } + if len(value) == 0 { + // Overide the value if it's fucked??? + value, err = ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, strings.NewReader(hash))) + if err != nil { + continue + } + } + for _, ref := range dec.stringRefs { + if ref.Reference == string(key) { + ref.Instance.Properties[ref.Property] = rbxfile.ValueSharedString(value) + } + } + } + } + } + + for _, propRef := range dec.propRefs { + dec.instLookup.Resolve(propRef) + } + + return nil +} + +func (dec *rdecoder) getItems(parent *rbxfile.Instance, tags []*Tag, classMembers map[string]rbxapi.Property) (instances []*rbxfile.Instance, properties map[string]rbxfile.Value) { + properties = make(map[string]rbxfile.Value) + hasProps := false + + for _, tag := range tags { + switch tag.StartName { + case "Item": + className, ok := tag.AttrValue("class") + if !ok { + dec.document.Warnings = append(dec.document.Warnings, errors.New("item with missing class attribute")) + continue + } + + classMemb := generateClassMembers(dec.codec.API, className) + if dec.codec.API != nil { + if dec.codec.API.GetClass(className) == nil { + dec.document.Warnings = append(dec.document.Warnings, fmt.Errorf("invalid class name `%s`", className)) + if dec.codec.ExcludeInvalidAPI { + continue + } + } + } + + instance := rbxfile.NewInstance(className, nil) + referent, ok := tag.AttrValue("referent") + if ok && len(referent) > 0 { + instance.Reference = referent + if !rbxfile.IsEmptyReference(referent) { + dec.instLookup[referent] = instance + } + } + + var children []*rbxfile.Instance + children, instance.Properties = dec.getItems(instance, tag.Tags, classMemb) + for _, child := range children { + instance.AddChild(child) + } + + instances = append(instances, instance) + + case "Properties": + if hasProps || parent == nil { + continue + } + hasProps = true + + for _, property := range tag.Tags { + name, value, ok := dec.getProperty(property, parent, classMembers) + if ok { + properties[name] = value + } + } + } + } + + return instances, properties +} + +// DecodeProperties decodes a list of tags as properties to a given instance. +// Returns a list of unresolved references. +func (c RobloxCodec) DecodeProperties(tags []*Tag, inst *rbxfile.Instance, refs rbxfile.References) (propRefs []rbxfile.PropRef) { + dec := &rdecoder{ + codec: c, + instLookup: refs, + } + + classMembers := generateClassMembers(dec.codec.API, inst.ClassName) + if dec.codec.API != nil && dec.codec.API.GetClass(inst.ClassName) == nil && dec.codec.ExcludeInvalidAPI { + return nil + } + + for _, property := range tags { + name, value, ok := dec.getProperty(property, inst, classMembers) + if ok { + inst.Properties[name] = value + } + } + + return dec.propRefs +} + +func (dec *rdecoder) getProperty(tag *Tag, instance *rbxfile.Instance, classMembers map[string]rbxapi.Property) (name string, value rbxfile.Value, ok bool) { + name, ok = tag.AttrValue("name") + if !ok { + return "", nil, false + } + + var valueType string + var enum rbxapi.Enum + if dec.codec.API != nil && classMembers != nil { + // Determine property type from API. + propAPI, ok := classMembers[name] + if ok { + valueType = propAPI.GetValueType().GetName() + if e := dec.codec.API.GetEnum(valueType); e != nil { + valueType = "token" + enum = e + } + goto processValue + } else if dec.codec.ExcludeInvalidAPI { + dec.document.Warnings = append(dec.document.Warnings, fmt.Errorf("invalid property name %s.`%s`", instance.ClassName, name)) + return "", nil, false + } + } + + // Guess property type from tag name + valueType = dec.codec.GetCanonType(tag.StartName) + +processValue: + value, ok = dec.getValue(tag, valueType, enum) + if !ok { + return "", nil, false + } + + switch value := value.(type) { + case rbxfile.ValueReference: + if ref := getContent(tag); !rbxfile.IsEmptyReference(ref) { + dec.propRefs = append(dec.propRefs, rbxfile.PropRef{ + Instance: instance, + Property: name, + Reference: ref, + }) + return "", nil, false + } + case rbxfile.ValueSharedString: + dec.stringRefs = append(dec.stringRefs, rbxfile.PropRef{ + Instance: instance, + Property: name, + Reference: string(value), + }) + return "", nil, false + } + + return name, value, ok +} + +// GetCanonType converts a string (usually from a tag name) to a decodable +// type. +func (RobloxCodec) GetCanonType(valueType string) string { + switch strings.ToLower(valueType) { + case "axes": + return "Axes" + case "binarystring": + return "BinaryString" + case "bool": + return "bool" + case "brickcolor": + return "BrickColor" + case "cframe", "coordinateframe": + return "CoordinateFrame" + case "color3": + return "Color3" + case "content": + return "Content" + case "double": + return "double" + case "faces": + return "Faces" + case "float": + return "float" + case "int": + return "int" + case "protectedstring": + return "ProtectedString" + case "ray": + return "Ray" + case "object", "ref": + return "Object" + case "string": + return "string" + case "token": + return "token" + case "udim": + return "UDim" + case "udim2": + return "UDim2" + case "vector2": + return "Vector2" + case "vector2int16": + return "Vector2int16" + case "vector3": + return "Vector3" + case "vector3int16": + return "Vector3int16" + case "numbersequence": + return "NumberSequence" + case "colorsequence": + return "ColorSequence" + case "numberrange": + return "NumberRange" + case "rect2d": + return "Rect2D" + case "physicalproperties": + return "PhysicalProperties" + case "color3uint8": + return "Color3uint8" + case "int64": + return "int64" + case "sharedstring": + return "SharedString" + } + return "" +} + +// Gets a rbxfile.Value from a property tag, using valueType to determine how +// the tag is interpreted. valueType must be an existing type as it appears in +// the API dump. If guessing the type, it should be converted to one of these +// first. +func (dec *rdecoder) getValue(tag *Tag, valueType string, enum rbxapi.Enum) (value rbxfile.Value, ok bool) { + switch valueType { + case "Axes": + var bits int32 + components{ + "axes": &bits, + }.getFrom(tag) + + return rbxfile.ValueAxes{ + X: bits&(1<<0) > 0, + Y: bits&(1<<1) > 0, + Z: bits&(1<<2) > 0, + }, true + + case "BinaryString": + dec := base64.NewDecoder(base64.StdEncoding, strings.NewReader(getContent(tag))) + v, err := ioutil.ReadAll(dec) + if err != nil { + return nil, false + } + return rbxfile.ValueBinaryString(v), true + + case "bool": + switch getContent(tag) { + case "false", "False", "FALSE": + return rbxfile.ValueBool(false), true + case "true", "True", "TRUE": + return rbxfile.ValueBool(true), true + default: + return nil, false + } + + case "BrickColor": + v, err := strconv.ParseUint(getContent(tag), 10, 32) + if err != nil { + return nil, false + } + return rbxfile.ValueBrickColor(v), true + + case "CoordinateFrame": + v := *new(rbxfile.ValueCFrame) + components{ + "X": &v.Position.X, + "Y": &v.Position.Y, + "Z": &v.Position.Z, + "R00": &v.Rotation[0], + "R01": &v.Rotation[1], + "R02": &v.Rotation[2], + "R10": &v.Rotation[3], + "R11": &v.Rotation[4], + "R12": &v.Rotation[5], + "R20": &v.Rotation[6], + "R21": &v.Rotation[7], + "R22": &v.Rotation[8], + }.getFrom(tag) + return v, true + + case "Color3": + content := getContent(tag) + if len(content) > 0 { + v, err := strconv.ParseUint(content, 10, 32) + if err != nil { + return nil, false + } + return rbxfile.ValueColor3{ + R: float32(v&0x00FF0000>>16) / 255, + G: float32(v&0x0000FF00>>8) / 255, + B: float32(v&0x000000FF) / 255, + }, true + } else { + //DIFF: If any tags are missing, entire value defaults. + v := *new(rbxfile.ValueColor3) + components{ + "R": &v.R, + "G": &v.G, + "B": &v.B, + }.getFrom(tag) + return v, true + } + + case "Content": + if tag.CData == nil && len(tag.Text) > 0 || tag.CData != nil && len(tag.CData) > 0 { + // Succeeds if CData is not nil but empty, even if Text is not + // empty. This is correct according to Roblox's codec. + return nil, false + } + + for _, subtag := range tag.Tags { + switch subtag.StartName { + case "binary": + dec.document.Warnings = append(dec.document.Warnings, errors.New("not reading binary data")) + fallthrough + case "hash": + // Ignored. + fallthrough + case "null": + //DIFF: If null tag has content, then `tag expected` error is + //thrown. + return rbxfile.ValueContent{}, true + case "url": + return rbxfile.ValueContent(getContent(subtag)), true + default: + //DIFF: Throws error `TextXmlParser::parse - Unknown tag ''.` + return nil, false + } + } + + // Tag has no subtags. + + //DIFF: Attempts to read end tag as a subtag, erroneously throwing an + //"unknown tag" error. + return nil, false + + case "double": + // TODO: check inf, nan, and overflow. ParseFloat reads special numbers + // in several forms. Depending on how Roblox parses such values, we may + // have to catch these forms early and treat them as invalid. + v, err := strconv.ParseFloat(getContent(tag), 64) + if err != nil { + return nil, false + } + return rbxfile.ValueDouble(v), true + + case "Faces": + var bits int32 + components{ + "faces": &bits, + }.getFrom(tag) + + return rbxfile.ValueFaces{ + Right: bits&(1<<0) > 0, + Top: bits&(1<<1) > 0, + Back: bits&(1<<2) > 0, + Left: bits&(1<<3) > 0, + Bottom: bits&(1<<4) > 0, + Front: bits&(1<<5) > 0, + }, true + + case "float": + v, err := strconv.ParseFloat(getContent(tag), 32) + if err != nil { + return nil, false + } + return rbxfile.ValueFloat(v), true + + case "int": + v, err := strconv.ParseInt(getContent(tag), 10, 32) + if err != nil { + // Allow constrained result, which matches Roblox behavior. + if err, ok := err.(*strconv.NumError); !ok || err.Err != strconv.ErrRange { + // In Roblox, invalid characters cause the property to be discarded + // (and therefore appear with the default value) rather than set to + // zero. + return nil, false + } + } + return rbxfile.ValueInt(v), true + + case "ProtectedString": + return rbxfile.ValueProtectedString(getContent(tag)), true + + case "Ray": + var origin, direction *Tag + components{ + "origin": &origin, + "direction": &direction, + }.getFrom(tag) + + v := *new(rbxfile.ValueRay) + + components{ + "X": &v.Origin.X, + "Y": &v.Origin.Y, + "Z": &v.Origin.Z, + }.getFrom(origin) + + components{ + "X": &v.Direction.X, + "Y": &v.Direction.Y, + "Z": &v.Direction.Z, + }.getFrom(direction) + + return v, true + + case "Object": + // Return empty ValueReference; this signals that the value will be + // acquired later. + return rbxfile.ValueReference{}, true + + case "string": + return rbxfile.ValueString(getContent(tag)), true + + case "token": + v, err := strconv.ParseInt(getContent(tag), 10, 32) + if err != nil { + return nil, false + } + if enum != nil { + // Verify that value is a valid enum item + for _, item := range enum.GetEnumItems() { + if int(v) == item.GetValue() { + return rbxfile.ValueToken(v), true + } + } + if dec.codec.ExcludeInvalidAPI { + dec.document.Warnings = append(dec.document.Warnings, fmt.Errorf("invalid item `%d` for enum %s", v, enum.GetName())) + return nil, false + } + } + // Assume that it is correct + return rbxfile.ValueToken(v), true + + case "UDim": + v := *new(rbxfile.ValueUDim) + components{ + "S": &v.Scale, + "O": &v.Offset, + }.getFrom(tag) + return v, true + + case "UDim2": + // DIFF: UDim2 is initialized with odd values + v := *new(rbxfile.ValueUDim2) + components{ + "XS": &v.X.Scale, + "XO": &v.X.Offset, + "YS": &v.Y.Scale, + "YO": &v.Y.Offset, + }.getFrom(tag) + return v, true + + case "Vector2": + // DIFF: If any component tags are missing, entire value fails + v := *new(rbxfile.ValueVector2) + components{ + "X": &v.X, + "Y": &v.Y, + }.getFrom(tag) + return v, true + + case "Vector2int16": + // Unknown; guessed + v := *new(rbxfile.ValueVector2int16) + components{ + "X": &v.X, + "Y": &v.Y, + }.getFrom(tag) + return v, true + + case "Vector3": + v := *new(rbxfile.ValueVector3) + components{ + "X": &v.X, + "Y": &v.Y, + "Z": &v.Z, + }.getFrom(tag) + return v, true + + case "Vector3int16": + // Unknown; guessed + v := *new(rbxfile.ValueVector3int16) + components{ + "X": &v.X, + "Y": &v.Y, + "Z": &v.Z, + }.getFrom(tag) + return v, true + + case "NumberSequence": + b := []byte(getContent(tag)) + v := make(rbxfile.ValueNumberSequence, 0, 4) + for i := 0; i < len(b); { + nsk := rbxfile.ValueNumberSequenceKeypoint{} + nsk.Time, i = scanFloat(b, i) + nsk.Value, i = scanFloat(b, i) + nsk.Envelope, i = scanFloat(b, i) + if i < 0 { + return nil, false + } + v = append(v, nsk) + } + return v, true + + case "ColorSequence": + b := []byte(getContent(tag)) + v := make(rbxfile.ValueColorSequence, 0, 4) + for i := 0; i < len(b); { + csk := rbxfile.ValueColorSequenceKeypoint{} + csk.Time, i = scanFloat(b, i) + csk.Value.R, i = scanFloat(b, i) + csk.Value.G, i = scanFloat(b, i) + csk.Value.B, i = scanFloat(b, i) + csk.Envelope, i = scanFloat(b, i) + if i < 0 { + return nil, false + } + v = append(v, csk) + } + return v, true + + case "NumberRange": + b := []byte(getContent(tag)) + v := *new(rbxfile.ValueNumberRange) + i := 0 + v.Min, i = scanFloat(b, i) + v.Max, i = scanFloat(b, i) + if i < 0 { + return nil, false + } + return v, true + + case "Rect2D": + var min, max *Tag + components{ + "min": &min, + "max": &max, + }.getFrom(tag) + + v := *new(rbxfile.ValueRect2D) + + components{ + "X": &v.Min.X, + "Y": &v.Min.Y, + }.getFrom(min) + + components{ + "X": &v.Max.X, + "Y": &v.Max.Y, + }.getFrom(max) + + return v, true + + case "PhysicalProperties": + v := *new(rbxfile.ValuePhysicalProperties) + var cp *Tag + components{ + "CustomPhysics": &cp, + "Density": &v.Density, + "Friction": &v.Friction, + "Elasticity": &v.Elasticity, + "FrictionWeight": &v.FrictionWeight, + "ElasticityWeight": &v.ElasticityWeight, + }.getFrom(tag) + vb, _ := dec.getValue(cp, "bool", enum) + v.CustomPhysics = bool(vb.(rbxfile.ValueBool)) + return v, true + + case "Color3uint8": + content := getContent(tag) + if len(content) > 0 { + v, err := strconv.ParseUint(content, 10, 32) + if err != nil { + return nil, false + } + return rbxfile.ValueColor3uint8{ + R: byte(v & 0x00FF0000 >> 16), + G: byte(v & 0x0000FF00 >> 8), + B: byte(v & 0x000000FF), + }, true + } else { + //DIFF: If any tags are missing, entire value defaults. + v := *new(rbxfile.ValueColor3uint8) + components{ + "R": &v.R, + "G": &v.G, + "B": &v.B, + }.getFrom(tag) + return v, true + } + + case "int64": + v, err := strconv.ParseInt(getContent(tag), 10, 64) + if err != nil { + if err, ok := err.(*strconv.NumError); !ok || err.Err != strconv.ErrRange { + return nil, false + } + } + return rbxfile.ValueInt64(v), true + + case "SharedString": + v, err := ioutil.ReadAll(base64.NewDecoder(base64.StdEncoding, strings.NewReader(getContent(tag)))) + if err != nil { + return nil, false + } + return rbxfile.ValueSharedString(v), true + + } + + return nil, false +} + +func scanFloat(b []byte, i int) (float32, int) { + if i < 0 || i >= len(b) { + return 0, -1 + } + s := i + for ; i < len(b); i++ { + if isSpace(b[i]) { + f, err := strconv.ParseFloat(string(b[s:i]), 32) + if err != nil { + return 0, -1 + } + for ; i < len(b); i++ { + if !isSpace(b[i]) { + break + } + } + return float32(f), i + } + } + return 0, -1 +} + +type components map[string]interface{} + +func (c components) getFrom(tag *Tag) { + if tag == nil { + return + } + + // Used to ensure that only the first matched tag is selected. + d := map[string]bool{} + + for _, subtag := range tag.Tags { + if p, ok := c[subtag.StartName]; ok && !d[subtag.StartName] { + d[subtag.StartName] = true + switch v := p.(type) { + case *uint8: + // Parsed as int32 % 256. + n, err := strconv.ParseInt(getContent(subtag), 10, 32) + if err != nil { + if err, ok := err.(*strconv.NumError); !ok || err.Err != strconv.ErrRange { + break + } + } + *v = uint8(n % 256) + case *int16: + n, err := strconv.ParseInt(getContent(subtag), 10, 16) + if err != nil { + if err, ok := err.(*strconv.NumError); !ok || err.Err != strconv.ErrRange { + break + } + } + *v = int16(n) + case *int32: + n, err := strconv.ParseInt(getContent(subtag), 10, 32) + if err != nil { + if err, ok := err.(*strconv.NumError); !ok || err.Err != strconv.ErrRange { + break + } + } + *v = int32(n) + case *float32: + if n, err := strconv.ParseFloat(getContent(subtag), 32); err == nil { + *v = float32(n) + } + case **Tag: + *v = subtag + } + } + } +} + +// Reads either the CData or the text of a tag. +func getContent(tag *Tag) string { + if tag.CData != nil { + // CData is preferred even if it is empty + return string(tag.CData) + } + return tag.Text +} + +type rencoder struct { + root *rbxfile.Root + codec RobloxCodec + document *Document + refs rbxfile.References + sharedStrings map[string][]byte + err error +} + +func (c RobloxCodec) Encode(root *rbxfile.Root) (document *Document, err error) { + enc := &rencoder{ + root: root, + codec: c, + refs: make(rbxfile.References), + sharedStrings: map[string][]byte{}, + } + + enc.encode() + return enc.document, enc.err + +} + +type sortTagsByNameAttr []*Tag + +func (t sortTagsByNameAttr) Len() int { + return len(t) +} +func (t sortTagsByNameAttr) Less(i, j int) bool { + return t[i].Attr[0].Value < t[j].Attr[0].Value +} +func (t sortTagsByNameAttr) Swap(i, j int) { + t[i], t[j] = t[j], t[i] +} + +type wrapWriter struct { + l int + n int + w io.Writer + nl []byte +} + +func newWrapWriter(length int, w io.Writer) *wrapWriter { + return &wrapWriter{l: length, w: w, nl: []byte{'\n'}} +} + +func (w *wrapWriter) Write(p []byte) (n int, err error) { + i := 0 + if w.n+len(p) >= w.l { + i = w.l - w.n + if n, err = w.w.Write(p[:i]); err != nil { + return n, err + } + if n, err = w.w.Write(w.nl); err != nil { + return n, err + } + w.n = 0 + } + n, err = w.w.Write(p[i:]) + w.n += n + return n, err +} + +func (enc *rencoder) encode() { + enc.document = &Document{ + Prefix: "", + Indent: "\t", + Suffix: "", + Root: NewRoot(), + } + if !enc.codec.ExcludeMetadata { + enc.document.Root.Tags = make([]*Tag, 0, len(enc.root.Metadata)) + for key, value := range enc.root.Metadata { + enc.document.Root.Tags = append(enc.document.Root.Tags, &Tag{ + StartName: "Meta", + Attr: []Attr{{Name: "name", Value: key}}, + Text: value, + }) + } + sort.Sort(sortTagsByNameAttr(enc.document.Root.Tags)) + } + if !enc.codec.ExcludeExternal { + enc.document.Root.Tags = append(enc.document.Root.Tags, + &Tag{StartName: "External", Text: "null"}, + &Tag{StartName: "External", Text: "nil"}, + ) + } + + for _, instance := range enc.root.Instances { + enc.encodeInstance(instance, enc.document.Root) + } + + if len(enc.sharedStrings) > 0 { + //TODO: Tags are sorted by hash. Check if they're sorted pre- or + //post-base64 encoding. + keys := make([]string, 0, len(enc.sharedStrings)) + for key := range enc.sharedStrings { + keys = append(keys, key) + } + sort.Strings(keys) + tag := &Tag{StartName: "SharedStrings", Tags: make([]*Tag, len(keys))} + var s strings.Builder + for i, key := range keys { + b64 := base64.NewEncoder(base64.StdEncoding, newWrapWriter(72, &s)) + b64.Write(enc.sharedStrings[key]) + b64.Close() + tag.Tags[i] = &Tag{ + StartName: "SharedString", + Attr: []Attr{{ + Name: "md5", + Value: base64.StdEncoding.EncodeToString([]byte(key)), + }}, + Text: s.String(), + } + s.Reset() + } + enc.document.Root.Tags = append(enc.document.Root.Tags, tag) + } +} + +func (enc *rencoder) encodeInstance(instance *rbxfile.Instance, parent *Tag) { + if enc.codec.API != nil { + if class := enc.codec.API.GetClass(instance.ClassName); class == nil { + enc.document.Warnings = append(enc.document.Warnings, fmt.Errorf("invalid class `%s`", instance.ClassName)) + if enc.codec.ExcludeInvalidAPI { + return + } + } + } + + ref := enc.refs.Get(instance) + properties := enc.encodeProperties(instance) + item := NewItem(instance.ClassName, ref, properties...) + if enc.codec.ExcludeReferent { + item.SetAttrValue("referent", "") + } + parent.Tags = append(parent.Tags, item) + + for _, child := range instance.Children { + enc.encodeInstance(child, item) + } +} + +func (c RobloxCodec) EncodeProperties(instance *rbxfile.Instance) (properties []*Tag) { + enc := &rencoder{codec: c} + return enc.encodeProperties(instance) +} + +func (enc *rencoder) encodeProperties(instance *rbxfile.Instance) (properties []*Tag) { + var apiMembers map[string]rbxapi.Property + if enc.codec.API != nil { + apiClass := enc.codec.API.GetClass(instance.ClassName) + if apiClass != nil { + m := apiClass.GetMembers() + apiMembers = make(map[string]rbxapi.Property, len(m)) + for _, member := range m { + if member, ok := member.(rbxapi.Property); ok { + apiMembers[member.GetName()] = member + } + } + } + } + + // Sort properties by name + sorted := make([]string, 0, len(instance.Properties)) + for name := range instance.Properties { + sorted = append(sorted, name) + } + sort.Strings(sorted) + + for _, name := range sorted { + value := instance.Properties[name] + if apiMembers != nil { + apiMember, ok := apiMembers[name] + if ok { + typ := apiMember.GetValueType().GetName() + token, istoken := value.(rbxfile.ValueToken) + enum := enc.codec.API.GetEnum(typ) + if istoken && enum == nil || !isCanonType(typ, value) { + enc.document.Warnings = append(enc.document.Warnings, + fmt.Errorf("invalid value type `%s` for property %s.%s (%s)", value, instance.ClassName, name, typ), + ) + if enc.codec.ExcludeInvalidAPI { + continue + } + } else if istoken && enum != nil { + for _, item := range enum.GetEnumItems() { + if int(token) == item.GetValue() { + goto finishToken + } + } + + enc.document.Warnings = append(enc.document.Warnings, + fmt.Errorf("invalid enum value `%d` for property %s.%s (%s)", uint32(token), instance.ClassName, name, enum.GetName()), + ) + if enc.codec.ExcludeInvalidAPI { + continue + } + + finishToken: + } + } else { + enc.document.Warnings = append(enc.document.Warnings, fmt.Errorf("invalid property %s.`%s`", instance.ClassName, name)) + if enc.codec.ExcludeInvalidAPI { + continue + } + } + } + + tag := enc.encodeProperty(instance.ClassName, name, value) + if tag != nil { + properties = append(properties, tag) + } + } + + return properties +} + +func (enc *rencoder) encodeProperty(class, prop string, value rbxfile.Value) *Tag { + attr := []Attr{Attr{Name: "name", Value: prop}} + switch value := value.(type) { + case rbxfile.ValueAxes: + var n uint64 + for i, b := range []bool{value.X, value.Y, value.Z} { + if b { + n |= (1 << uint(i)) + } + } + return &Tag{ + StartName: "Axes", + Attr: attr, + Tags: []*Tag{ + &Tag{ + StartName: "axes", + NoIndent: true, + Text: strconv.FormatUint(n, 10), + }, + }, + } + + case rbxfile.ValueBinaryString: + buf := new(bytes.Buffer) + sw := &lineSplit{w: buf, s: 72, n: 72} + bw := base64.NewEncoder(base64.StdEncoding, sw) + bw.Write([]byte(value)) + bw.Close() + tag := &Tag{ + StartName: "BinaryString", + Attr: attr, + NoIndent: true, + } + encodeContent(tag, buf.String()) + return tag + + case rbxfile.ValueBool: + var v string + if value { + v = "true" + } else { + v = "false" + } + return &Tag{ + StartName: "bool", + Attr: attr, + NoIndent: true, + Text: v, + } + + case rbxfile.ValueBrickColor: + return &Tag{ + StartName: "int", + Attr: attr, + NoIndent: true, + Text: strconv.FormatUint(uint64(value), 10), + } + + case rbxfile.ValueCFrame: + return &Tag{ + StartName: "CoordinateFrame", + Attr: attr, + Tags: []*Tag{ + &Tag{StartName: "X", NoIndent: true, Text: encodeFloat(value.Position.X)}, + &Tag{StartName: "Y", NoIndent: true, Text: encodeFloat(value.Position.Y)}, + &Tag{StartName: "Z", NoIndent: true, Text: encodeFloat(value.Position.Z)}, + &Tag{StartName: "R00", NoIndent: true, Text: encodeFloat(value.Rotation[0])}, + &Tag{StartName: "R01", NoIndent: true, Text: encodeFloat(value.Rotation[1])}, + &Tag{StartName: "R02", NoIndent: true, Text: encodeFloat(value.Rotation[2])}, + &Tag{StartName: "R10", NoIndent: true, Text: encodeFloat(value.Rotation[3])}, + &Tag{StartName: "R11", NoIndent: true, Text: encodeFloat(value.Rotation[4])}, + &Tag{StartName: "R12", NoIndent: true, Text: encodeFloat(value.Rotation[5])}, + &Tag{StartName: "R20", NoIndent: true, Text: encodeFloat(value.Rotation[6])}, + &Tag{StartName: "R21", NoIndent: true, Text: encodeFloat(value.Rotation[7])}, + &Tag{StartName: "R22", NoIndent: true, Text: encodeFloat(value.Rotation[8])}, + }, + } + + case rbxfile.ValueColor3: + r := uint64(value.R * 255) + g := uint64(value.G * 255) + b := uint64(value.B * 255) + return &Tag{ + StartName: "Color3", + Attr: attr, + NoIndent: true, + Text: strconv.FormatUint(0xFF<<24|r<<16|g<<8|b, 10), + } + + case rbxfile.ValueContent: + tag := &Tag{ + StartName: "Content", + Attr: attr, + NoIndent: true, + Tags: []*Tag{ + &Tag{ + StartName: "", + NoIndent: true, + }, + }, + } + if len(value) == 0 { + tag.Tags[0].StartName = "null" + } else { + tag.Tags[0].StartName = "url" + tag.Tags[0].Text = string(value) + } + return tag + + case rbxfile.ValueDouble: + return &Tag{ + StartName: "double", + Attr: attr, + NoIndent: true, + Text: encodeDouble(float64(value)), + } + + case rbxfile.ValueFaces: + var n uint64 + for i, b := range []bool{value.Right, value.Top, value.Back, value.Left, value.Bottom, value.Front} { + if b { + n |= (1 << uint(i)) + } + } + return &Tag{ + StartName: "Faces", + Attr: attr, + Tags: []*Tag{ + &Tag{ + StartName: "faces", + NoIndent: true, + Text: strconv.FormatUint(n, 10), + }, + }, + } + + case rbxfile.ValueFloat: + return &Tag{ + StartName: "float", + Attr: attr, + NoIndent: true, + Text: encodeFloat(float32(value)), + } + + case rbxfile.ValueInt: + return &Tag{ + StartName: "int", + Attr: attr, + NoIndent: true, + Text: strconv.FormatInt(int64(value), 10), + } + + case rbxfile.ValueProtectedString: + tag := &Tag{ + StartName: "ProtectedString", + Attr: attr, + NoIndent: true, + } + encodeContent(tag, string(value)) + return tag + + case rbxfile.ValueRay: + return &Tag{ + StartName: "Ray", + Attr: attr, + Tags: []*Tag{ + &Tag{ + StartName: "origin", + Tags: []*Tag{ + &Tag{StartName: "X", NoIndent: true, Text: encodeFloat(value.Origin.X)}, + &Tag{StartName: "Y", NoIndent: true, Text: encodeFloat(value.Origin.Y)}, + &Tag{StartName: "Z", NoIndent: true, Text: encodeFloat(value.Origin.Z)}, + }, + }, + &Tag{ + StartName: "direction", + Tags: []*Tag{ + &Tag{StartName: "X", NoIndent: true, Text: encodeFloat(value.Origin.X)}, + &Tag{StartName: "Y", NoIndent: true, Text: encodeFloat(value.Origin.Y)}, + &Tag{StartName: "Z", NoIndent: true, Text: encodeFloat(value.Origin.Z)}, + }, + }, + }, + } + + case rbxfile.ValueReference: + tag := &Tag{ + StartName: "Ref", + Attr: attr, + NoIndent: true, + } + + referent := value.Instance + if referent != nil { + tag.Text = enc.refs.Get(referent) + } else { + tag.Text = "null" + } + return tag + + case rbxfile.ValueString: + return &Tag{ + StartName: "string", + Attr: attr, + NoIndent: true, + Text: string(value), + } + + case rbxfile.ValueToken: + return &Tag{ + StartName: "token", + Attr: attr, + NoIndent: true, + Text: strconv.FormatUint(uint64(value), 10), + } + + case rbxfile.ValueUDim: + return &Tag{ + StartName: "UDim", + Attr: attr, + Tags: []*Tag{ + &Tag{StartName: "S", NoIndent: true, Text: encodeFloat(value.Scale)}, + &Tag{StartName: "O", NoIndent: true, Text: strconv.FormatInt(int64(value.Offset), 10)}, + }, + } + + case rbxfile.ValueUDim2: + return &Tag{ + StartName: "UDim2", + Attr: attr, + Tags: []*Tag{ + &Tag{StartName: "XS", NoIndent: true, Text: encodeFloat(value.X.Scale)}, + &Tag{StartName: "XO", NoIndent: true, Text: strconv.FormatInt(int64(value.X.Offset), 10)}, + &Tag{StartName: "YS", NoIndent: true, Text: encodeFloat(value.Y.Scale)}, + &Tag{StartName: "YO", NoIndent: true, Text: strconv.FormatInt(int64(value.Y.Offset), 10)}, + }, + } + + case rbxfile.ValueVector2: + return &Tag{ + StartName: "Vector2", + Attr: attr, + Tags: []*Tag{ + &Tag{StartName: "X", NoIndent: true, Text: encodeFloat(value.X)}, + &Tag{StartName: "Y", NoIndent: true, Text: encodeFloat(value.Y)}, + }, + } + + case rbxfile.ValueVector2int16: + return &Tag{ + StartName: "Vector2int16", + Attr: attr, + Tags: []*Tag{ + &Tag{StartName: "X", NoIndent: true, Text: strconv.FormatInt(int64(value.X), 10)}, + &Tag{StartName: "Y", NoIndent: true, Text: strconv.FormatInt(int64(value.Y), 10)}, + }, + } + + case rbxfile.ValueVector3: + return &Tag{ + StartName: "Vector3", + Attr: attr, + Tags: []*Tag{ + &Tag{StartName: "X", NoIndent: true, Text: encodeFloat(value.X)}, + &Tag{StartName: "Y", NoIndent: true, Text: encodeFloat(value.Y)}, + &Tag{StartName: "Z", NoIndent: true, Text: encodeFloat(value.Z)}, + }, + } + + case rbxfile.ValueVector3int16: + return &Tag{ + StartName: "Vector3int16", + Attr: attr, + Tags: []*Tag{ + &Tag{StartName: "X", NoIndent: true, Text: strconv.FormatInt(int64(value.X), 10)}, + &Tag{StartName: "Y", NoIndent: true, Text: strconv.FormatInt(int64(value.Y), 10)}, + &Tag{StartName: "Z", NoIndent: true, Text: strconv.FormatInt(int64(value.Z), 10)}, + }, + } + + case rbxfile.ValueNumberSequence: + b := make([]byte, 0, 16) + for _, nsk := range value { + b = append(b, []byte(encodeFloatPrec(nsk.Time, 6))...) + b = append(b, ' ') + b = append(b, []byte(encodeFloatPrec(nsk.Value, 6))...) + b = append(b, ' ') + b = append(b, []byte(encodeFloatPrec(nsk.Envelope, 6))...) + b = append(b, ' ') + } + return &Tag{ + StartName: "NumberSequence", + Attr: attr, + Text: string(b), + } + + case rbxfile.ValueColorSequence: + b := make([]byte, 0, 32) + for _, csk := range value { + b = append(b, []byte(encodeFloatPrec(csk.Time, 6))...) + b = append(b, ' ') + b = append(b, []byte(encodeFloatPrec(csk.Value.R, 6))...) + b = append(b, ' ') + b = append(b, []byte(encodeFloatPrec(csk.Value.G, 6))...) + b = append(b, ' ') + b = append(b, []byte(encodeFloatPrec(csk.Value.B, 6))...) + b = append(b, ' ') + b = append(b, []byte(encodeFloatPrec(csk.Envelope, 6))...) + b = append(b, ' ') + } + return &Tag{ + StartName: "ColorSequence", + Attr: attr, + Text: string(b), + } + + case rbxfile.ValueNumberRange: + b := make([]byte, 0, 8) + b = append(b, []byte(encodeFloatPrec(value.Min, 6))...) + b = append(b, ' ') + b = append(b, []byte(encodeFloatPrec(value.Max, 6))...) + b = append(b, ' ') + return &Tag{ + StartName: "NumberRange", + Attr: attr, + Text: string(b), + } + + case rbxfile.ValueRect2D: + return &Tag{ + StartName: "Rect2D", + Attr: attr, + Tags: []*Tag{ + &Tag{ + StartName: "min", + Tags: []*Tag{ + &Tag{StartName: "X", NoIndent: true, Text: encodeFloat(value.Min.X)}, + &Tag{StartName: "Y", NoIndent: true, Text: encodeFloat(value.Min.Y)}, + }, + }, + &Tag{ + StartName: "max", + Tags: []*Tag{ + &Tag{StartName: "X", NoIndent: true, Text: encodeFloat(value.Max.X)}, + &Tag{StartName: "Y", NoIndent: true, Text: encodeFloat(value.Max.Y)}, + }, + }, + }, + } + + case rbxfile.ValuePhysicalProperties: + if value.CustomPhysics { + return &Tag{ + StartName: "PhysicalProperties", + Attr: attr, + Tags: []*Tag{ + &Tag{StartName: "CustomPhysics", Text: "true"}, + &Tag{StartName: "Density", Text: encodeFloat(value.Density)}, + &Tag{StartName: "Friction", Text: encodeFloat(value.Friction)}, + &Tag{StartName: "Elasticity", Text: encodeFloat(value.Elasticity)}, + &Tag{StartName: "FrictionWeight", Text: encodeFloat(value.FrictionWeight)}, + &Tag{StartName: "ElasticityWeight", Text: encodeFloat(value.ElasticityWeight)}, + }, + } + } else { + return &Tag{ + StartName: "PhysicalProperties", + Attr: attr, + Tags: []*Tag{ + &Tag{StartName: "CustomPhysics", Text: "false"}, + }, + } + } + + case rbxfile.ValueColor3uint8: + r := uint64(value.R) + g := uint64(value.G) + b := uint64(value.B) + return &Tag{ + StartName: "Color3", + Attr: attr, + NoIndent: true, + Text: strconv.FormatUint(0xFF<<24|r<<16|g<<8|b, 10), + } + + case rbxfile.ValueInt64: + return &Tag{ + StartName: "int64", + Attr: attr, + NoIndent: true, + Text: strconv.FormatInt(int64(value), 10), + } + + case rbxfile.ValueSharedString: + buf := new(bytes.Buffer) + sw := &lineSplit{w: buf, s: 72, n: 72} + bw := base64.NewEncoder(base64.StdEncoding, sw) + bw.Write([]byte(value)) + bw.Close() + tag := &Tag{ + StartName: "SharedString", + Attr: attr, + NoIndent: true, + } + enc.sharedStrings[value.String()] = []byte("") // Normally empty???? + encodeContent(tag, buf.String()) + return tag + } + + return nil +} + +type lineSplit struct { + w io.Writer + s int + n int +} + +func (l *lineSplit) Write(p []byte) (n int, err error) { + for i := 0; ; { + var q []byte + if len(p[i:]) < l.n { + q = p[i:] + } else { + q = p[i : i+l.n] + } + n, err = l.w.Write(q) + if n < len(q) { + return + } + l.n -= len(q) + i += len(q) + if i >= len(p) { + break + } + if l.n <= 0 { + _, e := l.w.Write([]byte{'\n'}) + if e != nil { + return + } + l.n = l.s + } + } + return +} + +func encodeFloat(f float32) string { + return fixFloatExp(strconv.FormatFloat(float64(f), 'g', 9, 32), 3) +} + +func encodeFloatPrec(f float32, prec int) string { + return fixFloatExp(strconv.FormatFloat(float64(f), 'g', prec, 32), 3) +} + +func fixFloatExp(s string, n int) string { + if e := strings.Index(s, "e"); e >= 0 { + // Adjust exponent to have length of at least n, using leading zeros. + exp := s[e+2:] + if len(exp) < n { + s = s[:e+2] + strings.Repeat("0", n-len(exp)) + exp + } + } + return s +} + +func encodeDouble(f float64) string { + return strconv.FormatFloat(f, 'g', 9, 64) +} + +func encodeContent(tag *Tag, text string) { + if len(text) > 0 && strings.Index(text, "]]>") == -1 { + tag.CData = []byte(text) + return + } + tag.Text = text +} + +func isCanonType(t string, v rbxfile.Value) bool { + switch v.(type) { + case rbxfile.ValueAxes: + return t == "Axes" + case rbxfile.ValueBinaryString: + return t == "BinaryString" + case rbxfile.ValueBool: + return t == "bool" + case rbxfile.ValueBrickColor: + return t == "BrickColor" + case rbxfile.ValueCFrame: + return t == "CoordinateFrame" + case rbxfile.ValueColor3: + return t == "Color3" + case rbxfile.ValueContent: + return t == "Content" + case rbxfile.ValueDouble: + return t == "double" + case rbxfile.ValueFaces: + return t == "Faces" + case rbxfile.ValueFloat: + return t == "float" + case rbxfile.ValueInt: + return t == "int" + case rbxfile.ValueProtectedString: + return t == "ProtectedString" + case rbxfile.ValueRay: + return t == "Ray" + case rbxfile.ValueReference: + return t == "Object" + case rbxfile.ValueString: + return t == "string" + case rbxfile.ValueUDim: + return t == "UDim" + case rbxfile.ValueUDim2: + return t == "UDim2" + case rbxfile.ValueVector2: + return t == "Vector2" + case rbxfile.ValueVector2int16: + return t == "Vector2int16" + case rbxfile.ValueVector3: + return t == "Vector3" + case rbxfile.ValueVector3int16: + return t == "Vector3int16" + case rbxfile.ValueNumberSequence: + return t == "NumberSequence" + case rbxfile.ValueColorSequence: + return t == "ColorSequence" + case rbxfile.ValueNumberRange: + return t == "NumberRange" + case rbxfile.ValueRect2D: + return t == "Rect2D" + case rbxfile.ValuePhysicalProperties: + return t == "PhysicalProperties" + case rbxfile.ValueColor3uint8: + return t == "Color3uint8" + case rbxfile.ValueInt64: + return t == "int64" + case rbxfile.ValueSharedString: + return t == "SharedString" + } + return false +} diff --git a/vendor/git.itzana.me/itzaname/rbxfile/xml/document.go b/vendor/git.itzana.me/itzaname/rbxfile/xml/document.go new file mode 100644 index 0000000..d4d1356 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxfile/xml/document.go @@ -0,0 +1,1246 @@ +package xml + +// Decoder adapted from the standard XML package. + +// "DIFF" indicates a behavior that occurs in Roblox's codec, that is +// implemented differently in this codec. + +import ( + "bufio" + "bytes" + "errors" + "io" + "strconv" +) + +// Tag represents a Roblox XML tag construct. Unlike standard XML, the content +// of a tag must consist of the following, in order: +// 1) An optional CData section. +// 2) A sequence of zero or more whitespace, which is ignored (usually newlines and indentation). +// 3) A sequence of zero or more characters indicating textual content of the tag. +// 4) A sequence of zero or more complete tags, with optional whitespace between each. +type Tag struct { + // StartName is the name of the tag in the start tag. + StartName string + + // EndName is the name of the tag in the end tag. If empty, this is + // assumed to be equal to StartName. + EndName string + + // The attributes of the tag. + Attr []Attr + + // Empty indicates whether the tag has an empty-tag format. When encoding, + // the tag will be written in the empty-tag format, and any content will + // be ignored. When decoding, this value will be set if the decoded tag + // has the empty-tag format. + Empty bool + + // CData is a sequence of characters in a CDATA section. Only up to one + // section is allowed, and must be the first element in the tag. A nil + // array means that the tag does not contain a CDATA section. + CData []byte + + // Text is the textual content of the tag. + Text string + + // NoIndent indicates whether the tag contains prettifying whitespace, + // which occurs between the tag's CData and Text, as well as between each + // child tag. + // + // When decoding, this value is set to true if there is no whitespace of + // any kind between the CData and Text. It will only be set if the decoder + // has successfully detected global prefix and indent strings, but note + // that these do not affect how the whitespace is detected. + // + // When encoding, this value determines whether the tag and its + // descendants will be written with prettifying whitespace. + NoIndent bool + + // Tags is a list of child tags within the tag. + Tags []*Tag +} + +// AttrValue returns the value of the first attribute of the given name, and +// whether or not it exists. +func (t Tag) AttrValue(name string) (value string, exists bool) { + for _, a := range t.Attr { + if a.Name == name { + return a.Value, true + } + } + return "", false +} + +// SetAttrValue sets the value of the first attribute of the given name, if it +// exists. If value is an empty string, then the attribute will be removed +// instead. If the attribute does not exist and value is not empty, then the +// attribute is added. +func (t *Tag) SetAttrValue(name, value string) { + for i, a := range t.Attr { + if a.Name == name { + if value == "" { + t.Attr = append(t.Attr[:i], t.Attr[i+1:]...) + } else { + a.Value = value + } + return + } + } + if value == "" { + return + } + t.Attr = append(t.Attr, Attr{Name: name, Value: value}) +} + +// NewRoot initializes a Tag containing values standard to a root tag. +// Optionally, Item tags can be given as arguments, which will be added to the +// root as sub-tags. +func NewRoot(items ...*Tag) *Tag { + return &Tag{ + StartName: "roblox", + Attr: []Attr{ + Attr{ + Name: "xmlns:xmime", + Value: "http://www.w3.org/2005/05/xmlmime", + }, + Attr{ + Name: "xmlns:xsi", + Value: "http://www.w3.org/2001/XMLSchema-instance", + }, + Attr{ + Name: "xsi:noNamespaceSchemaLocation", + Value: "http://www.roblox.com/roblox.xsd", + }, + Attr{ + Name: "version", + Value: "4", + }, + }, + Tags: items, + } +} + +// NewItem initializes an "Item" Tag representing a Roblox class. +func NewItem(class, referent string, properties ...*Tag) *Tag { + return &Tag{ + StartName: "Item", + Attr: []Attr{ + Attr{Name: "class", Value: class}, + Attr{Name: "referent", Value: referent}, + }, + Tags: []*Tag{ + &Tag{ + StartName: "Properties", + Tags: properties, + }, + }, + } +} + +// NewProp initializes a basic property tag representing a property in a +// Roblox class. +func NewProp(valueType, propName, value string) *Tag { + return &Tag{ + StartName: valueType, + Attr: []Attr{ + Attr{Name: "name", Value: propName}, + }, + Text: value, + NoIndent: true, + } +} + +// Attr represents an attribute of a tag. +type Attr struct { + Name string + Value string +} + +//////////////////////////////////////////////////////////////// + +// Document represents an entire XML document. +type Document struct { + // Prefix is a string that appears at the start of each line in the + // document. + // + // When encoding, the prefix is added after each newline. Newlines are + // added automatically when either Prefix or Indent is not empty. + // + // When decoding, this value is set when indentation is detected in the + // document. When detected, the value becomes any leading whitespace + // before the root tag (at the start of the file). This only sets the + // value; no attempt is made to validate any other prettifying whitespace. + Prefix string + + // Indent is a string that indicates one level of indentation. + // + // When encoding, a sequence of indents appear after the Prefix, an amount + // equal to the current nesting depth in the markup. + // + // When decoding, this value is set when detecting indentation. It is set + // to the prettifying whitespace that occurs after the first newline and + // prefix, which occurs between the root tag's CDATA and Text data. This + // only sets the value; no attempt is made to validate any other + // prettifying whitespace. + Indent string + + // Suffix is a string that appears at the very end of the document. When + // encoding, this string is appended to the end of the file, after the + // root tag. When decoding, this value becomes any remaining text that + // appears after the root tag. + Suffix string + + // ExcludeRoot determines whether the root tag should be encoded. This can + // be combined with Prefix to write documents in-line. + ExcludeRoot bool + + // Root is the root tag in the document. + Root *Tag + + // Warnings is a list of non-fatal problems that have occurred. This will + // be cleared and populated when calling either ReadFrom and WriteTo. + // Codecs may also clear and populate this when decoding or encoding. + Warnings []error +} + +// A SyntaxError represents a syntax error in the XML input stream. +type SyntaxError struct { + Msg string + Line int +} + +func (e *SyntaxError) Error() string { + return "XML syntax error on line " + strconv.Itoa(e.Line) + ": " + e.Msg +} + +type decoder struct { + r io.ByteReader + buf bytes.Buffer + nextByte []byte + doc *Document + n int64 + err error + line int +} + +// Creates a SyntaxError with the current line number. +func (d *decoder) syntaxError(msg string) error { + return &SyntaxError{Msg: msg, Line: d.line} +} + +func (d *decoder) ignoreStartTag(err error) int { + // Treat error as warning. + d.doc.Warnings = append(d.doc.Warnings, err) + // Read until end of start tag. + for { + b, ok := d.mustgetc() + if !ok { + return -1 + } + if b == '>' { + break + } + } + return 0 +} + +func (d *decoder) decodeComment() int { + for { + if b, ok := d.mustgetc(); !ok { + return -1 + } else if b == '-' && d.match("->") { + break + } + } + return 2 +} + +//DIFF: Start tag parser has unexpected behavior that is difficult to +//pin-point. +func (d *decoder) decodeStartTag(tag *Tag) int { + b, ok := d.getc() + if !ok { + return -1 + } + + if b != '<' { + d.err = d.syntaxError("expected start tag") + return -1 + } + + if b, ok = d.mustgetc(); !ok { + return -1 + } + if b == '/' { + // + d.ungetc(b) + + if tag.StartName, ok = d.name(nameTag); !ok { + return d.ignoreStartTag(d.syntaxError("expected element name after <")) + } + + tag.Attr = make([]Attr, 0, 4) + for { + d.space() + if b, ok = d.mustgetc(); !ok { + return -1 + } + if b == '/' { + tag.Empty = true + if b, ok = d.mustgetc(); !ok { + return -1 + } + if b != '>' { + return d.ignoreStartTag(d.syntaxError("expected /> in element")) + } + break + } + if b == '>' { + break + } + d.ungetc(b) + + n := len(tag.Attr) + if n >= cap(tag.Attr) { + nattr := make([]Attr, n, 2*cap(tag.Attr)) + copy(nattr, tag.Attr) + tag.Attr = nattr + } + tag.Attr = tag.Attr[0 : n+1] + a := &tag.Attr[n] + if a.Name, ok = d.name(nameAttr); !ok { + return d.ignoreStartTag(d.syntaxError("expected attribute name in element")) + } + d.space() + if b, ok = d.mustgetc(); !ok { + return -1 + } + if b != '=' { + return d.ignoreStartTag(d.syntaxError("attribute name without = in element")) + } else { + d.space() + data := d.attrval() + if data == nil { + return -1 + } + a.Value = string(data) + } + } + return 1 +} + +func (d *decoder) decodeCData(tag *Tag) bool { + tag.CData = nil + + // attempt to read CData opener + const opener = "= 0; j-- { + d.ungetc(opener[j]) + } + return true + } + } + + // Have . + tag.CData = d.text(-1, true) + if tag.CData == nil { + return false + } + return true +} + +func (d *decoder) decodeText(tag *Tag) bool { + text := d.text(-1, false) + if text == nil { + tag.Text = "" + return false + } + tag.Text = string(text) + return true +} + +func (d *decoder) decodeEndTag(tag *Tag) bool { + b, ok := d.getc() + if !ok { + return false + } + + if b != '<' { + d.err = d.syntaxError("expected start tag") + return false + } + + if b, ok = d.mustgetc(); !ok { + return false + } + if b != '/' { + d.err = d.syntaxError("expected end tag") + return false + } + + // ' { + d.err = d.syntaxError("invalid characters between ") + return false + } + return true +} + +func (d *decoder) decodeTag(root bool) (tag *Tag, err error) { + if d.err != nil { + return nil, d.err + } + + tag = new(Tag) + noindent := false + nocontent := true + + if root { + // Attempt to detect prefix + p := d.readSpace() + if len(p) > 0 { + // Store it for later. Prefix will be unset if no indentation is + // detected. + d.doc.Prefix = string(p) + } + } + + startTagState := d.decodeStartTag(tag) + if startTagState < 0 { + return nil, d.err + } + if startTagState == 2 { + return nil, nil + } + + if root { + if tag.StartName != "roblox" { + d.err = d.syntaxError("no roblox tag") + return nil, d.err + } + + if v, ok := tag.AttrValue("version"); !ok { + //DIFF: returns success, but no data is read + d.err = d.syntaxError("version attribute not specified") + return nil, d.err + } else { + n, err := strconv.ParseInt(v, 10, 32) + if err != nil { + d.err = d.syntaxError("no version number") + return nil, d.err + } + if n < 4 { + d.err = d.syntaxError("schemaVersionLoading<4") + return nil, d.err + } + } + } + + if tag.Empty { + if startTagState == 0 { + return nil, nil + } + return + } + + if !d.decodeCData(tag) { + return nil, d.err + } + if len(tag.CData) > 0 { + nocontent = false + } + + // prettifying whitespace + if root { + // Attempt to detect indentation by looking at the (usually ignored) + // whitespace under the root tag after the CDATA. + ind := d.readSpace() + // Must contain a newline, otherwise it wouldn't be indentation. + if i := bytes.IndexByte(ind, '\n'); i > -1 { + if !bytes.HasPrefix(ind[i+1:], []byte(d.doc.Prefix)) { + // If line does not begin with the prefix detected previously, + // then assume that the whitespace is badly formed, and cease + // detection. + d.doc.Prefix = "" + } else { + // Found newline and prefix, all of the remaining whitespace + // indicates one level of indentation. + d.doc.Indent = string(ind[i+1+len(d.doc.Prefix):]) + } + } + } else { + if d.doc.Prefix != "" || d.doc.Indent != "" { + if len(d.readSpace()) == 0 { + noindent = true + } + } else { + d.space() + } + } + + if !d.decodeText(tag) { + return nil, d.err + } + if len(tag.Text) > 0 { + nocontent = false + } + + for { + // prettifying whitespace between tags + d.space() + + b, ok := d.getc() + if !ok { + return nil, d.err + } + + if b != '<' { + d.err = d.syntaxError("expected tag") + return nil, d.err + } + + if b, ok = d.mustgetc(); !ok { + return nil, d.err + } + if b == '/' { + // 0 { + nocontent = false + } + + if !nocontent { + // Do not set NoIndent if the tag is empty. + tag.NoIndent = noindent + } + + if startTagState == 0 { + // Ignore the entire tag. + return nil, nil + } + + return tag, nil +} + +func (d *decoder) attrval() []byte { + b, ok := d.mustgetc() + if !ok { + return nil + } + // Handle quoted attribute values + if b == '"' { + return d.text(int(b), false) + } + + d.err = d.syntaxError("unquoted or missing attribute value in element") + return nil +} + +func (d *decoder) readSpace() []byte { + d.buf.Reset() + for { + b, ok := d.getc() + if !ok { + return d.buf.Bytes() + } + if !isSpace(b) { + d.ungetc(b) + return d.buf.Bytes() + } + d.buf.WriteByte(b) + } + return d.buf.Bytes() +} + +// Skip spaces if any +func (d *decoder) space() { + for { + b, ok := d.getc() + if !ok { + return + } + if !isSpace(b) { + d.ungetc(b) + return + } + } +} + +func isSpace(b byte) bool { + switch b { + case ' ', '\r', '\n', '\t', '\f': + return true + default: + return false + } +} + +// Read a single byte. +// If there is no byte to read, return ok==false +// and leave the error in d.err. +// Maintain line number. +func (d *decoder) getc() (b byte, ok bool) { + if d.err != nil { + return 0, false + } + + if len(d.nextByte) > 0 { + b, d.nextByte = d.nextByte[len(d.nextByte)-1], d.nextByte[:len(d.nextByte)-1] + } else { + b, d.err = d.r.ReadByte() + if d.err != nil { + return 0, false + } + d.n++ + } + if b == '\n' { + d.line++ + } + + return b, true +} + +// Must read a single byte. +// If there is no byte to read, +// set d.err to SyntaxError("unexpected EOF") +// and return ok==false +func (d *decoder) mustgetc() (b byte, ok bool) { + if b, ok = d.getc(); !ok { + if d.err == io.EOF { + d.err = d.syntaxError("unexpected EOF") + } + } + return +} + +// Unread a single byte. +func (d *decoder) ungetc(b byte) { + if b == '\n' { + d.line-- + } + d.nextByte = append(d.nextByte, b) +} + +func (d *decoder) match(s string) bool { + for i := 0; i < len(s); i++ { + if b, ok := d.getc(); !ok { + return false + } else if b != s[i] { + d.ungetc(b) + for j := i - 1; j >= 0; j-- { + d.ungetc(s[j]) + } + return false + } + } + return true +} + +var entity = map[string]int{ + "lt": '<', + "gt": '>', + "amp": '&', + "apos": '\'', + "quot": '"', +} + +// Read plain text section (XML calls it character data). +// If quote >= 0, we are in a quoted string and need to find the matching quote. +// If cdata == true, we are in a . +// On failure return nil and leave the error in d.err. +func (d *decoder) text(quote int, cdata bool) []byte { + var b0, b1 byte + var trunc int + d.buf.Reset() +Input: + for { + b, ok := d.getc() + if !ok { + if cdata { + if d.err == io.EOF { + d.err = d.syntaxError("unexpected EOF in CDATA section") + } + return nil + } + break Input + } + + // . + // It is an error for ]]> to appear in ordinary text. + if b0 == ']' && b1 == ']' && b == '>' { + if cdata { + trunc = 2 + break Input + } + return nil + } + + // Stop reading text if we see a <. + if b == '<' && !cdata { + if quote >= 0 { + return nil + } + d.ungetc('<') + break Input + } + if quote >= 0 && b == byte(quote) { + break Input + } + //DIFF: incomplete entity (no semicolon) *inserts* semicolon at end of + //text + if b == '&' && !cdata { + // Read escaped character expression up to semicolon. + // XML in all its glory allows a document to define and use + // its own character names with directives. + // Parsers are required to recognize lt, gt, amp, apos, and quot + // even if they have not been declared. + before := d.buf.Len() + d.buf.WriteByte('&') + var ok bool + var text string + var haveText bool + if b, ok = d.mustgetc(); !ok { + return nil + } + if b == '#' { + //DIFF: characters between valid characters and semicolon are + //ignored + d.buf.WriteByte(b) + if b, ok = d.mustgetc(); !ok { + return nil + } + base := 10 + if b == 'x' { + //DIFF: ERROR: unable to parse hexidecimal character code + base = 16 + d.buf.WriteByte(b) + if b, ok = d.mustgetc(); !ok { + return nil + } + } + start := d.buf.Len() + for '0' <= b && b <= '9' || + base == 16 && 'a' <= b && b <= 'f' || + base == 16 && 'A' <= b && b <= 'F' { + d.buf.WriteByte(b) + if b, ok = d.mustgetc(); !ok { + return nil + } + } + if b != ';' { + //DIFF: if numeric entity does not end with a semicolon, + //then the remaining text is truncated. Note: This may be + //a sign that the text is parsed out first, then entities + //are converted afterwards. + d.ungetc(b) + } else { + s := string(d.buf.Bytes()[start:]) + d.buf.WriteByte(';') + n, err := strconv.ParseUint(s, base, 64) + //DIFF: numeric entitiy is parsed as int32 and converted + //to a byte + if err == nil && n <= 255 { + text = string([]byte{byte(n)}) + haveText = true + } + } + } else { + d.ungetc(b) + if !d.readName(nameEntity) { + if d.err != nil { + return nil + } + ok = false + } + if b, ok = d.mustgetc(); !ok { + return nil + } + if b != ';' { + d.ungetc(b) + } else { + name := d.buf.Bytes()[before+1:] + d.buf.WriteByte(';') + + s := string(name) + if r, ok := entity[s]; ok { + text = string(r) + haveText = true + } + } + } + + if haveText { + d.buf.Truncate(before) + d.buf.Write([]byte(text)) + b0, b1 = 0, 0 + continue Input + } + + b0, b1 = 0, 0 + continue Input + } + + // We must rewrite unescaped \r and \r\n into \n. + if b == '\r' { + d.buf.WriteByte('\n') + } else if b1 == '\r' && b == '\n' { + // Skip \r\n--we already wrote \n. + } else { + d.buf.WriteByte(b) + } + + b0, b1 = b1, b + } + buf := d.buf.Bytes() + buf = buf[0 : len(buf)-trunc] + + data := make([]byte, len(buf)) + copy(data, buf) + + return data +} + +// Get name: /first(first|second)*/ +// Do not set d.err if the name is missing (unless unexpected EOF is received): +// let the caller provide better context. +func (d *decoder) name(typ int) (s string, ok bool) { + d.buf.Reset() + if !d.readName(typ) { + return "", false + } + + return d.buf.String(), true +} + +// Read a name and append its bytes to d.buf. +// The name is delimited by any single-byte character not valid in names. +// All multi-byte characters are accepted; the caller must check their validity. +func (d *decoder) readName(typ int) (ok bool) { + var b byte + if b, ok = d.mustgetc(); !ok { + return + } + if !isNameByte(b, typ) { + d.ungetc(b) + return false + } + d.buf.WriteByte(b) + + for { + if b, ok = d.mustgetc(); !ok { + return + } + if !isNameByte(b, typ) { + d.ungetc(b) + break + } + d.buf.WriteByte(b) + } + return true +} + +const ( + nameTag = iota + nameAttr + nameEntity +) + +func isNameByte(c byte, t int) bool { + if '!' <= c && c <= '~' && c != '>' { + switch t { + case 1: + // Attribute + return c != '=' + case 2: + // Entity + return c != ';' + } + return true + } + return false +} + +// ReadFrom decode data from r into the Document. +func (doc *Document) ReadFrom(r io.Reader) (n int64, err error) { + if r == nil { + return 0, errors.New("reader is nil") + } + + doc.Prefix = "" + doc.Indent = "" + doc.Warnings = doc.Warnings[:0] + + d := &decoder{ + doc: doc, + nextByte: make([]byte, 0, 9), + line: 1, + } + if rb, ok := r.(io.ByteReader); ok { + d.r = rb + } else { + d.r = bufio.NewReader(r) + } + + doc.Root, err = d.decodeTag(true) + if err != nil { + return d.n, err + } + + d.buf.Reset() + for { + b, ok := d.getc() + if !ok { + break + } + d.buf.WriteByte(b) + } + doc.Suffix = d.buf.String() + + return d.n, nil +} + +type encoder struct { + *bufio.Writer + d *Document + putNewline bool + depth int + indentedIn bool + n int64 + err error +} + +func (e *encoder) encodeCData(tag *Tag) bool { + if tag.CData == nil { + return true + } + + e.writeString("") + if !e.flush() { + return false + } + return true +} + +func (e *encoder) encodeText(tag *Tag) bool { + e.escapeString(tag.Text, true) + if !e.flush() { + return false + } + return true +} + +func (e *encoder) checkName(name string, typ int) bool { + if len(name) == 0 { + return false + } + for _, c := range []byte(name) { + if !isNameByte(c, typ) { + return false + } + } + return true +} + +func (e *encoder) encodeTag(tag *Tag, noTags bool, noindent bool) int { + if e.err != nil { + return -1 + } + + endName := tag.EndName + + if !noTags { + if !e.checkName(tag.StartName, nameTag) { + e.d.Warnings = append(e.d.Warnings, errors.New("ignored tag with malformed start name `"+tag.StartName+"`")) + return 0 + } + + if !e.checkName(endName, nameTag) && endName != "" { + endName = tag.StartName + e.d.Warnings = append(e.d.Warnings, errors.New("tag with malformed end name `"+tag.EndName+"`, used start name instead")) + } + + e.writeByte('<') + e.writeString(tag.StartName) + + for _, attr := range tag.Attr { + if !e.checkName(attr.Name, nameAttr) { + e.d.Warnings = append(e.d.Warnings, errors.New("ignored attribute with malformed name `"+attr.Name+"`")) + continue + } + e.writeByte(' ') + e.writeString(attr.Name) + e.writeByte('=') + e.writeByte('"') + e.escapeString(attr.Value, false) + e.writeByte('"') + } + + if tag.Empty { + e.writeByte('/') + e.writeByte('>') + if !e.flush() { + return -1 + } + return 1 + } + + e.writeByte('>') + if !e.flush() { + return -1 + } + } + + if !e.encodeCData(tag) { + return -1 + } + + if !noindent && !tag.NoIndent { + if len(tag.Tags) > 0 { + if noTags { + e.writeIndent(0, true) + } else { + e.writeIndent(1, false) + } + } + } + + if !e.encodeText(tag) { + return -1 + } + + for i, sub := range tag.Tags { + if r := e.encodeTag(sub, false, noindent || tag.NoIndent); r < 0 { + return -1 + } else if r == 0 { + continue + } + if !noindent && !tag.NoIndent { + if i == len(tag.Tags)-1 { + if noTags { + e.writeIndent(0, true) + } else { + e.writeIndent(-1, false) + } + } else { + e.writeIndent(0, false) + } + } + } + + if !noTags { + e.writeByte('<') + e.writeByte('/') + if endName == "" { + e.writeString(tag.StartName) + } else { + e.writeString(endName) + } + e.writeByte('>') + + if !e.flush() { + return -1 + } + } + + return 1 +} + +func (e *encoder) write(p []byte) bool { + if e.err != nil { + return false + } + n, err := e.Write(p) + e.n += int64(n) + if err != nil { + e.err = err + return false + } + return true +} + +func (e *encoder) writeByte(b byte) bool { + if e.err != nil { + return false + } + if err := e.WriteByte(b); err != nil { + e.err = err + return false + } + e.n += 1 + return true +} + +func (e *encoder) writeString(s string) bool { + if e.err != nil { + return false + } + n, err := e.WriteString(s) + e.n += int64(n) + if err != nil { + e.err = err + return false + } + return true +} + +func (e *encoder) flush() bool { + if e.err != nil { + return false + } + if err := e.Flush(); err != nil { + e.err = err + return false + } + return true +} + +func (e *encoder) writeIndent(depthDelta int, notag bool) { + if len(e.d.Prefix) == 0 && len(e.d.Indent) == 0 { + return + } + if depthDelta < 0 { + e.depth-- + } else if depthDelta > 0 { + e.depth++ + } + if !notag { + e.WriteByte('\n') + if len(e.d.Prefix) > 0 { + e.WriteString(e.d.Prefix) + } + if len(e.d.Indent) > 0 { + for i := 0; i < e.depth; i++ { + e.WriteString(e.d.Indent) + } + } + } +} + +var ( + esc_quot = []byte(""") + esc_apos = []byte("'") + esc_amp = []byte("&") + esc_lt = []byte("<") + esc_gt = []byte(">") +) + +// escapeString writes to p the properly escaped XML equivalent of the plain +// text data s. If escapeLead is true, then leading whitespace will be +// escaped. +func (e *encoder) escapeString(s string, escapeLead bool) { + var esc []byte + last := 0 + bs := []byte(s) + for i := 0; i < len(bs); { + esc = nil + b := bs[i] + i++ + + if escapeLead { + if isSpace(b) { + goto numbered + } + escapeLead = false + } + + switch b { + case '"': + esc = esc_quot + case '\'': + esc = esc_apos + case '&': + esc = esc_amp + case '<': + esc = esc_lt + case '>': + esc = esc_gt + default: + if ' ' <= b && b <= '~' || b == '\n' || b == '\r' { + // literal + continue + } else { + goto numbered + } + } + + numbered: + if esc == nil { + n := []byte(strconv.FormatInt(int64(b), 10)) + esc = make([]byte, len(n)+3) + esc[0] = '&' + esc[1] = '#' + copy(esc[2:], n) + esc[len(esc)-1] = ';' + } + + e.writeString(s[last : i-1]) + e.write(esc) + last = i + } + e.writeString(s[last:]) +} + +// WriteTo encodes the Document as bytes to w. +func (d *Document) WriteTo(w io.Writer) (n int64, err error) { + d.Warnings = d.Warnings[:0] + + e := &encoder{Writer: bufio.NewWriter(w), d: d} + + e.writeString(e.d.Prefix) + + if r := e.encodeTag(d.Root, d.ExcludeRoot, d.Root.NoIndent); r < 0 { + return e.n, e.err + } + + e.writeString(e.d.Suffix) + e.flush() + return e.n, e.err +} diff --git a/vendor/git.itzana.me/itzaname/rbxfile/xml/format.go b/vendor/git.itzana.me/itzaname/rbxfile/xml/format.go new file mode 100644 index 0000000..e861442 --- /dev/null +++ b/vendor/git.itzana.me/itzaname/rbxfile/xml/format.go @@ -0,0 +1,102 @@ +package xml + +import ( + "errors" + "git.itzana.me/itzaname/rbxapi" + "git.itzana.me/itzaname/rbxfile" + "io" +) + +// Decoder decodes a Document to a generic rbxfile.Root structure. +type Decoder interface { + Decode(document *Document) (root *rbxfile.Root, err error) +} + +// Encoder encodes a rbxfile.Root structure to a Document. +type Encoder interface { + Encode(root *rbxfile.Root) (document *Document, err error) +} + +// Serializer implements functions that decode and encode directly between +// byte streams and rbxfile Root structures. +type Serializer struct { + Decoder Decoder + Encoder Encoder +} + +// NewSerializer returns a new Serializer with a specified decoder and +// encoder. If either value is nil, the default RobloxCodec will be used in +// its place. +func NewSerializer(d Decoder, e Encoder) Serializer { + s := Serializer{ + Decoder: d, + Encoder: e, + } + + if d == nil || e == nil { + var codec RobloxCodec + + if d == nil { + s.Decoder = codec + } + if e == nil { + s.Encoder = codec + } + } + + return s +} + +// Deserialize decodes data from r into a Root structure using the specified +// decoder. +func (s Serializer) Deserialize(r io.Reader) (root *rbxfile.Root, err error) { + if s.Decoder == nil { + return nil, errors.New("a decoder has not been not specified") + } + + document := new(Document) + + if _, err = document.ReadFrom(r); err != nil { + return nil, errors.New("error parsing document: " + err.Error()) + } + + root, err = s.Decoder.Decode(document) + if err != nil { + return nil, errors.New("error decoding data: " + err.Error()) + } + + return root, nil +} + +// Serialize encodes data from a Root structure to w using the specified +// encoder. +func (s Serializer) Serialize(w io.Writer, root *rbxfile.Root) (err error) { + if s.Encoder == nil { + return errors.New("an encoder has not been not specified") + } + + document, err := s.Encoder.Encode(root) + if err != nil { + return errors.New("error encoding data: " + err.Error()) + } + + if _, err = document.WriteTo(w); err != nil { + return errors.New("error encoding format: " + err.Error()) + } + + return nil +} + +// Deserialize decodes data from r into a Root structure using the default +// decoder. An optional API can be given to ensure more correct data. +func Deserialize(r io.Reader, api rbxapi.Root) (root *rbxfile.Root, err error) { + codec := RobloxCodec{API: api} + return NewSerializer(codec, codec).Deserialize(r) +} + +// Serialize encodes data from a Root structure to w using the default +// encoder. An optional API can be given to ensure more correct data. +func Serialize(w io.Writer, api rbxapi.Root, root *rbxfile.Root) (err error) { + codec := RobloxCodec{API: api} + return NewSerializer(codec, codec).Serialize(w, root) +} diff --git a/vendor/github.com/google/uuid/.travis.yml b/vendor/github.com/google/uuid/.travis.yml new file mode 100644 index 0000000..d8156a6 --- /dev/null +++ b/vendor/github.com/google/uuid/.travis.yml @@ -0,0 +1,9 @@ +language: go + +go: + - 1.4.3 + - 1.5.3 + - tip + +script: + - go test -v ./... diff --git a/vendor/github.com/google/uuid/CONTRIBUTING.md b/vendor/github.com/google/uuid/CONTRIBUTING.md new file mode 100644 index 0000000..04fdf09 --- /dev/null +++ b/vendor/github.com/google/uuid/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# How to contribute + +We definitely welcome patches and contribution to this project! + +### Legal requirements + +In order to protect both you and ourselves, you will need to sign the +[Contributor License Agreement](https://cla.developers.google.com/clas). + +You may have already signed it for other Google projects. diff --git a/vendor/github.com/google/uuid/CONTRIBUTORS b/vendor/github.com/google/uuid/CONTRIBUTORS new file mode 100644 index 0000000..b4bb97f --- /dev/null +++ b/vendor/github.com/google/uuid/CONTRIBUTORS @@ -0,0 +1,9 @@ +Paul Borman +bmatsuo +shawnps +theory +jboverfelt +dsymonds +cd1 +wallclockbuilder +dansouza diff --git a/vendor/github.com/google/uuid/LICENSE b/vendor/github.com/google/uuid/LICENSE new file mode 100644 index 0000000..5dc6826 --- /dev/null +++ b/vendor/github.com/google/uuid/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009,2014 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/google/uuid/README.md b/vendor/github.com/google/uuid/README.md new file mode 100644 index 0000000..f765a46 --- /dev/null +++ b/vendor/github.com/google/uuid/README.md @@ -0,0 +1,19 @@ +# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master) +The uuid package generates and inspects UUIDs based on +[RFC 4122](http://tools.ietf.org/html/rfc4122) +and DCE 1.1: Authentication and Security Services. + +This package is based on the github.com/pborman/uuid package (previously named +code.google.com/p/go-uuid). It differs from these earlier packages in that +a UUID is a 16 byte array rather than a byte slice. One loss due to this +change is the ability to represent an invalid UUID (vs a NIL UUID). + +###### Install +`go get github.com/google/uuid` + +###### Documentation +[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid) + +Full `go doc` style documentation for the package can be viewed online without +installing this package by using the GoDoc site here: +http://pkg.go.dev/github.com/google/uuid diff --git a/vendor/github.com/google/uuid/dce.go b/vendor/github.com/google/uuid/dce.go new file mode 100644 index 0000000..fa820b9 --- /dev/null +++ b/vendor/github.com/google/uuid/dce.go @@ -0,0 +1,80 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "fmt" + "os" +) + +// A Domain represents a Version 2 domain +type Domain byte + +// Domain constants for DCE Security (Version 2) UUIDs. +const ( + Person = Domain(0) + Group = Domain(1) + Org = Domain(2) +) + +// NewDCESecurity returns a DCE Security (Version 2) UUID. +// +// The domain should be one of Person, Group or Org. +// On a POSIX system the id should be the users UID for the Person +// domain and the users GID for the Group. The meaning of id for +// the domain Org or on non-POSIX systems is site defined. +// +// For a given domain/id pair the same token may be returned for up to +// 7 minutes and 10 seconds. +func NewDCESecurity(domain Domain, id uint32) (UUID, error) { + uuid, err := NewUUID() + if err == nil { + uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2 + uuid[9] = byte(domain) + binary.BigEndian.PutUint32(uuid[0:], id) + } + return uuid, err +} + +// NewDCEPerson returns a DCE Security (Version 2) UUID in the person +// domain with the id returned by os.Getuid. +// +// NewDCESecurity(Person, uint32(os.Getuid())) +func NewDCEPerson() (UUID, error) { + return NewDCESecurity(Person, uint32(os.Getuid())) +} + +// NewDCEGroup returns a DCE Security (Version 2) UUID in the group +// domain with the id returned by os.Getgid. +// +// NewDCESecurity(Group, uint32(os.Getgid())) +func NewDCEGroup() (UUID, error) { + return NewDCESecurity(Group, uint32(os.Getgid())) +} + +// Domain returns the domain for a Version 2 UUID. Domains are only defined +// for Version 2 UUIDs. +func (uuid UUID) Domain() Domain { + return Domain(uuid[9]) +} + +// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2 +// UUIDs. +func (uuid UUID) ID() uint32 { + return binary.BigEndian.Uint32(uuid[0:4]) +} + +func (d Domain) String() string { + switch d { + case Person: + return "Person" + case Group: + return "Group" + case Org: + return "Org" + } + return fmt.Sprintf("Domain%d", int(d)) +} diff --git a/vendor/github.com/google/uuid/doc.go b/vendor/github.com/google/uuid/doc.go new file mode 100644 index 0000000..5b8a4b9 --- /dev/null +++ b/vendor/github.com/google/uuid/doc.go @@ -0,0 +1,12 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package uuid generates and inspects UUIDs. +// +// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security +// Services. +// +// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to +// maps or compared directly. +package uuid diff --git a/vendor/github.com/google/uuid/go.mod b/vendor/github.com/google/uuid/go.mod new file mode 100644 index 0000000..fc84cd7 --- /dev/null +++ b/vendor/github.com/google/uuid/go.mod @@ -0,0 +1 @@ +module github.com/google/uuid diff --git a/vendor/github.com/google/uuid/hash.go b/vendor/github.com/google/uuid/hash.go new file mode 100644 index 0000000..b174616 --- /dev/null +++ b/vendor/github.com/google/uuid/hash.go @@ -0,0 +1,53 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "crypto/md5" + "crypto/sha1" + "hash" +) + +// Well known namespace IDs and UUIDs +var ( + NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) + Nil UUID // empty UUID, all zeros +) + +// NewHash returns a new UUID derived from the hash of space concatenated with +// data generated by h. The hash should be at least 16 byte in length. The +// first 16 bytes of the hash are used to form the UUID. The version of the +// UUID will be the lower 4 bits of version. NewHash is used to implement +// NewMD5 and NewSHA1. +func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { + h.Reset() + h.Write(space[:]) + h.Write(data) + s := h.Sum(nil) + var uuid UUID + copy(uuid[:], s) + uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4) + uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant + return uuid +} + +// NewMD5 returns a new MD5 (Version 3) UUID based on the +// supplied name space and data. It is the same as calling: +// +// NewHash(md5.New(), space, data, 3) +func NewMD5(space UUID, data []byte) UUID { + return NewHash(md5.New(), space, data, 3) +} + +// NewSHA1 returns a new SHA1 (Version 5) UUID based on the +// supplied name space and data. It is the same as calling: +// +// NewHash(sha1.New(), space, data, 5) +func NewSHA1(space UUID, data []byte) UUID { + return NewHash(sha1.New(), space, data, 5) +} diff --git a/vendor/github.com/google/uuid/marshal.go b/vendor/github.com/google/uuid/marshal.go new file mode 100644 index 0000000..14bd340 --- /dev/null +++ b/vendor/github.com/google/uuid/marshal.go @@ -0,0 +1,38 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "fmt" + +// MarshalText implements encoding.TextMarshaler. +func (uuid UUID) MarshalText() ([]byte, error) { + var js [36]byte + encodeHex(js[:], uuid) + return js[:], nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (uuid *UUID) UnmarshalText(data []byte) error { + id, err := ParseBytes(data) + if err != nil { + return err + } + *uuid = id + return nil +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (uuid UUID) MarshalBinary() ([]byte, error) { + return uuid[:], nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (uuid *UUID) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) + } + copy(uuid[:], data) + return nil +} diff --git a/vendor/github.com/google/uuid/node.go b/vendor/github.com/google/uuid/node.go new file mode 100644 index 0000000..d651a2b --- /dev/null +++ b/vendor/github.com/google/uuid/node.go @@ -0,0 +1,90 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "sync" +) + +var ( + nodeMu sync.Mutex + ifname string // name of interface being used + nodeID [6]byte // hardware for version 1 UUIDs + zeroID [6]byte // nodeID with only 0's +) + +// NodeInterface returns the name of the interface from which the NodeID was +// derived. The interface "user" is returned if the NodeID was set by +// SetNodeID. +func NodeInterface() string { + defer nodeMu.Unlock() + nodeMu.Lock() + return ifname +} + +// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs. +// If name is "" then the first usable interface found will be used or a random +// Node ID will be generated. If a named interface cannot be found then false +// is returned. +// +// SetNodeInterface never fails when name is "". +func SetNodeInterface(name string) bool { + defer nodeMu.Unlock() + nodeMu.Lock() + return setNodeInterface(name) +} + +func setNodeInterface(name string) bool { + iname, addr := getHardwareInterface(name) // null implementation for js + if iname != "" && addr != nil { + ifname = iname + copy(nodeID[:], addr) + return true + } + + // We found no interfaces with a valid hardware address. If name + // does not specify a specific interface generate a random Node ID + // (section 4.1.6) + if name == "" { + ifname = "random" + randomBits(nodeID[:]) + return true + } + return false +} + +// NodeID returns a slice of a copy of the current Node ID, setting the Node ID +// if not already set. +func NodeID() []byte { + defer nodeMu.Unlock() + nodeMu.Lock() + if nodeID == zeroID { + setNodeInterface("") + } + nid := nodeID + return nid[:] +} + +// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes +// of id are used. If id is less than 6 bytes then false is returned and the +// Node ID is not set. +func SetNodeID(id []byte) bool { + if len(id) < 6 { + return false + } + defer nodeMu.Unlock() + nodeMu.Lock() + copy(nodeID[:], id) + ifname = "user" + return true +} + +// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is +// not valid. The NodeID is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) NodeID() []byte { + var node [6]byte + copy(node[:], uuid[10:]) + return node[:] +} diff --git a/vendor/github.com/google/uuid/node_js.go b/vendor/github.com/google/uuid/node_js.go new file mode 100644 index 0000000..24b78ed --- /dev/null +++ b/vendor/github.com/google/uuid/node_js.go @@ -0,0 +1,12 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build js + +package uuid + +// getHardwareInterface returns nil values for the JS version of the code. +// This remvoves the "net" dependency, because it is not used in the browser. +// Using the "net" library inflates the size of the transpiled JS code by 673k bytes. +func getHardwareInterface(name string) (string, []byte) { return "", nil } diff --git a/vendor/github.com/google/uuid/node_net.go b/vendor/github.com/google/uuid/node_net.go new file mode 100644 index 0000000..0cbbcdd --- /dev/null +++ b/vendor/github.com/google/uuid/node_net.go @@ -0,0 +1,33 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !js + +package uuid + +import "net" + +var interfaces []net.Interface // cached list of interfaces + +// getHardwareInterface returns the name and hardware address of interface name. +// If name is "" then the name and hardware address of one of the system's +// interfaces is returned. If no interfaces are found (name does not exist or +// there are no interfaces) then "", nil is returned. +// +// Only addresses of at least 6 bytes are returned. +func getHardwareInterface(name string) (string, []byte) { + if interfaces == nil { + var err error + interfaces, err = net.Interfaces() + if err != nil { + return "", nil + } + } + for _, ifs := range interfaces { + if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) { + return ifs.Name, ifs.HardwareAddr + } + } + return "", nil +} diff --git a/vendor/github.com/google/uuid/sql.go b/vendor/github.com/google/uuid/sql.go new file mode 100644 index 0000000..f326b54 --- /dev/null +++ b/vendor/github.com/google/uuid/sql.go @@ -0,0 +1,59 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "database/sql/driver" + "fmt" +) + +// Scan implements sql.Scanner so UUIDs can be read from databases transparently +// Currently, database types that map to string and []byte are supported. Please +// consult database-specific driver documentation for matching types. +func (uuid *UUID) Scan(src interface{}) error { + switch src := src.(type) { + case nil: + return nil + + case string: + // if an empty UUID comes from a table, we return a null UUID + if src == "" { + return nil + } + + // see Parse for required string format + u, err := Parse(src) + if err != nil { + return fmt.Errorf("Scan: %v", err) + } + + *uuid = u + + case []byte: + // if an empty UUID comes from a table, we return a null UUID + if len(src) == 0 { + return nil + } + + // assumes a simple slice of bytes if 16 bytes + // otherwise attempts to parse + if len(src) != 16 { + return uuid.Scan(string(src)) + } + copy((*uuid)[:], src) + + default: + return fmt.Errorf("Scan: unable to scan type %T into UUID", src) + } + + return nil +} + +// Value implements sql.Valuer so that UUIDs can be written to databases +// transparently. Currently, UUIDs map to strings. Please consult +// database-specific driver documentation for matching types. +func (uuid UUID) Value() (driver.Value, error) { + return uuid.String(), nil +} diff --git a/vendor/github.com/google/uuid/time.go b/vendor/github.com/google/uuid/time.go new file mode 100644 index 0000000..e6ef06c --- /dev/null +++ b/vendor/github.com/google/uuid/time.go @@ -0,0 +1,123 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "sync" + "time" +) + +// A Time represents a time as the number of 100's of nanoseconds since 15 Oct +// 1582. +type Time int64 + +const ( + lillian = 2299160 // Julian day of 15 Oct 1582 + unix = 2440587 // Julian day of 1 Jan 1970 + epoch = unix - lillian // Days between epochs + g1582 = epoch * 86400 // seconds between epochs + g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs +) + +var ( + timeMu sync.Mutex + lasttime uint64 // last time we returned + clockSeq uint16 // clock sequence for this run + + timeNow = time.Now // for testing +) + +// UnixTime converts t the number of seconds and nanoseconds using the Unix +// epoch of 1 Jan 1970. +func (t Time) UnixTime() (sec, nsec int64) { + sec = int64(t - g1582ns100) + nsec = (sec % 10000000) * 100 + sec /= 10000000 + return sec, nsec +} + +// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and +// clock sequence as well as adjusting the clock sequence as needed. An error +// is returned if the current time cannot be determined. +func GetTime() (Time, uint16, error) { + defer timeMu.Unlock() + timeMu.Lock() + return getTime() +} + +func getTime() (Time, uint16, error) { + t := timeNow() + + // If we don't have a clock sequence already, set one. + if clockSeq == 0 { + setClockSequence(-1) + } + now := uint64(t.UnixNano()/100) + g1582ns100 + + // If time has gone backwards with this clock sequence then we + // increment the clock sequence + if now <= lasttime { + clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000 + } + lasttime = now + return Time(now), clockSeq, nil +} + +// ClockSequence returns the current clock sequence, generating one if not +// already set. The clock sequence is only used for Version 1 UUIDs. +// +// The uuid package does not use global static storage for the clock sequence or +// the last time a UUID was generated. Unless SetClockSequence is used, a new +// random clock sequence is generated the first time a clock sequence is +// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) +func ClockSequence() int { + defer timeMu.Unlock() + timeMu.Lock() + return clockSequence() +} + +func clockSequence() int { + if clockSeq == 0 { + setClockSequence(-1) + } + return int(clockSeq & 0x3fff) +} + +// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to +// -1 causes a new sequence to be generated. +func SetClockSequence(seq int) { + defer timeMu.Unlock() + timeMu.Lock() + setClockSequence(seq) +} + +func setClockSequence(seq int) { + if seq == -1 { + var b [2]byte + randomBits(b[:]) // clock sequence + seq = int(b[0])<<8 | int(b[1]) + } + oldSeq := clockSeq + clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant + if oldSeq != clockSeq { + lasttime = 0 + } +} + +// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in +// uuid. The time is only defined for version 1 and 2 UUIDs. +func (uuid UUID) Time() Time { + time := int64(binary.BigEndian.Uint32(uuid[0:4])) + time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32 + time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48 + return Time(time) +} + +// ClockSequence returns the clock sequence encoded in uuid. +// The clock sequence is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) ClockSequence() int { + return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff +} diff --git a/vendor/github.com/google/uuid/util.go b/vendor/github.com/google/uuid/util.go new file mode 100644 index 0000000..5ea6c73 --- /dev/null +++ b/vendor/github.com/google/uuid/util.go @@ -0,0 +1,43 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "io" +) + +// randomBits completely fills slice b with random data. +func randomBits(b []byte) { + if _, err := io.ReadFull(rander, b); err != nil { + panic(err.Error()) // rand should never fail + } +} + +// xvalues returns the value of a byte as a hexadecimal digit or 255. +var xvalues = [256]byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +} + +// xtob converts hex characters x1 and x2 into a byte. +func xtob(x1, x2 byte) (byte, bool) { + b1 := xvalues[x1] + b2 := xvalues[x2] + return (b1 << 4) | b2, b1 != 255 && b2 != 255 +} diff --git a/vendor/github.com/google/uuid/uuid.go b/vendor/github.com/google/uuid/uuid.go new file mode 100644 index 0000000..524404c --- /dev/null +++ b/vendor/github.com/google/uuid/uuid.go @@ -0,0 +1,245 @@ +// Copyright 2018 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "io" + "strings" +) + +// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC +// 4122. +type UUID [16]byte + +// A Version represents a UUID's version. +type Version byte + +// A Variant represents a UUID's variant. +type Variant byte + +// Constants returned by Variant. +const ( + Invalid = Variant(iota) // Invalid UUID + RFC4122 // The variant specified in RFC4122 + Reserved // Reserved, NCS backward compatibility. + Microsoft // Reserved, Microsoft Corporation backward compatibility. + Future // Reserved for future definition. +) + +var rander = rand.Reader // random function + +// Parse decodes s into a UUID or returns an error. Both the standard UUID +// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the +// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex +// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. +func Parse(s string) (UUID, error) { + var uuid UUID + switch len(s) { + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36: + + // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36 + 9: + if strings.ToLower(s[:9]) != "urn:uuid:" { + return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9]) + } + s = s[9:] + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + case 36 + 2: + s = s[1:] + + // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + case 32: + var ok bool + for i := range uuid { + uuid[i], ok = xtob(s[i*2], s[i*2+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + } + return uuid, nil + default: + return uuid, fmt.Errorf("invalid UUID length: %d", len(s)) + } + // s is now at least 36 bytes long + // it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return uuid, errors.New("invalid UUID format") + } + for i, x := range [16]int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34} { + v, ok := xtob(s[x], s[x+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + uuid[i] = v + } + return uuid, nil +} + +// ParseBytes is like Parse, except it parses a byte slice instead of a string. +func ParseBytes(b []byte) (UUID, error) { + var uuid UUID + switch len(b) { + case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) { + return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9]) + } + b = b[9:] + case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + b = b[1:] + case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + var ok bool + for i := 0; i < 32; i += 2 { + uuid[i/2], ok = xtob(b[i], b[i+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + } + return uuid, nil + default: + return uuid, fmt.Errorf("invalid UUID length: %d", len(b)) + } + // s is now at least 36 bytes long + // it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' { + return uuid, errors.New("invalid UUID format") + } + for i, x := range [16]int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34} { + v, ok := xtob(b[x], b[x+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + uuid[i] = v + } + return uuid, nil +} + +// MustParse is like Parse but panics if the string cannot be parsed. +// It simplifies safe initialization of global variables holding compiled UUIDs. +func MustParse(s string) UUID { + uuid, err := Parse(s) + if err != nil { + panic(`uuid: Parse(` + s + `): ` + err.Error()) + } + return uuid +} + +// FromBytes creates a new UUID from a byte slice. Returns an error if the slice +// does not have a length of 16. The bytes are copied from the slice. +func FromBytes(b []byte) (uuid UUID, err error) { + err = uuid.UnmarshalBinary(b) + return uuid, err +} + +// Must returns uuid if err is nil and panics otherwise. +func Must(uuid UUID, err error) UUID { + if err != nil { + panic(err) + } + return uuid +} + +// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// , or "" if uuid is invalid. +func (uuid UUID) String() string { + var buf [36]byte + encodeHex(buf[:], uuid) + return string(buf[:]) +} + +// URN returns the RFC 2141 URN form of uuid, +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid. +func (uuid UUID) URN() string { + var buf [36 + 9]byte + copy(buf[:], "urn:uuid:") + encodeHex(buf[9:], uuid) + return string(buf[:]) +} + +func encodeHex(dst []byte, uuid UUID) { + hex.Encode(dst, uuid[:4]) + dst[8] = '-' + hex.Encode(dst[9:13], uuid[4:6]) + dst[13] = '-' + hex.Encode(dst[14:18], uuid[6:8]) + dst[18] = '-' + hex.Encode(dst[19:23], uuid[8:10]) + dst[23] = '-' + hex.Encode(dst[24:], uuid[10:]) +} + +// Variant returns the variant encoded in uuid. +func (uuid UUID) Variant() Variant { + switch { + case (uuid[8] & 0xc0) == 0x80: + return RFC4122 + case (uuid[8] & 0xe0) == 0xc0: + return Microsoft + case (uuid[8] & 0xe0) == 0xe0: + return Future + default: + return Reserved + } +} + +// Version returns the version of uuid. +func (uuid UUID) Version() Version { + return Version(uuid[6] >> 4) +} + +func (v Version) String() string { + if v > 15 { + return fmt.Sprintf("BAD_VERSION_%d", v) + } + return fmt.Sprintf("VERSION_%d", v) +} + +func (v Variant) String() string { + switch v { + case RFC4122: + return "RFC4122" + case Reserved: + return "Reserved" + case Microsoft: + return "Microsoft" + case Future: + return "Future" + case Invalid: + return "Invalid" + } + return fmt.Sprintf("BadVariant%d", int(v)) +} + +// SetRand sets the random number generator to r, which implements io.Reader. +// If r.Read returns an error when the package requests random data then +// a panic will be issued. +// +// Calling SetRand with nil sets the random number generator to the default +// generator. +func SetRand(r io.Reader) { + if r == nil { + rander = rand.Reader + return + } + rander = r +} diff --git a/vendor/github.com/google/uuid/version1.go b/vendor/github.com/google/uuid/version1.go new file mode 100644 index 0000000..4631096 --- /dev/null +++ b/vendor/github.com/google/uuid/version1.go @@ -0,0 +1,44 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" +) + +// NewUUID returns a Version 1 UUID based on the current NodeID and clock +// sequence, and the current time. If the NodeID has not been set by SetNodeID +// or SetNodeInterface then it will be set automatically. If the NodeID cannot +// be set NewUUID returns nil. If clock sequence has not been set by +// SetClockSequence then it will be set automatically. If GetTime fails to +// return the current NewUUID returns nil and an error. +// +// In most cases, New should be used. +func NewUUID() (UUID, error) { + var uuid UUID + now, seq, err := GetTime() + if err != nil { + return uuid, err + } + + timeLow := uint32(now & 0xffffffff) + timeMid := uint16((now >> 32) & 0xffff) + timeHi := uint16((now >> 48) & 0x0fff) + timeHi |= 0x1000 // Version 1 + + binary.BigEndian.PutUint32(uuid[0:], timeLow) + binary.BigEndian.PutUint16(uuid[4:], timeMid) + binary.BigEndian.PutUint16(uuid[6:], timeHi) + binary.BigEndian.PutUint16(uuid[8:], seq) + + nodeMu.Lock() + if nodeID == zeroID { + setNodeInterface("") + } + copy(uuid[10:], nodeID[:]) + nodeMu.Unlock() + + return uuid, nil +} diff --git a/vendor/github.com/google/uuid/version4.go b/vendor/github.com/google/uuid/version4.go new file mode 100644 index 0000000..c110465 --- /dev/null +++ b/vendor/github.com/google/uuid/version4.go @@ -0,0 +1,43 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "io" + +// New creates a new random UUID or panics. New is equivalent to +// the expression +// +// uuid.Must(uuid.NewRandom()) +func New() UUID { + return Must(NewRandom()) +} + +// NewRandom returns a Random (Version 4) UUID. +// +// The strength of the UUIDs is based on the strength of the crypto/rand +// package. +// +// A note about uniqueness derived from the UUID Wikipedia entry: +// +// Randomly generated UUIDs have 122 random bits. One's annual risk of being +// hit by a meteorite is estimated to be one chance in 17 billion, that +// means the probability is about 0.00000000006 (6 × 10−11), +// equivalent to the odds of creating a few tens of trillions of UUIDs in a +// year and having one duplicate. +func NewRandom() (UUID, error) { + return NewRandomFromReader(rander) +} + +// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader. +func NewRandomFromReader(r io.Reader) (UUID, error) { + var uuid UUID + _, err := io.ReadFull(r, uuid[:]) + if err != nil { + return Nil, err + } + uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 + uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 + return uuid, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 0000000..b4a1b2a --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,12 @@ +# git.itzana.me/itzaname/go-roblox v1.0.1 +## explicit +git.itzana.me/itzaname/go-roblox +# git.itzana.me/itzaname/rbxapi v0.1.0 +git.itzana.me/itzaname/rbxapi +# git.itzana.me/itzaname/rbxfile v0.0.0-20210811000911-6fc7a2281e8d +## explicit +git.itzana.me/itzaname/rbxfile +git.itzana.me/itzaname/rbxfile/xml +# github.com/google/uuid v1.1.2 +## explicit +github.com/google/uuid