diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5e1d52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +package + +.DS_Store diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..e4f9060 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,13 @@ +Copyright 2020 the FIO Foundation, and Todd Garrison + +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. \ No newline at end of file diff --git a/abi-form.go b/abi-form.go new file mode 100644 index 0000000..1658888 --- /dev/null +++ b/abi-form.go @@ -0,0 +1,168 @@ +package cryptonym + +import ( + "encoding/json" + "fyne.io/fyne" + "fyne.io/fyne/layout" + "fyne.io/fyne/widget" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go" + "github.com/fioprotocol/fio-go/eos" + "gopkg.in/yaml.v3" + "math" + "regexp" +) + +func GetAbiViewer(w int, h int, api *fio.API) (tab *widget.Box, ok bool) { + structs := widget.NewMultiLineEntry() + actions := widget.NewMultiLineEntry() + tables := widget.NewMultiLineEntry() + asJson := &widget.Check{} + scrollViews := &fyne.Container{} + layoutStructs := &widget.TabItem{} + layoutActions := &widget.TabItem{} + layoutTables := &widget.TabItem{} + r := regexp.MustCompile("(?m)^-") + + getAbi := func(s string) { + if s == "" { + errs.ErrChan <- "queried for empty abi" + return + } + errs.ErrChan <- "getting abi for " + s + abiOut, err := api.GetABI(eos.AccountName(s)) + if err != nil { + errs.ErrChan <- err.Error() + return + } + + var yStruct []byte + if asJson.Checked { + yStruct, err = json.MarshalIndent(abiOut.ABI.Structs, "", " ") + } else { + yStruct, err = yaml.Marshal(abiOut.ABI.Structs) + } + if err != nil { + errs.ErrChan <- err.Error() + return + } + txt := r.ReplaceAllString(string(yStruct), "\n-") + func(s string) { + structs.SetText(s) + structs.OnChanged = func(string) { + structs.SetText(s) + } + }(txt) // deref + structs.SetText(txt) + + var yActions []byte + if asJson.Checked { + yActions, err = json.MarshalIndent(abiOut.ABI.Actions, "", " ") + } else { + yActions, err = yaml.Marshal(abiOut.ABI.Actions) + } + if err != nil { + errs.ErrChan <- err.Error() + return + } + txt = r.ReplaceAllString(string(yActions), "\n-") + func(s string) { + actions.OnChanged = func(string) { + actions.SetText(s) + } + }(txt) + actions.SetText(txt) + + var yTables []byte + if asJson.Checked { + yTables, err = json.MarshalIndent(abiOut.ABI.Tables, "", " ") + } else { + yTables, err = yaml.Marshal(abiOut.ABI.Tables) + } + if err != nil { + errs.ErrChan <- err.Error() + return + } + txt = r.ReplaceAllString(string(yTables), "\n-") + func(s string) { + tables.OnChanged = func(string) { + tables.SetText(s) + } + }(txt) // deref + tables.SetText(txt) + + layoutActions.Content.Resize(actions.MinSize()) + layoutStructs.Content.Resize(structs.MinSize()) + layoutTables.Content.Resize(tables.MinSize()) + layoutActions.Content.Refresh() + layoutStructs.Content.Refresh() + layoutTables.Content.Refresh() + scrollViews.Refresh() + tables.Refresh() + actions.Refresh() + structs.Refresh() + } + + abis := &widget.Select{} + + asJson = widget.NewCheck("Display Json", func(b bool) { + structs.SetText("") + actions.SetText("") + tables.SetText("") + getAbi(abis.Selected) + }) + + abis = widget.NewSelect(TableIndex.List(), func(s string) { + structs.SetText("") + actions.SetText("") + tables.SetText("") + getAbi(abis.Selected) + }) + + tabHeight := int(math.Round(float64(H) * .65)) + layoutStructs = widget.NewTabItem("Structs", + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(RWidth(), tabHeight)), + fyne.NewContainerWithLayout(layout.NewMaxLayout(), + widget.NewScrollContainer( + structs, + ), + ), + ), + ) + layoutActions = widget.NewTabItem("Actions", + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(RWidth(), tabHeight)), + fyne.NewContainerWithLayout(layout.NewMaxLayout(), + widget.NewScrollContainer( + actions, + ), + ), + ), + ) + layoutTables = widget.NewTabItem("Tables", + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(RWidth(), tabHeight)), + fyne.NewContainerWithLayout(layout.NewMaxLayout(), + widget.NewScrollContainer( + tables, + ), + ), + ), + ) + + scrollViews = fyne.NewContainer( + widget.NewTabContainer( + layoutStructs, + layoutActions, + layoutTables, + ), + ) + viewAbiLayout := widget.NewVBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(200, 30)), + widget.NewHBox( + abis, + asJson, + ), + ), + scrollViews, + ) + return viewAbiLayout, true +} diff --git a/account-form.go b/account-form.go new file mode 100644 index 0000000..c73dd09 --- /dev/null +++ b/account-form.go @@ -0,0 +1,358 @@ +package cryptonym + +import ( + "context" + "errors" + "fmt" + "fyne.io/fyne" + "fyne.io/fyne/layout" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go" + "strings" + "time" +) + +func NewAccountSearchTab(box chan fyne.Container, account *fio.Account) { + + accountOuput := &widget.Box{} + accountInput := NewClickEntry(&widget.Button{}) + accountSelect := widget.NewSelect(accountSearchType, func(s string) { + accountInput.Refresh() + }) + accountSelect.SetSelected(accountSearchType[0]) + accountInput.SetText(account.PubKey) + accountInput.OnChanged = func(s string) { + selected := accountSelect.Selected + switch { + case len(s) == 53 && strings.HasPrefix(s, "FIO"): + accountSelect.SetSelected("Public Key") + case len(s) == 51 && strings.HasPrefix(s, "5"): + accountSelect.SetSelected("Private Key") + case strings.Contains(s, "@"): + accountSelect.SetSelected("Fio Address") + case len(s) == 12: + accountSelect.SetSelected("Actor/Account") + case selected != "Fio Domain": + accountSelect.SetSelected("Fio Domain") + } + accountInput.SetText(s) + go func() { + time.Sleep(100 * time.Millisecond) + accountInput.Refresh() + }() + } + + accountSubmit := &widget.Button{} + emptyBox := func() *widget.Box { + return widget.NewVBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(RWidth(), 40)), + widget.NewHBox( + accountSelect, + accountInput, + accountSubmit, + ), + ), + layout.NewSpacer(), + ) + } + + mkBox := func(accountOutput *widget.Box) *widget.Box { + accountBox := widget.NewVBox( + accountOuput, + ) + accountOuput.Refresh() + return widget.NewVBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(RWidth(), 40)), + widget.NewHBox( + accountSelect, + accountInput, + accountSubmit, + ), + ), + fyne.NewContainerWithLayout(layout.NewVBoxLayout(), + accountBox, + ), + ) + } + + accountSubmit = widget.NewButtonWithIcon("Search", theme.SearchIcon(), func() { + go func() { + accountSubmit.Disable() + defer accountSubmit.Enable() + d := time.Now().Add(5 * time.Second) + ctx, cancel := context.WithDeadline(context.Background(), d) + defer cancel() + finished := make(chan bool) + go func() { + box <- *fyne.NewContainerWithLayout(layout.NewVBoxLayout(), + emptyBox(), + layout.NewSpacer(), + ) + info, err := AccountSearch(accountSelect.Selected, accountInput.Text) + if err != nil { + errs.ErrChan <- err.Error() + return + } + ao, err := info.report() + if err != nil { + errs.ErrChan <- err.Error() + return + } + deRef := *ao + accountOuput = &deRef + accountOuput.Refresh() + accountOuput.Show() + box <- *fyne.NewContainerWithLayout( + layout.NewMaxLayout(), + widget.NewScrollContainer(mkBox(&deRef)), + ) + finished <- true + }() + for { + select { + case <-finished: + return + case <-ctx.Done(): + errs.ErrChan <- "hit time limit while getting account information" + return + } + } + }() + }) + accountInput.Button = accountSubmit + box <- *fyne.NewContainerWithLayout(layout.NewVBoxLayout(), + emptyBox(), + layout.NewSpacer(), + ) +} + +func (as *AccountInformation) report() (*widget.Box, error) { + if as.Actor == "" || as.PubKey == "" { + return nil, errors.New("nothing to report, account or key name empty") + } + vSpace := widget.NewHBox(widget.NewLabel(" ")) + + names := func() *widget.Box { + b := widget.NewVBox() + if len(as.FioNames) > 0 { + for _, r := range as.FioNames { + b.Append(vSpace) + fioName := widget.NewEntry() + func(n string) { + fioName.SetText(n) + fioName.OnChanged = func(string) { + fioName.SetText(n) + } + }(r.Name) // deref + b.Append(fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(3), + widget.NewLabelWithStyle("Fio Name:", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + fioName, + layout.NewSpacer(), + )) + + expires := widget.NewEntry() + func(s string) { + expires.SetText(s) + expires.OnChanged = func(string) { + expires.SetText(s) + } + }(time.Unix(r.Expiration, 0).String()) // deref + b.Append(fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(3), + widget.NewLabelWithStyle("Expiration:", fyne.TextAlignTrailing, fyne.TextStyle{}), + expires, + layout.NewSpacer(), + )) + + bundle := widget.NewEntry() + func(s string) { + bundle.SetText(s) + bundle.OnChanged = func(string) { + bundle.SetText(s) + } + }(fmt.Sprintf("%d", r.BundleCount)) // deref + + b.Append(fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(3), + widget.NewLabelWithStyle("Free Bundled TX remaining:", fyne.TextAlignTrailing, fyne.TextStyle{}), + bundle, + layout.NewSpacer(), + )) + + addressBox := make([]*fyne.Container, 0) + for _, public := range r.Addresses { + if public.PublicAddress == as.PubKey { + continue + } + pubAddr := widget.NewEntry() + var symbol string + switch { + case public.TokenCode != "" && public.ChainCode != "": + symbol = fmt.Sprintf("%s • %s", public.TokenCode, public.ChainCode) + case public.TokenCode != "": + symbol = public.TokenCode + default: + symbol = public.ChainCode + } + func(s string) { + pubAddr.SetText(s) + pubAddr.OnChanged = func(string) { + pubAddr.SetText(s) + } + }(public.PublicAddress) // deref + + addressBox = append(addressBox, + fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(3), + widget.NewLabelWithStyle(symbol, fyne.TextAlignTrailing, fyne.TextStyle{Italic: true}), + pubAddr, + layout.NewSpacer(), + )) + } + for _, app := range addressBox { + b.Append(app) + } + + } + } + b.Append(fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.Size{ + Width: 20, + Height: 50, + }))) + return b + }() + + domains := func() *widget.Box { + b := widget.NewVBox() + if len(as.FioDomains) > 0 { + for _, r := range as.FioDomains { + + b.Append(fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(1), widget.NewLabel(" "))) + fioDomain := widget.NewEntry() + func(s string) { + fioDomain.SetText(s) + fioDomain.OnChanged = func(string) { + fioDomain.SetText(s) + } + }(r.Name) // deref + + b.Append(fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(3), + widget.NewLabelWithStyle("Fio Domain:", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + fioDomain, + layout.NewSpacer(), + )) + + public := widget.NewEntry() + toBool := func() string { + if r.IsPublic > 0 { + return "True" + } + return "False" + } + func(s string) { + public.SetText(s) + public.OnChanged = func(string) { + public.SetText(s) + } + }(toBool()) + + b.Append(fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(3), + widget.NewLabelWithStyle("Allows Public Registration:", fyne.TextAlignTrailing, fyne.TextStyle{}), + public, + layout.NewSpacer(), + )) + + expires := widget.NewEntry() + func(s string) { + expires.SetText(s) + expires.OnChanged = func(string) { + expires.SetText(s) + } + }(time.Unix(r.Expiration, 0).String()) // deref + + b.Append(fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(3), + widget.NewLabelWithStyle("Expiration:", fyne.TextAlignTrailing, fyne.TextStyle{}), + expires, + layout.NewSpacer(), + )) + + } + } + return b + }() + + report := &widget.Box{} + + entryActor := widget.NewEntry() + entryActor.SetText(as.Actor) + entryActor.OnChanged = func(string) { + entryActor.SetText(as.Actor) + } + entryPub := widget.NewEntry() + entryPub.SetText(as.PubKey) + entryPub.OnChanged = func(string) { + entryPub.SetText(as.PubKey) + } + entryBal := widget.NewEntry() + entryBal.SetText(p.Sprintf("ᵮ %.9g", float64(as.Balance)/1000000000.0)) + entryBal.OnChanged = func(string) { + entryBal.SetText(p.Sprintf("ᵮ %.9g", float64(as.Balance)/1000000000.0)) + } + + ramUsed := widget.NewEntry() + ramContainer := fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(3), + widget.NewLabelWithStyle("Ram Used: ", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + ramUsed, + layout.NewSpacer(), + ) + ramContainer.Hide() + if as.RamUsed != 0 { + ramContainer.Show() + ramUsed.SetText(p.Sprintf("%d", as.RamUsed)) + ramUsed.OnChanged = func(string) { + ramUsed.SetText(p.Sprintf("%d", as.RamUsed)) + } + ramUsed.Refresh() + } + + msig := widget.NewMultiLineEntry() + msigContainer := fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(3), + widget.NewLabelWithStyle("MultiSig Account Info: ", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + msig, + layout.NewSpacer(), + ) + msigContainer.Hide() + if len(as.MsigOwners) != 0 { + msigContainer.Show() + msig.SetText(fmt.Sprintf("Required Threshold: %d\nOwners:\n - %s", as.MsigThresh, strings.Join(as.MsigOwners, "\n - "))) + msig.OnChanged = func(string) { + msig.SetText(fmt.Sprintf("Required Threshold: %d\nOwners:\n - %s", as.MsigThresh, strings.Join(as.MsigOwners, "\n - "))) + } + msig.Refresh() + } + + report = widget.NewVBox( + fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(3), + widget.NewLabelWithStyle("Actor: ", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + entryActor, + layout.NewSpacer(), + ), + fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(3), + widget.NewLabelWithStyle("Public Key: ", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + entryPub, + layout.NewSpacer(), + ), + fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(3), + widget.NewLabelWithStyle("Balance: ", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + entryBal, + layout.NewSpacer(), + ), + ramContainer, + msigContainer, + domains, + + names, + ) + report.Refresh() + + return report, nil +} diff --git a/account-info.go b/account-info.go new file mode 100644 index 0000000..7ef022f --- /dev/null +++ b/account-info.go @@ -0,0 +1,394 @@ +package cryptonym + +import ( + "crypto/sha1" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go" + "github.com/fioprotocol/fio-go/eos" + "math" + "sort" + "strings" + "sync" + "time" +) + +type AccountInformation struct { + *sync.Mutex + + Actor string `json:"actor"` + PubKey string `json:"pub_key"` + PrivKey string `json:"priv_key"` + Balance int64 `json:"balance"` + BundleCred int `json:"bundle_cred"` + MsigOwners []string `json:"msig_owners"` + MsigThresh uint32 `json:"msig_thresh"` + RamUsed int64 `json:"ram_used"` + fioNames []string + FioNames []FioAddressStruct `json:"fio_names"` + fioDomains []string + FioDomains []FioDomainStruct `json:"fio_domains"` + PublicKeys []AddressesList `json:"public_keys"` + api *fio.API + Producer *ProducerInfo `json:"producer"` +} + +type FioAddressStruct struct { + Id int `json:"id"` + Name string `json:"name"` + NameHash string `json:"namehash"` + Domain string `json:"domain"` + DomainHash string `json:"domainhash"` + Expiration int64 `json:"expiration"` + OwnerAccount string `json:"owner_account"` + Addresses []AddressesList `json:"addresses"` + BundleCount uint64 `json:"bundleeligiblecountdown"` +} + +type FioDomainStruct struct { + Name string `json:"name"` + IsPublic uint8 `json:"is_public"` + Expiration int64 `json:"expiration"` + Account eos.AccountName `json:"account"` +} + +type AddressesList struct { + TokenCode string `json:"token_code"` + ChainCode string `json:"chain_code"` + PublicAddress string `json:"public_address"` +} + +type ProducerInfo struct { + Owner string `json:"owner"` + FioAddress string `json:"fio_address"` + TotalVotes float64 `json:"total_votes"` + ProducerPublicKey string `json:"producer_public_key"` + IsActive bool `json:"is_active"` + Url string `json:"url"` + UnpaidBlocks int `json:"unpaid_blocks"` + LastClaimTime time.Time `json:"last_claim_time"` + Location int `json:"location"` +} + +var bpLocationMux sync.RWMutex +var bpLocationMap = map[int]string{ + 10: "East Asia", + 20: "Australia", + 30: "West Asia", + 40: "Africa", + 50: "Europe", + 60: "East North America", + 70: "South America", + 80: "West North America", +} + +var accountSearchType = []string{ + "Public Key", + "Fio Address", + "Private Key", + "Actor/Account", + "Fio Domain", // TODO: how is index derived on fio.address domains table? +} + +func GetLocation(i int) string { + bpLocationMux.RLock() + defer bpLocationMux.RUnlock() + loc := bpLocationMap[i] + if loc == "" { + return "Invalid Location" + } + return loc +} + +func AccountSearch(searchFor string, value string) (as *AccountInformation, err error) { + as = &AccountInformation{} + as.api, _, err = fio.NewConnection(nil, Uri) + if err != nil { + return nil, err + } + as.api.Header.Set("User-Agent", "fio-cryptonym-wallet") + switch searchFor { + case "Actor/Account": + return as, as.searchForActor(value) + case "Public Key": + return as, as.searchForPub(value) + case "Private Key": + return as, as.searchForPriv(value) + case "Fio Address": + return as, as.searchForAddr(value) + case "Fio Domain": + return as, as.searchForDom(value) + } + return nil, nil +} + +type aMap struct { + Clientkey string `json:"clientkey"` +} + +func (as *AccountInformation) searchForActor(s string) error { + if s == "eosio" || strings.HasPrefix(s, "eosio.") || strings.HasPrefix(s, "fio.") { + resp, err := as.api.GetFioAccount(s) + if err != nil { + return err + } + as.PubKey = "n/a" + as.Actor = s + if len(resp.Permissions) > 0 { + if len(resp.Permissions[0].RequiredAuth.Keys) == 1 { + as.PubKey = resp.Permissions[0].RequiredAuth.Keys[0].PublicKey.String() + } + for _, p := range resp.Permissions { + if len(p.RequiredAuth.Accounts) > 0 { + for _, a := range p.RequiredAuth.Accounts { + as.MsigOwners = append(as.MsigOwners, string(a.Permission.Actor)) + } + } + } + } + if as.PubKey != "n/a" { + as.PubKey = "Warning, not msig! - " + as.PubKey + } + return nil + } + name, err := eos.StringToName(s) + if err != nil { + return err + } + resp, err := as.api.GetTableRows(eos.GetTableRowsRequest{ + Code: "fio.address", + Scope: "fio.address", + Table: "accountmap", + LowerBound: fmt.Sprintf("%d", name), + UpperBound: fmt.Sprintf("%d", name), + Limit: math.MaxInt32, + KeyType: "i64", + Index: "1", + JSON: true, + }) + if err != nil { + fmt.Println(err) + } + found := make([]aMap, 0) + err = json.Unmarshal(resp.Rows, &found) + if err != nil { + return err + } + if len(found) == 0 { + return errors.New("no matching account found in fio.address accountmap table") + } + as.Actor = s + as.PubKey = found[0].Clientkey + return as.searchForPub(as.PubKey) +} + +func (as *AccountInformation) searchForPub(s string) error { + names, found, err := as.api.GetFioNames(s) + if err != nil { + return err + } + as.PubKey = s + a, err := fio.ActorFromPub(s) + if err != nil { + return err + } + assets, err := as.api.GetCurrencyBalance(a, "FIO", "fio.token") + if err != nil { + return err + } + if len(assets) > 0 { + as.Balance = int64(assets[0].Amount) + } + as.Actor = string(a) + if found { + for _, n := range names.FioAddresses { + as.fioNames = appendUniq(as.fioNames, n.FioAddress) + } + for _, n := range names.FioDomains { + as.fioDomains = appendUniq(as.fioDomains, n.FioDomain) + } + } + as.getFioNames() + as.getFioDomains() + as.getExtra() + return nil +} + +func (as *AccountInformation) searchForPriv(s string) error { + a, err := fio.NewAccountFromWif(s) + if err != nil { + return err + } + as.PrivKey = s + as.PubKey = a.PubKey + return as.searchForPub(a.PubKey) +} + +func (as *AccountInformation) searchForAddr(s string) error { + pubAddr, found, err := as.api.PubAddressLookup(fio.Address(s), "FIO", "FIO") + if err != nil { + return err + } + if !found { + return errors.New("did not find any FIO public keys for that address") + } + as.fioNames = appendUniq(as.fioNames, s) + as.PubKey = pubAddr.PublicAddress + return as.searchForPub(pubAddr.PublicAddress) +} + +func (as *AccountInformation) getFioNames() { + const limit = 20 + n, err := eos.StringToName(as.Actor) + if err != nil { + errs.ErrChan <- err.Error() + return + } + name := fmt.Sprintf("%d", n) + row, err := as.api.GetTableRows(eos.GetTableRowsRequest{ + Code: "fio.address", + Scope: "fio.address", + Table: "fionames", + LowerBound: name, + UpperBound: name, + Limit: limit, + KeyType: "i64", + Index: "4", + JSON: true, + }) + if err != nil { + errs.ErrChan <- err.Error() + return + } + if len(row.Rows) > 2 { + fNames := make([]FioAddressStruct, 0) + err = json.Unmarshal(row.Rows, &fNames) + if err != nil { + errs.ErrChan <- err.Error() + return + } + as.FioNames = append(as.FioNames, fNames...) + } + if row.More { + errs.ErrChan <- fmt.Sprintf("truncated results to first %d addresses", limit) + } +} + +func (as *AccountInformation) getFioDomains() { + const limit = 20 + row, err := as.api.GetTableRows(eos.GetTableRowsRequest{ + Code: "fio.address", + Scope: "fio.address", + Table: "domains", + LowerBound: as.Actor, + UpperBound: as.Actor, + Limit: limit, + KeyType: "name", + Index: "2", + JSON: true, + }) + if err != nil { + errs.ErrChan <- err.Error() + return + } + if len(row.Rows) > 2 { + fDoms := make([]FioDomainStruct, 0) + err = json.Unmarshal(row.Rows, &fDoms) + if err != nil { + errs.ErrChan <- err.Error() + return + } + doms: + for _, fDom := range fDoms { + for _, existing := range as.FioDomains { + if existing.Name == fDom.Name { + continue doms + } + } + as.FioDomains = append(as.FioDomains, fDom) + } + } + if row.More { + errs.ErrChan <- fmt.Sprintf("truncated results to first %d domains", limit) + } +} + +// only works on >= v0.9.0 +func (as *AccountInformation) searchForDom(s string) error { + ss := FioDomainNameHash(s) + resp, err := as.api.GetTableRows(eos.GetTableRowsRequest{ + Code: "fio.address", + Scope: "fio.address", + Table: "domains", + LowerBound: ss, + UpperBound: ss, + Limit: 1, + KeyType: "i128", + Index: "4", + JSON: true, + }) + if err != nil { + return err + } + if len(resp.Rows) > 2 { + d := make([]FioDomainStruct, 0) + err = json.Unmarshal(resp.Rows, &d) + if err != nil { + return err + } + as.FioDomains = append(as.FioDomains, d...) + if as.Actor == "" && len(d) > 0 && d[0].Account != "" { + return as.searchForActor(string(d[0].Account)) + } + } + return nil +} + +func (as *AccountInformation) getExtra() { + if as.Actor != "" { + acc, err := as.api.GetFioAccount(as.Actor) + if err != nil { + return + } + as.RamUsed = int64(acc.RAMUsage) + for _, a := range acc.Permissions { + if a.PermName == "active" && a.RequiredAuth.Accounts != nil && len(a.RequiredAuth.Accounts) > 0 { + as.MsigThresh = a.RequiredAuth.Threshold + for _, owner := range a.RequiredAuth.Accounts { + as.MsigOwners = append(as.MsigOwners, fmt.Sprintf("%s (weight: %d)", owner.Permission.Actor, owner.Weight)) + } + } + } + } +} + +func FioDomainNameHash(s string) string { + sha := sha1.New() + sha.Write([]byte(s)) + // last 16 bytes of sha1-sum, as big-endian + return "0x" + hex.EncodeToString(FlipEndian(sha.Sum(nil)))[8:] +} + +func FlipEndian(orig []byte) []byte { + flipped := make([]byte, len(orig)) + for i := range orig { + flipped[len(flipped)-i-1] = orig[i] + } + return flipped +} + +func appendUniq(oldSlice []string, add ...string) (newSlice []string) { + u := make(map[string]bool) + oldSlice = append(oldSlice, add...) + for _, v := range oldSlice { + u[v] = true + } + for k := range u { + newSlice = append(newSlice, k) + } + sort.Strings(newSlice) + return +} diff --git a/account-info_test.go b/account-info_test.go new file mode 100644 index 0000000..0920920 --- /dev/null +++ b/account-info_test.go @@ -0,0 +1,64 @@ +package cryptonym + +import ( + "encoding/json" + "fmt" + "testing" +) + +/* + "Actor/Account", + "Public Key", + "Private Key", + "Fio Address", + "Fio Domain" + +*/ +func TestAccountSearch(t *testing.T) { + pub := "FIO6G9pXXM92Gy5eMwNquGULoCj3ZStwPLPdEb9mVXyEHqWN7HSuA" + priv := "5JBbUG5SDpLWxvBKihMeXLENinUzdNKNeozLas23Mj6ZNhz3hLS" + actor := "o2ouxipw2rt4" + address := "vote1@dapixdev" + + Uri = "http://localhost:8888" + + a, e := AccountSearch("Fio Address", address) + if e != nil { + t.Error(e.Error()) + } else { + j, _ := json.MarshalIndent(a, "", " ") + fmt.Println("address:") + fmt.Println(string(j)) + fmt.Println("") + } + + a, e = AccountSearch("Private Key", priv) + if e != nil { + t.Error(e.Error()) + } else { + j, _ := json.MarshalIndent(a, "", " ") + fmt.Println("priv:") + fmt.Println(string(j)) + fmt.Println("") + } + + a, e = AccountSearch("Public Key", pub) + if e != nil { + t.Error(e.Error()) + } else { + j, _ := json.MarshalIndent(a, "", " ") + fmt.Println("pub:") + fmt.Println(string(j)) + fmt.Println("") + } + + a, e = AccountSearch("Actor/Account", actor) + if e != nil { + t.Error(e.Error()) + } else { + j, _ := json.MarshalIndent(a, "", " ") + fmt.Println("actor:") + fmt.Println(string(j)) + fmt.Println("") + } +} diff --git a/action-form.go b/action-form.go new file mode 100644 index 0000000..1dd6d4e --- /dev/null +++ b/action-form.go @@ -0,0 +1,442 @@ +package cryptonym + +import ( + "errors" + "fmt" + "fyne.io/fyne" + "fyne.io/fyne/layout" + "fyne.io/fyne/widget" + fioassets "github.com/blockpane/cryptonym/assets" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/blockpane/cryptonym/fuzzer" + "github.com/fioprotocol/fio-go" + "github.com/fioprotocol/fio-go/eos" + "math/rand" + "os" + "strconv" + "strings" + "time" +) + +var ( + FormState = NewAbi(0) + bombsAway = &widget.Button{} + txWindowOpts = &txResultOpts{ + window: App.NewWindow("Tx Results"), + gone: true, + } +) + +func ResetTxResult() { + if txWindowOpts.window != nil && !txWindowOpts.gone { + txWindowOpts.window.Close() + } + txWindowOpts.window = nil + txWindowOpts.window = App.NewWindow("Tx Results") + txWindowOpts.gone = true + txWindowOpts.window.SetContent(layout.NewSpacer()) + txWindowOpts.window.Show() + go func() { + time.Sleep(time.Second) + txWindowOpts.window.Hide() + }() +} + +// GetAbiForm returns the fyne form for editing the request, it also handles state tracking via +// the FormState which is later used to build the transaction. +func GetAbiForm(action string, account *fio.Account, api *fio.API, opts *fio.TxOptions) (fyne.CanvasObject, error) { + if api.HttpClient == nil { + return widget.NewVBox(), nil + } + accountAction := strings.Split(action, "::") + if len(accountAction) != 2 { + e := "couldn't parse account and action for " + action + errs.ErrChan <- e + return nil, errors.New(e) + } + abi, err := api.GetABI(eos.AccountName(accountAction[0])) + if err != nil { + errs.ErrChan <- err.Error() + return nil, err + } + abiStruct := abi.ABI.StructForName(accountAction[1]) + form := widget.NewForm() + + abiState := NewAbi(len(abiStruct.Fields)) + abiState.Contract = accountAction[0] + abiState.Action = accountAction[1] + for i, deRef := range abiStruct.Fields { + fieldRef := &deRef + field := *fieldRef + + // input field + inLabel := widget.NewLabel("Input:") + if os.Getenv("ADVANCED") == "" { + inLabel.Hide() + } + in := widget.NewEntry() + in.SetText(defaultValues(accountAction[0], accountAction[1], field.Name, field.Type, account, api)) + inputBox := widget.NewHBox( + inLabel, + in, + ) + in.OnChanged = func(s string) { + FormState.UpdateInput(field.Name, in) + } + + // abi type + typeSelect := &widget.Select{} + typeSelect = widget.NewSelect(abiSelectTypes(field.Type), func(s string) { + FormState.UpdateType(field.Name, typeSelect) + }) + typeSelect.SetSelected(field.Type) + if os.Getenv("ADVANCED") == "" { + typeSelect.Hide() + } + + // count field, hidden by default + num := &widget.Select{} + num = widget.NewSelect(bytesLen, func(s string) { + FormState.UpdateLen(field.Name, num) + }) + num.Hide() + + // variant field + variation := &widget.Select{} + variation = widget.NewSelect(formVar, func(s string) { + showNum, numVals, sel := getLength(s) + if showNum { + num.Show() + } else { + num.Hide() + } + num.Options = numVals + num.SetSelected(sel) + FormState.UpdateLen(field.Name, num) + FormState.UpdateVariation(field.Name, variation) + }) + if os.Getenv("ADVANCED") == "" { + variation.Hide() + } + + // options for fuzzer + sendAs := &widget.Select{} + sendAs = widget.NewSelect(sendAsSelectTypes, func(send string) { + if !strings.Contains(send, "form value") { + inputBox.Hide() + } else { + inputBox.Show() + } + var sel string + variation.Options, sel = sendAsVariant(send) + variation.SetSelected(sel) + FormState.UpdateSendAs(field.Name, sendAs) + }) + sendAs.SetSelected("form value") + if os.Getenv("ADVANCED") == "" { + sendAs.Hide() + } + + form.Append(field.Name, + widget.NewVBox( + fyne.NewContainerWithLayout(layout.NewGridLayout(5), + typeSelect, + sendAs, + variation, + num, + ), + inputBox, + ), + ) + //name := field.Name + abiState.Update(&i, AbiFormItem{ + Contract: accountAction[0], + Action: accountAction[1], + Name: &field.Name, + Type: typeSelect, + SendAs: sendAs, + Variation: variation, + Input: in, + Len: num, + Order: i, + }) + if strings.HasPrefix(in.Text, "{") || strings.HasPrefix(in.Text, "[{") { + variation.SetSelected("json -> struct") + //in.Lock() + in.MultiLine = true + //in.Unlock() + } + if field.Name == "amount" || field.Name == "max_fee" { + variation.SetSelected("FIO -> suf") + if !strings.Contains(in.Text, ".") { + in.SetText("10,000.00") + } + } + } + + hideFailed := widget.NewCheck("Hide Failed", func(b bool) {}) + hideSuccess := widget.NewCheck("Hide Successful", func(b bool) {}) + zlibPack := widget.NewCheck("Pack With ZLib", func(b bool) { + useZlib = b + }) + zlibPack.Checked = useZlib + zlibPack.Refresh() + threadLabel := widget.NewLabel("Worker Count: ") + threadLabel.Hide() + threads := widget.NewSelect([]string{"1", "2", "4", "6", "8", "12", "16"}, func(s string) {}) + threads.SetSelected("1") + threads.Hide() + count := widget.NewEntry() + count.SetText("1") + delaySec := widget.NewEntry() + delaySec.SetText(fmt.Sprintf("%d", delayTxSec)) + if !deferTx { + delaySec.Hide() + } + delaySec.OnChanged = func(s string) { + i, err := strconv.Atoi(s) + if err != nil { + errs.ErrChan <- "error converting delay time to int, setting to 0!" + delayTxSec = 0 + delaySec.SetText("0") + delaySec.Refresh() + } + delayTxSec = i + } + deferCheck := &widget.Check{} + deferCheck = widget.NewCheck("Delay Transaction", func(b bool) { + if b { + deferCheck.Text = "Delay Transaction (seconds:)" + deferCheck.Refresh() + deferTx = true + delaySec.Show() + return + } + deferCheck.Text = "Delay Transaction" + deferCheck.Refresh() + deferTx = false + delaySec.Hide() + }) + if deferTx { + deferCheck.Text = "Delay Transaction (seconds:)" + deferCheck.Refresh() + } + deferCheck.Checked = deferTx + deferCheck.Refresh() + infinite := widget.NewCheck("Loop", func(b bool) { + if b { + count.Disable() + threadLabel.Show() + threads.Show() + return + } + count.Enable() + threadLabel.Hide() + threads.Hide() + threads.SetSelected("1") + }) + + err = EndPoints.Update(Uri, true) + if err != nil { + errs.ErrChan <- "Could not update api endpoint list: " + err.Error() + } + actionEndPointActive = "/v1/chain/push_transaction" + apiEndpoint := widget.NewSelect(EndPoints.Apis, func(s string) { + actionEndPointActive = s + }) + apiEndpoint.SetSelected("/v1/chain/push_transaction") + apiEndpoint.Refresh() + + txWindowOpts.window.Resize(fyne.NewSize(txW, txH)) + + // multisig options: + randProposal := func() string { + var s string + for i := 0; i < 12; i++ { + b := []byte{uint8(rand.Intn(26) + 97)} + s = s + string(b) + } + return s + } + requested := widget.NewEntry() + requested.SetText(getSigners(Settings.MsigAccount, api)) + msig := &widget.Box{} + wrap := &widget.Box{} + proposer := widget.NewEntry() + proposer.SetText(Settings.MsigAccount) + innerActionActor := widget.NewEntry() + innerActionActor.SetText("eosio") + innerActionActor.Hide() + innerActionLabel := widget.NewLabel("Actor for inner action: ") + innerActionLabel.Hide() + wrapCheck := widget.NewCheck("Wrap Msig", func(b bool) { + if b { + innerActionActor.Show() + innerActionLabel.Show() + proposer.SetText("eosio.wrap") + return + } + proposer.SetText(Settings.MsigAccount) + innerActionActor.Hide() + innerActionLabel.Hide() + }) + proposer.OnChanged = func(s string) { + requested.SetText(getSigners(s, api)) + if wrapCheck.Checked { + s = innerActionActor.Text + } + for _, row := range FormState.Rows { + if *row.Name == "actor" { + row.Input.SetText(s) + row.Input.Refresh() + break + } + } + go func() { + time.Sleep(100 * time.Millisecond) + requested.Refresh() + msig.Refresh() + }() + } + proposalName := widget.NewEntry() + proposalName.SetText(randProposal()) + proposalName.Hide() + proposalRand := widget.NewCheck("Random Name", func(b bool) { + if b { + proposalName.Hide() + } else { + proposalName.Show() + } + }) + proposalRand.SetChecked(true) + + msig = widget.NewHBox( + widget.NewLabelWithStyle("Proposal Name: ", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + proposalRand, + proposalName, + widget.NewLabelWithStyle("Multi-Sig Account: ", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + proposer, + widget.NewLabelWithStyle("Requested Signers: ", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(380, 44)), + widget.NewScrollContainer(requested), + ), + layout.NewSpacer(), + ) + msig.Hide() + forcedSpace := widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(20, 100)), + layout.NewSpacer(), + ), + ) + + wrap = widget.NewHBox( + wrapCheck, + innerActionLabel, + innerActionActor, + ) + wrap.Hide() + proposeCheck := widget.NewCheck("Propose", func(b bool) { + if b { + msig.Show() + //wrap.Show() + } else { + msig.Hide() + wrap.Hide() + } + }) + + bombsAway = widget.NewButtonWithIcon("Send", fioassets.NewFioLogoResource(), func() { + fuzzer.ResetIncrement() + errs.ErrChan <- "generating transaction" + repeat, err := strconv.Atoi(count.Text) + if err != nil { + repeat = 1 + } + txWindowOpts.msig = proposeCheck.Checked + txWindowOpts.msigSigners = requested.Text + txWindowOpts.msigAccount = proposer.Text + txWindowOpts.repeat = repeat + txWindowOpts.loop = infinite.Checked + txWindowOpts.threads = threads.Selected + txWindowOpts.hideFail = hideFailed.Checked + txWindowOpts.hideSucc = hideSuccess.Checked + txWindowOpts.wrap = wrapCheck.Checked + txWindowOpts.wrapActor = innerActionActor.Text + if proposalRand.Checked { + txWindowOpts.msigName = randProposal + } else { + txWindowOpts.msigName = func() string { + return proposalName.Text + } + } + TxResultsWindow(txWindowOpts, api, opts, account) + }) + + reqToSend := widget.NewLabel("Requests to send") + if os.Getenv("ADVANCED") == "" { + reqToSend.Hide() + count.Hide() + infinite.Hide() + threadLabel.Hide() + threads.Hide() + hideFailed.Hide() + hideSuccess.Hide() + zlibPack.Hide() + deferCheck.Hide() + delaySec.Hide() + } + bottom := widget.NewHBox( + widget.NewLabel(" "), + bombsAway, + reqToSend, + count, + infinite, + threadLabel, + threads, + hideFailed, + hideSuccess, + zlibPack, + deferCheck, + delaySec, + proposeCheck, + ) + newRowName := widget.NewEntry() + newRowName.SetPlaceHolder("New Row Name") + label := widget.NewLabel(action) + label.TextStyle = fyne.TextStyle{Bold: true, Monospace: true} + apiEndLabel := widget.NewLabel("API Endpoint") + addRowButton := abiState.AddNewRowButton(newRowName, account, form) + //topSpace := layout.NewSpacer() + if os.Getenv("ADVANCED") == "" { + apiEndLabel.Hide() + apiEndpoint.Hide() + addRowButton.Hide() + newRowName.Hide() + //topSpace.Hide() + } + content := widget.NewVScrollContainer( + widget.NewVBox( + label, + layout.NewSpacer(), + form, + layout.NewSpacer(), + bottom, + msig, + wrap, + widget.NewHBox( + apiEndLabel, + apiEndpoint, + layout.NewSpacer(), + addRowButton, + newRowName, + layout.NewSpacer(), + ), + forcedSpace, + ), + ) + + abiState.mux.Lock() + FormState = abiState + abiState.mux.Unlock() + return content, nil +} diff --git a/action-state.go b/action-state.go new file mode 100644 index 0000000..b2ed696 --- /dev/null +++ b/action-state.go @@ -0,0 +1,267 @@ +package cryptonym + +import ( + "fmt" + "fyne.io/fyne" + "fyne.io/fyne/layout" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go" + "reflect" + "strings" + "sync" +) + +type AbiFormItem struct { + Contract string + Action string + Order int + Name *string + Type *widget.Select + SendAs *widget.Select + Variation *widget.Select + Len *widget.Select + Input *widget.Entry + Value *interface{} + IsSlice bool + SliceValue []*interface{} + convert func(s interface{}) interface{} + typeOverride string + noJsonEscape bool // if true uses fmt, otherwise json-encodes the value ... fmt is useful for some numeric values +} + +type Abi struct { + mux sync.RWMutex + + lookUp map[string]int + Rows []AbiFormItem + Action string + Contract string +} + +func NewAbi(length int) *Abi { + return &Abi{ + Rows: make([]AbiFormItem, length), + lookUp: make(map[string]int), + } +} + +func (abi *Abi) AppendRow(myName string, account *fio.Account, form *widget.Form) { + go func() { + if myName == "" { + myName = fmt.Sprintf("new_row_%d", len(abi.Rows)) + } + if abi.Rows == nil { + abi.Rows = make([]AbiFormItem, 0) + } + typeSelect := &widget.Select{} + in := widget.NewEntry() + num := &widget.Select{} + inputBox := widget.NewHBox( + widget.NewLabel("Input:"), + in, + ) + variation := &widget.Select{} + sendAs := &widget.Select{} + + field := AbiFormItem{ + Contract: abi.Contract, + Action: abi.Action, + Order: len(abi.Rows), + Name: &myName, + + Type: typeSelect, + SendAs: sendAs, + Variation: variation, + Len: num, + Input: in, + } + + in.OnChanged = func(s string) { + FormState.UpdateInput(myName, in) + } + + typeSelect = widget.NewSelect(abiSelectTypes("string"), func(s string) { + FormState.UpdateType("string", typeSelect) + }) + typeSelect.SetSelected("string") + + sendAs = widget.NewSelect(sendAsSelectTypes, func(send string) { + if !strings.Contains(send, "form value") { + inputBox.Hide() + } else { + inputBox.Show() + } + var sel string + variation.Options, sel = sendAsVariant(send) + variation.SetSelected(sel) + FormState.UpdateSendAs(myName, sendAs) + }) + + sendAs.SetSelected("bytes/string") + variation = widget.NewSelect(bytesVar, func(s string) { + showNum, numVals, sel := getLength(s) + if showNum { + num.Show() + } else { + num.Hide() + } + num.Options = numVals + num.SetSelected(sel) + FormState.UpdateLen(myName, num) + FormState.UpdateVariation(myName, variation) + }) + variation.SetSelected("many AAAA...") + + num = widget.NewSelect(bytesLen, func(s string) { + FormState.UpdateLen(myName, num) + }) + num.SetSelected("131,072") + + form.Append(*field.Name, + widget.NewVBox( + fyne.NewContainerWithLayout(layout.NewGridLayout(5), + typeSelect, + sendAs, + variation, + num, + ), + inputBox, + ), + ) + + abi.mux.Lock() + abi.Rows = append(abi.Rows, field) + abi.lookUp[myName] = len(abi.Rows) - 1 + abi.mux.Unlock() + abi.UpdateInput(myName, in) + abi.UpdateSendAs(myName, sendAs) + abi.UpdateType(myName, typeSelect) + abi.UpdateLen(myName, num) + }() +} + +func (abi *Abi) AddNewRowButton(name *widget.Entry, account *fio.Account, form *widget.Form) *widget.Button { + b := &widget.Button{} + b = widget.NewButtonWithIcon("Add Row", theme.ContentAddIcon(), func() { + defer name.SetText("") + abi.mux.RLock() + if abi.Rows != nil && len(abi.Rows) > 0 { + if abi.lookUp[name.Text] > 0 || *abi.Rows[0].Name == name.Text { + errs.ErrChan <- "cannot add duplicate name for abi struct" + return + abi.mux.RUnlock() + } + } + abi.mux.RUnlock() + abi.AppendRow(name.Text, account, form) + }) + return b +} + +func (abi *Abi) UpdateValueWithConvert(index *int, value interface{}, isSlice bool, abiType string, noJsonEscape bool) { + if len(abi.Rows) == 0 { + return + } + newVal := value + abi.mux.Lock() + if abiType != "" { + abi.Rows[*index].typeOverride = abiType + if abiType == "string" { + // reflect on our interface to see if it should be converted from a number to a quoted number: + t := reflect.TypeOf(value) + if strings.Contains(t.String(), "int") || strings.Contains(t.String(), "float") { + newVal = fmt.Sprintf("%v", value) + } + } + } else { + abi.Rows[*index].typeOverride = "" + } + abi.mux.Unlock() + abi.UpdateValue(index, newVal, isSlice, noJsonEscape) +} + +func (abi *Abi) UpdateValue(index *int, value interface{}, isSlice bool, noJsonEscape bool) { + if len(abi.Rows) == 0 { + return + } + abi.Rows[*index].noJsonEscape = noJsonEscape + abi.mux.Lock() + if !isSlice { + abi.Rows[*index].Value = &value + } else { + sl := make([]*interface{}, 0) + sl = append(sl, &value) + abi.Rows[*index].IsSlice = true + abi.Rows[*index].SliceValue = sl + } + abi.mux.Unlock() +} + +func (abi *Abi) Update(index *int, abiForm AbiFormItem) { + abi.mux.Lock() + defer abi.mux.Unlock() + if len(abi.Rows) == 0 { + return + } + if index != nil { + abi.lookUp[*abiForm.Name] = *index + } + abi.Rows[abi.lookUp[*abiForm.Name]] = abiForm +} + +func (abi *Abi) UpdateType(name string, t *widget.Select) { + if len(abi.Rows) == 0 { + return + } + abi.mux.Lock() + if abi.lookUp[name] != 0 || *abi.Rows[0].Name == name { + abi.Rows[abi.lookUp[name]].Type = t + } + abi.mux.Unlock() +} + +func (abi *Abi) UpdateLen(name string, t *widget.Select) { + if len(abi.Rows) == 0 { + return + } + abi.mux.Lock() + if abi.lookUp[name] != 0 || *abi.Rows[0].Name == name { + abi.Rows[abi.lookUp[name]].Len = t + } + abi.mux.Unlock() +} + +func (abi *Abi) UpdateVariation(name string, t *widget.Select) { + if len(abi.Rows) == 0 { + return + } + abi.mux.Lock() + if abi.lookUp[name] != 0 || *abi.Rows[0].Name == name { + abi.Rows[abi.lookUp[name]].Variation = t + } + abi.mux.Unlock() +} + +func (abi *Abi) UpdateSendAs(name string, t *widget.Select) { + if len(abi.Rows) == 0 { + return + } + abi.mux.Lock() + if abi.lookUp[name] != 0 || *abi.Rows[0].Name == name { + abi.Rows[abi.lookUp[name]].SendAs = t + } + abi.mux.Unlock() +} + +func (abi *Abi) UpdateInput(name string, t *widget.Entry) { + if len(abi.Rows) == 0 { + return + } + abi.mux.Lock() + if abi.lookUp[name] != 0 || *abi.Rows[0].Name == name { + abi.Rows[abi.lookUp[name]].Input = t + } + abi.mux.Unlock() +} diff --git a/action-vars.go b/action-vars.go new file mode 100644 index 0000000..34349cd --- /dev/null +++ b/action-vars.go @@ -0,0 +1,549 @@ +package cryptonym + +import ( + "encoding/hex" + "encoding/json" + "fmt" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/blockpane/cryptonym/fuzzer" + "github.com/fioprotocol/fio-go" + "github.com/fioprotocol/fio-go/eos" + "math" + "math/rand" + "strconv" + "strings" +) + +func abiSelectTypes(mustExist string) []string { + types := []string{ + "authority", + "bool", + "byte", + "byte[]", + "checksum256", + "float128", + "float32", + "float64", + "hex_bytes", + "int128", + "int16", + "int32", + "int64", + "name", + "public_key", + "signature", + "string", + "string[]", + "symbol", + "time", + "timestamp", + "uint128", + "uint16", + "uint32", + "uint64", + "varint32", + "varuint32", + } + for _, t := range types { + if t == mustExist { + return types + } + } + types = append(types, mustExist) + return types +} + +var sendAsSelectTypes = []string{ + "form value", + "actor", + "pub key", + "fio types", + "number", + "bytes/string", + //"load file", +} + +var bytesVar = []string{ + "bytes", + "bytes: base64 encoded", + "bytes: hex encoded", + "random checksum", + "string", +} + +var bytesLen = []string{ + "random length", + "8", + "12", + "16", + "32", + "64", + "128", + "256", + "512", + "2,048", + "4,096", + //"8,192", + //"16,384", + //"32,768", + //"65,536", + //"131,072", + //"262,144", + //"524,288", + //"1,048,576", + //"2,097,152", + //"4,194,304", + //"8,388,608", + //"16,777,216", +} + +var formVar = []string{ + "as is", + "FIO -> suf", + "json -> struct", + "base64 -> byte[]", + "checksum256", + "fio address@ (invalid)", + "fio address@ (valid)", + "fio address@ (valid, max size)", + "hex -> byte[]", + "signature", +} + +var actorVar = []string{ + "mine", + "random", +} + +var numericVar = []string{ + "incrementing float", + "incrementing int", + "random float", + "random int", + "overflow int", + "random number (mixed)", + "max int", +} + +var maxIntVar = []string{ + "int8", + "uint8", + "int16", + "uint16", + "int32", + "uint32", + "int64", + "uint64", +} + +var fioVar = []string{ + "invalid fio domain", + "valid fio domain", + "valid fio domain (max size)", + "max length: newfundsreq.content", + "max length: recordobt.content", + "max length: regproducer.url", + "max length: voteproducer.producers", + "max length: addaddress.public_addresses", + "variable length: addaddress.public_addresses", + //TODO: + //"string[] of existing fio address", +} + +//TODO: "string[] of existing fio address".... +var addressLen = []string{ + "2", + "4", + "8", + "16", + "32", +} + +var floatLen = []string{ + "32", + "64", +} + +var intLen = []string{ + "8", + "16", + "32", + "64", + "128", +} + +var overflowLen = []string{ + "8", + "16", + "32", +} + +var numAddresses = []string{ + "1", + "2", + "3", + "4", + "5", + "10", + "50", + "100", + "1000", +} + +func sendAsVariant(kind string) (options []string, selected string) { + switch kind { + case "form value": + return formVar, "as is" + case "actor": + return actorVar, "mine" + case "pub key": + return actorVar, "mine" + case "number": + return numericVar, "random int" + case "bytes/string": + return bytesVar, "string" + case "fio types": + return fioVar, "invalid fio domain" + } + return []string{}, "--" +} + +func getLength(what string) (show bool, values []string, selected string) { + switch { + case what == "random float": + return true, floatLen, "32" + case what == "variable length addaddress.public_addresses": + return true, numAddresses, "1" + case what == "random int": + return true, intLen, "32" + case what == "overflow int": + return true, overflowLen, "16" + case what == "max int": + return true, maxIntVar, "int32" + case what == "random number (mixed)": + return false, []string{""}, "" + case strings.HasPrefix(what, "string") || + strings.HasPrefix(what, "bytes") || + strings.HasPrefix(what, "nop") || + strings.HasPrefix(what, "many"): + return true, bytesLen, "64" + } + return +} + +func defaultValues(contract string, action string, fieldName string, fieldType string, account *fio.Account, api *fio.API) string { + var returnValue string + switch { + case fieldName == "amount": + return "1,000.00" + case fieldName == "bundled_transactions": + return "100" + case fieldName == "max_fee": + api2, _, err := fio.NewConnection(nil, api.BaseURL) + if err != nil { + return "0" + } + api2.Header.Set("User-Agent", "fio-cryptonym-wallet") + fio.UpdateMaxFees(api2) + fee := fio.GetMaxFeeByAction(action) + if fee == 0 { + // as expensive as it gets ... pretty safe to return + fee = fio.GetMaxFee("register_fio_domain") + } + returnValue = p.Sprintf("%.9f", fee) + case fieldName == "is_public": + returnValue = "1" + case fieldType == "tokenpubaddr[]": + a, t := fuzzer.NewPubAddress(account) + returnValue = fmt.Sprintf(`[{ + "token_code": "%s", + "chain_code": "%s", + "public_address": "%s" +}]`, t, t, a) + case fieldName == "url": + returnValue = "https://fioprotocol.io" + case fieldName == "location": + returnValue = "80" + case fieldName == "fio_domain": + returnValue = "cryptonym" + case fieldType == "bool": + returnValue = "true" + case fieldType == "authority": + returnValue = `{ + "threshold": 2, + "keys": [], + "waits": [], + "accounts": [ + { + "permission": { + "actor": "npe3obkgoteh", + "permission": "active" + }, + "weight": 1 + }, + { + "permission": { + "actor": "extjnqh3j3gt", + "permission": "active" + }, + "weight": 1 + } + ] + }` + case strings.HasSuffix(fieldType, "int128"): + i28 := eos.Uint128{ + Lo: uint64(rand.Int63n(math.MaxInt64)), + Hi: uint64(rand.Int63n(math.MaxInt64)), + } + j, _ := json.Marshal(i28) + returnValue = strings.Trim(string(j), `"`) + case strings.HasPrefix(fieldType, "uint") || strings.HasPrefix(fieldType, "int"): + returnValue = strconv.Itoa(rand.Intn(256)) + case strings.HasPrefix(fieldType, "float"): + returnValue = "3.14159265359" + case fieldName == "owner" || fieldName == "account" || fieldName == "actor" || fieldType == "authority" || fieldName == "proxy": + actor, _ := fio.ActorFromPub(account.PubKey) + returnValue = string(actor) + case strings.Contains(fieldName, "public") || strings.HasSuffix(fieldName, "_key"): + returnValue = account.PubKey + case fieldName == "tpid": + returnValue = Settings.Tpid + case strings.HasSuffix(fieldName, "_address") || strings.HasPrefix(fieldName, "pay"): + returnValue = DefaultFioAddress + case fieldName == "authority" || (fieldName == "permission" && fieldType == "name"): + returnValue = "active" + case fieldName == "producers": + returnValue = GetCurrentVotes(string(account.Actor), api) + case fieldType == "transaction": + returnValue = `{ + "context_free_actions": [], + "actions": [ + { + "signatures": [ + "SIG_K1_..." + ], + "compression": "none", + "packed_context_free_data": "", + "packed_trx": "b474345e54..." + } + ], + "transaction_extensions": [] +}` + case fieldType == "asset": + returnValue = "100000.000000000 FIO" + case (fieldName == "to" || fieldName == "from") && fieldType == "name": + returnValue = string(account.Actor) + case fieldType == "permission_level": + returnValue = fmt.Sprintf(`{ + "actor":"%s", + "permission":"active" +}`, account.Actor) + case fieldType == "permission_level[]": + returnValue = fmt.Sprintf(`[{ + "actor":"%s", + "permission":"active" +}]`, account.Actor) + case fieldType == "proposal": + returnValue = `{ + "proposal_name":"proposal", + "packed_transaction":"0x0a0b0c0d0e0f" +}` + case fieldType == "blockchain_parameters": + returnValue = `{ + "max_block_net_usage": 1048576, + "target_block_net_usage_pct": 1000, + "max_transaction_net_usage": 524288, + "base_per_transaction_net_usage": 12, + "net_usage_leeway": 500, + "context_free_discount_net_usage_num": 20, + "context_free_discount_net_usage_den": 100, + "max_block_cpu_usage": 200000, + "target_block_cpu_usage_pct": 1000, + "max_transaction_cpu_usage": 150000, + "min_transaction_cpu_usage": 100, + "max_transaction_lifetime": 3600, + "deferred_trx_expiration_window": 600, + "max_transaction_delay": 3888000, + "max_inline_action_size": 4096, + "max_inline_action_depth": 4, + "max_authority_depth": 6, + "last_producer_schedule_update": 1580492400, + "last_pervote_bucket_fill": 1580492400, + "pervote_bucket": 0, + "perblock_bucket": 0, + "total_unpaid_blocks": 0, + "total_voted_fio": 76103319000000000, + "thresh_voted_fio_time": 1580492400, + "last_producer_schedule_size": 3, + "total_producer_vote_weight": "75000307600000000.00000000000000000", + "last_name_close": 1580492400, + "last_fee_update": 1580492400 +}` + case fieldType == "block_header": + returnValue = `{ + "timestamp": 1580492400, + "producer": "eosio", + "confirmed": 1, + "previous": "0000000000000000000000000000000000000000000000000000000000000000", + "transaction_mroot": "0000000000000000000000000000000000000000000000000000000000000000", + "action_mroot": "0000000000000000000000000000000000000000000000000000000000000000", + "schedule_version": 0 +}` + case fieldName == "end_point": + returnValue = `auth_update` + case fieldType == "extension": + returnValue = `{"type": 1, "data": "0x0a0b0c0d0e0f"}` + case fieldType == "feevalue": + returnValue = `{ + "end_point": "register_fio_domain", + "value": 8000000000 +}` + case fieldType == "feevalue[]": + returnValue = `[{ + "end_point": "register_fio_domain", + "value": 8000000000 + }, + { + "end_point": "register_fio_address", + "value": 1000000000 + } +]` + case strings.HasPrefix(fieldType, "checksum256"): + rc := make([]byte, 32) + rand.Read(rc) + returnValue = hex.EncodeToString(rc) + default: + returnValue = "" + } + return returnValue +} + +func getInterface(t string) interface{} { + types := map[string]interface{}{ + "authority": eos.Authority{}, + "bool": eos.Bool(true), + "byte": byte(0), + //"byte[]": make([]byte,0), + "byte[]": "", + "checksum256": eos.Checksum256{}, + "checksum256$": eos.Checksum256{}, + "float128": eos.Float128{}, + "float32": float32(0), + "float64": float64(0), + "hex_bytes": eos.HexBytes{}, + "int128": eos.Int128{}, + "int16": int16(0), + "int32": int32(0), + //"int64": eos.Int64(0), + "int64": int64(0), + "int8": int8(0), + "name": "", + "public_key": "", + "signature": eos.HexBytes{}, + "string": "", + "symbol": eos.Symbol{}, + "time": eos.Tstamp{}, + "timestamp": eos.Tstamp{}, + "uint128": eos.Uint128{}, + "uint16": uint16(0), + "uint32": uint32(0), + "uint64": eos.Uint64(0), + "varint32": eos.Varint32(0), + "varuint32": eos.Varuint32(0), + } + if types[t] != nil { + return types[t] + } + return "" +} + +type abiField struct { + Name string `json:"name"` + Type string `json:"type"` +} + +type abiStruct struct { + Name string `json:"name"` + Base string `json:"base"` + Fields []abiField `json:"fields"` +} + +func (abi *Abi) DeriveJsonAbi() (abiJson []byte) { + a := abiStruct{ + Name: abi.Action, + Fields: make([]abiField, 0), + } + for _, field := range abi.Rows { + t := field.Type.Selected + if field.typeOverride != "" { + t = field.typeOverride + } + a.Fields = append(a.Fields, abiField{ + Name: *field.Name, + Type: t, + }, + ) + } + j, e := json.MarshalIndent(a, "", " ") + if e != nil { + errs.ErrChan <- "could not generate ABI json: " + e.Error() + return nil + } + return j +} + +var PrivilegedActions = map[string]bool{ + "eosio::addaction": true, + "eosio::addlocked": true, + "eosio::burnaction": true, + "eosio::canceldelay": true, + "eosio::crautoproxy": true, + "eosio::incram": true, + "eosio::inhibitunlck": true, + "eosio::init": true, + "eosio::setpriv": true, + "eosio::newaccount": true, + "eosio::onblock": true, + "eosio::onerror": true, + "eosio::resetclaim": true, + "eosio::remaction": true, + "eosio::rmvproducer": true, + "eosio::setabi": true, + "eosio::setautoproxy": true, + "eosio::setcode": true, + "eosio::setparams": true, + "eosio::unlocktokens": true, + "eosio::updatepower": true, + "eosio::updlbpclaim": true, + "eosio::updlocked": true, + "eosio::updtrevision": true, + "eosio.wrap::execute": true, + "fio.address::bind2eosio": true, + "fio.address::decrcounter": true, + "fio.token::create": true, + "fio.token::issue": true, + "fio.token::mintfio": true, + "fio.token::retire": true, + "fio.token::transfer": true, + "fio.tpid::rewardspaid": true, + "fio.tpid::updatebounty": true, + "fio.tpid::updatetpid": true, + "fio.treasury::startclock": true, + "fio.treasury::bppoolupdate": true, + "fio.treasury::bprewdupdate": true, + "fio.treasury::fdtnrwdupdat": true, +} + +var ProducerActions = map[string]bool{ + "fio.treasury::bpclaim": true, + "fio.address::burnexpired": true, + "fio.fee::bundlevote": true, + "fio.fee::bytemandfee": true, + "fio.fee::createfee": true, + "fio.fee::mandatoryfee": true, + "fio.fee::setfeemult": true, + "fio.fee::setfeevote": true, + "fio.fee::updatefees": true, + "eosio::regproducer": true, + "eosio::unregprod": true, +} diff --git a/api-form.go b/api-form.go new file mode 100644 index 0000000..62dff08 --- /dev/null +++ b/api-form.go @@ -0,0 +1,217 @@ +package cryptonym + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "fyne.io/fyne" + "fyne.io/fyne/layout" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + fioassets "github.com/blockpane/cryptonym/assets" + errs "github.com/blockpane/cryptonym/errLog" + "gopkg.in/alessio/shellescape.v1" + "io/ioutil" + "math" + "net/http" + "strings" + "time" +) + +func NewApiRequestTab(container chan fyne.Container) { + apiList := SupportedApis{Apis: []string{"/v1/chain/get_info"}} + err := apiList.Update(Uri, false) + if err != nil { + errs.ErrChan <- "Error updating list of available APIs: " + err.Error() + } + inputEntry := widget.NewMultiLineEntry() + outputEntry := widget.NewMultiLineEntry() + statusLabel := widget.NewLabel("") + submit := &widget.Button{} + inputTab := &widget.TabItem{} + outputTab := &widget.TabItem{} + apiTabs := &widget.TabContainer{} + + submit = widget.NewButtonWithIcon("Submit", fioassets.NewFioLogoResource(), func() { + submit.Disable() + statusLabel.SetText("") + outputEntry.SetText("") + outputEntry.OnChanged = func(string) {} + apiTabs.SelectTab(outputTab) + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second)) + done := make(chan bool, 1) + defer func() { + submit.Enable() + outputEntry.Refresh() + cancel() + }() + go func() { + defer func() { + done <- true + }() + resp, err := http.Post(Uri+apiEndPointActive, "application/json", bytes.NewReader([]byte(inputEntry.Text))) + if err != nil { + outputEntry.SetText(err.Error()) + errs.ErrChan <- err.Error() + return + } + statusLabel.SetText(fmt.Sprintf("POST %s -- %s", Uri+apiEndPointActive, resp.Status)) + if resp.Body != nil { + defer func() { + resp.Body.Close() + outputEntry.Refresh() + }() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + outputEntry.SetText(err.Error()) + errs.ErrChan <- err.Error() + return + } + if len(body) > 131072 { + outputEntry.SetText("Response body is too big to show") + errs.ErrChan <- "Response body is too big to show" + return + } + j, err := json.MarshalIndent(json.RawMessage(body), "", " ") + if err != nil { + outputEntry.SetText(err.Error()) + errs.ErrChan <- err.Error() + return + } + txt := string(j) + func(s string) { + outputEntry.OnChanged = func(string) { + outputEntry.SetText(s) + } + }(txt) // deref + outputEntry.SetText(txt) + } + }() + for { + select { + case <-ctx.Done(): + outputEntry.SetText("Request timed out.") + return + case <-done: + return + } + } + }) // NewButtonWithIcon + + apiRadio := &widget.Radio{} + epbFilter := widget.NewEntry() + epbFilter.SetPlaceHolder("Filter Endpoints") + + apiUpdate := func(ep string) { + apiEndPointActive = ep + submit.SetText(ep) + submit.Refresh() + inputEntry.SetText(DefaultJsonFor(ep)) + inputEntry.Refresh() + } + apiRadio = widget.NewRadio(apiList.Apis, apiUpdate) + + copyToClip := &widget.Button{} + clipped := func() { + go func() { + clip := Win.Clipboard() + curl := fmt.Sprintf(`curl -s -XPOST %s -d %s`, shellescape.Quote(Uri+apiEndPointActive), shellescape.Quote(inputEntry.Text)) + clip.SetContent(curl) + copyToClip.Text = "Copied!" + if curl != clip.Content() { + clip.SetContent("Failed to Copy!") + } + copyToClip.Refresh() + time.Sleep(2 * time.Second) + copyToClip.Text = "Copy as curl" + copyToClip.Refresh() + }() + } + copyToClip = widget.NewButtonWithIcon("Copy as curl", theme.ContentCopyIcon(), clipped) + + hideSigned := &widget.Check{} + hide := func(b bool) { + apiRadio.Options = nil + apiRadio.Refresh() + newList := make([]string, 0) + for i := range apiList.Apis { + switch true { + case hideSigned.Checked && isSigned(apiList.Apis[i]): + break + case epbFilter.Text == "": + newList = append(newList, apiList.Apis[i]) + case strings.Contains(apiList.Apis[i], epbFilter.Text): + newList = append(newList, apiList.Apis[i]) + } + } + apiRadio.Options = newList + apiRadio.Refresh() + } + hideSigned = widget.NewCheck("Hide Signed", hide) + + submit.SetText(apiEndPointActive) + submit.Refresh() + inputTab = widget.NewTabItem("Request", + fyne.NewContainerWithLayout(layout.NewHBoxLayout(), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.Size{Width: apiRadio.MinSize().Width + 10, Height: int(math.Round(float64(H) * .63))}), + widget.NewVBox( + widget.NewHBox(hideSigned, epbFilter), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.Size{Width: apiRadio.MinSize().Width + 10, Height: int(math.Round(float64(H) * .63))}), + widget.NewGroupWithScroller("Endpoint", apiRadio), + ), + ), + ), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.Size{Width: 30, Height: H / 3})), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.Size{Width: W - apiRadio.MinSize().Width - 360, Height: int(math.Round(float64(H) * .63))}), + widget.NewScrollContainer(widget.NewVBox( + widget.NewHBox( + layout.NewSpacer(), + submit, + copyToClip, + layout.NewSpacer(), + ), + widget.NewLabelWithStyle("Request JSON:", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), + inputEntry, + layout.NewSpacer(), + ))), + ), + ) + outputTab = widget.NewTabItem("Response", + widget.NewScrollContainer(widget.NewVBox( + widget.NewHBox(widget.NewLabel("Resend:"), submit, layout.NewSpacer(), statusLabel, layout.NewSpacer(), layout.NewSpacer()), + outputEntry, + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.Size{ + Width: 100, + Height: 100, + })), + )), + ) + epbFilter.OnChanged = func(string) { + hide(hideSigned.Checked) + } + hideSigned.SetChecked(true) + apiTabs = widget.NewTabContainer(inputTab, outputTab) + container <- *fyne.NewContainerWithLayout(layout.NewMaxLayout(), apiTabs) +} + +func isSigned(s string) bool { + switch { + case strings.Contains(s, "get_"): + return false + case strings.Contains(s, "_to_"): + return false + case strings.Contains(s, "_json"): + return false + case strings.Contains(s, "_check"): + return false + case strings.Contains(s, "db_size/"): + return false + case strings.Contains(s, "history/"): + return false + case strings.Contains(s, "connections"): + return false + } + + return true +} diff --git a/api-get.go b/api-get.go new file mode 100644 index 0000000..5a03057 --- /dev/null +++ b/api-get.go @@ -0,0 +1,364 @@ +package cryptonym + +import ( + "encoding/json" + "errors" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go" + "io/ioutil" + "strings" + "sync" +) + +type SupportedApis struct { + Apis []string `json:"apis"` +} + +func (apiList *SupportedApis) Update(url string, filter bool) error { + api, _, err := fio.NewConnection(nil, url) + if api.HttpClient == nil || err != nil { + errMsg := "attempted to retrieve api information, but not connected " + if err != nil { + errMsg = errMsg + err.Error() + } + errs.ErrChan <- "fetchApis: " + errMsg + return errors.New(errMsg) + } + resp, err := api.HttpClient.Post(api.BaseURL+"/v1/node/get_supported_apis", "application/json", nil) + if err != nil { + errs.ErrChan <- "fetchApis: " + err.Error() + return err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + errs.ErrChan <- "fetchApis: " + err.Error() + return err + } + supported := &SupportedApis{} + err = json.Unmarshal(body, supported) + if err != nil { + errs.ErrChan <- "fetchApis: " + err.Error() + return err + } + supported.Apis = append(supported.Apis, "/v1/node/get_supported_apis") + if filter { + newList := make([]string, 0) + for _, a := range supported.Apis { + if strings.Contains(a, "get") || strings.Contains(a, "abi") || + strings.Contains(a, "net") || strings.Contains(a, "json") || + strings.Contains(a, "check") { + continue + } + newList = append(newList, a) + } + apiList.Apis = newList + return nil + } + apiList.Apis = supported.Apis + return nil +} + +func DefaultJsonFor(endpoint string) string { + defaultApiJsonMux.RLock() + defer defaultApiJsonMux.RUnlock() + //return defaultApiJson[endpoint] + switch endpoint { + case "/v1/chain/get_transaction_id": + return `{ + "transaction": { + "actions": [ + { + "account": "fio.token", + "name": "trnsfiopubky", + "authorization": [ + { + "actor": "` + defaultActor() + `", + "permission": "active" + } + ], + "data": "00" + } + ] + } +}` + case "/v1/chain/get_table_by_scope": + return `{ + "code": "eosio.msig", + "table": "proposal", + "lower_bound": "111111111111", + "upper_bound": "zzzzzzzzzzzz", + "limit": 10 +}` + case "/v1/chain/get_required_keys": + return `{ + "transaction": { + "actions": [{ + "account": "fio.token", + "name": "trnsfiopubky", + "authorization": [{ + "actor": "` + defaultActor() + `", + "permission": "active" + } + ], + "data": "00" + } + ], + "transaction_extensions": [] + }, + "available_keys": [ + "` + defaultPub() + `" + ] +}` + case "/v1/history/get_actions": + return `{ + "account_name": "` + defaultActor() + `", + "pos": -1 +}` + case "/v1/history/get_block_txids": + return `{"block_num": 123}` + case "/v1/history/get_key_accounts": + return `{ + "public_key": "` + defaultPub() + `" +}` + case "/v1/history/get_controlled_accounts": + return `{ + "controlling_account": "` + defaultActor() + `" +}` + case "/v1/history/get_transaction": + return `{ + "id": "1111111111111111111111111111111111111111111111111111111111111111" +}` + case "/v1/chain/get_scheduled_transactions": + return `{"limit":1, "json": true}` + // TODO: update *_whitelist when API is defined. + case "/v1/chain/check_whitelist": + return `{"fio_address":"` + defaultAddress() + `"}` + case "/v1/chain/get_whitelist": + return `{"fio_address":"` + defaultAddress() + `"}` + + case "/v1/chain/avail_check": + return `{ + "fio_name": "` + defaultAddress() + `" +}` + case "/v1/chain/get_abi": + return `{ + "account_name": "fio.address" +}` + case "/v1/chain/get_account": + return `{ + "account_name": "` + defaultActor() + `" +}` + case "/v1/chain/get_block": + return `{ + "block_num_or_id": "123" +}` + case "/v1/chain/get_block_header_state": + return `{ + "block_num_or_id": "123" +}` + case "/v1/chain/get_currency_balance": + return `{ + "account": "` + defaultActor() + `", + "code": "fio.token", + "symbol": "FIO" +}` + case "/v1/chain/get_currency_stats": + return `{ + "json": false, + "code": "fio.token", + "symbol": "FIO" +}` + case "/v1/chain/get_fee": + return `{ + "end_point": "add_pub_address", + "fio_address": "` + defaultAddress() + `" +}` + case "/v1/chain/get_fio_balance": + return `{ + "fio_public_key": "` + defaultPub() + `" +}` + case "/v1/chain/get_actor": + return `{ + "fio_public_key": "` + defaultPub() + `" +}` + case "/v1/chain/get_fio_addresses": + return `{ + "fio_public_key": "` + defaultPub() + `" +}` + case "/v1/chain/get_fio_domains": + return `{ + "fio_public_key": "` + defaultPub() + `" +}` + case "/v1/chain/get_fio_names": + return `{ + "fio_public_key": "` + defaultPub() + `" +}` + case "/v1/chain/get_obt_data": + return `{ + "fio_public_key": "` + defaultPub() + `", + "limit": 100, + "offset": 0 +}` + case "/v1/chain/get_pending_fio_requests": + return `{ + "fio_public_key": "` + defaultPub() + `", + "limit": 100, + "offset": 0 +}` + case "/v1/chain/get_cancelled_fio_requests": + return `{ + "fio_public_key": "` + defaultPub() + `", + "limit": 100, + "offset": 0 +}` + case "/v1/chain/get_raw_abi": + return `{ + "account_name": "fio.token" +}` + case "/v1/chain/get_pub_address": + return `{ + "fio_address": "` + defaultAddress() + `", + "token_code": "FIO", + "chain_code": "FIO" +}` + case "/v1/chain/get_sent_fio_requests": + return `{ + "fio_public_key": "` + defaultPub() + `", + "limit": 100, + "offset": 0 +}` + case "/v1/chain/get_code": + return `{ + "account_name": "fio.address" +}` + case "/v1/chain/get_code_hash": + return `{ + "account_name": "fio.address" +}` + case "/v1/chain/get_raw_code_and_abi": + return `{ + "account_name": "fio.address" +}` + case "/v1/chain/get_table_rows": + return `{ + "json": true, + "code": "fio.address", + "scope": "fio.address", + "table": "domains", + "table_key": "", + "lower_bound": "` + defaultActor() + `", + "upper_bound": "` + defaultActor() + `", + "limit": 1, + "key_type": "name", + "index_position": "2", + "encode_type": "dec", + "reverse": false, + "show_payer": false +}` + case "/v1/chain/serialize_json": + return `{ + "action": "regdomain", + "json": { + "fio_domain": "domain", + "owner_fio_public_key": "` + defaultPub() + `", + "max_fee": 40000000000, + "tpid": "` + defaultAddress() + `", + "actor": "` + defaultActor() + `" + } +}` + case "/v1/net/status": + return `"localhost:9876"` + case "/v1/chain/add_pub_address": + return signedTx + case "/v1/chain/burn_expired": + return signedTx + case "/v1/chain/claim_bp_rewards": + return signedTx + case "/v1/chain/new_funds_request": + return signedTx + case "/v1/chain/pay_tpid_rewards": + return signedTx + case "/v1/chain/proxy_vote": + return signedTx + case "/v1/chain/push_block": + return signedTx + case "/v1/chain/push_transaction": + return signedTx + case "/v1/chain/record_obt_data": + return signedTx + case "/v1/chain/register_fio_address": + return signedTx + case "/v1/chain/register_fio_domain": + return signedTx + case "/v1/chain/register_producer": + return signedTx + case "/v1/chain/register_proxy": + return signedTx + case "/v1/chain/reject_funds_request": + return signedTx + case "/v1/chain/renew_fio_address": + return signedTx + case "/v1/chain/renew_fio_domain": + return signedTx + case "/v1/chain/send_transaction": + return signedTx + case "/v1/chain/set_fio_domain_public": + return signedTx + case "/v1/chain/submit_bundled_transaction": + return signedTx + case "/v1/chain/submit_fee_multiplier": + return signedTx + case "/v1/chain/submit_fee_ratios": + return signedTx + case "/v1/chain/transfer_tokens_pub_key": + return signedTx + case "/v1/chain/unregister_producer": + return signedTx + case "/v1/chain/unregister_proxy": + return signedTx + case "/v1/chain/vote_producer": + return signedTx + default: + return "" + } +} + +func defaultPub() string { + if Account == nil || Account.PubKey == "" { + return `FIO5DAPixgyZjSYM1yUBf9DZmQp7J7Y2pSwYruMapoaVMeW3BZ2U1` + } + return Account.PubKey +} + +func defaultAddress() string { + if Api == nil || Api.HttpClient == nil || Account == nil { + return `example@fiotestnet` + } + if Account.Addresses == nil || len(Account.Addresses) == 0 { + a, _, _ := Account.GetNames(Api) + if a == 0 { + return `example@fiotestnet` + } + } + return Account.Addresses[0].FioAddress +} + +func defaultActor() string { + if Account == nil || Account.Actor == "" { + return "eab5rg3u14oz" + } + return string(Account.Actor) +} + +var ( + signedTx = `{ + "signatures": [ + "SIG_K1_..." + ], + "compression": "none", + "packed_context_free_data": "", + "packed_trx": "000102030405060708090a0b0c0d0e0f..." +}` + defaultApiJsonMux = sync.RWMutex{} +) diff --git a/api-get_test.go b/api-get_test.go new file mode 100644 index 0000000..d68e6b5 --- /dev/null +++ b/api-get_test.go @@ -0,0 +1,16 @@ +package cryptonym + +//func TestFetchApis(t *testing.T) { +// list := SupportedApis{} +// api, _, err := fio.NewConnection(nil, "http://127.0.0.1:8888") +// if err != nil { +// t.Error(err) +// } +// err = list.Update(api) +// if err != nil { +// t.Error(err) +// } +// if len(list.Apis) == 0 { +// t.Error("supported apis should not be empty") +// } +//} diff --git a/assets/fio-png.go b/assets/fio-png.go new file mode 100644 index 0000000..ba4ab56 --- /dev/null +++ b/assets/fio-png.go @@ -0,0 +1,2489 @@ +package fioassets + +import ( + "bytes" + "fyne.io/fyne" + "image" +) + +func NewFioLogo() (image.Image, string, error) { + return image.Decode(bytes.NewBuffer(Logo)) +} + +func NewFioLogoResource() *fyne.StaticResource { + return fyne.NewStaticResource("fio", Logo) +} + +var Logo = []byte{ + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x03, 0xe8, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x4d, 0xa3, 0xd4, 0xe4, 0x00, 0x00, 0x0a, + 0x37, 0x69, 0x43, 0x43, 0x50, 0x73, 0x52, 0x47, 0x42, 0x20, 0x49, 0x45, + 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2d, 0x32, 0x2e, 0x31, 0x00, 0x00, + 0x78, 0x9c, 0x9d, 0x96, 0x77, 0x54, 0x53, 0xd9, 0x16, 0x87, 0xcf, 0xbd, + 0x37, 0xbd, 0x50, 0x92, 0x10, 0x8a, 0x94, 0xd0, 0x6b, 0x68, 0x52, 0x02, + 0x48, 0x0d, 0xbd, 0x48, 0x91, 0x2e, 0x2a, 0x31, 0x09, 0x10, 0x4a, 0xc0, + 0x90, 0x00, 0x22, 0x36, 0x44, 0x54, 0x70, 0x44, 0x51, 0x91, 0xa6, 0x08, + 0x32, 0x28, 0xe0, 0x80, 0xa3, 0x43, 0x91, 0xb1, 0x22, 0x8a, 0x85, 0x01, + 0x51, 0xb1, 0xeb, 0x04, 0x19, 0x44, 0xd4, 0x71, 0x70, 0x14, 0x1b, 0x96, + 0x49, 0x64, 0xad, 0x19, 0xdf, 0xbc, 0x79, 0xef, 0xcd, 0x9b, 0xdf, 0x1f, + 0xf7, 0x7e, 0x6b, 0x9f, 0xbd, 0xcf, 0xdd, 0x67, 0xef, 0x7d, 0xd6, 0xba, + 0x00, 0x90, 0xfc, 0x83, 0x05, 0xc2, 0x4c, 0x58, 0x09, 0x80, 0x0c, 0xa1, + 0x58, 0x14, 0xe1, 0xe7, 0xc5, 0x88, 0x8d, 0x8b, 0x67, 0x60, 0x07, 0x01, + 0x0c, 0xf0, 0x00, 0x03, 0x6c, 0x00, 0xe0, 0x70, 0xb3, 0xb3, 0x42, 0x16, + 0xf8, 0x46, 0x02, 0x99, 0x02, 0x7c, 0xd8, 0x8c, 0x6c, 0x99, 0x13, 0xf8, + 0x17, 0xbd, 0xba, 0x0e, 0x20, 0xf9, 0xfb, 0x2a, 0xd3, 0x3f, 0x8c, 0xc1, + 0x00, 0xff, 0x9f, 0x94, 0xb9, 0x59, 0x22, 0x31, 0x00, 0x50, 0x98, 0x8c, + 0xe7, 0xf2, 0xf8, 0xd9, 0x5c, 0x19, 0x17, 0xc9, 0x38, 0x3d, 0x57, 0x9c, + 0x25, 0xb7, 0x4f, 0xc9, 0x98, 0xb6, 0x34, 0x4d, 0xce, 0x30, 0x4a, 0xce, + 0x22, 0x59, 0x82, 0x32, 0x56, 0x93, 0x73, 0xf2, 0x2c, 0x5b, 0x7c, 0xf6, + 0x99, 0x65, 0x0f, 0x39, 0xf3, 0x32, 0x84, 0x3c, 0x19, 0xcb, 0x73, 0xce, + 0xe2, 0x65, 0xf0, 0xe4, 0xdc, 0x27, 0xe3, 0x8d, 0x39, 0x12, 0xbe, 0x8c, + 0x91, 0x60, 0x19, 0x17, 0xe7, 0x08, 0xf8, 0xb9, 0x32, 0xbe, 0x26, 0x63, + 0x83, 0x74, 0x49, 0x86, 0x40, 0xc6, 0x6f, 0xe4, 0xb1, 0x19, 0x7c, 0x4e, + 0x36, 0x00, 0x28, 0x92, 0xdc, 0x2e, 0xe6, 0x73, 0x53, 0x64, 0x6c, 0x2d, + 0x63, 0x92, 0x28, 0x32, 0x82, 0x2d, 0xe3, 0x79, 0x00, 0xe0, 0x48, 0xc9, + 0x5f, 0xf0, 0xd2, 0x2f, 0x58, 0xcc, 0xcf, 0x13, 0xcb, 0x0f, 0xc5, 0xce, + 0xcc, 0x5a, 0x2e, 0x12, 0x24, 0xa7, 0x88, 0x19, 0x26, 0x5c, 0x53, 0x86, + 0x8d, 0x93, 0x13, 0x8b, 0xe1, 0xcf, 0xcf, 0x4d, 0xe7, 0x8b, 0xc5, 0xcc, + 0x30, 0x0e, 0x37, 0x8d, 0x23, 0xe2, 0x31, 0xd8, 0x99, 0x19, 0x59, 0x1c, + 0xe1, 0x72, 0x00, 0x66, 0xcf, 0xfc, 0x59, 0x14, 0x79, 0x6d, 0x19, 0xb2, + 0x22, 0x3b, 0xd8, 0x38, 0x39, 0x38, 0x30, 0x6d, 0x2d, 0x6d, 0xbe, 0x28, + 0xd4, 0x7f, 0x5d, 0xfc, 0x9b, 0x92, 0xf7, 0x76, 0x96, 0x5e, 0x84, 0x7f, + 0xee, 0x19, 0x44, 0x1f, 0xf8, 0xc3, 0xf6, 0x57, 0x7e, 0x99, 0x0d, 0x00, + 0xb0, 0xa6, 0x65, 0xb5, 0xd9, 0xfa, 0x87, 0x6d, 0x69, 0x15, 0x00, 0x5d, + 0xeb, 0x01, 0x50, 0xbb, 0xfd, 0x87, 0xcd, 0x60, 0x2f, 0x00, 0x8a, 0xb2, + 0xbe, 0x75, 0x0e, 0x7d, 0x71, 0x1e, 0xba, 0x7c, 0x5e, 0x52, 0xc4, 0xe2, + 0x2c, 0x67, 0x2b, 0xab, 0xdc, 0xdc, 0x5c, 0x4b, 0x01, 0x9f, 0x6b, 0x29, + 0x2f, 0xe8, 0xef, 0xfa, 0x9f, 0x0e, 0x7f, 0x43, 0x5f, 0x7c, 0xcf, 0x52, + 0xbe, 0xdd, 0xef, 0xe5, 0x61, 0x78, 0xf3, 0x93, 0x38, 0x92, 0x74, 0x31, + 0x43, 0x5e, 0x37, 0x6e, 0x66, 0x7a, 0xa6, 0x44, 0xc4, 0xc8, 0xce, 0xe2, + 0x70, 0xf9, 0x0c, 0xe6, 0x9f, 0x87, 0xf8, 0x1f, 0x07, 0xfe, 0x75, 0x1e, + 0x16, 0x11, 0xfc, 0x24, 0xbe, 0x88, 0x2f, 0x94, 0x45, 0x44, 0xcb, 0xa6, + 0x4c, 0x20, 0x4c, 0x96, 0xb5, 0x5b, 0xc8, 0x13, 0x88, 0x05, 0x99, 0x42, + 0x86, 0x40, 0xf8, 0x9f, 0x9a, 0xf8, 0x0f, 0xc3, 0xfe, 0xa4, 0xd9, 0xb9, + 0x96, 0x89, 0xda, 0xf8, 0x11, 0xd0, 0x96, 0x58, 0x02, 0xa5, 0x21, 0x1a, + 0x40, 0x7e, 0x1e, 0x00, 0x28, 0x2a, 0x11, 0x20, 0x09, 0x7b, 0x64, 0x2b, + 0xd0, 0xef, 0x7d, 0x0b, 0xc6, 0x47, 0x03, 0xf9, 0xcd, 0x8b, 0xd1, 0x99, + 0x98, 0x9d, 0xfb, 0xcf, 0x82, 0xfe, 0x7d, 0x57, 0xb8, 0x4c, 0xfe, 0xc8, + 0x16, 0x24, 0x7f, 0x8e, 0x63, 0x47, 0x44, 0x32, 0xb8, 0x12, 0x51, 0xce, + 0xec, 0x9a, 0xfc, 0x5a, 0x02, 0x34, 0x20, 0x00, 0x45, 0x40, 0x03, 0xea, + 0x40, 0x1b, 0xe8, 0x03, 0x13, 0xc0, 0x04, 0xb6, 0xc0, 0x11, 0xb8, 0x00, + 0x0f, 0xe0, 0x03, 0x02, 0x41, 0x28, 0x88, 0x04, 0x71, 0x60, 0x31, 0xe0, + 0x82, 0x14, 0x90, 0x01, 0x44, 0x20, 0x17, 0x14, 0x80, 0xb5, 0xa0, 0x18, + 0x94, 0x82, 0xad, 0x60, 0x27, 0xa8, 0x06, 0x75, 0xa0, 0x11, 0x34, 0x83, + 0x36, 0x70, 0x18, 0x74, 0x81, 0x63, 0xe0, 0x34, 0x38, 0x07, 0x2e, 0x81, + 0xcb, 0x60, 0x04, 0xdc, 0x01, 0x52, 0x30, 0x0e, 0x9e, 0x80, 0x29, 0xf0, + 0x0a, 0xcc, 0x40, 0x10, 0x84, 0x85, 0xc8, 0x10, 0x15, 0x52, 0x87, 0x74, + 0x20, 0x43, 0xc8, 0x1c, 0xb2, 0x85, 0x58, 0x90, 0x1b, 0xe4, 0x03, 0x05, + 0x43, 0x11, 0x50, 0x1c, 0x94, 0x08, 0x25, 0x43, 0x42, 0x48, 0x02, 0x15, + 0x40, 0xeb, 0xa0, 0x52, 0xa8, 0x1c, 0xaa, 0x86, 0xea, 0xa1, 0x66, 0xe8, + 0x5b, 0xe8, 0x28, 0x74, 0x1a, 0xba, 0x00, 0x0d, 0x43, 0xb7, 0xa0, 0x51, + 0x68, 0x12, 0xfa, 0x15, 0x7a, 0x07, 0x23, 0x30, 0x09, 0xa6, 0xc1, 0x5a, + 0xb0, 0x11, 0x6c, 0x05, 0xb3, 0x60, 0x4f, 0x38, 0x08, 0x8e, 0x84, 0x17, + 0xc1, 0xc9, 0xf0, 0x32, 0x38, 0x1f, 0x2e, 0x82, 0xb7, 0xc0, 0x95, 0x70, + 0x03, 0x7c, 0x10, 0xee, 0x84, 0x4f, 0xc3, 0x97, 0xe0, 0x11, 0x58, 0x0a, + 0x3f, 0x81, 0xa7, 0x11, 0x80, 0x10, 0x11, 0x3a, 0xa2, 0x8b, 0x30, 0x11, + 0x16, 0xc2, 0x46, 0x42, 0x91, 0x78, 0x24, 0x09, 0x11, 0x21, 0xab, 0x90, + 0x12, 0xa4, 0x02, 0x69, 0x40, 0xda, 0x90, 0x1e, 0xa4, 0x1f, 0xb9, 0x8a, + 0x48, 0x91, 0xa7, 0xc8, 0x5b, 0x14, 0x06, 0x45, 0x45, 0x31, 0x50, 0x4c, + 0x94, 0x0b, 0xca, 0x1f, 0x15, 0x85, 0xe2, 0xa2, 0x96, 0xa1, 0x56, 0xa1, + 0x36, 0xa3, 0xaa, 0x51, 0x07, 0x50, 0x9d, 0xa8, 0x3e, 0xd4, 0x55, 0xd4, + 0x28, 0x6a, 0x0a, 0xf5, 0x11, 0x4d, 0x46, 0x6b, 0xa2, 0xcd, 0xd1, 0xce, + 0xe8, 0x00, 0x74, 0x2c, 0x3a, 0x19, 0x9d, 0x8b, 0x2e, 0x46, 0x57, 0xa0, + 0x9b, 0xd0, 0x1d, 0xe8, 0xb3, 0xe8, 0x11, 0xf4, 0x38, 0xfa, 0x15, 0x06, + 0x83, 0xa1, 0x63, 0x8c, 0x31, 0x8e, 0x18, 0x7f, 0x4c, 0x1c, 0x26, 0x15, + 0xb3, 0x02, 0xb3, 0x19, 0xb3, 0x1b, 0xd3, 0x8e, 0x39, 0x85, 0x19, 0xc6, + 0x8c, 0x61, 0xa6, 0xb1, 0x58, 0xac, 0x3a, 0xd6, 0x1c, 0xeb, 0x8a, 0x0d, + 0xc5, 0x72, 0xb0, 0x62, 0x6c, 0x31, 0xb6, 0x0a, 0x7b, 0x10, 0x7b, 0x12, + 0x7b, 0x05, 0x3b, 0x8e, 0x7d, 0x83, 0x23, 0xe2, 0x74, 0x70, 0xb6, 0x38, + 0x5f, 0x5c, 0x3c, 0x4e, 0x88, 0x2b, 0xc4, 0x55, 0xe0, 0x5a, 0x70, 0x27, + 0x70, 0x57, 0x70, 0x13, 0xb8, 0x19, 0xbc, 0x12, 0xde, 0x10, 0xef, 0x8c, + 0x0f, 0xc5, 0xf3, 0xf0, 0xcb, 0xf1, 0x65, 0xf8, 0x46, 0x7c, 0x0f, 0x7e, + 0x08, 0x3f, 0x8e, 0x9f, 0x21, 0x28, 0x13, 0x8c, 0x09, 0xae, 0x84, 0x48, + 0x42, 0x2a, 0x61, 0x2d, 0xa1, 0x92, 0xd0, 0x46, 0x38, 0x4b, 0xb8, 0x4b, + 0x78, 0x41, 0x24, 0x12, 0xf5, 0x88, 0x4e, 0xc4, 0x70, 0xa2, 0x80, 0xb8, + 0x86, 0x58, 0x49, 0x3c, 0x44, 0x3c, 0x4f, 0x1c, 0x25, 0xbe, 0x25, 0x51, + 0x48, 0x66, 0x24, 0x36, 0x29, 0x81, 0x24, 0x21, 0x6d, 0x21, 0xed, 0x27, + 0x9d, 0x22, 0xdd, 0x22, 0xbd, 0x20, 0x93, 0xc9, 0x46, 0x64, 0x0f, 0x72, + 0x3c, 0x59, 0x4c, 0xde, 0x42, 0x6e, 0x26, 0x9f, 0x21, 0xdf, 0x27, 0xbf, + 0x51, 0xa0, 0x2a, 0x58, 0x2a, 0x04, 0x28, 0xf0, 0x14, 0x56, 0x2b, 0xd4, + 0x28, 0x74, 0x2a, 0x5c, 0x51, 0x78, 0xa6, 0x88, 0x57, 0x34, 0x54, 0xf4, + 0x54, 0x5c, 0xac, 0x98, 0xaf, 0x58, 0xa1, 0x78, 0x44, 0x71, 0x48, 0xf1, + 0xa9, 0x12, 0x5e, 0xc9, 0x48, 0x89, 0xad, 0xc4, 0x51, 0x5a, 0xa5, 0x54, + 0xa3, 0x74, 0x54, 0xe9, 0x86, 0xd2, 0xb4, 0x32, 0x55, 0xd9, 0x46, 0x39, + 0x54, 0x39, 0x43, 0x79, 0xb3, 0x72, 0x8b, 0xf2, 0x05, 0xe5, 0x47, 0x14, + 0x2c, 0xc5, 0x88, 0xe2, 0x43, 0xe1, 0x51, 0x8a, 0x28, 0xfb, 0x28, 0x67, + 0x28, 0x63, 0x54, 0x84, 0xaa, 0x4f, 0x65, 0x53, 0xb9, 0xd4, 0x75, 0xd4, + 0x46, 0xea, 0x59, 0xea, 0x38, 0x0d, 0x43, 0x33, 0xa6, 0x05, 0xd0, 0x52, + 0x69, 0xa5, 0xb4, 0x6f, 0x68, 0x83, 0xb4, 0x29, 0x15, 0x8a, 0x8a, 0x9d, + 0x4a, 0xb4, 0x4a, 0x9e, 0x4a, 0x8d, 0xca, 0x71, 0x15, 0x29, 0x1d, 0xa1, + 0x1b, 0xd1, 0x03, 0xe8, 0xe9, 0xf4, 0x32, 0xfa, 0x61, 0xfa, 0x75, 0xfa, + 0x3b, 0x55, 0x2d, 0x55, 0x4f, 0x55, 0xbe, 0xea, 0x26, 0xd5, 0x36, 0xd5, + 0x2b, 0xaa, 0xaf, 0xd5, 0xe6, 0xa8, 0x79, 0xa8, 0xf1, 0xd5, 0x4a, 0xd4, + 0xda, 0xd5, 0x46, 0xd4, 0xde, 0xa9, 0x33, 0xd4, 0x7d, 0xd4, 0xd3, 0xd4, + 0xb7, 0xa9, 0x77, 0xa9, 0xdf, 0xd3, 0x40, 0x69, 0x98, 0x69, 0x84, 0x6b, + 0xe4, 0x6a, 0xec, 0xd1, 0x38, 0xab, 0xf1, 0x74, 0x0e, 0x6d, 0x8e, 0xcb, + 0x1c, 0xee, 0x9c, 0x92, 0x39, 0x87, 0xe7, 0xdc, 0xd6, 0x84, 0x35, 0xcd, + 0x34, 0x23, 0x34, 0x57, 0x68, 0xee, 0xd3, 0x1c, 0xd0, 0x9c, 0xd6, 0xd2, + 0xd6, 0xf2, 0xd3, 0xca, 0xd2, 0xaa, 0xd2, 0x3a, 0xa3, 0xf5, 0x54, 0x9b, + 0xae, 0xed, 0xa1, 0x9d, 0xaa, 0xbd, 0x43, 0xfb, 0x84, 0xf6, 0xa4, 0x0e, + 0x55, 0xc7, 0x4d, 0x47, 0xa0, 0xb3, 0x43, 0xe7, 0xa4, 0xce, 0x63, 0x86, + 0x0a, 0xc3, 0x93, 0x91, 0xce, 0xa8, 0x64, 0xf4, 0x31, 0xa6, 0x74, 0x35, + 0x75, 0xfd, 0x75, 0x25, 0xba, 0xf5, 0xba, 0x83, 0xba, 0x33, 0x7a, 0xc6, + 0x7a, 0x51, 0x7a, 0x85, 0x7a, 0xed, 0x7a, 0xf7, 0xf4, 0x09, 0xfa, 0x2c, + 0xfd, 0x24, 0xfd, 0x1d, 0xfa, 0xbd, 0xfa, 0x53, 0x06, 0x3a, 0x06, 0x21, + 0x06, 0x05, 0x06, 0xad, 0x06, 0xb7, 0x0d, 0xf1, 0x86, 0x2c, 0xc3, 0x14, + 0xc3, 0x5d, 0x86, 0xfd, 0x86, 0xaf, 0x8d, 0x8c, 0x8d, 0x62, 0x8c, 0x36, + 0x18, 0x75, 0x19, 0x3d, 0x32, 0x56, 0x33, 0x0e, 0x30, 0xce, 0x37, 0x6e, + 0x35, 0xbe, 0x6b, 0x42, 0x36, 0x71, 0x37, 0x59, 0x66, 0xd2, 0x60, 0x72, + 0xcd, 0x14, 0x63, 0xca, 0x32, 0x4d, 0x33, 0xdd, 0x6d, 0x7a, 0xd9, 0x0c, + 0x36, 0xb3, 0x37, 0x4b, 0x31, 0xab, 0x31, 0x1b, 0x32, 0x87, 0xcd, 0x1d, + 0xcc, 0x05, 0xe6, 0xbb, 0xcd, 0x87, 0x2d, 0xd0, 0x16, 0x4e, 0x16, 0x42, + 0x8b, 0x06, 0x8b, 0x1b, 0x4c, 0x12, 0xd3, 0x93, 0x99, 0xc3, 0x6c, 0x65, + 0x8e, 0x5a, 0xd2, 0x2d, 0x83, 0x2d, 0x0b, 0x2d, 0xbb, 0x2c, 0x9f, 0x59, + 0x19, 0x58, 0xc5, 0x5b, 0x6d, 0xb3, 0xea, 0xb7, 0xfa, 0x68, 0x6d, 0x6f, + 0x9d, 0x6e, 0xdd, 0x68, 0x7d, 0xc7, 0x86, 0x62, 0x13, 0x68, 0x53, 0x68, + 0xd3, 0x63, 0xf3, 0xab, 0xad, 0x99, 0x2d, 0xd7, 0xb6, 0xc6, 0xf6, 0xda, + 0x5c, 0xf2, 0x5c, 0xdf, 0xb9, 0xab, 0xe7, 0x76, 0xcf, 0x7d, 0x6e, 0x67, + 0x6e, 0xc7, 0xb7, 0xdb, 0x63, 0x77, 0xd3, 0x9e, 0x6a, 0x1f, 0x62, 0xbf, + 0xc1, 0xbe, 0xd7, 0xfe, 0x83, 0x83, 0xa3, 0x83, 0xc8, 0xa1, 0xcd, 0x61, + 0xd2, 0xd1, 0xc0, 0x31, 0xd1, 0xb1, 0xd6, 0xf1, 0x06, 0x8b, 0xc6, 0x0a, + 0x63, 0x6d, 0x66, 0x9d, 0x77, 0x42, 0x3b, 0x79, 0x39, 0xad, 0x76, 0x3a, + 0xe6, 0xf4, 0xd6, 0xd9, 0xc1, 0x59, 0xec, 0x7c, 0xd8, 0xf9, 0x17, 0x17, + 0xa6, 0x4b, 0x9a, 0x4b, 0x8b, 0xcb, 0xa3, 0x79, 0xc6, 0xf3, 0xf8, 0xf3, + 0x1a, 0xe7, 0x8d, 0xb9, 0xea, 0xb9, 0x72, 0x5c, 0xeb, 0x5d, 0xa5, 0x6e, + 0x0c, 0xb7, 0x44, 0xb7, 0xbd, 0x6e, 0x52, 0x77, 0x5d, 0x77, 0x8e, 0x7b, + 0x83, 0xfb, 0x03, 0x0f, 0x7d, 0x0f, 0x9e, 0x47, 0x93, 0xc7, 0x84, 0xa7, + 0xa9, 0x67, 0xaa, 0xe7, 0x41, 0xcf, 0x67, 0x5e, 0xd6, 0x5e, 0x22, 0xaf, + 0x0e, 0xaf, 0xd7, 0x6c, 0x67, 0xf6, 0x4a, 0xf6, 0x29, 0x6f, 0xc4, 0xdb, + 0xcf, 0xbb, 0xc4, 0x7b, 0xd0, 0x87, 0xe2, 0x13, 0xe5, 0x53, 0xed, 0x73, + 0xdf, 0x57, 0xcf, 0x37, 0xd9, 0xb7, 0xd5, 0x77, 0xca, 0xcf, 0xde, 0x6f, + 0x85, 0xdf, 0x29, 0x7f, 0xb4, 0x7f, 0x90, 0xff, 0x36, 0xff, 0x1b, 0x01, + 0x5a, 0x01, 0xdc, 0x80, 0xe6, 0x80, 0xa9, 0x40, 0xc7, 0xc0, 0x95, 0x81, + 0x7d, 0x41, 0xa4, 0xa0, 0x05, 0x41, 0xd5, 0x41, 0x0f, 0x82, 0xcd, 0x82, + 0x45, 0xc1, 0x3d, 0x21, 0x70, 0x48, 0x60, 0xc8, 0xf6, 0x90, 0xbb, 0xf3, + 0x0d, 0xe7, 0x0b, 0xe7, 0x77, 0x85, 0x82, 0xd0, 0x80, 0xd0, 0xed, 0xa1, + 0xf7, 0xc2, 0x8c, 0xc3, 0x96, 0x85, 0x7d, 0x1f, 0x8e, 0x09, 0x0f, 0x0b, + 0xaf, 0x09, 0x7f, 0x18, 0x61, 0x13, 0x51, 0x10, 0xd1, 0xbf, 0x80, 0xba, + 0x60, 0xc9, 0x82, 0x96, 0x05, 0xaf, 0x22, 0xbd, 0x22, 0xcb, 0x22, 0xef, + 0x44, 0x99, 0x44, 0x49, 0xa2, 0x7a, 0xa3, 0x15, 0xa3, 0x13, 0xa2, 0x9b, + 0xa3, 0x5f, 0xc7, 0x78, 0xc7, 0x94, 0xc7, 0x48, 0x63, 0xad, 0x62, 0x57, + 0xc6, 0x5e, 0x8a, 0xd3, 0x88, 0x13, 0xc4, 0x75, 0xc7, 0x63, 0xe3, 0xa3, + 0xe3, 0x9b, 0xe2, 0xa7, 0x17, 0xfa, 0x2c, 0xdc, 0xb9, 0x70, 0x3c, 0xc1, + 0x3e, 0xa1, 0x38, 0xe1, 0xfa, 0x22, 0xe3, 0x45, 0x79, 0x8b, 0x2e, 0x2c, + 0xd6, 0x58, 0x9c, 0xbe, 0xf8, 0xf8, 0x12, 0xc5, 0x25, 0x9c, 0x25, 0x47, + 0x12, 0xd1, 0x89, 0x31, 0x89, 0x2d, 0x89, 0xef, 0x39, 0xa1, 0x9c, 0x06, + 0xce, 0xf4, 0xd2, 0x80, 0xa5, 0xb5, 0x4b, 0xa7, 0xb8, 0x6c, 0xee, 0x2e, + 0xee, 0x13, 0x9e, 0x07, 0x6f, 0x07, 0x6f, 0x92, 0xef, 0xca, 0x2f, 0xe7, + 0x4f, 0x24, 0xb9, 0x26, 0x95, 0x27, 0x3d, 0x4a, 0x76, 0x4d, 0xde, 0x9e, + 0x3c, 0x99, 0xe2, 0x9e, 0x52, 0x91, 0xf2, 0x54, 0xc0, 0x16, 0x54, 0x0b, + 0x9e, 0xa7, 0xfa, 0xa7, 0xd6, 0xa5, 0xbe, 0x4e, 0x0b, 0x4d, 0xdb, 0x9f, + 0xf6, 0x29, 0x3d, 0x26, 0xbd, 0x3d, 0x03, 0x97, 0x91, 0x98, 0x71, 0x54, + 0x48, 0x11, 0xa6, 0x09, 0xfb, 0x32, 0xb5, 0x33, 0xf3, 0x32, 0x87, 0xb3, + 0xcc, 0xb3, 0x8a, 0xb3, 0xa4, 0xcb, 0x9c, 0x97, 0xed, 0x5c, 0x36, 0x25, + 0x0a, 0x12, 0x35, 0x65, 0x43, 0xd9, 0x8b, 0xb2, 0xbb, 0xc5, 0x34, 0xd9, + 0xcf, 0xd4, 0x80, 0xc4, 0x44, 0xb2, 0x5e, 0x32, 0x9a, 0xe3, 0x96, 0x53, + 0x93, 0xf3, 0x26, 0x37, 0x3a, 0xf7, 0x48, 0x9e, 0x72, 0x9e, 0x30, 0x6f, + 0x60, 0xb9, 0xd9, 0xf2, 0x4d, 0xcb, 0x27, 0xf2, 0x7d, 0xf3, 0xbf, 0x5e, + 0x81, 0x5a, 0xc1, 0x5d, 0xd1, 0x5b, 0xa0, 0x5b, 0xb0, 0xb6, 0x60, 0x74, + 0xa5, 0xe7, 0xca, 0xfa, 0x55, 0xd0, 0xaa, 0xa5, 0xab, 0x7a, 0x57, 0xeb, + 0xaf, 0x2e, 0x5a, 0x3d, 0xbe, 0xc6, 0x6f, 0xcd, 0x81, 0xb5, 0x84, 0xb5, + 0x69, 0x6b, 0x7f, 0x28, 0xb4, 0x2e, 0x2c, 0x2f, 0x7c, 0xb9, 0x2e, 0x66, + 0x5d, 0x4f, 0x91, 0x56, 0xd1, 0x9a, 0xa2, 0xb1, 0xf5, 0x7e, 0xeb, 0x5b, + 0x8b, 0x15, 0x8a, 0x45, 0xc5, 0x37, 0x36, 0xb8, 0x6c, 0xa8, 0xdb, 0x88, + 0xda, 0x28, 0xd8, 0x38, 0xb8, 0x69, 0xee, 0xa6, 0xaa, 0x4d, 0x1f, 0x4b, + 0x78, 0x25, 0x17, 0x4b, 0xad, 0x4b, 0x2b, 0x4a, 0xdf, 0x6f, 0xe6, 0x6e, + 0xbe, 0xf8, 0x95, 0xcd, 0x57, 0x95, 0x5f, 0x7d, 0xda, 0x92, 0xb4, 0x65, + 0xb0, 0xcc, 0xa1, 0x6c, 0xcf, 0x56, 0xcc, 0x56, 0xe1, 0xd6, 0xeb, 0xdb, + 0xdc, 0xb7, 0x1d, 0x28, 0x57, 0x2e, 0xcf, 0x2f, 0x1f, 0xdb, 0x1e, 0xb2, + 0xbd, 0x73, 0x07, 0x63, 0x47, 0xc9, 0x8e, 0x97, 0x3b, 0x97, 0xec, 0xbc, + 0x50, 0x61, 0x57, 0x51, 0xb7, 0x8b, 0xb0, 0x4b, 0xb2, 0x4b, 0x5a, 0x19, + 0x5c, 0xd9, 0x5d, 0x65, 0x50, 0xb5, 0xb5, 0xea, 0x7d, 0x75, 0x4a, 0xf5, + 0x48, 0x8d, 0x57, 0x4d, 0x7b, 0xad, 0x66, 0xed, 0xa6, 0xda, 0xd7, 0xbb, + 0x79, 0xbb, 0xaf, 0xec, 0xf1, 0xd8, 0xd3, 0x56, 0xa7, 0x55, 0x57, 0x5a, + 0xf7, 0x6e, 0xaf, 0x60, 0xef, 0xcd, 0x7a, 0xbf, 0xfa, 0xce, 0x06, 0xa3, + 0x86, 0x8a, 0x7d, 0x98, 0x7d, 0x39, 0xfb, 0x1e, 0x36, 0x46, 0x37, 0xf6, + 0x7f, 0xcd, 0xfa, 0xba, 0xb9, 0x49, 0xa3, 0xa9, 0xb4, 0xe9, 0xc3, 0x7e, + 0xe1, 0x7e, 0xe9, 0x81, 0x88, 0x03, 0x7d, 0xcd, 0x8e, 0xcd, 0xcd, 0x2d, + 0x9a, 0x2d, 0x65, 0xad, 0x70, 0xab, 0xa4, 0x75, 0xf2, 0x60, 0xc2, 0xc1, + 0xcb, 0xdf, 0x78, 0x7f, 0xd3, 0xdd, 0xc6, 0x6c, 0xab, 0x6f, 0xa7, 0xb7, + 0x97, 0x1e, 0x02, 0x87, 0x24, 0x87, 0x1e, 0x7f, 0x9b, 0xf8, 0xed, 0xf5, + 0xc3, 0x41, 0x87, 0x7b, 0x8f, 0xb0, 0x8e, 0xb4, 0x7d, 0x67, 0xf8, 0x5d, + 0x6d, 0x07, 0xb5, 0xa3, 0xa4, 0x13, 0xea, 0x5c, 0xde, 0x39, 0xd5, 0x95, + 0xd2, 0x25, 0xed, 0x8e, 0xeb, 0x1e, 0x3e, 0x1a, 0x78, 0xb4, 0xb7, 0xc7, + 0xa5, 0xa7, 0xe3, 0x7b, 0xcb, 0xef, 0xf7, 0x1f, 0xd3, 0x3d, 0x56, 0x73, + 0x5c, 0xe5, 0x78, 0xd9, 0x09, 0xc2, 0x89, 0xa2, 0x13, 0x9f, 0x4e, 0xe6, + 0x9f, 0x9c, 0x3e, 0x95, 0x75, 0xea, 0xe9, 0xe9, 0xe4, 0xd3, 0x63, 0xbd, + 0x4b, 0x7a, 0xef, 0x9c, 0x89, 0x3d, 0x73, 0xad, 0x2f, 0xbc, 0x6f, 0xf0, + 0x6c, 0xd0, 0xd9, 0xf3, 0xe7, 0x7c, 0xcf, 0x9d, 0xe9, 0xf7, 0xec, 0x3f, + 0x79, 0xde, 0xf5, 0xfc, 0xb1, 0x0b, 0xce, 0x17, 0x8e, 0x5e, 0x64, 0x5d, + 0xec, 0xba, 0xe4, 0x70, 0xa9, 0x73, 0xc0, 0x7e, 0xa0, 0xe3, 0x07, 0xfb, + 0x1f, 0x3a, 0x06, 0x1d, 0x06, 0x3b, 0x87, 0x1c, 0x87, 0xba, 0x2f, 0x3b, + 0x5d, 0xee, 0x19, 0x9e, 0x37, 0x7c, 0xe2, 0x8a, 0xfb, 0x95, 0xd3, 0x57, + 0xbd, 0xaf, 0x9e, 0xbb, 0x16, 0x70, 0xed, 0xd2, 0xc8, 0xfc, 0x91, 0xe1, + 0xeb, 0x51, 0xd7, 0x6f, 0xde, 0x48, 0xb8, 0x21, 0xbd, 0xc9, 0xbb, 0xf9, + 0xe8, 0x56, 0xfa, 0xad, 0xe7, 0xb7, 0x73, 0x6e, 0xcf, 0xdc, 0x59, 0x73, + 0x17, 0x7d, 0xb7, 0xe4, 0x9e, 0xd2, 0xbd, 0x8a, 0xfb, 0x9a, 0xf7, 0x1b, + 0x7e, 0x34, 0xfd, 0xb1, 0x5d, 0xea, 0x20, 0x3d, 0x3e, 0xea, 0x3d, 0x3a, + 0xf0, 0x60, 0xc1, 0x83, 0x3b, 0x63, 0xdc, 0xb1, 0x27, 0x3f, 0x65, 0xff, + 0xf4, 0x7e, 0xbc, 0xe8, 0x21, 0xf9, 0x61, 0xc5, 0x84, 0xce, 0x44, 0xf3, + 0x23, 0xdb, 0x47, 0xc7, 0x26, 0x7d, 0x27, 0x2f, 0x3f, 0x5e, 0xf8, 0x78, + 0xfc, 0x49, 0xd6, 0x93, 0x99, 0xa7, 0xc5, 0x3f, 0x2b, 0xff, 0x5c, 0xfb, + 0xcc, 0xe4, 0xd9, 0x77, 0xbf, 0x78, 0xfc, 0x32, 0x30, 0x15, 0x3b, 0x35, + 0xfe, 0x5c, 0xf4, 0xfc, 0xd3, 0xaf, 0x9b, 0x5f, 0xa8, 0xbf, 0xd8, 0xff, + 0xd2, 0xee, 0x65, 0xef, 0x74, 0xd8, 0xf4, 0xfd, 0x57, 0x19, 0xaf, 0x66, + 0x5e, 0x97, 0xbc, 0x51, 0x7f, 0x73, 0xe0, 0x2d, 0xeb, 0x6d, 0xff, 0xbb, + 0x98, 0x77, 0x13, 0x33, 0xb9, 0xef, 0xb1, 0xef, 0x2b, 0x3f, 0x98, 0x7e, + 0xe8, 0xf9, 0x18, 0xf4, 0xf1, 0xee, 0xa7, 0x8c, 0x4f, 0x9f, 0x7e, 0x03, + 0xf7, 0x84, 0xf3, 0xfb, 0x8f, 0x70, 0x66, 0x2a, 0x00, 0x00, 0x00, 0x09, + 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x2e, 0x23, 0x00, 0x00, 0x2e, 0x23, + 0x01, 0x78, 0xa5, 0x3f, 0x76, 0x00, 0x00, 0x20, 0x00, 0x49, 0x44, 0x41, + 0x54, 0x78, 0x9c, 0xec, 0xdd, 0x0b, 0x94, 0xdc, 0x67, 0x61, 0xdf, 0xfd, + 0xe7, 0xf9, 0xcf, 0x65, 0x2f, 0xda, 0x5d, 0x5d, 0x76, 0x67, 0x25, 0xed, + 0x4a, 0xb6, 0xf1, 0x0d, 0x1b, 0xec, 0x00, 0xb6, 0x64, 0x87, 0x70, 0x42, + 0x12, 0x35, 0x31, 0x60, 0x4b, 0x56, 0x48, 0x42, 0x20, 0x25, 0x2f, 0x29, + 0x2d, 0xe0, 0x0b, 0x7e, 0x8d, 0x8d, 0x63, 0x49, 0x38, 0xb1, 0x65, 0xc9, + 0x24, 0x60, 0x1b, 0x87, 0x40, 0x09, 0x96, 0xda, 0xb4, 0x34, 0x24, 0x9c, + 0x86, 0x34, 0x79, 0x29, 0xd8, 0x40, 0xcb, 0x9b, 0x43, 0x5a, 0xda, 0xa4, + 0x6f, 0x6a, 0x0c, 0xa4, 0x0e, 0x17, 0x73, 0xf3, 0x55, 0x96, 0x2c, 0xc9, + 0x77, 0x4b, 0x2b, 0x69, 0x77, 0x67, 0xde, 0xff, 0x8c, 0x0d, 0x11, 0xbe, + 0xea, 0xb2, 0xbb, 0xcf, 0x7f, 0x66, 0x3e, 0x9f, 0x73, 0xc6, 0x33, 0x8f, + 0x8d, 0x76, 0x7f, 0x47, 0x91, 0xbd, 0xfb, 0xcd, 0xcc, 0xce, 0xbf, 0xdc, + 0x68, 0x34, 0x02, 0x00, 0x00, 0x00, 0x90, 0x56, 0x39, 0xf5, 0x00, 0x00, + 0x00, 0x00, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x21, 0x08, 0x74, 0x00, + 0x00, 0x00, 0x28, 0x00, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x05, 0x20, 0xd0, + 0x01, 0x00, 0x00, 0xa0, 0x00, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x14, 0x80, + 0x40, 0x07, 0x00, 0x00, 0x80, 0x02, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, + 0x00, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x0a, 0x40, 0xa0, 0x03, 0x00, 0x00, + 0x40, 0x01, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x00, 0x81, 0x0e, 0x00, + 0x00, 0x00, 0x05, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x00, 0x04, 0x3a, + 0x00, 0x00, 0x00, 0x14, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x02, 0x10, + 0xe8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x0a, + 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x01, 0x08, 0x74, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x05, 0x20, 0xd0, 0x01, 0x00, + 0x00, 0xa0, 0x00, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x14, 0x80, 0x40, 0x07, + 0x00, 0x00, 0x80, 0x02, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x02, + 0x1d, 0x00, 0x00, 0x00, 0x0a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x01, + 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x00, 0x81, 0x0e, 0x00, 0x00, 0x00, + 0x05, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x00, 0x04, 0x3a, 0x00, 0x00, + 0x00, 0x14, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x02, 0x10, 0xe8, 0x00, + 0x00, 0x00, 0x50, 0x00, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x0a, 0x40, 0xa0, + 0x03, 0x00, 0x00, 0x40, 0x01, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x81, 0x0e, 0x00, 0x00, 0x00, 0x05, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, + 0x00, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x14, 0x80, 0x40, 0x07, 0x00, 0x00, + 0x80, 0x02, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x02, 0x1d, 0x00, + 0x00, 0x00, 0x0a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x01, 0x08, 0x74, + 0x00, 0x00, 0x00, 0x28, 0x00, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x05, 0x20, + 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x00, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x14, + 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x02, 0x10, 0xe8, 0x00, 0x00, 0x00, + 0x50, 0x00, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x0a, 0x40, 0xa0, 0x03, 0x00, + 0x00, 0x40, 0x01, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x00, 0x81, 0x0e, + 0x00, 0x00, 0x00, 0x05, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x00, 0x04, + 0x3a, 0x00, 0x00, 0x00, 0x14, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x02, + 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x02, 0x1d, 0x00, 0x00, 0x00, + 0x0a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x01, 0x08, 0x74, 0x00, 0x00, + 0x00, 0x28, 0x00, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x05, 0x20, 0xd0, 0x01, + 0x00, 0x00, 0xa0, 0x00, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x14, 0x80, 0x40, + 0x07, 0x00, 0x00, 0x80, 0x02, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x00, + 0x02, 0x1d, 0x00, 0x00, 0x00, 0x0a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, + 0x01, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x00, 0x81, 0x0e, 0x00, 0x00, + 0x00, 0x05, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x00, 0x04, 0x3a, 0x00, + 0x00, 0x00, 0x14, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x02, 0x10, 0xe8, + 0x00, 0x00, 0x00, 0x50, 0x00, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x0a, 0x40, + 0xa0, 0x03, 0x00, 0x00, 0x40, 0x01, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, + 0x00, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x05, 0x20, 0xd0, 0x01, 0x00, 0x00, + 0xa0, 0x00, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x14, 0x80, 0x40, 0x07, 0x00, + 0x00, 0x80, 0x02, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x00, 0x02, 0x1d, + 0x00, 0x00, 0x00, 0x0a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x01, 0x08, + 0x74, 0x00, 0x00, 0x00, 0x28, 0x00, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x05, + 0x20, 0xd0, 0x01, 0x00, 0x38, 0x62, 0xcb, 0x17, 0xaf, 0x3e, 0xb9, 0xd1, + 0x28, 0xfd, 0x4a, 0x8c, 0xe1, 0xe7, 0xf2, 0xe3, 0xe9, 0xf9, 0x6d, 0x38, + 0xbf, 0xd5, 0x43, 0x23, 0xdc, 0x1f, 0x62, 0xe3, 0xff, 0x0b, 0x21, 0xfe, + 0xa7, 0x6d, 0xbb, 0x6e, 0xf9, 0x42, 0x3d, 0x97, 0x78, 0x2a, 0x40, 0xe1, + 0x09, 0x74, 0x00, 0x00, 0x0e, 0xdb, 0xf8, 0xe2, 0xf3, 0xcf, 0xcd, 0x42, + 0xbc, 0x22, 0x84, 0xd2, 0xaa, 0x3c, 0xce, 0x9f, 0x2d, 0x86, 0x93, 0xf3, + 0xbf, 0xe4, 0xb7, 0xf0, 0xb6, 0xf1, 0xda, 0x9a, 0x3b, 0xc6, 0x6a, 0xab, + 0xdf, 0xf9, 0xc0, 0xae, 0x5b, 0xff, 0x7e, 0xae, 0x77, 0x02, 0xb4, 0x13, + 0x81, 0x0e, 0x00, 0xc0, 0x21, 0x5b, 0x56, 0x5b, 0x7b, 0x4e, 0xcc, 0xc2, + 0xe6, 0x3c, 0xce, 0xcf, 0x3e, 0x8c, 0x5f, 0x76, 0x7a, 0x29, 0x2b, 0x7d, + 0x65, 0x7c, 0x74, 0xed, 0x3b, 0xb6, 0xed, 0xfc, 0xec, 0x9f, 0xce, 0xda, + 0x38, 0x80, 0x36, 0x27, 0xd0, 0x01, 0x00, 0x78, 0x51, 0xe3, 0x8b, 0x57, + 0xbf, 0x36, 0x6b, 0x94, 0xae, 0xcb, 0xe3, 0xfc, 0xb5, 0x47, 0xf8, 0x21, + 0xaa, 0x59, 0x0c, 0xff, 0x61, 0xd9, 0xe8, 0xea, 0xc7, 0xef, 0xdf, 0x79, + 0xeb, 0x67, 0x67, 0x74, 0x1c, 0x40, 0x87, 0x10, 0xe8, 0x00, 0x00, 0x3c, + 0xaf, 0xe5, 0x23, 0xe7, 0xbf, 0xba, 0x51, 0x8a, 0x9b, 0xb3, 0x50, 0xfa, + 0xc5, 0xf0, 0x5c, 0x2f, 0x65, 0x3f, 0x3c, 0x59, 0x8c, 0xa5, 0x3f, 0xa9, + 0xd5, 0xce, 0x7f, 0xf9, 0xae, 0x5d, 0x9f, 0xdb, 0x36, 0x03, 0xf3, 0x00, + 0x3a, 0x8a, 0x40, 0x07, 0x00, 0xe0, 0x59, 0xc6, 0x47, 0xd7, 0x9c, 0x99, + 0x85, 0xb8, 0x39, 0x94, 0xe2, 0xb9, 0x47, 0xdf, 0xe5, 0x3f, 0x61, 0x7e, + 0x4f, 0x0c, 0x37, 0xe5, 0xf7, 0x6f, 0x99, 0xd9, 0x0f, 0x0b, 0xd0, 0xfe, + 0x04, 0x3a, 0x00, 0x00, 0x3f, 0xb6, 0x74, 0x78, 0xf5, 0xe9, 0xe5, 0x72, + 0xe9, 0xda, 0x2c, 0x66, 0x6f, 0x0c, 0x61, 0x06, 0x9e, 0x33, 0x7f, 0x0e, + 0x31, 0xc6, 0x37, 0x2d, 0x1f, 0x3d, 0x77, 0xe3, 0x7d, 0x3b, 0xbf, 0x70, + 0xe7, 0x6c, 0x7c, 0x7c, 0x80, 0x76, 0x25, 0xd0, 0x01, 0x00, 0x08, 0xcb, + 0x46, 0xd7, 0x9e, 0x92, 0xdf, 0x6d, 0xcc, 0xe3, 0xfc, 0xd7, 0xf3, 0xfb, + 0x6c, 0x96, 0x3f, 0x5d, 0xd6, 0x88, 0xe5, 0x7f, 0x95, 0xdf, 0xaf, 0x9b, + 0xe5, 0xcf, 0x03, 0xd0, 0x56, 0x04, 0x3a, 0x00, 0x40, 0x17, 0x5b, 0xb2, + 0xf0, 0xbc, 0x13, 0x2a, 0xd5, 0xd2, 0x35, 0x31, 0xc6, 0xb7, 0xe6, 0xc7, + 0xd2, 0x5c, 0x7d, 0xde, 0x18, 0xe2, 0x9b, 0x82, 0x40, 0x07, 0xf8, 0x09, + 0x02, 0x1d, 0x00, 0xa0, 0x0b, 0x2d, 0x1b, 0x5e, 0x7b, 0x4c, 0x2c, 0x85, + 0xdf, 0xa9, 0x54, 0xcb, 0x6f, 0xcf, 0x8f, 0x95, 0x04, 0x13, 0x8e, 0x1b, + 0x1f, 0x5e, 0x7d, 0xec, 0xb6, 0x87, 0x6e, 0xbd, 0x27, 0xc1, 0xe7, 0x06, + 0x28, 0x24, 0x81, 0x0e, 0x00, 0xd0, 0x45, 0x6a, 0xb5, 0xf3, 0xc7, 0x7b, + 0xb2, 0x78, 0x55, 0x2c, 0x87, 0x77, 0xe4, 0xc7, 0x6a, 0xca, 0x2d, 0x59, + 0xb9, 0xd4, 0xbc, 0x96, 0xba, 0x40, 0x07, 0x78, 0x9a, 0x40, 0x07, 0x00, + 0xe8, 0x02, 0x63, 0x43, 0xaf, 0x1f, 0xcd, 0x7a, 0xab, 0xeb, 0x7b, 0xb3, + 0x78, 0x51, 0x7e, 0xec, 0x4b, 0xbd, 0xa7, 0xa9, 0x11, 0x1a, 0x27, 0xa4, + 0xde, 0x00, 0x50, 0x24, 0x02, 0x1d, 0x00, 0xa0, 0x83, 0x2d, 0x5f, 0xf0, + 0x4b, 0xc3, 0xa1, 0xa7, 0xef, 0xb7, 0x4b, 0x7d, 0x3d, 0x97, 0xe4, 0xc7, + 0x81, 0xd4, 0x7b, 0x0e, 0x16, 0x43, 0x38, 0x26, 0xf5, 0x06, 0x80, 0x22, + 0x11, 0xe8, 0x00, 0x00, 0x1d, 0x68, 0x78, 0xf8, 0x9c, 0xf9, 0x7d, 0xa5, + 0xbe, 0xf7, 0xc6, 0x9e, 0xfe, 0xcb, 0xf2, 0xe3, 0x50, 0xea, 0x3d, 0xcf, + 0x63, 0x30, 0xf5, 0x00, 0x80, 0x22, 0x11, 0xe8, 0x00, 0x00, 0x1d, 0x64, + 0x49, 0xb6, 0x6a, 0xa0, 0x54, 0x9b, 0x77, 0x69, 0x7f, 0xb9, 0xef, 0xb7, + 0xf3, 0xe3, 0xc2, 0xd4, 0x7b, 0x5e, 0x44, 0x7f, 0xea, 0x01, 0x00, 0x45, + 0x22, 0xd0, 0x01, 0x00, 0x3a, 0xc0, 0xb2, 0x6c, 0x6d, 0x7f, 0x1c, 0x09, + 0x17, 0x57, 0x6a, 0x03, 0xeb, 0x42, 0x88, 0xb5, 0xd4, 0x7b, 0x0e, 0x45, + 0x23, 0xc4, 0xd9, 0xbe, 0xde, 0x3a, 0x40, 0x5b, 0x11, 0xe8, 0x00, 0x00, + 0x6d, 0x2c, 0xcb, 0xce, 0xeb, 0x19, 0xab, 0x95, 0x2f, 0xc8, 0x93, 0xfc, + 0x7d, 0xf9, 0x71, 0x49, 0xeb, 0x27, 0xbb, 0x01, 0x68, 0x4b, 0x02, 0x1d, + 0x00, 0xa0, 0x0d, 0x65, 0xd9, 0xca, 0xca, 0xd2, 0x91, 0x25, 0x6f, 0x1f, + 0xaf, 0x95, 0x7f, 0x37, 0x3f, 0x2e, 0x4f, 0xbd, 0x07, 0x80, 0xa3, 0x27, + 0xd0, 0x01, 0x00, 0xda, 0x48, 0x96, 0xad, 0x2a, 0x8f, 0xd5, 0x06, 0xde, + 0x36, 0x5e, 0x1b, 0x6b, 0x86, 0xf9, 0x4b, 0x52, 0xef, 0x01, 0x60, 0xe6, + 0x08, 0x74, 0x00, 0x80, 0x36, 0x90, 0xe5, 0xc6, 0x46, 0xd6, 0xbc, 0x65, + 0x7c, 0x64, 0x70, 0x63, 0x7e, 0x3c, 0x39, 0xf5, 0x1e, 0x00, 0x66, 0x9e, + 0x40, 0x07, 0x00, 0x28, 0xb0, 0xbc, 0xcb, 0xe3, 0xd2, 0xe1, 0xd5, 0xbf, + 0x3a, 0x5e, 0x5b, 0xdd, 0x0c, 0xf3, 0xd3, 0x52, 0xef, 0x01, 0x60, 0xf6, + 0x08, 0x74, 0x00, 0x80, 0x02, 0x6a, 0x86, 0xf9, 0xf8, 0xc8, 0x79, 0x6b, + 0xc6, 0x6b, 0x6b, 0x36, 0xe5, 0xc7, 0x57, 0xa6, 0xde, 0x03, 0xc0, 0xec, + 0x13, 0xe8, 0x00, 0x00, 0x05, 0xb3, 0xac, 0xb6, 0xf6, 0x9c, 0xf1, 0xda, + 0xea, 0xeb, 0x42, 0x88, 0x67, 0xa5, 0xde, 0x02, 0xc0, 0xdc, 0x11, 0xe8, + 0x00, 0x00, 0x05, 0x31, 0x36, 0xba, 0xe6, 0x17, 0xb2, 0x98, 0x5d, 0x17, + 0xb3, 0xf0, 0x1a, 0x97, 0x4b, 0x03, 0xe8, 0x3e, 0x02, 0x1d, 0x00, 0x20, + 0xb1, 0xe5, 0x23, 0x6b, 0x7e, 0x26, 0x94, 0xb2, 0xeb, 0x4a, 0x31, 0x5b, + 0x95, 0x7a, 0x0b, 0x00, 0xe9, 0x08, 0x74, 0x00, 0x80, 0x44, 0xc6, 0x47, + 0x57, 0xaf, 0xc8, 0x42, 0xb6, 0x29, 0x8f, 0xf3, 0x73, 0x53, 0x6f, 0x01, + 0x20, 0x3d, 0x81, 0x0e, 0x00, 0x30, 0xc7, 0x96, 0x0c, 0x9f, 0xff, 0x8a, + 0x4a, 0x39, 0x5e, 0x9b, 0xc5, 0xd2, 0xda, 0xe0, 0xb5, 0xec, 0x00, 0x3c, + 0x4d, 0xa0, 0x03, 0x00, 0xcc, 0x91, 0xf1, 0xda, 0x9a, 0x53, 0x63, 0xcc, + 0xae, 0xcd, 0xe3, 0xfc, 0xd7, 0xf2, 0x63, 0x96, 0x7a, 0x0f, 0x00, 0xc5, + 0x22, 0xd0, 0x01, 0x00, 0x66, 0xd9, 0x92, 0x85, 0x6f, 0x38, 0xb1, 0x52, + 0xad, 0x5c, 0x9d, 0x65, 0xd9, 0x5b, 0xf3, 0x63, 0x29, 0xf5, 0x1e, 0x00, + 0x8a, 0x49, 0xa0, 0x03, 0x00, 0xcc, 0x92, 0xb1, 0x45, 0xe7, 0x1e, 0x97, + 0x95, 0x2b, 0x57, 0x57, 0xaa, 0xd5, 0xb7, 0x05, 0xdf, 0x77, 0x01, 0xf0, + 0x22, 0x7c, 0xa1, 0x00, 0x00, 0x98, 0x61, 0xb5, 0xda, 0xf9, 0xe3, 0x3d, + 0x59, 0xbc, 0xaa, 0x54, 0xa9, 0xbc, 0x23, 0x3f, 0x56, 0x53, 0xef, 0x01, + 0xa0, 0x3d, 0x08, 0x74, 0x00, 0x80, 0x19, 0xb2, 0x64, 0x70, 0xcd, 0x92, + 0x4a, 0x5f, 0xb6, 0xbe, 0x37, 0x8b, 0x17, 0xe6, 0xc7, 0xde, 0xd4, 0x7b, + 0x00, 0x68, 0x2f, 0x02, 0x1d, 0x00, 0xe0, 0x28, 0x2d, 0x9b, 0xbf, 0x76, + 0x24, 0xcf, 0xf1, 0x2b, 0x2b, 0xfd, 0xd9, 0x25, 0xf9, 0xb1, 0x3f, 0xf5, + 0x1e, 0x00, 0xda, 0x93, 0x40, 0x07, 0x00, 0x38, 0x42, 0x8b, 0x16, 0xad, + 0x5a, 0xd0, 0x5f, 0x1e, 0xbc, 0x3c, 0xf6, 0x86, 0xcb, 0xf2, 0xe3, 0x50, + 0xea, 0x3d, 0x00, 0xb4, 0x37, 0x81, 0x0e, 0x00, 0x70, 0x98, 0x46, 0x47, + 0x7f, 0x76, 0xb0, 0x27, 0x2e, 0x7c, 0xcf, 0xbc, 0xca, 0xe0, 0x7b, 0xf3, + 0xe3, 0xc2, 0xd4, 0x7b, 0x00, 0xe8, 0x0c, 0x02, 0x1d, 0x00, 0xe0, 0x10, + 0x2d, 0xcb, 0xd6, 0xf6, 0x87, 0x91, 0xc6, 0x25, 0xd5, 0xb8, 0xe8, 0xca, + 0xfc, 0x38, 0x92, 0x7a, 0x0f, 0x00, 0x9d, 0x45, 0xa0, 0x03, 0x00, 0xbc, + 0x88, 0x2c, 0x5b, 0xd5, 0x3b, 0x56, 0x1b, 0x7c, 0x57, 0xac, 0x85, 0xf7, + 0x85, 0x10, 0x97, 0xa4, 0xde, 0x03, 0x40, 0x67, 0x12, 0xe8, 0x00, 0x00, + 0xcf, 0x23, 0xcb, 0x4e, 0xaf, 0x8e, 0x8f, 0x1c, 0xff, 0x8e, 0xf1, 0xda, + 0xe0, 0x55, 0xf9, 0x71, 0x3c, 0xf5, 0x1e, 0x00, 0x3a, 0x9b, 0x40, 0x07, + 0x00, 0x78, 0x86, 0x2c, 0x5b, 0x55, 0x1e, 0xab, 0x0d, 0xbc, 0x6d, 0xbc, + 0x76, 0xfc, 0xd5, 0xf9, 0xf1, 0xb8, 0xd4, 0x7b, 0x00, 0xe8, 0x0e, 0x02, + 0x1d, 0x00, 0xe0, 0x69, 0x59, 0x96, 0x95, 0x96, 0x8e, 0x9c, 0xf7, 0x1b, + 0xe3, 0xb5, 0xc1, 0x6b, 0xf2, 0xe3, 0x49, 0xa9, 0xf7, 0x00, 0xd0, 0x5d, + 0x04, 0x3a, 0x00, 0xd0, 0xf5, 0xf2, 0x30, 0x8f, 0xe3, 0xb5, 0x35, 0x6f, + 0x1a, 0xaf, 0xad, 0xbe, 0x36, 0x84, 0x78, 0x6a, 0xea, 0x3d, 0x00, 0x74, + 0x27, 0x81, 0x0e, 0x00, 0x74, 0xad, 0x56, 0x98, 0x8f, 0x9c, 0xb7, 0x26, + 0x8f, 0xf3, 0xcd, 0xf9, 0xf1, 0x15, 0x79, 0x9c, 0xa7, 0x9e, 0x04, 0x40, + 0x17, 0x13, 0xe8, 0x00, 0x40, 0x57, 0x1a, 0x1f, 0x59, 0xf3, 0xfa, 0xf1, + 0xda, 0xea, 0x4d, 0x79, 0x94, 0x9f, 0x95, 0x7a, 0x0b, 0x00, 0x34, 0x09, + 0x74, 0x00, 0xa0, 0xab, 0x2c, 0x1f, 0x5d, 0xbb, 0xaa, 0x11, 0xc3, 0xe6, + 0xac, 0x94, 0xbd, 0x26, 0xf5, 0x16, 0x00, 0x38, 0x98, 0x40, 0x07, 0x00, + 0xba, 0xc2, 0xb2, 0x91, 0xd5, 0xaf, 0x89, 0xa5, 0xd2, 0xe6, 0x10, 0xc3, + 0x2a, 0x2f, 0x64, 0x07, 0xa0, 0x88, 0x04, 0x3a, 0x00, 0xd0, 0xd1, 0xc6, + 0x6a, 0xe7, 0x9f, 0x55, 0xca, 0xe2, 0xa6, 0x3c, 0xce, 0x5f, 0x9f, 0x7a, + 0x0b, 0x00, 0xbc, 0x10, 0x81, 0x0e, 0x00, 0x74, 0xa4, 0xe5, 0x8b, 0xd6, + 0xbc, 0xb2, 0x51, 0xc9, 0x36, 0xe5, 0x71, 0xbe, 0x26, 0x78, 0xf7, 0x37, + 0x00, 0xda, 0x80, 0x40, 0x07, 0x00, 0x3a, 0xca, 0x78, 0xed, 0xbc, 0x97, + 0xc5, 0x58, 0xde, 0x18, 0x2b, 0xd9, 0x9b, 0xa2, 0x30, 0x07, 0xa0, 0x8d, + 0x08, 0x74, 0x00, 0xa0, 0x23, 0x2c, 0x5f, 0xbc, 0xfa, 0xe4, 0x10, 0x4a, + 0x1b, 0xb3, 0xac, 0xfc, 0x96, 0xfc, 0x98, 0xa5, 0xde, 0x03, 0x00, 0x87, + 0x4b, 0xa0, 0x03, 0x00, 0x6d, 0x6d, 0x6c, 0xd1, 0xb9, 0xc7, 0x65, 0xe5, + 0xca, 0xd5, 0x31, 0x96, 0xde, 0x16, 0x7c, 0x6f, 0x03, 0x40, 0x1b, 0xf3, + 0x45, 0x0c, 0x00, 0x68, 0x4b, 0xcb, 0x47, 0xde, 0xb0, 0x3c, 0x94, 0x2a, + 0x57, 0x95, 0x2a, 0x95, 0x7f, 0x99, 0x1f, 0xab, 0xa9, 0xf7, 0x00, 0xc0, + 0xd1, 0x12, 0xe8, 0x00, 0x40, 0x5b, 0x59, 0x32, 0xb8, 0x66, 0x49, 0xa5, + 0x2f, 0xbe, 0x2f, 0x94, 0xaa, 0xef, 0xca, 0x8f, 0xbd, 0xa9, 0xf7, 0x00, + 0xc0, 0x4c, 0x11, 0xe8, 0x00, 0x40, 0x5b, 0x58, 0x36, 0x7f, 0xed, 0x48, + 0x9e, 0xe3, 0x57, 0x56, 0xfa, 0xb3, 0x4b, 0xf2, 0x63, 0x7f, 0xea, 0x3d, + 0x00, 0x30, 0xd3, 0x04, 0x3a, 0x00, 0x50, 0x68, 0x0b, 0x17, 0xae, 0x59, + 0x38, 0xaf, 0x12, 0xdf, 0x1b, 0x7b, 0xe3, 0x7b, 0xf2, 0xe3, 0x60, 0xea, + 0x3d, 0x00, 0x30, 0x5b, 0x04, 0x3a, 0x00, 0x50, 0x48, 0xb5, 0xda, 0x79, + 0x43, 0xbd, 0x59, 0xf9, 0xb2, 0x81, 0x6a, 0x76, 0x79, 0x7e, 0x5c, 0x90, + 0x7a, 0x0f, 0x00, 0xcc, 0x36, 0x81, 0x0e, 0x00, 0x14, 0xca, 0x58, 0xf6, + 0xfa, 0x79, 0xd9, 0x48, 0xf5, 0xdd, 0x79, 0x9c, 0xaf, 0xcb, 0x8f, 0xc3, + 0xa9, 0xf7, 0x00, 0xc0, 0x5c, 0x11, 0xe8, 0x00, 0x40, 0x21, 0x64, 0xd9, + 0x6b, 0xfa, 0xc6, 0x6b, 0xc3, 0x17, 0x95, 0x6a, 0x3d, 0xeb, 0xf3, 0xe3, + 0x68, 0xea, 0x3d, 0x00, 0x30, 0xd7, 0x04, 0x3a, 0x00, 0x90, 0x54, 0x96, + 0x9d, 0x5e, 0x1d, 0x1f, 0x39, 0xfe, 0x1d, 0xe3, 0xb5, 0x91, 0xab, 0xf2, + 0xe3, 0x78, 0xea, 0x3d, 0x00, 0x90, 0x8a, 0x40, 0x07, 0x00, 0x92, 0xc8, + 0xb2, 0x95, 0x95, 0xb1, 0xda, 0xd8, 0x6f, 0x8d, 0xd7, 0x8e, 0xff, 0x9d, + 0xfc, 0x78, 0x5c, 0xea, 0x3d, 0x00, 0x90, 0x9a, 0x40, 0x07, 0x00, 0xe6, + 0x54, 0x96, 0x65, 0xa5, 0xa5, 0xc3, 0xab, 0xdf, 0x3a, 0x5e, 0x1b, 0xbb, + 0x26, 0x3f, 0x9e, 0x90, 0x7a, 0x0f, 0x00, 0x14, 0x85, 0x40, 0x07, 0x00, + 0xe6, 0x44, 0x1e, 0xe6, 0xd9, 0x78, 0x6d, 0xcd, 0xaf, 0x8d, 0xd7, 0x56, + 0x5f, 0x1b, 0x42, 0x3c, 0x35, 0xf5, 0x1e, 0x00, 0x28, 0x1a, 0x81, 0x0e, + 0x00, 0xcc, 0xaa, 0xbc, 0xcb, 0xe3, 0xd8, 0xc8, 0x9a, 0xb5, 0x79, 0x9c, + 0x5f, 0x9b, 0x1f, 0x5f, 0x91, 0xc7, 0x79, 0xea, 0x49, 0x00, 0x50, 0x48, + 0x02, 0x1d, 0x00, 0x98, 0x35, 0xe3, 0x8b, 0xcf, 0x3f, 0x77, 0x7c, 0x64, + 0xf5, 0xe6, 0xbc, 0xc9, 0xcf, 0x4c, 0xbd, 0x05, 0x00, 0x8a, 0x4e, 0xa0, + 0x03, 0x00, 0x33, 0x6e, 0xf9, 0xe8, 0xda, 0x55, 0x8d, 0x18, 0xae, 0xcb, + 0x42, 0xfc, 0x19, 0x4f, 0x98, 0x03, 0xc0, 0xa1, 0x11, 0xe8, 0x00, 0xc0, + 0x8c, 0x19, 0x5f, 0xbc, 0xe6, 0x67, 0xb3, 0x46, 0x76, 0x5d, 0x1e, 0xe5, + 0x3f, 0xa7, 0xcb, 0x01, 0xe0, 0xf0, 0x08, 0x74, 0x00, 0xe0, 0xa8, 0x8d, + 0xd5, 0x56, 0x9f, 0x5d, 0xca, 0x4a, 0x9b, 0xb3, 0x90, 0x9d, 0xe3, 0x19, + 0x73, 0x00, 0x38, 0x32, 0x02, 0x1d, 0x00, 0x38, 0x62, 0xcb, 0x17, 0xad, + 0x79, 0x65, 0xa3, 0x92, 0x6d, 0xca, 0xe3, 0x7c, 0x4d, 0xf0, 0xee, 0x6f, + 0x00, 0x70, 0x54, 0x04, 0x3a, 0x00, 0x70, 0xd8, 0xc6, 0x46, 0xce, 0x3f, + 0xad, 0x54, 0x8a, 0x9b, 0x42, 0x25, 0x7b, 0x63, 0x14, 0xe6, 0x00, 0x30, + 0x23, 0x04, 0x3a, 0x00, 0x70, 0xc8, 0x96, 0x2f, 0x5e, 0x7d, 0x72, 0x08, + 0xa5, 0x8d, 0x79, 0x9c, 0xbf, 0x25, 0x3f, 0x66, 0xa9, 0xf7, 0x00, 0x40, + 0x27, 0x11, 0xe8, 0x00, 0xc0, 0x8b, 0x5a, 0xba, 0x70, 0xcd, 0xf1, 0xa5, + 0x6a, 0x76, 0x75, 0x0c, 0xa5, 0xdf, 0x0c, 0xbe, 0x7f, 0x00, 0x80, 0x59, + 0xe1, 0x0b, 0x2c, 0x00, 0xf0, 0xbc, 0x96, 0x8f, 0xbc, 0x61, 0x79, 0xc8, + 0xaa, 0xbf, 0x5b, 0xae, 0x66, 0x6f, 0xcf, 0x8f, 0x95, 0xd4, 0x7b, 0x00, + 0xa0, 0x93, 0x09, 0x74, 0x00, 0xe0, 0x59, 0x16, 0x2f, 0x3e, 0x77, 0x69, + 0xb5, 0x51, 0xde, 0x10, 0x4a, 0xd5, 0x0b, 0xf2, 0x63, 0x4f, 0xea, 0x3d, + 0x00, 0xd0, 0x0d, 0x04, 0x3a, 0x00, 0xf0, 0x63, 0x63, 0x43, 0xaf, 0x1f, + 0xcd, 0xfa, 0x7a, 0xae, 0xac, 0x86, 0xca, 0xc5, 0x21, 0x86, 0xfe, 0xd4, + 0x7b, 0x00, 0xa0, 0x9b, 0x08, 0x74, 0x00, 0x20, 0x2c, 0x5c, 0xf8, 0x86, + 0x45, 0x03, 0x95, 0xea, 0x15, 0xa5, 0xbe, 0x9e, 0x4b, 0xf3, 0xe3, 0x40, + 0xea, 0x3d, 0x00, 0xd0, 0x8d, 0x04, 0x3a, 0x00, 0x74, 0xb1, 0x5a, 0xed, + 0xbc, 0xa1, 0xde, 0xac, 0x7c, 0xd9, 0x40, 0xb5, 0xfa, 0xde, 0xfc, 0x38, + 0x3f, 0xf5, 0x1e, 0x00, 0xe8, 0x66, 0x02, 0x1d, 0x00, 0xba, 0xd0, 0x92, + 0x6c, 0xd5, 0x40, 0xa9, 0x36, 0xef, 0xd2, 0x3c, 0xce, 0xaf, 0xc8, 0x8f, + 0x8b, 0x52, 0xef, 0x01, 0x00, 0x04, 0x3a, 0x00, 0x74, 0x95, 0x2c, 0x7b, + 0x4d, 0xdf, 0x78, 0x6d, 0xf8, 0xa2, 0x4a, 0x6d, 0x70, 0x7d, 0x7e, 0x1c, + 0x4d, 0xbd, 0x07, 0x00, 0xf8, 0x27, 0x02, 0x1d, 0x00, 0xba, 0x40, 0x96, + 0x9d, 0xd7, 0x33, 0x36, 0x5c, 0x7a, 0xe7, 0xf8, 0xc8, 0xc8, 0xfb, 0xf2, + 0xe3, 0x58, 0xea, 0x3d, 0x00, 0xc0, 0xb3, 0x09, 0x74, 0x00, 0xe8, 0x60, + 0x59, 0xb6, 0xb2, 0xb2, 0x74, 0x64, 0xc9, 0xdb, 0xc7, 0x6a, 0xe5, 0xdf, + 0x89, 0x21, 0x1c, 0x93, 0x7a, 0x0f, 0x00, 0xf0, 0xfc, 0x04, 0x3a, 0x00, + 0x74, 0xa0, 0x2c, 0xcb, 0x4a, 0x4b, 0x87, 0x57, 0xbf, 0x75, 0xbc, 0x36, + 0x76, 0x4d, 0x7e, 0x3c, 0x21, 0xf5, 0x1e, 0x00, 0xe0, 0xc5, 0x09, 0x74, + 0x00, 0xe8, 0x20, 0x79, 0x98, 0x67, 0x63, 0xb5, 0xd5, 0xbf, 0x3e, 0x56, + 0x5b, 0xb3, 0x31, 0x86, 0x70, 0x4a, 0xea, 0x3d, 0x00, 0xc0, 0xa1, 0x13, + 0xe8, 0x00, 0xd0, 0x01, 0xf2, 0x2e, 0x8f, 0x4b, 0x87, 0xcf, 0x7b, 0xe3, + 0x78, 0x6d, 0xcd, 0xb5, 0xf9, 0xf1, 0xf4, 0xd4, 0x7b, 0x00, 0x80, 0xc3, + 0x27, 0xd0, 0x01, 0xa0, 0xcd, 0x8d, 0x2f, 0x3e, 0xff, 0xdc, 0xf1, 0x91, + 0xd5, 0x9b, 0x43, 0x8c, 0x67, 0xa6, 0xde, 0x02, 0x00, 0x1c, 0x39, 0x81, + 0x0e, 0x00, 0x6d, 0x6a, 0x59, 0x6d, 0xed, 0x39, 0x31, 0x0b, 0x9b, 0xb3, + 0x10, 0xcf, 0x0e, 0x31, 0xf5, 0x1a, 0x00, 0xe0, 0x68, 0x09, 0x74, 0x00, + 0x68, 0x33, 0xe3, 0x8b, 0x57, 0xbf, 0x36, 0x6b, 0x94, 0xae, 0xcb, 0xe3, + 0xfc, 0xb5, 0xa9, 0xb7, 0x00, 0x00, 0x33, 0x47, 0xa0, 0x03, 0x40, 0x9b, + 0x18, 0xab, 0xad, 0x3e, 0xbb, 0x94, 0x95, 0x36, 0x67, 0xa1, 0x74, 0x8e, + 0x67, 0xcc, 0x01, 0xa0, 0xf3, 0x08, 0x74, 0x00, 0x28, 0xb8, 0xf1, 0xd1, + 0x35, 0x67, 0x66, 0x21, 0x6e, 0xce, 0xe3, 0xfc, 0xdc, 0xd4, 0x5b, 0x00, + 0x80, 0xd9, 0x23, 0xd0, 0x01, 0xa0, 0xa0, 0xc6, 0x46, 0xce, 0x3f, 0xad, + 0x54, 0x8a, 0x9b, 0xb2, 0x98, 0xbd, 0x31, 0x3f, 0x7a, 0xce, 0x1c, 0x00, + 0x3a, 0x9c, 0x40, 0x07, 0x80, 0x82, 0x59, 0x3e, 0x7a, 0xee, 0x4b, 0x43, + 0xac, 0x5c, 0x93, 0xc7, 0xf9, 0x5b, 0xf2, 0x63, 0x96, 0x7a, 0x0f, 0x00, + 0x30, 0x37, 0x04, 0x3a, 0x00, 0x14, 0xc4, 0x92, 0x85, 0xe7, 0x9d, 0x50, + 0xa9, 0x96, 0xae, 0xc9, 0xe3, 0xfc, 0xad, 0xf9, 0xb1, 0x94, 0x7a, 0x0f, + 0x00, 0x30, 0xb7, 0x04, 0x3a, 0x00, 0x24, 0xb6, 0x6c, 0x78, 0xed, 0x31, + 0xb1, 0x14, 0x7e, 0xa7, 0x52, 0x2d, 0xbf, 0x3d, 0x3f, 0x56, 0x52, 0xef, + 0x01, 0x00, 0xd2, 0x10, 0xe8, 0x00, 0x90, 0xc8, 0xe8, 0xe8, 0xeb, 0xc6, + 0xaa, 0xb1, 0xf7, 0x7d, 0xb1, 0x1c, 0xde, 0x99, 0x1f, 0x7b, 0x52, 0xef, + 0x01, 0x00, 0xd2, 0x12, 0xe8, 0x00, 0x30, 0xc7, 0xc6, 0x86, 0x5e, 0x3f, + 0x9a, 0xf5, 0x56, 0xd7, 0xf7, 0xc4, 0xde, 0x8b, 0xf2, 0x63, 0x5f, 0xea, + 0x3d, 0x00, 0x40, 0x31, 0x08, 0x74, 0x00, 0x98, 0x23, 0x0b, 0x17, 0xbe, + 0x61, 0xd1, 0x40, 0xa5, 0x7a, 0x45, 0xa9, 0xaf, 0xe7, 0xd2, 0xfc, 0x38, + 0x90, 0x7a, 0x0f, 0x00, 0x50, 0x2c, 0x02, 0x1d, 0x00, 0x66, 0xd9, 0xf0, + 0xf0, 0x39, 0xf3, 0xfb, 0x4a, 0x7d, 0xef, 0x1d, 0xa8, 0x56, 0x2f, 0xcb, + 0x8f, 0x43, 0xa9, 0xf7, 0x00, 0x00, 0xc5, 0x24, 0xd0, 0x01, 0x60, 0x96, + 0x2c, 0xc9, 0x56, 0x0d, 0x94, 0x6a, 0xf3, 0x2e, 0xed, 0x2f, 0xf7, 0x5d, + 0x91, 0x1f, 0x17, 0xa5, 0xde, 0x03, 0x00, 0x14, 0x9b, 0x40, 0x07, 0x80, + 0x19, 0xb6, 0x2c, 0x5b, 0xdb, 0x1f, 0x47, 0xc2, 0xc5, 0x95, 0xda, 0xe0, + 0x95, 0xf9, 0x71, 0x34, 0xf5, 0x1e, 0x00, 0xa0, 0x3d, 0x08, 0x74, 0x00, + 0x98, 0x21, 0x59, 0x76, 0x5e, 0xcf, 0x58, 0xad, 0x7c, 0x41, 0xac, 0x85, + 0x0d, 0xf9, 0x71, 0x69, 0xea, 0x3d, 0x00, 0x40, 0x7b, 0x11, 0xe8, 0x00, + 0x70, 0x94, 0xb2, 0x6c, 0x65, 0x65, 0xe9, 0xc8, 0x92, 0xb7, 0x8f, 0xd7, + 0xca, 0xbf, 0x9b, 0x1f, 0x97, 0xa7, 0xde, 0x03, 0x00, 0xb4, 0x27, 0x81, + 0x0e, 0x00, 0x47, 0x28, 0xcb, 0x56, 0x95, 0xc7, 0x87, 0x07, 0x7f, 0x73, + 0xbc, 0xb6, 0xf4, 0xea, 0x10, 0xe2, 0xf1, 0xa9, 0xf7, 0x00, 0x00, 0xed, + 0x4d, 0xa0, 0x03, 0xc0, 0x61, 0xca, 0x72, 0x63, 0x23, 0x6b, 0xde, 0x32, + 0x3e, 0x32, 0xb8, 0x31, 0xc4, 0x70, 0x72, 0x1e, 0xe7, 0xa9, 0x27, 0x01, + 0x00, 0x1d, 0x40, 0xa0, 0x03, 0xc0, 0x21, 0xca, 0xbb, 0x3c, 0x2e, 0x1d, + 0x3e, 0xef, 0x8d, 0xe3, 0xb5, 0xd5, 0x9b, 0xf2, 0xe3, 0x69, 0xa9, 0xf7, + 0x00, 0x00, 0x9d, 0x45, 0xa0, 0x03, 0xc0, 0x8b, 0x68, 0x86, 0xf9, 0xf8, + 0xc8, 0x79, 0x6b, 0xc6, 0x6b, 0x6b, 0x9a, 0x61, 0xfe, 0xca, 0xd4, 0x7b, + 0x00, 0x80, 0xce, 0x24, 0xd0, 0x01, 0xe0, 0x05, 0x2c, 0xab, 0xad, 0x3d, + 0x27, 0x0f, 0xf3, 0xcd, 0xf9, 0xc3, 0xb3, 0x53, 0x6f, 0x01, 0x00, 0x3a, + 0x9b, 0x40, 0x07, 0x80, 0xe7, 0x30, 0xbe, 0x78, 0xed, 0xcf, 0x65, 0xa1, + 0x71, 0x5d, 0xcc, 0xe2, 0xcf, 0xa6, 0xde, 0x02, 0x00, 0x74, 0x07, 0x81, + 0x0e, 0x00, 0x07, 0x59, 0x3e, 0xb2, 0xe6, 0x67, 0x42, 0x29, 0xbb, 0x2e, + 0x0b, 0x61, 0x95, 0x37, 0x7f, 0x03, 0x00, 0xe6, 0x92, 0x40, 0x07, 0x80, + 0xdc, 0xf8, 0xe8, 0x9a, 0x33, 0xb3, 0x10, 0x37, 0xe7, 0x71, 0x7e, 0x6e, + 0xea, 0x2d, 0x00, 0x40, 0x77, 0x12, 0xe8, 0x00, 0x74, 0xb5, 0xa5, 0xc3, + 0xab, 0x4f, 0x2f, 0x97, 0x4b, 0xd7, 0x66, 0x31, 0x7b, 0x63, 0xf0, 0x94, + 0x39, 0x00, 0x90, 0x90, 0x40, 0x07, 0xa0, 0x2b, 0x8d, 0xd7, 0xd6, 0x9c, + 0x1a, 0x63, 0x76, 0x6d, 0x1e, 0xe7, 0xbf, 0x96, 0x1f, 0xb3, 0xd4, 0x7b, + 0x00, 0x00, 0x04, 0x3a, 0x00, 0x5d, 0x65, 0xc9, 0xc2, 0xf3, 0x4e, 0xa8, + 0x54, 0x4b, 0xd7, 0x64, 0x59, 0xf6, 0xd6, 0xfc, 0x58, 0x4a, 0xbd, 0x07, + 0x00, 0xe0, 0x47, 0x04, 0x3a, 0x00, 0x5d, 0x61, 0x6c, 0xd1, 0xb9, 0xc7, + 0x95, 0xca, 0xe5, 0xdf, 0xa9, 0x54, 0xcb, 0xbf, 0x95, 0x1f, 0x2b, 0xa9, + 0xf7, 0x00, 0x00, 0x3c, 0x93, 0x40, 0x07, 0xa0, 0xa3, 0xd5, 0x6a, 0xe7, + 0x8f, 0xf7, 0x64, 0xf1, 0xaa, 0x52, 0xa5, 0xf2, 0x8e, 0xfc, 0x58, 0x4d, + 0xbd, 0x07, 0x00, 0xe0, 0xf9, 0x08, 0x74, 0x00, 0x3a, 0xd2, 0xd8, 0xd0, + 0xeb, 0x47, 0xb3, 0xde, 0xea, 0xfa, 0xde, 0x2c, 0x5e, 0x94, 0x1f, 0xfb, + 0x52, 0xef, 0x01, 0x00, 0x78, 0x31, 0x02, 0x1d, 0x80, 0x8e, 0xb2, 0x7c, + 0xc1, 0x2f, 0x0d, 0x37, 0x7a, 0xfa, 0xd7, 0x95, 0xfa, 0xaa, 0xef, 0x0e, + 0x21, 0xce, 0x4b, 0xbd, 0x07, 0x00, 0xe0, 0x50, 0x09, 0x74, 0x00, 0x3a, + 0xc2, 0xa2, 0x45, 0xab, 0x16, 0xf4, 0x97, 0x07, 0x2f, 0x8f, 0x3d, 0xfd, + 0x97, 0xc5, 0x10, 0x86, 0x5c, 0x31, 0x0d, 0x00, 0x68, 0x37, 0x02, 0x1d, + 0x80, 0xb6, 0x36, 0x3a, 0xfa, 0xb3, 0x83, 0x3d, 0x71, 0xe1, 0x7b, 0xe6, + 0x55, 0x06, 0xdf, 0x9b, 0x1f, 0x17, 0xa6, 0xde, 0x03, 0x00, 0x70, 0xa4, + 0x04, 0x3a, 0x00, 0x6d, 0x69, 0x59, 0xb6, 0xb6, 0x3f, 0x8c, 0x34, 0x2e, + 0xa9, 0xc6, 0x45, 0x57, 0xe6, 0xc7, 0x91, 0xd4, 0x7b, 0x00, 0x00, 0x8e, + 0x96, 0x40, 0x07, 0xa0, 0xad, 0x64, 0xd9, 0xaa, 0xde, 0xb1, 0xda, 0xe0, + 0xbb, 0x62, 0x2d, 0xbc, 0x2f, 0x84, 0xb8, 0x24, 0xf5, 0x1e, 0x00, 0x80, + 0x99, 0x22, 0xd0, 0x01, 0x68, 0x0b, 0x59, 0x76, 0x7a, 0x75, 0x7c, 0xe4, + 0x25, 0xff, 0x72, 0xbc, 0x36, 0x78, 0x55, 0x7e, 0x5c, 0x9e, 0x7a, 0x0f, + 0x00, 0xc0, 0x4c, 0x13, 0xe8, 0x00, 0x14, 0x5a, 0x96, 0xad, 0x2a, 0x8f, + 0xd5, 0x06, 0xde, 0x36, 0x5e, 0x3b, 0xfe, 0xea, 0xfc, 0x78, 0x5c, 0xea, + 0x3d, 0x00, 0x00, 0xb3, 0x45, 0xa0, 0x03, 0x50, 0x48, 0x59, 0x6e, 0x6c, + 0x64, 0xcd, 0x5b, 0xc6, 0x47, 0x06, 0x37, 0xe6, 0xc7, 0x93, 0x53, 0xef, + 0x01, 0x00, 0x98, 0x6d, 0x02, 0x1d, 0x80, 0x42, 0xc9, 0xbb, 0x3c, 0x8e, + 0xd7, 0xd6, 0xbc, 0x29, 0xbf, 0x35, 0xc3, 0xfc, 0x65, 0xa9, 0xf7, 0x00, + 0x00, 0xcc, 0x15, 0x81, 0x0e, 0x40, 0x21, 0xb4, 0xc2, 0x7c, 0xe4, 0xbc, + 0x35, 0x79, 0x98, 0x6f, 0xca, 0x8f, 0xaf, 0x4c, 0xbd, 0x07, 0x00, 0x60, + 0xae, 0x09, 0x74, 0x00, 0x92, 0x1b, 0x1f, 0x59, 0xf3, 0xfa, 0xf1, 0xda, + 0xea, 0x3c, 0xcc, 0xe3, 0x59, 0xa9, 0xb7, 0x00, 0x00, 0xa4, 0x22, 0xd0, + 0x01, 0x48, 0x66, 0xf9, 0xe8, 0xda, 0x55, 0x8d, 0x18, 0x36, 0x67, 0xa5, + 0xec, 0x35, 0xa9, 0xb7, 0x00, 0x00, 0xa4, 0x26, 0xd0, 0x01, 0x98, 0x73, + 0xcb, 0x46, 0x56, 0xbf, 0x26, 0x96, 0x4a, 0x9b, 0x43, 0x0c, 0xab, 0x62, + 0xea, 0x31, 0x00, 0x00, 0x05, 0x21, 0xd0, 0x01, 0x98, 0x33, 0xe3, 0xa3, + 0xab, 0x57, 0x64, 0x21, 0xdb, 0x94, 0xc7, 0xf9, 0xb9, 0xa9, 0xb7, 0x00, + 0x00, 0x14, 0x8d, 0x40, 0x07, 0x60, 0xd6, 0x2d, 0x19, 0x3e, 0xff, 0x15, + 0xe5, 0x72, 0xdc, 0x9c, 0xc5, 0xd2, 0x9a, 0xfc, 0xe8, 0x49, 0x73, 0x00, + 0x80, 0xe7, 0x20, 0xd0, 0x01, 0x98, 0x35, 0xe3, 0xb5, 0x35, 0xa7, 0xc6, + 0x98, 0x5d, 0x5b, 0x29, 0xc7, 0x5f, 0xcb, 0x8f, 0x59, 0xea, 0x3d, 0x00, + 0x00, 0x45, 0x26, 0xd0, 0x01, 0x98, 0x71, 0x8b, 0x17, 0x9f, 0x7f, 0x52, + 0x35, 0xc4, 0x6b, 0xb2, 0x2c, 0xfb, 0x8d, 0xfc, 0x58, 0x4a, 0xbd, 0x07, + 0x00, 0xa0, 0x1d, 0x08, 0x74, 0x00, 0x66, 0xcc, 0xd8, 0xa2, 0x73, 0x8f, + 0xcb, 0xca, 0x95, 0xab, 0xab, 0x31, 0xbe, 0x2d, 0xf8, 0x1a, 0x03, 0x00, + 0x70, 0x58, 0x7c, 0xf3, 0x04, 0xc0, 0x51, 0xab, 0xd5, 0xce, 0x1f, 0xef, + 0xc9, 0xe2, 0x55, 0xa5, 0x4a, 0xe5, 0x1d, 0xf9, 0xb1, 0x9a, 0x7a, 0x0f, + 0x00, 0x40, 0x3b, 0x12, 0xe8, 0x00, 0x1c, 0xb1, 0x25, 0x83, 0x6b, 0x96, + 0x54, 0xfa, 0xe2, 0xfb, 0x7a, 0xb3, 0xf8, 0xae, 0xfc, 0xd8, 0x9b, 0x7a, + 0x0f, 0x00, 0x40, 0x3b, 0x13, 0xe8, 0x00, 0x1c, 0xb6, 0x65, 0xf3, 0xd7, + 0x8e, 0xe4, 0x39, 0x7e, 0x65, 0xa5, 0x3f, 0xbb, 0x24, 0x3f, 0xf6, 0xa7, + 0xde, 0x03, 0x00, 0xd0, 0x09, 0x04, 0x3a, 0x00, 0x87, 0x6c, 0xd1, 0xa2, + 0x55, 0x0b, 0xfa, 0xcb, 0x83, 0x97, 0xc7, 0xde, 0x70, 0x79, 0x7e, 0x1c, + 0x4c, 0xbd, 0x07, 0x00, 0xa0, 0x93, 0x08, 0x74, 0x00, 0x5e, 0x54, 0xad, + 0x76, 0xde, 0x50, 0x6f, 0x56, 0xbe, 0x6c, 0x5e, 0x65, 0xb0, 0x19, 0xe6, + 0x0b, 0x52, 0xef, 0x01, 0x00, 0xe8, 0x44, 0x02, 0x1d, 0x80, 0xe7, 0xb5, + 0x2c, 0x5b, 0xdb, 0x1f, 0x46, 0x1a, 0x97, 0xf4, 0x64, 0xe5, 0x2b, 0xf3, + 0xe3, 0x48, 0xea, 0x3d, 0x00, 0x00, 0x9d, 0x4c, 0xa0, 0x03, 0xf0, 0x2c, + 0x59, 0xb6, 0xaa, 0x77, 0x69, 0x6d, 0xde, 0x85, 0x59, 0x2d, 0x5b, 0x1f, + 0x42, 0x5c, 0x92, 0x7a, 0x0f, 0x00, 0x40, 0x37, 0x10, 0xe8, 0x00, 0xfc, + 0x58, 0x96, 0x9d, 0x5e, 0x1d, 0x1f, 0x39, 0xfe, 0x1d, 0xe3, 0xb5, 0xc1, + 0xab, 0xf2, 0xe3, 0x78, 0xea, 0x3d, 0x00, 0x00, 0xdd, 0x44, 0xa0, 0x03, + 0xd0, 0x7c, 0xc6, 0xbc, 0x3c, 0x56, 0x1b, 0x78, 0xdb, 0x78, 0xed, 0xf8, + 0xab, 0xf3, 0xe3, 0x71, 0xa9, 0xf7, 0x00, 0x00, 0x74, 0x23, 0x81, 0x0e, + 0xd0, 0xc5, 0xb2, 0x2c, 0x2b, 0x2d, 0x1d, 0x5e, 0xfd, 0xd6, 0xf1, 0xda, + 0x60, 0x33, 0xcc, 0x4f, 0x4c, 0xbd, 0x07, 0x00, 0xa0, 0x9b, 0x09, 0x74, + 0x80, 0x2e, 0x94, 0x87, 0x79, 0x36, 0x5e, 0x5b, 0xf3, 0x6b, 0xe3, 0xb5, + 0xd5, 0xd7, 0x86, 0x10, 0x4f, 0x4d, 0xbd, 0x07, 0x00, 0x00, 0x81, 0x0e, + 0xd0, 0x55, 0xf2, 0x2e, 0x8f, 0x63, 0x23, 0x6b, 0xd6, 0xe6, 0x71, 0x7e, + 0x6d, 0x7e, 0x7c, 0x45, 0x1e, 0xe7, 0xa9, 0x27, 0x01, 0x00, 0xf0, 0x34, + 0x81, 0x0e, 0xd0, 0x25, 0xc6, 0x17, 0x9f, 0x7f, 0x6e, 0x1e, 0xe6, 0x9b, + 0xf2, 0x87, 0x2b, 0x52, 0x6f, 0x01, 0x00, 0xe0, 0xd9, 0x04, 0x3a, 0x40, + 0x87, 0x5b, 0x3e, 0xba, 0x76, 0x55, 0x23, 0x86, 0xeb, 0xb2, 0x10, 0x7f, + 0x26, 0xf5, 0x16, 0x00, 0x00, 0x9e, 0x9f, 0x40, 0x07, 0xe8, 0x50, 0xcb, + 0x46, 0x56, 0xbf, 0x26, 0x96, 0x4a, 0xd7, 0x85, 0x18, 0x7e, 0xc1, 0x0b, + 0xd9, 0x01, 0x00, 0x8a, 0x4f, 0xa0, 0x03, 0x74, 0x98, 0xb1, 0xda, 0xf9, + 0x67, 0x95, 0xb2, 0x78, 0x5d, 0x1e, 0xe7, 0xe7, 0xa4, 0xde, 0x02, 0x00, + 0xc0, 0xa1, 0x13, 0xe8, 0x00, 0x1d, 0x62, 0xf9, 0xa2, 0x35, 0xaf, 0x6c, + 0x54, 0xb2, 0x4d, 0x79, 0x9c, 0xaf, 0x09, 0xde, 0xfd, 0x0d, 0x00, 0xa0, + 0xed, 0x08, 0x74, 0x80, 0x36, 0x37, 0x36, 0x72, 0xfe, 0x69, 0xa5, 0x52, + 0xd8, 0x18, 0x2a, 0xd9, 0xaf, 0x46, 0x61, 0x0e, 0x00, 0xd0, 0xb6, 0x04, + 0x3a, 0x40, 0x9b, 0x5a, 0xbe, 0x78, 0xf5, 0xc9, 0x21, 0x94, 0x36, 0x96, + 0x4a, 0xf1, 0x2d, 0xf9, 0x31, 0x4b, 0xbd, 0x07, 0x00, 0x80, 0xa3, 0x23, + 0xd0, 0x01, 0xda, 0xcc, 0xb2, 0xe1, 0xb5, 0xc7, 0x84, 0x72, 0xe3, 0xda, + 0x18, 0x4a, 0xff, 0x57, 0xf0, 0xdf, 0x71, 0x00, 0x80, 0x8e, 0xe1, 0x1b, + 0x3b, 0x80, 0x36, 0xb1, 0x7c, 0xc1, 0x2f, 0x0d, 0x37, 0x7a, 0xfa, 0x37, + 0xc4, 0x72, 0xb8, 0x24, 0x84, 0xd8, 0x9b, 0x7a, 0x0f, 0x00, 0x00, 0x33, + 0x4b, 0xa0, 0x03, 0x14, 0xdc, 0xb2, 0x6c, 0x6d, 0x7f, 0x18, 0x09, 0x97, + 0xc6, 0x9e, 0xfe, 0xf5, 0x31, 0x84, 0x05, 0xa9, 0xf7, 0x00, 0x00, 0x30, + 0x3b, 0x04, 0x3a, 0x40, 0x41, 0x65, 0x59, 0x56, 0x1a, 0xab, 0xad, 0xf9, + 0x17, 0x71, 0x24, 0x6c, 0x0e, 0x31, 0x8c, 0xa5, 0xde, 0x03, 0x00, 0xc0, + 0xec, 0x12, 0xe8, 0x00, 0x05, 0x34, 0xbe, 0xf8, 0xfc, 0x73, 0xc7, 0x6b, + 0xab, 0xaf, 0xcf, 0x1f, 0x9e, 0xe6, 0x7d, 0xd9, 0x01, 0x00, 0xba, 0x83, + 0x40, 0x07, 0x28, 0x90, 0xf1, 0xd1, 0x35, 0x67, 0x66, 0x31, 0xbb, 0x21, + 0x0b, 0x71, 0x55, 0xea, 0x2d, 0x00, 0x00, 0xcc, 0x2d, 0x81, 0x0e, 0x50, + 0x00, 0xa3, 0xa3, 0xaf, 0x1b, 0xeb, 0x89, 0x3d, 0x1f, 0xc8, 0xe3, 0xbc, + 0xf9, 0xce, 0xec, 0x9e, 0x33, 0x07, 0x00, 0xe8, 0x42, 0x02, 0x1d, 0x20, + 0xa1, 0x2c, 0x7b, 0x4d, 0xdf, 0x78, 0x6d, 0xf8, 0x8a, 0x9e, 0xd8, 0xbb, + 0x3e, 0x3f, 0x0e, 0xa4, 0xde, 0x03, 0x00, 0x40, 0x3a, 0x02, 0x1d, 0x20, + 0x91, 0xe5, 0x8b, 0xd7, 0xfe, 0x7a, 0x1e, 0xe7, 0x37, 0x84, 0x10, 0x8f, + 0x4d, 0xbd, 0x05, 0x00, 0x80, 0xf4, 0x04, 0x3a, 0xc0, 0x1c, 0x5b, 0xbc, + 0xf8, 0xfc, 0x93, 0x2a, 0x21, 0xfe, 0x51, 0x0c, 0xe1, 0x97, 0xbc, 0x9a, + 0x1d, 0x00, 0x80, 0x1f, 0x11, 0xe8, 0x00, 0x73, 0x24, 0xcb, 0x56, 0xf5, + 0x8e, 0x8d, 0x0c, 0x6c, 0xa8, 0xc6, 0xb8, 0x21, 0x3f, 0xf6, 0xa4, 0xde, + 0x03, 0x00, 0x40, 0xb1, 0x08, 0x74, 0x80, 0x39, 0xb0, 0x7c, 0x74, 0xed, + 0xaa, 0xf1, 0xda, 0xe0, 0x96, 0xfc, 0xe1, 0x49, 0xa9, 0xb7, 0x00, 0x00, + 0x50, 0x4c, 0x02, 0x1d, 0x60, 0x16, 0x0d, 0x0f, 0x9f, 0x33, 0xbf, 0xbf, + 0xd4, 0xfb, 0xa1, 0x10, 0xe3, 0xbf, 0x0a, 0x5e, 0xcf, 0x0e, 0x00, 0xc0, + 0x0b, 0x10, 0xe8, 0x00, 0xb3, 0x64, 0x6c, 0xf4, 0xfc, 0x9f, 0xef, 0x2f, + 0xf7, 0xfe, 0x07, 0x6f, 0x02, 0x07, 0x00, 0xc0, 0xa1, 0x10, 0xe8, 0x00, + 0x33, 0x2c, 0xcb, 0x56, 0x56, 0xc6, 0x6b, 0x4b, 0x37, 0x97, 0x62, 0x5c, + 0xd7, 0x3c, 0xa6, 0xde, 0x03, 0x00, 0x40, 0x7b, 0x10, 0xe8, 0x00, 0x33, + 0x68, 0xf9, 0xc8, 0x1b, 0x96, 0x8f, 0xd7, 0xc6, 0xfe, 0x22, 0x7f, 0xf8, + 0xd3, 0xa9, 0xb7, 0x00, 0x00, 0xd0, 0x5e, 0x04, 0x3a, 0xc0, 0x0c, 0x19, + 0x1b, 0x5d, 0xf3, 0x0b, 0xa5, 0x52, 0xe5, 0xd3, 0xf9, 0xc3, 0x5a, 0xea, + 0x2d, 0x00, 0x00, 0xb4, 0x1f, 0x81, 0x0e, 0x30, 0x03, 0x96, 0x2d, 0x3e, + 0xff, 0x9d, 0xa5, 0x98, 0xfd, 0x51, 0xfe, 0xb0, 0x92, 0x7a, 0x0b, 0x00, + 0x00, 0xed, 0x49, 0xa0, 0x03, 0x1c, 0x85, 0x2c, 0xcb, 0xe2, 0xf8, 0xc8, + 0x9a, 0xf7, 0xc7, 0x18, 0xaf, 0x4a, 0xbd, 0x05, 0x00, 0x80, 0xf6, 0x26, + 0xd0, 0x01, 0x8e, 0x50, 0x1e, 0xe7, 0xd9, 0x78, 0x6d, 0x75, 0xf3, 0x59, + 0xf3, 0x0b, 0x53, 0x6f, 0x01, 0x00, 0xa0, 0xfd, 0x09, 0x74, 0x80, 0x23, + 0xd0, 0x8a, 0xf3, 0x91, 0xd5, 0x5b, 0x42, 0x88, 0xef, 0x4c, 0xbd, 0x05, + 0x00, 0x80, 0xce, 0x20, 0xd0, 0x01, 0x8e, 0xc0, 0xf8, 0xc8, 0x9a, 0x9b, + 0x42, 0x0c, 0xe2, 0x1c, 0x00, 0x80, 0x19, 0x23, 0xd0, 0x01, 0x0e, 0xd3, + 0xf2, 0xd1, 0xf3, 0xaf, 0x08, 0x31, 0x5e, 0x96, 0x7a, 0x07, 0x00, 0x00, + 0x9d, 0x45, 0xa0, 0x03, 0x1c, 0x86, 0xf1, 0xc5, 0xe7, 0x9f, 0x9b, 0xc5, + 0x78, 0x7d, 0xea, 0x1d, 0x00, 0x00, 0x74, 0x1e, 0x81, 0x0e, 0x70, 0x88, + 0x96, 0x0d, 0xaf, 0x3d, 0x26, 0x2b, 0xc7, 0x3f, 0xcb, 0x1f, 0x96, 0x52, + 0x6f, 0x01, 0x00, 0xa0, 0xf3, 0x08, 0x74, 0x80, 0x43, 0xf0, 0xd4, 0x9b, + 0xc2, 0xad, 0xf9, 0xd3, 0xfc, 0xe1, 0xc2, 0xd4, 0x5b, 0x00, 0x00, 0xe8, + 0x4c, 0x02, 0x1d, 0xe0, 0x10, 0x8c, 0xd5, 0xd6, 0xbc, 0x3b, 0xbf, 0x7b, + 0x6d, 0x69, 0xfe, 0xbc, 0x50, 0x5d, 0x3a, 0x12, 0x4a, 0xbd, 0xd5, 0xbc, + 0xda, 0x63, 0x68, 0x4c, 0x4d, 0x87, 0xe9, 0x27, 0xf6, 0x86, 0xfd, 0x3b, + 0x1e, 0x0e, 0x8d, 0x89, 0xfd, 0xa9, 0x67, 0x02, 0x00, 0xd0, 0xc6, 0x04, + 0x3a, 0xc0, 0x8b, 0x18, 0x1d, 0x7d, 0xdd, 0xd8, 0x82, 0x13, 0x4f, 0xb8, + 0xa9, 0x32, 0xba, 0x30, 0x34, 0x26, 0xa7, 0x7e, 0xe2, 0x9f, 0xc5, 0x6a, + 0x16, 0xb2, 0xe1, 0xf9, 0xa1, 0x32, 0xb2, 0x20, 0xc4, 0x72, 0x29, 0x4c, + 0x3e, 0xfc, 0x78, 0x98, 0xf8, 0xee, 0x7d, 0x21, 0x34, 0x1a, 0x89, 0xd6, + 0x02, 0x00, 0xd0, 0xae, 0x04, 0x3a, 0xc0, 0x0b, 0x58, 0x79, 0xe1, 0x85, + 0x95, 0xd1, 0x9f, 0x3d, 0xeb, 0x1f, 0xeb, 0xfb, 0x0e, 0x54, 0x9e, 0x19, + 0xe7, 0x3f, 0x21, 0x0f, 0xf2, 0xe6, 0x3f, 0x2f, 0x0f, 0xf6, 0x87, 0xa1, + 0xb3, 0x5f, 0x16, 0xa6, 0x9e, 0x9c, 0x08, 0x7b, 0xbf, 0x75, 0x77, 0x08, + 0xf5, 0xfa, 0x9c, 0x6d, 0x05, 0x00, 0xa0, 0xbd, 0x09, 0x74, 0x80, 0xe7, + 0x91, 0xad, 0xda, 0x54, 0x7e, 0xc5, 0x29, 0x27, 0xff, 0x6d, 0x7d, 0xdf, + 0x83, 0x87, 0xf5, 0x73, 0xe7, 0xcd, 0x97, 0xbd, 0x37, 0x5f, 0x02, 0x3f, + 0x74, 0xd6, 0xa9, 0x61, 0xf2, 0xb1, 0x3d, 0x61, 0xe2, 0x3b, 0xf7, 0x78, + 0x46, 0x1d, 0x00, 0x80, 0x17, 0x25, 0xd0, 0x01, 0x9e, 0xc7, 0x19, 0x2b, + 0x17, 0x7f, 0xe8, 0xc0, 0xfd, 0xbb, 0x56, 0x1e, 0xe9, 0xaf, 0x6f, 0x86, + 0x7a, 0x79, 0x5e, 0x6f, 0x18, 0xfa, 0xe9, 0x97, 0x85, 0xc9, 0x87, 0x9f, + 0x08, 0x13, 0x77, 0xde, 0x3b, 0x93, 0xf3, 0x00, 0x00, 0xe8, 0x30, 0x02, + 0x1d, 0xe0, 0x39, 0x9c, 0xb1, 0x7e, 0xeb, 0x6b, 0xb2, 0xe9, 0xc6, 0x7b, + 0xa6, 0x1e, 0xdb, 0x73, 0xd4, 0x1f, 0xab, 0x31, 0x39, 0xfd, 0xd4, 0x4b, + 0xdf, 0x5f, 0x7d, 0x5a, 0x98, 0xdc, 0xfd, 0x58, 0x98, 0xf8, 0xde, 0x7d, + 0x33, 0xb0, 0x10, 0x00, 0x80, 0x4e, 0x23, 0xd0, 0x01, 0x9e, 0x43, 0x16, + 0xc3, 0x07, 0x26, 0x1f, 0x7a, 0x6c, 0x46, 0x5f, 0x9a, 0xde, 0xfa, 0x19, + 0xf5, 0xf9, 0xf3, 0x5a, 0xa1, 0x7e, 0x60, 0xe7, 0xc3, 0x61, 0xdf, 0x0f, + 0x1e, 0x98, 0xb1, 0x8f, 0x0d, 0x00, 0x40, 0xfb, 0x13, 0xe8, 0x00, 0xcf, + 0x70, 0xc6, 0x86, 0xad, 0x67, 0x95, 0x42, 0xf8, 0xd9, 0xa9, 0x47, 0x9f, + 0x98, 0x95, 0x8f, 0xdf, 0x0c, 0xf5, 0xca, 0xc2, 0xa1, 0x50, 0x7d, 0xf5, + 0xa2, 0x70, 0x60, 0xc7, 0x43, 0x61, 0xdf, 0x5d, 0xdb, 0x67, 0xe5, 0xf3, + 0x00, 0x00, 0xd0, 0x5e, 0x04, 0x3a, 0xc0, 0x33, 0x64, 0x8d, 0xc6, 0x3f, + 0x9f, 0x9e, 0xd8, 0x1f, 0x1a, 0x07, 0x5e, 0xe0, 0x5d, 0xdb, 0x67, 0x40, + 0x2b, 0xd4, 0x87, 0xe7, 0x87, 0xea, 0x92, 0xe1, 0xb0, 0x7f, 0xdb, 0xae, + 0xb0, 0xff, 0xde, 0x07, 0x67, 0xf5, 0xf3, 0x01, 0x00, 0x50, 0x6c, 0x02, + 0x1d, 0xe0, 0x19, 0x62, 0x8c, 0xe7, 0x4e, 0x3f, 0xbe, 0x77, 0xce, 0x3e, + 0x5f, 0x33, 0xd4, 0xab, 0xa3, 0x0b, 0x43, 0xcf, 0x78, 0x2d, 0xec, 0xcb, + 0x23, 0xfd, 0x40, 0x1e, 0xeb, 0x00, 0x00, 0x74, 0x1f, 0x81, 0x0e, 0x70, + 0x90, 0x57, 0xad, 0xdb, 0x3a, 0x5a, 0xce, 0xc2, 0x49, 0x53, 0x8f, 0x1f, + 0xfd, 0x9b, 0xc3, 0x1d, 0xae, 0x66, 0xa8, 0xf7, 0x2c, 0x1d, 0x0e, 0xbd, + 0xc7, 0x2c, 0x0e, 0x13, 0x77, 0xef, 0x08, 0x93, 0xdb, 0x77, 0xcf, 0xf9, + 0x06, 0x00, 0x00, 0xd2, 0x11, 0xe8, 0x00, 0x07, 0x29, 0x67, 0xf5, 0x97, + 0x37, 0xa6, 0x1a, 0xa1, 0xb1, 0x7f, 0x32, 0xd9, 0x86, 0x66, 0xa8, 0xf7, + 0x8e, 0x8f, 0x84, 0xbe, 0x63, 0xf3, 0x50, 0xff, 0xe1, 0xf6, 0x30, 0xb9, + 0xf3, 0xe1, 0x64, 0x5b, 0x00, 0x00, 0x98, 0x3b, 0x02, 0x1d, 0xe0, 0x20, + 0xf5, 0x7a, 0x38, 0xbe, 0xbe, 0x67, 0x5f, 0xea, 0x19, 0x2d, 0xcd, 0xeb, + 0xa8, 0xf7, 0x1e, 0x33, 0x1a, 0x7a, 0x8f, 0x5f, 0x1a, 0x26, 0xbe, 0x77, + 0x7f, 0x98, 0x6a, 0xbe, 0xab, 0x3c, 0x00, 0x00, 0x1d, 0x4b, 0xa0, 0x03, + 0x1c, 0x24, 0x66, 0x71, 0xa4, 0xbe, 0xb7, 0x18, 0x81, 0xfe, 0x63, 0x79, + 0xa8, 0xf7, 0xbd, 0x64, 0x69, 0x08, 0x27, 0x2e, 0x6b, 0x5d, 0x43, 0x7d, + 0xea, 0xe1, 0xc7, 0x53, 0x2f, 0x02, 0x00, 0x60, 0x16, 0x08, 0x74, 0x80, + 0x83, 0xc4, 0x10, 0xe7, 0xd5, 0xf7, 0x1d, 0x48, 0x3d, 0xe3, 0xb9, 0x4d, + 0xe7, 0xa1, 0x7e, 0xc2, 0x78, 0x2b, 0xd4, 0xf7, 0xde, 0x79, 0x5f, 0x98, + 0x7e, 0x6c, 0x76, 0x2e, 0x03, 0x07, 0x00, 0x40, 0x1a, 0x02, 0x1d, 0xe0, + 0x27, 0x34, 0xaa, 0x85, 0x0d, 0xf4, 0xa6, 0x46, 0xa3, 0x75, 0xeb, 0x3f, + 0x79, 0x59, 0xeb, 0xb8, 0xf7, 0xce, 0x7b, 0xc2, 0x5c, 0xbe, 0xe3, 0x3c, + 0x00, 0x00, 0xb3, 0x47, 0xa0, 0x03, 0x1c, 0xa4, 0xb1, 0x7f, 0x72, 0xb0, + 0xf9, 0xb3, 0xdf, 0x85, 0xd7, 0x0c, 0xf5, 0x5c, 0xff, 0x4b, 0x8f, 0x6d, + 0x3d, 0xde, 0xfb, 0xed, 0x3c, 0xd4, 0xf7, 0x4c, 0x24, 0x1e, 0x05, 0x00, + 0xc0, 0xd1, 0x10, 0xe8, 0x00, 0x07, 0x99, 0x7c, 0x7c, 0xcf, 0xb2, 0xd4, + 0x1b, 0x0e, 0xcb, 0x8f, 0x42, 0xfd, 0xe5, 0xc7, 0x85, 0xc6, 0x74, 0x3d, + 0xec, 0xf9, 0xd6, 0xdd, 0xa1, 0x31, 0xb1, 0x3f, 0xf1, 0x28, 0x00, 0x00, + 0x8e, 0x84, 0x40, 0x07, 0x38, 0x48, 0x63, 0xdf, 0x81, 0xd1, 0xd4, 0x1b, + 0x8e, 0x48, 0xbd, 0x11, 0x62, 0x8c, 0x61, 0xe0, 0xf4, 0x13, 0x42, 0x63, + 0x6a, 0x2a, 0xec, 0xf9, 0x66, 0x1e, 0xea, 0xfb, 0x0b, 0xfc, 0x52, 0x7d, + 0x00, 0x00, 0x9e, 0x45, 0xa0, 0x03, 0x1c, 0xa4, 0x7e, 0x60, 0x7a, 0x30, + 0xf5, 0x86, 0xa3, 0x52, 0xaf, 0x87, 0x98, 0x65, 0x61, 0xe0, 0x15, 0x27, + 0x86, 0xc6, 0xe4, 0xe4, 0x53, 0xa1, 0x7e, 0x20, 0xdd, 0x35, 0xdd, 0x01, + 0x00, 0x38, 0x74, 0x02, 0x1d, 0xe0, 0x60, 0xd3, 0xf5, 0x81, 0xd4, 0x13, + 0x66, 0x44, 0x33, 0xd4, 0x4b, 0xa5, 0x30, 0xf0, 0xaa, 0x93, 0x42, 0xf3, + 0x4d, 0xef, 0xf6, 0x7c, 0xeb, 0xae, 0x10, 0x26, 0xdb, 0xe0, 0x67, 0xeb, + 0x01, 0x00, 0xba, 0x98, 0x40, 0x07, 0xf8, 0x09, 0xf5, 0x9e, 0xd4, 0x0b, + 0x66, 0xd4, 0x74, 0x3d, 0x64, 0x95, 0x72, 0x18, 0x3c, 0xe3, 0x94, 0x50, + 0x9f, 0xd8, 0xd7, 0x7a, 0x46, 0xbd, 0x79, 0xb9, 0x36, 0x00, 0x00, 0x8a, + 0x47, 0xa0, 0x03, 0x1c, 0xac, 0x1e, 0x4a, 0xa9, 0x27, 0xcc, 0x8a, 0x3c, + 0xca, 0xb3, 0x6a, 0x25, 0x0c, 0xad, 0x3c, 0x25, 0x4c, 0x3d, 0x39, 0x11, + 0xf6, 0x7e, 0xeb, 0xee, 0xd6, 0xb3, 0xec, 0x00, 0x00, 0x14, 0x87, 0x40, + 0x07, 0x38, 0x48, 0x23, 0x84, 0x2c, 0xf5, 0x86, 0xd9, 0xd4, 0xbc, 0x84, + 0x5c, 0xa9, 0xb7, 0x1a, 0x86, 0xce, 0x3a, 0x35, 0x4c, 0x3d, 0xbe, 0xa7, + 0x75, 0x79, 0xb6, 0x1f, 0xbd, 0x13, 0x3c, 0x00, 0x00, 0x69, 0x09, 0x74, + 0x80, 0x9f, 0x14, 0x53, 0x0f, 0x98, 0x0b, 0xad, 0x50, 0xef, 0xef, 0x0d, + 0x43, 0x67, 0xbf, 0x2c, 0x4c, 0x3e, 0xfa, 0x64, 0x98, 0xf8, 0xce, 0x3d, + 0xa9, 0x27, 0x01, 0x00, 0x74, 0x3d, 0x81, 0x0e, 0x70, 0xb0, 0x7a, 0xa3, + 0x2b, 0x02, 0xfd, 0x47, 0x9a, 0xa1, 0x5e, 0x1e, 0xe8, 0x0b, 0x43, 0xaf, + 0x3e, 0x2d, 0x4c, 0xee, 0x7e, 0x2c, 0x4c, 0x7c, 0xef, 0xbe, 0xd4, 0x93, + 0x00, 0x00, 0xba, 0x96, 0x40, 0x07, 0x20, 0x34, 0x26, 0xa7, 0x42, 0x79, + 0xfe, 0xbc, 0x56, 0xa8, 0x1f, 0xd8, 0xf5, 0x48, 0xd8, 0xf7, 0xfd, 0x6d, + 0xa9, 0x27, 0x01, 0x00, 0x74, 0x1d, 0x81, 0x0e, 0x70, 0x90, 0x18, 0x5b, + 0x3f, 0x87, 0xde, 0xb5, 0x9a, 0xa1, 0x5e, 0x59, 0x30, 0x18, 0xaa, 0xcd, + 0x50, 0xdf, 0xf1, 0x50, 0xd8, 0x77, 0xd7, 0xf6, 0xd4, 0x93, 0x00, 0x00, + 0xba, 0x86, 0x40, 0x07, 0x38, 0x48, 0x23, 0xc6, 0x6e, 0xee, 0xf3, 0x1f, + 0x6b, 0x85, 0xfa, 0xf0, 0xfc, 0x50, 0x5d, 0x32, 0x1c, 0xf6, 0x3f, 0xb0, + 0x2b, 0xec, 0xbf, 0xe7, 0xc1, 0xd4, 0x93, 0x00, 0x00, 0x3a, 0x9e, 0x40, + 0x07, 0x38, 0x48, 0xa3, 0xd1, 0x70, 0xed, 0xb1, 0x83, 0x34, 0x43, 0xbd, + 0x5a, 0x5b, 0x18, 0x7a, 0xc6, 0x6a, 0x61, 0xdf, 0xfd, 0x3b, 0xc3, 0x81, + 0xfb, 0x76, 0xa6, 0x9e, 0x04, 0x00, 0xd0, 0xb1, 0x04, 0x3a, 0xc0, 0x41, + 0xb2, 0xe8, 0xea, 0xe0, 0xcf, 0xa5, 0x19, 0xea, 0x3d, 0x8b, 0x17, 0x85, + 0xde, 0x65, 0xa3, 0x61, 0xe2, 0xee, 0x1d, 0x61, 0x72, 0xfb, 0xee, 0xd4, + 0x93, 0x00, 0x00, 0x3a, 0x8e, 0x40, 0x07, 0x38, 0x58, 0x8c, 0xfa, 0xfc, + 0x05, 0x34, 0x43, 0xbd, 0x77, 0x7c, 0x24, 0xf4, 0x1d, 0xbb, 0x38, 0x4c, + 0xdc, 0xb5, 0x3d, 0x4c, 0x3e, 0xf8, 0x70, 0xea, 0x49, 0x00, 0x00, 0x1d, + 0x43, 0xa0, 0x03, 0x1c, 0xcc, 0xcf, 0xa0, 0x1f, 0x92, 0xe6, 0xe5, 0xd9, + 0x7a, 0x97, 0x8f, 0x86, 0xbe, 0x97, 0x2c, 0x0d, 0x7b, 0x7f, 0xb0, 0x2d, + 0x4c, 0xed, 0x7a, 0x34, 0xf5, 0x24, 0x00, 0x80, 0xb6, 0x27, 0xd0, 0x01, + 0x38, 0x62, 0xcd, 0x50, 0xef, 0x3b, 0x76, 0x49, 0x08, 0xc7, 0x8f, 0xb7, + 0xae, 0xa1, 0x3e, 0xf5, 0xf0, 0xe3, 0xa9, 0x27, 0x01, 0x00, 0xb4, 0x2d, + 0x81, 0x0e, 0xc0, 0xd1, 0x9b, 0xce, 0x43, 0xfd, 0x84, 0xf1, 0x10, 0x4e, + 0x5c, 0x16, 0xf6, 0x7e, 0xf7, 0xbe, 0x30, 0xfd, 0xe8, 0x13, 0xa9, 0x17, + 0x01, 0x6d, 0xa0, 0xcb, 0xaf, 0x6c, 0x09, 0xf0, 0x2c, 0x02, 0x1d, 0x80, + 0x99, 0xd1, 0x68, 0xb4, 0x6e, 0xfd, 0x27, 0x2d, 0x6b, 0x1d, 0xf7, 0xde, + 0x79, 0x6f, 0x98, 0x7e, 0x7c, 0x4f, 0xe2, 0x51, 0x40, 0x91, 0x35, 0x1a, + 0x8d, 0xfd, 0xa9, 0x37, 0x00, 0x14, 0x89, 0x40, 0x07, 0x60, 0x66, 0x35, + 0x9e, 0x7a, 0x42, 0xac, 0xff, 0xa5, 0xc7, 0xb4, 0x1e, 0xef, 0xfd, 0xf6, + 0x3d, 0x61, 0x7a, 0xcf, 0x44, 0xe2, 0x51, 0x40, 0x21, 0xc5, 0xb0, 0x37, + 0xf5, 0x04, 0x80, 0x22, 0x11, 0xe8, 0x00, 0xcc, 0x8e, 0x1f, 0x85, 0xfa, + 0xcb, 0x8f, 0x0b, 0x8d, 0xe9, 0x7a, 0x1e, 0xea, 0x77, 0x87, 0xfa, 0x5e, + 0x4f, 0x96, 0x01, 0x3f, 0xc1, 0x35, 0x1b, 0x01, 0x0e, 0x22, 0xd0, 0x01, + 0x98, 0x5d, 0xf5, 0x46, 0x88, 0x31, 0x86, 0x79, 0xa7, 0x9d, 0x10, 0x1a, + 0x53, 0x53, 0x61, 0x4f, 0x1e, 0xea, 0x8d, 0x89, 0x03, 0xa9, 0x57, 0x01, + 0x85, 0x10, 0xef, 0x4b, 0xbd, 0x00, 0xa0, 0x48, 0x04, 0x3a, 0x00, 0x73, + 0xa3, 0x5e, 0x0f, 0x31, 0xcb, 0xc2, 0xc0, 0xe9, 0x27, 0x86, 0xc6, 0xe4, + 0x64, 0xd8, 0xf3, 0xcd, 0x3c, 0xd4, 0x0f, 0x4c, 0xa6, 0x5e, 0x05, 0x24, + 0x54, 0x0f, 0xf5, 0x1f, 0xa6, 0xde, 0x00, 0x50, 0x24, 0x02, 0x1d, 0x80, + 0xb9, 0xd5, 0x0c, 0xf5, 0x52, 0x29, 0x0c, 0xbc, 0xea, 0xa4, 0xd0, 0xd8, + 0xdf, 0x0c, 0xf5, 0xbb, 0xf2, 0x60, 0x9f, 0x4a, 0xbd, 0x0a, 0x48, 0xa0, + 0x34, 0x3d, 0xf5, 0x7f, 0x52, 0x6f, 0x00, 0x28, 0x12, 0x81, 0x0e, 0x40, + 0x1a, 0xd3, 0x79, 0xa8, 0x97, 0xf3, 0x50, 0x3f, 0xe3, 0xa5, 0xa1, 0x3e, + 0xb1, 0xaf, 0xf5, 0x8c, 0x7a, 0xf3, 0x72, 0x6d, 0x40, 0x77, 0x68, 0x84, + 0xb0, 0xfb, 0xfe, 0xdd, 0x5f, 0xf4, 0x12, 0x77, 0x80, 0x83, 0x08, 0x74, + 0x00, 0xd2, 0xca, 0xa3, 0x3c, 0xab, 0x56, 0xc2, 0xd0, 0xca, 0x53, 0xc2, + 0xd4, 0x93, 0x13, 0x61, 0xef, 0xb7, 0xee, 0x6e, 0x3d, 0xcb, 0x0e, 0x74, + 0xb6, 0x18, 0x1a, 0xff, 0x23, 0xf5, 0x06, 0x80, 0xa2, 0x11, 0xe8, 0x00, + 0x14, 0x42, 0x63, 0x6a, 0x3a, 0x94, 0x7a, 0xab, 0x61, 0xe8, 0xac, 0x53, + 0xc3, 0xd4, 0x13, 0x7b, 0x9f, 0x0a, 0xf5, 0xa7, 0xdf, 0x09, 0x1e, 0xe8, + 0x3c, 0x8d, 0x10, 0xff, 0x26, 0xf5, 0x06, 0x80, 0xa2, 0x11, 0xe8, 0x00, + 0x14, 0x4a, 0x2b, 0xd4, 0xfb, 0x7a, 0xc2, 0xd0, 0xd9, 0x2f, 0x0b, 0x93, + 0x8f, 0x3e, 0x19, 0x26, 0xbe, 0x73, 0x4f, 0xea, 0x49, 0xc0, 0x2c, 0x98, + 0x3e, 0x50, 0xff, 0x7c, 0xea, 0x0d, 0x00, 0x45, 0x23, 0xd0, 0x01, 0x28, + 0xa4, 0x66, 0xa8, 0x97, 0x07, 0xfa, 0xc2, 0xd0, 0x4f, 0xbf, 0x3c, 0x4c, + 0x3e, 0xfc, 0x78, 0x98, 0xf8, 0xae, 0x1f, 0x55, 0x85, 0x0e, 0xf2, 0x8d, + 0xed, 0x8f, 0xdc, 0xe2, 0x1d, 0xdc, 0x01, 0x9e, 0x41, 0xa0, 0x03, 0x50, + 0x68, 0xad, 0x50, 0x1f, 0x9a, 0x17, 0x86, 0x5e, 0x7d, 0x5a, 0x38, 0xb0, + 0xeb, 0xd1, 0xb0, 0xef, 0xfb, 0xf7, 0xa7, 0x9e, 0x04, 0x1c, 0xa5, 0x46, + 0x23, 0x7c, 0x3a, 0xf5, 0x06, 0x80, 0x22, 0x12, 0xe8, 0x00, 0xb4, 0x85, + 0xe6, 0xa5, 0xd8, 0x2a, 0x0b, 0x06, 0x42, 0xb5, 0x19, 0xea, 0x3b, 0x1e, + 0x0e, 0xfb, 0xee, 0x7a, 0x20, 0xf5, 0x24, 0xe0, 0xc8, 0x4c, 0x4d, 0xc6, + 0xc9, 0x3f, 0x49, 0x3d, 0x02, 0xa0, 0x88, 0x04, 0x3a, 0x00, 0x6d, 0xa5, + 0x15, 0xea, 0xc3, 0x43, 0xa1, 0xba, 0x64, 0x51, 0xd8, 0xff, 0xc0, 0xee, + 0xb0, 0xff, 0x9e, 0x1d, 0xa9, 0x27, 0x01, 0x87, 0xa5, 0x71, 0xcb, 0x83, + 0x0f, 0x7e, 0x61, 0x7b, 0xea, 0x15, 0x00, 0x45, 0x24, 0xd0, 0x01, 0x68, + 0x4b, 0xcd, 0x50, 0xaf, 0xd6, 0x16, 0x84, 0x9e, 0xb1, 0x91, 0xb0, 0xef, + 0xfe, 0x9d, 0xe1, 0xc0, 0x7d, 0x3b, 0x53, 0x4f, 0x02, 0x0e, 0x41, 0x63, + 0x2a, 0xfe, 0x41, 0xea, 0x0d, 0x00, 0x45, 0x25, 0xd0, 0x01, 0x68, 0x6b, + 0xcd, 0x50, 0xef, 0x59, 0xbc, 0x28, 0xf4, 0x2e, 0x1b, 0x0d, 0xfb, 0xee, + 0xde, 0x11, 0x0e, 0x6c, 0xdf, 0x9d, 0x7a, 0x12, 0xf0, 0x3c, 0x1a, 0x21, + 0xfc, 0xdd, 0xfd, 0x0f, 0x7d, 0xf6, 0x7f, 0xa6, 0xde, 0x01, 0x50, 0x54, + 0x02, 0x1d, 0x80, 0x8e, 0xd0, 0x0a, 0xf5, 0xf1, 0x91, 0xd0, 0x7b, 0xec, + 0xe2, 0x30, 0x71, 0xf7, 0xf6, 0x30, 0xb9, 0xe3, 0xe1, 0xd4, 0x93, 0x80, + 0x67, 0x88, 0x8d, 0x70, 0x75, 0xea, 0x0d, 0x00, 0x45, 0x26, 0xd0, 0x01, + 0xe8, 0x28, 0xcd, 0x77, 0x7d, 0x6f, 0x3e, 0x9b, 0xde, 0x77, 0xdc, 0xd2, + 0xb0, 0xf7, 0x07, 0xdb, 0xc2, 0xd4, 0xae, 0x47, 0x53, 0x4f, 0x02, 0x9e, + 0xf2, 0xa5, 0xfb, 0x76, 0x7e, 0xf6, 0xcb, 0xa9, 0x47, 0x00, 0x14, 0x99, + 0x40, 0x07, 0xa0, 0x23, 0x35, 0x43, 0xbd, 0xef, 0xd8, 0x25, 0x21, 0x1c, + 0x3f, 0x16, 0x26, 0xbe, 0x77, 0x7f, 0x98, 0x7a, 0xf8, 0xf1, 0xd4, 0x93, + 0xa0, 0x9b, 0x4d, 0xd6, 0xeb, 0xf5, 0xcb, 0x52, 0x8f, 0x00, 0x28, 0x3a, + 0x81, 0x0e, 0x40, 0x67, 0x9b, 0xae, 0x87, 0xbe, 0x3c, 0xd2, 0xc3, 0x49, + 0xcb, 0xc2, 0xc4, 0x77, 0xef, 0x0b, 0x53, 0x8f, 0x3c, 0x91, 0x7a, 0x11, + 0x74, 0x9d, 0x46, 0x08, 0x1f, 0xde, 0xb6, 0xeb, 0x96, 0x6f, 0xa7, 0xde, + 0x01, 0x50, 0x74, 0x02, 0x1d, 0x80, 0xee, 0xd0, 0x0c, 0xf5, 0x13, 0x97, + 0xb5, 0x1e, 0xee, 0xbd, 0xf3, 0xde, 0x30, 0xfd, 0xf8, 0x9e, 0xc4, 0x83, + 0xa0, 0x5b, 0x34, 0xbe, 0xfd, 0xc0, 0xae, 0x87, 0xae, 0x4d, 0xbd, 0x02, + 0xa0, 0x1d, 0x08, 0x74, 0x00, 0xba, 0x47, 0xa3, 0xd1, 0xba, 0xeb, 0x7f, + 0xe9, 0x31, 0xcd, 0x43, 0xd8, 0xfb, 0x9d, 0x3c, 0xd4, 0x9f, 0xd8, 0x9b, + 0x76, 0x13, 0x74, 0xb6, 0x03, 0xf5, 0x46, 0xfd, 0x6d, 0xf5, 0xfa, 0xdf, + 0x4e, 0xa4, 0x1e, 0x02, 0xd0, 0x0e, 0x04, 0x3a, 0x00, 0xdd, 0xe7, 0x47, + 0xa1, 0x7e, 0xca, 0xb1, 0xad, 0xc7, 0x7b, 0xbe, 0x75, 0x4f, 0xa8, 0xef, + 0xd5, 0x0f, 0x30, 0xd3, 0x1a, 0xf5, 0xc6, 0x15, 0xdb, 0x76, 0xdd, 0xfa, + 0xd5, 0xd4, 0x3b, 0x00, 0xda, 0x85, 0x40, 0x07, 0xa0, 0x7b, 0x3d, 0x1d, + 0xea, 0xf3, 0x4e, 0x3b, 0xae, 0xf5, 0xa6, 0x72, 0x7b, 0xbe, 0x7d, 0x77, + 0x68, 0x4c, 0x1c, 0x48, 0x3c, 0x0a, 0x3a, 0x45, 0xe3, 0x93, 0xf7, 0xef, + 0xfa, 0xdc, 0xc7, 0x52, 0xaf, 0x00, 0x68, 0x27, 0x02, 0x1d, 0x00, 0xea, + 0x8d, 0x10, 0xb3, 0x2c, 0x0c, 0x9c, 0x7e, 0x62, 0xeb, 0x7a, 0xea, 0x7b, + 0xbe, 0x95, 0x87, 0xfa, 0x7e, 0xa1, 0x0e, 0x47, 0xaa, 0x11, 0xc2, 0xff, + 0xfb, 0xc0, 0xae, 0xbb, 0xde, 0x99, 0x7a, 0x07, 0x40, 0xbb, 0x11, 0xe8, + 0x00, 0xf0, 0x23, 0xf5, 0x7a, 0x88, 0xa5, 0x3c, 0xd4, 0x5f, 0x99, 0x87, + 0xfa, 0xfe, 0xc9, 0xb0, 0xe7, 0x9b, 0x77, 0xb5, 0x82, 0x1d, 0x38, 0x74, + 0x79, 0x9c, 0xff, 0xed, 0x81, 0xc6, 0xc3, 0xbf, 0x5a, 0xaf, 0xdf, 0xe1, + 0xff, 0xcb, 0x05, 0x70, 0x98, 0x04, 0x3a, 0x00, 0x3c, 0xd3, 0x74, 0x1e, + 0xea, 0xe5, 0x52, 0x18, 0x38, 0xe3, 0xa5, 0xa1, 0x3e, 0xb1, 0xaf, 0xf5, + 0x8c, 0x7a, 0x98, 0x9a, 0x4e, 0xbd, 0x0a, 0xda, 0xc1, 0xdf, 0x4c, 0xed, + 0x7a, 0xe2, 0xfc, 0x9d, 0xf5, 0xff, 0xf1, 0x64, 0xea, 0x21, 0x00, 0xed, + 0x48, 0xa0, 0x03, 0xc0, 0xf3, 0x99, 0x9e, 0x0e, 0x59, 0xb5, 0x12, 0x86, + 0x56, 0x9c, 0x12, 0xa6, 0xf7, 0x3c, 0x1d, 0xea, 0xd3, 0x42, 0x1d, 0x9e, + 0x4b, 0x23, 0x34, 0x3e, 0xf1, 0xc0, 0xae, 0xbb, 0x2e, 0xf4, 0xcc, 0x39, + 0xc0, 0x91, 0x13, 0xe8, 0x00, 0xf0, 0x22, 0x9a, 0x6f, 0x20, 0x97, 0xf5, + 0xe4, 0xa1, 0xbe, 0xf2, 0x94, 0x30, 0xf5, 0xc4, 0xde, 0xb0, 0xb7, 0x19, + 0xea, 0x4f, 0xbf, 0xc1, 0x1c, 0xd0, 0xd8, 0x93, 0xff, 0xdb, 0x70, 0xf9, + 0xfd, 0x0f, 0x7e, 0xee, 0xdf, 0xa6, 0x5e, 0x02, 0xd0, 0xee, 0x04, 0x3a, + 0x00, 0x1c, 0xa2, 0x66, 0xa8, 0x97, 0xfa, 0x7a, 0xc2, 0xd0, 0xd9, 0x2f, + 0x0b, 0x93, 0x8f, 0x3e, 0x19, 0x26, 0xbe, 0x73, 0x4f, 0xea, 0x49, 0x90, + 0xda, 0x17, 0x27, 0x0f, 0x4c, 0x5e, 0xba, 0xe3, 0x91, 0x2f, 0x7e, 0x3f, + 0xf5, 0x10, 0x80, 0x4e, 0x20, 0xd0, 0x01, 0xe0, 0x30, 0x35, 0x43, 0xbd, + 0x3c, 0xd0, 0x17, 0x86, 0x7e, 0xfa, 0xb4, 0x30, 0xf9, 0xf0, 0xe3, 0x61, + 0xe2, 0xbb, 0xf7, 0xa6, 0x9e, 0x04, 0x73, 0xab, 0x11, 0xbe, 0x56, 0x8f, + 0xe1, 0x77, 0xb7, 0x3d, 0xf8, 0xd9, 0x2f, 0xa6, 0x9e, 0x02, 0xd0, 0x49, + 0x04, 0x3a, 0x00, 0x1c, 0xa1, 0xc6, 0xd4, 0x54, 0x28, 0x0f, 0xf5, 0x87, + 0xa1, 0x57, 0xe7, 0xa1, 0xbe, 0xeb, 0xd1, 0x30, 0xf1, 0xfd, 0xfb, 0x53, + 0x4f, 0x82, 0x59, 0xd6, 0xf8, 0xc7, 0x7a, 0xbd, 0xb1, 0x71, 0xfb, 0x43, + 0x9f, 0xff, 0x4c, 0x3d, 0x7f, 0x90, 0x7a, 0x0d, 0x40, 0xa7, 0x11, 0xe8, + 0x00, 0x70, 0x94, 0x9a, 0x97, 0x62, 0x2b, 0x2f, 0x18, 0x68, 0x85, 0xfa, + 0x81, 0x07, 0x1f, 0x0e, 0xfb, 0x7e, 0xf8, 0x40, 0xea, 0x49, 0x30, 0xb3, + 0x1a, 0xe1, 0xbb, 0x79, 0x8d, 0x6f, 0x7a, 0x60, 0xf7, 0xad, 0x7f, 0x9e, + 0x87, 0x79, 0x3d, 0xf5, 0x1c, 0x80, 0x4e, 0x25, 0xd0, 0x01, 0x60, 0x86, + 0x34, 0x43, 0xbd, 0xb2, 0x68, 0x28, 0x54, 0x17, 0x2f, 0x0a, 0xfb, 0xb7, + 0xef, 0x0e, 0xfb, 0xef, 0xde, 0x91, 0x7a, 0x12, 0x1c, 0xad, 0x1f, 0xd4, + 0xeb, 0x8d, 0xcd, 0xdb, 0x1f, 0xba, 0xf5, 0x53, 0x79, 0x97, 0xbb, 0x84, + 0x01, 0xc0, 0x2c, 0x13, 0xe8, 0x00, 0x30, 0xc3, 0x9a, 0xa1, 0x5e, 0x1d, + 0x59, 0x10, 0x7a, 0x96, 0x8e, 0x84, 0x7d, 0xf7, 0xef, 0x0c, 0x07, 0xee, + 0xdb, 0x99, 0x7a, 0x12, 0x1c, 0x96, 0x46, 0x08, 0xf7, 0x36, 0x1a, 0xf5, + 0xdf, 0xdb, 0xbe, 0x7b, 0xc7, 0x27, 0xea, 0xf5, 0xdb, 0x26, 0x53, 0xef, + 0x01, 0xe8, 0x16, 0x02, 0x1d, 0x00, 0x66, 0x49, 0x33, 0xd4, 0x7b, 0x16, + 0x2f, 0x0a, 0xbd, 0xe3, 0xa3, 0x61, 0xdf, 0xbd, 0x0f, 0x86, 0x03, 0x0f, + 0xec, 0x4a, 0x3d, 0x09, 0x5e, 0xcc, 0xf6, 0x3c, 0xce, 0x3f, 0xf8, 0xc0, + 0xae, 0xa9, 0xad, 0xf5, 0xfa, 0xe7, 0xf7, 0xa7, 0x1e, 0x03, 0xd0, 0x6d, + 0x04, 0x3a, 0x00, 0xcc, 0xb2, 0xe6, 0x9b, 0xc9, 0xf5, 0x8c, 0x0d, 0x87, + 0xde, 0x63, 0x46, 0xc3, 0xc4, 0xdd, 0xdb, 0xc3, 0xe4, 0x8e, 0x87, 0x53, + 0x4f, 0x82, 0x67, 0xda, 0x99, 0xff, 0x49, 0xbd, 0x7e, 0xdb, 0xae, 0x87, + 0x6e, 0xae, 0xd7, 0xff, 0x76, 0x22, 0xf5, 0x18, 0x80, 0x6e, 0x25, 0xd0, + 0x01, 0x60, 0x8e, 0x34, 0x2f, 0xcf, 0xd6, 0xbb, 0x6c, 0x34, 0xf4, 0x1d, + 0xb7, 0x34, 0x4c, 0xfc, 0xf0, 0x81, 0x30, 0xb9, 0xf3, 0x91, 0xd4, 0x93, + 0xe0, 0xe1, 0x7a, 0xa8, 0xdf, 0x34, 0xbd, 0x6b, 0xcf, 0x47, 0x77, 0xd4, + 0xbf, 0xfc, 0x64, 0xea, 0x31, 0x00, 0xdd, 0x4e, 0xa0, 0x03, 0xc0, 0x1c, + 0x6b, 0x85, 0xfa, 0x31, 0x8b, 0x43, 0xef, 0x4b, 0xf2, 0x50, 0xff, 0xfe, + 0xb6, 0x30, 0xf5, 0xd0, 0x63, 0xa9, 0x27, 0xd1, 0x7d, 0x1e, 0x6b, 0x34, + 0x1a, 0x7f, 0x38, 0x31, 0xbd, 0xef, 0xc3, 0x0f, 0x3d, 0xf4, 0x25, 0x7f, + 0x00, 0x01, 0x0a, 0x42, 0xa0, 0x03, 0x40, 0x2a, 0xd3, 0xf5, 0xd0, 0x97, + 0x47, 0x7a, 0x38, 0x71, 0x3c, 0x4c, 0x7c, 0xf7, 0xbe, 0x30, 0xf5, 0xc8, + 0x13, 0xa9, 0x17, 0xd1, 0xf9, 0x9e, 0x0c, 0x8d, 0xf0, 0xd1, 0x27, 0x27, + 0x0f, 0xdc, 0xf4, 0xc8, 0x23, 0x5f, 0xf4, 0xb3, 0x16, 0x00, 0x05, 0x23, + 0xd0, 0x01, 0x20, 0xb5, 0x66, 0xa8, 0x9f, 0xb8, 0xac, 0xf5, 0x70, 0xef, + 0x9d, 0xf7, 0x86, 0xe9, 0xc7, 0xf7, 0x24, 0x1e, 0x44, 0x07, 0x9a, 0x68, + 0x34, 0x1a, 0x37, 0xd7, 0xf7, 0x1d, 0xb8, 0xfe, 0x81, 0xc7, 0xff, 0x8b, + 0xcb, 0x0a, 0x00, 0x14, 0x94, 0x40, 0x07, 0x80, 0x22, 0x68, 0x34, 0x5a, + 0x77, 0xfd, 0x2f, 0x3d, 0xa6, 0x75, 0xbf, 0xf7, 0x3b, 0xf7, 0x84, 0xe9, + 0x27, 0xf6, 0xa6, 0x5c, 0x44, 0x67, 0xd8, 0x9f, 0xff, 0xd9, 0xda, 0x7a, + 0x20, 0x4e, 0x7d, 0xf0, 0xc1, 0x9d, 0x5f, 0xd8, 0x9e, 0x7a, 0x0c, 0x00, + 0x2f, 0x4c, 0xa0, 0x03, 0x40, 0x91, 0xfc, 0x28, 0xd4, 0x4f, 0x39, 0xb6, + 0xf5, 0x78, 0xcf, 0xb7, 0xee, 0x09, 0xf5, 0xbd, 0xde, 0x54, 0x9b, 0xc3, + 0x36, 0x19, 0x1a, 0xe1, 0x13, 0xa1, 0x7e, 0xe0, 0xfd, 0xf7, 0xed, 0xfe, + 0xe2, 0x7d, 0xa9, 0xc7, 0x00, 0x70, 0x68, 0x04, 0x3a, 0x00, 0x14, 0xd1, + 0xd3, 0xa1, 0x3e, 0xef, 0xb4, 0xe3, 0x42, 0x63, 0xba, 0x9e, 0x87, 0xfa, + 0xdd, 0xa1, 0x31, 0xe1, 0xb2, 0xd4, 0xbc, 0xa8, 0xa9, 0xfc, 0x4f, 0xce, + 0x9f, 0x4d, 0x1f, 0xa8, 0x5f, 0xb7, 0xfd, 0x91, 0x5b, 0x7e, 0x98, 0x7a, + 0x0c, 0x00, 0x87, 0x47, 0xa0, 0x03, 0x40, 0x91, 0xd5, 0x1b, 0x21, 0xc6, + 0x18, 0x06, 0x4e, 0x3f, 0xa1, 0x75, 0x3d, 0xf5, 0x3d, 0xdf, 0xcc, 0x43, + 0x7d, 0xff, 0x81, 0xd4, 0xab, 0x28, 0x9e, 0x7a, 0x7e, 0xfb, 0xf3, 0x10, + 0xa6, 0x37, 0xdd, 0xff, 0xe0, 0xad, 0xdf, 0x4d, 0x3d, 0x06, 0x80, 0x23, + 0x23, 0xd0, 0x01, 0xa0, 0x1d, 0xd4, 0xeb, 0x21, 0x66, 0x59, 0x18, 0x78, + 0xe5, 0x89, 0x79, 0xa0, 0x4f, 0x3e, 0x15, 0xea, 0x93, 0x93, 0xa9, 0x57, + 0x91, 0x5e, 0xf3, 0xa5, 0x16, 0x9f, 0x99, 0x9e, 0x6e, 0x6c, 0x7c, 0x60, + 0xf7, 0xe7, 0xfe, 0x31, 0xf5, 0x18, 0x00, 0x8e, 0x8e, 0x40, 0x07, 0x80, + 0x76, 0x32, 0x9d, 0x87, 0x7a, 0xb9, 0x14, 0x06, 0xce, 0x38, 0x29, 0xd4, + 0x27, 0x0e, 0x84, 0x3d, 0xdf, 0xba, 0x2b, 0x84, 0xa9, 0x99, 0xbc, 0x92, + 0x47, 0x00, 0x00, 0x20, 0x00, 0x49, 0x44, 0x41, 0x54, 0xe9, 0xd4, 0xab, + 0x48, 0xa1, 0x11, 0x6e, 0x6d, 0xc4, 0xb0, 0xf1, 0xfe, 0x07, 0x3f, 0xfb, + 0xb5, 0xd4, 0x53, 0x00, 0x98, 0x19, 0x02, 0x1d, 0x00, 0xda, 0x51, 0x1e, + 0xea, 0x59, 0xb5, 0x1c, 0x86, 0x56, 0x9c, 0x12, 0xa6, 0xf7, 0xec, 0x6b, + 0xfd, 0x8c, 0x7a, 0x98, 0x16, 0xea, 0x5d, 0xe2, 0x4b, 0xd3, 0xf5, 0xe9, + 0x6b, 0x1e, 0xd8, 0x75, 0xeb, 0xdf, 0xa7, 0x1e, 0x02, 0xc0, 0xcc, 0x12, + 0xe8, 0x00, 0xd0, 0xc6, 0x1a, 0x53, 0xd3, 0x21, 0xeb, 0xa9, 0x84, 0xa1, + 0x95, 0xa7, 0x84, 0xa9, 0x27, 0xf6, 0x86, 0xbd, 0xdf, 0xbe, 0xa7, 0xf5, + 0x72, 0x78, 0x3a, 0x50, 0x23, 0xfc, 0xf7, 0x7a, 0x9c, 0xbe, 0x66, 0xdb, + 0x83, 0xb7, 0x7e, 0x25, 0xf5, 0x14, 0x00, 0x66, 0x87, 0x40, 0x07, 0x80, + 0x0e, 0xd0, 0x0c, 0xf5, 0x52, 0x5f, 0x4f, 0x18, 0x3a, 0xeb, 0xd4, 0x30, + 0xf9, 0xd8, 0x9e, 0x30, 0xf1, 0x9d, 0x7b, 0x7e, 0xfc, 0x4e, 0xf0, 0xb4, + 0xb7, 0xfc, 0xff, 0x8a, 0x7f, 0x17, 0xeb, 0xf5, 0x8d, 0xf7, 0xed, 0xba, + 0xe5, 0xaf, 0x53, 0x6f, 0x01, 0x60, 0x76, 0x09, 0x74, 0x00, 0xe8, 0x20, + 0xcd, 0x50, 0x2f, 0xcf, 0xeb, 0x0d, 0x43, 0x67, 0xbf, 0x3c, 0x4c, 0x3e, + 0xf2, 0x78, 0x98, 0xb8, 0xf3, 0xde, 0xd4, 0x93, 0x38, 0x52, 0x8d, 0xc6, + 0xed, 0xf5, 0x18, 0xae, 0xd9, 0xf6, 0xe0, 0xe7, 0xbe, 0x90, 0x7a, 0x0a, + 0x00, 0x73, 0x43, 0xa0, 0x03, 0x40, 0x07, 0x6a, 0x5e, 0x92, 0xad, 0x3c, + 0xd8, 0x1f, 0x86, 0x5e, 0x7d, 0x5a, 0x98, 0xdc, 0xfd, 0x68, 0x98, 0xf8, + 0xde, 0xfd, 0xa9, 0x27, 0x71, 0xe8, 0xee, 0xa8, 0xd7, 0xeb, 0xd7, 0x6e, + 0x7f, 0xe8, 0xf3, 0x9f, 0xc9, 0xef, 0xbd, 0x0c, 0x02, 0xa0, 0x8b, 0x08, + 0x74, 0x00, 0xe8, 0x60, 0x8d, 0xc9, 0x3c, 0xd4, 0xe7, 0x0f, 0xb4, 0x42, + 0xfd, 0xc0, 0xce, 0x87, 0xc3, 0xbe, 0x1f, 0x3c, 0x90, 0x7a, 0x12, 0xcf, + 0xab, 0xf1, 0xed, 0x10, 0xe2, 0xb5, 0xdb, 0x76, 0xdd, 0xf2, 0x97, 0x79, + 0x98, 0x7b, 0x23, 0x01, 0x80, 0x2e, 0x24, 0xd0, 0x01, 0xa0, 0x0b, 0x34, + 0x43, 0xbd, 0xb2, 0x70, 0x28, 0x54, 0x5f, 0xbd, 0x28, 0x1c, 0xd8, 0xfe, + 0x50, 0xd8, 0x77, 0xf7, 0xf6, 0xd4, 0x93, 0xf8, 0x27, 0x3f, 0xa8, 0xd7, + 0x1b, 0x9b, 0xb7, 0x3f, 0x74, 0xeb, 0xa7, 0xf2, 0x2e, 0xf7, 0x56, 0xfc, + 0x00, 0x5d, 0x4c, 0xa0, 0x03, 0x40, 0x17, 0x69, 0x85, 0xfa, 0xc8, 0xfc, + 0x50, 0x5d, 0x3a, 0x1c, 0xf6, 0xdf, 0xbf, 0x33, 0xec, 0xbf, 0x6f, 0x67, + 0xea, 0x49, 0x5d, 0xac, 0x71, 0x4f, 0x23, 0xc4, 0xf7, 0x3f, 0xb0, 0xeb, + 0x81, 0x3f, 0xa9, 0xd7, 0x6f, 0x9b, 0x4c, 0xbd, 0x06, 0x80, 0xf4, 0x04, + 0x3a, 0x00, 0x74, 0xa1, 0x66, 0xa8, 0x57, 0x17, 0x2f, 0x0a, 0x3d, 0xcb, + 0x46, 0xc3, 0xbe, 0x7b, 0x1f, 0x0c, 0x07, 0xb6, 0xed, 0x4a, 0x3d, 0xa9, + 0x9b, 0x6c, 0x0b, 0x8d, 0xf0, 0xfb, 0xdb, 0x76, 0xdf, 0xf5, 0xc7, 0xf5, + 0xfa, 0x1d, 0x07, 0x52, 0x8f, 0x01, 0xa0, 0x38, 0x04, 0x3a, 0x00, 0x74, + 0xb1, 0x66, 0xa8, 0xf7, 0x2c, 0x1d, 0x0e, 0xbd, 0xcb, 0x47, 0xc3, 0xc4, + 0xdd, 0x3b, 0xc2, 0xe4, 0x8e, 0x87, 0x52, 0x4f, 0xea, 0x64, 0x3b, 0xf3, + 0xdf, 0xf1, 0xeb, 0xb7, 0xed, 0x7a, 0xe8, 0xe6, 0x7a, 0xfd, 0x6f, 0x27, + 0x52, 0x8f, 0x01, 0xa0, 0x78, 0x04, 0x3a, 0x00, 0xd0, 0xba, 0x3c, 0x5b, + 0xef, 0xb2, 0x5a, 0xe8, 0x3b, 0x6e, 0x49, 0x98, 0xf8, 0xe1, 0xf6, 0x30, + 0xb9, 0xf3, 0xe1, 0xd4, 0x93, 0x3a, 0xc9, 0x43, 0x8d, 0x46, 0xf8, 0xd0, + 0xd4, 0xee, 0x27, 0x3e, 0xb6, 0xa3, 0xfe, 0xe5, 0x27, 0x53, 0x8f, 0x01, + 0xa0, 0xb8, 0x04, 0x3a, 0x00, 0xf0, 0x63, 0xad, 0x50, 0x3f, 0x66, 0x34, + 0xf4, 0x1e, 0xbf, 0xb4, 0x75, 0x69, 0xb6, 0xa9, 0x87, 0x1e, 0x4b, 0x3d, + 0xa9, 0x9d, 0x3d, 0x9a, 0xdf, 0x3e, 0xbc, 0xaf, 0x3e, 0xf5, 0x87, 0xbb, + 0x76, 0x7d, 0xfe, 0xf1, 0xd4, 0x63, 0x00, 0x28, 0x3e, 0x81, 0x0e, 0x00, + 0x3c, 0x5b, 0x1e, 0xea, 0x7d, 0x2f, 0x59, 0x1a, 0xc2, 0x89, 0xe3, 0x61, + 0xe2, 0xbb, 0xf7, 0x85, 0xa9, 0x47, 0x9e, 0x48, 0xbd, 0xa8, 0x9d, 0x3c, + 0x19, 0x1a, 0xe1, 0xa3, 0x4f, 0x4e, 0xd6, 0x3f, 0xf4, 0xc8, 0x23, 0xb7, + 0x3c, 0x92, 0x7a, 0x0c, 0x00, 0xed, 0x43, 0xa0, 0x03, 0x00, 0xcf, 0x6f, + 0xba, 0x1e, 0xfa, 0x4e, 0x5c, 0x16, 0x42, 0x8c, 0x61, 0xef, 0x9d, 0xf7, + 0x86, 0xe9, 0xc7, 0xbc, 0x42, 0xfb, 0x05, 0xec, 0x6d, 0x84, 0xf0, 0xb1, + 0xb0, 0x2f, 0xdc, 0x78, 0xff, 0x63, 0x9f, 0xdd, 0x9d, 0x7a, 0x0c, 0x00, + 0xed, 0x47, 0xa0, 0x03, 0x00, 0x2f, 0xac, 0xd1, 0x68, 0xdd, 0xfa, 0x4f, + 0x5e, 0xde, 0x3a, 0xee, 0xfd, 0xce, 0x3d, 0x61, 0xfa, 0x89, 0xbd, 0x89, + 0x47, 0x15, 0xca, 0xfe, 0xfc, 0xf7, 0x67, 0xeb, 0xe4, 0x44, 0xe3, 0x03, + 0x3b, 0x9e, 0xb8, 0x65, 0x47, 0xea, 0x31, 0x00, 0xb4, 0x2f, 0x81, 0x0e, + 0x00, 0x1c, 0x9a, 0x66, 0xa8, 0xe7, 0xfa, 0x4f, 0x39, 0xb6, 0xf5, 0x78, + 0xcf, 0xb7, 0xef, 0x09, 0xf5, 0x3d, 0x5d, 0xfd, 0x66, 0xe4, 0x07, 0xf2, + 0xdf, 0x88, 0x7f, 0x1f, 0xa6, 0x27, 0x7f, 0xff, 0xbe, 0xdd, 0x5f, 0xbc, + 0x2f, 0xf5, 0x18, 0x00, 0xda, 0x9f, 0x40, 0x07, 0x00, 0x0e, 0xcf, 0xd3, + 0xa1, 0x3e, 0xef, 0xe5, 0xc7, 0x85, 0xc6, 0x74, 0x3d, 0xec, 0xf9, 0xd6, + 0xdd, 0xa1, 0x31, 0xb1, 0x3f, 0xf1, 0xa8, 0x39, 0x35, 0x95, 0xff, 0x16, + 0x7c, 0xb2, 0x3e, 0x35, 0x79, 0xdd, 0x03, 0x0f, 0x7f, 0xe1, 0xee, 0xd4, + 0x63, 0x00, 0xe8, 0x1c, 0x02, 0x1d, 0x00, 0x38, 0x32, 0xf5, 0x46, 0x88, + 0x31, 0x86, 0x81, 0xd3, 0x4f, 0x08, 0x8d, 0xa9, 0xa9, 0xb0, 0xe7, 0x9b, + 0x79, 0xa8, 0xef, 0x3f, 0x90, 0x7a, 0xd5, 0x6c, 0xaa, 0xe7, 0xb7, 0x3f, + 0x0f, 0x61, 0x7a, 0xd3, 0xfd, 0x3b, 0x6f, 0xfd, 0x6e, 0xea, 0x31, 0x00, + 0x74, 0x1e, 0x81, 0x0e, 0x00, 0x1c, 0x9d, 0x7a, 0x3d, 0xc4, 0x2c, 0x0b, + 0x03, 0xaf, 0x38, 0x31, 0x34, 0x0e, 0x4c, 0x3e, 0xf5, 0x8c, 0x7a, 0x7e, + 0xdf, 0x41, 0x1a, 0x8d, 0x46, 0xf8, 0x4f, 0x8d, 0xc6, 0xd4, 0xa6, 0x6d, + 0xbb, 0x3e, 0xff, 0xad, 0xd4, 0x63, 0x00, 0xe8, 0x5c, 0x02, 0x1d, 0x00, + 0x98, 0x19, 0xcd, 0x50, 0x2f, 0x97, 0xc2, 0xc0, 0xab, 0x4e, 0x0a, 0xf5, + 0x7d, 0x07, 0xc2, 0x9e, 0x6f, 0xde, 0xd5, 0xba, 0x5c, 0x5b, 0x1b, 0x6b, + 0xbe, 0x98, 0xff, 0x96, 0x38, 0x59, 0xdf, 0x78, 0xff, 0xc3, 0xb7, 0x7c, + 0x23, 0xf5, 0x18, 0x00, 0x3a, 0x9f, 0x40, 0x07, 0x00, 0x66, 0xd6, 0x74, + 0x3d, 0x64, 0x95, 0x72, 0x18, 0x3c, 0xf3, 0x94, 0x50, 0xdf, 0xbb, 0xaf, + 0xf5, 0x8c, 0x7a, 0x98, 0x6e, 0xbb, 0x50, 0xff, 0xd2, 0x74, 0xbd, 0x71, + 0xf5, 0x03, 0xbb, 0x3e, 0xf7, 0xbf, 0x53, 0x0f, 0x01, 0xa0, 0x7b, 0x08, + 0x74, 0x00, 0x60, 0x76, 0xe4, 0x51, 0x9e, 0xf5, 0x54, 0xc2, 0xd0, 0xca, + 0x53, 0xc2, 0xd4, 0x13, 0x7b, 0xc3, 0xde, 0x6f, 0xdf, 0xd3, 0x7a, 0x96, + 0xbd, 0xe0, 0xfe, 0xa6, 0x31, 0x3d, 0x7d, 0xf5, 0xfd, 0xbb, 0x6f, 0xfd, + 0xdb, 0xd4, 0x43, 0x00, 0xe8, 0x3e, 0x02, 0x1d, 0x00, 0x98, 0x55, 0x8d, + 0xa9, 0xe9, 0x50, 0xea, 0xeb, 0x09, 0x43, 0x67, 0x9d, 0x1a, 0xa6, 0x1e, + 0xdb, 0xd3, 0xba, 0x8e, 0xfa, 0x8f, 0xde, 0x09, 0xbe, 0x28, 0xf2, 0x35, + 0x7f, 0x17, 0x1b, 0xe1, 0xea, 0xfb, 0x76, 0x7e, 0xf6, 0xcb, 0xa9, 0xb7, + 0x00, 0xd0, 0xbd, 0x04, 0x3a, 0x00, 0x30, 0x27, 0x5a, 0xa1, 0x3e, 0xaf, + 0x37, 0x0c, 0x9d, 0xfd, 0xb2, 0x30, 0xf9, 0xc8, 0x13, 0x61, 0xe2, 0xce, + 0x7b, 0x53, 0x4f, 0x6a, 0xfa, 0x6a, 0x3d, 0x34, 0x36, 0x6e, 0x7b, 0xf0, + 0x73, 0x5f, 0x48, 0x3d, 0x04, 0x00, 0x04, 0x3a, 0x00, 0x30, 0xa7, 0x9a, + 0xa1, 0x5e, 0x1e, 0xec, 0x0f, 0x43, 0xaf, 0x3e, 0x2d, 0x4c, 0xee, 0x7e, + 0x2c, 0x4c, 0x7c, 0xef, 0xbe, 0x14, 0x33, 0xfe, 0x21, 0x34, 0xea, 0xd7, + 0x6c, 0xdb, 0xfd, 0xf9, 0x5b, 0xea, 0xf5, 0x7a, 0xb1, 0x9e, 0xce, 0x07, + 0xa0, 0x6b, 0x09, 0x74, 0x00, 0x20, 0x89, 0xc6, 0xe4, 0x54, 0x28, 0xcf, + 0x9f, 0xd7, 0x0a, 0xf5, 0x03, 0x3b, 0x1f, 0x09, 0xfb, 0x7e, 0xb0, 0x6d, + 0x2e, 0x3e, 0xeb, 0xb7, 0x43, 0x88, 0xd7, 0x6e, 0xdb, 0x75, 0xcb, 0x5f, + 0xe6, 0x61, 0x5e, 0xf8, 0x1f, 0x88, 0x07, 0xa0, 0xbb, 0x08, 0x74, 0x00, + 0x20, 0xa9, 0x66, 0xa8, 0x57, 0x16, 0x0e, 0x86, 0x6a, 0x33, 0xd4, 0x77, + 0x3c, 0x14, 0xf6, 0xdd, 0xb5, 0x7d, 0x36, 0x3e, 0xcd, 0xf7, 0xea, 0x8d, + 0xfa, 0xe6, 0xed, 0xbb, 0x3f, 0xff, 0x1f, 0xf3, 0x2e, 0x6f, 0xbb, 0xb7, + 0x94, 0x07, 0xa0, 0x3b, 0x08, 0x74, 0x00, 0xa0, 0x10, 0x5a, 0xa1, 0x3e, + 0x3c, 0x3f, 0x54, 0x97, 0x0c, 0x87, 0xfd, 0xdb, 0x76, 0x85, 0xfd, 0xf7, + 0x3e, 0x38, 0x13, 0x1f, 0xf6, 0xee, 0x46, 0x68, 0x5c, 0xf7, 0xc0, 0xae, + 0x27, 0x3f, 0x59, 0xaf, 0x7f, 0x79, 0x6a, 0x26, 0x3e, 0x20, 0x00, 0xcc, + 0x16, 0x81, 0x0e, 0x00, 0x14, 0x4a, 0x33, 0xd4, 0xab, 0xa3, 0x0b, 0x43, + 0xcf, 0x78, 0x2d, 0xec, 0xbb, 0xef, 0xc1, 0x70, 0xe0, 0xfe, 0x5d, 0x47, + 0xf2, 0x61, 0xb6, 0x85, 0x46, 0xf8, 0xfd, 0x6d, 0xbb, 0x7f, 0xf8, 0xc7, + 0xf5, 0xfa, 0x1d, 0x07, 0x66, 0x7a, 0x23, 0x00, 0xcc, 0x06, 0x81, 0x0e, + 0x00, 0x14, 0x52, 0x33, 0xd4, 0x7b, 0x96, 0x0c, 0x87, 0xde, 0x65, 0xa3, + 0x61, 0xe2, 0xee, 0x1d, 0x61, 0x72, 0xc7, 0x43, 0x87, 0xf2, 0xcb, 0x76, + 0x34, 0x42, 0xf8, 0xc0, 0x03, 0xbb, 0x9e, 0xf8, 0x37, 0xf5, 0xfa, 0x97, + 0xf7, 0xcd, 0xf6, 0x46, 0x00, 0x98, 0x49, 0x02, 0x1d, 0x00, 0x28, 0xb4, + 0xe6, 0xbb, 0xbe, 0xf7, 0x2e, 0xab, 0x85, 0xbe, 0xe3, 0x96, 0x84, 0x89, + 0x1f, 0x6e, 0x0f, 0x93, 0x3b, 0x1f, 0x7e, 0xf6, 0xff, 0x26, 0x84, 0xdd, + 0xa1, 0xd1, 0xb8, 0x31, 0xec, 0x8e, 0x1f, 0xbb, 0xbf, 0xfe, 0xd9, 0xbd, + 0x09, 0x66, 0x02, 0xc0, 0x51, 0x13, 0xe8, 0x00, 0x40, 0x5b, 0x68, 0x85, + 0xfa, 0x31, 0xa3, 0xa1, 0xf7, 0xf8, 0xa5, 0x61, 0xe2, 0xfb, 0xdb, 0xc2, + 0xd4, 0xee, 0x47, 0x9b, 0x7f, 0xbb, 0xf9, 0x97, 0x0f, 0x1f, 0x68, 0x3c, + 0xfc, 0xe1, 0x9d, 0x3b, 0xff, 0xc7, 0x13, 0x89, 0x27, 0x02, 0xc0, 0x51, + 0x11, 0xe8, 0x00, 0x40, 0x7b, 0xc9, 0x43, 0xbd, 0xef, 0x84, 0xb1, 0x50, + 0x79, 0xd5, 0x49, 0xdf, 0xab, 0x87, 0xe9, 0x5f, 0xbf, 0xe3, 0xcf, 0x36, + 0x7e, 0x23, 0xf5, 0x24, 0x00, 0x98, 0x09, 0x02, 0x1d, 0x00, 0x68, 0x1f, + 0x59, 0x6c, 0xbd, 0xd3, 0x7b, 0xa5, 0xb6, 0x20, 0xc4, 0x52, 0x76, 0x52, + 0xfe, 0x77, 0xfe, 0xf7, 0x8a, 0x0d, 0x5b, 0x3e, 0x71, 0x60, 0xaa, 0xf1, + 0xfe, 0xff, 0xf3, 0xa1, 0x8b, 0xee, 0x4b, 0x3d, 0x0f, 0x00, 0x8e, 0x86, + 0x40, 0x07, 0x00, 0x8a, 0x2f, 0x36, 0xc3, 0x7c, 0xe8, 0xa9, 0x30, 0x2f, + 0x97, 0x0e, 0xfe, 0x27, 0x95, 0x18, 0xe2, 0xbb, 0x7a, 0xca, 0xf1, 0xb7, + 0x56, 0x6e, 0xd8, 0xba, 0x35, 0x1c, 0xd8, 0xf7, 0xc1, 0xdb, 0xfe, 0xe0, + 0x3d, 0xb3, 0x72, 0x21, 0x75, 0x00, 0x98, 0x6d, 0x02, 0x1d, 0x00, 0x28, + 0xae, 0x3c, 0xcc, 0xcb, 0x8b, 0x06, 0x43, 0xb5, 0x19, 0xe6, 0x95, 0x17, + 0xfc, 0xb6, 0xa5, 0x27, 0xbf, 0x5d, 0x1a, 0xaa, 0xbd, 0xef, 0x5c, 0xb9, + 0x61, 0xcb, 0xcd, 0x53, 0xf5, 0x78, 0xfd, 0xd7, 0x6f, 0xb8, 0x60, 0xe7, + 0x1c, 0xad, 0x04, 0x80, 0x19, 0x21, 0xd0, 0x01, 0x80, 0xe2, 0x69, 0x86, + 0xf9, 0xc2, 0x81, 0x50, 0xa9, 0x2d, 0x0c, 0x59, 0xf5, 0xb0, 0xbe, 0x5d, + 0xe9, 0xcb, 0x7f, 0xf1, 0x7b, 0xcb, 0x59, 0x78, 0xd7, 0x8a, 0xf5, 0x5b, + 0x3f, 0xba, 0x7f, 0x6a, 0xe2, 0xa6, 0x3b, 0x6e, 0xba, 0xec, 0xd9, 0x6f, + 0xfb, 0x0e, 0x00, 0x05, 0x24, 0xd0, 0x01, 0x80, 0x42, 0x29, 0x2f, 0x18, + 0x0c, 0x95, 0xc5, 0x0b, 0xf2, 0x30, 0xaf, 0x1c, 0xcd, 0x87, 0x19, 0xc8, + 0x1b, 0xff, 0xaa, 0xde, 0x4a, 0xdf, 0xbb, 0x57, 0xae, 0xdf, 0xf2, 0x07, + 0x8f, 0xef, 0x9b, 0xfa, 0xc3, 0x3b, 0x3f, 0x72, 0xc9, 0xe3, 0x33, 0xb5, + 0x11, 0x00, 0x66, 0x83, 0x40, 0x07, 0x00, 0x0a, 0xa1, 0x34, 0x7f, 0x5e, + 0xa8, 0x2e, 0x5e, 0x14, 0xb2, 0x9e, 0xa3, 0x0a, 0xf3, 0x67, 0x9a, 0x1f, + 0x62, 0xdc, 0x34, 0xd4, 0x57, 0x79, 0xcf, 0x8a, 0xf5, 0x5b, 0x6e, 0xda, + 0xbf, 0xa7, 0xf1, 0xd1, 0x3b, 0x3e, 0x76, 0xd1, 0x93, 0x33, 0xf9, 0x09, + 0x00, 0x60, 0xa6, 0x08, 0x74, 0x00, 0x20, 0xa9, 0xd2, 0x50, 0x33, 0xcc, + 0x17, 0x86, 0xac, 0xb7, 0x3a, 0x9b, 0x9f, 0x66, 0x51, 0x8c, 0xf1, 0xf7, + 0x7a, 0x07, 0xe2, 0x7b, 0x56, 0xac, 0xdb, 0x72, 0xfd, 0xae, 0xe9, 0x7d, + 0x37, 0xdf, 0x73, 0xd3, 0x65, 0x13, 0xb3, 0xf9, 0x09, 0x01, 0xe0, 0x70, + 0x09, 0x74, 0x00, 0x20, 0x89, 0xd2, 0x60, 0x7f, 0xa8, 0xe4, 0x61, 0x5e, + 0xea, 0xeb, 0x99, 0xcb, 0x4f, 0x3b, 0x1a, 0xb3, 0x78, 0x53, 0x2d, 0xeb, + 0xbb, 0x62, 0xc5, 0x86, 0xad, 0x1f, 0x78, 0x62, 0x62, 0xf2, 0xdf, 0xde, + 0xf9, 0x91, 0x4b, 0xf6, 0xcf, 0xe5, 0x00, 0x00, 0x78, 0x3e, 0x02, 0x1d, + 0x00, 0x98, 0x53, 0xa5, 0x79, 0x7d, 0xa1, 0xb2, 0x24, 0x0f, 0xf3, 0xfe, + 0xde, 0x64, 0x1b, 0x62, 0x08, 0x63, 0xf9, 0xdd, 0xbf, 0x1e, 0xea, 0xab, + 0x5c, 0xb9, 0x62, 0xc3, 0x96, 0xdf, 0x8b, 0x8f, 0x7e, 0xe3, 0x13, 0xb7, + 0x6d, 0xd9, 0x32, 0x99, 0x6c, 0x10, 0x00, 0x04, 0x81, 0x0e, 0x00, 0xcc, + 0x91, 0x6c, 0x5e, 0x6f, 0xeb, 0x67, 0xcc, 0x4b, 0xf3, 0xd2, 0x85, 0xf9, + 0x73, 0x38, 0x26, 0x86, 0xb8, 0x35, 0x2c, 0x78, 0xd5, 0xba, 0x33, 0xd7, + 0xdf, 0xbc, 0xf9, 0xeb, 0x77, 0xff, 0xb7, 0x4f, 0xd5, 0x3f, 0xfd, 0xe9, + 0xe9, 0xd4, 0xa3, 0x00, 0xe8, 0x4e, 0x02, 0x1d, 0x00, 0x98, 0x55, 0x59, + 0x7f, 0xcf, 0x53, 0x61, 0x3e, 0xd0, 0x97, 0x7a, 0xca, 0x0b, 0x39, 0x21, + 0x8b, 0xd9, 0x9f, 0x9c, 0xf9, 0x92, 0x55, 0x57, 0xad, 0x5c, 0xb7, 0x65, + 0xf3, 0xed, 0x1f, 0x7a, 0xf0, 0xcf, 0xeb, 0xf5, 0x8d, 0xf5, 0xd4, 0xa3, + 0x00, 0xe8, 0x2e, 0x02, 0x1d, 0x00, 0x98, 0x15, 0xcd, 0x37, 0x7d, 0xab, + 0xe4, 0x61, 0x5e, 0x1e, 0xea, 0x4f, 0x3d, 0xe5, 0x70, 0xbc, 0x34, 0x64, + 0xf1, 0x53, 0x67, 0xae, 0x5b, 0xf2, 0xbe, 0x15, 0x1b, 0x6e, 0xde, 0xf8, + 0xb5, 0x1b, 0xde, 0xfd, 0x99, 0x7a, 0xbd, 0xde, 0x48, 0x3d, 0x0a, 0x80, + 0xee, 0x20, 0xd0, 0x01, 0x80, 0x19, 0xd5, 0x0a, 0xf3, 0xd1, 0x85, 0xa1, + 0x3c, 0x7f, 0x5e, 0xea, 0x29, 0x47, 0xe3, 0xb4, 0x18, 0xb2, 0xbf, 0x3a, + 0x63, 0xdd, 0xcd, 0xb7, 0x9f, 0xb9, 0xfe, 0xe3, 0xd7, 0xdc, 0x7e, 0xfd, + 0xc5, 0x5f, 0x48, 0x3d, 0x08, 0x80, 0xce, 0x27, 0xd0, 0x01, 0x80, 0x19, + 0x11, 0x7b, 0x2a, 0xa1, 0xda, 0x0c, 0xf3, 0x05, 0x03, 0xa9, 0xa7, 0xcc, + 0x98, 0x18, 0xc2, 0x99, 0x31, 0x96, 0x3e, 0xbf, 0x72, 0xfd, 0x96, 0xbf, + 0x0f, 0xa1, 0x71, 0xcd, 0x6d, 0xd7, 0x5f, 0xf4, 0xa5, 0xd4, 0x9b, 0x00, + 0xe8, 0x5c, 0x02, 0x1d, 0x00, 0x38, 0x2a, 0xb1, 0xda, 0x0c, 0xf3, 0x05, + 0x79, 0x98, 0x0f, 0xb6, 0x8a, 0xb6, 0x23, 0xc5, 0x78, 0x76, 0xfe, 0x97, + 0xff, 0xba, 0x72, 0xc3, 0xd6, 0xaf, 0x34, 0xa6, 0xa7, 0xaf, 0xfe, 0xea, + 0x8d, 0x17, 0x7f, 0x25, 0xf5, 0x24, 0x00, 0x3a, 0x8f, 0x40, 0x07, 0x00, + 0x8e, 0x48, 0xac, 0x94, 0x43, 0xa5, 0x19, 0xe6, 0x0b, 0x07, 0xf3, 0x7e, + 0xed, 0xd4, 0x32, 0x7f, 0x96, 0xd7, 0xc6, 0x52, 0xe9, 0xbf, 0xaf, 0x5c, + 0xbf, 0xe5, 0x4b, 0xa1, 0x51, 0xbf, 0xe6, 0xb6, 0x1b, 0x2e, 0xfe, 0xfb, + 0xd4, 0x83, 0x00, 0xe8, 0x1c, 0x02, 0x1d, 0x00, 0x38, 0x2c, 0xb1, 0x5c, + 0x0a, 0x95, 0x5a, 0x1e, 0xe6, 0xc3, 0x43, 0xdd, 0x14, 0xe6, 0x3f, 0x29, + 0xc6, 0x73, 0x42, 0x2c, 0x9d, 0xb3, 0x72, 0xc3, 0xd6, 0x2f, 0x34, 0xa6, + 0x1b, 0xd7, 0x7c, 0xf5, 0xc6, 0x0b, 0x6f, 0x4f, 0x3d, 0x09, 0x80, 0xf6, + 0x27, 0xd0, 0x01, 0x80, 0x43, 0xf2, 0xe3, 0x30, 0x5f, 0x94, 0x87, 0x79, + 0xd6, 0xa5, 0x61, 0xfe, 0x6c, 0xe7, 0xc6, 0x52, 0x7c, 0xc3, 0x8a, 0x0d, + 0x5b, 0x3e, 0x33, 0x5d, 0x8f, 0x1b, 0xbf, 0x7e, 0xc3, 0x05, 0xff, 0x98, + 0x7a, 0x10, 0x00, 0xed, 0x4b, 0xa0, 0x03, 0x00, 0x2f, 0xac, 0x94, 0x85, + 0xca, 0xc8, 0x82, 0xfc, 0xd6, 0x0c, 0xf3, 0x2c, 0xf5, 0x9a, 0x22, 0x8a, + 0x31, 0xc4, 0x5f, 0x29, 0x67, 0xe1, 0x97, 0x57, 0x6e, 0xd8, 0xfa, 0x17, + 0x61, 0x7a, 0x6a, 0xd3, 0x6d, 0x37, 0xbe, 0xfb, 0x3b, 0xa9, 0x47, 0x01, + 0xd0, 0x7e, 0x04, 0x3a, 0x00, 0xf0, 0xdc, 0xb2, 0x66, 0x98, 0xcf, 0x6f, + 0xdd, 0x62, 0x49, 0x98, 0x1f, 0x82, 0xe6, 0x6f, 0xd2, 0x5b, 0x42, 0xa9, + 0xfc, 0xa6, 0x95, 0xeb, 0xb7, 0x7c, 0x2a, 0x84, 0xa9, 0xcd, 0xb7, 0x5d, + 0x7f, 0xc9, 0x0f, 0x52, 0x8f, 0x02, 0xa0, 0x7d, 0x08, 0x74, 0x00, 0xe0, + 0x27, 0x35, 0xc3, 0x7c, 0x78, 0xa8, 0xf5, 0x72, 0x76, 0x61, 0x7e, 0x44, + 0x4a, 0x21, 0xc6, 0xb7, 0x85, 0x50, 0xf9, 0x8d, 0x15, 0x1b, 0xb6, 0x7c, + 0x22, 0xee, 0x3f, 0xf0, 0x7b, 0xb7, 0x7d, 0xf8, 0xd2, 0x7b, 0x53, 0x8f, + 0x02, 0xa0, 0xf8, 0x04, 0x3a, 0x00, 0xf0, 0x94, 0x18, 0xff, 0x29, 0xcc, + 0xcb, 0xa5, 0xd4, 0x6b, 0x3a, 0x41, 0x25, 0x86, 0xf8, 0xae, 0xd0, 0xd3, + 0xf3, 0x5b, 0x2b, 0x37, 0x6c, 0xfd, 0xb7, 0xf5, 0x30, 0xf5, 0x81, 0xdb, + 0x3f, 0xf8, 0xee, 0x07, 0x52, 0x8f, 0x02, 0xa0, 0xb8, 0x04, 0x3a, 0x00, + 0x74, 0xbb, 0x3c, 0xcc, 0xcb, 0x8b, 0x06, 0x43, 0xb5, 0xb6, 0x30, 0xc4, + 0x8a, 0x30, 0x9f, 0x05, 0x3d, 0xf9, 0xed, 0x92, 0x2c, 0x94, 0xff, 0xd5, + 0xca, 0x0d, 0x5b, 0x6e, 0x9e, 0xaa, 0xc7, 0xeb, 0xbf, 0x7e, 0xc3, 0x05, + 0x3b, 0x53, 0x8f, 0x02, 0xa0, 0x78, 0x04, 0x3a, 0x00, 0x74, 0xab, 0x66, + 0x98, 0x2f, 0x1c, 0x6c, 0x5d, 0xcb, 0x3c, 0xab, 0xf8, 0x96, 0x60, 0x0e, + 0xf4, 0xe5, 0xbf, 0xe9, 0xef, 0x2d, 0x67, 0xe1, 0x5d, 0x2b, 0xd6, 0x6f, + 0xfd, 0xe8, 0xfe, 0xa9, 0x89, 0x9b, 0xee, 0xb8, 0xe9, 0xb2, 0x87, 0x53, + 0x8f, 0x02, 0xa0, 0x38, 0x7c, 0x35, 0x06, 0x80, 0x2e, 0x54, 0x5e, 0x90, + 0x87, 0xf9, 0xe2, 0x3c, 0xcc, 0xab, 0x95, 0xd4, 0x53, 0xba, 0xd1, 0x40, + 0x8c, 0xe1, 0xaa, 0xde, 0x72, 0xdf, 0x25, 0x2b, 0xd6, 0x6f, 0xf9, 0xc3, + 0xf8, 0xd8, 0xe3, 0x7f, 0x70, 0xdb, 0x96, 0x75, 0x8f, 0xa5, 0x1e, 0x05, + 0x40, 0x7a, 0x02, 0x1d, 0x00, 0xba, 0x48, 0x79, 0xc1, 0x40, 0xa8, 0x8c, + 0x2e, 0x0c, 0x59, 0x8f, 0x30, 0x4f, 0x2e, 0x86, 0xa1, 0x18, 0xe2, 0x35, + 0x61, 0xc1, 0xfc, 0x66, 0xa8, 0xdf, 0xb4, 0x7f, 0x4f, 0xe3, 0xa3, 0x77, + 0x7c, 0xec, 0xa2, 0x27, 0x53, 0xcf, 0x02, 0x20, 0x1d, 0x81, 0x0e, 0x00, + 0x5d, 0xa0, 0x34, 0x34, 0x2f, 0x54, 0x17, 0xe7, 0x61, 0xde, 0x5b, 0x4d, + 0x3d, 0x85, 0x67, 0x5b, 0x14, 0x63, 0xfc, 0xbd, 0xde, 0x81, 0xf8, 0x9e, + 0x95, 0xeb, 0x6f, 0xbe, 0x31, 0x3c, 0xb6, 0xf3, 0xe3, 0xb7, 0x6d, 0xd9, + 0xb8, 0x37, 0xf5, 0x28, 0x00, 0xe6, 0x9e, 0x40, 0x07, 0x80, 0x0e, 0x56, + 0x1a, 0xec, 0x0f, 0x95, 0x3c, 0xcc, 0x4b, 0x7d, 0x3d, 0xa9, 0xa7, 0xf0, + 0xe2, 0x46, 0x43, 0xcc, 0x6e, 0x0c, 0x0b, 0x96, 0xbc, 0x77, 0xe5, 0x86, + 0xad, 0x1f, 0x7c, 0x7c, 0x62, 0x72, 0xeb, 0x9d, 0x1f, 0xb9, 0x64, 0x7f, + 0xea, 0x51, 0x00, 0xcc, 0x1d, 0x81, 0x0e, 0x00, 0x1d, 0xa8, 0x34, 0xd0, + 0x97, 0x87, 0xf9, 0xa2, 0x50, 0xea, 0x17, 0xe6, 0x6d, 0x68, 0x69, 0x7e, + 0xfb, 0xc8, 0x50, 0x6f, 0xe5, 0xb7, 0x57, 0x6c, 0xd8, 0xf2, 0xfe, 0xf8, + 0xe8, 0x37, 0x3e, 0x71, 0xdb, 0x96, 0x2d, 0x93, 0xa9, 0x47, 0x01, 0x30, + 0xfb, 0x04, 0x3a, 0x00, 0x74, 0x90, 0x6c, 0x5e, 0x6f, 0xa8, 0x36, 0xc3, + 0x3c, 0xbf, 0xa7, 0xcd, 0xc5, 0xb0, 0x3c, 0x86, 0xb8, 0x35, 0x2c, 0x78, + 0xd5, 0xfa, 0x95, 0xeb, 0xb7, 0x5e, 0x77, 0xfb, 0x6d, 0x3b, 0xfe, 0xac, + 0xfe, 0xe5, 0x8d, 0x53, 0xa9, 0x67, 0x01, 0x30, 0x7b, 0x04, 0x3a, 0x00, + 0x74, 0x80, 0xac, 0xbf, 0xe7, 0xa9, 0x30, 0x1f, 0xe8, 0x4b, 0x3d, 0x85, + 0x99, 0x77, 0x7c, 0x1e, 0xeb, 0x9f, 0x38, 0xf3, 0xac, 0x25, 0xef, 0x5b, + 0xb9, 0x6e, 0xcb, 0xa6, 0xdb, 0x3f, 0xf4, 0xe0, 0x9f, 0xd7, 0xeb, 0x1b, + 0xeb, 0xa9, 0x47, 0x01, 0x30, 0xf3, 0x04, 0x3a, 0x00, 0xb4, 0xb1, 0xac, + 0xaf, 0xa7, 0xf5, 0x33, 0xe6, 0xe5, 0xc1, 0xfe, 0xd4, 0x53, 0x98, 0x7d, + 0x27, 0x87, 0x2c, 0x7e, 0xea, 0xcc, 0x75, 0x4b, 0xde, 0xb7, 0x62, 0xc3, + 0xcd, 0x1b, 0xbf, 0x76, 0xc3, 0xbb, 0x3f, 0x53, 0xaf, 0xd7, 0x1b, 0xa9, + 0x47, 0x01, 0x30, 0x73, 0x04, 0x3a, 0x00, 0xb4, 0xa1, 0xe6, 0xbb, 0xb1, + 0x37, 0x2f, 0x97, 0x56, 0x9e, 0x3f, 0x2f, 0xf5, 0x14, 0xe6, 0xde, 0x69, + 0x31, 0x64, 0x7f, 0x75, 0xe6, 0xba, 0x9b, 0xbf, 0x71, 0xe6, 0x86, 0x2d, + 0x1b, 0x6f, 0xff, 0xe0, 0x85, 0x9f, 0x4b, 0x3d, 0x08, 0x80, 0x99, 0x21, + 0xd0, 0x01, 0xa0, 0x8d, 0xc4, 0x9e, 0x4a, 0xa8, 0x36, 0xc3, 0x7c, 0xc1, + 0x40, 0xea, 0x29, 0xa4, 0xf7, 0xca, 0x2c, 0xc4, 0xcf, 0xae, 0x5c, 0xbf, + 0xe5, 0xef, 0x43, 0x68, 0x5c, 0x73, 0xdb, 0xf5, 0x17, 0x7d, 0x29, 0xf5, + 0x20, 0x00, 0x8e, 0x8e, 0x40, 0x07, 0x80, 0x36, 0x10, 0xab, 0x07, 0x85, + 0x79, 0x4c, 0xbd, 0x86, 0x42, 0x89, 0xf1, 0xec, 0xfc, 0x2f, 0xff, 0x75, + 0xe5, 0x86, 0xad, 0x5f, 0xa9, 0x37, 0x1a, 0xd7, 0xdc, 0x7e, 0xfd, 0x85, + 0xff, 0x3d, 0xf5, 0x24, 0x00, 0x8e, 0x8c, 0x40, 0x07, 0x80, 0x02, 0x8b, + 0x95, 0x72, 0xa8, 0x8c, 0x2e, 0x08, 0xe5, 0x85, 0x83, 0x79, 0x87, 0x29, + 0x73, 0x5e, 0xd0, 0x6b, 0xb3, 0x18, 0xff, 0xdb, 0xca, 0xf5, 0x5b, 0xff, + 0xba, 0x3e, 0x5d, 0xdf, 0x78, 0xfb, 0x87, 0x2e, 0xfa, 0xbb, 0xd4, 0x83, + 0x00, 0x38, 0x3c, 0x02, 0x1d, 0x00, 0x0a, 0x28, 0x96, 0x4b, 0x4f, 0xfd, + 0x8c, 0xf9, 0x22, 0x61, 0xce, 0x61, 0x8a, 0xe1, 0x17, 0xb3, 0x72, 0xf6, + 0x8b, 0x2b, 0x37, 0x6c, 0xfd, 0x42, 0x63, 0xba, 0x71, 0xcd, 0x57, 0x6f, + 0xbc, 0xf0, 0xf6, 0xd4, 0x93, 0x00, 0x38, 0x34, 0x02, 0x1d, 0x00, 0x0a, + 0xa4, 0x15, 0xe6, 0xb5, 0x05, 0x79, 0x98, 0x0f, 0x85, 0x98, 0x09, 0x73, + 0x8e, 0xca, 0xb9, 0xb1, 0x14, 0xdf, 0xb0, 0x62, 0xc3, 0x96, 0xcf, 0xc4, + 0xc9, 0xe9, 0x6b, 0x6f, 0xbb, 0xe9, 0xdd, 0x77, 0xa4, 0x1e, 0x04, 0xc0, + 0x0b, 0x13, 0xe8, 0x00, 0x50, 0x04, 0xa5, 0x2c, 0x54, 0x46, 0x16, 0xe4, + 0xb7, 0x66, 0x98, 0x67, 0xa9, 0xd7, 0xd0, 0x39, 0x62, 0x0c, 0xf1, 0x57, + 0x42, 0xa5, 0xfc, 0xcb, 0x2b, 0x36, 0x6c, 0xfd, 0xcb, 0x10, 0xea, 0xd7, + 0x7e, 0xf5, 0x83, 0x17, 0x7d, 0x3b, 0xf5, 0x28, 0x00, 0x9e, 0x9b, 0x40, + 0x07, 0x80, 0x94, 0xf2, 0x18, 0xaf, 0xd4, 0xe6, 0x87, 0xca, 0xf0, 0xfc, + 0x10, 0x4b, 0xc2, 0x9c, 0x59, 0xd3, 0x7c, 0x3d, 0xc6, 0xaf, 0xe7, 0x77, + 0xbf, 0xba, 0x72, 0xfd, 0x96, 0x4f, 0x85, 0x30, 0xb5, 0xf9, 0xb6, 0xeb, + 0x2f, 0xf9, 0x41, 0xea, 0x51, 0x00, 0xfc, 0x24, 0x81, 0x0e, 0x00, 0x29, + 0x34, 0xc3, 0x7c, 0x78, 0xa8, 0xf5, 0x72, 0x76, 0x61, 0xce, 0x1c, 0x2a, + 0x85, 0x18, 0xdf, 0x16, 0x42, 0xe5, 0x37, 0x56, 0x6e, 0xd8, 0xfa, 0x27, + 0x8d, 0xe9, 0xe9, 0xf7, 0x7f, 0xf5, 0xc6, 0x8b, 0xef, 0x49, 0x3d, 0x0a, + 0x80, 0xa7, 0x08, 0x74, 0x00, 0x98, 0x4b, 0x59, 0x0c, 0x95, 0x45, 0x4f, + 0x87, 0x79, 0xb9, 0x94, 0x7a, 0x0d, 0xdd, 0xab, 0x92, 0xdf, 0xde, 0x11, + 0x4a, 0xa5, 0xb7, 0xad, 0xd8, 0xb0, 0xf5, 0x8f, 0xeb, 0x93, 0xe1, 0xf7, + 0xbf, 0x76, 0xd3, 0x05, 0xdb, 0x52, 0x8f, 0x02, 0xe8, 0x76, 0x02, 0x1d, + 0x00, 0xe6, 0x42, 0x8c, 0xad, 0x37, 0x7e, 0xab, 0x8e, 0x0a, 0x73, 0x8a, + 0x23, 0x86, 0x50, 0xcd, 0xef, 0x2e, 0x2e, 0x55, 0xc2, 0xdb, 0x57, 0x6e, + 0xd8, 0x72, 0xf3, 0x54, 0x3d, 0x5e, 0xff, 0xf5, 0x1b, 0x2e, 0xd8, 0x99, + 0x7a, 0x17, 0x40, 0xb7, 0x12, 0xe8, 0x00, 0x30, 0x9b, 0x9a, 0x61, 0xbe, + 0x70, 0xb0, 0x75, 0x2d, 0xf3, 0xac, 0xe2, 0xcb, 0x2e, 0x85, 0xd5, 0x97, + 0xff, 0x61, 0x7d, 0x6f, 0x29, 0x0b, 0x17, 0xac, 0x5c, 0xbf, 0xf5, 0x5f, + 0xef, 0xdd, 0x3b, 0xfd, 0xa1, 0x6f, 0xfe, 0xeb, 0x8b, 0x1f, 0x4a, 0x3d, + 0x0a, 0xa0, 0xdb, 0xf8, 0x4e, 0x01, 0x00, 0x66, 0xc9, 0x53, 0x61, 0xbe, + 0x30, 0x64, 0x55, 0x5f, 0x6e, 0x69, 0x0f, 0x31, 0x84, 0x79, 0xf9, 0x5f, + 0x36, 0xf4, 0xf7, 0x97, 0x2e, 0x5e, 0xb1, 0x7e, 0xcb, 0x1f, 0xd6, 0x27, + 0xf7, 0x7f, 0xf8, 0x6b, 0x7f, 0xf0, 0x9e, 0x47, 0x53, 0xef, 0x02, 0xe8, + 0x16, 0xbe, 0x63, 0x00, 0x80, 0x19, 0x56, 0x5e, 0x30, 0xf0, 0x54, 0x98, + 0xf7, 0x54, 0x52, 0x4f, 0x81, 0x23, 0x13, 0xc3, 0x50, 0x0c, 0xf1, 0x9a, + 0x52, 0xb5, 0xf7, 0xff, 0xce, 0x43, 0xfd, 0x43, 0xfb, 0xf7, 0x34, 0x3e, + 0x7a, 0xc7, 0xc7, 0x2e, 0x7a, 0x32, 0xf5, 0x2c, 0x80, 0x4e, 0x27, 0xd0, + 0x01, 0x60, 0x86, 0x94, 0x86, 0xe6, 0x85, 0xea, 0xe2, 0x3c, 0xcc, 0x7b, + 0xab, 0xa9, 0xa7, 0xc0, 0x4c, 0x59, 0x18, 0x63, 0xfc, 0xbd, 0xde, 0x81, + 0x78, 0xf9, 0x8a, 0xf5, 0x5b, 0x6f, 0x8c, 0x8f, 0xed, 0xf8, 0xd8, 0x6d, + 0x5b, 0x36, 0xee, 0x4d, 0x3d, 0x0a, 0xa0, 0x53, 0x09, 0x74, 0x00, 0x38, + 0x4a, 0xa5, 0xa1, 0xfe, 0x50, 0x1d, 0x5d, 0x14, 0xb2, 0x3e, 0x61, 0x4e, + 0xc7, 0x1a, 0x89, 0x31, 0x5c, 0x1f, 0x16, 0x2c, 0xb9, 0x7c, 0xe5, 0x86, + 0xad, 0x1f, 0x78, 0x7c, 0x62, 0x72, 0xeb, 0x9d, 0x1f, 0xb9, 0x64, 0x7f, + 0xea, 0x51, 0x00, 0x9d, 0x46, 0xa0, 0x03, 0xc0, 0x11, 0x2a, 0x0d, 0xf4, + 0x85, 0xca, 0xe2, 0x45, 0xa1, 0xd4, 0xdf, 0x93, 0x7a, 0x0a, 0xcc, 0x95, + 0x25, 0xf9, 0xed, 0x23, 0x43, 0xbd, 0x95, 0xdf, 0x5e, 0xb9, 0x6e, 0xeb, + 0x75, 0xb7, 0xdf, 0xf3, 0xe5, 0x7f, 0x5f, 0xff, 0xf4, 0xa7, 0xa7, 0x53, + 0x8f, 0x02, 0xe8, 0x14, 0x02, 0x1d, 0x00, 0x0e, 0x53, 0x36, 0xaf, 0x37, + 0x54, 0x9b, 0x61, 0x9e, 0xdf, 0x43, 0x57, 0x8a, 0x61, 0x79, 0x7e, 0xfb, + 0x37, 0x67, 0xbc, 0x64, 0xd5, 0x25, 0x2b, 0xaf, 0xbc, 0xf9, 0xdd, 0xb7, + 0xdd, 0x78, 0xd1, 0xff, 0x4c, 0x3d, 0x09, 0xa0, 0x13, 0x08, 0x74, 0x00, + 0x38, 0x44, 0x59, 0x7f, 0x6f, 0xeb, 0x67, 0xcc, 0x9b, 0xcf, 0x9c, 0x03, + 0xad, 0x77, 0x7d, 0xff, 0xa9, 0x50, 0xca, 0xfe, 0xdb, 0x8a, 0xf5, 0x5b, + 0xaf, 0xff, 0xda, 0x6d, 0x3b, 0x36, 0xd6, 0xbf, 0xbc, 0x71, 0x2a, 0xf5, + 0x26, 0x80, 0x76, 0x26, 0xd0, 0x01, 0xe0, 0x45, 0x64, 0x7d, 0x3d, 0xa1, + 0x92, 0x87, 0x79, 0x79, 0xb0, 0x3f, 0xf5, 0x14, 0x28, 0xa2, 0x52, 0x8c, + 0xe1, 0xaa, 0x33, 0xce, 0x5a, 0x72, 0xe6, 0xca, 0x0b, 0x6f, 0x78, 0xf3, + 0x6d, 0x5b, 0xd6, 0x3d, 0x96, 0x7a, 0x10, 0x40, 0xbb, 0x12, 0xe8, 0x00, + 0xf0, 0x3c, 0x9a, 0xef, 0xc6, 0xde, 0x0a, 0xf3, 0xa1, 0x79, 0xa9, 0xa7, + 0x40, 0xe1, 0xc5, 0x10, 0x5e, 0xd7, 0x58, 0x30, 0xff, 0x2b, 0xaf, 0x5a, + 0xb7, 0xf5, 0x97, 0xbe, 0x7e, 0xc3, 0x05, 0x3b, 0x53, 0xef, 0x01, 0x68, + 0x47, 0x02, 0x1d, 0x00, 0x9e, 0x21, 0xf6, 0x54, 0x5a, 0x3f, 0x63, 0x5e, + 0x9e, 0x2f, 0xcc, 0xe1, 0x70, 0x34, 0x5f, 0xf2, 0x5e, 0x8e, 0xe1, 0x6f, + 0x5e, 0x71, 0xf9, 0x47, 0x57, 0xfd, 0xc3, 0x87, 0x2f, 0x7d, 0x30, 0xf5, + 0x1e, 0x80, 0x76, 0x23, 0xd0, 0x01, 0xe0, 0x69, 0xb1, 0x9a, 0x87, 0xf9, + 0xe8, 0xc2, 0x50, 0x5e, 0x30, 0xd0, 0x2a, 0x0d, 0xe0, 0x08, 0xc4, 0xf0, + 0xb2, 0x4a, 0x4f, 0xcf, 0xe7, 0x4f, 0xbf, 0xe4, 0xe6, 0x9f, 0xbf, 0xe3, + 0x63, 0x17, 0x3d, 0x99, 0x7a, 0x0e, 0x40, 0x3b, 0x11, 0xe8, 0x00, 0x74, + 0xbd, 0x58, 0x29, 0xb7, 0x5e, 0xca, 0x5e, 0x59, 0x30, 0x28, 0xcc, 0x61, + 0x06, 0xe4, 0xff, 0x1a, 0x9d, 0xd9, 0x3b, 0x90, 0xfd, 0x45, 0x96, 0x6d, + 0x5a, 0x5d, 0xaf, 0x6f, 0xac, 0xa7, 0xde, 0x03, 0xd0, 0x2e, 0x04, 0x3a, + 0x00, 0x5d, 0xab, 0x15, 0xe6, 0xb5, 0x05, 0xa1, 0xbc, 0x68, 0x30, 0xc4, + 0xa8, 0xcc, 0x61, 0x86, 0xbd, 0xe1, 0xcc, 0x75, 0x4b, 0x36, 0xe7, 0xf7, + 0xbf, 0x9b, 0x7a, 0x08, 0x40, 0xbb, 0x10, 0xe8, 0x00, 0x74, 0x9d, 0x58, + 0x2e, 0x3d, 0x1d, 0xe6, 0x43, 0x21, 0x66, 0xc2, 0x1c, 0x66, 0xd1, 0x55, + 0x2b, 0xd7, 0xdf, 0xfc, 0x95, 0xdb, 0xae, 0xbf, 0xe8, 0x4b, 0xa9, 0x87, + 0x00, 0xb4, 0x03, 0x81, 0x0e, 0x40, 0xf7, 0x28, 0x95, 0x42, 0xb5, 0x36, + 0x3f, 0x94, 0x87, 0xe7, 0x0b, 0x73, 0x98, 0x1b, 0x31, 0xc4, 0xf8, 0xef, + 0x5e, 0xf1, 0xee, 0x2d, 0x3f, 0xf5, 0x0f, 0x7f, 0x74, 0xe1, 0x23, 0xa9, + 0xc7, 0x00, 0x14, 0x9d, 0x40, 0x07, 0xa0, 0xf3, 0x95, 0xb2, 0x50, 0x19, + 0x99, 0x1f, 0x2a, 0xcd, 0x30, 0xcf, 0x1f, 0x03, 0x73, 0x29, 0x2e, 0xab, + 0x0e, 0x36, 0x6e, 0xcc, 0x1f, 0xbc, 0x23, 0xf5, 0x12, 0x80, 0xa2, 0x13, + 0xe8, 0x00, 0x74, 0xae, 0xec, 0xe9, 0x30, 0x1f, 0x11, 0xe6, 0x90, 0x56, + 0x7c, 0xfb, 0x8a, 0x75, 0x1f, 0xff, 0x77, 0x5f, 0xbd, 0xe1, 0xe2, 0xff, + 0x95, 0x7a, 0x09, 0x40, 0x91, 0x09, 0x74, 0x00, 0x3a, 0x4f, 0x16, 0x5b, + 0xcf, 0x96, 0xb7, 0xc2, 0xbc, 0x5c, 0x4a, 0xbd, 0x06, 0x68, 0xfe, 0x5b, + 0x19, 0x4b, 0x1f, 0xc9, 0xb2, 0xec, 0xec, 0x7a, 0xbd, 0xde, 0x48, 0x3d, + 0x06, 0xa0, 0xa8, 0x04, 0x3a, 0x00, 0x9d, 0x23, 0xc6, 0xd6, 0x1b, 0xbf, + 0x55, 0x47, 0x17, 0x08, 0x73, 0x28, 0x9a, 0x18, 0x56, 0x9e, 0x79, 0xe5, + 0xcd, 0xbf, 0x9c, 0x3f, 0xfa, 0x4c, 0xea, 0x29, 0x00, 0x45, 0x25, 0xd0, + 0x01, 0x68, 0x7f, 0xad, 0x30, 0x1f, 0x0c, 0xd5, 0xda, 0x82, 0xd6, 0xa5, + 0xd3, 0x80, 0x82, 0x8a, 0x8d, 0x4d, 0x59, 0x96, 0xfd, 0x67, 0xcf, 0xa2, + 0x03, 0x3c, 0x37, 0xdf, 0xc5, 0x00, 0xd0, 0xd6, 0xca, 0x0b, 0x07, 0x43, + 0x65, 0x74, 0x61, 0xc8, 0xaa, 0xbe, 0xa4, 0x41, 0xf1, 0xc5, 0xd3, 0xcf, + 0xb8, 0xf2, 0x8f, 0x5e, 0x97, 0x3f, 0xf8, 0x2f, 0xa9, 0x97, 0x00, 0x14, + 0x91, 0xef, 0x66, 0x00, 0x68, 0x4b, 0xe5, 0x05, 0x03, 0x4f, 0x85, 0x79, + 0x4f, 0x25, 0xf5, 0x14, 0xe0, 0x30, 0xc4, 0x98, 0xbd, 0x37, 0x08, 0x74, + 0x80, 0xe7, 0x24, 0xd0, 0x01, 0x68, 0x2b, 0xa5, 0xf9, 0xf3, 0x42, 0x75, + 0x71, 0x33, 0xcc, 0xab, 0xa9, 0xa7, 0x00, 0x47, 0xe6, 0x17, 0x5f, 0x71, + 0xc5, 0x96, 0xe3, 0xff, 0xe1, 0xa6, 0x0b, 0x7f, 0x98, 0x7a, 0x08, 0x40, + 0xd1, 0x08, 0x74, 0x00, 0xda, 0x42, 0x69, 0xa8, 0x3f, 0x54, 0x47, 0x17, + 0x85, 0xac, 0x4f, 0x98, 0x43, 0x9b, 0x8b, 0xd5, 0x4a, 0xfc, 0x17, 0xf9, + 0xfd, 0x35, 0xa9, 0x87, 0x00, 0x14, 0x8d, 0x40, 0x07, 0xa0, 0xd0, 0x4a, + 0x83, 0xfd, 0xad, 0x97, 0xb2, 0x97, 0xfa, 0x7b, 0x52, 0x4f, 0x01, 0x66, + 0xce, 0x9b, 0x83, 0x40, 0x07, 0x78, 0x16, 0x81, 0x0e, 0x40, 0x21, 0x95, + 0xe6, 0xf5, 0x85, 0xca, 0x92, 0x66, 0x98, 0xf7, 0xa6, 0x9e, 0x02, 0xcc, + 0xbc, 0x93, 0xcf, 0xb8, 0xe2, 0xe3, 0x2f, 0xff, 0xda, 0x4d, 0x17, 0x7f, + 0x33, 0xf5, 0x10, 0x80, 0x22, 0x11, 0xe8, 0x00, 0x14, 0x4a, 0x96, 0x07, + 0x79, 0xf3, 0x67, 0xcc, 0x4b, 0x03, 0x7d, 0xa9, 0xa7, 0x00, 0xb3, 0x28, + 0xab, 0x64, 0xe7, 0xe6, 0x77, 0x02, 0x1d, 0xe0, 0x20, 0x02, 0x1d, 0x80, + 0x42, 0xc8, 0xfa, 0x7a, 0x9e, 0x0a, 0xf3, 0xc1, 0xfe, 0xd4, 0x53, 0x80, + 0x39, 0x10, 0x43, 0xfc, 0xf9, 0xfc, 0xee, 0xc6, 0xd4, 0x3b, 0x00, 0x8a, + 0x44, 0xa0, 0x03, 0x90, 0x54, 0xd6, 0x5b, 0x0d, 0x95, 0xc5, 0x8b, 0x42, + 0x79, 0x48, 0x98, 0x43, 0x97, 0x79, 0x4d, 0x96, 0x65, 0xb1, 0x5e, 0xaf, + 0x37, 0x52, 0x0f, 0x01, 0x28, 0x0a, 0x81, 0x0e, 0x40, 0x12, 0xb1, 0xa7, + 0xda, 0x7a, 0xc6, 0xbc, 0x3c, 0x7f, 0x5e, 0xea, 0x29, 0x40, 0x1a, 0xf3, + 0x4f, 0xbf, 0xfc, 0xe3, 0x2f, 0xc9, 0xef, 0x5d, 0x6e, 0x0d, 0xe0, 0x69, + 0x02, 0x1d, 0x80, 0x39, 0x15, 0xab, 0x95, 0xa7, 0xc3, 0x7c, 0xa0, 0xf9, + 0x1a, 0x57, 0xa0, 0x8b, 0xe5, 0xff, 0x39, 0xf8, 0xa9, 0x20, 0xd0, 0x01, + 0x7e, 0x4c, 0xa0, 0x03, 0x30, 0x27, 0x62, 0xa5, 0x1c, 0x2a, 0x79, 0x98, + 0x57, 0x16, 0x0c, 0x0a, 0x73, 0xa0, 0xa5, 0xd1, 0x88, 0x2f, 0x49, 0xbd, + 0x01, 0xa0, 0x48, 0x04, 0x3a, 0x00, 0xb3, 0xaa, 0x15, 0xe6, 0xb5, 0x05, + 0xa1, 0xbc, 0x68, 0x30, 0xc4, 0xa8, 0xcc, 0x81, 0x7f, 0x92, 0xff, 0x17, + 0x61, 0x79, 0xea, 0x0d, 0x00, 0x45, 0x22, 0xd0, 0x01, 0x98, 0x15, 0xb1, + 0x5c, 0x7a, 0x2a, 0xcc, 0x87, 0x87, 0x84, 0x39, 0xf0, 0x9c, 0x1a, 0x21, + 0x2c, 0x4c, 0xbd, 0x01, 0xa0, 0x48, 0x04, 0x3a, 0x00, 0x33, 0x2a, 0x96, + 0x9a, 0x61, 0x3e, 0x3f, 0x0f, 0xf3, 0xf9, 0x21, 0x66, 0xc2, 0x1c, 0x78, + 0x01, 0x31, 0xb8, 0x7c, 0x03, 0xc0, 0x41, 0x04, 0x3a, 0x00, 0x33, 0xa3, + 0x94, 0x85, 0xca, 0xc8, 0xfc, 0xd6, 0x2d, 0x66, 0x59, 0xea, 0x35, 0x40, + 0x1b, 0x88, 0x8d, 0xd0, 0x93, 0x7a, 0x03, 0x40, 0x91, 0x08, 0x74, 0x00, + 0x8e, 0x4e, 0x76, 0x50, 0x98, 0x97, 0x84, 0x39, 0x00, 0xc0, 0x91, 0x12, + 0xe8, 0x00, 0x1c, 0x99, 0x2c, 0x86, 0xca, 0xf0, 0xd3, 0x61, 0x5e, 0x2e, + 0xa5, 0x5e, 0x03, 0x00, 0xd0, 0xf6, 0x04, 0x3a, 0x00, 0x87, 0x27, 0x36, + 0xc3, 0x7c, 0xa8, 0xf5, 0x06, 0x70, 0xc2, 0x1c, 0x00, 0x60, 0xe6, 0x08, + 0x74, 0x00, 0x0e, 0x4d, 0x1e, 0xe6, 0xcd, 0x4b, 0xa5, 0x55, 0x9b, 0x61, + 0x5e, 0xf1, 0xe5, 0x03, 0x00, 0x60, 0xa6, 0xf9, 0x0e, 0x0b, 0x80, 0x17, + 0x55, 0x5e, 0x38, 0x18, 0x2a, 0xa3, 0x0b, 0x43, 0x56, 0xf5, 0x65, 0x03, + 0x00, 0x60, 0xb6, 0xf8, 0x4e, 0x0b, 0x80, 0xe7, 0x55, 0x5e, 0x90, 0x87, + 0xf9, 0xe2, 0x05, 0x79, 0x98, 0x57, 0x52, 0x4f, 0x01, 0x00, 0xe8, 0x78, + 0x02, 0x1d, 0x80, 0x67, 0x29, 0xcd, 0x9f, 0x17, 0xaa, 0x8b, 0x17, 0x85, + 0xac, 0x47, 0x98, 0x03, 0x00, 0xcc, 0x15, 0x81, 0x0e, 0xc0, 0x8f, 0x95, + 0x86, 0x9a, 0x61, 0xbe, 0x30, 0x64, 0xbd, 0xd5, 0xd4, 0x53, 0x00, 0x00, + 0xba, 0x8e, 0x40, 0x07, 0x20, 0x94, 0x06, 0xfb, 0x43, 0x25, 0x0f, 0xf3, + 0x52, 0x5f, 0x4f, 0xea, 0x29, 0x00, 0x00, 0x5d, 0x4b, 0xa0, 0x03, 0x74, + 0xb1, 0xd2, 0xbc, 0xbe, 0x50, 0x59, 0x92, 0x87, 0x79, 0x7f, 0x6f, 0xea, + 0x29, 0x00, 0x00, 0x5d, 0x4f, 0xa0, 0x03, 0x74, 0xa1, 0x6c, 0x5e, 0x6f, + 0xeb, 0x67, 0xcc, 0x4b, 0xf3, 0x84, 0x39, 0x00, 0x40, 0x51, 0x08, 0x74, + 0x80, 0x2e, 0x92, 0xf5, 0xf7, 0x3c, 0x15, 0xe6, 0x03, 0x7d, 0xa9, 0xa7, + 0x00, 0x00, 0xf0, 0x0c, 0x02, 0x1d, 0xa0, 0x0b, 0x34, 0xdf, 0xf4, 0xad, + 0x92, 0x87, 0x79, 0x79, 0xa8, 0x3f, 0xf5, 0x14, 0x00, 0x00, 0x9e, 0x87, + 0x40, 0x07, 0xe8, 0x60, 0xad, 0x30, 0x1f, 0x5d, 0x18, 0xca, 0xf3, 0xe7, + 0xa5, 0x9e, 0x02, 0x00, 0xc0, 0x8b, 0x10, 0xe8, 0x00, 0x1d, 0x28, 0xf6, + 0x54, 0x42, 0xb5, 0x19, 0xe6, 0x0b, 0x06, 0x52, 0x4f, 0x01, 0x00, 0xe0, + 0x10, 0x09, 0x74, 0x80, 0x0e, 0x12, 0xab, 0xe5, 0xd6, 0x33, 0xe6, 0x95, + 0x05, 0x83, 0xf9, 0x21, 0xf5, 0x1a, 0x00, 0x00, 0x0e, 0x87, 0x40, 0x07, + 0x38, 0x48, 0xa3, 0xde, 0xa8, 0xa7, 0xde, 0x70, 0x24, 0x62, 0xa5, 0x19, + 0xe6, 0x0b, 0x42, 0x79, 0xe1, 0x60, 0x88, 0x51, 0x99, 0x03, 0x00, 0xb4, + 0x23, 0x81, 0x0e, 0x70, 0xb0, 0x46, 0x98, 0x4e, 0x3d, 0xe1, 0x70, 0xc4, + 0x72, 0x29, 0x54, 0x6a, 0x79, 0x98, 0x0f, 0x0f, 0x09, 0x73, 0x00, 0x80, + 0x36, 0x27, 0xd0, 0x01, 0x0e, 0x56, 0xaf, 0xef, 0x4b, 0x3d, 0xe1, 0x50, + 0xb4, 0xc2, 0x7c, 0xe4, 0xe9, 0x30, 0xcf, 0x84, 0x39, 0x00, 0x40, 0x27, + 0x10, 0xe8, 0x00, 0x07, 0x69, 0x4c, 0x4d, 0x3d, 0x1c, 0xb2, 0x2c, 0xf5, + 0x8c, 0xe7, 0x57, 0xca, 0x5a, 0x61, 0x5e, 0x19, 0x69, 0x86, 0x79, 0x81, + 0x77, 0x02, 0x00, 0x70, 0xd8, 0x04, 0x3a, 0xc0, 0x41, 0xa6, 0x26, 0xf6, + 0xdf, 0x57, 0x9e, 0xd7, 0xf7, 0xd3, 0xa9, 0x77, 0x3c, 0x4b, 0xd6, 0x0c, + 0xf3, 0xf9, 0xad, 0x5b, 0x2c, 0x09, 0x73, 0x00, 0x80, 0x4e, 0x24, 0xd0, + 0x01, 0x0e, 0x32, 0xf5, 0xe0, 0x43, 0x7f, 0x57, 0x3e, 0x7e, 0xd9, 0x9b, + 0x52, 0xef, 0xf8, 0xb1, 0x66, 0x98, 0x0f, 0x0f, 0xb5, 0x7e, 0xce, 0x5c, + 0x98, 0x03, 0x00, 0x74, 0x36, 0x81, 0x0e, 0x70, 0x90, 0xfd, 0x0f, 0x3f, + 0xf6, 0xb5, 0xbe, 0x93, 0x8f, 0x0d, 0x8d, 0xa9, 0xc4, 0xef, 0x15, 0x17, + 0xe3, 0x3f, 0x85, 0x79, 0xb9, 0x94, 0x76, 0x0b, 0x00, 0x00, 0x73, 0x42, + 0xa0, 0x03, 0x1c, 0x64, 0xef, 0x81, 0x78, 0xc7, 0xfc, 0x94, 0x03, 0xf2, + 0x30, 0x2f, 0x2f, 0x1a, 0x0c, 0xd5, 0xda, 0xc2, 0x10, 0x2b, 0xc2, 0x1c, + 0x00, 0xa0, 0x9b, 0x08, 0x74, 0x80, 0x83, 0x3c, 0xf2, 0xc8, 0x2d, 0x8f, + 0xbc, 0xec, 0x9c, 0x2b, 0x1e, 0x0b, 0x95, 0xf2, 0xdc, 0x76, 0x7a, 0x33, + 0xcc, 0x17, 0x0e, 0xb6, 0xae, 0x65, 0x9e, 0x55, 0xfc, 0xa7, 0x19, 0x00, + 0xa0, 0x1b, 0xf9, 0x2e, 0x10, 0xe0, 0x19, 0xf6, 0x3f, 0xf4, 0xf8, 0x2d, + 0x3d, 0x4b, 0x16, 0xfd, 0xe6, 0x5c, 0x7d, 0xbe, 0xf2, 0x82, 0x3c, 0xcc, + 0x17, 0xe7, 0x61, 0x5e, 0xad, 0xcc, 0xd5, 0xa7, 0x04, 0x00, 0xa0, 0x80, + 0x04, 0x3a, 0xc0, 0x33, 0x4c, 0xdc, 0xb3, 0xf3, 0x83, 0xbd, 0xcb, 0x46, + 0x7f, 0xb3, 0x31, 0x35, 0x35, 0xab, 0x9f, 0xa7, 0x34, 0x7f, 0x20, 0x54, + 0x17, 0x2f, 0x0c, 0x59, 0x8f, 0x30, 0x07, 0x00, 0x40, 0xa0, 0x03, 0x3c, + 0xcb, 0x03, 0xbb, 0x3f, 0xfb, 0xcd, 0x97, 0xaf, 0x79, 0xdf, 0x43, 0xf9, + 0xc3, 0xe1, 0xd9, 0xf8, 0xf8, 0xa5, 0xa1, 0x79, 0x4f, 0x85, 0x79, 0x6f, + 0x75, 0x36, 0x3e, 0x3c, 0x00, 0x00, 0x6d, 0x4a, 0xa0, 0x03, 0x3c, 0x87, + 0xc9, 0x89, 0xfd, 0x97, 0x97, 0x7b, 0x2a, 0x9f, 0x9c, 0xc9, 0x8f, 0x59, + 0x1a, 0xec, 0x0f, 0x95, 0x3c, 0xcc, 0x4b, 0x7d, 0x3d, 0x33, 0xf9, 0x61, + 0x01, 0x00, 0xe8, 0x10, 0x02, 0x1d, 0xe0, 0x39, 0x7c, 0xf7, 0xaf, 0xff, + 0xe0, 0x4f, 0x7f, 0xea, 0x9f, 0x6f, 0xfa, 0xf0, 0xd4, 0x63, 0x7b, 0x8f, + 0xfa, 0x59, 0xf4, 0xd2, 0x40, 0x5f, 0x1e, 0xe6, 0x8b, 0x42, 0xa9, 0x5f, + 0x98, 0x03, 0x00, 0xf0, 0xfc, 0x04, 0x3a, 0xc0, 0xf3, 0xe8, 0x7f, 0xc9, + 0x92, 0x7f, 0xb1, 0xf7, 0x9e, 0x5d, 0xb7, 0x4c, 0x3d, 0xf2, 0xc4, 0x11, + 0xfd, 0xfa, 0x6c, 0x5e, 0x6f, 0xa8, 0x36, 0xc3, 0x3c, 0xbf, 0x07, 0x00, + 0x80, 0x17, 0x23, 0xd0, 0x01, 0x9e, 0xc7, 0xff, 0xfe, 0xc0, 0x45, 0x9f, + 0x5f, 0xb1, 0x7e, 0xcb, 0xb7, 0xf7, 0x57, 0xca, 0xa7, 0x4e, 0xee, 0x7c, + 0xe4, 0x90, 0x7f, 0x5d, 0xd6, 0xdf, 0xdb, 0xfa, 0x19, 0xf3, 0xe6, 0x33, + 0xe7, 0x00, 0x00, 0x70, 0xa8, 0x04, 0x3a, 0xc0, 0xf3, 0xa8, 0xd7, 0xeb, + 0x8d, 0x95, 0xeb, 0xb6, 0xbc, 0x3f, 0x8f, 0xed, 0x4f, 0x95, 0xe7, 0xcf, + 0x0b, 0x07, 0xf2, 0x48, 0x9f, 0x7e, 0x7c, 0x6f, 0x08, 0x8d, 0xc6, 0xb3, + 0xfe, 0xb7, 0xb1, 0x54, 0x0a, 0xa5, 0xa1, 0xfe, 0xd6, 0xb5, 0xcc, 0x3d, + 0x63, 0x0e, 0x00, 0xc0, 0x91, 0x10, 0xe8, 0x00, 0x2f, 0xe0, 0xf6, 0x0f, + 0x3d, 0xf8, 0xe7, 0x67, 0xac, 0x5b, 0xb2, 0x3e, 0xeb, 0xad, 0xfe, 0x54, + 0xef, 0x31, 0x8b, 0x43, 0xa3, 0xde, 0x08, 0xf5, 0x89, 0xfd, 0xa1, 0x71, + 0x60, 0xb2, 0xd5, 0xe9, 0xb1, 0x94, 0xb5, 0x2e, 0x93, 0xe6, 0x1d, 0xd9, + 0x01, 0x00, 0x38, 0x5a, 0x02, 0x1d, 0xe0, 0x05, 0xd4, 0xeb, 0x1b, 0xeb, + 0x2b, 0xd7, 0xdf, 0x7c, 0x65, 0x88, 0xd9, 0x7f, 0x6d, 0x9e, 0x63, 0x16, + 0x9f, 0x7a, 0x86, 0xdc, 0xb3, 0xe4, 0x00, 0x00, 0xcc, 0x30, 0x81, 0x0e, + 0xf0, 0x22, 0x6e, 0xbb, 0xfe, 0xa2, 0x2f, 0xad, 0x5c, 0xbf, 0xf5, 0xaf, + 0x42, 0x0c, 0xbf, 0x9a, 0x7a, 0x0b, 0x00, 0x00, 0x9d, 0x4b, 0xa0, 0x03, + 0x1c, 0x82, 0xc9, 0x38, 0x79, 0x59, 0x39, 0x54, 0xfe, 0x59, 0x0c, 0x61, + 0x41, 0xea, 0x2d, 0x00, 0x00, 0x74, 0x26, 0x81, 0x0e, 0x70, 0x08, 0xbe, + 0xf1, 0xc1, 0x4b, 0xee, 0x5f, 0xb1, 0x61, 0xeb, 0xa5, 0xf9, 0xc3, 0x4f, + 0xa6, 0xde, 0x02, 0x00, 0x40, 0x67, 0x12, 0xe8, 0x00, 0x87, 0xe8, 0xab, + 0x1f, 0xbc, 0xe0, 0x4f, 0xf3, 0x48, 0x7f, 0x5d, 0x0c, 0xe1, 0xad, 0xa9, + 0xb7, 0x00, 0x00, 0xd0, 0x79, 0x04, 0x3a, 0xc0, 0x61, 0x98, 0xae, 0x3f, + 0x79, 0x41, 0x29, 0x1b, 0x38, 0x3d, 0x8f, 0xf4, 0x9f, 0x4a, 0xbd, 0x05, + 0x00, 0x80, 0xce, 0x22, 0xd0, 0x01, 0x0e, 0xc3, 0xd7, 0x6f, 0xb8, 0x62, + 0xcf, 0xca, 0xcb, 0x3f, 0xba, 0xa6, 0xd1, 0xd3, 0xf3, 0xbf, 0xf2, 0x48, + 0x1f, 0x4b, 0xbd, 0x07, 0x00, 0x80, 0xce, 0x21, 0xd0, 0x01, 0x0e, 0xd3, + 0x6d, 0x1f, 0xbe, 0xf4, 0xde, 0x33, 0x7f, 0xfb, 0x63, 0xe7, 0xc6, 0x72, + 0xe5, 0x6f, 0xf2, 0xe3, 0xc2, 0xd4, 0x7b, 0x00, 0x00, 0xe8, 0x0c, 0x02, + 0x1d, 0xe0, 0x08, 0xdc, 0xfe, 0xa1, 0x4b, 0xfe, 0x61, 0xe5, 0xfa, 0x8f, + 0xff, 0x62, 0x88, 0xa5, 0x2f, 0xe5, 0xc7, 0xe1, 0xd4, 0x7b, 0x00, 0x00, + 0x68, 0x7f, 0x02, 0x1d, 0xe0, 0x08, 0xdd, 0x76, 0xfd, 0xc5, 0x5f, 0x7b, + 0xd5, 0xba, 0xad, 0x3f, 0x5f, 0xca, 0xc2, 0xad, 0x31, 0x84, 0x63, 0x53, + 0xef, 0x01, 0x00, 0xa0, 0xbd, 0x09, 0x74, 0x80, 0xa3, 0xf0, 0xf5, 0x1b, + 0x2e, 0xf8, 0xc7, 0x57, 0x5c, 0xfe, 0xd1, 0xb3, 0xab, 0x3d, 0x3d, 0x7f, + 0x91, 0x1f, 0x5f, 0x9b, 0x7a, 0x0f, 0x00, 0x00, 0xed, 0x4b, 0xa0, 0x03, + 0x1c, 0xa5, 0x7f, 0xf8, 0xf0, 0xa5, 0x0f, 0x66, 0x6f, 0x7e, 0xf3, 0xaa, + 0x33, 0x8e, 0xfb, 0x85, 0xf5, 0x31, 0xc6, 0x6b, 0xf2, 0xbf, 0xd5, 0x93, + 0x7a, 0x13, 0x00, 0x00, 0xed, 0x47, 0xa0, 0x03, 0xcc, 0x80, 0xfa, 0xa7, + 0x3f, 0x3d, 0x9d, 0xdf, 0xfd, 0xfe, 0x8a, 0x2b, 0x6e, 0xfe, 0x8b, 0x58, + 0xc9, 0x6e, 0xcc, 0x1f, 0xff, 0x72, 0xea, 0x4d, 0x00, 0x00, 0xb4, 0x17, + 0x81, 0x0e, 0x30, 0x83, 0xbe, 0x7a, 0xd3, 0x45, 0xdf, 0xcf, 0xef, 0xde, + 0xb8, 0x72, 0xdd, 0xc7, 0xcf, 0x0e, 0x31, 0xdb, 0x1c, 0x62, 0x3c, 0x27, + 0xf5, 0x26, 0x00, 0x00, 0xda, 0x83, 0x40, 0x07, 0x98, 0x05, 0xb7, 0xdd, + 0x70, 0xf1, 0xdf, 0xe7, 0x77, 0xaf, 0x5b, 0x71, 0xe5, 0xc7, 0x5f, 0x1b, + 0x4b, 0xa5, 0xcd, 0xf9, 0xe3, 0x9f, 0x4b, 0xbd, 0x09, 0x00, 0x80, 0x62, + 0x13, 0xe8, 0x00, 0xb3, 0xe8, 0xab, 0x37, 0x5e, 0xfc, 0x95, 0xfc, 0xee, + 0xe7, 0xcf, 0x5c, 0xb7, 0xe5, 0x17, 0xb3, 0x2c, 0x6e, 0xca, 0x1f, 0xff, + 0x4c, 0xea, 0x4d, 0x00, 0x00, 0x14, 0x93, 0x40, 0x07, 0x98, 0x03, 0xb7, + 0xdf, 0x70, 0xe1, 0x5f, 0xe7, 0x77, 0x7f, 0x7d, 0xe6, 0xfa, 0x8f, 0x9f, + 0x1b, 0x63, 0x69, 0x73, 0x0c, 0xe1, 0xcc, 0xd4, 0x9b, 0x00, 0x00, 0x28, + 0x16, 0x81, 0x0e, 0x30, 0x87, 0x6e, 0xbf, 0xfe, 0xe2, 0x2f, 0x64, 0x59, + 0xf6, 0xc5, 0x33, 0xd6, 0xfd, 0xd1, 0x1b, 0x63, 0x88, 0xd7, 0x86, 0x10, + 0x4f, 0x4f, 0xbd, 0x09, 0x00, 0x80, 0x62, 0x10, 0xe8, 0x00, 0x73, 0xac, + 0x5e, 0xaf, 0x37, 0xf2, 0xbb, 0xff, 0x27, 0xcb, 0x36, 0xfd, 0xe7, 0x33, + 0x7f, 0x7b, 0xc9, 0xaf, 0x35, 0xb2, 0x70, 0x6d, 0x0c, 0xe1, 0xd4, 0xd4, + 0xbb, 0x00, 0x00, 0x48, 0x4b, 0xa0, 0x03, 0x24, 0x52, 0xaf, 0x6f, 0xac, + 0xe7, 0x77, 0x7f, 0x91, 0xbd, 0xf9, 0xcd, 0x7f, 0xf5, 0xaa, 0xe3, 0x7e, + 0xfe, 0xad, 0x59, 0xcc, 0x9a, 0xd7, 0x50, 0x3f, 0x21, 0xf5, 0x2e, 0x00, + 0x00, 0xd2, 0x10, 0xe8, 0x00, 0x89, 0x3d, 0x7d, 0x0d, 0xf5, 0x4f, 0xae, + 0xbc, 0xf0, 0xc2, 0xff, 0x18, 0xe6, 0xbf, 0xf2, 0xb7, 0x1a, 0x31, 0xfe, + 0x6e, 0x0c, 0xe1, 0xd8, 0xd4, 0xbb, 0x00, 0x00, 0x98, 0x5b, 0x02, 0x1d, + 0xa0, 0x20, 0x6e, 0xdb, 0xb2, 0x65, 0x32, 0xbf, 0xfb, 0xe3, 0xd3, 0xdf, + 0xbc, 0xe9, 0x93, 0xbd, 0x2f, 0x59, 0xf2, 0x8e, 0xd0, 0x08, 0x57, 0x85, + 0x18, 0xc6, 0x53, 0xef, 0x02, 0xe0, 0xff, 0x6f, 0xef, 0xce, 0xe3, 0xf4, + 0xae, 0xeb, 0x7b, 0xef, 0x7f, 0xbf, 0xbf, 0xeb, 0x9a, 0x2c, 0x10, 0x16, + 0x41, 0x31, 0xb4, 0xa7, 0xa2, 0xf5, 0x6e, 0xb8, 0x6d, 0xa5, 0x4a, 0xc2, + 0x68, 0xab, 0x56, 0x6b, 0x6c, 0xa9, 0x42, 0x15, 0x92, 0x19, 0x9d, 0xcc, + 0x4c, 0x20, 0x82, 0x62, 0x12, 0x92, 0xb2, 0x65, 0x05, 0x84, 0xc9, 0x04, + 0x59, 0xb2, 0x02, 0x31, 0xab, 0x28, 0x1c, 0x90, 0xcc, 0xc2, 0xc9, 0x02, + 0x62, 0x50, 0x69, 0x9b, 0x9e, 0xd2, 0x5b, 0x5b, 0x3b, 0x2c, 0xda, 0x78, + 0x6c, 0xc1, 0xda, 0xba, 0xb4, 0x15, 0xad, 0xc5, 0x05, 0x04, 0x02, 0xc9, + 0x75, 0x9d, 0xef, 0x08, 0xe7, 0x3e, 0xd4, 0x8a, 0xe6, 0x82, 0x24, 0xdf, + 0xdf, 0xcc, 0x3c, 0x9f, 0x8f, 0xc7, 0x75, 0x3e, 0xbf, 0x5f, 0xfa, 0x38, + 0xe4, 0x3d, 0xff, 0x48, 0x5e, 0x90, 0x0c, 0x00, 0x07, 0x87, 0x40, 0x07, + 0x28, 0x99, 0x5d, 0xfd, 0x5d, 0x4f, 0xa5, 0xb3, 0xfe, 0xb8, 0x79, 0xd7, + 0xdd, 0xf4, 0xb2, 0xca, 0x98, 0xd9, 0xb1, 0x88, 0x8b, 0xd2, 0xfb, 0x31, + 0xb9, 0x77, 0x01, 0x00, 0x70, 0x60, 0x09, 0x74, 0x80, 0x92, 0xfa, 0xe6, + 0xaa, 0x0b, 0x9e, 0x48, 0x67, 0xf5, 0x89, 0x0b, 0x57, 0x6d, 0xaa, 0xc4, + 0x71, 0x73, 0x62, 0x0c, 0x0b, 0xd3, 0xfb, 0xd1, 0xb9, 0x77, 0x01, 0x00, + 0x70, 0x60, 0x08, 0x74, 0x80, 0x92, 0x7b, 0x60, 0xf9, 0xbc, 0x9f, 0xa4, + 0xb3, 0xfc, 0xf8, 0xf3, 0xd7, 0x6e, 0x3c, 0x7c, 0x4c, 0xf5, 0x82, 0x7a, + 0x8c, 0x17, 0xc6, 0x10, 0x8e, 0xcc, 0xbd, 0x0b, 0x00, 0x80, 0xfd, 0x4b, + 0xa0, 0x03, 0x0c, 0x11, 0x0f, 0x5e, 0x3f, 0xf7, 0xc7, 0xe9, 0x2c, 0x7d, + 0xdd, 0x9c, 0x8d, 0x1f, 0x6d, 0x1a, 0x17, 0xe7, 0xc7, 0x18, 0xce, 0x4b, + 0xef, 0xe3, 0x72, 0xef, 0x02, 0x00, 0x60, 0xff, 0x10, 0xe8, 0x00, 0x43, + 0xcc, 0x97, 0xd7, 0xcd, 0xfa, 0x41, 0x3a, 0x97, 0x36, 0x2f, 0x58, 0x7d, + 0x6d, 0x28, 0x0e, 0x5d, 0x10, 0x62, 0x98, 0x9b, 0xde, 0x0f, 0xc9, 0xbd, + 0x0b, 0x00, 0x80, 0x17, 0x47, 0xa0, 0x03, 0x0c, 0x51, 0x03, 0x2b, 0x2e, + 0xfa, 0x7e, 0x3a, 0x8b, 0x4e, 0x58, 0xb4, 0xe1, 0xda, 0x31, 0xb1, 0xb8, + 0x38, 0x3d, 0xcf, 0x4c, 0x9f, 0xd1, 0x99, 0x67, 0x01, 0x00, 0xf0, 0x02, + 0x09, 0x74, 0x80, 0xe7, 0x68, 0xed, 0xec, 0x9b, 0x57, 0xaf, 0xd5, 0xbe, + 0xba, 0xb5, 0xb7, 0xe3, 0x33, 0xb9, 0xb7, 0xec, 0xab, 0x5d, 0xcb, 0x66, + 0x3f, 0x9c, 0xce, 0xf9, 0xbf, 0x3d, 0x7f, 0xc3, 0xca, 0xd1, 0xd5, 0x78, + 0x49, 0x3d, 0xc4, 0xb3, 0x63, 0x08, 0xa3, 0x72, 0xef, 0x02, 0x00, 0xa0, + 0x31, 0x02, 0x1d, 0xe0, 0x39, 0xea, 0xf5, 0xfa, 0xcb, 0x62, 0x51, 0x7c, + 0x3a, 0x85, 0xfa, 0xb5, 0x8f, 0x7c, 0x67, 0xf7, 0x87, 0x77, 0xee, 0x9c, + 0xf1, 0x64, 0xee, 0x4d, 0xfb, 0xea, 0xef, 0x56, 0xce, 0xfe, 0x76, 0x3a, + 0xb3, 0x27, 0x5e, 0xb4, 0x69, 0x59, 0xa5, 0x29, 0x5c, 0x16, 0x62, 0x38, + 0x33, 0xf8, 0xdf, 0x79, 0x00, 0x80, 0x21, 0xc3, 0x2f, 0xdc, 0x00, 0xfe, + 0xab, 0x22, 0x7d, 0xe6, 0x1d, 0x75, 0xec, 0xe8, 0x77, 0xb5, 0xb6, 0xf7, + 0x7c, 0x68, 0x4b, 0x6f, 0xc7, 0xe7, 0x73, 0x0f, 0x6a, 0xc4, 0xfd, 0xab, + 0x67, 0x7e, 0x23, 0x9d, 0x0f, 0x9c, 0x38, 0x7f, 0xfd, 0xb2, 0x6a, 0xa5, + 0xd2, 0x95, 0x42, 0x7d, 0x5a, 0x78, 0xe6, 0x6b, 0x02, 0x00, 0xa0, 0xc4, + 0x04, 0x3a, 0xc0, 0xf3, 0xfb, 0xcd, 0x50, 0x14, 0x7f, 0xd5, 0xda, 0xd1, + 0x7b, 0xf3, 0xde, 0xf0, 0xe4, 0xa5, 0xdb, 0x7b, 0xce, 0xfa, 0xb7, 0xdc, + 0x83, 0x1a, 0xf1, 0xc0, 0xca, 0x73, 0x1f, 0x4a, 0xa7, 0x73, 0xd2, 0x82, + 0xb5, 0x57, 0xc6, 0x4a, 0x53, 0x57, 0x0c, 0xe1, 0xbd, 0xe9, 0x3d, 0xe6, + 0xde, 0x05, 0x00, 0xc0, 0xcf, 0x27, 0xd0, 0x01, 0x7e, 0xb1, 0x18, 0x62, + 0x7c, 0x7f, 0x25, 0x8c, 0x7d, 0x5f, 0x0a, 0xf5, 0xeb, 0x77, 0x3f, 0x1a, + 0x56, 0xdc, 0x79, 0x67, 0xfb, 0x0f, 0x72, 0x8f, 0x6a, 0xc4, 0x7d, 0x2b, + 0xe6, 0x7e, 0x35, 0x9d, 0xb6, 0x89, 0x8b, 0x36, 0x5c, 0x5d, 0x89, 0x45, + 0x77, 0x7a, 0x7e, 0x77, 0x10, 0xea, 0x00, 0x00, 0xa5, 0x23, 0xd0, 0x01, + 0xf6, 0xcd, 0x21, 0x29, 0xd4, 0x2f, 0x1e, 0x7d, 0x78, 0x98, 0xdd, 0xd2, + 0xd9, 0x77, 0xed, 0x93, 0x3f, 0xac, 0x5d, 0xb7, 0x63, 0x47, 0xc7, 0x8f, + 0x73, 0x8f, 0x6a, 0xc4, 0xfd, 0xcb, 0x66, 0x7f, 0x29, 0x9d, 0xd3, 0x26, + 0x2e, 0xde, 0xf4, 0x86, 0x4a, 0xbd, 0x7e, 0x45, 0xfa, 0x7a, 0x4e, 0xce, + 0xbd, 0x09, 0x00, 0x80, 0xff, 0x4b, 0xa0, 0x03, 0x34, 0xe6, 0xc8, 0x18, + 0x42, 0xf7, 0xd8, 0x23, 0x8b, 0xf3, 0x5a, 0x3b, 0xfb, 0x96, 0x3f, 0xf6, + 0x1f, 0x7b, 0xd6, 0x7d, 0xf6, 0xb3, 0xd3, 0x7f, 0x92, 0x7b, 0x54, 0x23, + 0xee, 0xbf, 0x66, 0xe6, 0xdf, 0xa6, 0xf3, 0x47, 0x13, 0x17, 0x6d, 0x7a, + 0x73, 0x25, 0xa6, 0x50, 0x0f, 0xf1, 0xed, 0xb9, 0x37, 0x01, 0x00, 0x20, + 0xd0, 0x01, 0x5e, 0xa8, 0xa3, 0xd3, 0x67, 0xd9, 0xb8, 0xa3, 0xab, 0xf3, + 0x5a, 0xda, 0x7b, 0x97, 0x3d, 0x5c, 0xfb, 0xee, 0x86, 0xcf, 0xf7, 0x5f, + 0xf0, 0x44, 0xee, 0x51, 0x8d, 0xb8, 0x7f, 0xd9, 0xcc, 0xc1, 0x6f, 0x7e, + 0x37, 0xb9, 0x79, 0xe1, 0xa6, 0xc9, 0xa1, 0x08, 0x29, 0xd4, 0xc3, 0x9b, + 0x72, 0x6f, 0x02, 0x00, 0x18, 0xc9, 0x04, 0x3a, 0xc0, 0x8b, 0x73, 0x4c, + 0x2c, 0xe2, 0xaa, 0x63, 0x8b, 0xf1, 0x17, 0xb5, 0x76, 0xf6, 0x5d, 0xf5, + 0xe0, 0x9e, 0x07, 0x3f, 0xbe, 0xab, 0xbf, 0xeb, 0xa9, 0xdc, 0xa3, 0x1a, + 0x31, 0xb0, 0x7c, 0xe6, 0xce, 0x74, 0x76, 0x4e, 0x5a, 0xb4, 0xfe, 0x94, + 0x22, 0x56, 0x06, 0xff, 0x8c, 0xfa, 0x49, 0xb9, 0x37, 0x01, 0x00, 0x8c, + 0x44, 0x02, 0x1d, 0x60, 0xff, 0xf8, 0xd5, 0xf4, 0x59, 0x37, 0xa1, 0x7a, + 0xfc, 0x82, 0x14, 0xea, 0x57, 0x7e, 0xf3, 0xa1, 0x47, 0x6f, 0x1e, 0x18, + 0x38, 0xe7, 0xe9, 0xdc, 0xa3, 0x1a, 0x71, 0xdf, 0xb2, 0x73, 0xef, 0x2a, + 0x8a, 0xe2, 0x33, 0x27, 0x2e, 0x5c, 0xff, 0xee, 0x22, 0xc4, 0xa5, 0xe9, + 0x87, 0x5e, 0x97, 0x7b, 0x13, 0x00, 0xc0, 0x48, 0x22, 0xd0, 0x01, 0xf6, + 0xa3, 0x18, 0xc2, 0x2b, 0xd3, 0xb9, 0xe1, 0xb8, 0x09, 0x87, 0x2d, 0x6e, + 0x6d, 0xef, 0x5d, 0xba, 0xad, 0x76, 0xfb, 0xe6, 0x5a, 0x7f, 0xff, 0xde, + 0xdc, 0xbb, 0xf6, 0x55, 0xad, 0x56, 0xab, 0xa7, 0xf3, 0xa9, 0xa2, 0xe8, + 0xfe, 0xf4, 0xa4, 0xf9, 0xe3, 0x5b, 0xeb, 0x45, 0x58, 0x92, 0xbe, 0xa6, + 0xd7, 0xe4, 0xde, 0x05, 0x00, 0x30, 0x12, 0x08, 0x74, 0x80, 0x03, 0xe3, + 0xd5, 0xa1, 0x88, 0x37, 0x4f, 0x8d, 0xa7, 0x2f, 0x9e, 0xda, 0xd9, 0xb7, + 0xe4, 0xf6, 0xde, 0x07, 0xb7, 0xd4, 0x6a, 0x5d, 0xb5, 0xdc, 0xa3, 0xf6, + 0xd5, 0xb3, 0x5b, 0x6f, 0x2b, 0xda, 0xda, 0xb6, 0x4e, 0x3c, 0xee, 0xed, + 0xed, 0xb1, 0x88, 0x97, 0xa7, 0xf7, 0xdf, 0xc8, 0xbd, 0x0b, 0x00, 0x60, + 0x38, 0x13, 0xe8, 0x00, 0x07, 0x52, 0x8c, 0xaf, 0x29, 0x42, 0xe8, 0x9f, + 0x3a, 0x6d, 0xc2, 0x97, 0x9f, 0x09, 0xf5, 0x8e, 0x3b, 0x9e, 0xfd, 0xb7, + 0xd4, 0x43, 0xc2, 0xb3, 0xff, 0xf6, 0xff, 0xd6, 0x62, 0x72, 0x77, 0xdf, + 0xc4, 0xe6, 0xf1, 0x67, 0xc6, 0x18, 0x2e, 0x0b, 0xcf, 0xfc, 0x2e, 0x01, + 0x00, 0x00, 0xf6, 0x33, 0x81, 0x0e, 0x70, 0x30, 0xc4, 0xf8, 0xba, 0x14, + 0xea, 0xdb, 0xa7, 0xb4, 0xf7, 0xdc, 0x37, 0x65, 0x5a, 0xef, 0xe5, 0xdb, + 0xfb, 0xda, 0xef, 0xca, 0x3d, 0xa9, 0x11, 0xb5, 0x9d, 0x5d, 0x7b, 0xd2, + 0xb9, 0xf1, 0x84, 0xb6, 0xee, 0x5b, 0xc7, 0xbc, 0x6a, 0xfc, 0x07, 0x43, + 0x3d, 0x5c, 0x12, 0xe2, 0x4f, 0xff, 0xdc, 0x3d, 0x00, 0x00, 0xfb, 0x89, + 0x40, 0x07, 0x38, 0x88, 0x62, 0x08, 0x93, 0x2a, 0x95, 0xb8, 0xa3, 0xb5, + 0xb3, 0xef, 0x0b, 0xf5, 0x10, 0x2e, 0xdb, 0xba, 0x79, 0xda, 0xce, 0xdc, + 0x9b, 0x1a, 0xf1, 0xec, 0x77, 0xa8, 0x5f, 0xff, 0xeb, 0x67, 0x77, 0xdf, + 0x78, 0xf4, 0x31, 0xe3, 0x3f, 0x94, 0x9e, 0x2f, 0x4e, 0x9f, 0xf1, 0x99, + 0x67, 0x01, 0x00, 0x0c, 0x0b, 0x02, 0x1d, 0x20, 0x8f, 0x37, 0xa5, 0x58, + 0xff, 0xf3, 0x96, 0xce, 0xbe, 0xbf, 0xac, 0xd7, 0xf7, 0x5e, 0xb6, 0xad, + 0xa7, 0xf3, 0xaf, 0x72, 0x0f, 0x6a, 0xc4, 0x3f, 0xdd, 0xd8, 0xf5, 0x64, + 0x3a, 0x6b, 0x9a, 0x67, 0x75, 0x7f, 0xbc, 0x7e, 0xc4, 0xf8, 0xb9, 0x31, + 0x86, 0x05, 0xe9, 0xfd, 0xa5, 0xb9, 0x77, 0x01, 0x00, 0x0c, 0x65, 0x02, + 0x1d, 0x20, 0xa3, 0x14, 0xe9, 0x6f, 0x8b, 0xb1, 0x72, 0x4f, 0x6b, 0x47, + 0xef, 0xdd, 0x7b, 0xc3, 0xde, 0xcb, 0xb7, 0xf7, 0x4c, 0xff, 0x62, 0xee, + 0x4d, 0x8d, 0x18, 0xd8, 0xd8, 0xf5, 0x78, 0x3a, 0xcb, 0x7f, 0x73, 0xf1, + 0x8a, 0x0d, 0x87, 0xd6, 0x0f, 0xbb, 0xb0, 0x1e, 0xe3, 0x85, 0xe9, 0x6b, + 0x3a, 0x32, 0xf7, 0x2e, 0x00, 0x80, 0xa1, 0x48, 0xa0, 0x03, 0x94, 0x41, + 0x8c, 0x27, 0x57, 0x42, 0xf5, 0x0f, 0x5b, 0x3b, 0xfb, 0xee, 0xac, 0xed, + 0xa9, 0x77, 0x6d, 0xeb, 0x6f, 0xff, 0x52, 0xee, 0x49, 0x8d, 0xf8, 0xea, + 0x35, 0x0b, 0x1e, 0x4d, 0x67, 0xe9, 0xc4, 0x8b, 0xae, 0x5f, 0x53, 0x34, + 0x8d, 0x4e, 0x91, 0x1e, 0x2f, 0x08, 0x31, 0x1c, 0x9e, 0x7b, 0x17, 0x00, + 0xc0, 0x50, 0x22, 0xd0, 0x01, 0xca, 0x23, 0xa6, 0xcf, 0x7b, 0x8a, 0x6a, + 0x7c, 0x77, 0x4b, 0x67, 0xdf, 0xf6, 0x50, 0xdb, 0xd3, 0xb5, 0xb5, 0x77, + 0xfa, 0x57, 0x72, 0x8f, 0x6a, 0xc4, 0xfd, 0xab, 0xcf, 0xff, 0x61, 0x3a, + 0x5d, 0xcd, 0x0b, 0x56, 0x7f, 0x34, 0x14, 0x87, 0x2e, 0x48, 0x5f, 0xd1, + 0xdc, 0xf4, 0x7e, 0x48, 0xee, 0x5d, 0x00, 0x00, 0x43, 0x81, 0x40, 0x07, + 0x28, 0x9f, 0x98, 0x4a, 0x7d, 0x6a, 0x28, 0xaa, 0xa7, 0xb7, 0x76, 0xf4, + 0xf6, 0xd5, 0xeb, 0x7b, 0xba, 0xb7, 0xf6, 0x9e, 0xf1, 0x50, 0xee, 0x51, + 0x8d, 0x18, 0x58, 0x71, 0xd1, 0xf7, 0xd3, 0x59, 0x74, 0xc2, 0xa2, 0x0d, + 0xd7, 0x8e, 0x8e, 0xc5, 0xa2, 0xf4, 0xf5, 0xcc, 0x4a, 0xef, 0x63, 0x72, + 0xef, 0x02, 0x00, 0x28, 0x33, 0x81, 0x0e, 0x50, 0x5e, 0x45, 0x4a, 0xf5, + 0x8e, 0x18, 0x9b, 0xde, 0xd7, 0xd2, 0xd9, 0x77, 0xcb, 0x9e, 0x3d, 0x7b, + 0x3e, 0x72, 0x47, 0xff, 0xf4, 0x7f, 0xce, 0x3d, 0xaa, 0x11, 0xbb, 0x96, + 0xcd, 0x7e, 0x38, 0x9d, 0x0b, 0x27, 0xce, 0xdb, 0xb4, 0xb2, 0x68, 0x0a, + 0x97, 0xa4, 0xe7, 0x0f, 0xa6, 0x58, 0x1f, 0x95, 0x7b, 0x17, 0x00, 0x40, + 0x19, 0x09, 0x74, 0x80, 0xf2, 0xab, 0xa6, 0xa8, 0x3d, 0xbb, 0xa9, 0x5a, + 0x3d, 0xa3, 0xb5, 0xa3, 0xf7, 0xa6, 0xbd, 0x61, 0xf7, 0x47, 0xb6, 0xf7, + 0xbc, 0xff, 0xdb, 0xb9, 0x47, 0x35, 0xe2, 0xfe, 0x55, 0x33, 0xff, 0x35, + 0x9d, 0x39, 0x13, 0x2f, 0xda, 0xb4, 0xa2, 0xd2, 0x14, 0x2e, 0x0b, 0x31, + 0x9c, 0x19, 0xfc, 0x3d, 0x08, 0x00, 0xe0, 0x3f, 0xf1, 0x8b, 0x23, 0x80, + 0xa1, 0xa3, 0x29, 0xc4, 0xf8, 0xa1, 0x4a, 0x18, 0x33, 0xa3, 0xa5, 0xa3, + 0x77, 0x53, 0x7c, 0xaa, 0x76, 0xcd, 0x96, 0x2d, 0x9d, 0xdf, 0xc9, 0x3d, + 0xaa, 0x11, 0xf7, 0xaf, 0x9e, 0xf9, 0x8d, 0x74, 0x3e, 0x70, 0xd2, 0xbc, + 0x0d, 0x57, 0xc7, 0x6a, 0x4c, 0xa1, 0x1e, 0x3b, 0xd3, 0x7b, 0x25, 0xef, + 0x2a, 0x00, 0x80, 0x72, 0x10, 0xe8, 0x00, 0x43, 0xcf, 0xe8, 0x18, 0xe3, + 0x79, 0x61, 0x74, 0xe5, 0x83, 0x29, 0xd4, 0xd7, 0x3f, 0xb9, 0x77, 0xf7, + 0xf2, 0x1d, 0xfd, 0xef, 0xff, 0xf7, 0xdc, 0xa3, 0x1a, 0x71, 0xef, 0xaa, + 0xd9, 0xff, 0x98, 0xce, 0x8c, 0x93, 0x16, 0x6f, 0xb8, 0x26, 0x84, 0x62, + 0x49, 0x0c, 0xa1, 0x35, 0x0c, 0xfe, 0x96, 0x7e, 0x00, 0x80, 0x11, 0x4c, + 0xa0, 0x03, 0x0c, 0x5d, 0x87, 0xa4, 0x50, 0x9f, 0x3f, 0xb6, 0x3a, 0x66, + 0x56, 0x0a, 0xf5, 0x35, 0x4f, 0x3d, 0x1a, 0x56, 0xde, 0x79, 0x67, 0xfb, + 0x0f, 0x72, 0x8f, 0x6a, 0xc4, 0xbd, 0xd7, 0xcc, 0xfe, 0xfb, 0x74, 0xda, + 0x26, 0xcd, 0x5f, 0xfb, 0xba, 0xa2, 0xda, 0xb4, 0x24, 0x3d, 0x9f, 0x16, + 0x9e, 0xf9, 0x6e, 0xf6, 0x00, 0x00, 0x23, 0x8e, 0x40, 0x07, 0x18, 0xfa, + 0xc6, 0xa5, 0x50, 0xbf, 0x64, 0xf4, 0xe1, 0x61, 0x4e, 0x4b, 0x67, 0xdf, + 0xea, 0x27, 0x7f, 0x58, 0xbb, 0x6e, 0xc7, 0x8e, 0x8e, 0x1f, 0xe7, 0x1e, + 0xd5, 0x88, 0xfb, 0x56, 0xce, 0xfd, 0x72, 0x3a, 0x53, 0x26, 0x2d, 0x5e, + 0x77, 0x52, 0x11, 0xaa, 0xdd, 0xe9, 0xf9, 0x94, 0xdc, 0x9b, 0x00, 0x00, + 0x0e, 0x36, 0x81, 0x0e, 0x30, 0x7c, 0x1c, 0x11, 0x43, 0xe8, 0x1e, 0x7b, + 0x64, 0x71, 0x5e, 0x0a, 0xf5, 0x95, 0x3f, 0xd8, 0xf3, 0x83, 0xb5, 0x3b, + 0xfb, 0x67, 0x3f, 0x96, 0x7b, 0x54, 0x23, 0xee, 0xbb, 0x66, 0xce, 0xbd, + 0xe9, 0x9c, 0x3a, 0x69, 0xfe, 0x86, 0x37, 0xc5, 0x4a, 0x71, 0x45, 0x8c, + 0x61, 0x72, 0xee, 0x4d, 0x00, 0x00, 0x07, 0x8b, 0x40, 0x07, 0x18, 0x7e, + 0x8e, 0x4e, 0xa1, 0x7e, 0xf5, 0x51, 0xd5, 0x97, 0x5c, 0xd8, 0xd2, 0xde, + 0xbb, 0xec, 0xe1, 0xda, 0x77, 0x37, 0x7c, 0xbe, 0xff, 0x82, 0x27, 0x72, + 0x8f, 0x6a, 0xc4, 0x7d, 0x2b, 0x67, 0x7f, 0x21, 0x9d, 0x77, 0x34, 0x2f, + 0xde, 0xf0, 0xf6, 0x10, 0x8a, 0x2b, 0xd2, 0xf3, 0x9b, 0x73, 0x6f, 0x02, + 0x00, 0x38, 0xd0, 0x04, 0x3a, 0xc0, 0xf0, 0x75, 0x4c, 0x2c, 0xe2, 0xaa, + 0xf1, 0xc5, 0xf8, 0x79, 0xad, 0x9d, 0x7d, 0x57, 0x3e, 0xb8, 0xe7, 0xc1, + 0x8f, 0xef, 0xea, 0xef, 0x7a, 0x2a, 0xf7, 0xa8, 0x46, 0x0c, 0x5c, 0x33, + 0xfb, 0x2f, 0xd2, 0x79, 0x4b, 0xf3, 0xa2, 0x0d, 0x27, 0x87, 0xf8, 0xd3, + 0x50, 0x7f, 0x43, 0xee, 0x4d, 0x00, 0x00, 0x07, 0x8a, 0x40, 0x07, 0x18, + 0xe6, 0x62, 0x08, 0xbf, 0x92, 0xce, 0xba, 0xe3, 0xab, 0xc7, 0x2f, 0x9a, + 0xda, 0xd1, 0x7b, 0xe5, 0xb7, 0xbf, 0xf6, 0xd8, 0x4d, 0x03, 0x03, 0xe7, + 0x3c, 0x9d, 0x7b, 0x57, 0x23, 0x06, 0x96, 0xcd, 0xbe, 0xbb, 0x28, 0x8a, + 0x3f, 0x3d, 0x71, 0xe1, 0xfa, 0x77, 0x17, 0x21, 0x0e, 0xfe, 0x19, 0xf5, + 0xd7, 0xe7, 0xde, 0x04, 0x00, 0xb0, 0xbf, 0x09, 0x74, 0x80, 0x91, 0xe3, + 0x15, 0x45, 0x8c, 0x9b, 0x8e, 0x9b, 0x70, 0xd8, 0xc2, 0xd6, 0xf6, 0xde, + 0xa5, 0xdb, 0x6a, 0xb7, 0x6f, 0xae, 0xf5, 0xf7, 0xef, 0xcd, 0x3d, 0x6a, + 0x5f, 0xd5, 0x6a, 0xb5, 0x7a, 0x3a, 0x9f, 0x4a, 0xa1, 0x7e, 0xe7, 0x89, + 0x0b, 0xd7, 0xb5, 0x14, 0xa1, 0xe8, 0x4a, 0xef, 0xaf, 0xcd, 0xbd, 0x0b, + 0x00, 0x60, 0x7f, 0x11, 0xe8, 0x00, 0x23, 0xcf, 0xab, 0x43, 0x11, 0x6f, + 0x9e, 0x5a, 0x4c, 0xb9, 0xb8, 0xa5, 0xbd, 0xb7, 0x7b, 0x7b, 0xff, 0x43, + 0xb7, 0xd5, 0x6a, 0x5d, 0xb5, 0xdc, 0xa3, 0xf6, 0xd5, 0xb3, 0xa1, 0xbe, + 0xa5, 0x28, 0xba, 0xb7, 0x4d, 0x71, 0x19, 0x47, 0xaa, 0x00, 0x00, 0x20, + 0x00, 0x49, 0x44, 0x41, 0x54, 0x9a, 0xff, 0xf2, 0x69, 0xe9, 0x6b, 0x19, + 0x0c, 0xf5, 0x09, 0xb9, 0x77, 0x01, 0x00, 0xbc, 0x58, 0x02, 0x1d, 0x60, + 0xe4, 0xfa, 0x7f, 0x63, 0x11, 0x7b, 0xa7, 0xb6, 0x1f, 0x7f, 0x49, 0x0a, + 0xf5, 0x25, 0xdb, 0xfb, 0x3b, 0xb7, 0x3f, 0x1b, 0xbf, 0x43, 0xc2, 0xb3, + 0xff, 0x50, 0xa1, 0xa7, 0x98, 0xdc, 0x7d, 0xdb, 0xc4, 0xe6, 0xf1, 0x67, + 0xc6, 0x58, 0xff, 0x70, 0x08, 0xf1, 0x55, 0xb9, 0x77, 0x01, 0x00, 0xbc, + 0x50, 0x02, 0x1d, 0x80, 0x13, 0x52, 0xa8, 0x6f, 0x9d, 0xd2, 0xde, 0x73, + 0xdf, 0x94, 0x69, 0xbd, 0x97, 0x6f, 0xef, 0x6b, 0xbf, 0x2b, 0xf7, 0xa0, + 0x46, 0xd4, 0x76, 0x76, 0xed, 0x49, 0xe7, 0xc6, 0xe6, 0x59, 0xb3, 0x3e, + 0x59, 0x3f, 0xf2, 0xf5, 0x67, 0xc5, 0x7a, 0xfc, 0x70, 0x88, 0xe1, 0xd7, + 0x72, 0xef, 0x02, 0x00, 0x68, 0x94, 0x40, 0x07, 0xe0, 0xa7, 0x62, 0x08, + 0x93, 0x2a, 0x95, 0xb8, 0xa3, 0xa5, 0xb3, 0xef, 0xaf, 0x6b, 0x7b, 0xeb, + 0x83, 0xa1, 0xfe, 0x67, 0xb9, 0x37, 0x35, 0x62, 0x60, 0xe3, 0xc6, 0xc1, + 0x6f, 0x7c, 0xf7, 0xb1, 0xe3, 0xcf, 0x5f, 0x7b, 0xf3, 0xe1, 0x63, 0x9b, + 0x66, 0xa6, 0xe7, 0x8b, 0xd3, 0x67, 0x7c, 0xe6, 0x59, 0x00, 0x00, 0xfb, + 0x4c, 0xa0, 0x03, 0xf0, 0x9f, 0xa4, 0x50, 0xff, 0xdd, 0x14, 0xea, 0x7f, + 0xda, 0xda, 0xd9, 0x77, 0x4f, 0xa8, 0xd5, 0x2e, 0xdb, 0xd2, 0xdb, 0x71, + 0x4f, 0xee, 0x4d, 0x8d, 0x78, 0xf0, 0xfa, 0xb9, 0xbb, 0xd3, 0x59, 0xd3, + 0x3c, 0xab, 0xfb, 0xe3, 0xe1, 0x88, 0x63, 0xce, 0xad, 0xc7, 0x62, 0x61, + 0xfa, 0x9a, 0x5e, 0x96, 0x7b, 0x17, 0x00, 0xc0, 0x2f, 0x23, 0xd0, 0x01, + 0x78, 0x3e, 0x6f, 0x0d, 0x45, 0xf1, 0x97, 0xad, 0x1d, 0xbd, 0x77, 0xef, + 0x0d, 0x7b, 0x2f, 0xdf, 0xde, 0x33, 0xfd, 0x8b, 0xb9, 0x07, 0x35, 0x62, + 0x60, 0x63, 0xd7, 0xe3, 0xe9, 0xac, 0x3c, 0x61, 0xee, 0x86, 0x8d, 0xa3, + 0x0f, 0x8d, 0xe7, 0xc5, 0x18, 0xe7, 0xa7, 0xf7, 0x97, 0xe4, 0xde, 0x05, + 0x00, 0xf0, 0x7c, 0x04, 0x3a, 0x00, 0xbf, 0x58, 0x8c, 0x27, 0x57, 0x42, + 0xf5, 0xe4, 0x96, 0x8e, 0xde, 0x4f, 0xc7, 0x7a, 0xad, 0x6b, 0x4b, 0x6f, + 0xe7, 0xfd, 0xb9, 0x27, 0x35, 0x62, 0xd7, 0xda, 0xd9, 0x8f, 0xa5, 0x73, + 0x55, 0xf3, 0xac, 0xe5, 0xeb, 0xea, 0x47, 0x1c, 0x7e, 0x51, 0x0c, 0xf1, + 0x82, 0x10, 0xc3, 0xe1, 0xb9, 0x77, 0x01, 0x00, 0xfc, 0x2c, 0x81, 0x0e, + 0xc0, 0x3e, 0x89, 0x31, 0xfe, 0x71, 0x88, 0x95, 0x53, 0x5b, 0x3a, 0xfb, + 0xb6, 0x87, 0xda, 0x9e, 0xae, 0xad, 0xbd, 0xd3, 0xbf, 0x92, 0x7b, 0x53, + 0x23, 0x06, 0x36, 0x2e, 0xfc, 0x51, 0x3a, 0x5d, 0xbf, 0xf5, 0x27, 0xeb, + 0xd7, 0x1c, 0x72, 0x48, 0x65, 0x7e, 0x8a, 0xf4, 0xb9, 0xe9, 0x7d, 0x5c, + 0xee, 0x5d, 0x00, 0x00, 0xff, 0x87, 0x40, 0x07, 0xa0, 0x11, 0x31, 0x86, + 0x30, 0x35, 0x14, 0xd5, 0xd3, 0x5b, 0x3b, 0x7a, 0xfb, 0xea, 0xf5, 0x3d, + 0xdd, 0x5b, 0x7b, 0xcf, 0x78, 0x28, 0xf7, 0xa8, 0x46, 0xfc, 0xaf, 0x8f, + 0x9e, 0xfb, 0x1f, 0xe9, 0x5c, 0x7c, 0xe2, 0xc2, 0x4d, 0xd7, 0x56, 0x8b, + 0xfa, 0xa2, 0xf4, 0x25, 0xcd, 0x4e, 0xef, 0x63, 0x73, 0xef, 0x02, 0x00, + 0x10, 0xe8, 0x00, 0xbc, 0x10, 0x45, 0x4a, 0xf5, 0x8e, 0x18, 0x9b, 0xda, + 0x5a, 0x3a, 0xfb, 0x36, 0xd7, 0xeb, 0x4f, 0x2d, 0xdd, 0xd6, 0x73, 0xe6, + 0xd7, 0x73, 0x8f, 0x6a, 0xc4, 0x03, 0xcb, 0x67, 0x7e, 0x2f, 0x9d, 0x79, + 0x93, 0x16, 0xaf, 0x5b, 0x15, 0x43, 0xf5, 0xd2, 0xf4, 0xfc, 0xc1, 0x18, + 0xc2, 0xa8, 0xdc, 0xbb, 0x00, 0x80, 0x91, 0x4b, 0xa0, 0x03, 0xf0, 0x62, + 0x54, 0x52, 0xd4, 0x9e, 0x19, 0xe3, 0xa8, 0xf6, 0xd6, 0x8e, 0xde, 0x9b, + 0xf6, 0x86, 0xdd, 0x1f, 0xd9, 0xde, 0xf3, 0xfe, 0x6f, 0xe7, 0x1e, 0xd5, + 0x88, 0xfb, 0xae, 0x99, 0xf3, 0x6f, 0xe9, 0xcc, 0x69, 0xbe, 0x70, 0xcd, + 0xb2, 0xfa, 0xe8, 0x51, 0x97, 0xc6, 0x10, 0xcf, 0x4a, 0xef, 0x4d, 0xb9, + 0x77, 0x01, 0x00, 0x23, 0x8f, 0x40, 0x07, 0x60, 0x7f, 0x68, 0x0a, 0x31, + 0x7e, 0xa8, 0x12, 0xc6, 0xcc, 0x68, 0xe9, 0xe8, 0xdd, 0x14, 0x9f, 0xaa, + 0x5d, 0xb3, 0x65, 0x4b, 0xe7, 0x77, 0x72, 0x8f, 0x6a, 0xc4, 0xc0, 0xb5, + 0xe7, 0x7d, 0x2b, 0x9d, 0x99, 0xcd, 0x8b, 0xd6, 0x2e, 0x4f, 0x7f, 0x7b, + 0xbc, 0x3c, 0x7d, 0x3d, 0x9d, 0xe9, 0xbd, 0x92, 0x7b, 0x17, 0x00, 0x30, + 0x72, 0x08, 0x74, 0x00, 0xf6, 0xa7, 0xd1, 0x31, 0xc6, 0xf3, 0xc2, 0xe8, + 0xca, 0x39, 0xad, 0x9d, 0x7d, 0x1b, 0xea, 0x4f, 0xee, 0x59, 0xb6, 0x75, + 0xeb, 0xf4, 0xef, 0xe5, 0x1e, 0xd5, 0x88, 0x81, 0x65, 0x73, 0x07, 0x7f, + 0xab, 0xfe, 0x8c, 0xe6, 0x05, 0xeb, 0xae, 0x0e, 0x95, 0x6a, 0x57, 0x7a, + 0x7e, 0x5f, 0x18, 0xfc, 0x2d, 0xfd, 0x00, 0x00, 0x07, 0x98, 0x40, 0x07, + 0xe0, 0x40, 0x18, 0xfc, 0xa6, 0x6b, 0x17, 0xc5, 0x31, 0xd5, 0x0f, 0xb5, + 0x74, 0xf4, 0xae, 0x79, 0x7c, 0xef, 0x13, 0xab, 0x3e, 0xd3, 0x7f, 0xf6, + 0x23, 0xb9, 0x47, 0x35, 0x62, 0x60, 0xc5, 0x9c, 0x7f, 0x48, 0xa7, 0xbd, + 0x79, 0xde, 0xba, 0xab, 0xea, 0x4d, 0x95, 0x25, 0x31, 0xc4, 0x29, 0xe9, + 0x3d, 0xe6, 0xde, 0x05, 0x00, 0x0c, 0x5f, 0x02, 0x1d, 0x80, 0x03, 0x69, + 0x5c, 0x8c, 0xf1, 0x92, 0x43, 0xab, 0x87, 0xcc, 0x69, 0xed, 0xe8, 0xbd, + 0xee, 0xc7, 0x7b, 0x1f, 0xbb, 0xf6, 0xee, 0xfe, 0x73, 0x7e, 0x94, 0x7b, + 0x54, 0x23, 0x06, 0x56, 0xcd, 0xd9, 0x95, 0x4e, 0xcb, 0x49, 0x0b, 0x36, + 0x4e, 0x8a, 0x95, 0xb8, 0x34, 0x3d, 0x9f, 0x92, 0x7b, 0x13, 0x00, 0x30, + 0x3c, 0x09, 0x74, 0x00, 0x0e, 0x86, 0x23, 0x42, 0x8c, 0x5d, 0x87, 0x57, + 0x0f, 0xfb, 0x93, 0x14, 0xea, 0xab, 0x1e, 0xd9, 0xfb, 0xc3, 0x35, 0x3b, + 0xfb, 0x67, 0x3f, 0x96, 0x7b, 0x54, 0x23, 0xee, 0x5d, 0x31, 0xeb, 0xbe, + 0x74, 0x4e, 0x3d, 0x69, 0xe1, 0xfa, 0xdf, 0x8d, 0xb1, 0xb2, 0x34, 0xc4, + 0xf0, 0x07, 0xb9, 0x37, 0x01, 0x00, 0xc3, 0x8b, 0x40, 0x07, 0xe0, 0x60, + 0x3a, 0x2a, 0x85, 0xfa, 0x95, 0x47, 0x55, 0x5f, 0x72, 0x7e, 0x4b, 0x7b, + 0xef, 0xb2, 0x87, 0x6b, 0xdf, 0xdd, 0xf0, 0xf9, 0xfe, 0x0b, 0x9e, 0xc8, + 0x3d, 0xaa, 0x11, 0xf7, 0x2e, 0x3f, 0xf7, 0xaf, 0xd3, 0xf9, 0xc3, 0x93, + 0x16, 0xac, 0x7f, 0x6b, 0xac, 0x54, 0xae, 0x48, 0xcf, 0x6f, 0xcd, 0xbd, + 0x09, 0x00, 0x18, 0x1e, 0x04, 0x3a, 0x00, 0x39, 0x1c, 0x13, 0x8b, 0xb8, + 0x6a, 0x7c, 0x31, 0x7e, 0xde, 0xd4, 0x8e, 0xde, 0xab, 0x77, 0xff, 0xe8, + 0x3f, 0x6e, 0xd8, 0xb1, 0x63, 0xee, 0xee, 0xdc, 0xa3, 0x1a, 0x71, 0xef, + 0x8a, 0x73, 0xef, 0x49, 0xe7, 0x6d, 0xcd, 0x8b, 0x36, 0x9c, 0x1c, 0x42, + 0x5c, 0x1a, 0x62, 0x7c, 0x63, 0xee, 0x4d, 0x00, 0xc0, 0xd0, 0x26, 0xd0, + 0x01, 0xc8, 0x26, 0x86, 0xf0, 0x2b, 0x31, 0xc6, 0x8f, 0x8e, 0x3d, 0xf2, + 0xa5, 0x0b, 0x52, 0xa8, 0x5f, 0xf9, 0xed, 0xaf, 0x3d, 0x76, 0xd3, 0xc0, + 0xc0, 0x39, 0x4f, 0xe7, 0xde, 0xd5, 0x88, 0x81, 0x65, 0xb3, 0xef, 0x4e, + 0xe7, 0xee, 0xe6, 0x45, 0x9b, 0xfe, 0x38, 0xc4, 0x7a, 0x77, 0xfa, 0xaa, + 0x26, 0xe6, 0xde, 0x04, 0x00, 0x0c, 0x4d, 0x02, 0x1d, 0x80, 0x32, 0x78, + 0x45, 0x11, 0xe3, 0xa6, 0xe3, 0x26, 0x1c, 0xb6, 0xb0, 0xb5, 0xbd, 0x77, + 0xe9, 0xb6, 0xda, 0xed, 0x9b, 0x6b, 0xfd, 0xfd, 0x7b, 0x73, 0x8f, 0x6a, + 0xc4, 0xc0, 0xb2, 0x99, 0x9f, 0x2e, 0x8a, 0x62, 0xc7, 0xc4, 0x85, 0xeb, + 0xa6, 0xc4, 0x50, 0xa4, 0x50, 0x0f, 0xaf, 0xcd, 0xbd, 0x09, 0x00, 0x18, + 0x5a, 0x04, 0x3a, 0x00, 0x65, 0xf2, 0xea, 0x50, 0xc4, 0x9b, 0xa7, 0x16, + 0x53, 0x2e, 0x69, 0xed, 0xe8, 0x59, 0xba, 0xad, 0xef, 0x6b, 0x7d, 0xb5, + 0x5a, 0x57, 0x2d, 0xf7, 0xa8, 0x7d, 0x55, 0xab, 0xd5, 0xea, 0xe9, 0x6c, + 0x2b, 0x8a, 0xee, 0xdb, 0x27, 0xcd, 0x7f, 0xf9, 0xb4, 0xf4, 0xb5, 0x5c, + 0x9e, 0xde, 0x8f, 0xcf, 0xbd, 0x0b, 0x00, 0x18, 0x1a, 0x04, 0x3a, 0x00, + 0x65, 0x74, 0x7c, 0x88, 0xc5, 0xe6, 0xa9, 0xd3, 0x26, 0x5c, 0xdc, 0xd2, + 0xde, 0xdb, 0xb5, 0xbd, 0xbf, 0x73, 0xfb, 0xb3, 0xf1, 0x3b, 0x24, 0x3c, + 0xfb, 0x0f, 0x15, 0x7a, 0x8a, 0xb6, 0xb6, 0xfe, 0x13, 0x5f, 0xf9, 0xfb, + 0x9d, 0x45, 0x2c, 0x06, 0x43, 0xfd, 0xd5, 0xb9, 0x77, 0x01, 0x00, 0xe5, + 0x26, 0xd0, 0x01, 0x28, 0xaf, 0x18, 0x5f, 0x1b, 0x63, 0xd8, 0x3a, 0x75, + 0xda, 0xe6, 0xfb, 0x5b, 0xda, 0x7b, 0xba, 0xb6, 0xf6, 0x76, 0x7c, 0x3a, + 0xf7, 0xa4, 0x46, 0x3c, 0xfb, 0xdb, 0xf4, 0x6f, 0x69, 0x9e, 0x35, 0xab, + 0xb7, 0x7e, 0xe4, 0xeb, 0xcf, 0x8a, 0x21, 0x5e, 0x9a, 0xde, 0x5f, 0x91, + 0x7b, 0x17, 0x00, 0x50, 0x4e, 0x02, 0x1d, 0x80, 0xf2, 0x8b, 0x71, 0x62, + 0x8c, 0xf1, 0xce, 0xd6, 0xce, 0xbe, 0x2f, 0xd6, 0xea, 0xb5, 0xcb, 0xb7, + 0xf5, 0x74, 0xdc, 0x9d, 0x7b, 0x52, 0x23, 0x06, 0x36, 0x6e, 0x1c, 0xfc, + 0xc6, 0x77, 0x1f, 0x3b, 0xfe, 0xfc, 0xb5, 0x37, 0x1f, 0x36, 0xb6, 0xe9, + 0x9c, 0xf4, 0x7c, 0xf1, 0xe0, 0x37, 0xc8, 0xcb, 0xbd, 0x0b, 0x00, 0x28, + 0x17, 0x81, 0x0e, 0xc0, 0x50, 0xf2, 0xc6, 0x22, 0x16, 0x9f, 0x4b, 0xa1, + 0x7e, 0x4f, 0xa8, 0xd5, 0x2e, 0xdb, 0xd2, 0xdb, 0x71, 0x4f, 0xee, 0x41, + 0x8d, 0x78, 0xf0, 0xfa, 0x9f, 0xfe, 0xa7, 0xe4, 0xd6, 0x1e, 0x37, 0xef, + 0xba, 0x4f, 0xbc, 0xac, 0x32, 0x66, 0x76, 0x2c, 0xe2, 0xa2, 0xf4, 0x7e, + 0x4c, 0xee, 0x5d, 0x00, 0x40, 0x39, 0x08, 0x74, 0x00, 0x86, 0xa2, 0xb7, + 0x86, 0xa2, 0xf8, 0xcb, 0xd6, 0x8e, 0xde, 0xbb, 0xf7, 0x86, 0xbd, 0x97, + 0x6f, 0xef, 0x99, 0xfe, 0xc5, 0xdc, 0x83, 0x1a, 0xf1, 0xcd, 0x55, 0x17, + 0x3c, 0x91, 0xce, 0xea, 0x13, 0xe6, 0x6e, 0xf8, 0xd8, 0xe8, 0x43, 0xe3, + 0x79, 0x31, 0xc6, 0x79, 0xe9, 0xfd, 0xa8, 0xdc, 0xbb, 0x00, 0x80, 0xbc, + 0x04, 0x3a, 0x00, 0x43, 0x57, 0x8c, 0x27, 0x57, 0x42, 0xf5, 0xe4, 0xd6, + 0xce, 0xbe, 0xbb, 0x6a, 0x7b, 0xea, 0x97, 0x6f, 0xeb, 0x6f, 0xbf, 0x2f, + 0xf7, 0xa4, 0x46, 0xec, 0x5a, 0x3b, 0xfb, 0xb1, 0x74, 0xae, 0x6a, 0x9e, + 0xb5, 0x7c, 0x5d, 0xfd, 0xc8, 0x23, 0x2e, 0x8c, 0x21, 0x5c, 0x90, 0xde, + 0x8f, 0xc8, 0xbd, 0x0b, 0x00, 0xc8, 0x43, 0xa0, 0x03, 0x30, 0x1c, 0x9c, + 0x52, 0x54, 0xe3, 0xbb, 0x5a, 0x3a, 0xfb, 0xb6, 0xd7, 0xf7, 0xd4, 0x96, + 0x6c, 0xeb, 0xef, 0xd8, 0x95, 0x7b, 0x50, 0x23, 0x06, 0x36, 0x2e, 0xfc, + 0x51, 0x3a, 0x4b, 0x4e, 0x98, 0x77, 0xdd, 0x9a, 0xd1, 0xd5, 0xb1, 0xf3, + 0x62, 0x0c, 0xe7, 0xa5, 0xf7, 0x71, 0xb9, 0x77, 0x01, 0x00, 0x07, 0x97, + 0x40, 0x07, 0x60, 0xb8, 0x48, 0x5d, 0x1b, 0xa6, 0xc6, 0x6a, 0x71, 0x7a, + 0x0a, 0xf5, 0xdb, 0xf6, 0x84, 0xd0, 0x7d, 0xc7, 0xe6, 0x69, 0xff, 0x90, + 0x7b, 0x54, 0x23, 0x76, 0xad, 0xba, 0xe0, 0x91, 0x74, 0x2e, 0x3d, 0x71, + 0xe1, 0xa6, 0xeb, 0xab, 0x45, 0x7d, 0x51, 0xfa, 0x92, 0x66, 0xa7, 0xf7, + 0xb1, 0xb9, 0x77, 0x01, 0x00, 0x07, 0x87, 0x40, 0x07, 0x60, 0xb8, 0x29, + 0x52, 0xa8, 0x4f, 0x6b, 0x0a, 0xe1, 0xbd, 0x29, 0xd4, 0x37, 0xd7, 0xeb, + 0x4f, 0x2d, 0xdd, 0xd6, 0x73, 0xe6, 0xd7, 0x73, 0x8f, 0x6a, 0xc4, 0x03, + 0xcb, 0x67, 0x7e, 0x2f, 0x9d, 0x79, 0xcd, 0x17, 0x5d, 0xbf, 0x32, 0x8c, + 0x1a, 0xb3, 0x38, 0x3d, 0xcf, 0x4c, 0x9f, 0xd1, 0x99, 0x67, 0x01, 0x00, + 0x07, 0x98, 0x40, 0x07, 0x60, 0xb8, 0xaa, 0xa4, 0x50, 0x3f, 0x33, 0xc6, + 0x51, 0xed, 0xad, 0x1d, 0xbd, 0x37, 0x3d, 0xbd, 0x77, 0xcf, 0x95, 0x77, + 0xf4, 0x9f, 0xf1, 0xad, 0xdc, 0xa3, 0x1a, 0x31, 0xb0, 0xfa, 0xfc, 0xef, + 0xa4, 0x73, 0xfe, 0x6f, 0xcf, 0xdf, 0xb0, 0x72, 0x54, 0x35, 0x7e, 0x38, + 0x86, 0x78, 0x56, 0x7a, 0x6f, 0xca, 0xbd, 0x0b, 0x00, 0x38, 0x30, 0x04, + 0x3a, 0x00, 0xc3, 0x5d, 0x53, 0x88, 0xf1, 0x43, 0x4d, 0xd5, 0xa6, 0x19, + 0xad, 0x9d, 0x7d, 0x37, 0xec, 0xad, 0x3f, 0x71, 0xf5, 0xf6, 0x9e, 0xb3, + 0xfe, 0x2d, 0xf7, 0xa8, 0x46, 0xfc, 0xdd, 0xca, 0xd9, 0xdf, 0x4e, 0x67, + 0xe6, 0xeb, 0xe6, 0x6d, 0x5c, 0xd6, 0x54, 0x8d, 0x97, 0xc5, 0x18, 0xa6, + 0x07, 0x7f, 0x0f, 0x07, 0x80, 0x61, 0xc7, 0xdf, 0xdc, 0x01, 0x18, 0x29, + 0x06, 0x7f, 0x8b, 0xf8, 0xdc, 0x4a, 0x1c, 0xfb, 0x81, 0x14, 0xea, 0x1b, + 0xea, 0x4f, 0xee, 0x59, 0xb6, 0x75, 0xeb, 0xf4, 0xef, 0xe5, 0x1e, 0xd5, + 0x88, 0x2f, 0xaf, 0x9a, 0xf5, 0x4f, 0xe9, 0x9c, 0x75, 0xe2, 0xfc, 0xf5, + 0x57, 0x57, 0x2b, 0x95, 0xae, 0x10, 0xc3, 0xb4, 0xf4, 0x5e, 0xe4, 0xde, + 0x05, 0x00, 0xec, 0x1f, 0x02, 0x1d, 0x80, 0x91, 0x66, 0xf0, 0x9b, 0xae, + 0x5d, 0x14, 0xc7, 0x54, 0x3f, 0xd4, 0xd2, 0xd1, 0xbb, 0xe6, 0xf1, 0xbd, + 0x4f, 0xac, 0xfa, 0x4c, 0xff, 0xd9, 0x8f, 0xe4, 0x1e, 0xd5, 0x88, 0x07, + 0x56, 0x9e, 0xfb, 0x50, 0x3a, 0x9d, 0x27, 0x2e, 0xdc, 0x74, 0x75, 0xa5, + 0xa8, 0x77, 0xc7, 0x10, 0xa7, 0xa4, 0xf7, 0x98, 0x7b, 0x17, 0x00, 0xf0, + 0xe2, 0x08, 0x74, 0x00, 0x46, 0xaa, 0x71, 0x31, 0xc6, 0x4b, 0x0e, 0xad, + 0x1e, 0x32, 0xb7, 0xa5, 0xb3, 0xef, 0xba, 0x47, 0xf7, 0x3c, 0xba, 0xfa, + 0xee, 0xfe, 0x73, 0x7e, 0x94, 0x7b, 0x54, 0x23, 0x1e, 0x58, 0x3e, 0xf3, + 0x2b, 0xe9, 0xb4, 0x4c, 0x5c, 0xb4, 0xe1, 0xf5, 0x95, 0x58, 0x74, 0xa7, + 0xe7, 0xf7, 0xe4, 0xde, 0x04, 0x00, 0xbc, 0x70, 0x02, 0x1d, 0x80, 0x91, + 0xee, 0xf0, 0x18, 0xc2, 0xe5, 0x87, 0x57, 0x0f, 0x9b, 0xdb, 0xda, 0xd1, + 0xbb, 0xea, 0x91, 0xbd, 0x3f, 0x5c, 0xb3, 0xb3, 0x7f, 0xf6, 0x63, 0xb9, + 0x47, 0x35, 0xe2, 0xfe, 0x65, 0xb3, 0xbf, 0x94, 0xce, 0x69, 0xcd, 0x0b, + 0xd7, 0xbf, 0x31, 0xc4, 0x62, 0x69, 0x88, 0xf1, 0xe4, 0xdc, 0x9b, 0x00, + 0x80, 0xc6, 0x09, 0x74, 0x00, 0x78, 0xc6, 0x51, 0x29, 0x6c, 0xaf, 0x3c, + 0xaa, 0xfa, 0x92, 0xf3, 0x53, 0xa8, 0xaf, 0x78, 0xfa, 0x27, 0x8f, 0xad, + 0xbf, 0xe3, 0x8e, 0x73, 0x1e, 0xcf, 0x3d, 0xaa, 0x11, 0x03, 0xcb, 0xcf, + 0xfd, 0x62, 0x3a, 0x7f, 0x74, 0xd2, 0x82, 0xf5, 0x6f, 0x8d, 0x95, 0xca, + 0xd2, 0xf4, 0xfc, 0xb6, 0xdc, 0x9b, 0x00, 0x80, 0x7d, 0x27, 0xd0, 0x01, + 0xe0, 0x3f, 0x3b, 0x26, 0x85, 0xfa, 0x8a, 0xa6, 0x71, 0x87, 0x5d, 0xd4, + 0xd2, 0xd1, 0x73, 0xcd, 0x93, 0x3f, 0x7a, 0x64, 0xd3, 0x8e, 0x1d, 0x73, + 0x77, 0xe7, 0x1e, 0xd5, 0x88, 0x7b, 0x57, 0x9c, 0x7b, 0x4f, 0x3a, 0xbf, + 0x3f, 0x69, 0xe1, 0xc6, 0x3f, 0x28, 0x8a, 0x38, 0xf8, 0x5b, 0xdf, 0xdf, + 0x94, 0x7b, 0x13, 0x00, 0xf0, 0xcb, 0x09, 0x74, 0x00, 0xf8, 0xf9, 0x8e, + 0x8d, 0xb1, 0xb8, 0x7e, 0xcc, 0x91, 0x2f, 0x9d, 0x3f, 0xb5, 0xa3, 0xf7, + 0x23, 0xdf, 0xfe, 0xda, 0x63, 0x37, 0x0d, 0x0c, 0x9c, 0xf3, 0x74, 0xee, + 0x51, 0x8d, 0xb8, 0x6f, 0xf9, 0xac, 0x3f, 0x4b, 0xe7, 0xcf, 0x26, 0x2d, + 0x5a, 0x7f, 0x4a, 0x8c, 0x95, 0xa5, 0x31, 0x84, 0x49, 0xb9, 0x37, 0x01, + 0x00, 0xcf, 0x4f, 0xa0, 0x03, 0xc0, 0x2f, 0x90, 0xa2, 0xf6, 0xd7, 0x62, + 0x8c, 0x9b, 0x8e, 0x9b, 0x70, 0xd8, 0xa2, 0x14, 0xea, 0x57, 0xfc, 0xf0, + 0xe1, 0x87, 0x6e, 0xdd, 0xb9, 0xb3, 0x6b, 0x4f, 0xee, 0x5d, 0x8d, 0xb8, + 0x6f, 0xd9, 0xb9, 0x77, 0x15, 0x45, 0xf1, 0x99, 0x89, 0x0b, 0xd7, 0x4d, + 0x89, 0x21, 0x2e, 0x49, 0x5f, 0xd5, 0x09, 0xb9, 0x37, 0x01, 0x00, 0xff, + 0x95, 0x40, 0x07, 0x80, 0x7d, 0xf3, 0xeb, 0x45, 0x8c, 0x37, 0x1d, 0x75, + 0xec, 0xf1, 0x17, 0xb7, 0x76, 0xf4, 0x74, 0x6f, 0xeb, 0xfb, 0x5a, 0x5f, + 0xad, 0xd6, 0x55, 0xcb, 0x3d, 0x6a, 0x5f, 0xd5, 0x6a, 0xb5, 0x7a, 0x3a, + 0xdb, 0x8a, 0xa2, 0xfb, 0xf6, 0x49, 0xf3, 0xc7, 0xb7, 0xd6, 0x8b, 0xb0, + 0x24, 0x86, 0xf0, 0x9a, 0xdc, 0xbb, 0x00, 0x80, 0xff, 0x4b, 0xa0, 0x03, + 0x40, 0x63, 0x26, 0x84, 0x58, 0x6c, 0x9e, 0x3a, 0x6d, 0xc2, 0xc5, 0x2d, + 0xed, 0xbd, 0x5d, 0xdb, 0xfb, 0x3b, 0xb7, 0x3f, 0x1b, 0xbf, 0x43, 0xc2, + 0xb3, 0xff, 0x50, 0xe1, 0xb6, 0xa2, 0xad, 0x6d, 0xeb, 0x89, 0xaf, 0xfc, + 0xfd, 0xce, 0x22, 0x16, 0x97, 0xa7, 0xf7, 0x57, 0xe7, 0xde, 0x05, 0x00, + 0x08, 0x74, 0x00, 0x78, 0x61, 0x62, 0x7c, 0x6d, 0x8c, 0x61, 0xeb, 0x94, + 0xf6, 0x9e, 0x2f, 0xb5, 0x76, 0x6c, 0xee, 0xda, 0xd2, 0xd3, 0xf9, 0xa9, + 0xdc, 0x93, 0x1a, 0x51, 0xeb, 0xef, 0xdf, 0x9b, 0xce, 0x2d, 0xcd, 0xb3, + 0x66, 0xf5, 0x86, 0x23, 0x5e, 0x3f, 0x23, 0x7d, 0x3d, 0x97, 0xa6, 0xf7, + 0x57, 0x66, 0x9e, 0x05, 0x00, 0x23, 0x9a, 0x40, 0x07, 0x80, 0x17, 0x21, + 0x86, 0xf0, 0xfa, 0x10, 0x2b, 0x77, 0xb4, 0x76, 0xf6, 0x7d, 0xb1, 0x56, + 0xaf, 0x5d, 0xbe, 0xad, 0xa7, 0xe3, 0xee, 0xdc, 0x9b, 0x1a, 0x31, 0xb0, + 0x71, 0xe3, 0xe0, 0x37, 0xbe, 0xfb, 0xf8, 0x09, 0x6d, 0xdd, 0xb7, 0x8c, + 0x79, 0xd5, 0xf8, 0x0f, 0x86, 0x7a, 0xb8, 0x24, 0x7d, 0x51, 0xbf, 0x9a, + 0x7b, 0x17, 0x00, 0x8c, 0x44, 0x02, 0x1d, 0x00, 0xf6, 0x8f, 0x37, 0x16, + 0xb1, 0xf8, 0x5c, 0x0a, 0xf5, 0x7b, 0xea, 0x21, 0x5c, 0xbe, 0x75, 0xf3, + 0xb4, 0xbf, 0xcc, 0x3d, 0xa8, 0x11, 0xbb, 0xfa, 0xbb, 0x9e, 0x4a, 0x67, + 0xfd, 0x71, 0xf3, 0xae, 0xbb, 0xe9, 0x65, 0x95, 0x31, 0xb3, 0x63, 0x11, + 0x17, 0x85, 0xc1, 0xff, 0xe4, 0x1c, 0x00, 0x70, 0xd0, 0x08, 0x74, 0x00, + 0xd8, 0xbf, 0xde, 0x1a, 0x43, 0xf8, 0x9f, 0x29, 0xd4, 0xff, 0x6c, 0x6f, + 0x7d, 0x6f, 0xd7, 0xf6, 0x9e, 0xce, 0x2f, 0xe4, 0x1e, 0xd4, 0x88, 0x6f, + 0xae, 0xba, 0xe0, 0x89, 0x74, 0x56, 0x9f, 0xb8, 0x70, 0xd5, 0xa6, 0x4a, + 0x1c, 0x37, 0x27, 0xc6, 0xb0, 0x30, 0xbd, 0x1f, 0x9d, 0x7b, 0x17, 0x00, + 0x8c, 0x04, 0x02, 0x1d, 0x00, 0x0e, 0x8c, 0x3f, 0xa8, 0xc4, 0xca, 0x1f, + 0xa4, 0x50, 0xbf, 0xab, 0xb6, 0xa7, 0x7e, 0xf9, 0xb6, 0xfe, 0xf6, 0xfb, + 0x72, 0x0f, 0x6a, 0xc4, 0x03, 0xcb, 0xe7, 0xfd, 0x24, 0x9d, 0xe5, 0xc7, + 0x9f, 0xbf, 0x76, 0xe3, 0xe1, 0x63, 0xaa, 0x17, 0xd4, 0x63, 0xbc, 0x30, + 0x86, 0x70, 0x64, 0xee, 0x5d, 0x00, 0x30, 0x9c, 0x09, 0x74, 0x00, 0x38, + 0xb0, 0x4e, 0x29, 0xaa, 0xf1, 0x5d, 0x2d, 0x9d, 0x7d, 0xdb, 0xeb, 0x7b, + 0x6a, 0x4b, 0xb6, 0xf5, 0x77, 0xec, 0xca, 0x3d, 0xa8, 0x11, 0x0f, 0x5e, + 0x3f, 0xf7, 0xc7, 0xe9, 0x2c, 0x7d, 0xdd, 0x9c, 0x8d, 0x1f, 0x6d, 0x1a, + 0x17, 0xe7, 0xc7, 0x18, 0xce, 0x4b, 0xef, 0xe3, 0x72, 0xef, 0x02, 0x80, + 0xe1, 0x48, 0xa0, 0x03, 0xc0, 0x81, 0x97, 0xba, 0x36, 0x4c, 0x8d, 0xd5, + 0xe2, 0xf4, 0xd6, 0xce, 0xbe, 0x2d, 0xf5, 0xda, 0x9e, 0x25, 0x5b, 0x7b, + 0xa7, 0xff, 0x7d, 0xee, 0x51, 0x8d, 0xf8, 0xf2, 0xba, 0x59, 0x3f, 0x48, + 0xe7, 0xd2, 0xe6, 0x05, 0xab, 0xaf, 0x0d, 0xc5, 0xa1, 0x0b, 0x42, 0x0c, + 0x73, 0xd3, 0xfb, 0x21, 0xb9, 0x77, 0x01, 0xc0, 0x70, 0x22, 0xd0, 0x01, + 0xe0, 0xe0, 0x29, 0xd2, 0xe7, 0x7d, 0xb1, 0xa8, 0xb6, 0xb4, 0x74, 0xf6, + 0x6d, 0xae, 0xd7, 0x9f, 0x5a, 0xba, 0xad, 0xe7, 0xcc, 0xaf, 0xe7, 0x1e, + 0xd5, 0x88, 0x81, 0x15, 0x17, 0x7d, 0x3f, 0x9d, 0x45, 0x27, 0x2c, 0xda, + 0x70, 0xed, 0x98, 0x58, 0x5c, 0x9c, 0x9e, 0x67, 0xa6, 0xcf, 0xe8, 0xcc, + 0xb3, 0x00, 0x60, 0x58, 0x10, 0xe8, 0x00, 0x70, 0xf0, 0x55, 0x62, 0x08, + 0x67, 0xc6, 0x38, 0xaa, 0xbd, 0xb5, 0xb3, 0xef, 0xe6, 0xb0, 0xbb, 0xf6, + 0x91, 0x2d, 0x5b, 0x3a, 0xbe, 0x99, 0x7b, 0x54, 0x23, 0x76, 0x2d, 0x9b, + 0xfd, 0x70, 0x3a, 0xe7, 0xff, 0xf6, 0xfc, 0x0d, 0x2b, 0x47, 0x57, 0xe3, + 0x25, 0xf5, 0x10, 0xcf, 0x4e, 0x5f, 0xd3, 0xa8, 0xdc, 0xbb, 0x00, 0x60, + 0x28, 0x13, 0xe8, 0x00, 0x90, 0x4f, 0x53, 0xfa, 0x7c, 0x30, 0x8c, 0x2e, + 0xce, 0x4c, 0xa1, 0xfe, 0xf1, 0xa7, 0xf6, 0x3c, 0x75, 0xd5, 0xa7, 0xfa, + 0xcf, 0xfc, 0xd7, 0xdc, 0xa3, 0x1a, 0xf1, 0x77, 0x2b, 0x67, 0x7f, 0x3b, + 0x9d, 0xd9, 0x13, 0x2f, 0xda, 0xb4, 0xac, 0xd2, 0x14, 0x2e, 0x0b, 0x31, + 0x9c, 0x19, 0xfc, 0xfa, 0x02, 0x00, 0x5e, 0x10, 0x7f, 0x03, 0x05, 0x80, + 0xfc, 0x06, 0xff, 0xcd, 0xf3, 0xb9, 0xa3, 0xaa, 0xa3, 0xce, 0x4a, 0xa1, + 0xbe, 0xa1, 0xfe, 0xe4, 0x9e, 0x65, 0x5b, 0xb7, 0x4e, 0xff, 0x5e, 0xee, + 0x51, 0x8d, 0xb8, 0x7f, 0xf5, 0xcc, 0x6f, 0xa4, 0xf3, 0x81, 0x13, 0xe7, + 0xaf, 0x5f, 0x56, 0xad, 0x54, 0xba, 0x52, 0xa8, 0x4f, 0x0b, 0xcf, 0xfc, + 0x96, 0x7e, 0x00, 0x60, 0x1f, 0x09, 0x74, 0x00, 0x28, 0x8f, 0xb1, 0xe9, + 0x73, 0x51, 0x1c, 0x53, 0xfd, 0x50, 0x4b, 0x67, 0xdf, 0xda, 0xf0, 0xe4, + 0xd3, 0x2b, 0xb7, 0x6e, 0x3d, 0xe3, 0x3f, 0x72, 0x8f, 0x6a, 0xc4, 0x03, + 0x2b, 0xcf, 0x7d, 0x28, 0x9d, 0xce, 0x49, 0x0b, 0xd6, 0x5e, 0x19, 0x2b, + 0x4d, 0x5d, 0x31, 0x84, 0xf7, 0xa6, 0xf7, 0x98, 0x7b, 0x17, 0x00, 0x0c, + 0x05, 0x02, 0x1d, 0x00, 0xca, 0x67, 0x5c, 0x2a, 0xda, 0xc5, 0x61, 0x4c, + 0xd3, 0xb9, 0x29, 0xd4, 0xaf, 0xab, 0x3f, 0xb1, 0xfb, 0xda, 0x6d, 0xdb, + 0x66, 0xfc, 0x30, 0xf7, 0xa8, 0x46, 0xdc, 0xb7, 0x62, 0xee, 0x57, 0xd3, + 0x69, 0x9b, 0xb8, 0x68, 0xc3, 0xd5, 0x95, 0x58, 0x74, 0xa7, 0xe7, 0x77, + 0x07, 0xa1, 0x0e, 0x00, 0xbf, 0x90, 0x40, 0x07, 0x80, 0xf2, 0x3a, 0x3c, + 0x15, 0xed, 0xe5, 0x71, 0xec, 0xe8, 0x3f, 0x69, 0xed, 0xe8, 0x5d, 0xf9, + 0xc8, 0xde, 0x1f, 0xae, 0xd9, 0xd9, 0x3f, 0xfb, 0xb1, 0xdc, 0xa3, 0x1a, + 0x71, 0xff, 0xb2, 0xd9, 0x5f, 0x4a, 0xe7, 0xb4, 0x89, 0x8b, 0x37, 0xbd, + 0xa1, 0x08, 0xf5, 0xee, 0x18, 0xe2, 0x3b, 0x73, 0x6f, 0x02, 0x80, 0xb2, + 0x12, 0xe8, 0x00, 0x50, 0x7e, 0x2f, 0x09, 0x31, 0x5e, 0x79, 0x54, 0xf5, + 0x25, 0x17, 0xb6, 0x76, 0xf6, 0xad, 0x78, 0xfa, 0xb1, 0x47, 0xd7, 0xde, + 0x71, 0xc7, 0x39, 0x8f, 0xe7, 0x1e, 0xd5, 0x88, 0xfb, 0xaf, 0x99, 0xf9, + 0xb7, 0xe9, 0xbc, 0x6b, 0xe2, 0xa2, 0x4d, 0x6f, 0x2e, 0x42, 0x58, 0x1a, + 0x63, 0x98, 0x9c, 0x7b, 0x13, 0x00, 0x94, 0x8d, 0x40, 0x07, 0x80, 0xa1, + 0xe3, 0xa5, 0xe9, 0xb3, 0xac, 0xe9, 0xd0, 0x71, 0x17, 0xb6, 0x74, 0xf4, + 0x5c, 0xfd, 0xe4, 0x8f, 0x1e, 0xd9, 0xb4, 0x63, 0xc7, 0xdc, 0xdd, 0xb9, + 0x47, 0x35, 0xe2, 0xfe, 0x65, 0x33, 0x3f, 0x9f, 0xce, 0x3b, 0x9a, 0x17, + 0x6e, 0x9a, 0x1c, 0x8a, 0x70, 0x45, 0x7a, 0x7e, 0x53, 0xee, 0x4d, 0x00, + 0x50, 0x16, 0x02, 0x1d, 0x00, 0x86, 0x9a, 0x18, 0xc7, 0xc7, 0x10, 0xaf, + 0x1f, 0x73, 0xe4, 0x4b, 0xe7, 0x4f, 0xed, 0xe8, 0xfd, 0xc8, 0xb7, 0xbf, + 0xf6, 0xd8, 0x4d, 0x03, 0x03, 0xe7, 0x3c, 0x9d, 0x7b, 0x56, 0x23, 0x06, + 0x96, 0xcf, 0xdc, 0x99, 0xce, 0xce, 0x49, 0x8b, 0xd6, 0x9f, 0x52, 0xc4, + 0xca, 0xe0, 0x9f, 0x51, 0x3f, 0x29, 0xf7, 0x26, 0x00, 0xc8, 0x4d, 0xa0, + 0x03, 0xc0, 0x10, 0x15, 0x43, 0xf8, 0xb5, 0x18, 0xe3, 0xa6, 0xe3, 0x26, + 0x1c, 0xb6, 0xb8, 0xa5, 0xb3, 0xef, 0x23, 0x3f, 0xf8, 0xce, 0x83, 0xb7, + 0xec, 0xdc, 0xd9, 0xb5, 0x27, 0xf7, 0xae, 0x46, 0xdc, 0xb7, 0xec, 0xdc, + 0xbb, 0x8a, 0xa2, 0xf8, 0xcc, 0x89, 0x0b, 0xd7, 0xbf, 0xbb, 0x08, 0x71, + 0x69, 0xfa, 0xa1, 0xd7, 0xe5, 0xde, 0x04, 0x00, 0xb9, 0x08, 0x74, 0x00, + 0x18, 0xfa, 0x5e, 0x95, 0x62, 0xfd, 0x13, 0x47, 0x1d, 0x7b, 0xfc, 0xa2, + 0xd6, 0x8e, 0x9e, 0xee, 0x6d, 0x7d, 0x5f, 0xeb, 0xab, 0xd5, 0xba, 0x6a, + 0xb9, 0x47, 0xed, 0xab, 0x5a, 0xad, 0x56, 0x4f, 0xe7, 0x53, 0x45, 0xd1, + 0xfd, 0xe9, 0x49, 0xf3, 0xc7, 0xb7, 0xd6, 0x8b, 0xb0, 0x24, 0x7d, 0x3d, + 0xaf, 0xc9, 0xbd, 0x0b, 0x00, 0x0e, 0x36, 0x81, 0x0e, 0x00, 0xc3, 0xc7, + 0x84, 0x10, 0x8b, 0xcd, 0x53, 0xa7, 0x4d, 0xb8, 0xf8, 0x99, 0x50, 0x9f, + 0xbe, 0xf5, 0xd9, 0xf8, 0x1d, 0x12, 0x9e, 0xfd, 0x87, 0x0a, 0xb7, 0x15, + 0x6d, 0x6d, 0x5b, 0x27, 0x1e, 0xf7, 0xf6, 0xf6, 0x58, 0xc4, 0xcb, 0xd3, + 0xfb, 0x6f, 0xe4, 0xde, 0x05, 0x00, 0x07, 0x8b, 0x40, 0x07, 0x80, 0xe1, + 0x26, 0xc6, 0xd7, 0xa6, 0xff, 0xe7, 0x7f, 0x4c, 0x69, 0xef, 0xf9, 0x52, + 0x6b, 0xc7, 0xe6, 0xae, 0x6d, 0x7d, 0x67, 0xdc, 0x39, 0xa4, 0x42, 0xbd, + 0xbf, 0x7f, 0x6f, 0x3a, 0xb7, 0x16, 0x93, 0xbb, 0xfb, 0x26, 0x36, 0x8f, + 0x3f, 0x33, 0xc6, 0x70, 0x59, 0x7a, 0x7f, 0x65, 0xe6, 0x59, 0x00, 0x70, + 0xc0, 0x09, 0x74, 0x00, 0x18, 0xa6, 0x62, 0x08, 0xaf, 0x0f, 0xb1, 0x72, + 0x47, 0x0a, 0xf5, 0xbf, 0x9d, 0xda, 0xd1, 0x73, 0xd9, 0xb6, 0x9e, 0x8e, + 0xbb, 0x73, 0x6f, 0x6a, 0x44, 0xed, 0x99, 0x3f, 0x4f, 0x7f, 0xe3, 0x09, + 0x6d, 0xdd, 0xb7, 0x8e, 0x79, 0xd5, 0xf8, 0x0f, 0x86, 0x7a, 0xb8, 0x24, + 0x7d, 0x51, 0xbf, 0x9a, 0x7b, 0x17, 0x00, 0x1c, 0x28, 0x02, 0x1d, 0x00, + 0x86, 0xb9, 0x14, 0xea, 0x6f, 0x88, 0xb1, 0xf8, 0x5c, 0x6b, 0x67, 0xdf, + 0xe7, 0xf7, 0xee, 0xad, 0x5f, 0xb6, 0xbd, 0xaf, 0xfd, 0x2f, 0x72, 0x6f, + 0x6a, 0xc4, 0xae, 0xfe, 0xae, 0xa7, 0xd2, 0x59, 0xff, 0xeb, 0x67, 0x77, + 0xdf, 0x78, 0xf4, 0x31, 0xe3, 0x3f, 0x94, 0x9e, 0x2f, 0x4e, 0x9f, 0xf1, + 0x99, 0x67, 0x01, 0xc0, 0x7e, 0x27, 0xd0, 0x01, 0x60, 0xe4, 0x78, 0x73, + 0xa5, 0x12, 0x77, 0xa6, 0x50, 0xdf, 0xb9, 0xb7, 0xbe, 0xf7, 0xb2, 0xed, + 0x3d, 0x9d, 0x5f, 0xc8, 0x3d, 0xa8, 0x11, 0xff, 0x74, 0x63, 0xd7, 0x93, + 0xe9, 0xac, 0x69, 0x9e, 0xd5, 0xfd, 0xf1, 0xfa, 0x11, 0xe3, 0xe7, 0xc6, + 0x18, 0x16, 0x84, 0x67, 0xfe, 0xdb, 0xf0, 0x00, 0x30, 0x2c, 0x08, 0x74, + 0x00, 0x18, 0x79, 0x26, 0x57, 0x62, 0x65, 0x72, 0x0a, 0xf5, 0xbb, 0xf6, + 0xd6, 0xf7, 0x74, 0x6d, 0xef, 0x99, 0x7e, 0x6f, 0xee, 0x41, 0x8d, 0x18, + 0xd8, 0xd8, 0xf5, 0x78, 0x3a, 0xcb, 0x7f, 0x73, 0xf1, 0x8a, 0x0d, 0x87, + 0xd6, 0x0f, 0xbb, 0xb0, 0x1e, 0xe3, 0x85, 0x31, 0x84, 0x23, 0x73, 0xef, + 0x02, 0x80, 0x17, 0x4b, 0xa0, 0x03, 0xc0, 0xc8, 0x75, 0x4a, 0x25, 0x56, + 0xdf, 0x95, 0x42, 0xfd, 0x8e, 0x50, 0xdb, 0xbb, 0x64, 0x4b, 0x6f, 0xe7, + 0x97, 0x73, 0x0f, 0x6a, 0xc4, 0x57, 0xaf, 0x59, 0xf0, 0x68, 0x3a, 0x4b, + 0x27, 0x5e, 0x74, 0xfd, 0x9a, 0xa2, 0x69, 0x74, 0x8a, 0xf4, 0x78, 0x41, + 0x88, 0xe1, 0xf0, 0xdc, 0xbb, 0x00, 0xe0, 0x85, 0x12, 0xe8, 0x00, 0x30, + 0xb2, 0xc5, 0xf4, 0x39, 0x3d, 0x14, 0x95, 0xf7, 0xa4, 0x50, 0xdf, 0x52, + 0xaf, 0xed, 0x59, 0xb2, 0xb5, 0x77, 0xfa, 0xdf, 0xe7, 0x1e, 0xd5, 0x88, + 0xfb, 0x57, 0x9f, 0xff, 0xc3, 0x74, 0xba, 0x9a, 0x17, 0xac, 0xfe, 0x68, + 0x28, 0x0e, 0x5d, 0x90, 0xbe, 0xa2, 0xb9, 0xe9, 0xfd, 0x90, 0xdc, 0xbb, + 0x00, 0xa0, 0x51, 0x02, 0x1d, 0x00, 0x18, 0x54, 0xa4, 0xcf, 0xfb, 0x62, + 0x51, 0x6d, 0x69, 0xe9, 0xec, 0xdb, 0xbc, 0x77, 0xcf, 0xde, 0x2b, 0x6e, + 0xef, 0xef, 0xfc, 0xc7, 0xdc, 0xa3, 0x1a, 0x31, 0xb0, 0xe2, 0xa2, 0xef, + 0xa7, 0xb3, 0xe8, 0x84, 0x45, 0x1b, 0xae, 0x1d, 0x1d, 0x8b, 0x45, 0x31, + 0x84, 0x59, 0xe9, 0x7d, 0x4c, 0xee, 0x5d, 0x00, 0xb0, 0xaf, 0x04, 0x3a, + 0x00, 0xf0, 0x5c, 0x95, 0x14, 0xb6, 0x67, 0x56, 0xab, 0x95, 0x8e, 0x14, + 0xea, 0xb7, 0xd4, 0xeb, 0xbb, 0xaf, 0xd8, 0xd6, 0x33, 0xe3, 0x1b, 0xb9, + 0x47, 0x35, 0x62, 0xd7, 0xb2, 0xd9, 0x0f, 0xa7, 0x73, 0xe1, 0xc4, 0x79, + 0x9b, 0x56, 0x16, 0x4d, 0xe1, 0x92, 0xf4, 0xfc, 0xc1, 0xf4, 0x35, 0x8d, + 0xca, 0xbd, 0x0b, 0x00, 0x7e, 0x19, 0x81, 0x0e, 0x00, 0xfc, 0x3c, 0xd5, + 0x14, 0xb5, 0x67, 0xc7, 0x38, 0x7a, 0x7a, 0x0a, 0xf5, 0xd9, 0x5b, 0x37, + 0x4f, 0xbb, 0x31, 0xf7, 0xa0, 0x46, 0xdd, 0xbf, 0x6a, 0xe6, 0xbf, 0xa6, + 0x33, 0x67, 0xe2, 0x45, 0x9b, 0x56, 0x54, 0x9a, 0xc2, 0x65, 0x21, 0x86, + 0x33, 0x83, 0x5f, 0xfb, 0x00, 0x50, 0x62, 0xfe, 0x26, 0x05, 0x00, 0xfc, + 0x22, 0xa3, 0x42, 0xbd, 0xfe, 0x9a, 0xdc, 0x23, 0x5e, 0x8c, 0xfb, 0x57, + 0xcf, 0xfc, 0x46, 0x3a, 0x1f, 0x38, 0x69, 0xde, 0x86, 0xab, 0x63, 0x35, + 0xa6, 0x50, 0x8f, 0x9d, 0xe9, 0xbd, 0x92, 0x77, 0x15, 0x00, 0xfc, 0x57, + 0x02, 0x1d, 0x00, 0x18, 0x11, 0xee, 0x5d, 0x35, 0x7b, 0xf0, 0xcf, 0xd4, + 0xcf, 0x38, 0x69, 0xf1, 0x86, 0x6b, 0x42, 0x28, 0x96, 0xc4, 0x10, 0x5a, + 0xc3, 0x33, 0x7f, 0xf6, 0x1e, 0x00, 0x4a, 0x41, 0xa0, 0x03, 0x00, 0x23, + 0xca, 0xbd, 0xd7, 0xcc, 0x1e, 0xfc, 0x2e, 0xf5, 0x6d, 0x93, 0xe6, 0xaf, + 0x7d, 0x5d, 0x51, 0x6d, 0x5a, 0x92, 0x9e, 0x4f, 0x0b, 0xcf, 0x7c, 0x37, + 0x7b, 0x00, 0xc8, 0x4a, 0xa0, 0x03, 0x00, 0x23, 0xd2, 0x7d, 0x2b, 0xe7, + 0x0e, 0xfe, 0x77, 0xdf, 0xa7, 0x4c, 0x5a, 0xbc, 0xee, 0xa4, 0x22, 0x54, + 0xbb, 0xd3, 0xf3, 0x29, 0xb9, 0x37, 0x01, 0x30, 0xb2, 0x09, 0x74, 0x00, + 0x60, 0x44, 0xbb, 0xef, 0x9a, 0x39, 0xf7, 0xa6, 0x73, 0xea, 0xa4, 0xf9, + 0x1b, 0xde, 0x14, 0x2b, 0xc5, 0x15, 0x31, 0x86, 0xc9, 0xb9, 0x37, 0x01, + 0x30, 0x32, 0x09, 0x74, 0x00, 0x80, 0x30, 0xf8, 0x6f, 0xd4, 0x67, 0x7f, + 0x21, 0x9d, 0x77, 0x34, 0x2f, 0xde, 0xf0, 0xf6, 0x10, 0x8a, 0x2b, 0xd2, + 0xf3, 0x9b, 0x73, 0x6f, 0x02, 0x60, 0x64, 0x11, 0xe8, 0x00, 0x00, 0xcf, + 0x31, 0x70, 0xcd, 0xec, 0xbf, 0x48, 0xe7, 0x2d, 0xcd, 0x8b, 0x36, 0x9c, + 0x1c, 0xe2, 0x4f, 0x43, 0xfd, 0x0d, 0xb9, 0x37, 0x01, 0x30, 0x32, 0x08, + 0x74, 0x00, 0x80, 0x9f, 0x63, 0x60, 0xd9, 0xec, 0xbb, 0x8b, 0xa2, 0xf8, + 0xd3, 0x13, 0x17, 0xae, 0x7f, 0x77, 0x11, 0xe2, 0xe0, 0x9f, 0x51, 0x7f, + 0x7d, 0xee, 0x4d, 0x00, 0x0c, 0x6f, 0x02, 0x1d, 0x00, 0xe0, 0x79, 0xd4, + 0x6a, 0xb5, 0x7a, 0x3a, 0x9f, 0x4a, 0xa1, 0x7e, 0xe7, 0x89, 0x0b, 0xd7, + 0xb5, 0x14, 0xa1, 0xe8, 0x4a, 0xef, 0xaf, 0xcd, 0xbd, 0x0b, 0x80, 0xe1, + 0x49, 0xa0, 0x03, 0x00, 0xfc, 0x12, 0xcf, 0x86, 0xfa, 0x96, 0xa2, 0xe8, + 0xde, 0x36, 0x69, 0xfe, 0xcb, 0xa7, 0x85, 0x22, 0x0e, 0x86, 0xfa, 0x84, + 0xdc, 0xbb, 0x00, 0x18, 0x5e, 0x04, 0x3a, 0x00, 0xc0, 0x3e, 0xaa, 0xd5, + 0xba, 0x6a, 0xe9, 0xf4, 0x14, 0x93, 0xbb, 0x6f, 0x9b, 0xd8, 0x3c, 0xfe, + 0xcc, 0x18, 0xeb, 0x1f, 0x0e, 0x21, 0xbe, 0x2a, 0xf7, 0x2e, 0x00, 0x86, + 0x07, 0x81, 0x0e, 0x00, 0xd0, 0xa0, 0xda, 0xce, 0xae, 0x3d, 0xe9, 0xdc, + 0xd8, 0x3c, 0x6b, 0xd6, 0x27, 0xeb, 0x47, 0xbe, 0xfe, 0xac, 0x58, 0x8f, + 0x1f, 0x0e, 0x31, 0xfc, 0x5a, 0xee, 0x5d, 0x00, 0x0c, 0x6d, 0x02, 0x1d, + 0x00, 0xe0, 0x05, 0x1a, 0xd8, 0xb8, 0xf1, 0xe9, 0x74, 0x3e, 0x76, 0xfc, + 0xf9, 0x6b, 0x6f, 0x3e, 0x7c, 0x6c, 0xd3, 0xcc, 0xf4, 0x7c, 0x71, 0xfa, + 0x8c, 0xcf, 0x3c, 0x0b, 0x80, 0x21, 0x4a, 0xa0, 0x03, 0x00, 0xbc, 0x48, + 0x0f, 0x5e, 0x3f, 0x77, 0x77, 0x3a, 0x6b, 0x9a, 0x67, 0x75, 0x7f, 0x3c, + 0x1c, 0x71, 0xcc, 0xb9, 0xf5, 0x58, 0x2c, 0x8c, 0x21, 0xbc, 0x2c, 0xf7, + 0x2e, 0x00, 0x86, 0x16, 0x81, 0x0e, 0x00, 0xb0, 0x9f, 0x0c, 0x6c, 0xec, + 0x7a, 0x3c, 0x9d, 0x95, 0x27, 0xcc, 0xdd, 0xb0, 0x71, 0xf4, 0xa1, 0xf1, + 0xbc, 0x18, 0xe3, 0xfc, 0xf4, 0xfe, 0x92, 0xdc, 0xbb, 0x00, 0x18, 0x1a, + 0x04, 0x3a, 0x00, 0xc0, 0x7e, 0xb6, 0x6b, 0xed, 0xec, 0xc7, 0xd2, 0xb9, + 0xaa, 0x79, 0xd6, 0xf2, 0x75, 0xf5, 0x23, 0x0e, 0xbf, 0x28, 0x86, 0x78, + 0x41, 0x88, 0xe1, 0xf0, 0xdc, 0xbb, 0x00, 0x28, 0x37, 0x81, 0x0e, 0x00, + 0x70, 0x80, 0x0c, 0x6c, 0x5c, 0xf8, 0xa3, 0x74, 0xba, 0x7e, 0xeb, 0x4f, + 0xd6, 0xaf, 0x39, 0xe4, 0x90, 0xca, 0xfc, 0x14, 0xe9, 0x73, 0xd3, 0xfb, + 0xb8, 0xdc, 0xbb, 0x00, 0x28, 0x27, 0x81, 0x0e, 0x00, 0x70, 0x80, 0xfd, + 0xaf, 0x8f, 0x9e, 0xfb, 0x1f, 0xe9, 0x5c, 0x7c, 0xe2, 0xc2, 0x4d, 0xd7, + 0x56, 0x8b, 0xfa, 0xa2, 0x10, 0xe2, 0xec, 0xf4, 0x3e, 0x36, 0xf7, 0x2e, + 0x00, 0xca, 0x45, 0xa0, 0x03, 0x00, 0x1c, 0x24, 0x0f, 0x2c, 0x9f, 0xf9, + 0xbd, 0x74, 0xe6, 0x4d, 0x5a, 0xbc, 0x6e, 0x55, 0x0c, 0xd5, 0x4b, 0xd3, + 0xf3, 0x07, 0x63, 0x08, 0xa3, 0x72, 0xef, 0x02, 0xa0, 0x1c, 0x04, 0x3a, + 0x00, 0xc0, 0x41, 0x76, 0xdf, 0x35, 0x73, 0xfe, 0x2d, 0x9d, 0x39, 0xcd, + 0x17, 0xae, 0x59, 0x56, 0x1f, 0x3d, 0xea, 0xd2, 0x18, 0xe2, 0x59, 0xe9, + 0xbd, 0x29, 0xf7, 0x2e, 0x00, 0xf2, 0x12, 0xe8, 0x00, 0x00, 0x99, 0x0c, + 0x5c, 0x7b, 0xde, 0xb7, 0xd2, 0x99, 0xd9, 0xbc, 0x68, 0xed, 0xf2, 0xf4, + 0xcb, 0xb2, 0xcb, 0x43, 0x8c, 0x9d, 0xe9, 0xbd, 0x92, 0x7b, 0x17, 0x00, + 0x79, 0x08, 0x74, 0x00, 0x80, 0xcc, 0x06, 0x96, 0xcd, 0xfd, 0x7a, 0x3a, + 0x33, 0x9a, 0x17, 0xac, 0xbb, 0x3a, 0x54, 0xaa, 0x5d, 0xe9, 0xf9, 0x7d, + 0xe9, 0x53, 0x64, 0x9e, 0x05, 0xc0, 0x41, 0x26, 0xd0, 0x01, 0x00, 0x4a, + 0x62, 0x60, 0xc5, 0x9c, 0x7f, 0x48, 0xa7, 0xbd, 0x79, 0xde, 0xba, 0xab, + 0xea, 0x4d, 0x95, 0x25, 0x31, 0xc4, 0x29, 0xe9, 0x3d, 0xe6, 0xde, 0x05, + 0xc0, 0xc1, 0x21, 0xd0, 0x01, 0x00, 0x4a, 0x66, 0x60, 0xd5, 0x9c, 0x5d, + 0xe9, 0xb4, 0x9c, 0xb4, 0x60, 0xe3, 0xa4, 0x58, 0x89, 0x4b, 0xd3, 0xf3, + 0x29, 0xb9, 0x37, 0x01, 0x70, 0xe0, 0x09, 0x74, 0x00, 0x80, 0x92, 0xba, + 0x77, 0xc5, 0xac, 0xfb, 0xd2, 0x39, 0xf5, 0xa4, 0xc5, 0x1b, 0x7f, 0x27, + 0xd6, 0x43, 0x77, 0x88, 0xf1, 0xe4, 0xdc, 0x9b, 0x00, 0x38, 0x70, 0x04, + 0x3a, 0x00, 0x40, 0xc9, 0xdd, 0x7b, 0xcd, 0xac, 0xbf, 0x49, 0xe7, 0x8f, + 0x4e, 0x5a, 0xb0, 0xfe, 0xad, 0xb1, 0x52, 0xb9, 0x22, 0x3d, 0xbf, 0x35, + 0xf7, 0x26, 0x00, 0xf6, 0x3f, 0x81, 0x0e, 0x00, 0x30, 0x44, 0xdc, 0xbb, + 0xe2, 0xdc, 0x7b, 0xd2, 0x79, 0x5b, 0xf3, 0xa2, 0x0d, 0x27, 0x87, 0x10, + 0x97, 0x86, 0x18, 0xdf, 0x98, 0x7b, 0x13, 0x00, 0xfb, 0x8f, 0x40, 0x07, + 0x00, 0x18, 0x62, 0x06, 0x96, 0xcd, 0xbe, 0x3b, 0x9d, 0xbb, 0x9b, 0x17, + 0x6d, 0xfa, 0xe3, 0x10, 0xeb, 0xdd, 0x29, 0xd6, 0x27, 0xe6, 0xde, 0x04, + 0xc0, 0x8b, 0x27, 0xd0, 0x01, 0x00, 0x86, 0xa8, 0x81, 0x65, 0x33, 0x3f, + 0x5d, 0x14, 0xc5, 0x8e, 0x89, 0x0b, 0xd7, 0x4d, 0x89, 0xa1, 0x48, 0xa1, + 0x1e, 0x5e, 0x9b, 0x7b, 0x13, 0x00, 0x2f, 0x9c, 0x40, 0x07, 0x00, 0x18, + 0xc2, 0x6a, 0xb5, 0x5a, 0x3d, 0x9d, 0x6d, 0x45, 0xd1, 0x7d, 0xfb, 0xa4, + 0xf9, 0x2f, 0x9f, 0x16, 0x8a, 0x78, 0x79, 0x7a, 0x3f, 0x3e, 0xf7, 0x2e, + 0x00, 0x1a, 0x27, 0xd0, 0x01, 0x00, 0x86, 0x81, 0x5a, 0xad, 0xab, 0x96, + 0x4e, 0x4f, 0xd1, 0xd6, 0xd6, 0x7f, 0xe2, 0x2b, 0x7f, 0xbf, 0xb3, 0x88, + 0xc5, 0x60, 0xa8, 0xbf, 0x3a, 0xf7, 0xae, 0x5f, 0xa4, 0x1e, 0xeb, 0x7b, + 0x73, 0x6f, 0x00, 0x28, 0x13, 0x81, 0x0e, 0x00, 0x30, 0x8c, 0xd4, 0xfa, + 0xfb, 0x07, 0xa3, 0xf7, 0x96, 0xe6, 0x59, 0xb3, 0x7a, 0xeb, 0x47, 0xbe, + 0xfe, 0xac, 0x18, 0xe2, 0xa5, 0xe9, 0xfd, 0x15, 0xb9, 0x77, 0xfd, 0x5c, + 0xf5, 0xf0, 0x78, 0xee, 0x09, 0x00, 0x65, 0x22, 0xd0, 0x01, 0x00, 0x86, + 0xa1, 0x81, 0x8d, 0x1b, 0x9f, 0x4e, 0xe7, 0x63, 0xc7, 0x9f, 0xbf, 0xf6, + 0xe6, 0xc3, 0xc6, 0x36, 0x9d, 0x93, 0x9e, 0x2f, 0x8e, 0x21, 0xfc, 0x4a, + 0xee, 0x5d, 0x3f, 0xe3, 0x47, 0xb9, 0x07, 0x00, 0x94, 0x89, 0x40, 0x07, + 0x00, 0x18, 0xc6, 0x1e, 0xbc, 0x7e, 0xee, 0xee, 0x74, 0xd6, 0x1e, 0x37, + 0xef, 0xba, 0x4f, 0xbc, 0xac, 0x32, 0x66, 0x76, 0x2c, 0xe2, 0xa2, 0xf4, + 0x7e, 0x4c, 0xee, 0x5d, 0x3f, 0x15, 0xc3, 0x37, 0x73, 0x4f, 0x00, 0x28, + 0x13, 0x81, 0x0e, 0x00, 0x30, 0x02, 0x7c, 0x73, 0xd5, 0x05, 0x4f, 0xa4, + 0xb3, 0xfa, 0x84, 0xb9, 0x1b, 0x3e, 0x36, 0xfa, 0xd0, 0x78, 0x5e, 0x8c, + 0x71, 0x5e, 0x7a, 0x3f, 0x2a, 0xe7, 0xa6, 0x18, 0xc2, 0xd7, 0x72, 0xfe, + 0xfc, 0x00, 0x65, 0x23, 0xd0, 0x01, 0x00, 0x46, 0x90, 0x5d, 0x6b, 0x67, + 0x3f, 0x96, 0xce, 0x55, 0xcd, 0xb3, 0x96, 0xaf, 0xab, 0x1f, 0x79, 0xc4, + 0x85, 0x29, 0x92, 0x2f, 0x48, 0xef, 0x47, 0xe4, 0xd8, 0xf2, 0x64, 0xbd, + 0xfe, 0x37, 0x39, 0x7e, 0x5e, 0x80, 0xb2, 0x12, 0xe8, 0x00, 0x00, 0x23, + 0xd0, 0xc0, 0xc6, 0x85, 0x83, 0x7f, 0xfe, 0x7b, 0xc9, 0x09, 0xf3, 0xae, + 0x5b, 0x33, 0xba, 0x3a, 0x76, 0x5e, 0x8c, 0xe1, 0xbc, 0xf4, 0x3e, 0xee, + 0x60, 0xfd, 0xfc, 0xf5, 0x10, 0xfe, 0x7e, 0xd7, 0xb2, 0xd9, 0x0f, 0x1f, + 0xac, 0x9f, 0x0f, 0x60, 0x28, 0x10, 0xe8, 0x00, 0x00, 0x23, 0xd8, 0xae, + 0x55, 0x17, 0x3c, 0x92, 0xce, 0xa5, 0x27, 0x2e, 0xdc, 0x74, 0x7d, 0xb5, + 0xa8, 0x2f, 0x0a, 0x21, 0xce, 0x4e, 0xef, 0x63, 0x0f, 0xc2, 0x4f, 0x7d, + 0xdb, 0x41, 0xf8, 0x39, 0x00, 0x86, 0x14, 0x81, 0x0e, 0x00, 0x40, 0x78, + 0x60, 0xf9, 0xcc, 0xef, 0xa5, 0x33, 0xaf, 0xf9, 0xa2, 0xeb, 0x57, 0x86, + 0x51, 0x63, 0x16, 0xa7, 0xe7, 0x99, 0xe9, 0x33, 0xfa, 0x00, 0xfd, 0x74, + 0x4f, 0xc7, 0xdd, 0xbb, 0x6f, 0x3c, 0x40, 0x7f, 0x6d, 0x80, 0x21, 0x4b, + 0xa0, 0x03, 0x00, 0xf0, 0xff, 0x1b, 0x58, 0x7d, 0xfe, 0x77, 0xd2, 0x39, + 0xff, 0xb7, 0xe7, 0x6f, 0x58, 0x39, 0xaa, 0x1a, 0x3f, 0x1c, 0x43, 0x3c, + 0x2b, 0xbd, 0x37, 0xed, 0xcf, 0x9f, 0xa3, 0x1e, 0xea, 0x37, 0xdd, 0x7b, + 0xed, 0x79, 0xdf, 0xda, 0x9f, 0x7f, 0x4d, 0x80, 0xe1, 0x40, 0xa0, 0x03, + 0x00, 0xf0, 0x5f, 0xfc, 0xdd, 0xca, 0xd9, 0xdf, 0x4e, 0x67, 0xe6, 0xeb, + 0xe6, 0x6d, 0x5c, 0xd6, 0x54, 0x8d, 0x97, 0xc5, 0x18, 0xa6, 0x87, 0xfd, + 0xf3, 0x6b, 0xc7, 0x87, 0x77, 0x3f, 0xfd, 0xe4, 0xc5, 0xfb, 0xe1, 0xaf, + 0x03, 0x30, 0xec, 0x08, 0x74, 0x00, 0x00, 0x9e, 0xd7, 0x97, 0x57, 0xcd, + 0xfa, 0xa7, 0x74, 0xce, 0x3a, 0x71, 0xfe, 0xfa, 0xab, 0xab, 0x95, 0x4a, + 0x57, 0x88, 0x61, 0x5a, 0x7a, 0x2f, 0x5e, 0xe0, 0x5f, 0x6e, 0x77, 0xd8, + 0x5b, 0x7b, 0xef, 0xb3, 0x7f, 0xee, 0x1d, 0x80, 0x9f, 0x21, 0xd0, 0x01, + 0x00, 0xf8, 0xa5, 0x1e, 0x58, 0x79, 0xee, 0x43, 0xe9, 0x74, 0x9e, 0xb8, + 0x70, 0xd3, 0xd5, 0x95, 0xa2, 0xde, 0x1d, 0x43, 0x9c, 0x12, 0x7e, 0xfa, + 0x9f, 0x32, 0xdf, 0x47, 0xf5, 0xf0, 0xe3, 0x10, 0x6a, 0xef, 0x1d, 0x58, + 0x31, 0xfb, 0xff, 0x3b, 0x60, 0x23, 0x01, 0x86, 0x38, 0x81, 0x0e, 0x00, + 0xc0, 0x3e, 0x7b, 0x60, 0xf9, 0xcc, 0xaf, 0xa4, 0xd3, 0x32, 0x69, 0xfe, + 0xda, 0xd7, 0x15, 0x95, 0xea, 0x45, 0xf5, 0x18, 0xa7, 0xa5, 0x4a, 0x1f, + 0xf5, 0x8b, 0xfe, 0xff, 0xd4, 0x43, 0xf8, 0x5c, 0x0c, 0x4f, 0xcf, 0x19, + 0x58, 0x36, 0xf7, 0xeb, 0x07, 0x69, 0x26, 0xc0, 0x90, 0x24, 0xd0, 0x01, + 0x00, 0x68, 0xd8, 0x7d, 0x2b, 0xe7, 0x7e, 0x39, 0x9d, 0x19, 0x13, 0x2f, + 0xba, 0xfe, 0xfc, 0x62, 0xd4, 0x98, 0x77, 0xa7, 0x0c, 0x7f, 0x4b, 0x0c, + 0xf1, 0xb7, 0x52, 0x8d, 0x1f, 0x3d, 0xf8, 0x7f, 0xaf, 0xc7, 0xfa, 0xbf, + 0xc6, 0x7a, 0xf8, 0xeb, 0x3d, 0xb5, 0xb8, 0xed, 0x81, 0x15, 0x33, 0x1f, + 0xc8, 0x3c, 0x17, 0x60, 0x48, 0x10, 0xe8, 0x00, 0x00, 0xbc, 0x60, 0xf7, + 0xaf, 0x3e, 0xff, 0x87, 0xe9, 0x7c, 0xf2, 0xd9, 0x0f, 0x00, 0x2f, 0x82, + 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, + 0x02, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, + 0x40, 0x09, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, + 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, + 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, + 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x4a, + 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, 0x08, 0x74, 0x00, 0x00, 0x00, + 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, + 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, + 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, + 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, + 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, + 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, + 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, + 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, + 0x03, 0x00, 0x00, 0x40, 0x09, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, + 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, + 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, + 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, 0x00, + 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, 0x08, 0x74, + 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, + 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, + 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, + 0x50, 0x02, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, + 0x00, 0x40, 0x09, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, + 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, + 0x3a, 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, + 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, 0x00, 0x00, 0x00, + 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, 0x08, 0x74, 0x00, 0x00, + 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, + 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, + 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, + 0x02, 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, + 0x09, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, + 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, + 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, + 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, + 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, + 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, + 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, + 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, + 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, 0x08, + 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, + 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, + 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, + 0x00, 0x50, 0x02, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, + 0x00, 0x00, 0x40, 0x09, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, + 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, + 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, + 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, 0x00, 0x00, + 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, 0x08, 0x74, 0x00, + 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, + 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, 0x80, + 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, + 0x02, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, + 0x40, 0x09, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, + 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, + 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, + 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x4a, + 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, 0x08, 0x74, 0x00, 0x00, 0x00, + 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, + 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, + 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, + 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, + 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, + 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, + 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, + 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, 0xe0, 0xb9, 0x62, 0xdc, 0x9d, 0x7b, + 0x02, 0x00, 0x00, 0x23, 0x93, 0x40, 0x07, 0x78, 0x8e, 0x58, 0xaf, 0x3f, + 0x9e, 0x22, 0x3d, 0xf7, 0x0c, 0x00, 0x00, 0x46, 0x20, 0x81, 0x0e, 0xf0, + 0x1c, 0xf5, 0x50, 0x7f, 0x2c, 0x06, 0x81, 0x0e, 0x00, 0xc0, 0xc1, 0x27, + 0xd0, 0x01, 0x9e, 0x23, 0xa5, 0xf9, 0xbf, 0xe5, 0xde, 0x00, 0x00, 0xc0, + 0xc8, 0x24, 0xd0, 0x01, 0x9e, 0xab, 0x5e, 0xff, 0x17, 0xff, 0x02, 0x1d, + 0x00, 0x80, 0x1c, 0x04, 0x3a, 0xc0, 0x73, 0x3c, 0x51, 0x7b, 0xfa, 0x1b, + 0x63, 0x8b, 0x4a, 0xee, 0x19, 0x00, 0x00, 0x8c, 0x40, 0x02, 0x1d, 0xe0, + 0x39, 0x76, 0xf4, 0xbf, 0xff, 0xdf, 0x5b, 0x3b, 0xfb, 0xbe, 0x9b, 0x1e, + 0x5f, 0x9e, 0x7b, 0x0b, 0x00, 0x00, 0x23, 0x8b, 0x40, 0x07, 0xf8, 0xaf, + 0xfe, 0x2e, 0x7d, 0xfe, 0x30, 0xf7, 0x08, 0x00, 0x00, 0x46, 0x16, 0x81, + 0x0e, 0xf0, 0x33, 0xea, 0x21, 0xdc, 0x17, 0x05, 0x3a, 0x00, 0x00, 0x07, + 0x99, 0x40, 0x07, 0xf8, 0x59, 0xb5, 0xda, 0x3d, 0xa1, 0x28, 0x16, 0xe7, + 0x9e, 0x01, 0x00, 0xc0, 0xc8, 0x22, 0xd0, 0x01, 0x7e, 0xc6, 0x93, 0x3f, + 0x0e, 0x9f, 0x1f, 0x7b, 0x64, 0xd8, 0x13, 0xfc, 0x6f, 0x24, 0x00, 0x00, + 0x07, 0x91, 0x5f, 0x7c, 0x02, 0xfc, 0x8c, 0x1d, 0x3b, 0x3a, 0x7e, 0xdc, + 0xd2, 0xd9, 0xf7, 0x37, 0x31, 0x84, 0xb7, 0xe4, 0xde, 0x02, 0x00, 0xc0, + 0xc8, 0x21, 0xd0, 0x01, 0x7e, 0x9e, 0x5a, 0xfd, 0x53, 0xa1, 0x88, 0x02, + 0x1d, 0x00, 0x80, 0x83, 0x46, 0xa0, 0x03, 0xfc, 0x5c, 0x7b, 0xee, 0x08, + 0xa1, 0x69, 0x79, 0xee, 0x15, 0x00, 0x00, 0x8c, 0x1c, 0x02, 0x1d, 0xe0, + 0xe7, 0xd8, 0xda, 0x7b, 0xc6, 0x43, 0x2d, 0x9d, 0x7d, 0x83, 0xdf, 0xcd, + 0x7d, 0x52, 0xee, 0x2d, 0x00, 0x00, 0x8c, 0x0c, 0x02, 0x1d, 0xe0, 0xf9, + 0xd4, 0x6b, 0xb7, 0x84, 0x58, 0x08, 0x74, 0x00, 0x00, 0x0e, 0x0a, 0x81, + 0x0e, 0xf0, 0x3c, 0xf6, 0xc4, 0xa2, 0xa7, 0x29, 0x84, 0x15, 0xe9, 0x71, + 0x54, 0xee, 0x2d, 0x00, 0x00, 0x0c, 0x7f, 0x02, 0x1d, 0xe0, 0x79, 0xdc, + 0xb1, 0x79, 0xda, 0xf7, 0x5b, 0x3a, 0xfb, 0xfa, 0x62, 0x08, 0x67, 0xe6, + 0xde, 0x02, 0x00, 0xc0, 0xf0, 0x27, 0xd0, 0x01, 0x7e, 0x81, 0xbd, 0x21, + 0x5c, 0x57, 0x15, 0xe8, 0x00, 0x00, 0x1c, 0x04, 0x02, 0x1d, 0xe0, 0x17, + 0xb8, 0x7d, 0xf3, 0xb4, 0x07, 0x5a, 0x3b, 0xfb, 0xfe, 0x22, 0x3d, 0xbe, + 0x3d, 0xf7, 0x16, 0x00, 0x00, 0x86, 0x37, 0x81, 0x0e, 0xf0, 0xcb, 0xd4, + 0x6a, 0x4b, 0x42, 0x51, 0x08, 0x74, 0x00, 0x00, 0x0e, 0x28, 0x81, 0x0e, + 0xf0, 0x4b, 0x6c, 0xe9, 0xed, 0xb8, 0xa7, 0xb5, 0xa3, 0xf7, 0xcf, 0x43, + 0x8c, 0xef, 0xc8, 0xbd, 0x05, 0x00, 0x80, 0xe1, 0x4b, 0xa0, 0x03, 0xec, + 0x83, 0xbd, 0x61, 0xef, 0xe2, 0x4a, 0xa8, 0x7e, 0x31, 0x3d, 0x16, 0xb9, + 0xb7, 0x00, 0x00, 0x30, 0x3c, 0x09, 0x74, 0x80, 0x7d, 0xb0, 0xbd, 0x67, + 0xfa, 0xbd, 0xad, 0x9d, 0x7d, 0x37, 0xa7, 0xc7, 0xb3, 0x72, 0x6f, 0x01, + 0x00, 0x60, 0x78, 0x12, 0xe8, 0x00, 0xfb, 0x6a, 0xf7, 0xde, 0x4b, 0xc3, + 0xe8, 0xca, 0xd4, 0xf4, 0x74, 0x44, 0xee, 0x29, 0x00, 0x00, 0x0c, 0x3f, + 0x02, 0x1d, 0x60, 0x1f, 0x6d, 0xd9, 0xd2, 0xf9, 0x9d, 0xd6, 0xce, 0xbe, + 0xf9, 0xe9, 0xf1, 0x86, 0xdc, 0x5b, 0x00, 0x00, 0x18, 0x7e, 0x04, 0x3a, + 0x40, 0x03, 0xb6, 0xf5, 0x76, 0x7c, 0x62, 0x6a, 0x7b, 0x4f, 0x5b, 0x7a, + 0xfc, 0x83, 0xdc, 0x5b, 0x00, 0x00, 0x18, 0x5e, 0x04, 0x3a, 0x40, 0x03, + 0x6a, 0xb5, 0x5a, 0xbd, 0xb5, 0xb5, 0xe7, 0xac, 0x30, 0xba, 0x78, 0x20, + 0xbd, 0xbe, 0x34, 0xf7, 0x1e, 0x00, 0x00, 0x86, 0x0f, 0x81, 0x0e, 0xd0, + 0xa0, 0x2d, 0x5b, 0x3a, 0xfe, 0xa5, 0xb5, 0xb3, 0xef, 0xfd, 0xe9, 0xf1, + 0xce, 0xf4, 0x89, 0x99, 0xe7, 0x00, 0x00, 0x30, 0x4c, 0x08, 0x74, 0x80, + 0x17, 0x60, 0xcb, 0xe6, 0x69, 0x3b, 0x5a, 0x3b, 0x7a, 0xaf, 0x0c, 0x31, + 0x7e, 0x38, 0xf7, 0x16, 0x00, 0x00, 0x86, 0x07, 0x81, 0x0e, 0xf0, 0x02, + 0x6d, 0xeb, 0xeb, 0xbc, 0x7c, 0x6a, 0x7b, 0xcf, 0x84, 0xf4, 0xf8, 0xbe, + 0xdc, 0x5b, 0x00, 0x00, 0x18, 0xfa, 0x04, 0x3a, 0xc0, 0x0b, 0x34, 0xf8, + 0xe7, 0xd1, 0xdf, 0xdc, 0x76, 0xdd, 0xfb, 0x8f, 0xad, 0xbc, 0xfc, 0xd8, + 0x10, 0xe3, 0xef, 0xe5, 0xde, 0x03, 0x00, 0xc0, 0xd0, 0x26, 0xd0, 0x01, + 0x5e, 0x84, 0xcf, 0xf7, 0x5f, 0xf0, 0xc4, 0x94, 0x29, 0x37, 0x9d, 0x5a, + 0x1c, 0x32, 0xf6, 0x73, 0x31, 0x84, 0xdf, 0xcd, 0xbd, 0x07, 0x00, 0x80, + 0xa1, 0x4b, 0xa0, 0x03, 0xbc, 0x48, 0xdb, 0xb7, 0x9f, 0xf5, 0xe8, 0xc9, + 0x6d, 0x37, 0xbc, 0xeb, 0xf0, 0xca, 0xb8, 0x3b, 0xfd, 0x9b, 0x74, 0x00, + 0x00, 0x5e, 0x28, 0x81, 0x0e, 0xb0, 0x1f, 0xdc, 0xdd, 0x7f, 0xce, 0x8f, + 0x26, 0x4f, 0xbe, 0xf9, 0xe4, 0x97, 0x1c, 0x3b, 0xfa, 0x96, 0x18, 0xc2, + 0x7b, 0x73, 0xef, 0x01, 0x00, 0x60, 0xe8, 0x11, 0xe8, 0x00, 0xfb, 0xc9, + 0xce, 0x9d, 0x33, 0x9e, 0x2c, 0x8a, 0xee, 0x69, 0x53, 0xda, 0x8f, 0xff, + 0x4a, 0x8a, 0xf4, 0xcb, 0xd3, 0x0f, 0x55, 0x72, 0x6f, 0x02, 0x00, 0x60, + 0xe8, 0x10, 0xe8, 0x00, 0xfb, 0x51, 0xad, 0xd6, 0x55, 0x4b, 0x67, 0xe9, + 0xd4, 0x8e, 0xcd, 0xf7, 0x14, 0xb1, 0x72, 0x73, 0x7a, 0x7e, 0x45, 0xee, + 0x4d, 0x00, 0x00, 0x0c, 0x0d, 0x02, 0x1d, 0xe0, 0x00, 0xd8, 0xd6, 0xd3, + 0xf9, 0x3f, 0xdf, 0xf9, 0xce, 0x5b, 0x7f, 0x73, 0xdc, 0xd1, 0xd5, 0xc1, + 0x7f, 0x93, 0x7e, 0x61, 0xfa, 0x34, 0xe5, 0xde, 0x04, 0x00, 0x40, 0xb9, + 0x09, 0x74, 0x80, 0x03, 0xe4, 0xb3, 0x9f, 0x9d, 0xfe, 0x93, 0x74, 0x16, + 0xb5, 0xb4, 0x7f, 0xf2, 0x13, 0xa1, 0x68, 0xba, 0x3a, 0x86, 0x30, 0x25, + 0xbd, 0xc7, 0xdc, 0xbb, 0x00, 0x00, 0x28, 0x27, 0x81, 0x0e, 0x70, 0x80, + 0x6d, 0xed, 0x3d, 0xe3, 0xa1, 0x74, 0x5a, 0xa6, 0x74, 0x6c, 0x7e, 0x53, + 0x25, 0x56, 0x56, 0xa4, 0xe7, 0x37, 0xe5, 0xde, 0x04, 0x00, 0x40, 0xf9, + 0x08, 0x74, 0x80, 0x83, 0x64, 0x7b, 0x4f, 0xe7, 0x17, 0x8a, 0xa2, 0x78, + 0xcb, 0x94, 0xb6, 0xcd, 0x53, 0x62, 0x11, 0xaf, 0x4e, 0x3f, 0x34, 0x21, + 0xf7, 0x26, 0x00, 0x00, 0xca, 0x43, 0xa0, 0x03, 0x1c, 0x44, 0xb5, 0x5a, + 0xad, 0x9e, 0xce, 0xb6, 0xe6, 0xe6, 0x1b, 0xee, 0x3c, 0x6e, 0xc2, 0x61, + 0xe7, 0x84, 0x67, 0xbe, 0xdb, 0xfb, 0xcb, 0x33, 0xcf, 0x02, 0x00, 0xa0, + 0x04, 0x04, 0x3a, 0x40, 0x06, 0x03, 0x03, 0xe7, 0x3c, 0x9d, 0xce, 0xfa, + 0x29, 0x53, 0x6e, 0xfa, 0x64, 0x71, 0xc8, 0xd8, 0xf9, 0x31, 0x84, 0x79, + 0xe9, 0xfd, 0xd0, 0xdc, 0xbb, 0x00, 0x00, 0xc8, 0x47, 0xa0, 0x03, 0x64, + 0xb4, 0x7d, 0xfb, 0x59, 0x8f, 0xa6, 0xd3, 0xd5, 0xda, 0xba, 0x79, 0x63, + 0x18, 0x55, 0x2c, 0x09, 0x31, 0x9e, 0x1d, 0xfc, 0x6f, 0x33, 0x00, 0xc0, + 0x88, 0xe4, 0x17, 0x81, 0x00, 0x25, 0xb0, 0x65, 0x4b, 0xe7, 0x77, 0xd2, + 0x99, 0x39, 0xa5, 0xad, 0xe7, 0xfa, 0x4a, 0xb5, 0x18, 0xfc, 0xf3, 0xe9, + 0xef, 0xc9, 0xbd, 0x09, 0x00, 0x80, 0x83, 0x4b, 0xa0, 0x03, 0x94, 0xc8, + 0xf6, 0xfe, 0x8e, 0xaf, 0xa6, 0x73, 0x5a, 0x4b, 0x67, 0xdf, 0xdb, 0xd2, + 0x5d, 0x1e, 0x43, 0x78, 0x43, 0xee, 0x4d, 0x00, 0x00, 0x1c, 0x1c, 0x02, + 0x1d, 0xa0, 0x84, 0xb6, 0x6e, 0x9e, 0xf6, 0x97, 0x45, 0x51, 0xfc, 0xce, + 0xe9, 0xed, 0x3d, 0xef, 0x2d, 0x42, 0xb8, 0x2a, 0xfd, 0xd0, 0xab, 0x73, + 0x6f, 0x02, 0x00, 0xe0, 0xc0, 0x12, 0xe8, 0x00, 0x25, 0xf5, 0xec, 0x77, + 0x7c, 0xbf, 0xed, 0x84, 0xb6, 0xee, 0xdb, 0x27, 0x54, 0x7e, 0x63, 0x56, + 0x8c, 0xc5, 0x65, 0xe9, 0xfd, 0xa5, 0xb9, 0x77, 0x01, 0x00, 0x70, 0x60, + 0x08, 0x74, 0x80, 0x92, 0xdb, 0xd5, 0xdf, 0xf5, 0x54, 0x3a, 0x6b, 0xa6, + 0x4e, 0xbd, 0xf9, 0x96, 0x62, 0xec, 0xe8, 0x8b, 0xd3, 0xf3, 0x79, 0xe9, + 0x33, 0x26, 0xf3, 0x2c, 0x00, 0x00, 0xf6, 0x33, 0x81, 0x0e, 0x30, 0x44, + 0x6c, 0xdb, 0x36, 0xe3, 0x87, 0xe9, 0x2c, 0x3a, 0xad, 0xed, 0x93, 0xeb, + 0xaa, 0xd5, 0xa6, 0x2b, 0x62, 0x08, 0xd3, 0xd3, 0x7b, 0x91, 0x7b, 0x17, + 0x00, 0x00, 0xfb, 0x87, 0x40, 0x07, 0x18, 0x62, 0xee, 0xe8, 0x3f, 0xe3, + 0x5b, 0xe9, 0xcc, 0x68, 0x6d, 0xdf, 0xbc, 0x3a, 0xc4, 0x62, 0x79, 0x88, + 0xf1, 0xe4, 0xdc, 0x9b, 0x00, 0x00, 0x78, 0xf1, 0x04, 0x3a, 0xc0, 0x10, + 0xb5, 0xa5, 0xb7, 0xf3, 0xcb, 0xe9, 0xfc, 0x51, 0x4b, 0x67, 0xdf, 0x1f, + 0x86, 0x67, 0xbe, 0xe3, 0xfb, 0xeb, 0x73, 0x6f, 0x02, 0x00, 0xe0, 0x85, + 0x13, 0xe8, 0x00, 0x43, 0xdc, 0xd6, 0xcd, 0xd3, 0xfe, 0xb4, 0x28, 0xba, + 0x27, 0x4d, 0x6d, 0x9b, 0x30, 0x3d, 0x14, 0xf1, 0x8a, 0xf4, 0x43, 0xaf, + 0xc8, 0xbd, 0x09, 0x00, 0x80, 0xc6, 0x09, 0x74, 0x80, 0x61, 0xa0, 0x56, + 0xeb, 0xaa, 0xa5, 0x73, 0xcb, 0xe4, 0xc9, 0x37, 0xdf, 0x76, 0xd4, 0xb1, + 0xa3, 0x07, 0xbf, 0x89, 0xdc, 0xe2, 0xf4, 0x79, 0x49, 0xe6, 0x59, 0x00, + 0x00, 0x34, 0x40, 0xa0, 0x03, 0x0c, 0x23, 0x3b, 0x77, 0xce, 0x78, 0x32, + 0x9d, 0xe5, 0xef, 0x6a, 0xbb, 0xf1, 0xe3, 0x87, 0x56, 0x0f, 0xb9, 0x34, + 0x3d, 0xcf, 0x49, 0x9f, 0xd1, 0x99, 0x67, 0x01, 0x00, 0xb0, 0x0f, 0x04, + 0x3a, 0xc0, 0x30, 0xf4, 0x99, 0xfe, 0xb3, 0x1f, 0x49, 0x67, 0xde, 0x69, + 0x6d, 0xb7, 0xae, 0x6d, 0xaa, 0x54, 0x3e, 0x12, 0x62, 0x6c, 0x4f, 0xef, + 0x31, 0xf7, 0x2e, 0x00, 0x00, 0x9e, 0x9f, 0x40, 0x07, 0x18, 0xc6, 0xee, + 0xe8, 0x9f, 0xfe, 0xcf, 0xe9, 0x74, 0x4e, 0x6d, 0xeb, 0x5d, 0x5d, 0x54, + 0xe3, 0xf2, 0xf4, 0x3c, 0x39, 0xf7, 0x26, 0x00, 0x00, 0x7e, 0x3e, 0x81, + 0x0e, 0x30, 0x02, 0x6c, 0xeb, 0x6f, 0xbf, 0x2f, 0x9d, 0x77, 0x4c, 0x99, + 0xd6, 0x7b, 0x4a, 0xa5, 0x12, 0xaf, 0x49, 0xcf, 0x27, 0xe4, 0xde, 0x04, + 0x00, 0xc0, 0x7f, 0x26, 0xd0, 0x01, 0x46, 0x90, 0xed, 0x7d, 0xed, 0x77, + 0x15, 0x6d, 0x6d, 0x9f, 0x9b, 0x52, 0x9d, 0x32, 0x23, 0x86, 0xb0, 0x34, + 0xfd, 0xd0, 0xaf, 0xe6, 0xde, 0x04, 0x00, 0xc0, 0x33, 0x04, 0x3a, 0xc0, + 0x08, 0x53, 0xeb, 0xef, 0xdf, 0x9b, 0xce, 0x8d, 0xa7, 0x9d, 0x76, 0x43, + 0x5f, 0xd3, 0xa1, 0xe3, 0x2e, 0x08, 0x31, 0x2e, 0x4a, 0xef, 0x87, 0xe7, + 0xde, 0x05, 0x00, 0x30, 0xd2, 0x09, 0x74, 0x80, 0x11, 0xea, 0x8e, 0x3b, + 0xce, 0x79, 0x3c, 0x9d, 0xab, 0x4e, 0x6d, 0xfb, 0xef, 0x37, 0x8c, 0xa9, + 0x8c, 0xfe, 0x70, 0x8c, 0x71, 0x56, 0x7a, 0x1f, 0x95, 0x7b, 0x17, 0x00, + 0xc0, 0x48, 0x25, 0xd0, 0x01, 0x46, 0xb8, 0x1d, 0xfd, 0xef, 0xff, 0xf7, + 0x74, 0xce, 0x3f, 0xbd, 0x6d, 0xf3, 0x47, 0xab, 0xd5, 0xca, 0x95, 0xe9, + 0xf9, 0xbd, 0xc1, 0x77, 0x7c, 0x07, 0x00, 0x38, 0xe8, 0x04, 0x3a, 0x00, + 0x3f, 0x75, 0x7b, 0x7f, 0xe7, 0x3f, 0xa6, 0xd3, 0x36, 0xa5, 0xe3, 0xd6, + 0xd5, 0x95, 0x50, 0x59, 0x11, 0x62, 0xfc, 0xbd, 0xdc, 0x9b, 0x00, 0x00, + 0x46, 0x12, 0x81, 0x0e, 0xc0, 0x7f, 0xb2, 0xbd, 0x67, 0xfa, 0x17, 0xd3, + 0x79, 0x6b, 0x6b, 0xc7, 0xe6, 0xf7, 0x84, 0x50, 0x5c, 0x93, 0x42, 0xfd, + 0x35, 0xb9, 0x37, 0x01, 0x00, 0x8c, 0x04, 0x02, 0x1d, 0x80, 0x9f, 0x6b, + 0x4b, 0x4f, 0xe7, 0xa7, 0x26, 0x4f, 0xee, 0xbe, 0xeb, 0xc8, 0xf1, 0x13, + 0xce, 0x2e, 0x62, 0x5c, 0x92, 0x7e, 0xe8, 0xd8, 0xdc, 0x9b, 0x00, 0x00, + 0x86, 0x33, 0x81, 0x0e, 0xc0, 0xf3, 0xda, 0xb9, 0xb3, 0x6b, 0x4f, 0x3a, + 0x1f, 0x7b, 0xe7, 0x3b, 0x6f, 0xdd, 0x7c, 0xe8, 0xd1, 0xd5, 0x79, 0x31, + 0x84, 0x05, 0xe9, 0x7d, 0x5c, 0xee, 0x5d, 0x00, 0x00, 0xc3, 0x91, 0x40, + 0x07, 0xe0, 0x97, 0xfa, 0xec, 0x67, 0xa7, 0xff, 0x24, 0x9d, 0xa5, 0xa7, + 0x77, 0xf6, 0x6d, 0xaa, 0xd4, 0xeb, 0x4b, 0x62, 0x8c, 0x1f, 0x0c, 0xfe, + 0x1e, 0x02, 0x00, 0xb0, 0x5f, 0xf9, 0xc5, 0x15, 0x00, 0xfb, 0xec, 0xf6, + 0xcd, 0xd3, 0xbe, 0x9b, 0xce, 0xec, 0x96, 0xf6, 0x5b, 0xd7, 0xc4, 0xa2, + 0x7a, 0x4d, 0x7a, 0x7e, 0x4f, 0xee, 0x4d, 0x00, 0x00, 0xc3, 0x85, 0x40, + 0x07, 0xa0, 0x61, 0x5b, 0x7b, 0xa7, 0xff, 0x7d, 0x3a, 0xa7, 0x4d, 0xed, + 0xd8, 0xfc, 0x7b, 0x45, 0xac, 0xac, 0x48, 0xcf, 0x6f, 0xcc, 0xbd, 0x09, + 0x00, 0x60, 0xa8, 0x13, 0xe8, 0x00, 0xbc, 0x60, 0xdb, 0x7a, 0x3a, 0xff, + 0xaa, 0x28, 0x8a, 0xdf, 0x3d, 0xbd, 0xbd, 0xe7, 0xbd, 0x45, 0x08, 0x83, + 0xff, 0x0d, 0xf5, 0xff, 0x27, 0xf7, 0x26, 0x00, 0x80, 0xa1, 0x4a, 0xa0, + 0x03, 0xf0, 0xa2, 0xd4, 0x6a, 0xb5, 0x7a, 0x3a, 0xb7, 0x9d, 0xd0, 0xd6, + 0x7d, 0xfb, 0x84, 0xca, 0x6f, 0xcc, 0x8a, 0x21, 0x7e, 0x38, 0xc4, 0xf8, + 0xb2, 0xdc, 0xbb, 0x00, 0x00, 0x86, 0x1a, 0x81, 0x0e, 0xc0, 0x7e, 0xb1, + 0xab, 0xbf, 0xeb, 0xa9, 0x74, 0xd6, 0x9c, 0x7a, 0x6a, 0xcf, 0x7f, 0x1f, + 0x73, 0x44, 0x58, 0x14, 0x63, 0xbc, 0x20, 0xbd, 0x1f, 0x92, 0x7b, 0x17, + 0x00, 0xc0, 0x50, 0x21, 0xd0, 0x01, 0xd8, 0xaf, 0x76, 0xec, 0xe8, 0xf8, + 0x71, 0x3a, 0x97, 0xbe, 0xa7, 0xed, 0x96, 0xf5, 0x4d, 0xd5, 0x51, 0x4b, + 0x63, 0x08, 0x33, 0xd2, 0x7b, 0x25, 0xf7, 0x2e, 0x00, 0x80, 0xb2, 0x13, + 0xe8, 0x00, 0x1c, 0x10, 0x9f, 0xea, 0x3f, 0xf3, 0x5f, 0xd3, 0xf9, 0x40, + 0x4b, 0xfb, 0xad, 0xd7, 0xc6, 0xa2, 0xba, 0x2c, 0x3d, 0x9f, 0x92, 0x7b, + 0x13, 0xbb, 0xa6, 0xb9, 0xe9, 0x00, 0x00, 0x09, 0x29, 0x49, 0x44, 0x41, + 0x54, 0x00, 0x40, 0x99, 0x09, 0x74, 0x00, 0x0e, 0xa8, 0xad, 0xbd, 0xd3, + 0xbf, 0x92, 0xce, 0xa9, 0x2d, 0x9d, 0x7d, 0x93, 0xd3, 0x5d, 0x1e, 0x43, + 0x98, 0x94, 0x7b, 0x13, 0x00, 0x40, 0x19, 0x09, 0x74, 0x00, 0x0e, 0x8a, + 0xad, 0x9b, 0xa7, 0xed, 0x2c, 0x8a, 0xa2, 0x79, 0xea, 0xb4, 0x5b, 0xdb, + 0x43, 0x2c, 0x3e, 0x92, 0x7e, 0xe8, 0x55, 0xb9, 0x37, 0x01, 0x00, 0x94, + 0x89, 0x40, 0x07, 0xe0, 0xa0, 0x79, 0xf6, 0x3b, 0xbe, 0xf7, 0x9c, 0x7a, + 0xea, 0xda, 0xad, 0x63, 0x0e, 0x3f, 0x7a, 0x4e, 0x2c, 0xe2, 0xa5, 0xe9, + 0xfd, 0xa8, 0xdc, 0xbb, 0x00, 0x00, 0xca, 0x40, 0xa0, 0x03, 0x70, 0xd0, + 0xed, 0xd8, 0x31, 0x77, 0x77, 0x3a, 0xab, 0xdf, 0xfd, 0xee, 0xde, 0x9b, + 0x46, 0x1f, 0x1e, 0x17, 0xa7, 0xe7, 0xf3, 0xd2, 0x67, 0x4c, 0xe6, 0x59, + 0x00, 0x00, 0x59, 0x09, 0x74, 0x00, 0xb2, 0xb9, 0xf3, 0xce, 0xf6, 0x1f, + 0xa4, 0xb3, 0xe8, 0xb4, 0xb6, 0x4f, 0xae, 0xab, 0x56, 0x9b, 0xae, 0x88, + 0x21, 0x4c, 0x4f, 0xef, 0x45, 0xee, 0x5d, 0x00, 0x00, 0x39, 0x08, 0x74, + 0x00, 0xb2, 0xbb, 0xa3, 0xff, 0x8c, 0x6f, 0xa5, 0x33, 0x63, 0x6a, 0x5b, + 0xef, 0xb5, 0x45, 0x35, 0x2e, 0x4f, 0xcf, 0x7f, 0x98, 0x7b, 0x13, 0x00, + 0xc0, 0xc1, 0x26, 0xd0, 0x01, 0x28, 0x8d, 0x6d, 0xfd, 0xed, 0x5f, 0x4a, + 0xe7, 0xe4, 0xa9, 0x1d, 0x3d, 0x27, 0x17, 0x21, 0x85, 0x7a, 0x8c, 0xaf, + 0xcb, 0xbd, 0x09, 0x00, 0xe0, 0x60, 0x11, 0xe8, 0x00, 0x94, 0xce, 0xb6, + 0x9e, 0x8e, 0xbb, 0x8b, 0xa2, 0xfb, 0xcf, 0xa6, 0xb6, 0x4d, 0x98, 0x1e, + 0x8a, 0x78, 0x45, 0xfa, 0xa1, 0x57, 0xe4, 0xde, 0x04, 0x00, 0x70, 0xa0, + 0x09, 0x74, 0x00, 0x4a, 0xa9, 0x56, 0xeb, 0xaa, 0xa5, 0x73, 0xcb, 0xe4, + 0xc9, 0x37, 0xdf, 0x76, 0xd4, 0xb1, 0xa3, 0x07, 0xbf, 0x89, 0xdc, 0xc5, + 0xe9, 0x73, 0x64, 0xe6, 0x59, 0x00, 0x00, 0x07, 0x8c, 0x40, 0x07, 0xa0, + 0xd4, 0x76, 0xee, 0x9c, 0xf1, 0x64, 0x3a, 0xcb, 0x5b, 0x5a, 0x3e, 0xf9, + 0x89, 0x38, 0xa6, 0xe9, 0x92, 0xf4, 0x3c, 0x27, 0x7d, 0x46, 0x67, 0x9e, + 0x05, 0x00, 0xb0, 0xdf, 0x09, 0x74, 0x00, 0x86, 0x84, 0xad, 0x5b, 0xcf, + 0xf8, 0x8f, 0x74, 0xe6, 0x9d, 0xd6, 0x76, 0xeb, 0xda, 0xa6, 0x4a, 0xe5, + 0x23, 0x21, 0xc6, 0xf6, 0xf4, 0x1e, 0x73, 0xef, 0x02, 0x00, 0xd8, 0x5f, + 0x04, 0x3a, 0x00, 0x43, 0xca, 0x1d, 0xfd, 0xd3, 0xff, 0x39, 0x9d, 0xce, + 0xa9, 0x6d, 0xbd, 0xd7, 0xc7, 0x6a, 0x5c, 0x9e, 0x0a, 0xfd, 0x6d, 0xb9, + 0x37, 0x01, 0x00, 0xec, 0x0f, 0x02, 0x1d, 0x80, 0x21, 0x69, 0x5b, 0x7f, + 0xfb, 0xdf, 0xa6, 0xf3, 0xfb, 0xad, 0x1d, 0x9b, 0xdf, 0x13, 0x62, 0xe5, + 0xea, 0xf4, 0xfc, 0x9b, 0xb9, 0x37, 0x01, 0x00, 0xbc, 0x18, 0x02, 0x1d, + 0x80, 0x21, 0x6d, 0x4b, 0x4f, 0xe7, 0xa7, 0x8a, 0xb6, 0xb6, 0x1d, 0xa7, + 0x57, 0x4e, 0xff, 0x40, 0x8c, 0xb1, 0x2b, 0x86, 0xf0, 0x2b, 0xb9, 0x37, + 0x01, 0x00, 0xbc, 0x10, 0x02, 0x1d, 0x80, 0x21, 0xaf, 0xd6, 0xdf, 0xbf, + 0x37, 0x9d, 0x8f, 0xbd, 0xf3, 0x9d, 0xb7, 0x6e, 0x3e, 0xf4, 0xe8, 0xea, + 0xbc, 0x14, 0xe9, 0xf3, 0xd3, 0xfb, 0x61, 0xb9, 0x77, 0x01, 0x00, 0x34, + 0x42, 0xa0, 0x03, 0x30, 0x6c, 0x7c, 0xf6, 0xb3, 0xd3, 0x7f, 0x92, 0xce, + 0xd2, 0x96, 0x96, 0x5b, 0x37, 0xc6, 0x31, 0xd5, 0xae, 0xf4, 0x7c, 0x4e, + 0xfa, 0x34, 0x65, 0x9e, 0x05, 0x00, 0xb0, 0x4f, 0x04, 0x3a, 0x00, 0xc3, + 0xce, 0xd6, 0xad, 0xd3, 0xbf, 0x97, 0xce, 0x9c, 0x96, 0xf6, 0x4f, 0x5e, + 0x1f, 0x8a, 0xa6, 0xab, 0x63, 0x08, 0x53, 0x82, 0xef, 0xf8, 0x0e, 0x00, + 0x94, 0x9c, 0x40, 0x07, 0x60, 0xd8, 0xda, 0xda, 0x7b, 0xc6, 0x43, 0xe9, + 0xb4, 0x4c, 0xe9, 0xd8, 0xfc, 0xa6, 0x4a, 0xac, 0x2c, 0x4f, 0xcf, 0x6f, + 0xce, 0xbd, 0x09, 0x00, 0xe0, 0xf9, 0x08, 0x74, 0x00, 0x86, 0xbd, 0xed, + 0x3d, 0x9d, 0x5f, 0x48, 0xe7, 0x2d, 0x2d, 0xed, 0xbd, 0x53, 0x63, 0x11, + 0xaf, 0x4a, 0xcf, 0xc7, 0xe7, 0xde, 0x04, 0x00, 0xf0, 0xb3, 0x04, 0x3a, + 0x00, 0x23, 0xc6, 0xd6, 0xde, 0xf6, 0x6d, 0xcd, 0xcd, 0x37, 0xdc, 0x79, + 0xdc, 0x84, 0xc3, 0x06, 0xff, 0x6c, 0xfa, 0xe5, 0xe9, 0xf3, 0xf2, 0xdc, + 0x9b, 0x00, 0x00, 0xfe, 0x0f, 0x81, 0x0e, 0xc0, 0x88, 0x32, 0x30, 0x70, + 0xce, 0xd3, 0xe9, 0xac, 0x9f, 0x32, 0xe5, 0xa6, 0x4f, 0x16, 0x87, 0x8c, + 0x9d, 0x1f, 0x43, 0x98, 0x97, 0xde, 0x0f, 0xcd, 0xbd, 0x0b, 0x00, 0x40, + 0xa0, 0x03, 0x30, 0x22, 0x6d, 0xdf, 0x7e, 0xd6, 0xa3, 0xe9, 0x74, 0xb5, + 0xb6, 0x6e, 0xde, 0x18, 0x46, 0x15, 0x4b, 0x42, 0x8c, 0x67, 0x07, 0x7f, + 0x5f, 0x04, 0x00, 0x32, 0xf2, 0x0b, 0x11, 0x00, 0x46, 0xb4, 0x2d, 0x5b, + 0x3a, 0xbf, 0x93, 0xce, 0xcc, 0x29, 0x6d, 0x3d, 0xd7, 0x57, 0xaa, 0xc5, + 0xd5, 0xe9, 0xf9, 0x3d, 0xb9, 0x37, 0x01, 0x00, 0x23, 0x93, 0x40, 0x07, + 0x80, 0x64, 0x7b, 0x7f, 0xc7, 0x57, 0xd3, 0x39, 0xad, 0xb5, 0xbd, 0xe7, + 0xad, 0xf5, 0xa2, 0x58, 0x11, 0x43, 0x78, 0x43, 0xee, 0x4d, 0x00, 0xc0, + 0xc8, 0x22, 0xd0, 0x01, 0xe0, 0x39, 0xb6, 0xf4, 0x76, 0xdc, 0x53, 0x14, + 0xc5, 0xef, 0x9c, 0xde, 0xde, 0xf3, 0xde, 0x22, 0x84, 0xc1, 0xef, 0xf8, + 0xfe, 0xea, 0xdc, 0x9b, 0x00, 0x80, 0x91, 0x41, 0xa0, 0x03, 0xc0, 0xcf, + 0xa8, 0xd5, 0x6a, 0xf5, 0x74, 0x6e, 0x3b, 0xa1, 0xad, 0xfb, 0xf6, 0x09, + 0x95, 0xdf, 0x98, 0x15, 0x63, 0x71, 0x59, 0x7a, 0x7f, 0x69, 0xee, 0x5d, + 0x00, 0xc0, 0xf0, 0x26, 0xd0, 0x01, 0xe0, 0x79, 0xec, 0xea, 0xef, 0x7a, + 0x2a, 0x9d, 0x35, 0x27, 0xb7, 0xdd, 0x70, 0xf3, 0x61, 0x95, 0x71, 0x0b, + 0x63, 0x8c, 0x17, 0xa6, 0xf7, 0xb1, 0xb9, 0x77, 0x01, 0x00, 0xc3, 0x93, + 0x40, 0x07, 0x80, 0x5f, 0xe2, 0xee, 0xfe, 0x73, 0x7e, 0x94, 0xce, 0xa5, + 0xad, 0xad, 0x3d, 0x1b, 0xea, 0xa3, 0x8b, 0xee, 0x18, 0xc2, 0x8c, 0xf4, + 0x5e, 0xc9, 0xbd, 0x0b, 0x00, 0x18, 0x5e, 0x04, 0x3a, 0x00, 0xec, 0xa3, + 0x2d, 0x5b, 0x3a, 0xfe, 0x25, 0x9d, 0x0f, 0x4c, 0x6d, 0xeb, 0xb9, 0xae, + 0xa8, 0x16, 0xd7, 0xa4, 0xe7, 0x53, 0x72, 0x6f, 0x02, 0x00, 0x86, 0x0f, + 0x81, 0x0e, 0x00, 0x0d, 0xda, 0xd6, 0xdf, 0xb1, 0x2b, 0x9d, 0x53, 0xa7, + 0x76, 0xf4, 0xbc, 0xa3, 0x08, 0x71, 0x79, 0x88, 0x71, 0x62, 0xee, 0x4d, + 0x00, 0xc0, 0xd0, 0x27, 0xd0, 0x01, 0xe0, 0x05, 0xda, 0xd6, 0xd3, 0xf1, + 0xe7, 0x45, 0xd1, 0xdd, 0x3c, 0xb5, 0x6d, 0xc2, 0xf4, 0x50, 0xc4, 0xa5, + 0xe9, 0x87, 0x8e, 0xcb, 0xbd, 0x09, 0x00, 0x18, 0xba, 0x04, 0x3a, 0x00, + 0xbc, 0x08, 0xb5, 0x5a, 0x57, 0x2d, 0x9d, 0x5b, 0x26, 0x4f, 0xbe, 0xf9, + 0xb6, 0xa3, 0x8e, 0x1d, 0x7d, 0x5e, 0x7a, 0x5e, 0x9c, 0x3e, 0x2f, 0xc9, + 0x3c, 0x0b, 0x00, 0x18, 0x82, 0x04, 0x3a, 0x00, 0xec, 0x07, 0x3b, 0x77, + 0xce, 0x78, 0x32, 0x9d, 0xe5, 0xef, 0x6a, 0xbb, 0xf1, 0xe3, 0x87, 0x56, + 0x0f, 0xb9, 0x34, 0x3d, 0xcf, 0x49, 0x9f, 0xd1, 0x99, 0x67, 0x01, 0x00, + 0x43, 0x88, 0x40, 0x07, 0x80, 0xfd, 0xe8, 0x33, 0xfd, 0x67, 0x3f, 0x92, + 0xce, 0xbc, 0xa9, 0x1d, 0x37, 0x7f, 0xb4, 0x08, 0xa3, 0xae, 0x0c, 0x31, + 0x4e, 0x4b, 0xef, 0x45, 0xee, 0x5d, 0x00, 0x40, 0xf9, 0x09, 0x74, 0x00, + 0x38, 0x00, 0xb6, 0xf5, 0xcc, 0xf8, 0x46, 0x3a, 0x9d, 0x53, 0xdb, 0x7a, + 0x57, 0x17, 0xd5, 0xb8, 0x3c, 0x3d, 0x4f, 0xce, 0x3c, 0xe9, 0xc5, 0xd8, + 0x9b, 0x7b, 0x00, 0x00, 0x8c, 0x04, 0x02, 0x1d, 0x00, 0x0e, 0xa0, 0x6d, + 0xfd, 0xed, 0xf7, 0xa5, 0xf3, 0x8e, 0x29, 0xd3, 0x7a, 0x4f, 0xa9, 0x54, + 0xe2, 0xe0, 0x7f, 0x9a, 0xed, 0x84, 0xdc, 0x9b, 0x1a, 0x16, 0xe3, 0xe3, + 0xb9, 0x27, 0x00, 0xc0, 0x48, 0x20, 0xd0, 0x01, 0xe0, 0x20, 0xd8, 0xde, + 0xd7, 0x7e, 0x57, 0xd1, 0xd6, 0xf6, 0xb9, 0x29, 0xd5, 0x29, 0x33, 0x62, + 0x08, 0xdd, 0xe9, 0x87, 0xfe, 0x5b, 0xee, 0x4d, 0xfb, 0x2a, 0xed, 0xfd, + 0x49, 0xee, 0x0d, 0x00, 0x30, 0x12, 0x08, 0x74, 0x00, 0x38, 0x48, 0x6a, + 0xfd, 0xfd, 0x83, 0xbf, 0x55, 0xfc, 0xc6, 0xd3, 0x4e, 0xbb, 0xa1, 0xaf, + 0xe9, 0xd0, 0x71, 0x17, 0x84, 0x18, 0x17, 0xa6, 0xf7, 0x23, 0x72, 0xef, + 0xfa, 0x65, 0xea, 0xb5, 0xfa, 0xc3, 0xb9, 0x37, 0x00, 0xc0, 0x48, 0x20, + 0xd0, 0x01, 0xe0, 0x20, 0xbb, 0xe3, 0x8e, 0x73, 0x06, 0x7f, 0xcb, 0xf8, + 0x55, 0xa7, 0x75, 0xf6, 0x7d, 0xac, 0x5a, 0xaf, 0x5f, 0x16, 0x63, 0x9c, + 0x95, 0xde, 0x47, 0xe5, 0xde, 0xf5, 0x7c, 0x6a, 0xb1, 0xf6, 0xcf, 0xb9, + 0x37, 0x00, 0xc0, 0x48, 0x20, 0xd0, 0x01, 0x20, 0x93, 0x3b, 0x36, 0x4f, + 0xfb, 0x7e, 0x3a, 0xe7, 0x9f, 0xde, 0xb6, 0xf9, 0xa3, 0xd5, 0x6a, 0xe5, + 0xca, 0xf4, 0xfc, 0xde, 0xf0, 0xd3, 0xdf, 0x51, 0x5e, 0x2e, 0x4f, 0xed, + 0x7d, 0xfa, 0x6b, 0xb9, 0x37, 0x00, 0xc0, 0x48, 0x20, 0xd0, 0x01, 0x20, + 0xb3, 0xdb, 0xfb, 0x3b, 0xff, 0x31, 0x9d, 0xb6, 0x29, 0x1d, 0xb7, 0xae, + 0xae, 0xc4, 0xea, 0xe0, 0x77, 0x7c, 0x7f, 0x6b, 0xee, 0x4d, 0xcf, 0xf1, + 0xf5, 0x1d, 0xfd, 0xef, 0xff, 0xf7, 0xdc, 0x23, 0x00, 0x60, 0x24, 0x10, + 0xe8, 0x00, 0x50, 0x12, 0xdb, 0x7b, 0xa6, 0x7f, 0x31, 0x9d, 0xb7, 0xb5, + 0x76, 0x6c, 0x7e, 0x4f, 0x08, 0xc5, 0x35, 0x21, 0xc6, 0xd7, 0xe4, 0xde, + 0x54, 0x0f, 0xe1, 0xf3, 0xb9, 0x37, 0x00, 0xc0, 0x48, 0x21, 0xd0, 0x01, + 0xa0, 0x64, 0xb6, 0xf4, 0x74, 0x7e, 0x6a, 0xf2, 0xe4, 0xee, 0xbb, 0x8e, + 0x1c, 0x3f, 0xe1, 0xec, 0x22, 0xc6, 0x25, 0xe9, 0x87, 0x8e, 0xcd, 0xb5, + 0x25, 0xd6, 0x6b, 0xb7, 0xe7, 0xfa, 0xb9, 0x01, 0x60, 0xa4, 0x11, 0xe8, + 0x00, 0x50, 0x42, 0x3b, 0x77, 0x76, 0xed, 0x49, 0xe7, 0x63, 0xef, 0x7c, + 0xe7, 0xad, 0x9b, 0x0f, 0x3d, 0xba, 0x3a, 0x2f, 0x86, 0x30, 0x3f, 0xbd, + 0x1f, 0x76, 0x90, 0x67, 0xfc, 0xf8, 0x3b, 0x7b, 0xbf, 0xf7, 0xd9, 0x83, + 0xfc, 0x73, 0x02, 0xc0, 0x88, 0x25, 0xd0, 0x01, 0xa0, 0xc4, 0x3e, 0xfb, + 0xd9, 0xe9, 0x83, 0xff, 0x0d, 0xf2, 0xa5, 0xa7, 0x77, 0xf6, 0x6d, 0x4a, + 0x7f, 0xd3, 0xbe, 0x3c, 0x3d, 0x7f, 0x28, 0x1c, 0xbc, 0xbf, 0x7f, 0xdf, + 0xf4, 0xf9, 0xfe, 0x0b, 0x9e, 0x38, 0x48, 0x3f, 0x17, 0x00, 0x8c, 0x78, + 0x02, 0x1d, 0x00, 0x86, 0x80, 0xdb, 0x37, 0x4f, 0xfb, 0x6e, 0x3a, 0x73, + 0xa6, 0xb4, 0x6d, 0x5e, 0x53, 0x54, 0x2b, 0x57, 0xc5, 0x10, 0xa6, 0x1e, + 0xe0, 0x9f, 0xf2, 0xf1, 0xb0, 0xbb, 0xb6, 0xf2, 0x00, 0xff, 0x1c, 0x00, + 0xc0, 0x73, 0x08, 0x74, 0x00, 0x18, 0x42, 0xb6, 0xf7, 0x77, 0x3e, 0x98, + 0x4e, 0x4b, 0x6b, 0x7b, 0xcf, 0x9b, 0x43, 0x51, 0x0c, 0x7e, 0xc7, 0xf7, + 0x37, 0x1d, 0x88, 0x9f, 0xa7, 0x1e, 0xc2, 0x55, 0x5b, 0xb7, 0x74, 0xfc, + 0xcb, 0x81, 0xf8, 0x6b, 0x03, 0x00, 0x3f, 0x9f, 0x40, 0x07, 0x80, 0x21, + 0x68, 0x4b, 0x6f, 0xc7, 0xe7, 0x8b, 0xa2, 0x78, 0xcb, 0x94, 0xb6, 0xcd, + 0x53, 0x62, 0x11, 0xaf, 0x4e, 0x3f, 0x34, 0x61, 0xbf, 0xfd, 0xc5, 0xeb, + 0xf5, 0xbf, 0xfa, 0xc1, 0xc3, 0x0f, 0x2d, 0xdb, 0x6f, 0x7f, 0x3d, 0x00, + 0x60, 0x9f, 0x08, 0x74, 0x00, 0x18, 0xa2, 0x6a, 0xb5, 0x5a, 0x3d, 0x9d, + 0x6d, 0xcd, 0xcd, 0x37, 0xdc, 0x79, 0xdc, 0x84, 0xc3, 0xce, 0x49, 0xcf, + 0x5d, 0xe9, 0x73, 0xcc, 0x8b, 0xfc, 0xcb, 0xfe, 0xc3, 0x13, 0x7b, 0x77, + 0xb7, 0x3c, 0xfb, 0x4d, 0xea, 0x00, 0x80, 0x83, 0x48, 0xa0, 0x03, 0xc0, + 0x10, 0x37, 0x30, 0x70, 0xce, 0xd3, 0xe9, 0xac, 0x3f, 0xf5, 0xd4, 0x9e, + 0x5b, 0xc7, 0x1c, 0x59, 0x2c, 0x8c, 0x21, 0x9c, 0x9f, 0xde, 0xc7, 0x35, + 0xfc, 0x17, 0xaa, 0xd7, 0xff, 0x6a, 0x30, 0xce, 0x77, 0xf4, 0xbf, 0xff, + 0xdf, 0xf7, 0xfb, 0x48, 0x00, 0xe0, 0x97, 0x12, 0xe8, 0x00, 0x30, 0x4c, + 0xec, 0xd8, 0xd1, 0xf1, 0xe3, 0x74, 0x3e, 0xdc, 0xd2, 0x72, 0xeb, 0x9a, + 0x30, 0xba, 0x72, 0x7e, 0x8c, 0xf1, 0x83, 0x61, 0xdf, 0xfe, 0x8d, 0xfa, + 0x0f, 0x52, 0x9c, 0x5f, 0xf5, 0xc8, 0xc3, 0x0f, 0x5d, 0xe7, 0xdf, 0x9c, + 0x03, 0x40, 0x3e, 0x02, 0x1d, 0x00, 0x86, 0x99, 0xad, 0x5b, 0xa7, 0x7f, + 0x2f, 0x9d, 0x4b, 0x9b, 0x9b, 0x6f, 0x58, 0xf2, 0xdf, 0x5e, 0x3d, 0x6e, + 0x72, 0xa5, 0x12, 0xdf, 0x99, 0xde, 0xdf, 0x90, 0x3e, 0xaf, 0x4e, 0x9f, + 0x23, 0xc2, 0x4f, 0xbf, 0x07, 0x5c, 0xf8, 0x4e, 0xfa, 0x3c, 0x90, 0x1e, + 0xee, 0xaa, 0x3d, 0xfe, 0xc4, 0xff, 0xd8, 0xbe, 0xfd, 0xac, 0x47, 0x33, + 0x4e, 0x06, 0x00, 0x82, 0x40, 0x07, 0x80, 0x61, 0xeb, 0xd9, 0xdf, 0xfa, + 0xfe, 0xb9, 0x67, 0x3f, 0x00, 0x40, 0xc9, 0x09, 0x74, 0x00, 0x00, 0x00, + 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, + 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, + 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, + 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, + 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, + 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, + 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, + 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, + 0x03, 0x00, 0x00, 0x40, 0x09, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, + 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, + 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, + 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, 0x00, + 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, 0x08, 0x74, + 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, + 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, + 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, + 0x50, 0x02, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, + 0x00, 0x40, 0x09, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, + 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, + 0x3a, 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, + 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, 0x00, 0x00, 0x00, + 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, 0x08, 0x74, 0x00, 0x00, + 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, + 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, + 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, + 0x02, 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, + 0x09, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, + 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, + 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, + 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, + 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, + 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, + 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, + 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, + 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, 0x08, + 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, + 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, + 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, 0x12, 0x10, 0xe8, 0x00, 0x00, + 0x00, 0x50, 0x02, 0x02, 0x1d, 0x00, 0x00, 0x00, 0x4a, 0x40, 0xa0, 0x03, + 0x00, 0x00, 0x40, 0x09, 0x08, 0x74, 0x00, 0x00, 0x00, 0x28, 0x01, 0x81, + 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, 0x01, 0x00, 0x00, 0xa0, 0x04, + 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, 0x80, 0x40, 0x07, 0x00, 0x00, 0x80, + 0x12, 0x10, 0xe8, 0x00, 0x00, 0x00, 0x50, 0x02, 0x02, 0x1d, 0x00, 0x00, + 0x00, 0x4a, 0x40, 0xa0, 0x03, 0x00, 0x00, 0x40, 0x09, 0x08, 0x74, 0x00, + 0x00, 0x00, 0x28, 0x01, 0x81, 0x0e, 0x00, 0x00, 0x00, 0x25, 0x20, 0xd0, + 0x01, 0x00, 0x00, 0xa0, 0x04, 0x04, 0x3a, 0x00, 0x00, 0x00, 0x94, 0xc0, + 0xff, 0x06, 0x85, 0x9c, 0x0c, 0xdb, 0xfe, 0xcf, 0x92, 0x15, 0x00, 0x00, + 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82} diff --git a/cmd/cryptonym-wallet/.gitignore b/cmd/cryptonym-wallet/.gitignore new file mode 100644 index 0000000..ba15096 --- /dev/null +++ b/cmd/cryptonym-wallet/.gitignore @@ -0,0 +1 @@ +cryptonym-wallet diff --git a/cmd/cryptonym-wallet/Icon.png b/cmd/cryptonym-wallet/Icon.png new file mode 100644 index 0000000..1733305 Binary files /dev/null and b/cmd/cryptonym-wallet/Icon.png differ diff --git a/cmd/cryptonym-wallet/main.go b/cmd/cryptonym-wallet/main.go new file mode 100644 index 0000000..29d8d41 --- /dev/null +++ b/cmd/cryptonym-wallet/main.go @@ -0,0 +1,1030 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "fyne.io/fyne" + "fyne.io/fyne/layout" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + explorer "github.com/blockpane/cryptonym" + fioassets "github.com/blockpane/cryptonym/assets" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go" + "golang.org/x/text/language" + "golang.org/x/text/message" + "net/url" + "os" + "runtime" + "sort" + "strings" + "sync" + "time" +) + +type tabs struct { + Editor widget.TabItem + Api widget.TabItem + Info widget.TabItem + Browser widget.TabItem + Abi widget.TabItem + AccountInfo widget.TabItem + KeyGen widget.TabItem + Msig widget.TabItem + Vote widget.TabItem + Requests widget.TabItem +} + +var ( + uri = &explorer.Uri + proxy = func() *string { + s := "127.0.0.1:8080" + return &s + }() + api = explorer.Api + opts = explorer.Opts + account = explorer.Account + balance float64 + actionsGroup = &widget.Group{} + connectButton = &widget.Button{} + proxyCheck = &widget.Check{} + keyContent = &widget.Box{} + tabContent = &widget.TabContainer{} + tabEntries = tabs{} + hostEntry = explorer.NewClickEntry(connectButton) + myFioAddress = widget.NewEntry() + moneyBags = widget.NewSelect(moneySlice(), func(s string) {}) + wifEntry = widget.NewPasswordEntry() + balanceLabel = widget.NewLabel("Balance: unknown") + loadButton = &widget.Button{} + importButton = &widget.Button{} + balanceButton = &widget.Button{} + regenButton = &widget.Button{} + uriContent = uriInput(true) + uriContainer = &fyne.Container{} + ready = false + connectedChan = make(chan bool, 1) + p = message.NewPrinter(language.English) + keyBox = &widget.Box{} + serverInfoCh = make(chan explorer.ServerInfo) + serverInfoRef = make(chan bool) + serverInfoBox = explorer.InitServerInfo(serverInfoCh, serverInfoRef) +) + +// ActionButtons is a slice of pointers to our action buttons, this way we can set them to hidden if using +// the filter .... +var ( + ActionButtons = make([]*widget.Button, 0) + ActionLabels = make([]*widget.Label, 0) + filterActions = &widget.Entry{} + filterCheck = &widget.Check{} + prodsCheck = &widget.Check{} +) + +var savedKeys = map[string]string{ + "devnet vote1": "5JBbUG5SDpLWxvBKihMeXLENinUzdNKNeozLas23Mj6ZNhz3hLS", + "devnet vote2": "5KC6Edd4BcKTLnRuGj2c8TRT9oLuuXLd3ZuCGxM9iNngc3D8S93", + "devnet bp1": "5KQ6f9ZgUtagD3LZ4wcMKhhvK9qy4BuwL3L1pkm6E2v62HCne2R", + "devnet locked1": "5HwvMtAEd7kwDPtKhZrwA41eRMdFH5AaBKPRim6KxkTXcg5M9L5", +} + +func main() { + // the MacOS resolver causes serious performance issues, if GODEBUG is empty, then set it to force pure go resolver. + if runtime.GOOS == "darwin" { + gdb := os.Getenv("GODEBUG") + if gdb == "" { + _ = os.Setenv("GODEBUG", "netdns=go") + } + } + topLayout := &fyne.Container{} + errs.ErrTxt[0] = fmt.Sprintf("\nEvent Log: started at %s", time.Now().Format(time.Stamp)) + errs.ErrMsgs.SetText(strings.Join(errs.ErrTxt, "\n")) + keyContent = keyBoxContent() + myFioAddress.Hide() + + loadButton.Disable() + balanceButton.Disable() + regenButton.Disable() + + space := strings.Repeat(" ", 55) + go func() { + for { + select { + case <-connectedChan: + time.Sleep(time.Second) + serverInfoRef <- true + explorer.Connected = true + uriContainer.Objects = []fyne.CanvasObject{ + widget.NewVBox( + widget.NewLabel(" "), + widget.NewHBox( + widget.NewLabel(space), + widget.NewLabel(" nodeos @ "+*uri+" "), + widget.NewLabel(space), + ), + ), + } + loadButton.Enable() + balanceButton.Enable() + regenButton.Enable() + refreshMyName() + case <-errs.RefreshChan: + if !ready { + continue + } + refreshNotNil(loadButton) + refreshNotNil(balanceButton) + refreshNotNil(regenButton) + refreshNotNil(actionsGroup) + refreshNotNil(hostEntry) + refreshNotNil(uriContent) + refreshNotNil(keyContent) + refreshNotNil(topLayout) + refreshNotNil(errs.ErrMsgs) + refreshNotNil(tabEntries.Info.Content) + refreshNotNil(tabEntries.Editor.Content) + refreshNotNil(tabEntries.Api.Content) + refreshNotNil(tabEntries.Msig.Content) + if explorer.TableIndex.IsCreated() { + refreshNotNil(tabEntries.Browser.Content) + refreshNotNil(tabEntries.Abi.Content) + } + refreshNotNil(tabContent) + if moneyBags.Hidden { + moneyBags.Show() + } + } + } + }() + + if reconnect(account) { + connectedChan <- true + } + + updateActions(ready, opts) + tabEntries = makeTabs() + // KeyGen has to be created after others to prevent a race: + tabContent = widget.NewTabContainer( + &tabEntries.Info, + &tabEntries.AccountInfo, + &tabEntries.Abi, + &tabEntries.Browser, + &tabEntries.Editor, + &tabEntries.Api, + widget.NewTabItem("Key Gen", explorer.KeyGenTab()), + &tabEntries.Vote, + &tabEntries.Msig, + &tabEntries.Requests, + ) + + uriContainer = fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(5, 35)), + uriContent, + ) + tabEntries.Info.Content = widget.NewVBox( + layout.NewSpacer(), + uriContainer, + layout.NewSpacer(), + ) + refreshNotNil(tabEntries.Info.Content) + + topLayout = fyne.NewContainerWithLayout(layout.NewHBoxLayout(), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(explorer.ActionW, explorer.PctHeight())), + actionsGroup, + ), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(10, explorer.PctHeight())), + layout.NewSpacer(), + ), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(explorer.RWidth(), explorer.PctHeight())), + fyne.NewContainerWithLayout(layout.NewVBoxLayout(), + tabContent, + layout.NewSpacer(), + fyne.NewContainerWithLayout(layout.NewVBoxLayout(), + keyContent, + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(explorer.RWidth(), 150)), + widget.NewScrollContainer( + errs.ErrMsgs, + ), + ), + ), + ), + ), + ) + + go func(repaint chan bool) { + for { + select { + case <-repaint: + // bug in Refresh doesn't get the group title, hide then show works + actionsGroup.Hide() + actionsGroup.Show() + if tabContent != nil { + tabContent.SelectTabIndex(0) + } + explorer.Win.Content().Refresh() + explorer.RefreshQr <- true + errs.RefreshChan <- true + } + } + }(explorer.RepaintChan) + + explorer.Win.SetMainMenu(fyne.NewMainMenu(fyne.NewMenu("Settings", + fyne.NewMenuItem("Options", func() { + go explorer.SettingsWindow() + }), + fyne.NewMenuItem("Reload Saved Settings", func() { + go explorer.PromptForPassword() + }), + fyne.NewMenuItem("Connect to different server", func() { + go uriModal() + }), + fyne.NewMenuItem("", func() {}), + fyne.NewMenuItem("Dark Theme", func() { + explorer.WinSettings.T = "Dark" + explorer.RefreshQr <- true + fyne.CurrentApp().Settings().SetTheme(explorer.CustomTheme()) + explorer.RepaintChan <- true + }), + fyne.NewMenuItem("Darker Theme", func() { + explorer.WinSettings.T = "Darker" + explorer.RefreshQr <- true + fyne.CurrentApp().Settings().SetTheme(explorer.DarkerTheme().ToFyneTheme()) + explorer.RepaintChan <- true + }), + fyne.NewMenuItem("Light Theme", func() { + explorer.WinSettings.T = "Light" + explorer.RefreshQr <- true + fyne.CurrentApp().Settings().SetTheme(explorer.ExLightTheme().ToFyneTheme()) + explorer.RepaintChan <- true + }), + fyne.NewMenuItem("Grey Theme", func() { + explorer.WinSettings.T = "Grey" + explorer.RefreshQr <- true + fyne.CurrentApp().Settings().SetTheme(explorer.ExGreyTheme().ToFyneTheme()) + explorer.RepaintChan <- true + }), + ))) + + ready = true + updateActions(ready, opts) + explorer.Win.Resize(fyne.NewSize(explorer.W-10, (explorer.H*95)/100)) + explorer.Win.SetFixedSize(true) + *uri = "http://127.0.0.1:8888" + hostEntry.SetText(*uri) + errs.RefreshChan <- true + explorer.Win.SetContent(topLayout) + explorer.Win.SetMaster() + explorer.Win.SetOnClosed(func() { + explorer.App.Quit() + }) + go func() { + time.Sleep(100 * time.Millisecond) + switch explorer.WinSettings.T { + case "Dark": + fyne.CurrentApp().Settings().SetTheme(explorer.CustomTheme()) + case "Darker": + fyne.CurrentApp().Settings().SetTheme(explorer.DarkerTheme().ToFyneTheme()) + case "Grey": + fyne.CurrentApp().Settings().SetTheme(explorer.ExGreyTheme().ToFyneTheme()) + case "Light": + fyne.CurrentApp().Settings().SetTheme(explorer.ExLightTheme().ToFyneTheme()) + } + go explorer.PromptForPassword() + go settingsReload(explorer.SettingsLoaded) + }() + explorer.Win.ShowAndRun() +} + +func refreshNotNil(object fyne.CanvasObject) { + if object != nil { + object.Refresh() + } +} + +func settingsReload(newSettings chan *explorer.FioSettings) { + for { + select { + case s := <-newSettings: + if !strings.HasPrefix(s.Server, "http") { + s.Server = "http://" + s.Server + } + *uri = s.Server + hostEntry.SetText(*uri) + savedKeys = map[string]string{ + s.DefaultKeyDesc: s.DefaultKey, + s.FavKey2Desc: s.FavKey2, + s.FavKey3Desc: s.FavKey3, + s.FavKey4Desc: s.FavKey4, + } + moneyBags.Options = moneySlice() + newAccount, err := fio.NewAccountFromWif(s.DefaultKey) + keyContent.Children = keyBoxContent().Children + if err != nil { + errs.ErrChan <- "error loading key from saved settings. " + err.Error() + } else { + dr := *newAccount + account = &dr + explorer.Account = &dr + wifEntry.SetText(s.DefaultKey) + importButton.OnTapped() + tabContent.SelectTabIndex(0) + } + } + } +} + +func refreshMyName() { + if account.Addresses != nil && len(account.Addresses) > 0 { + txt := account.Addresses[0].FioAddress + func(s string) { + myFioAddress.OnChanged = func(string) { + myFioAddress.SetText(s) + } + }(txt) + myFioAddress.SetText(txt) + explorer.DefaultFioAddress = account.Addresses[0].FioAddress + } else { + if found, _, e := account.GetNames(api); e == nil && found > 0 { + txt := account.Addresses[0].FioAddress + func(s string) { + myFioAddress.OnChanged = func(string) { + myFioAddress.SetText(s) + } + }(txt) + myFioAddress.SetText(txt) + explorer.DefaultFioAddress = account.Addresses[0].FioAddress + } else { + myFioAddress.OnChanged = func(string) { + myFioAddress.SetText("") + } + explorer.DefaultFioAddress = "" + myFioAddress.SetText("") + myFioAddress.Hide() + } + } + if myFioAddress.Text == "" && !myFioAddress.Hidden { + myFioAddress.Hide() + } else if myFioAddress.Hidden { + myFioAddress.Show() + } + refreshNotNil(myFioAddress) + refreshNotNil(keyContent) +} + +var clientMux = sync.Mutex{} + +var apiDeadCounter int + +func refreshInfo(deadline time.Duration) (string, bool) { + if api == nil || explorer.Api == nil || api.BaseURL == "" { + return "", false + } + d := time.Now().Add(deadline) + ctx, cancel := context.WithDeadline(context.Background(), d) + defer cancel() + resultChan := make(chan string, 1) + go func() { + clientMux.Lock() + defer clientMux.Unlock() + i, e := api.GetInfo() + if e != nil { + apiDeadCounter += 1 + errs.ErrChan <- e.Error() + if apiDeadCounter >= 10 { + errs.ErrChan <- "connection seems to be having issues, trying to reconnect" + var err error + explorer.Api, explorer.Opts, err = fio.NewConnection(explorer.Account.KeyBag, explorer.Uri) + if err != nil { + errs.ErrChan <- err.Error() + return + } + api = explorer.Api + opts = explorer.Opts + } + return + } + apiDeadCounter = 0 + j, _ := json.MarshalIndent(i, "", " ") + serverInfoCh <- explorer.ServerInfo{ + Info: i, + Uri: api.BaseURL, + } + resultChan <- string(j) + }() + select { + case s := <-resultChan: + return s, true + case <-ctx.Done(): + errs.ErrChan <- "failed to update server info in time, reducing poll frequency." + return "", false + } +} + +func reconnect(account *fio.Account) (result bool) { + errs.DisconnectChan <- result + defer func() { + errs.DisconnectChan <- result + }() + clientMux.Lock() + defer clientMux.Unlock() + var err error + api, opts, err = fio.NewConnection(account.KeyBag, *uri) + if err != nil { + if *uri != "" { + errs.ErrChan <- err.Error() + } + return + } + api.Header.Set("User-Agent", "fio-cryptonym-wallet") + explorer.Api, explorer.Opts, _ = fio.NewConnection(account.KeyBag, *uri) + explorer.Api.Header.Set("User-Agent", "fio-cryptonym-wallet") + errs.ErrChan <- "connected to nodeos at " + *uri + explorer.Win.SetTitle(fmt.Sprintf("Cryptonym - nodeos @ %s", *uri)) + errs.RefreshChan <- true + go func() { + time.Sleep(2 * time.Second) + explorer.BalanceChan <- true + }() + explorer.ResetTxResult() + result = true + return +} + +func updateActions(ready bool, opts *fio.TxOptions) { + newGroup := true + if actionsGroup == nil || actionsGroup.Text == "" { + actionsGroup = widget.NewGroupWithScroller("Not Connected") + } + if !ready { + return + } + if len(ActionButtons) > 0 { + newGroup = false + } + var err error + clientMux.Lock() + api, opts, err = fio.NewConnection(account.KeyBag, *uri) + if err != nil { + errs.ErrChan <- "not connected to nodeos server" + clientMux.Unlock() + return + } + api.Header.Set("User-Agent", "fio-cryptonym-wallet") + explorer.Api, explorer.Opts, _ = fio.NewConnection(account.KeyBag, *uri) + explorer.Api.Header.Set("User-Agent", "fio-cryptonym-wallet") + _, err = api.GetInfo() + if err != nil { + errs.ErrChan <- "not connected to nodeos server" + clientMux.Unlock() + return + } + actionsGroup.Text = "Actions" + + if newGroup { + filterActions = widget.NewEntry() + filterActions.SetPlaceHolder("Filter Actions") + filterActions.OnChanged = showHideActions + actionsGroup.Append(filterActions) + filterCheck = widget.NewCheck("Hide Privileged", func(bool) { + showHideActions(filterActions.Text) + }) + filterCheck.SetChecked(true) + actionsGroup.Append(widget.NewHBox(layout.NewSpacer(), filterCheck, layout.NewSpacer())) + prodsCheck = widget.NewCheck("Hide Producer", func(bool) { + showHideActions(filterActions.Text) + }) + prodsCheck.SetChecked(true) + actionsGroup.Append(widget.NewHBox(layout.NewSpacer(), prodsCheck, layout.NewSpacer())) + } else { + filterActions.SetText("") + } + + a, err := explorer.GetAccountSummary(api) + clientMux.Unlock() + if err != nil { + errs.ErrChan <- err.Error() + return + } + if a == nil || a.Actions == nil || len(a.Actions) == 0 { + errs.ErrChan <- "could not find any ABIs" + return + } + found := 0 + if !newGroup { + for _, l := range ActionLabels { + if l != nil { + l.SetText("disabled") + l.Hide() + } + } + for _, b := range ActionButtons { + if b != nil { + b.SetText("disabled") + b.Hide() + } + } + } + for _, contract := range a.Index { + label := widget.NewLabel(strings.ToUpper(contract)) + actionsGroup.Append(label) + ActionLabels = append(ActionLabels, label) + sort.Strings(a.Actions[contract]) + for _, b := range a.Actions[contract] { + button := &widget.Button{} + button = widget.NewButton(fmt.Sprintf("%s::%s", contract, b), func() { + tabContent.SelectTabIndex(4) + if form, e := explorer.GetAbiForm(button.Text, account, api, opts); e == nil { + tabEntries.Editor.Content = form + tabEntries.Editor.Text = "Action - " + button.Text + errs.RefreshChan <- true + } + }) + ActionButtons = append(ActionButtons, button) + button.Style = 0 + actionsGroup.Append(button) + found = found + 1 + } + } + if found > 0 { + errs.ErrChan <- fmt.Sprintf("found %d actions", found) + tabEntries.Info = *widget.NewTabItem("Server", + widget.NewVBox( + serverInfoBox, + )) + if browser, ok := explorer.GetTableBrowser(explorer.W, explorer.H, api); ok { + tabEntries.Browser = *widget.NewTabItem("Tables", browser) + } + if abiView, ok := explorer.GetAbiViewer(explorer.W, explorer.H, api); ok { + tabEntries.Abi = *widget.NewTabItem("ABIs", abiView) + } + updateTabChan := make(chan fyne.Container) + go func(newBox chan fyne.Container) { + for { + select { + case nb := <-newBox: + tabEntries.AccountInfo = *widget.NewTabItem("Accounts", &nb) + errs.RefreshChan <- true + } + } + }(updateTabChan) + updateApiChan := make(chan fyne.Container) + go func(newApiTab chan fyne.Container) { + for { + select { + case nb := <-newApiTab: + tabEntries.Api = *widget.NewTabItem("APIs", &nb) + errs.RefreshChan <- true + } + } + }(updateApiChan) + explorer.NewAccountSearchTab(updateTabChan, account) + explorer.NewApiRequestTab(updateApiChan) + updateMsigChan := make(chan fyne.Container) + go func(newMsigTab chan fyne.Container) { + for { + select { + case nb := <-newMsigTab: + tabEntries.Msig = *widget.NewTabItem("mSig", &nb) + // TODO: use a cancel context to timeout if nothing is listening on the channel: + go func() { + time.Sleep(time.Second) + if explorer.MsigLoaded { + explorer.MsigRefreshRequests <- false + } + }() + errs.RefreshChan <- true + } + } + }(updateMsigChan) + go func() { + time.Sleep(2 * time.Second) + explorer.UpdateAuthContent(updateMsigChan, api, opts, account) + }() + updateVoteChan := make(chan fyne.CanvasObject) + go func(content chan fyne.CanvasObject) { + for { + select { + case c := <-content: + tabEntries.Vote = *widget.NewTabItem("Vote", fyne.NewContainerWithLayout( + layout.NewFixedGridLayout(fyne.NewSize(explorer.RWidth(), explorer.PctHeight()-250)), + c, + )) + tabContent.Refresh() + } + } + }(updateVoteChan) + go func() { + time.Sleep(4 * time.Second) + explorer.VoteContent(updateVoteChan, explorer.RefreshVotesChan) + }() + updateRequestChan := make(chan fyne.CanvasObject) + go func(content chan fyne.CanvasObject) { + for { + select { + case c := <-content: + tabEntries.Requests = *widget.NewTabItem("Requests", fyne.NewContainerWithLayout( + layout.NewFixedGridLayout(fyne.NewSize(explorer.RWidth(), explorer.PctHeight()-250)), + c, + )) + tabContent.Refresh() + } + } + }(updateRequestChan) + go func() { + time.Sleep(3 * time.Second) + explorer.RequestContent(updateRequestChan, explorer.RefreshRequestsChan) + }() + showHideActions("") + errs.RefreshChan <- true + connectedChan <- true + } +} + +func showHideActions(s string) { + for _, b := range ActionButtons { + if b == nil { + continue + } + switch { + case b.Text == "disabled": + b.Hide() + case (s != "" && !strings.Contains(b.Text, s)) || (filterCheck.Checked && explorer.PrivilegedActions[b.Text]) || + (prodsCheck.Checked && explorer.ProducerActions[b.Text]): + b.Hide() + default: + b.Show() + } + } + for _, l := range ActionLabels { + if l == nil { + continue + } + if l.Text == "disabled" { + l.Hide() + } + } +} + +func moneySlice() []string { + o := make([]string, 0) + for k := range savedKeys { + o = append(o, k) + } + sort.Strings(o) + return o +} + +func makeTabs() tabs { + return tabs{ + Editor: *widget.NewTabItem("Actions", widget.NewLabel("Select a Contract Action on the left")), + Api: *widget.NewTabItem("APIs", widget.NewLabel("Not Connected")), + Info: *widget.NewTabItem("Server", widget.NewLabel("Not Connected")), + Browser: *widget.NewTabItem("Tables", widget.NewLabel("Not Connected")), + Abi: *widget.NewTabItem("ABIs", widget.NewLabel("Not Connected")), + AccountInfo: *widget.NewTabItem("Accounts", widget.NewLabel("Not Connected")), + KeyGen: *widget.NewTabItem("Key Gen", widget.NewLabel("")), + Msig: *widget.NewTabItem("mSig", widget.NewLabel("Not Connected")), + Vote: *widget.NewTabItem("Vote", widget.NewLabel("Not Connected")), + Requests: *widget.NewTabItem("Requests", widget.NewLabel("Not Connected")), + } +} + +func uriModal() { + if explorer.PasswordVisible { + return + } + go uriSimplerInput() +} + +func uriSimplerInput() { + nw := explorer.App.NewWindow("new connection") + hEntry := &explorer.EnterEntry{} + cancelButton := widget.NewButton("cancel", func() { + nw.Hide() + }) + connectButton = widget.NewButtonWithIcon("connect", fioassets.NewFioLogoResource(), func() { + connectButton.Disable() + myFioAddress.OnChanged = func(string) { + myFioAddress.SetText("") + } + explorer.DefaultFioAddress = "" + myFioAddress.SetText("") + myFioAddress.Hide() + host := hostEntry.Text + if !strings.HasPrefix(host, "http") { + host = "http://" + host + } + *uri = host + if reconnect(account) { + updateActions(true, opts) + nw.Hide() + nw = nil + } else { + connectButton.Enable() + } + }) + hostEntry.Button = connectButton + hEntry = explorer.NewEnterEntry(func() { + connectButton.Disable() + host := hostEntry.Text + if !strings.HasPrefix(host, "http") { + host = "http://" + host + } + *uri = host + if reconnect(account) { + updateActions(true, opts) + nw.Hide() + nw = nil + } else { + connectButton.Enable() + } + }) + mainSelect := widget.NewSelect(explorer.MainnetApi, func(s string) { + hostEntry.SetText(s) + }) + mainSelect.PlaceHolder = "Mainnet Nodes" + hEntry.Text = *uri + nw.SetContent(widget.NewVBox( + layout.NewSpacer(), + widget.NewHBox(layout.NewSpacer(), hostEntry, cancelButton, connectButton, layout.NewSpacer()), + widget.NewHBox(layout.NewSpacer(), mainSelect, layout.NewSpacer()), + layout.NewSpacer()), + ) + nw.Resize(fyne.NewSize(400, 200)) + //nw.SetFixedSize(true) + nw.Show() +} + +func uriInput(showProxy bool) *widget.Box { + hostEntry.Text = *uri + proxyProto := widget.NewSelect([]string{"http://", "https://"}, func(s string) {}) + proxyProto.Hide() + proxyUrl := &widget.Entry{} + proxyUrl = widget.NewEntry() + proxyUrl.OnChanged = func(s string) { + *proxy = s + } + proxyUrl.SetText(*proxy) + proxyUrl.Hide() + proxyCheck = widget.NewCheck("Use a proxy", func(b bool) { + switch b { + case true: + proxyProto.Show() + proxyProto.SetSelected("http://") + proxyUrl.Show() + default: + proxyProto.Hide() + proxyUrl.Hide() + } + }) + if !showProxy || os.Getenv("ADVANCED") == "" { + proxyCheck.Hide() + } + connectButton = widget.NewButtonWithIcon("connect", fioassets.NewFioLogoResource(), func() { + connectButton.Disable() + if proxyCheck.Checked { + if _, e := url.Parse(proxyProto.Selected + proxyUrl.Text); e != nil { + errs.ErrChan <- "invalid proxy URL specified" + connectButton.Enable() + return + } + if strings.Contains(hostEntry.Text, "127.0.0.1") || strings.Contains(hostEntry.Text, "localhost") { + errs.ErrChan <- "Warning: will not use proxy for connections to localhost" + } + // all we need to do is set the ENV vars and eos-go will pick up our settings + refreshNotNil(proxyUrl) + switch proxyProto.Selected { + case "https://": + os.Unsetenv("HTTP_PROXY") + os.Unsetenv("HTTPS_PROXY") + os.Setenv("HTTPS_PROXY", proxyProto.Selected+*proxy) + default: + os.Unsetenv("HTTP_PROXY") + os.Unsetenv("HTTPS_PROXY") + os.Setenv("HTTP_PROXY", proxyProto.Selected+*proxy) + } + } + host := hostEntry.Text + if !strings.HasPrefix(host, "http") { + host = "http://" + host + } + *uri = host + if reconnect(account) { + updateActions(true, opts) + } else { + connectButton.Enable() + } + }) + hostEntry.Button = connectButton + if !showProxy { + connectButton.Hide() + } + + return widget.NewHBox(connectButton, hostEntry, proxyCheck, proxyProto, proxyUrl) +} + +var ( + refreshWorkerCounter int +) + +func keyBoxContent() *widget.Box { + doImport := func() {} + entryChan := make(chan string) + moneyBags.OnChanged = func(s string) { + if s != "" && s != moneyBags.PlaceHolder && wifEntry != nil { + entryChan <- s + } + } + moneyBags.PlaceHolder = "Quick Load Saved Key" + moneyBags.Refresh() + pubkey := widget.NewEntry() + func(s string) { + pubkey.OnChanged = func(string) { + pubkey.SetText(s) + } + }(account.PubKey) // deref + pubkey.SetText(account.PubKey) + actor := widget.NewEntry() + func(s string) { + actor.OnChanged = func(string) { + actor.SetText(s) + } + }(string(account.Actor)) // deref + actor.SetText(string(account.Actor)) + var txt string + if account.Addresses != nil && len(account.Addresses) > 0 { + txt = account.Addresses[0].FioAddress + } else { + myFioAddress.Hide() + } + func(s string) { + myFioAddress.OnChanged = func(string) { + myFioAddress.SetText(s) + } + }(txt) + myFioAddress.SetText(txt) + + wifWindow := explorer.App.NewWindow("Import WIF") + doImport = func() { + explorer.Win.RequestFocus() + newAcc, err := fio.NewAccountFromWif(wifEntry.Text) + errs.ErrChan <- "Importing new WIF ..." + if err != nil { + errs.ErrChan <- "import failed: " + err.Error() + //wifWindow.Hide() + return + } + tabContent.SelectTabIndex(1) + myFioAddress.OnChanged = func(string) { + myFioAddress.SetText("") + } + myFioAddress.SetText("") + myFioAddress.Hide() + derefAcc := *newAcc + account = &derefAcc + explorer.Account = &derefAcc + func(s string) { + actor.OnChanged = func(string) { + actor.SetText(s) + } + }(string(account.Actor)) + actor.SetText(string(account.Actor)) + updateActions(reconnect(account), opts) + func(s string) { + pubkey.OnChanged = func(string) { + pubkey.SetText(s) + } + }(account.PubKey) + pubkey.SetText(account.PubKey) + myFioAddress.OnChanged = func(string) { + myFioAddress.SetText("") + } + myFioAddress.SetText("") + if !myFioAddress.Hidden { + myFioAddress.Hide() + go refreshMyName() + explorer.RefreshVotesChan <- true + explorer.RefreshRequestsChan <- true + } + } + + importButton = widget.NewButton("Import", func() { + doImport() + wifWindow.Close() + }) + loadButton = widget.NewButtonWithIcon("Load Key", theme.MenuDropUpIcon(), func() { + wifEntry.SetPlaceHolder("5xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx") + wifWindow := explorer.App.NewWindow("Import WIF") + wifWindow.Resize(fyne.NewSize(450, 180)) + keyBox = widget.NewVBox( + wifEntry, + layout.NewSpacer(), + widget.NewHBox( + widget.NewButton("Import", func() { + doImport() + wifWindow.Close() + }), + widget.NewButton( + "Cancel", func() { + wifWindow.Close() + explorer.Win.RequestFocus() + return + }, + ), + layout.NewSpacer(), moneyBags, + ), + ) + wifWindow.SetContent(keyBox) + wifWindow.Show() + }) + + balanceButton = widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), func() { + errs.ErrChan <- "refreshing" + explorer.BalanceChan <- true + refreshMyName() + }) + balanceButton.Disable() + updateBal := func() { + if !explorer.Connected { + balanceButton.Disable() + } + if account != nil && api.BaseURL != "" { + if !balanceButton.Disabled() { + balanceButton.Disable() + } + fioBalance, e := api.GetBalance(account.Actor) + balanceButton.Enable() + if e != nil { + errs.ErrChan <- "Error getting balance: " + e.Error() + return + } + if explorer.TxResultBalanceChanOpen { + explorer.TxResultBalanceChan <- p.Sprintf("FIO Balance:\n%.9g", fioBalance) + } + if balance != fioBalance { + errs.ErrChan <- p.Sprintf("balance changed by: %f", fioBalance-balance) + balanceLabel.SetText(p.Sprintf("FIO Balance: %.9g", fioBalance)) + balanceLabel.Refresh() + balance = fioBalance + } + } + } + go func() { + refreshWorkerCounter += 1 + balance = 0.0 + tick := time.NewTicker(30 * time.Second) + infoTickDuration := 3 * time.Second + infoTick := time.NewTicker(infoTickDuration) + for { + select { + case <-tick.C: + if errs.Connected { + updateBal() + } + case <-infoTick.C: + // try to catch a race when loading settings since this can get called twice in some circumstances + if refreshWorkerCounter > 1 { + refreshWorkerCounter -= 1 + return + } + + if explorer.Connected && tabContent.CurrentTab().Text == "Server" { + if _, updated := refreshInfo(infoTickDuration / time.Duration(2)); updated { + if infoTickDuration > 2*time.Second { + infoTickDuration -= time.Second + infoTick = time.NewTicker(infoTickDuration) + } + continue + } + // server is responding slowly, poll less frequently + infoTickDuration = infoTickDuration * 2 + infoTick = time.NewTicker(infoTickDuration) + } + case <-explorer.BalanceChan: + if errs.Connected { + updateBal() + } + case newWif := <-entryChan: + infoTickDuration = time.Second + infoTick = time.NewTicker(infoTickDuration) + if savedKeys[newWif] != "" { + wifEntry.SetText(savedKeys[newWif]) + } + } + } + }() + + return widget.NewVBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(explorer.RWidth(), 90)), + fyne.NewContainerWithLayout(layout.NewGridLayoutWithRows(2), + widget.NewHBox( + widget.NewLabel("Current Key: "), + pubkey, + actor, + myFioAddress, + ), + widget.NewHBox(balanceLabel, balanceButton, loadButton), + ), + ), + ) +} diff --git a/contract-explorer.go b/contract-explorer.go new file mode 100644 index 0000000..ef9deda --- /dev/null +++ b/contract-explorer.go @@ -0,0 +1 @@ +package cryptonym diff --git a/errLog/init.go b/errLog/init.go new file mode 100644 index 0000000..36e8e86 --- /dev/null +++ b/errLog/init.go @@ -0,0 +1,45 @@ +package errs + +import ( + "fyne.io/fyne/widget" + "log" + "strings" + "time" +) + +var ( + ErrChan = make(chan string) + DisconnectChan = make(chan bool) + ErrTxt = make([]string, 50) + ErrMsgs = widget.NewMultiLineEntry() + RefreshChan = make(chan bool) + Connected bool +) + +func init() { + go func(msg chan string, disconnected chan bool) { + last := time.Now() + t := time.NewTicker(500 * time.Millisecond) + for { + select { + case d := <-disconnected: + Connected = d + time.Sleep(250 * time.Millisecond) + case m := <-msg: + log.Println(m) + ErrTxt = append([]string{time.Now().Format(time.Stamp) + " -- " + m}, ErrTxt[:len(ErrTxt)-1]...) + last = time.Now() + case <-t.C: + if time.Now().After(last.Add(500 * time.Millisecond)) { + txt := strings.Join(ErrTxt, "\n") + func(s string) { + ErrMsgs.OnChanged = func(string) { + ErrMsgs.SetText(s) + } + }(txt) + ErrMsgs.SetText(txt) + } + } + } + }(ErrChan, DisconnectChan) +} diff --git a/fuzzer/fio-types.go b/fuzzer/fio-types.go new file mode 100644 index 0000000..ba079e5 --- /dev/null +++ b/fuzzer/fio-types.go @@ -0,0 +1,162 @@ +package fuzzer + +import ( + "encoding/json" + "fmt" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go" + "github.com/fioprotocol/fio-go/eos" + "math/rand" + "sort" + "strings" +) + +func MaxRandomFioAddressAt(domain string) string { + if !strings.HasPrefix(domain, "@") { + domain = "@" + domain + } + return RandomString(64-len(domain)) + domain +} + +func MaxRandomFioDomain() string { + return RandomString(62) +} + +func MaxAddPubAddress() string { + addresses := make([]fio.TokenPubAddr, 5) + for i := range addresses { + addresses[i].ChainCode = RandomString(10) + addresses[i].TokenCode = RandomString(10) + addresses[i].PublicAddress = RandomString(128) + } + j, _ := json.Marshal(addresses) + return string(j) +} + +func MaxNewFundsContent() string { + // end up with 96 bytes of encoding overhead: + return RandomBytes(296-96, EncodeBase64).(string) +} + +func MaxRecObtContent() string { + // end up with 144 bytes of encoding overhead: + return RandomBytes(432-144, EncodeBase64).(string) +} + +func MaxProducerUrl() string { + return "http://" + RandomString(500) + ".com" +} + +func MaxVoteProducers(url string) interface{} { + errs.ErrChan <- "for this to be effective, you may need to register a lot of producers with long names" + errs.ErrChan <- "querying producers table to find up to 30 producers, with the longest fio addresses." + api, _, err := fio.NewConnection(nil, url) + if err != nil { + errs.ErrChan <- err.Error() + return []string{""} + } + api.Header.Set("User-Agent", "fio-cryptonym-wallet") + producers, err := api.GetFioProducers() + if err != nil { + errs.ErrChan <- err.Error() + return []string{""} + } + bpFioNames := make([]string, 0) + for _, fioAddress := range producers.Producers { + bpFioNames = append(bpFioNames, string(fioAddress.FioAddress)) + } + + sort.Slice(bpFioNames, func(i, j int) bool { + return len(bpFioNames[i]) > len(bpFioNames[j]) + }) + if len(producers.Producers) < 30 { + errs.ErrChan <- fmt.Sprintf("only found %d producers", len(producers.Producers)) + return bpFioNames + } + return bpFioNames[:30] +} + +type fioNamesResp struct { + Name string `json:"name"` +} + +func RandomExistingFioAddress(url string) string { + api, _, err := fio.NewConnection(nil, url) + if err != nil { + errs.ErrChan <- err.Error() + return "" + } + api.Header.Set("User-Agent", "fio-cryptonym-wallet") + gtr, err := api.GetTableRows(eos.GetTableRowsRequest{ + Code: "fio.address", + Scope: "fio.address", + Table: "fionames", + LowerBound: "0", + Limit: 500, + JSON: true, + }) + if err != nil { + errs.ErrChan <- err.Error() + return "" + } + names := make([]fioNamesResp, 0) + err = json.Unmarshal(gtr.Rows, &names) + if len(names) == 0 { + return "" + } + return names[rand.Intn(len(names))].Name +} + +func RandomActor() eos.AccountName { + k, err := fio.NewRandomAccount() + if err != nil { + nonBlockErr(err.Error()) + return "" + } + actor, _ := fio.ActorFromPub(k.PubKey) + return actor +} + +func RandomFioPubKey() string { + k, err := fio.NewRandomAccount() + if err != nil { + nonBlockErr(err.Error()) + return "" + } + return k.PubKey +} + +func FioAddressAt(domain string) string { + if !strings.HasPrefix(domain, "@") { + domain = "@" + domain + } + addr := word() + domain + if len(addr) >= 64 { + return addr[len(addr)-64:] + } + return addr +} + +func InvalidFioAddressAt(domain string) string { + if !strings.HasPrefix(domain, "@") { + domain = "@" + domain + } + return word() + string(badChars[rand.Intn(len(badChars))]) + word() + domain +} + +func FioDomain() string { + return word() +} + +func InvalidFioDomain() string { + frontMiddleEnd := rand.Intn(3) + switch frontMiddleEnd { + case 0: + return string(badChars[rand.Intn(len(badChars))]) + word() + case 1: + return word() + string(badChars[rand.Intn(len(badChars))]) + word() + case 2: + return word() + string(badChars[rand.Intn(len(badChars))]) + } + return word() + string(badChars[rand.Intn(len(badChars))]) + word() +} diff --git a/fuzzer/fuzz_test.go b/fuzzer/fuzz_test.go new file mode 100644 index 0000000..ac1ff98 --- /dev/null +++ b/fuzzer/fuzz_test.go @@ -0,0 +1,98 @@ +package fuzzer + +import ( + "encoding/hex" + "fmt" + "github.com/fioprotocol/fio-go" + "math/rand" + "strings" + "testing" +) + +func TestRandomString(t *testing.T) { + for i := 0; i < 10; i++ { + s := RandomString(17) + fmt.Println(s) + if len(s) < 16 { + t.Error("too short") + } + } +} + +func TestRandomActor(t *testing.T) { + for i := 0; i < 10; i++ { + s := RandomActor() + fmt.Println(s) + if len(s) != 12 { + t.Error("wrong size") + } + } +} + +func TestRandomFioPubKey(t *testing.T) { + for i := 0; i < 10; i++ { + s := RandomFioPubKey() + fmt.Println(s) + if len(s) != 53 { + t.Error("wrong size") + } + } +} + +func TestRandomBytes(t *testing.T) { + for _, enc := range []int8{EncodeBase64, EncodeHexString, EncodeRaw} { + for i := 0; i < 10; i++ { + s := RandomBytes(rand.Intn(112)+16, enc) + fmt.Println(s) + if s == "" { + t.Error("empty!") + } + } + } +} + +func TestRandomChecksum(t *testing.T) { + for i := 0; i < 10; i++ { + s := RandomChecksum() + fmt.Println(s) + if len(s) != 64 { + t.Error("wrong size for checksum256") + } + } +} + +func TestHexToBytes(t *testing.T) { + for i := 0; i < 10; i++ { + b := make([]byte, 32) + rand.Read(b) + s := HexToBytes(hex.EncodeToString(b)) + if string(b) != s { + t.Error("bytes didn't match") + continue + } + fmt.Println(s, " == ", string(b)) + } +} + +func TestChecksumOf(t *testing.T) { + for i := 0; i < 10; i++ { + b := make([]byte, rand.Intn(128)+32) + s := ChecksumOf(string(b)) + fmt.Println(s) + if len(s) != 64 { + t.Error("wrong size") + } + } +} + +func TestSignatureFor(t *testing.T) { + key, _ := fio.NewRandomAccount() + for i := 0; i < 10; i++ { + b := make([]byte, rand.Intn(128)+32) + s := SignatureFor(string(b), key) + fmt.Println(s) + if !strings.HasPrefix(s, "SIG_K1_") { + t.Error("invalid signature") + } + } +} diff --git a/fuzzer/init.go b/fuzzer/init.go new file mode 100644 index 0000000..fc3e1e2 --- /dev/null +++ b/fuzzer/init.go @@ -0,0 +1,10 @@ +package fuzzer + +import ( + "math/rand" + "time" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} diff --git a/fuzzer/simple.go b/fuzzer/simple.go new file mode 100644 index 0000000..82fd662 --- /dev/null +++ b/fuzzer/simple.go @@ -0,0 +1,379 @@ +package fuzzer + +import ( + "bytes" + "context" + cr "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "fmt" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcutil" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/ethereum/go-ethereum/crypto" + "github.com/fioprotocol/fio-go" + "github.com/fioprotocol/fio-go/eos" + "github.com/fioprotocol/fio-go/eos/ecc" + "math" + "math/rand" + "strconv" + "strings" + "time" +) + +const ( + EncodeRaw int8 = iota + EncodeHexString + EncodeBase64 + badChars = `~!#$%^&*()+=\|{}[]";:?/.>,<@"` + "`" +) + +func RandomString(length int) string { + var payload string + for i := 0; i < length; i++ { + payload = payload + string(byte(rand.Intn(26)+97)) + } + return payload +} + +type RandomNumberResult struct { + abi string + value interface{} + convert func(s interface{}) interface{} +} + +func (rn RandomNumberResult) AbiType() string { + return rn.abi +} + +func (rn RandomNumberResult) String() string { + return fmt.Sprintf("%v", rn.value) +} + +func (rn RandomNumberResult) Interface() interface{} { + return rn.value +} + +func (rn RandomNumberResult) ConvertFunc() func(s interface{}) interface{} { + return rn.convert +} + +func RandomNumber() RandomNumberResult { + intLens := [...]int{8, 16, 32, 64} // don't send 128 here. + floatLen := 32 + + var result interface{} + signed := false + rn := RandomNumberResult{} + + negative := 1 + if rand.Intn(2) > 0 { + signed = true + if rand.Intn(2) > 0 { + negative = -1 + } + } + + intOrFloat := rand.Intn(2) + switch intOrFloat { + case 0: + l := intLens[rand.Intn(len(intLens))] + result = RandomInteger(l) + rn.abi = fmt.Sprintf("int%d", l) + if !signed { + rn.abi = fmt.Sprintf("uint%d", l) + } + switch l { + case 8: + rn.convert = func(s interface{}) interface{} { + parsed, _ := strconv.ParseInt(fmt.Sprintf("%d", s), 10, 8) + if !signed { + return uint8(parsed) + } + return int8(parsed * int64(negative)) + } + case 16: + rn.convert = func(s interface{}) interface{} { + parsed, _ := strconv.ParseInt(fmt.Sprintf("%d", s), 10, 16) + if !signed { + return uint16(parsed) + } + return int16(parsed * int64(negative)) + } + case 32: + rn.convert = func(s interface{}) interface{} { + parsed, _ := strconv.ParseInt(fmt.Sprintf("%d", s), 10, 32) + if !signed { + return uint32(parsed) + } + return int32(parsed * int64(negative)) + } + case 64: + rn.convert = func(s interface{}) interface{} { + parsed, _ := strconv.ParseInt(fmt.Sprintf("%d", s), 10, 64) + if !signed { + return uint64(parsed) + } + return parsed * int64(negative) + } + } + case 1: + fl := floatLen * (rand.Intn(2) + 1) + if fl == 32 { + rn.abi = "float32" + result = float32(RandomFloat(fl) * float64(negative)) + } else { + rn.abi = "float64" + result = RandomFloat(fl) * float64(negative) + } + + } + rn.value = result + return rn +} + +func MaxInt(size string) uint64 { + switch size { + case "int8": + return uint64(math.MaxInt8) + case "uint8": + return uint64(math.MaxUint8) + case "int16": + return uint64(math.MaxInt16) + case "uint16": + return uint64(math.MaxUint16) + case "int32": + return uint64(math.MaxInt32) + case "uint32": + return uint64(math.MaxUint32) + case "int64": + return uint64(math.MaxInt64) + case "uint64": + return math.MaxUint64 + default: + return uint64(math.MaxInt32) + } +} + +func RandomInteger(size int) RandomNumberResult { + switch size { + case 8: + //return int8(rand.Intn(math.MaxInt8-1) + 1) + return RandomNumberResult{ + abi: "int8", + value: int8(rand.Intn(math.MaxInt8-1) + 1), + } + case 16: + return RandomNumberResult{ + abi: "int16", + value: int16(rand.Intn(math.MaxInt16-1) + 1), + } + case 32: + return RandomNumberResult{ + abi: "int32", + value: int32(rand.Intn(math.MaxInt32-1) + 1), + } + case 64: + return RandomNumberResult{ + abi: "int64", + value: int64(rand.Intn(math.MaxInt64-1) + 1), + } + } + return RandomNumberResult{ + abi: "int32", + value: int32(rand.Intn(math.MaxInt16-1)+1) * -1, + } +} + +func OverFlowInt(size int, signed bool) string { + switch { + case size == 8 && signed: + return fmt.Sprintf("%d", int16(math.MaxInt8)+1) + case size == 16 && signed: + return fmt.Sprintf("%d", int32(math.MaxInt16)+1) + case size == 32 && signed: + return fmt.Sprintf("%d", int64(math.MaxInt32)+1) + case size == 8 && !signed: + return fmt.Sprintf("%d", uint16(math.MaxUint8)+1) + case size == 16 && !signed: + return fmt.Sprintf("%d", uint32(math.MaxUint16)+1) + case size == 32 && !signed: + return fmt.Sprintf("%d", uint64(math.MaxUint32)+1) + } + return "" +} + +func RandomInt128() string { + j, _ := eos.Int128{ + Lo: rand.Uint64(), + Hi: rand.Uint64(), + }.MarshalJSON() + return string(j) +} + +func RandomFloat(size int) float64 { + switch size { + case 32: + f := rand.Float32() + if f < .4 { + f = f + rand.Float32()*1000.0 + } + return float64(f) + case 64: + f := rand.Float64() + if f < .4 { + f = f + rand.Float64()*10000.0 + // if we are sending a f64, make sure it's a big one. + if f < math.MaxFloat32 { + f = f + float64(math.MaxInt16+rand.Intn(math.MaxInt16)) + } + } + return f + } + return 0.0 +} + +func RandomBytes(size int, encode int8) interface{} { + var pl []byte + // reading MBs of data from urand is a bad idea, use math.rand instead + if size <= 4096 { + pl = make([]byte, size) + _, _ = cr.Read(pl) + } else { + pl = make([]byte, size) + _, _ = rand.Read(pl) + } + switch encode { + case EncodeRaw: + return string(pl) + case EncodeHexString: + return hex.EncodeToString(pl) + case EncodeBase64: + buf := bytes.NewBuffer([]byte{}) + b64 := base64.NewEncoder(base64.StdEncoding, buf) + _, e := b64.Write(pl) + if e != nil { + nonBlockErr("warning: error creating base64 - " + e.Error()) + } + return string(buf.Bytes()) + } + return "" +} + +func RandomChecksum() eos.Checksum256 { + cs := make([]byte, 32) + _, _ = cr.Read(cs) + return cs +} + +func HexToBytes(hexData string) string { + b, e := hex.DecodeString(hexData) + if e != nil { + nonBlockErr("warning: could not decode hex data, sending empty string") + return "" + } + return string(b) +} + +func ChecksumOf(value string) string { + if value == "" { + nonBlockErr("warning: sending checksum256 of an empty string") + } + sum := sha256.New() + sum.Write([]byte(value)) + return hex.EncodeToString(sum.Sum(nil)) +} + +func SignatureFor(value string, key *fio.Account) string { + hash, err := hex.DecodeString(ChecksumOf(value)) + if err != nil { + nonBlockErr("couldn't hash value: " + err.Error()) + if len(hash) == 0 { + return "" + } + } + sig, err := key.KeyBag.Keys[0].Sign(hash) + if err != nil { + nonBlockErr("couldn't hash value: " + err.Error()) + return "" + } + return sig.String() +} + +func word() string { + var w string + for i := 0; i < 6; i++ { + w = w + string(byte(rand.Intn(26)+97)) + } + return w +} + +var incrementingInt int64 + +func IncrementingInt() int64 { + incrementingInt = incrementingInt + 1 + return incrementingInt +} + +var incrementingFloat float64 + +func IncrementingFloat() float64 { + incrementingFloat = incrementingFloat + 1.00001 + return incrementingFloat +} + +func ResetIncrement() { + incrementingInt = 0 + incrementingFloat = 0.0 +} + +func RandomAddAddress(count int) string { + addresses := make([]string, 0) + for i := 0; i < count; i++ { + code := word() + addresses = append(addresses, fmt.Sprintf(`{"token_code": "%s", "chain_code": "%s", "public_address": "%s"}`, code, code, RandomBytes(32, EncodeHexString))) + } + return fmt.Sprintf(`[%s]`, strings.Join(addresses, ", ")) +} + +func NewPubAddress(user *fio.Account) (address string, chain string) { + r := rand.Intn(3) + switch r { + case 0: + chain = "BTC" + wif, _ := btcutil.DecodeWIF(user.KeyBag.Keys[0].String()) + btcutil.Hash160(wif.PrivKey.PubKey().SerializeUncompressed()) + a, _ := btcutil.NewAddressPubKeyHash(btcutil.Hash160(wif.PrivKey.PubKey().SerializeUncompressed()), &chaincfg.MainNetParams) + address = a.String() + case 1: + chain = "ETH" + epk, _ := ecc.NewPublicKey("FIO" + user.PubKey[3:]) + pk, _ := epk.Key() + address = crypto.PubkeyToAddress(*pk.ToECDSA()).String() + case 2: + chain = "EOS" + address = "EOS" + user.PubKey[3:] + } + return +} + +//// FIXME! +//func LoadFile(filename string) string { +// nonBlockErr("warning: LoadFile is not implemented yet, sending empty string!") +// return "" +//} + +// try to notify on errors, but don't deadlock since we aren't entirely sure this is running inside the app +func nonBlockErr(msg string) { + d := time.Now().Add(50 * time.Millisecond) + ctx, cancel := context.WithDeadline(context.Background(), d) + defer cancel() + go func(s string) { + errs.ErrChan <- s + }(msg) + select { + case <-ctx.Done(): + return + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..17c3070 --- /dev/null +++ b/go.mod @@ -0,0 +1,19 @@ +module github.com/blockpane/cryptonym + +go 1.14 + +require ( + fyne.io/fyne v1.3.3 + github.com/alessio/shellescape v1.2.2 // indirect + github.com/blockpane/prettyfyne v0.0.0-20200910224427-ab9af6316e6c + github.com/btcsuite/btcd v0.20.1-beta + github.com/btcsuite/btcutil v1.0.1 + github.com/ethereum/go-ethereum v1.9.15 + github.com/fioprotocol/fio-go v1.0.1-0.20200923061247-06f15c940f04 + github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/text v0.3.3 + gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 + gopkg.in/yaml.v2 v2.2.8 + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b021e2c --- /dev/null +++ b/go.sum @@ -0,0 +1,305 @@ +fyne.io/fyne v1.2.3 h1:5xwtSBNjxxmg+GF/lYvvf4xPzyjgWQoJVrzb+bt5gaA= +fyne.io/fyne v1.2.3/go.mod h1:JhDdBrPP/Kdr1H5ZT3HW8E/6zlz+GkOldWqSirGBDnY= +fyne.io/fyne v1.3.3 h1:jpdp9kTJ1ZOKQjyHZfzQGz/E3V35KVoMTawtii/1/j4= +fyne.io/fyne v1.3.3/go.mod h1:osD/JXxGf8AC7aB+Ek0YuFF2QXzdTFFzMRM8cdqrwvQ= +github.com/Azure/azure-pipeline-go v0.2.1/go.mod h1:UGSo8XybXnIGZ3epmeBw7Jdz+HiUVpqIlpz/HKHylF4= +github.com/Azure/azure-pipeline-go v0.2.2/go.mod h1:4rQ/NZncSvGqNkkOsNpOU1tgoNuIlp9AfUH5G1tvCHc= +github.com/Azure/azure-storage-blob-go v0.7.0/go.mod h1:f9YQKtsG1nMisotuTPpO0tjNuEjKRYAcJU8/ydDI++4= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alessio/shellescape v1.2.2 h1:8LnL+ncxhWT2TR00dfJRT25JWWrhkMZXneHVWnetDZg= +github.com/alessio/shellescape v1.2.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= +github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/blockpane/prettyfyne v0.0.0-20200910224427-ab9af6316e6c h1:dxHAZFPoy1l1sr7op8Hpow7LkaNoQW46AMJT2nkbIzU= +github.com/blockpane/prettyfyne v0.0.0-20200910224427-ab9af6316e6c/go.mod h1:pNzAa8Nd4AxVFrlSL8wiPKGVXyUrA+DcHVa6jPaDGm0= +github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= +github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.1 h1:GKOz8BnRjYrb/JTKgaOk+zh26NWNdSNvdvv0xoAZMSA= +github.com/btcsuite/btcutil v1.0.1/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9/go.mod h1:1MxXX1Ux4x6mqPmjkUgTP1CdXIBXKX7T+Jk9Gxrmx+U= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/dop251/goja v0.0.0-20200219165308-d1232e640a87/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= +github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/ethereum/go-ethereum v1.9.15 h1:wrWl+QrtutRUJ9LZXdUqBoGoo2b1tOCYRDrAOQhCY3A= +github.com/ethereum/go-ethereum v1.9.15/go.mod h1:slT8bPPRhXsyNTwHQxrOnjuTZ1sDXRajW11EkJ84QJ0= +github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fioprotocol/fio-go v1.0.1-0.20200923061247-06f15c940f04 h1:1s2ZTfjMrGE32ZNL0Tr1ScI+zVZQmSbty2kHXm1tsp4= +github.com/fioprotocol/fio-go v1.0.1-0.20200923061247-06f15c940f04/go.mod h1:cX96BK9Y8fQPJP2GXGQvCFovHJ35lw7ICUUUAzqnH40= +github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fyne-io/mobile v0.0.2 h1:eGmCR5lkFxk0PnPafGppLFRD5QODJfSVdrjhLjanOVg= +github.com/fyne-io/mobile v0.0.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw= +github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= +github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f h1:7MsFMbSn8Lcw0blK4+NEOf8DuHoOBDhJsHz04yh13pM= +github.com/go-gl/glfw v0.0.0-20181213070059-819e8ce5125f/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3 h1:q521PfSp5/z6/sD9FZZOWj4d1MLmfQW8PkRnI9M6PCE= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8= +github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= +github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= +github.com/influxdata/influxdb v1.2.3-0.20180221223340-01288bdb0883/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= +github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc= +github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/josephspurrier/goversioninfo v0.0.0-20190124120936-8611f5a5ff3f/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE= +github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/julienschmidt/httprouter v1.1.1-0.20170430222011-975b5c4c7c21/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng= +github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= +github.com/mattn/go-isatty v0.0.5-0.20180830101745-3fb116b82035/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= +github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2-0.20190409134802-7e037d187b0c/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pborman/uuid v0.0.0-20170112150404-1b00554d8222/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shirou/gopsutil v2.20.5-0.20200531151128-663af789c085+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086 h1:RYiqpb2ii2Z6J4x0wxK46kvPBbFuZcdhS+CIztmYgZs= +github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/srwiley/oksvg v0.0.0-20190829233741-58e08c8fe40e h1:LJUrNHytcMXWKxnULIHPe5SCb1jDpO9o672VB1x2EuQ= +github.com/srwiley/oksvg v0.0.0-20190829233741-58e08c8fe40e/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4= +github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM= +github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4= +github.com/srwiley/rasterx v0.0.0-20181219215540-696f7edb7a7e h1:FFotfUvew9Eg02LYRl8YybAnm0HCwjjfY5JlOI1oB00= +github.com/srwiley/rasterx v0.0.0-20181219215540-696f7edb7a7e/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= +github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM= +github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= +github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= +github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= +github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3/go.mod h1:hpGUWaI9xL8pRQCTXQgocU38Qw1g0Us7n5PxxTwTCYU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= +github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc= +github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= +github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8= +github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/sjson v1.1.1 h1:7h1vk049Jnd5EH9NyzNiEuwYW4b5qgreBbqRC19AS3U= +github.com/tidwall/sjson v1.1.1/go.mod h1:yvVuSnpEQv5cYIrO+AT6kw4QVfd5SDZoGIS7/5+fZFs= +github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= +golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775 h1:TC0v2RSO1u2kn1ZugjrFXkRZAEaqMN/RW+OTZkBzmLE= +golang.org/x/sys v0.0.0-20200327173247-9dae0f8f5775/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03 h1:XpToik3MpT5iW3iHgNwnh3a8QwugfomvxOlyDnaOils= +golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 h1:8ajkpB4hXVftY5ko905id+dOnmorcS2CHNxxHLLDcFM= +gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61/go.mod h1:IfMagxm39Ys4ybJrDb7W3Ob8RwxftP0Yy+or/NVz1O8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200603215123-a4a8cb9d2cbc/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/init.go b/init.go new file mode 100644 index 0000000..c28acf0 --- /dev/null +++ b/init.go @@ -0,0 +1,151 @@ +package cryptonym + +import ( + "encoding/json" + "fmt" + "fyne.io/fyne/app" + "fyne.io/fyne/widget" + "github.com/fioprotocol/fio-go" + "golang.org/x/text/language" + "golang.org/x/text/message" + "os" + "runtime" +) + +const ( + AppTitle = "Cryptonym" +) + +var ( + WinSettings = getSavedWindowSettings() + W = WinSettings.W + H = WinSettings.H + txW = 1200 + txH = 500 + ActionW = 220 // width of action buttons on left side + WidthReduce = 26 // trim down size of right window this much to account for padding + App = app.NewWithID("explorer") + Win = App.NewWindow(AppTitle) + BalanceChan = make(chan bool) + BalanceLabel = widget.NewLabel("") + DefaultFioAddress = "" + TableIndex = NewTableIndex() + delayTxSec = 1000000000 + EndPoints = SupportedApis{Apis: []string{"/v1/chain/push_transaction"}} + actionEndPointActive = "/v1/chain/push_transaction" + apiEndPointActive = "/v1/chain/get_info" + p = message.NewPrinter(language.English) + RepaintChan = make(chan bool, 1) + PasswordVisible bool + SettingsLoaded = make(chan *FioSettings) + Settings = DefaultSettings() + TxResultBalanceChan = make(chan string) + TxResultBalanceChanOpen = false + useZlib = false + deferTx = false + Connected bool + Uri = "" + Api = &fio.API{} + Opts = &fio.TxOptions{} + Account = func() *fio.Account { + a, _ := fio.NewAccountFromWif("5JBbUG5SDpLWxvBKihMeXLENinUzdNKNeozLas23Mj6ZNhz3hLS") // vote1@dapixdev + return a + }() +) + +func init() { + txW = (W * 65) / 100 + txH = (H * 85) / 100 + go func() { + TxResultBalanceChanOpen = true + defer func() { + TxResultBalanceChanOpen = false + }() + for { + select { + case bal := <-TxResultBalanceChan: + BalanceLabel.SetText(bal) + BalanceLabel.Refresh() + } + } + }() + startErrLog() +} + +type winSettings struct { + W int `json:"w"` + H int `json:"h"` + T string `json:"t"` +} + +func getSavedWindowSettings() winSettings { + def := winSettings{ + W: 1440, + H: 900, + T: "Light", + } + d, e := os.UserConfigDir() + if e != nil || d == "" { + return def + } + f, e := os.Open(fmt.Sprintf("%s%c%s%cwindow.json", d, os.PathSeparator, settingsDir, os.PathSeparator)) + if e != nil { + return def + } + defer f.Close() + s, e := os.Stat(fmt.Sprintf("%s%c%s%cwindow.json", d, os.PathSeparator, settingsDir, os.PathSeparator)) + if e != nil { + return def + } + b := make([]byte, s.Size()) + _, e = f.Read(b) + if e != nil { + return def + } + wSet := winSettings{} + e = json.Unmarshal(b, &wSet) + if e != nil { + return def + } + if wSet.W == 0 || wSet.H == 0 { + wSet = def + } + if wSet.T == "" { + wSet.T = "Light" + } + + if runtime.GOOS != "darwin" { + wSet.H -= 50 + } + return wSet +} + +func saveWindowSettings(w int, h int, t string) bool { + d, e := os.UserConfigDir() + if e != nil || d == "" { + return false + } + if ok, _ := MkDir(); !ok { + return false + } + fn := fmt.Sprintf("%s%c%s%cwindow.json", d, os.PathSeparator, settingsDir, os.PathSeparator) + f, e := os.OpenFile(fn, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) + if e != nil { + return false + } + defer f.Close() + j, _ := json.Marshal(&winSettings{W: w, H: h, T: t}) + _, e = f.Write(j) + if e != nil { + return false + } + return true +} + +func RWidth() int { + return W - ActionW - WidthReduce +} + +func PctHeight() int { + return (H * 95) / 100 +} diff --git a/key-generator.go b/key-generator.go new file mode 100644 index 0000000..7e55979 --- /dev/null +++ b/key-generator.go @@ -0,0 +1,411 @@ +package cryptonym + +import ( + "bytes" + "fmt" + "fyne.io/fyne" + "fyne.io/fyne/canvas" + "fyne.io/fyne/layout" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + "github.com/fioprotocol/fio-go" + "github.com/fioprotocol/fio-go/eos" + "github.com/fioprotocol/fio-go/eos/ecc" + "github.com/skip2/go-qrcode" + "image" + "image/color" + "math" + "os" + "runtime" + "strconv" + "strings" + "time" +) + +var imageSize = func() (size int) { + size = 196 + scale := os.Getenv("FYNE_SCALE") + if scale != "" { + new, err := strconv.Atoi(scale) + if err != nil { + return + } + size = ((new * 10) * size) / 10 + } + return +}() + +var RefreshQr = make(chan bool, 1) + +func KeyGenTab() *widget.Box { + + keyStr := "" + waitMsg := " ... waiting for entropy from operating system ... " + entry := widget.NewEntry() + var key *ecc.PrivateKey + var showingPriv bool + var err error + + vanityQuit := make(chan bool, 1) + vanityOpt := &vanityOptions{} + vanityOpt.threads = runtime.NumCPU() + vanitySearch := widget.NewSelect([]string{"Actor", "Pubkey", "Either"}, func(s string) { + switch s { + case "Actor": + vanityOpt.actor = true + vanityOpt.pub = false + case "Pubkey": + vanityOpt.actor = false + vanityOpt.pub = true + default: + vanityOpt.actor = true + vanityOpt.pub = true + } + }) + vanitySearch.SetSelected("Actor") + vanitySearch.Hide() + vanityMatch := widget.NewCheck("match anywhere", func(b bool) { + if b { + vanityOpt.anywhere = true + return + } + vanityOpt.anywhere = false + }) + vanityMatch.Hide() + vanityLabel := widget.NewLabel(" ") + vanityEntry := NewClickEntry(&widget.Button{}) + vanityEntry.SetPlaceHolder("enter string to search for") + vanityEntry.OnChanged = func(s string) { + vanityLabel.SetText(" ") + if len(s) >= 6 { + vanityLabel.SetText("Note: searching for 6 or more characters can take a very long time.") + } + vanityOpt.word = strings.ToLower(s) + vanityLabel.Refresh() + } + vanityEntry.Hide() + vanityStopButton := &widget.Button{} + vanityStopButton = widget.NewButtonWithIcon("Stop Searching", theme.CancelIcon(), func() { + vanityQuit <- true + entry.SetText("Vanity key generation cancelled") + vanityStopButton.Hide() + }) + vanityStopButton.Hide() + vanityCheck := widget.NewCheck("Generate Vanity Address", func(b bool) { + if b { + waitMsg = "Please wait, generating vanity key" + vanitySearch.Show() + vanityMatch.Show() + vanityEntry.Show() + return + } + waitMsg = " ... waiting for entropy from operating system ... " + vanitySearch.Hide() + vanityMatch.Hide() + vanityEntry.Hide() + vanityStopButton.Hide() + }) + vanityBox := widget.NewVBox( + widget.NewHBox( + layout.NewSpacer(), + vanityCheck, + vanitySearch, + vanityMatch, + vanityEntry, + layout.NewSpacer(), + ), + widget.NewHBox( + layout.NewSpacer(), + vanityLabel, + layout.NewSpacer(), + ), + ) + + emptyQr := disabledImage(imageSize, imageSize) + qrImage := canvas.NewImageFromImage(emptyQr) + qrImage.FillMode = canvas.ImageFillOriginal + newQrPub := image.Image(emptyQr) + newQrPriv := image.Image(emptyQr) + copyToClip := widget.NewButton("", nil) + swapQrButton := widget.NewButton("", nil) + + setWait := func(s string) { + keyStr = s + swapQrButton.Disable() + copyToClip.Disable() + qrImage.Image = emptyQr + entry.SetText(keyStr) + copyToClip.Refresh() + swapQrButton.Refresh() + qrImage.Refresh() + entry.Refresh() + } + setWait(waitMsg) + + qrPriv := make([]byte, 0) + qrPub := make([]byte, 0) + qrLabel := widget.NewLabel("Public Key:") + qrLabel.Alignment = fyne.TextAlignCenter + + swapQr := func() { + switch showingPriv { + case false: + swapQrButton.Text = "Show Pub Key QR Code" + qrLabel.SetText("Private Key:") + qrImage.Image = newQrPriv + showingPriv = true + case true: + swapQrButton.Text = "Show Priv Key QR Code" + qrLabel.SetText("Public Key:") + qrImage.Image = newQrPub + showingPriv = false + } + qrLabel.Refresh() + qrImage.Refresh() + swapQrButton.Refresh() + } + swapQrButton = widget.NewButtonWithIcon("Show Private Key QR Code", theme.VisibilityIcon(), swapQr) + + regenButton := &widget.Button{} + newKey := true + var setBusy bool + setKey := func() { + time.Sleep(20 * time.Millisecond) // lame, but prevents a double event on darwin?!? + if setBusy { + return + } + setBusy = true + regenButton.Disable() + go func() { + defer func() { + vanityStopButton.Hide() + regenButton.Enable() + setBusy = false + }() + type ki struct { + kq []byte + pq []byte + k string + e error + } + result := make(chan ki) + go func() { + if vanityCheck.Checked { + if vanityOpt.word == "" { + keyStr = "empty search string provided!" + result <- ki{} + return + } + vanityStopButton.Show() + acc, err := vanityKey(vanityOpt, vanityQuit) + if err != nil { + keyStr = "Sorry, there was a problem generating the key\n" + err.Error() + result <- ki{} + return + } + if acc == nil || acc.KeyBag == nil || acc.KeyBag.Keys[0] == nil { + keyStr = "Sorry, there was a problem generating the key - got empty key!\n" + result <- ki{} + return + } + vanityStopButton.Hide() + key = acc.KeyBag.Keys[0] + } else if newKey { + key, err = ecc.NewRandomPrivateKey() + if err != nil { + keyStr = "Sorry, there was a problem generating the key\n" + err.Error() + result <- ki{} + return + } + } + newKey = true + keyInfo := ki{} + keyInfo.k, keyInfo.kq, keyInfo.pq, keyInfo.e = genKey(key) + result <- keyInfo + }() + // don't show the wait message right away to avoid flicker: + tick := time.NewTicker(time.Second) + var skipSetWait bool + for { + select { + case _ = <-tick.C: + if skipSetWait { + return + } + qrImage.Image = disabledImage(imageSize, imageSize) + qrImage.Refresh() + setWait(waitMsg) + case ki := <-result: + if ki.kq == nil { + return + } + skipSetWait = true + keyStr, qrPriv, qrPub, err = ki.k, ki.kq, ki.pq, ki.e + if err != nil { + keyStr = "Sorry, there was a problem generating the key\n" + err.Error() + } + qrReaderPriv := bytes.NewReader(qrPriv) + newQrPriv, _, err = image.Decode(qrReaderPriv) + if err != nil { + keyStr = "Sorry, there was a problem generating the qr code\n" + err.Error() + } + + qrReader := bytes.NewReader(qrPub) + newQrPub, _, err = image.Decode(qrReader) + if err != nil { + keyStr = "Sorry, there was a problem generating the qr code\n" + err.Error() + } + qrImage.Image = newQrPub + swapQrButton.Enable() + copyToClip.Enable() + swapQrButton.Refresh() + copyToClip.Refresh() + entry.SetText(keyStr) + entry.OnChanged = func(string) { + entry.SetText(keyStr) + } + qrImage.Refresh() + showingPriv = true + swapQr() + return + } + } + }() + } + + clipped := func() { + go func() { + clip := Win.Clipboard() + clip.SetContent(keyStr) + copyToClip.Text = "Copied!" + if keyStr != clip.Content() { + clip.SetContent("Failed to Copy!") + } + copyToClip.Refresh() + time.Sleep(2 * time.Second) + copyToClip.Text = "Copy To Clipboard" + copyToClip.Refresh() + }() + } + copyToClip = widget.NewButtonWithIcon("Copy To Clipboard", theme.ContentCopyIcon(), clipped) + + go setKey() + + go func() { + for { + select { + case <-RefreshQr: + newKey = false + setKey() + } + } + }() + + regenButton = widget.NewButtonWithIcon("Regenerate", theme.ViewRefreshIcon(), setKey) + vanityEntry.Button = regenButton + return widget.NewVBox( + layout.NewSpacer(), + vanityBox, + fyne.NewContainerWithLayout(layout.NewGridLayout(3), + widget.NewHBox( + layout.NewSpacer(), + widget.NewVBox( + qrLabel, + qrImage, + swapQrButton, + ), + ), + widget.NewHBox( + widget.NewLabel(" "), + widget.NewVBox( + layout.NewSpacer(), + widget.NewHBox( + entry, + ), + widget.NewHBox( + regenButton, + copyToClip, + vanityStopButton, + layout.NewSpacer(), + ), + ), + ), + layout.NewSpacer(), + ), + layout.NewSpacer(), + ) +} + +func disabledImage(w, h int) *image.RGBA { + img := image.NewRGBA(image.Rect(0, 0, w, h)) + rr, gg, bb, _ := App.Settings().Theme().ButtonColor().RGBA() + c := color.RGBA{R: uint8(rr), G: uint8(gg), B: uint8(bb), A: math.MaxUint8} + for x := 0; x < w; x++ { + for y := 0; y < h; y++ { + img.Set(x, y, c) + } + } + return img +} + +func genKey(key *ecc.PrivateKey) (keyinfo string, privQr []byte, pubQr []byte, e error) { + var err error + // convert the WIF into an EOS account structure + kb := eos.NewKeyBag() + err = kb.ImportPrivateKey(key.String()) + if err != nil { + return "", nil, nil, err + } + + // get the FIO actor name (an EOS account derived from the public key.) + // in FIO an account has a 1:1 relationship to an account, and the account is + // created automatically + fioActor, err := fio.ActorFromPub(kb.Keys[0].PublicKey().String()) + if err != nil { + return "", nil, nil, err + } + + // Public key URI formatted + var pngPub []byte + qPub, err := qrcode.New("fio:FIO"+kb.Keys[0].PublicKey().String()[3:]+"?label="+string(fioActor), qrcode.Medium) + if err != nil { + return "", nil, nil, err + } + if strings.Contains(WinSettings.T, "Dark") { + qPub.ForegroundColor = darkestGrey + qPub.BackgroundColor = lightestGrey + } else { + qPub.ForegroundColor = color.Black + qPub.BackgroundColor = color.White + } + pngPub, err = qPub.PNG(imageSize) + if err != nil { + return "", nil, nil, err + } + + // Private Key + var pngPriv []byte + qPriv, err := qrcode.New(kb.Keys[0].String(), qrcode.Medium) + if err != nil { + return "", nil, nil, err + } + if strings.Contains(WinSettings.T, "Dark") { + qPriv.ForegroundColor = darkestGrey + qPriv.BackgroundColor = lightestGrey + } else { + qPriv.ForegroundColor = color.Black + qPriv.BackgroundColor = color.White + } + pngPriv, err = qPriv.PNG(imageSize) + if err != nil { + return "", nil, nil, err + } + + b := bytes.NewBuffer([]byte{}) + // print out the result: a FIO public key is really an EOS key, with FIO at the beginning + b.WriteString(fmt.Sprintln("Private Key: ", kb.Keys[0].String())) + b.WriteString(fmt.Sprintln("Public Key: FIO" + kb.Keys[0].PublicKey().String()[3:])) + b.WriteString(fmt.Sprintln("Account Name: ", fioActor)) + + return b.String(), pngPriv, pngPub, nil +} diff --git a/log_darwin.go b/log_darwin.go new file mode 100644 index 0000000..185861f --- /dev/null +++ b/log_darwin.go @@ -0,0 +1,33 @@ +package cryptonym + +import ( + "fmt" + errs "github.com/blockpane/cryptonym/errLog" + "log" + "os" + "syscall" +) + +func startErrLog() { + d, e := os.UserConfigDir() + if e != nil { + log.Println(e) + return + } + errLog, e := os.OpenFile(fmt.Sprintf("%s%c%s%cerror.log", d, os.PathSeparator, settingsDir, os.PathSeparator), os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_SYNC, 0600) + if e != nil { + log.Println(e) + return + } + e = syscall.Dup2(int(errLog.Fd()), 1) + if e != nil { + log.Println(e) + return + } + e = syscall.Dup2(int(errLog.Fd()), 2) + if e != nil { + log.Println(e) + return + } + errs.ErrChan <- fmt.Sprintf("Writing session log to: %s%c%s%cerror.log", d, os.PathSeparator, settingsDir, os.PathSeparator) +} diff --git a/log_linux.go b/log_linux.go new file mode 100644 index 0000000..185861f --- /dev/null +++ b/log_linux.go @@ -0,0 +1,33 @@ +package cryptonym + +import ( + "fmt" + errs "github.com/blockpane/cryptonym/errLog" + "log" + "os" + "syscall" +) + +func startErrLog() { + d, e := os.UserConfigDir() + if e != nil { + log.Println(e) + return + } + errLog, e := os.OpenFile(fmt.Sprintf("%s%c%s%cerror.log", d, os.PathSeparator, settingsDir, os.PathSeparator), os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_SYNC, 0600) + if e != nil { + log.Println(e) + return + } + e = syscall.Dup2(int(errLog.Fd()), 1) + if e != nil { + log.Println(e) + return + } + e = syscall.Dup2(int(errLog.Fd()), 2) + if e != nil { + log.Println(e) + return + } + errs.ErrChan <- fmt.Sprintf("Writing session log to: %s%c%s%cerror.log", d, os.PathSeparator, settingsDir, os.PathSeparator) +} diff --git a/log_windows.go b/log_windows.go new file mode 100644 index 0000000..1a987fd --- /dev/null +++ b/log_windows.go @@ -0,0 +1,3 @@ +package cryptonym + +func startErrLog() {} diff --git a/mac-package.sh b/mac-package.sh new file mode 100755 index 0000000..13b72df --- /dev/null +++ b/mac-package.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# builds a macos package (.app) and places it inside a compressed .dmg + +mkdir -p "package/Cryptonym" +mkdir -p package/old +mv package/*.dmg package/old/ + +go build -ldflags "-s -w" -o cmd/cryptonym-wallet/cryptonym-wallet cmd/cryptonym-wallet/main.go + +upx -9 cmd/cryptonym-wallet/cryptonym-wallet +fyne package -sourceDir cmd/cryptonym-wallet -name "Cryptonym" -os darwin && mv "Cryptonym.app" "package/Cryptonym/" +sed -i'' -e 's/.string.1\.0.\/string./\'$(git describe --tags --always --long)'\<\/string>/g' "package/Cryptonym/Cryptonym.app/Contents/Info.plist" +rm -f cmd/cryptonym-wallet/cryptonym-wallet +pushd package +hdiutil create -srcfolder "Cryptonym" "Cryptonym.dmg" +popd +rm -fr "package/Cryptonym" +open "package/Cryptonym.dmg" + diff --git a/msig-form.go b/msig-form.go new file mode 100644 index 0000000..53ada11 --- /dev/null +++ b/msig-form.go @@ -0,0 +1,444 @@ +package cryptonym + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "fyne.io/fyne" + "fyne.io/fyne/dialog" + "fyne.io/fyne/layout" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + fioassets "github.com/blockpane/cryptonym/assets" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go" + "github.com/fioprotocol/fio-go/eos" + "math" + "sort" + "strconv" + "strings" + "sync" + "time" +) + +type signer struct { + weight *widget.Entry + actor *widget.Entry + index int +} + +var ( + MsigRefreshRequests = make(chan bool) + MsigLastTab = 0 + MsigLoaded bool +) + +func UpdateAuthContent(container chan fyne.Container, api *fio.API, opts *fio.TxOptions, account *fio.Account) { + for !Connected { + time.Sleep(time.Second) + } + authTab := func() {} //recursive + authTab = func() { + accountEntry := widget.NewEntry() + newAccount := &fio.Account{} + update := &widget.TabItem{} + fee := widget.NewLabelWithStyle(p.Sprintf("Required Fee: %s %G", fio.FioSymbol, fio.GetMaxFee(fio.FeeAuthUpdate)*2.0), fyne.TextAlignTrailing, fyne.TextStyle{}) + warning := widget.NewHBox( + widget.NewIcon(theme.WarningIcon()), + widget.NewLabelWithStyle("Warning: converting active account to multi-sig!", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + ) + warning.Hide() + newRandCheck := widget.NewCheck("Create New and Burn", func(b bool) { + if b { + newAccount, _ = fio.NewRandomAccount() + accountEntry.SetText(string(newAccount.Actor)) + fee.SetText(p.Sprintf("Required Fee: %s%G", fio.FioSymbol, fio.GetMaxFee(fio.FeeAuthUpdate)*2.0+fio.GetMaxFee(fio.FeeTransferTokensPubKey))) + fee.Refresh() + warning.Hide() + } else { + accountEntry.SetText(string(account.Actor)) + fee.SetText(p.Sprintf("Required Fee: %s%G", fio.FioSymbol, fio.GetMaxFee(fio.FeeAuthUpdate)*2.0)) + fee.Refresh() + warning.Show() + } + }) + accountEntry.SetText(string(account.Actor)) + newRandCheck.SetChecked(true) + + threshEntry := widget.NewEntry() + threshEntry.SetText("2") + tMux := sync.Mutex{} + threshEntry.OnChanged = func(s string) { + tMux.Lock() + time.Sleep(300 * time.Millisecond) + if _, e := strconv.Atoi(s); e != nil { + tMux.Unlock() + threshEntry.SetText("2") + return + } + tMux.Unlock() + } + + signerSlice := make([]signer, 0) // keeps order correct when adding rows, and is sorted when submitting tx + newSigner := func(s string) *fyne.Container { + if s == "" { + for i := 0; i < 12; i++ { + b := []byte{uint8(len(signerSlice) + 96)} // assuming we will start with 1 by default + s = s + string(b) + } + } + w := widget.NewEntry() + w.SetText("1") + a := widget.NewEntry() + a.SetText(s) + index := len(signerSlice) + shouldAppend := func() bool { + for _, sc := range signerSlice { + if sc.actor == a { + return false + } + } + return true + } + if shouldAppend() { + signerSlice = append(signerSlice, signer{ + weight: w, + actor: a, + index: index, + }) + } else { + return nil + } + threshEntry.SetText(fmt.Sprintf("%d", 1+len(signerSlice)/2)) + threshEntry.Refresh() + return fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(6), + layout.NewSpacer(), + widget.NewLabelWithStyle("Actor "+strconv.Itoa(signerSlice[index].index+1)+": ", fyne.TextAlignTrailing, fyne.TextStyle{}), + signerSlice[index].actor, + widget.NewLabelWithStyle("Vote Weight: ", fyne.TextAlignTrailing, fyne.TextStyle{}), + signerSlice[index].weight, + layout.NewSpacer(), + ) + } + signerGroup := widget.NewGroup(" Signers ", newSigner(string(account.Actor))) + addSigner := widget.NewButtonWithIcon("Add Signer", theme.ContentAddIcon(), func() { + signerGroup.Append(newSigner("")) + signerGroup.Refresh() + update.Content.Refresh() + }) + + resetSigners := widget.NewButtonWithIcon("Reset", theme.ContentClearIcon(), func() { + MsigLastTab = 1 + authTab() + go func() { + time.Sleep(100 * time.Millisecond) + for _, a := range fyne.CurrentApp().Driver().AllWindows() { + a.Content().Refresh() + } + }() + }) + + submitButton := &widget.Button{} + submitButton = widget.NewButtonWithIcon("Submit", fioassets.NewFioLogoResource(), func() { + submitButton.Disable() + ok, _, msg := checkSigners(signerSlice, "active") + if !ok { + dialog.ShowError(msg, Win) + return + } + defer submitButton.Enable() + if newRandCheck.Checked { + if ok, err := fundRandMsig(newAccount, account, len(signerSlice), api, opts); !ok { + errs.ErrChan <- "new msig account was not created!" + dialog.ShowError(err, Win) + return + } + } + acc := &fio.Account{} + acc = account + if newRandCheck.Checked { + acc = newAccount + } + t, err := strconv.Atoi(threshEntry.Text) + if err != nil { + errs.ErrChan <- "Invalid threshold, refusing to continue" + return + } + ok, info, err := updateAuthResult(acc, signerSlice, t) + if ok { + dialog.ShowCustom("Success", "OK", info, Win) + return + } + dialog.ShowError(err, Win) + }) + + update = widget.NewTabItem("Update Auth", + widget.NewScrollContainer( + widget.NewVBox( + widget.NewHBox( + widget.NewLabelWithStyle("Account: ", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + newRandCheck, + accountEntry, + widget.NewLabelWithStyle("Threshold: ", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + threshEntry, + ), + signerGroup, + layout.NewSpacer(), + widget.NewHBox(layout.NewSpacer(), addSigner, resetSigners, layout.NewSpacer(), warning, fee, submitButton, layout.NewSpacer()), + widget.NewLabel(""), + ), + )) + tabs := widget.NewTabContainer(MsigRequestsContent(api, opts, account), update) + tabs.SelectTabIndex(MsigLastTab) + container <- *fyne.NewContainerWithLayout(layout.NewMaxLayout(), tabs) + } + go func() { + for { + select { + case r := <-MsigRefreshRequests: + if r { + a := *Api + api = &a + o := *Opts + opts = &o + u := *Account + account = &u + authTab() + } + // do we ever need a new routine? Think this return is causing a deadlock... + //return + } + } + }() + authTab() + MsigLoaded = true +} + +func getSigners(account string, api *fio.API) string { + if strings.HasPrefix(account, "eosio") { + return getTopProds(api) + } + info, err := api.GetFioAccount(account) + if err != nil { + return "" + } + for _, auth := range info.Permissions { + if auth.PermName == "active" && auth.RequiredAuth.Accounts != nil && len(auth.RequiredAuth.Accounts) > 0 { + signers := make([]string, 0) + for _, signer := range auth.RequiredAuth.Accounts { + signers = append(signers, string(signer.Permission.Actor)) + } + if len(signers) != 0 { + return strings.Join(signers, ", ") + } + } + } + return account + " is not a multi-sig account!" +} + +type topProd struct { + Producer string `json:"owner"` + IsActive uint8 `json:"is_active"` +} + +func getTopProds(api *fio.API) string { + const want = 30 + gtr, err := api.GetTableRowsOrder(fio.GetTableRowsOrderRequest{ + Code: "eosio", + Scope: "eosio", + Table: "producers", + LowerBound: "0", + UpperBound: "18446744073709551615", + Index: "4", + KeyType: "i64", + Limit: 100, + JSON: true, + Reverse: true, + }) + if err != nil { + errs.ErrChan <- err.Error() + return "" + } + tp := make([]topProd, 0) + err = json.Unmarshal(gtr.Rows, &tp) + if err != nil { + errs.ErrChan <- err.Error() + return "" + } + prods := make([]string, 0) + for _, p := range tp { + if p.IsActive == 1 { + prods = append(prods, p.Producer) + if len(prods) == want { + break + } + } + } + sort.Strings(prods) + return strings.Join(prods, ", ") +} + +func fundRandMsig(msig *fio.Account, funder *fio.Account, count int, api *fio.API, opts *fio.TxOptions) (ok bool, err error) { + feeMultGuess := float64(42*count) / 1000.0 + feeMultGuess = math.Ceil(feeMultGuess) + if feeMultGuess < 1.0 { + feeMultGuess = 1 + } + if feeMultGuess > 1.0 { + errs.ErrChan <- fmt.Sprintf("NOTE: fees are increased due to number of signers, transaction will be %s %g", fio.FioSymbol, fio.GetMaxFee(fio.FeeAuthUpdate)*feeMultGuess*2) + } + errs.ErrChan <- "creating new msig account, sending funds, please wait" + resp, err := api.SignPushTransaction( + fio.NewTransaction( + []*fio.Action{fio.NewTransferTokensPubKey(funder.Actor, msig.PubKey, fio.Tokens((fio.GetMaxFee(fio.FeeAuthUpdate)*feeMultGuess*2.0)+fio.GetMaxFee(fio.FeeTransferTokensPubKey)))}, + opts, + ), opts.ChainID, fio.CompressionNone, + ) + if err != nil { + errs.ErrChan <- err.Error() + errs.ErrChan <- "Could not fund new msig account:" + return false, err + } + errs.ErrChan <- p.Sprintf("Funded new account (%s) txid: %v", resp.TransactionID) + BalanceChan <- true + return true, nil +} + +func checkSigners(signers []signer, level eos.PermissionName) (ok bool, permLevelWeight []eos.PermissionLevelWeight, msg error) { + weights := make([]eos.PermissionLevelWeight, 0) + signerWeights := make(map[string]int) + unique := make(map[string]bool) + signerOrder := make([]string, 0) + for _, s := range signers { + + def := bytes.Repeat([]byte(s.actor.Text[:1]), 12) + if s.actor.Text == string(def) { + // reject on a default value + msg = errors.New(s.actor.Text + " is not a valid signer for an msig account, refusing to continue.") + errs.ErrChan <- msg.Error() + return false, nil, msg + } + signerOrder = append(signerOrder, s.actor.Text) + w, e := strconv.Atoi(s.weight.Text) + if e != nil { + msg = errors.New("invalid weight specified for actor: " + s.actor.Text) + errs.ErrChan <- msg.Error() + return false, nil, msg + } + signerOrder = append(signerOrder, s.actor.Text) + signerWeights[s.actor.Text] = w + } + sort.Strings(signerOrder) + for _, so := range signerOrder { + if unique[so] { + continue + } + unique[so] = true + weights = append(weights, eos.PermissionLevelWeight{ + Permission: eos.PermissionLevel{ + Actor: eos.AccountName(so), + Permission: level, + }, + Weight: uint16(signerWeights[so]), + }) + } + if len(weights) > 0 { + return true, weights, nil + } + return false, nil, errors.New("could not parse permission levels") +} + +// getRequestsForAccount will query the propose table, and returns a map with the proposal name, and a zlib compressed copy +// of the packed transactions (they can be huge!) +func getRequestsForAccount(actor string, limit int, lowerLimit string, account *fio.Account, api *fio.API, opts *fio.TxOptions) (map[string][]byte, error) { + // TODO: all the queries. + return nil, nil +} + +func updateAuthResult(account *fio.Account, signers []signer, threshold int) (ok bool, result *widget.Box, err error) { + ok, activePermLevel, _ := checkSigners(signers, "active") + ok, ownerPermLevel, _ := checkSigners(signers, "owner") + if !ok { + return ok, nil, errors.New("update auth Failed, an invalid actor was supplied") + } + a, o, e := fio.NewConnection(account.KeyBag, Uri) + if e != nil { + errs.ErrChan <- e.Error() + errs.ErrChan <- "Could not update auth, new connection failed:" + return false, nil, errors.New("update auth Failed, could not connect to server") + } + a.Header.Set("User-Agent", "fio-cryptonym-wallet") + feeMultGuess := float64(42*len(activePermLevel)) / 1000.0 + feeMultGuess = math.Ceil(feeMultGuess) + if feeMultGuess < 1 { + feeMultGuess = 1.0 + } + updateActive := fio.NewAction("eosio", "updateauth", account.Actor, fio.UpdateAuth{ + Account: account.Actor, + Permission: "active", + Parent: "owner", + Auth: fio.Authority{ + Threshold: uint32(threshold), + Accounts: activePermLevel, + }, + MaxFee: fio.Tokens(fio.GetMaxFee(fio.FeeAuthUpdate) * feeMultGuess), + }) + buf := bytes.NewBuffer(nil) + updateOwner := fio.NewActionWithPermission("eosio", "updateauth", account.Actor, "owner", fio.UpdateAuth{ + Account: account.Actor, + Permission: "owner", + //Parent: "owner", + Auth: fio.Authority{ + Threshold: uint32(threshold), + Accounts: ownerPermLevel, + }, + MaxFee: fio.Tokens(fio.GetMaxFee(fio.FeeAuthUpdate) * feeMultGuess), + }) + _, tx, e := a.SignTransaction( + fio.NewTransaction( + []*fio.Action{updateOwner, updateActive}, o), + o.ChainID, fio.CompressionNone, + ) + if e != nil { + errs.ErrChan <- e.Error() + errs.ErrChan <- account.KeyBag.Keys[0].String() + errs.ErrChan <- "use this private key to recover funds." + errs.ErrChan <- "Could not update auth for owner, sign transaction failed:" + return false, nil, errors.New("Update Auth Failed: " + e.Error()) + } + out, e := a.PushTransactionRaw(tx) + if e != nil { + errs.ErrChan <- e.Error() + errs.ErrChan <- account.KeyBag.Keys[0].String() + errs.ErrChan <- "use this private key to recover funds." + errs.ErrChan <- "Could not update auth for owner, push transaction failed:" + return false, nil, errors.New("Update Auth Failed: " + e.Error()) + } + j, _ := json.MarshalIndent(out, "", " ") + if len(j) > 2 { + buf.Write(j) + } + actors := make([]string, 0) + for _, act := range signers { + actors = append(actors, act.actor.Text) + } + sort.Strings(actors) + txResult := widget.NewMultiLineEntry() + txResult.SetText(string(buf.Bytes())) + errs.ErrChan <- fmt.Sprintf("Update auth success: %s updated permsissions with signers %v", string(account.Actor), actors) + return true, widget.NewVBox( + widget.NewHBox( + widget.NewLabel("Successfully created new msig account for "+string(account.Actor)+" "), + widget.NewButtonWithIcon("", theme.ContentCopyIcon(), func() { + cb := Win.Clipboard() + cb.SetContent(string(account.Actor)) + }), + ), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize((W*50)/100, (H*50)/100)), + widget.NewScrollContainer( + txResult, + ), + ), + ), nil +} diff --git a/msig-requests.go b/msig-requests.go new file mode 100644 index 0000000..f1bfb54 --- /dev/null +++ b/msig-requests.go @@ -0,0 +1,610 @@ +package cryptonym + +import ( + "encoding/json" + "fmt" + "fyne.io/fyne" + "fyne.io/fyne/canvas" + "fyne.io/fyne/dialog" + "fyne.io/fyne/layout" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + fioassets "github.com/blockpane/cryptonym/assets" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go" + "github.com/fioprotocol/fio-go/eos" + "golang.org/x/text/language" + "golang.org/x/text/message" + "sort" + "strings" +) + +func MsigRequestsContent(api *fio.API, opts *fio.TxOptions, account *fio.Account) *widget.TabItem { + pendingTab := widget.NewTabItem("Pending Requests", + widget.NewScrollContainer( + ProposalRows(0, 100, api, opts, account), + ), + ) + return pendingTab +} + +func requestBox(proposer string, requests []*fio.MsigApprovalsInfo, index int, proposalWindow fyne.Window, api *fio.API, opts *fio.TxOptions, account *fio.Account) fyne.CanvasObject { + p := message.NewPrinter(language.AmericanEnglish) + aFee := fio.GetMaxFee(fio.FeeMsigApprove) + dFee := fio.GetMaxFee(fio.FeeMsigUnapprove) + cFee := fio.GetMaxFee(fio.FeeMsigCancel) + eFee := fio.GetMaxFee(fio.FeeMsigExec) + proposalHash := eos.Checksum256{} + + refresh := func() { + _, ai, err := api.GetApprovals(fio.Name(proposer), 10) + if err != nil { + errs.ErrChan <- err.Error() + return + } + requests = ai + } + + approve := widget.NewButtonWithIcon(p.Sprintf("Approve %s %g", fio.FioSymbol, aFee), theme.ConfirmIcon(), func() { + _, tx, err := api.SignTransaction( + fio.NewTransaction([]*fio.Action{ + fio.NewMsigApprove(eos.AccountName(proposer), requests[index].ProposalName, account.Actor, proposalHash), + }, opts), + opts.ChainID, fio.CompressionNone, + ) + if err != nil { + errs.ErrChan <- err.Error() + resultPopup(err.Error(), proposalWindow) + return + } + res, err := api.PushTransactionRaw(tx) + if err != nil { + errs.ErrChan <- err.Error() + resultPopup(err.Error(), proposalWindow) + return + } + errs.ErrChan <- fmt.Sprintf("sending approval for proposal '%s' proposed by %s", requests[index].ProposalName, proposer) + j, _ := json.MarshalIndent(res, "", " ") + refresh() + proposalWindow.SetContent( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize((W*85)/100, (H*85)/100)), + requestBox(proposer, requests, index, proposalWindow, api, opts, account), + )) + proposalWindow.Content().Refresh() + resultPopup(string(j), proposalWindow) + }) + approve.Hide() + deny := widget.NewButtonWithIcon(p.Sprintf("Un-Approve %s %g", fio.FioSymbol, dFee), theme.ContentUndoIcon(), func() { + _, tx, err := api.SignTransaction( + fio.NewTransaction([]*fio.Action{ + fio.NewMsigUnapprove(eos.AccountName(proposer), requests[index].ProposalName, account.Actor), + }, opts), + opts.ChainID, fio.CompressionNone, + ) + if err != nil { + errs.ErrChan <- err.Error() + resultPopup(err.Error(), proposalWindow) + return + } + res, err := api.PushTransactionRaw(tx) + if err != nil { + errs.ErrChan <- err.Error() + resultPopup(err.Error(), proposalWindow) + return + } + j, _ := json.MarshalIndent(res, "", " ") + errs.ErrChan <- fmt.Sprintf("withdrawing approval for proposal '%s' proposed by %s", requests[index].ProposalName, proposer) + refresh() + proposalWindow.SetContent( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize((W*85)/100, (H*85)/100)), + requestBox(proposer, requests, index, proposalWindow, api, opts, account), + )) + proposalWindow.Content().Refresh() + resultPopup(string(j), proposalWindow) + }) + deny.Hide() + cancel := widget.NewButtonWithIcon(p.Sprintf("Cancel %s %g", fio.FioSymbol, cFee), theme.DeleteIcon(), func() { + _, tx, err := api.SignTransaction( + fio.NewTransaction([]*fio.Action{ + fio.NewMsigCancel(eos.AccountName(proposer), requests[index].ProposalName, account.Actor), + }, opts), + opts.ChainID, fio.CompressionNone, + ) + if err != nil { + errs.ErrChan <- err.Error() + resultPopup(err.Error(), proposalWindow) + return + } + res, err := api.PushTransactionRaw(tx) + if err != nil { + errs.ErrChan <- err.Error() + resultPopup(err.Error(), proposalWindow) + return + } + j, _ := json.MarshalIndent(res, "", " ") + errs.ErrChan <- fmt.Sprintf("cancel proposal '%s'", requests[index].ProposalName) + resultPopup(string(j), proposalWindow) + }) + cancel.Hide() + execute := widget.NewButtonWithIcon(p.Sprintf("Execute %s %g", fio.FioSymbol, eFee), fioassets.NewFioLogoResource(), func() { + _, tx, err := api.SignTransaction( + fio.NewTransaction([]*fio.Action{ + fio.NewMsigExec(eos.AccountName(proposer), requests[index].ProposalName, fio.Tokens(eFee), account.Actor), + }, opts), + opts.ChainID, fio.CompressionNone, + ) + if err != nil { + errs.ErrChan <- err.Error() + resultPopup(err.Error(), proposalWindow) + return + } + res, err := api.PushTransactionRaw(tx) + if err != nil { + errs.ErrChan <- err.Error() + resultPopup(err.Error(), proposalWindow) + return + } + j, _ := json.MarshalIndent(res, "", " ") + errs.ErrChan <- fmt.Sprintf("executing proposal '%s' proposed by %s", requests[index].ProposalName, proposer) + refresh() + proposalWindow.SetContent( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize((W*85)/100, (H*85)/100)), + requestBox(proposer, requests, index, proposalWindow, api, opts, account), + )) + proposalWindow.Content().Refresh() + resultPopup(string(j), proposalWindow) + }) + execute.Hide() + if proposer == string(account.Actor) { + cancel.Show() + } + if len(requests) <= index { + return widget.NewHBox( + layout.NewSpacer(), + widget.NewLabelWithStyle("Requests table has changed, please refresh and try again.", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), + layout.NewSpacer(), + ) + } + if requests[index].HasApproved(account.Actor) { + deny.Show() + } + if requests[index].HasRequested(account.Actor) && !requests[index].HasApproved(account.Actor) { + approve.Show() + } + approvers := make(map[string]bool) + approvalWeightLabel := widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{}) + proposalTitle := widget.NewHBox( + layout.NewSpacer(), + widget.NewLabel("Proposal Name: "), + widget.NewLabelWithStyle(string(requests[index].ProposalName), fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + widget.NewButtonWithIcon("", theme.ContentCopyIcon(), func() { + Win.Clipboard().SetContent(string(requests[index].ProposalName)) + }), + layout.NewSpacer(), + ) + proposalAuthor := widget.NewHBox( + layout.NewSpacer(), + widget.NewLabel("Proposal Author: "), + widget.NewLabelWithStyle(proposer, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + widget.NewButtonWithIcon("", theme.ContentCopyIcon(), func() { + Win.Clipboard().SetContent(proposer) + }), + layout.NewSpacer(), + ) + approversRows := widget.NewVBox(proposalTitle, proposalAuthor, approvalWeightLabel) + producers, pErr := api.GetProducerSchedule() + var top21Count int + isProd := func(name eos.AccountName) string { + result := make([]string, 0) + if pErr != nil || producers == nil { + return "" + } + for _, p := range producers.Active.Producers { + if p.AccountName == name { + result = append(result, "Top 21 Producer") + break + } + } + if account.Actor == name { + result = append(result, "Current Actor") + } + if string(name) == proposer { + result = append(result, "Proposal Author") + } + return strings.Join(result, " ~ ") + } + + for _, approver := range requests[index].RequestedApprovals { + approvers[string(approver.Level.Actor)] = false + } + for _, approver := range requests[index].ProvidedApprovals { + approvers[string(approver.Level.Actor)] = true + } + approversRows.Append(widget.NewHBox( + layout.NewSpacer(), approve, deny, cancel, execute, layout.NewSpacer(), + )) + approversRows.Append( + fyne.NewContainerWithLayout(layout.NewGridLayout(3), + fyne.NewContainerWithLayout(layout.NewGridLayout(2), + widget.NewLabelWithStyle("Approved", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + widget.NewLabelWithStyle("Name", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + ), + widget.NewLabelWithStyle("Account", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + layout.NewSpacer(), + ), + ) + + approversSorted := func() []string { + s := make([]string, 0) + for k := range approvers { + s = append(s, k) + } + sort.Strings(s) + return s + }() + var checked int + for _, k := range approversSorted { + // actor, fio address, has approved, is produce + //hasApproved := theme.CancelIcon() + hasApproved := theme.CheckButtonIcon() + if approvers[k] { + //hasApproved = theme.ConfirmIcon() + hasApproved = theme.CheckButtonCheckedIcon() + checked += 1 + for _, p := range producers.Active.Producers { + if p.AccountName == eos.AccountName(k) { + top21Count += 1 + break + } + } + } + var firstName string + n, ok, _ := api.GetFioNamesForActor(k) + if ok && len(n.FioAddresses) > 0 { + firstName = n.FioAddresses[0].FioAddress + } + deref := &k + actor := *deref + copyButton := widget.NewButtonWithIcon("", theme.ContentCopyIcon(), func() { + Win.Clipboard().SetContent(actor) + }) + approversRows.Append(fyne.NewContainerWithLayout(layout.NewGridLayout(3), + fyne.NewContainerWithLayout(layout.NewGridLayout(2), + widget.NewHBox( + layout.NewSpacer(), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(32, 32)), + canvas.NewImageFromResource(hasApproved), + )), + widget.NewLabel(firstName), + ), + widget.NewHBox( + layout.NewSpacer(), + copyButton, + widget.NewLabelWithStyle(k, fyne.TextAlignTrailing, fyne.TextStyle{Monospace: true}), + ), + widget.NewLabelWithStyle(isProd(eos.AccountName(k)), fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + )) + + } + + type actionActor struct { + Actor string `json:"actor"` + } + // will use for counting vote weights ... + actorMap := make(map[string]bool) + actions := make([]msigActionInfo, 0) + actionString := "" + tx, err := api.GetProposalTransaction(eos.AccountName(proposer), requests[index].ProposalName) + if err != nil { + return widget.NewHBox(widget.NewLabelWithStyle(err.Error(), fyne.TextAlignCenter, fyne.TextStyle{Bold: true})) + } else { + proposalHash = tx.ProposalHash + for _, action := range tx.PackedTransaction.Actions { + abi, err := api.GetABI(action.Account) + if err != nil { + errs.ErrChan <- err.Error() + continue + } + decoded, err := abi.ABI.DecodeAction(action.HexData, action.Name) + if err != nil { + errs.ErrChan <- err.Error() + continue + } + actions = append(actions, msigActionInfo{ + Action: decoded, + Account: string(action.Account), + Name: string(action.Name), + ProposalHash: tx.ProposalHash, + }) + aActor := &actionActor{} + err = json.Unmarshal(decoded, aActor) + if err == nil && aActor.Actor != "" { + actorMap[aActor.Actor] = true + } + } + a, err := json.MarshalIndent(actions, "", " ") + if err != nil { + errs.ErrChan <- err.Error() + } else { + actionString = string(a) + } + } + hasApprovals := true + approvalsNeeded := make([]string, 0) + have := "have" + var privAction bool + for a := range PrivilegedActions { + sa := strings.Split(a, "::") + if len(sa) != 2 { + continue + } + if string(tx.PackedTransaction.Actions[0].Name) == sa[1] { + privAction = true + break + } + } + if privAction { + if checked == 1 { + have = "has" + } + var required int + type tp struct { + Producer string `json:"producer"` + } + rows := make([]tp, 0) + gtr, err := api.GetTableRows(eos.GetTableRowsRequest{ + Code: "eosio", + Scope: "eosio", + Table: "topprods", + Limit: 21, + JSON: true, + }) + if err != nil { + errs.ErrChan <- err.Error() + required = 15 + } else { + _ = json.Unmarshal(gtr.Rows, &rows) + if len(rows) == 0 || len(rows) == 21 { + required = 15 + } else { + required = (len(rows) / 2) + ((len(rows) / 2) / 2) + } + } + var top21Voted string + if top21Count > 0 { + top21Voted = fmt.Sprintf(" - (%d are Top 21 Producers)", top21Count) + } + approvalsNeeded = append(approvalsNeeded, fmt.Sprintf("Account %s requires %d approvals, %d %s been provided%s", tx.PackedTransaction.Actions[0].Account, required, checked, have, top21Voted)) + if checked < required { + hasApprovals = false + } + } else { + for msigAccount := range actorMap { + needs, has, err := getVoteWeight(msigAccount, requests[index].ProvidedApprovals, api) + if err != nil { + errs.ErrChan <- err.Error() + hasApprovals = false + } + if has == 1 { + have = "has" + } + approvalsNeeded = append(approvalsNeeded, fmt.Sprintf("Account %s requires %d approvals, %d %s been provided", msigAccount, needs, has, have)) + if has < needs { + hasApprovals = false + } + } + } + if hasApprovals { + execute.SetText(p.Sprintf("Execute %s %g", fio.FioSymbol, eFee)) + execute.Show() + } + approvalWeightLabel.SetText(strings.Join(approvalsNeeded, "\n")) + approvalWeightLabel.Refresh() + actionEntry := widget.NewLabelWithStyle("", fyne.TextAlignLeading, fyne.TextStyle{Monospace: true}) + actionLines := strings.Split(actionString, "\n") + for i := range actionLines { + if len(actionLines[i]) > 128 { + actionLines[i] = actionLines[i][:125] + "..." + } + } + actionEntry.SetText(strings.Join(actionLines, "\n")) + actionBox := widget.NewGroup("Transaction", widget.NewHBox( + layout.NewSpacer(), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize((W*75)/100, actionEntry.MinSize().Height)), + actionEntry, + ), + layout.NewSpacer(), + )) + return widget.NewGroupWithScroller(string(requests[index].ProposalName), approversRows, actionBox) +} + +type msigActionInfo struct { + Account string `json:"account"` + Name string `json:"name"` + Action json.RawMessage `json:"action"` + ProposalHash eos.Checksum256 `json:"proposal_hash"` +} + +func getVoteWeight(account string, providedApprovals []fio.MsigApproval, api *fio.API) (required int, current int, err error) { + acc, err := api.GetFioAccount(account) + if err != nil { + return 0, 0, nil + } + weights := make(map[string]int) + for _, a := range acc.Permissions { + if a.PermName == "active" && a.RequiredAuth.Accounts != nil && len(a.RequiredAuth.Accounts) > 0 { + required = int(a.RequiredAuth.Threshold) + for _, owner := range a.RequiredAuth.Accounts { + weights[string(owner.Permission.Actor)] = int(owner.Weight) + } + } + } + for _, approved := range providedApprovals { + current = current + weights[string(approved.Level.Actor)] + } + return +} + +func resultPopup(result string, window fyne.Window) { + mle := widget.NewMultiLineEntry() + mle.SetText(result) + dialog.ShowCustom("Transaction Result", "Done", + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize((W*50)/100, (H*50)/100)), + widget.NewScrollContainer( + mle, + ), + ), + window, + ) +} + +func ProposalRows(offset int, limit int, api *fio.API, opts *fio.TxOptions, account *fio.Account) *widget.Box { + _, proposals, err := api.GetProposals(offset, limit) + if err != nil { + errs.ErrChan <- err.Error() + return widget.NewHBox(widget.NewLabel(err.Error())) + } + refButton := &widget.Button{} + refButton = widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), func() { + refButton.Disable() + MsigLastTab = 0 + go func() { + MsigRefreshRequests <- true + }() + }) + //onlyPriv := widget.NewCheck("Only Privileged", func(b bool) {}) + //onlyPriv.SetChecked(true) + //last14 := widget.NewCheck("Only show last 14 days", func(b bool) {}) + //last14.SetChecked(true) + + vb := widget.NewVBox() + vb.Append(widget.NewHBox( + //layout.NewSpacer(), onlyPriv, last14, refButton, layout.NewSpacer(), + layout.NewSpacer(), refButton, layout.NewSpacer(), + ), + ) + proposalsSorted := func() []string { + p := make([]string, 0) + for k := range proposals { + p = append(p, k) + } + sort.Strings(p) + return p + }() + for _, proposer := range proposalsSorted { + more, approvalsInfo, err := api.GetApprovals(fio.Name(proposer), 10) + if err != nil { + errs.ErrChan <- err.Error() + continue + } + names, found, err := api.GetFioNamesForActor(proposer) + if err != nil { + errs.ErrChan <- err.Error() + continue + } + fioAddresses := make([]string, 0) + if found { + for _, n := range names.FioAddresses { + fioAddresses = append(fioAddresses, n.FioAddress) + } + } + var fioAddrs string + if len(fioAddresses) > 0 { + fioAddrs = strings.Join(fioAddresses, ", ") + if len(fioAddrs) > 32 { + fioAddrs = fioAddrs[:28] + "..." + } + } + var sep string + if fioAddrs != "" { + sep = " – " + } + groupRows := fyne.NewContainerWithLayout(layout.NewGridLayout(2)) + if more { + groupRows.AddObject( + widget.NewLabel("Account has more than 10 proposals, not all are shown."), + ) + groupRows.AddObject(layout.NewSpacer()) + } + group := widget.NewGroup(fmt.Sprintf(" (%s)%s%s ", proposer, sep, fioAddrs), groupRows) + sort.Slice(approvalsInfo, func(i, j int) bool { + a, _ := eos.StringToName(string(approvalsInfo[i].ProposalName)) + b, _ := eos.StringToName(string(approvalsInfo[j].ProposalName)) + return a < b + }) + for index, prop := range approvalsInfo { + hasNeeds := fmt.Sprintf("%d of %d approvals", len(prop.ProvidedApprovals), len(prop.ProvidedApprovals)+len(prop.RequestedApprovals)) + gpt, err := api.GetProposalTransaction(eos.AccountName(proposer), prop.ProposalName) + if err != nil { + errs.ErrChan <- err.Error() + continue + } + for _, action := range gpt.PackedTransaction.Actions { + a, err := api.GetABI(action.Account) + if err != nil { + errs.ErrChan <- err.Error() + continue + } + derefIdx := &index + idx := *derefIdx + derefProp := &proposer + newProp := *derefProp + view := func() *widget.Button { + return widget.NewButtonWithIcon("", theme.VisibilityIcon(), func() { + proposalWindow := App.NewWindow(newProp + " - " + string(prop.ProposalName)) + proposalWindow.SetContent( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize((W*85)/100, (H*85)/100)), + requestBox(newProp, approvalsInfo, idx, proposalWindow, api, opts, account), + )) + proposalWindow.SetFixedSize(true) + proposalWindow.SetOnClosed(func() { + Win.RequestFocus() + }) + proposalWindow.Show() + }) + }() + decoded, err := a.ABI.DecodeAction(action.HexData, action.Name) + if err != nil { + errs.ErrChan <- err.Error() + } + details := make(map[string]interface{}) + // don't try to decode an empty action + if len(decoded) > 0 { + err = json.Unmarshal(decoded, &details) + if err != nil { + errs.ErrChan <- err.Error() + continue + } + } + ds := make([]string, 0) + keys := make([]string, 0) + for k := range details { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + if k == "tpid" || k == "max_fee" || k == "actor" { + continue + } + ds = append(ds, fmt.Sprintf("%s: %+v", k, details[k])) + } + summaryCols := widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(150, 30)), view), + fyne.NewContainerWithLayout(layout.NewGridLayout(3), + widget.NewHBox(layout.NewSpacer(), widget.NewLabelWithStyle(string(prop.ProposalName), fyne.TextAlignLeading, fyne.TextStyle{Monospace: true}), layout.NewSpacer()), + widget.NewHBox(layout.NewSpacer(), widget.NewLabelWithStyle(fmt.Sprintf("%16s", action.Name), fyne.TextAlignCenter, fyne.TextStyle{Monospace: true}), layout.NewSpacer()), + widget.NewHBox(layout.NewSpacer(), widget.NewLabelWithStyle(hasNeeds, fyne.TextAlignCenter, fyne.TextStyle{}), layout.NewSpacer()), + )) + txSummary := strings.Join(ds, ", ") + if len(txSummary) > 64 { + txSummary = txSummary[:60] + "..." + } + groupRows.AddObject(summaryCols) + groupRows.AddObject(widget.NewLabelWithStyle(txSummary, fyne.TextAlignTrailing, fyne.TextStyle{Monospace: true})) + } + } + vb.Append(group) + vb.Append(widget.NewHBox(widget.NewLabel(" "))) + } + return vb +} diff --git a/payload.go b/payload.go new file mode 100644 index 0000000..db634eb --- /dev/null +++ b/payload.go @@ -0,0 +1,529 @@ +package cryptonym + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/blockpane/cryptonym/fuzzer" + "github.com/fioprotocol/fio-go" + "github.com/fioprotocol/fio-go/eos" + "math" + "math/rand" + "strconv" + "strings" + "time" +) + +// TODO: rethink this, instead of calling this every request, maybe pass pointers to functions? +func (abi *Abi) GeneratePayloads(key *fio.Account) error { + abi.mux.RLock() + defer abi.mux.RUnlock() + + for i := 0; i < len(abi.Rows); i++ { + form := &abi.Rows[i] + err := func() error { + var v interface{} + v = nil + isSlice := false + if strings.HasSuffix(form.Type.Selected, `[]`) { + isSlice = true + } + switch form.SendAs.Selected { + + case "form value": + switch form.Variation.Selected { + case "as is": + if strings.Contains(form.Type.Selected, "int") { + t, e := strconv.ParseInt(form.Input.Text, 10, 64) + if e != nil { + return errors.New(*form.Name + ": " + e.Error()) + } + abi.mux.RUnlock() + FormState.UpdateValue(&i, t, isSlice, true) + abi.mux.RLock() + return nil + } else if strings.Contains(form.Type.Selected, "float") { + t, e := strconv.ParseFloat(form.Input.Text, 64) + if e != nil { + return errors.New(*form.Name + ": " + e.Error()) + } + abi.mux.RUnlock() + FormState.UpdateValue(&i, t, isSlice, true) + abi.mux.RLock() + return nil + } + v = form.Input.Text + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + return nil + case "FIO -> suf": + unFriendly := strings.ReplaceAll(strings.ReplaceAll(form.Input.Text, ",", ""), "_", "") + f, e := strconv.ParseFloat(unFriendly, 64) + if e != nil { + return errors.New(*form.Name + ": " + e.Error()) + } + t := uint64(f * 1_000_000_000.0) + abi.mux.RUnlock() + FormState.UpdateValue(&i, t, isSlice, true) + abi.mux.RLock() + return nil + case "json -> struct": + v = form.Input.Text + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, false, true) + abi.mux.RLock() + return nil + case "hex -> byte[]": + h, e := hex.DecodeString(form.Input.Text) + if e != nil { + return errors.New(*form.Name + ": " + e.Error()) + } + abi.mux.RUnlock() + FormState.UpdateValue(&i, h, isSlice, false) + abi.mux.RLock() + return nil + case "base64 -> byte[]": + buf := bytes.NewReader([]byte(form.Input.Text)) + b64 := base64.NewDecoder(base64.StdEncoding, buf) + b := make([]byte, 0) + _, e := b64.Read(b) + if e != nil { + return errors.New(*form.Name + ": " + e.Error()) + } + abi.mux.RUnlock() + FormState.UpdateValue(&i, b, isSlice, false) + abi.mux.RLock() + return nil + case "checksum256": + v = fuzzer.ChecksumOf(form.Input.Text) + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + return nil + case "signature": + v = fuzzer.SignatureFor(form.Input.Text, key) + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + return nil + case "fio address@ (valid)": + v = fuzzer.FioAddressAt(form.Input.Text) + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + case "fio address@ (valid, max size)": + v = fuzzer.MaxRandomFioAddressAt(form.Input.Text) + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + case "fio address@ (invalid)": + v = fuzzer.InvalidFioAddressAt(form.Input.Text) + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + } + + case "actor": + switch form.Variation.Selected { + case "mine": + v = key.Actor + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + return nil + case "random": + v = fuzzer.RandomActor() + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + return nil + } + + case "pub key": + switch form.Variation.Selected { + case "mine": + v = key.PubKey + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + return nil + case "random": + v = fuzzer.RandomFioPubKey() + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + return nil + } + + case "fio types": + switch form.Variation.Selected { + // TODO: + //case "random array of existing fio address": + case "max length: addaddress.public_addresses": + v = fuzzer.MaxAddPubAddress() + fmt.Println(v) + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, false, true) + abi.mux.RLock() + return nil + case "max length: voteproducer.producers": + v = fuzzer.MaxVoteProducers(Uri) + fmt.Println(v) + abi.mux.RUnlock() + FormState.UpdateValueWithConvert(&i, v, false, "string[]", false) + abi.mux.RLock() + return nil + case "variable length: addaddress.public_addresses": + var payloadLen int + var err error + if form.Len.Selected == "" { + payloadLen = 1 + form.Len.SetSelected("1") + } else { + payloadLen, err = strconv.Atoi(form.Len.Selected) + if err != nil { + payloadLen = 1 + } + } + v = fuzzer.RandomAddAddress(payloadLen) + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, false, true) + abi.mux.RLock() + return nil + case "invalid fio domain": + v = fuzzer.InvalidFioDomain() + case "valid fio domain (max size)": + v = fuzzer.MaxRandomFioDomain() + case "valid fio domain": + v = fuzzer.FioDomain() + case "max length: newfundsreq.content": + v = fuzzer.MaxNewFundsContent() + case "max length: recordobt.content": + v = fuzzer.MaxRecObtContent() + case "max length: regproducer.url": + v = fuzzer.MaxProducerUrl() + case "random existing fio address": + v = fuzzer.RandomExistingFioAddress(Uri) + } + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + return nil + + case "number": + var l int + var e error + if form.Variation.Selected != "random number (mixed)" && form.Variation.Selected != "max int" { + if form.Len.Selected == "" { + return errors.New(*form.Name + ": no number specified") + } + l, e = strconv.Atoi(form.Len.Selected) + if e != nil { + return errors.New(*form.Name + ": " + e.Error()) + } + } + var noJsonEscape = true + switch form.Variation.Selected { + case "max int": + abiType := form.Len.Selected + if form.Type.Selected == "string" { + abiType = "string" + noJsonEscape = false + } + v = fuzzer.MaxInt(form.Len.Selected) + abi.mux.RUnlock() + //FormState.UpdateValue(&i, v, false, true) + FormState.UpdateValueWithConvert(&i, v, isSlice, abiType, noJsonEscape) + abi.mux.RLock() + return nil + case "incrementing float": + abiType := "float64" + if form.Type.Selected == "string" { + abiType = "string" + noJsonEscape = false + } + v = fuzzer.IncrementingFloat() + abi.mux.RUnlock() + FormState.UpdateValueWithConvert(&i, v, isSlice, abiType, noJsonEscape) + abi.mux.RLock() + return nil + case "incrementing int": + abiType := "int64" + if form.Type.Selected == "string" { + abiType = "string" + noJsonEscape = false + } + v = fuzzer.IncrementingInt() + abi.mux.RUnlock() + FormState.UpdateValueWithConvert(&i, v, isSlice, abiType, noJsonEscape) + abi.mux.RLock() + return nil + case "random float": + abiType := fmt.Sprintf("float%d", l) + if form.Type.Selected == "string" { + abiType = "string" + noJsonEscape = false + } + v = fuzzer.RandomFloat(l) + abi.mux.RUnlock() + FormState.UpdateValueWithConvert(&i, v, isSlice, abiType, noJsonEscape) + abi.mux.RLock() + return nil + case "random int": + switch l { + case 128: + v = fuzzer.RandomInt128() + abi.mux.RUnlock() + FormState.UpdateValueWithConvert(&i, v, isSlice, "string", false) + default: + abiType := fmt.Sprintf("int%d", l) + if form.Type.Selected == "string" { + abiType = "string" + noJsonEscape = false + } + v = fuzzer.RandomInteger(l).Interface() + abi.mux.RUnlock() + FormState.UpdateValueWithConvert(&i, v, isSlice, abiType, noJsonEscape) + } + abi.mux.RLock() + return nil + case "overflow int": + abiType := "uint64" + if form.Type.Selected == "string" { + abiType = "string" + noJsonEscape = false + } + signed := false + if strings.HasPrefix(form.Type.Selected, "int") { + signed = true + } + v = fuzzer.OverFlowInt(l, signed) + abi.mux.RUnlock() + FormState.UpdateValueWithConvert(&i, v, isSlice, abiType, noJsonEscape) + abi.mux.RLock() + return nil + case "random number (mixed)": + rn := fuzzer.RandomNumber() + abiType := rn.AbiType() + if form.Type.Selected == "string" { + abiType = "string" + noJsonEscape = false + } + abi.mux.RUnlock() + FormState.UpdateValueWithConvert(&i, rn.String(), false, abiType, noJsonEscape) + abi.mux.RLock() + return nil + } + + case "bytes/string": + var hasLen bool + var payloadLen int + if form.Len.Selected != "" { + hasLen = true + e := func() error { + if form.Len.Selected == "random length" { + payloadLen = rand.Intn(math.MaxInt16 + 8) + return nil + } + var e error + payloadLen, e = strconv.Atoi(strings.ReplaceAll(form.Len.Selected, ",", "")) + if e != nil { + return errors.New(*form.Name + ": invalid number for payload length") + } + return nil + }() + if e != nil { + return e + } + } + lenError := func() error { + return errors.New(*form.Name + ": no length specified for random payload") + } + switch form.Variation.Selected { + case "string": + if !hasLen { + return lenError() + } + v = fuzzer.RandomString(payloadLen) + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + return nil + case "bytes": + if !hasLen { + return lenError() + } + v = fuzzer.RandomBytes(payloadLen, fuzzer.EncodeRaw) + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + return nil + case "bytes: hex encoded": + if !hasLen { + return lenError() + } + v = fuzzer.RandomBytes(payloadLen, fuzzer.EncodeHexString) + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + return nil + case "bytes: base64 encoded": + if !hasLen { + return lenError() + } + v = fuzzer.RandomBytes(payloadLen, fuzzer.EncodeBase64) + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + return nil + case "random checksum": + v = fuzzer.RandomChecksum() + abi.mux.RUnlock() + FormState.UpdateValue(&i, v, isSlice, false) + abi.mux.RLock() + return nil + } + + case "load file": + return errors.New("load file is not implemented yet") + + default: + return errors.New("unknown generator provided") + } + return nil + }() + if err != nil { + return err + } + } + return nil +} + +func (abi *Abi) PackAndSign(api *fio.API, opts *fio.TxOptions, account *fio.Account, msig bool) (json.RawMessage, *eos.PackedTransaction, error) { + if len(abi.Rows) == 0 { + return nil, nil, nil + } + abi.mux.RLock() + defer abi.mux.RUnlock() + + // build a json string + jsonString := "{" + for i, row := range abi.Rows { + if row.convert == nil { + row.convert = func(s interface{}) interface{} { + return s + } + } + b := make([]byte, 0) + var err error + switch { + case !row.IsSlice && !row.noJsonEscape: + b, err = json.Marshal(row.convert(row.Value)) + if err != nil { + errs.ErrChan <- err.Error() + } + case !row.IsSlice && row.noJsonEscape: + b = []byte(fmt.Sprintf("%v", *row.Value)) + //FIXME, this is converting everything to string arrays + case row.IsSlice: + payloads := func() []string { + p := strings.Split(fmt.Sprintf("%v", row.Input.Text), ",") + s := make([]string, len(p)) + for i := range p { + s[i] = strings.TrimSpace(p[i]) + } + return s + }() + b, err = json.Marshal(payloads) + if err != nil { + errs.ErrChan <- err.Error() + } + } + jsonString = jsonString + fmt.Sprintf(`"%s":%s`, *row.Name, string(b)) + if i < len(abi.Rows)-1 { + jsonString = jsonString + "," + } + } + jsonString = jsonString + "}" + + rawJ := json.RawMessage([]byte(jsonString)) + // make sure we can marshall the json we just created ... + if len(jsonString) >= 524288 { + rawJ = []byte(fmt.Sprintf(`{"message":"Not showing request: %d bytes is too large"}"`, len(jsonString))) + } else { + err := json.Unmarshal([]byte(jsonString), &rawJ) + if err != nil { + return nil, nil, errors.New("could not marshal new data into json: " + err.Error()) + } + } + + // get the "real" abi, and we will update it with any changes: + newAbi, err := api.GetABI(eos.AccountName(abi.Rows[0].Contract)) + if err != nil { + return nil, nil, err + } + newStructBytes := abi.DeriveJsonAbi() + newDef := eos.StructDef{} + err = json.Unmarshal(newStructBytes, &newDef) + for i, def := range newAbi.ABI.Structs { + if def.Name == abi.Action { + newAbi.ABI.Structs[i] = newDef + break + } + } + + encoded, err := newAbi.ABI.EncodeAction(eos.ActionName(abi.Action), []byte(jsonString)) + if err != nil { + return nil, nil, err + } + + actionData := eos.NewActionData(nil) + actionData.HexData = encoded + var finalActor string + for _, r := range abi.Rows { + if *r.Name == "actor" { + finalActor = fmt.Sprintf("%v", *r.Value) + } + } + if finalActor == "" { + finalActor = string(account.Actor) + } + action := &fio.Action{ + Account: eos.AccountName(abi.Contract), + Name: eos.ActionName(abi.Action), + Authorization: []eos.PermissionLevel{ + { + Actor: eos.AccountName(finalActor), + Permission: "active", + }, + }, + ActionData: actionData, + } + compression := fio.CompressionNone + if useZlib { + compression = fio.CompressionZlib + } + opts.TxOptions.DelaySecs = 0 + if deferTx { + opts.TxOptions.DelaySecs = uint32(delayTxSec) + } + signMe := fio.NewTransaction([]*fio.Action{action}, opts) + if msig { + signMe.Expiration.Time = time.Now().Add(time.Hour) + } + _, packedTx, err := api.SignTransaction( + signMe, + opts.ChainID, + compression, + ) + if err != nil { + return nil, nil, err + } + + return rawJ, packedTx, nil +} diff --git a/requests-form.go b/requests-form.go new file mode 100644 index 0000000..7eac788 --- /dev/null +++ b/requests-form.go @@ -0,0 +1,679 @@ +package cryptonym + +import ( + "fmt" + "fyne.io/fyne" + "fyne.io/fyne/canvas" + "fyne.io/fyne/dialog" + "fyne.io/fyne/layout" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go" + "sort" + "strconv" + "sync" + "time" +) + +const spaces = " " // placeholder for pubkeys + +var RefreshRequestsChan = make(chan bool) + +func RequestContent(reqContent chan fyne.CanvasObject, refresh chan bool) { + content, err := GetPending(refresh, Account, Api) + if err != nil { + panic(err) + } + reqContent <- content + go func() { + for { + select { + case <- refresh: + content, err := GetPending(refresh, Account, Api) + if err != nil { + errs.ErrChan <-err.Error() + continue + } + reqContent <- content + } + } + }() +} + +func GetPending(refreshChan chan bool, account *fio.Account, api *fio.API) (form fyne.CanvasObject, err error) { + sendNew := widget.NewButtonWithIcon("Request Funds", theme.DocumentCreateIcon(), func() { + closed := make(chan interface{}) + d := dialog.NewCustom( + "Send a new funds request", + "Cancel", + NewRequest(account, api), + Win, + ) + go func() { + <- closed + d.Hide() + }() + d.SetOnClosed(func() { + refreshChan <- true + }) + d.Show() + }) + topDesc := widget.NewLabel("") + top := widget.NewHBox( + layout.NewSpacer(), + topDesc, + widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), func() { + refreshChan <-true + }), + sendNew, + layout.NewSpacer(), + ) + + pending, has, err := api.GetPendingFioRequests(account.PubKey, 101, 0) + if err != nil { + return widget.NewHBox(widget.NewLabel(err.Error())), err + } + if !has { + return widget.NewVBox(top, widget.NewLabel("No pending requests.")), err + } + howMany := len(pending.Requests) + topDesc.SetText(fmt.Sprint(howMany)+" pending requests.") + if howMany > 100 { + topDesc.SetText("More than 100 pending requests.") + } + if howMany > 25 { + topDesc.SetText(topDesc.Text + fmt.Sprintf(" (only first 25 displayed.)")) + pending.Requests = pending.Requests[:25] + } + + requests := fyne.NewContainerWithLayout(layout.NewGridLayout(5), + widget.NewLabelWithStyle("Actions", fyne.TextAlignLeading, fyne.TextStyle{}), + widget.NewLabelWithStyle("ID / Time", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), + widget.NewLabelWithStyle("From", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + widget.NewLabelWithStyle("To", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}), + widget.NewLabelWithStyle("Summary", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), + ) + + sort.Slice(pending.Requests, func(i, j int) bool { + return pending.Requests[i].FioRequestId < pending.Requests[j].FioRequestId + }) + for _, req := range pending.Requests{ + func(req fio.RequestStatus) { + id := widget.NewLabelWithStyle(fmt.Sprintf("%d | " + req.TimeStamp.Local().Format(time.Stamp), req.FioRequestId), fyne.TextAlignCenter, fyne.TextStyle{}) + payer := req.PayerFioAddress + if len(payer) > 32 { + payer = payer[:29] + "..." + } + payee := req.PayeeFioAddress + if len(payee) > 32 { + payee = payee[:29] + "..." + } + fr := widget.NewLabelWithStyle(payee, fyne.TextAlignTrailing, fyne.TextStyle{Bold:true}) + to := widget.NewLabelWithStyle(payer, fyne.TextAlignLeading, fyne.TextStyle{}) + view := widget.NewButtonWithIcon("View", theme.VisibilityIcon(),func() { + closed := make(chan interface{}) + d := dialog.NewCustom( + fmt.Sprintf("FIO Request ID %d (%s)", req.FioRequestId, req.PayeeFioAddress), + "Close", + ViewRequest(req.FioRequestId, closed, refreshChan, account, api), + Win, + ) + go func() { + <- closed + d.Hide() + refreshChan <-true + }() + d.Show() + }) + rejectBtn := widget.NewButtonWithIcon("", theme.DeleteIcon(), func() { + _, err := api.SignPushActions(fio.NewRejectFndReq(account.Actor, strconv.FormatUint(req.FioRequestId, 10))) + if err != nil { + errs.ErrChan <- err.Error() + return + } + errs.ErrChan <-"rejected request id: " + strconv.FormatUint(req.FioRequestId, 10) + refreshChan <-true + }) + rejectBtn.HideShadow = true + requests.AddObject(widget.NewHBox(view, layout.NewSpacer())) + requests.AddObject(id) + requests.AddObject(fr) + requests.AddObject(to) + obt, err := fio.DecryptContent(account, req.PayeeFioPublicKey, req.Content, fio.ObtRequestType) + var summary string + if err != nil { + view.Hide() + summary = "invalid content" + errs.ErrChan <-err.Error() + } else { + summary = obt.Request.ChainCode + if obt.Request.ChainCode != obt.Request.TokenCode { + summary += "/"+obt.Request.TokenCode + } + summary += fmt.Sprintf(" (%s) %q", obt.Request.Amount, obt.Request.Memo) + if len(summary) > 32 { + summary = summary[:29] + "..." + } + } + requests.AddObject(widget.NewHBox(layout.NewSpacer(), widget.NewLabelWithStyle(summary, fyne.TextAlignTrailing, fyne.TextStyle{Italic:true}), rejectBtn)) + }(req) + } + form = fyne.NewContainerWithLayout(layout.NewVBoxLayout(),top, requests) + + return +} + +func ViewRequest(id uint64, closed chan interface{}, refresh chan bool, account *fio.Account, api *fio.API) fyne.CanvasObject { + req, err := api.GetFioRequest(id) + if err != nil { + return widget.NewLabel(err.Error()) + } + decrypted, err := fio.DecryptContent(account, req.PayeeKey, req.Content, fio.ObtRequestType) + if err != nil { + return widget.NewLabel(err.Error()) + } + reqData := make([]*widget.FormItem, 0) + add := func(name string, value string) { + if len(value) > 0 { + a := widget.NewEntry() + a.SetText(value) + a.OnChanged = func(string) { + a.SetText(value) + } + reqData = append(reqData, widget.NewFormItem(name, a)) + } + } + reqData = append(reqData, widget.NewFormItem("Request Envelope", layout.NewSpacer())) + add("Request ID", strconv.FormatUint(id, 10)) + add("Time", req.Time.Format(time.UnixDate)) + add("Payer (To)", req.PayerFioAddress) + add("", req.PayerKey) + add("Payee (From)", req.PayeeFioAddress) + add("", req.PayeeKey) + reqData = append(reqData, widget.NewFormItem("", layout.NewSpacer())) + reqData = append(reqData, widget.NewFormItem("Decrypted Request", layout.NewSpacer())) + add("Payee Public Address", decrypted.Request.PayeePublicAddress) + add("Amount", decrypted.Request.Amount) + add("Chain Code", decrypted.Request.ChainCode) + add("Token Code", decrypted.Request.TokenCode) + add("Memo", decrypted.Request.Memo) + add("Hash", decrypted.Request.Hash) + add("Offline Url", decrypted.Request.OfflineUrl) + + errMsg := widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Monospace:true}) + errIcon := fyne.NewContainerWithLayout(layout.NewFixedGridLayout( fyne.NewSize(20, 20)), canvas.NewImageFromResource(theme.WarningIcon())) + errIcon.Hide() + respondBtn := &widget.Button{} + respondBtn = widget.NewButtonWithIcon("Record Response", theme.MailReplyIcon(), func() { + closing := make(chan interface{}) + d := dialog.NewCustom( + fmt.Sprintf("Respond: Request ID %d (%s)", req.FioRequestId, req.PayeeFioAddress), + "Cancel", + RespondRequest(req, decrypted.Request, closing, account, api), + Win, + ) + go func() { + <- closing + close(closed) + d.Hide() + }() + d.Show() + }) + rejectBtn := &widget.Button{} + rejectBtn = widget.NewButtonWithIcon("Reject", theme.DeleteIcon(), func() { + rejectBtn.Disable() + respondBtn.Disable() + resp, err := api.SignPushActions(fio.NewRejectFndReq(account.Actor, strconv.FormatUint(id, 10))) + if err != nil { + errIcon.Show() + errMsg.SetText(err.Error()) + errMsg.Refresh() + rejectBtn.Enable() + respondBtn.Enable() + return + } + errIcon.Hide() + errMsg.SetText("Done. Transaction ID: " + resp.TransactionID) + refresh <-true + }) + buttons := widget.NewVBox( + widget.NewHBox( + layout.NewSpacer(), + respondBtn, + rejectBtn, + layout.NewSpacer(), + ), + widget.NewHBox(layout.NewSpacer(), errIcon, errMsg, layout.NewSpacer()), + ) + f := widget.NewForm(reqData...) + return widget.NewVBox( + widget.NewHBox(layout.NewSpacer(), f, layout.NewSpacer()), + buttons, + ) +} + +func RespondRequest(req *fio.FundsReqTableResp, decrypted *fio.ObtRequestContent, closed chan interface{}, account *fio.Account, api *fio.API) fyne.CanvasObject { + var memo string + if len(decrypted.Memo) > 0 { + memo = "re: " + decrypted.Memo + } + record := &fio.ObtRecordContent{ + PayerPublicAddress: "", + PayeePublicAddress: decrypted.PayeePublicAddress, + Amount: decrypted.Amount, + ChainCode: decrypted.ChainCode, + TokenCode: decrypted.TokenCode, + Status: "", + ObtId: strconv.FormatUint(req.FioRequestId, 10), + Memo: memo, + Hash: "", + OfflineUrl: "", + } + bytesRemaining := widget.NewLabel("") + remaining := func(l *widget.Label) { + content, err := record.Encrypt(account, req.PayeeKey) + if err != nil { + l.SetText(err.Error()) + } + tooLarge := "" + if 432 - len(content) < 0 { + tooLarge = "Content is too large! " + } + over := 432 - len(content) + if over >= 0 { + over = 0 + } + l.SetText(fmt.Sprintf("%s%d bytes over (%d bytes after encryption and encoding) 432 max", tooLarge, over, len(content))) + } + respData := make([]*widget.FormItem, 0) + add := func(name string, value string, disabled bool, updateField *string) { + a := widget.NewEntry() + a.SetText(value) + if disabled { + a.Disable() + } + a.OnChanged = func(string) { + if updateField != nil { + *updateField = a.Text + } + remaining(bytesRemaining) + } + respData = append(respData, widget.NewFormItem(name, a)) + } + add("Request ID", strconv.FormatUint(req.FioRequestId, 10), true, nil) + add("Payer (To)", req.PayerFioAddress, true, nil) + add("Payee (From)", req.PayeeFioAddress, true, nil) + respData = append(respData, widget.NewFormItem("", layout.NewSpacer())) + add("Payer Public Key", "", false, &record.PayerPublicAddress) + add("Payee Public Key", decrypted.PayeePublicAddress, true, nil) + add("Chain Code", decrypted.ChainCode, true, nil) + add("Token Code", decrypted.TokenCode, true,nil) + add("Amount", decrypted.Amount, false, &record.Amount) + add("Status", "", false, &record.Status) + add("Memo", memo, false, &record.Memo) + add("Hash", "", false, &record.Hash) + add("Offline Url", "", false, &record.OfflineUrl) + + errMsg := widget.NewLabel("") + sendResponse := &widget.Button{} + sendResponse = widget.NewButtonWithIcon("Send Response", theme.ConfirmIcon(), func() { + content, err := record.Encrypt(account, req.PayeeKey) + if err != nil { + errMsg.SetText("Encrypt response: " + err.Error()) + return + } + resp, err := api.SignPushActions(fio.NewRecordSend(account.Actor, strconv.FormatUint(req.FioRequestId, 10), req.PayerFioAddress, req.PayeeFioAddress, content)) + if err != nil { + errMsg.SetText("Push Action: " + err.Error()) + return + } + errs.ErrChan <-"Success, txid: " + resp.TransactionID + errMsg.SetText("Success, txid: " + resp.TransactionID) + sendResponse.Disable() + time.Sleep(2*time.Second) + close(closed) + }) + + remaining(bytesRemaining) + return widget.NewVBox( + bytesRemaining, + widget.NewForm(respData...), + errMsg, + sendResponse, + ) +} + +func NewRequest(account *fio.Account, api *fio.API) fyne.CanvasObject { + n, _, err := account.GetNames(api) + if err != nil { + return widget.NewLabel(err.Error()) + } + if n == 0 { + return widget.NewLabel("You must have at least one FIO Name to send requests.") + } + send := &widget.Button{} + var content, payerFio, payerPub, payeeFio string + nfr := &fio.ObtRequestContent{} + + reqFormData := make([]*widget.FormItem, 0) + add := func(name string, value string, disabled bool, updateField *string) { + a := widget.NewEntry() + a.SetText(value) + if disabled { + a.Disable() + } + a.OnChanged = func(string) { + if updateField != nil { + *updateField = a.Text + } + } + reqFormData = append(reqFormData, widget.NewFormItem(name, a)) + } + payeeNameSelect := widget.NewSelect(func() []string { + names := make([]string, len(account.Addresses)) + for i := range account.Addresses { + names[i] = account.Addresses[i].FioAddress + } + return names + }(), + func(s string) { + payeeFio = s + }, + ) + payeeNameSelect.SetSelected(payeeNameSelect.Options[0]) + reqFormData = append(reqFormData, widget.NewFormItem("Your FIO Name", payeeNameSelect)) + + payerPubLabel := widget.NewLabelWithStyle(spaces, fyne.TextAlignLeading, fyne.TextStyle{Monospace:true}) + payerName := widget.NewEntry() + invalid := func() { + payerPubLabel.SetText(spaces) + payerPub = "" + send.Disable() + } + payerName.OnChanged = func(s string) { + if !fio.Address(s).Valid() { + invalid() + return + } + pa, ok, _ := api.PubAddressLookup(fio.Address(s), "FIO", "FIO") + if !ok || pa.PublicAddress == "" { + invalid() + return + } + payerPubLabel.SetText(pa.PublicAddress) + payerPub = pa.PublicAddress + payerFio = s + send.Enable() + } + reqFormData = append(reqFormData, widget.NewFormItem("Recipient's FIO Name", payerName)) + reqFormData = append(reqFormData, widget.NewFormItem("", payerPubLabel)) + + + reqFormData = append(reqFormData, widget.NewFormItem("", layout.NewSpacer())) + + payeeRecvAddress := widget.NewEntry() + payeeRecvAddress.OnChanged = func(s string) { + nfr.PayeePublicAddress = s + } + reqFormData = append(reqFormData, widget.NewFormItem("Send to Address", payeeRecvAddress)) + + chainSelect := &widget.SelectEntry{} + tokenSelect := widget.NewSelectEntry(make([]string, 0)) + tokenSelect.OnChanged = func(s string) { + nfr.TokenCode = s + resp, found, _:= api.PubAddressLookup(fio.Address(payeeFio), chainSelect.Text, tokenSelect.Text) + if !found { + return + } + payeeRecvAddress.SetText(resp.PublicAddress) + } + chainSelect = widget.NewSelectEntry(GetChains()) + chainSelect.OnChanged = func(s string) { + tokenSelect.SetOptions(GetTokens(s)) + tokenSelect.SetText(s) + tokenSelect.Refresh() + nfr.ChainCode = s + } + chainSelect.SetText("BTC") + tokenSelect.SetText("BTC") + reqFormData = append(reqFormData, widget.NewFormItem("Chain Code", chainSelect)) + reqFormData = append(reqFormData, widget.NewFormItem("Token Code", tokenSelect)) + + warn := fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(20, 20)), + canvas.NewImageFromResource(theme.WarningIcon()), + ) + amountEntry := widget.NewEntry() + amountEntry.SetText("0.0") + amountEntry.OnChanged = func(s string) { + fl, err := strconv.ParseFloat(s, 64) + if err != nil || fl == 0 { + warn.Show() + return + } + warn.Hide() + nfr.Amount = s + } + reqFormData = append(reqFormData, widget.NewFormItem("Amount", widget.NewHBox(amountEntry, warn))) + add("Memo", "", false, &nfr.Memo) + add("Hash", "", false, &nfr.Hash) + add("Offline Url", "", false, &nfr.OfflineUrl) + errLabel := widget.NewLabel("") + sendRequest := func(){ + defer func() { + go func() { + // prevent accidental click click click + time.Sleep(2*time.Second) + send.Enable() + }() + }() + send.Disable() + content, err = nfr.Encrypt(account, payerPub) + if err != nil { + errLabel.SetText(err.Error()) + errs.ErrChan <- err.Error() + return + } + resp, err := api.SignPushActions(fio.NewFundsReq(account.Actor, payerFio, payeeFio, content)) + if err != nil { + errLabel.SetText(err.Error()) + errs.ErrChan <-err.Error() + return + } + errLabel.SetText("Success, txid: " + resp.TransactionID) + } + send = widget.NewButtonWithIcon("Send Request", theme.ConfirmIcon(), sendRequest) + send.Disable() + return widget.NewVBox( + widget.NewForm(reqFormData...), + widget.NewHBox(layout.NewSpacer(), errLabel, layout.NewSpacer()), + send, + ) +} + +var chainMux = sync.Mutex{} + +func GetChains() []string { + chainMux.Lock() + defer chainMux.Unlock() + result := make([]string, len(chainTokens)) + i := 0 + for k := range chainTokens { + result[i] = k + i += 1 + } + sort.Strings(result) + return result +} + +func GetTokens(s string) []string { + chainMux.Lock() + defer chainMux.Unlock() + if s == "" || chainTokens[s] == nil { + return make([]string, 0) + } + return chainTokens[s] +} + +var chainTokens = map[string][]string{ + "ABBC": {"ABBC"}, + "ADA": {"ADA"}, + "ALGO": {"ALGO"}, + "ATOM": {"ATOM"}, + "BAND": {"BAND"}, + "BCH": { + "BCH", + "FLEX", + }, + "BHD": {"BHD"}, + "BNB": { + "ANKR", + "BNB", + "CHZ", + "ERD", + "ONE", + "RUNE", + "SWINGBY", + }, + "BSV": {"BSV"}, + "BTC": {"BTC"}, + "BTM": {"BTM"}, + "CET": {"CET"}, + "CHX": {"CHX"}, + "CKB": {"CKB"}, + "DASH": {"DASH"}, + "DOGE": {"DOGE"}, + "DOT": {"DOT"}, + "EOS": {"EOS"}, + "ETC": {"ETC"}, + "ETH": { + "AERGO", + "AKRO", + "ALTBEAR", + "ALTBULL", + "BAND", + "BAT", + "BEPRO", + "BNBBEAR", + "BNBBULL", + "BOLT", + "BTCBEAR", + "BTCBULL", + "BTMX", + "BVOL", + "BXA", + "CELR", + "CET", + "CHR", + "COTI", + "COVA", + "CRO", + "CVNT", + "DAD", + "DEEP", + "DIA", + "DOS", + "DREP", + "DUO", + "ELF", + "EOSBEAR", + "EOSBULL", + "ETH", + "ETHBEAR", + "ETHBULL", + "EXCHBEAR", + "EXCHBULL", + "FET", + "FRM", + "FTM", + "FTT", + "GEEQ", + "GT", + "HT", + "IBVOL", + "INFT", + "JRT", + "KCS", + "LAMB", + "LAMBS", + "LBA", + "LFT", + "LINK", + "LTCBEAR", + "LTCBULL", + "LTO", + "MATIC", + "MITX", + "MIX", + "OKB", + "OLT", + "OM", + "ORN", + "PAX", + "PROM", + "QCX", + "RNT", + "SEELE", + "SLV", + "SRM", + "STAKE", + "STPT", + "SWAP", + "TOKO", + "UAT", + "USDC", + "USDT", + "VALOR", + "VRA", + "XRPBEAR", + "XRPBULL", + "ZRX", + }, + "ETZ": {"ETZ"}, + "FIAT": { + "ACH", + "IBAN", + }, + "FIO": {"FIO"}, + "FSN": {"FSN"}, + "HPB": {"HPB"}, + "IOST": {"IOST"}, + "KAVA": {"KAVA"}, + "LTC": {"LTC"}, + "LTO": {"LTO"}, + "MHC": {"MHC"}, + "NEO": { + "GAS", + "NEO", + }, + "OLT": {"OLT"}, + "OMNI": {"USDT"}, + "ONE": {"ONE"}, + "ONT": {"ONG", + "ONT"}, + "QTUM": {"QTUM"}, + "RVN": {"RVN"}, + "SOL": {"SOL"}, + "TRX": { + "BTT", + "TRX", + "USDT", + }, + "VET": {"VET"}, + "WAN": { + "RVX", + "WAN", + }, + "XEM": {"XEM"}, + "XLM": {"XLM"}, + "XMR": {"XMR"}, + "XNS": {"XNS"}, + "XRP": {"XRP"}, + "XTZ": {"XTZ"}, + "YAP": {"YAP"}, + "ZEC": {"ZEC"}, + "ZIL": {"ZIL"}, +} + + + diff --git a/server-info.go b/server-info.go new file mode 100644 index 0000000..ef6b1a7 --- /dev/null +++ b/server-info.go @@ -0,0 +1,368 @@ +package cryptonym + +import ( + "encoding/json" + "fmt" + "fyne.io/fyne" + "fyne.io/fyne/canvas" + "fyne.io/fyne/layout" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go/eos" + "golang.org/x/text/language" + "golang.org/x/text/message" + "io/ioutil" + "net/url" + "strings" + "time" +) + +type ServerInfo struct { + Info *eos.InfoResp + Uri string +} + +type prodInfo struct { + address string + url *url.URL +} + +func InitServerInfo(info chan ServerInfo, reconnected chan bool) fyne.CanvasObject { + knownChains := map[string]string{ + "b20901380af44ef59c5918439a1f9a41d83669020319a80574b804a5f95cbd7e": "FIO Testnet", + "21dcae42c0182200e93f954a074011f9048a7624c6fe81d3c9541a614a88bd1c": "FIO Mainnet", + "e143d39294a14616dbbee394f1c159a4eb71b656b9ca1094ebf924dc3714d7ae": "Dapix Development Chain", + } + + prods := make(map[string]*prodInfo) + + uriLabel := widget.NewLabel(Uri) + rows := []fyne.CanvasObject{ + widget.NewLabelWithStyle("Server", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(25, 25)), layout.NewSpacer()), + uriLabel, + ), + layout.NewSpacer(), + } + + versionLabel := widget.NewLabel("") + rows = append(rows, []fyne.CanvasObject{ + widget.NewLabelWithStyle("Server Version", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(25, 25)), layout.NewSpacer()), + versionLabel, + ), + layout.NewSpacer(), + }...) + + chainIdKnownLabel := widget.NewLabelWithStyle("", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + chainIdIcon := canvas.NewImageFromResource(theme.WarningIcon()) + chainIdIcon.Hide() + chainIdLabel := widget.NewLabelWithStyle("", fyne.TextAlignLeading, fyne.TextStyle{Monospace: true}) + chainIdBox := widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(25, 25)), chainIdIcon), + chainIdKnownLabel, + chainIdLabel, + ) + rows = append(rows, []fyne.CanvasObject{ + widget.NewLabelWithStyle("Chain ID", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + chainIdBox, + layout.NewSpacer(), + }...) + + headTimeLabel := widget.NewLabelWithStyle("", fyne.TextAlignLeading, fyne.TextStyle{Monospace: true}) + headTimeLagLabel := widget.NewLabel("") + headTimeLagIcon := canvas.NewImageFromResource(theme.WarningIcon()) + rows = append(rows, []fyne.CanvasObject{ + widget.NewLabelWithStyle("Head Block Time", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(25, 25)), headTimeLagIcon), + headTimeLagLabel, + headTimeLabel, + ), + layout.NewSpacer(), + }...) + + headBlockLabel := widget.NewLabel("") + rows = append(rows, []fyne.CanvasObject{ + widget.NewLabelWithStyle("Head Block", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(25, 25)), layout.NewSpacer()), + headBlockLabel, + ), + layout.NewSpacer(), + }...) + + libLabel := widget.NewLabel("") + libWarnIcon := canvas.NewImageFromResource(theme.WarningIcon()) + libWarnIcon.Hide() + libWarnLabel := widget.NewLabelWithStyle("", fyne.TextAlignLeading, fyne.TextStyle{Italic: true}) + rows = append(rows, []fyne.CanvasObject{ + widget.NewLabelWithStyle("Last Irreversible Block", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(25, 25)), libWarnIcon), + libLabel, + libWarnLabel, + ), + layout.NewSpacer(), + }...) + + prodLabel := widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Monospace: true}) + prodAddrLabel := widget.NewLabelWithStyle("", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + prodUrl := widget.NewHyperlinkWithStyle("", &url.URL{}, fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}) + rows = append(rows, []fyne.CanvasObject{ + widget.NewLabelWithStyle("Current Producer", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}), + widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(25, 25)), layout.NewSpacer()), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize((W*40)/100, 35)), + fyne.NewContainerWithLayout(layout.NewGridLayout(3), + prodAddrLabel, + prodLabel, + prodUrl, + )), + ), + layout.NewSpacer(), + }...) + + histApiLabel := widget.NewLabelWithStyle("History API", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}) + histApiLabel.Hide() + histApiSp := layout.NewSpacer() + histApiSp.Hide() + histApiBox := widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(25, 25)), canvas.NewImageFromResource(theme.ConfirmIcon())), + widget.NewLabelWithStyle("History API is available", fyne.TextAlignLeading, fyne.TextStyle{Italic: true}), + ) + histApiBox.Hide() + rows = append(rows, []fyne.CanvasObject{ + histApiLabel, + histApiBox, + histApiSp, + }...) + + sizeLabel := widget.NewLabelWithStyle("DB Size", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}) + sizeLabel.Hide() + sizeBytesLabel := widget.NewLabel("") + sizeBytesLabel.Hide() + sizeWarnIcon := canvas.NewImageFromResource(theme.WarningIcon()) + sizeWarnIcon.Hide() + sizeWarnLabel := widget.NewLabel("Over 75% of RAM is used!") + sizeWarnLabel.Hide() + sizeSp := layout.NewSpacer() + sizeSp.Hide() + sizeBox := widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(25, 25)), sizeWarnIcon), + sizeBytesLabel, + sizeWarnLabel, + ) + rows = append(rows, []fyne.CanvasObject{ + sizeLabel, + sizeBox, + sizeSp, + }...) + + netApiLabel := widget.NewLabelWithStyle("Network API", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}) + netApiLabel.Hide() + netSp := layout.NewSpacer() + netSp.Hide() + netApiBox := widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(25, 25)), canvas.NewImageFromResource(theme.WarningIcon())), + widget.NewLabelWithStyle("Network API is Enabled!", fyne.TextAlignLeading, fyne.TextStyle{Italic: true}), + widget.NewLabel("allows anyone to control network connections"), + ) + netApiBox.Hide() + rows = append(rows, []fyne.CanvasObject{ + netApiLabel, + netApiBox, + netSp, + }...) + + prodApiLabel := widget.NewLabelWithStyle("Producer API", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true}) + prodApiLabel.Hide() + prodApiBox := widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(25, 25)), canvas.NewImageFromResource(theme.WarningIcon())), + widget.NewLabelWithStyle("Producer API is Enabled!", fyne.TextAlignLeading, fyne.TextStyle{Italic: true}), + widget.NewLabel("allows anyone to control the producer plugin"), + ) + prodApiBox.Hide() + prodApiSp := layout.NewSpacer() + prodApiSp.Hide() + rows = append(rows, []fyne.CanvasObject{ + prodApiLabel, + prodApiBox, + prodApiSp, + }...) + + container := fyne.NewContainerWithLayout(layout.NewGridLayout(3)) + + pp := message.NewPrinter(language.AmericanEnglish) + dbRefresh := func() { + s, err := Api.GetDBSize() + if err != nil { + return + } + if s.Size != 0 { + sizeBytesLabel.SetText(pp.Sprintf("RAM used %d MB", (s.UsedBytes/1024)/1024)) + sizeBytesLabel.Show() + if (s.UsedBytes/s.Size)*100 > 75 { + sizeWarnLabel.Show() + } else if !sizeWarnLabel.Hidden { + sizeWarnLabel.Hide() + } + } + } + + update := func() { + prods = make(map[string]*prodInfo) + for { + time.Sleep(3 * time.Second) + if Api == nil || Api.BaseURL == "" { + continue + } + uriLabel.SetText(Api.BaseURL) + producers, err := Api.GetFioProducers() + if err != nil { + continue + } + for _, prod := range producers.Producers { + ur := prod.Url + if !strings.HasPrefix(ur, "http") { + ur = "https://" + prod.Url + } + u, err := url.Parse(ur) + if err != nil { + u, _ = url.Parse("http://127.0.0.1") + } + prods[string(prod.Owner)] = &prodInfo{ + address: string(prod.FioAddress), + url: u, + } + } + + // now update list of available APIs + resp, err := Api.HttpClient.Post(Api.BaseURL+"/v1/node/get_supported_apis", "application/json", nil) + if err != nil { + errs.ErrChan <- "fetchApis: " + err.Error() + continue + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + errs.ErrChan <- "fetchApis: " + err.Error() + continue + } + supported := &SupportedApis{} + err = json.Unmarshal(body, supported) + if err != nil { + errs.ErrChan <- "fetchApis: " + err.Error() + continue + } + prodApiLabel.Hide() + prodApiBox.Hide() + prodApiSp.Hide() + netApiLabel.Hide() + netApiBox.Hide() + netSp.Hide() + histApiLabel.Hide() + histApiBox.Hide() + histApiSp.Hide() + sizeLabel.Hide() + sizeBox.Hide() + sizeWarnLabel.Hide() + sizeWarnIcon.Hide() + for _, a := range supported.Apis { + switch { + case strings.HasPrefix(a, "/v1/producer"): + prodApiLabel.Show() + prodApiBox.Show() + prodApiSp.Show() + case strings.HasPrefix(a, "/v1/net"): + netApiLabel.Show() + netApiBox.Show() + netSp.Show() + case strings.HasPrefix(a, "/v1/db"): + dbRefresh() + sizeLabel.Show() + sizeBox.Show() + sizeSp.Show() + case strings.HasPrefix(a, "/v1/hist"): + histApiLabel.Show() + histApiBox.Show() + histApiSp.Show() + } + } + return + } + } + + go func() { + tick := time.NewTicker(5 * time.Second) + var lastHead uint32 + for { + select { + case <-tick.C: + if !sizeLabel.Hidden { + go dbRefresh() + } + case <-reconnected: + go update() + case si := <-info: + go func() { + headBlockLabel.SetText(pp.Sprintf("%d", si.Info.HeadBlockNum)) + if lastHead == si.Info.HeadBlockNum { + headTimeLagIcon.Show() + } else { + lastHead = si.Info.HeadBlockNum + if !headTimeLagIcon.Hidden { + headTimeLagIcon.Hide() + } + } + libLabel.SetText(pp.Sprintf("%d (%d)", si.Info.LastIrreversibleBlockNum, int64(si.Info.LastIrreversibleBlockNum)-int64(si.Info.HeadBlockNum))) + if si.Info.HeadBlockNum-si.Info.LastIrreversibleBlockNum >= 450 { + libWarnIcon.Show() + libWarnLabel.SetText(fmt.Sprintf("Last Irreversible Block is %d blocks behind", int64(si.Info.HeadBlockNum)-int64(si.Info.LastIrreversibleBlockNum))) + } else if !libWarnIcon.Hidden { + libWarnIcon.Hide() + libWarnLabel.SetText("") + } + chainIdLabel.SetText(si.Info.ChainID.String()) + versionLabel.SetText(si.Info.ServerVersionString) + headTimeLabel.SetText(si.Info.HeadBlockTime.Local().String()) + if si.Info.HeadBlockTime.Before(time.Now().Add(-time.Minute)) { + headTimeLagIcon.Show() + headTimeLagLabel.Show() + headTimeLagLabel.SetText(pp.Sprintf("%v", time.Now().Sub(si.Info.HeadBlockTime.Time))) + } else if !headTimeLagLabel.Hidden { + headTimeLagIcon.Hide() + headTimeLagLabel.Hide() + } + if knownChains[si.Info.ChainID.String()] != "" { + chainIdKnownLabel.SetText(knownChains[si.Info.ChainID.String()]) + if !chainIdIcon.Hidden { + chainIdIcon.Hide() + } + } else { + chainIdIcon.Show() + chainIdKnownLabel.SetText("Unknown Chain") + } + prodLabel.SetText(string(si.Info.HeadBlockProducer)) + prodUrl.SetURL(nil) + if prods[string(si.Info.HeadBlockProducer)] != nil { + short := strings.Replace(prods[string(si.Info.HeadBlockProducer)].url.String(), "https://", "", -1) + short = strings.Replace(short, "http://", "", -1) + short = strings.Split(short, "/")[0] + prodUrl.SetURL(prods[string(si.Info.HeadBlockProducer)].url) + prodUrl.SetText(short) + prodAddrLabel.SetText(prods[string(si.Info.HeadBlockProducer)].address) + } + }() + } + } + }() + + for _, canv := range rows { + container.AddObject(canv) + } + return container +} diff --git a/settings-window.go b/settings-window.go new file mode 100644 index 0000000..8acb77e --- /dev/null +++ b/settings-window.go @@ -0,0 +1,580 @@ +package cryptonym + +import ( + "errors" + "fmt" + "fyne.io/fyne" + "fyne.io/fyne/canvas" + "fyne.io/fyne/dialog" + "fyne.io/fyne/layout" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + fioassets "github.com/blockpane/cryptonym/assets" + errs "github.com/blockpane/cryptonym/errLog" + "net/url" + "os" + "strconv" +) + +const settingsTitle = "Cryptonym Settings" + +var MainnetApi = []string{ + // These allow TLS 1.0, excluding + //"https://fio.eoscannon.io", + //"https://fio-mainnet.eosblocksmith.io", + //"https://fio.eos.barcelona", + //"https://fio.eosargentina.io", + //"https://api.fio.services", + + // Does not allow access to get_supported_apis endpoint: + //"https://fioapi.nodeone.io", + //"https://fio.maltablock.org", + + "https://fio.eosdac.io", //ok + "https://fio.eosphere.io", //ok + "https://fio.eosrio.io", //ok + "https://fio.eosusa.news", //ok + "https://api.fio.alohaeos.com", //ok + "https://fio.genereos.io", //ok + "https://fio.greymass.com", //ok + "https://api.fio.eosdetroit.io", // ok + "https://fio.zenblocks.io", // ok + "https://api.fio.currencyhub.io", // ok + "https://fio.cryptolions.io", // ok + "https://fio.eosdublin.io", // ok + "https://api.fio.greeneosio.com", // ok + "https://api.fiosweden.org", // ok + "https://fio.eu.eosamsterdam.net", //ok + "https://fioapi.ledgerwise.io", // sort of ok, lots of errors + "https://fio.acherontrading.com", //ok +} + +func SettingsWindow() { + if PasswordVisible { + return + } + w := App.NewWindow(settingsTitle) + w.Resize(fyne.NewSize(600, 800)) + w.SetOnClosed(func() { + for _, w := range fyne.CurrentApp().Driver().AllWindows() { + if w.Title() == AppTitle { + w.RequestFocus() + return + } + } + }) + + if Settings == nil || Settings.Server == "" { + Settings = DefaultSettings() + } + + var filename string + updateFieldsFromSettings := func() {} + settingsFileLabel := widget.NewLabel("") + serverEntry := widget.NewEntry() + proxyEntry := widget.NewEntry() + widthEntry := widget.NewEntry() + heightEntry := widget.NewEntry() + tpidEntry := widget.NewEntry() + advanced := widget.NewCheck("Enable Advanced (expert) Features", func(b bool) { + Settings.AdvancedFeatures = b + if b { + _ = os.Setenv("ADVANCED", "true") + return + } + _ = os.Setenv("ADVANCED", "") + }) + if os.Getenv("ADVANCED") != "" { + advanced.SetChecked(true) + } + + sizeRow := widget.NewHBox( + layout.NewSpacer(), + widget.NewLabel("Initial window size: "), + widthEntry, + widget.NewLabel(" X "), + heightEntry, + widget.NewLabelWithStyle(" (requires restart)", fyne.TextAlignCenter, fyne.TextStyle{Italic: true}), + layout.NewSpacer(), + ) + + advancedRow := widget.NewHBox( + layout.NewSpacer(), + advanced, + layout.NewSpacer(), + ) + + themeSelect := widget.NewSelect([]string{"Dark", "Darker", "Grey", "Light"}, func(s string) { + switch s { + case "Dark": + fyne.CurrentApp().Settings().SetTheme(CustomTheme()) + RepaintChan <- true + case "Light": + fyne.CurrentApp().Settings().SetTheme(ExLightTheme().ToFyneTheme()) + RepaintChan <- true + case "Darker": + fyne.CurrentApp().Settings().SetTheme(DarkerTheme().ToFyneTheme()) + RepaintChan <- true + case "Grey": + fyne.CurrentApp().Settings().SetTheme(ExGreyTheme().ToFyneTheme()) + RepaintChan <- true + } + WinSettings.T = s + RefreshQr <- true + }) + + defKeyEntry := widget.NewPasswordEntry() + defKeyEntry.SetPlaceHolder("WIF Private Key") + defKeyDescEntry := widget.NewEntry() + defKeyDescEntry.SetPlaceHolder("Description") + + favKey2Entry := widget.NewPasswordEntry() + favKey2Entry.SetPlaceHolder("WIF Private Key") + favKey2DescEntry := widget.NewEntry() + favKey2DescEntry.SetPlaceHolder("Description") + + favKey3Entry := widget.NewPasswordEntry() + favKey3Entry.SetPlaceHolder("WIF Private Key") + favKey3DescEntry := widget.NewEntry() + favKey3DescEntry.SetPlaceHolder("Description") + + favKey4Entry := widget.NewPasswordEntry() + favKey4Entry.SetPlaceHolder("WIF Private Key") + favKey4DescEntry := widget.NewEntry() + favKey4DescEntry.SetPlaceHolder("Description") + + msigDefaultEntry := widget.NewEntry() + msigDefaultEntry.SetPlaceHolder("abcdefghi") + + defaultsButton := widget.NewButton("Load Defaults", func() { + Settings = DefaultSettings() + updateFieldsFromSettings() + }) + + passEntry := widget.NewPasswordEntry() + passConfirm := widget.NewPasswordEntry() + saveButton := widget.NewButtonWithIcon("Save", theme.DocumentSaveIcon(), func() { + if passEntry.Text == "" { + return + } + updateSize := true + width, err := strconv.Atoi(widthEntry.Text) + if err != nil { + updateSize = false + errs.ErrChan <- "Settings: got invalid width setting for window size" + } + height, err := strconv.Atoi(heightEntry.Text) + if err != nil { + updateSize = false + errs.ErrChan <- "Settings: got invalid height setting for window size" + } + + Settings.Server = serverEntry.Text + Settings.Proxy = proxyEntry.Text + Settings.DefaultKey = defKeyEntry.Text + Settings.DefaultKeyDesc = defKeyDescEntry.Text + Settings.FavKey2 = favKey2Entry.Text + Settings.FavKey2Desc = favKey2DescEntry.Text + Settings.FavKey3 = favKey3Entry.Text + Settings.FavKey3Desc = favKey3DescEntry.Text + Settings.FavKey4 = favKey4Entry.Text + Settings.FavKey4Desc = favKey4DescEntry.Text + Settings.MsigAccount = msigDefaultEntry.Text + Settings.AdvancedFeatures = advanced.Checked + if Settings.AdvancedFeatures { + _ = os.Setenv("ADVANCED", "true") + } + Settings.Tpid = tpidEntry.Text + ok, err := SaveEncryptedSettings(passEntry.Text, Settings) + if ok { + if updateSize { + if ok := saveWindowSettings(width, height, themeSelect.Selected); !ok { + errs.ErrChan <- "Settings: was unable to save window size." + } + } + SettingsLoaded <- Settings + w.Close() + return + } + msg := "Could not save config file! " + if err != nil { + msg = msg + err.Error() + } + myWindow := func() fyne.Window { + for _, window := range App.Driver().AllWindows() { + if window.Title() == settingsTitle { + return window + } + } + return App.Driver().AllWindows()[0] + } + dialog.ShowError(errors.New(msg), myWindow()) + }) + saveButton.Disable() + + warningIcon := fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(24, 24)), + canvas.NewImageFromResource(theme.WarningIcon()), + ) + matchWarningIcon := fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(24, 24)), + canvas.NewImageFromResource(theme.WarningIcon()), + ) + matchWarningIcon.Hide() + passEntry.SetPlaceHolder("please enter a password") + passEntry.OnChanged = func(s string) { + if len(s) < 8 { + warningIcon.Show() + saveButton.Disable() + return + } + warningIcon.Hide() + if passConfirm.Text != s { + matchWarningIcon.Show() + saveButton.Disable() + return + } + matchWarningIcon.Hide() + warningIcon.Hide() + saveButton.Enable() + } + passConfirm.SetPlaceHolder("confirm the password") + passConfirm.OnChanged = func(s string) { + if len(passEntry.Text) < 8 { + warningIcon.Show() + saveButton.Disable() + return + } + warningIcon.Hide() + if passEntry.Text != s { + matchWarningIcon.Show() + saveButton.Disable() + return + } + matchWarningIcon.Hide() + warningIcon.Hide() + saveButton.Enable() + } + + cancelButton := widget.NewButton("Cancel", func() { + w.Close() + }) + + updateFieldsFromSettings = func() { + heightEntry.SetText(fmt.Sprint(H)) + widthEntry.SetText(fmt.Sprint(W)) + serverEntry.SetText(Settings.Server) + proxyEntry.SetText(Settings.Proxy) + defKeyEntry.SetText(Settings.DefaultKey) + defKeyDescEntry.SetText(Settings.DefaultKeyDesc) + favKey2Entry.SetText(Settings.FavKey2) + favKey2DescEntry.SetText(Settings.FavKey2Desc) + favKey3Entry.SetText(Settings.FavKey3) + favKey3DescEntry.SetText(Settings.FavKey3Desc) + favKey4Entry.SetText(Settings.FavKey4) + favKey4DescEntry.SetText(Settings.FavKey4Desc) + msigDefaultEntry.SetText(Settings.MsigAccount) + tpidEntry.SetText(Settings.Tpid) + advanced.SetChecked(Settings.AdvancedFeatures) + if Settings.AdvancedFeatures { + _ = os.Setenv("ADVANCED", "true") + } + themeSelect.Selected = WinSettings.T + themeSelect.Refresh() + } + + configDir, err := os.UserConfigDir() + if err != nil { + w.SetContent(widget.NewLabel("Unable to determine config directory: " + err.Error())) + } else { + filename = fmt.Sprintf("%s%c%s%c%s", configDir, os.PathSeparator, settingsDir, os.PathSeparator, settingsFileName) + settingsFileLabel.SetText(filename) + w.SetContent( + widget.NewVBox( + fyne.NewContainerWithLayout(layout.NewGridLayoutWithRows(2), + fyne.NewContainerWithLayout(layout.NewGridLayout(2), + widget.NewHBox(layout.NewSpacer(), warningIcon, widget.NewLabel("Password for encryption: ")), + passEntry, + ), + fyne.NewContainerWithLayout(layout.NewGridLayout(2), + widget.NewHBox(layout.NewSpacer(), matchWarningIcon, widget.NewLabel("Confirm: ")), + passConfirm, + ), + ), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(50, 50)), layout.NewSpacer()), + widget.NewHBox( + layout.NewSpacer(), + widget.NewLabelWithStyle("Preferred Server", fyne.TextAlignTrailing, fyne.TextStyle{}), + serverEntry, layout.NewSpacer(), + ), + //widget.NewHBox( + // layout.NewSpacer(), + // widget.NewLabelWithStyle("Preferred Proxy", fyne.TextAlignTrailing, fyne.TextStyle{}), + // proxyEntry, layout.NewSpacer(), + //), + widget.NewHBox( + layout.NewSpacer(), + widget.NewLabelWithStyle("Default Theme", fyne.TextAlignTrailing, fyne.TextStyle{}), + themeSelect, layout.NewSpacer(), + ), + sizeRow, + advancedRow, + widget.NewHBox( + layout.NewSpacer(), + widget.NewLabelWithStyle("Preferred MSIG Account for Proposals", fyne.TextAlignTrailing, fyne.TextStyle{}), + msigDefaultEntry, layout.NewSpacer(), + ), + widget.NewHBox( + layout.NewSpacer(), + widget.NewLabelWithStyle("Preferred TPID", fyne.TextAlignTrailing, fyne.TextStyle{}), + tpidEntry, layout.NewSpacer(), + ), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(50, 50)), layout.NewSpacer()), + widget.NewHBox( + widget.NewLabelWithStyle("*Quick Load Key", fyne.TextAlignLeading, fyne.TextStyle{}), + widget.NewHBox( + defKeyEntry, layout.NewSpacer(), defKeyDescEntry, + ), + layout.NewSpacer(), + ), + widget.NewHBox( + widget.NewLabelWithStyle(" Quick Load Key ", fyne.TextAlignLeading, fyne.TextStyle{}), + widget.NewHBox( + favKey2Entry, layout.NewSpacer(), favKey2DescEntry, + ), + layout.NewSpacer(), + ), + widget.NewHBox( + widget.NewLabelWithStyle(" Quick Load Key ", fyne.TextAlignLeading, fyne.TextStyle{}), + widget.NewHBox( + favKey3Entry, layout.NewSpacer(), favKey3DescEntry, + ), + layout.NewSpacer(), + ), + widget.NewHBox( + widget.NewLabelWithStyle(" Quick Load Key ", fyne.TextAlignLeading, fyne.TextStyle{}), + widget.NewHBox( + favKey4Entry, layout.NewSpacer(), favKey4DescEntry, + ), + layout.NewSpacer(), + ), + widget.NewLabelWithStyle("* - Default Key", fyne.TextAlignCenter, fyne.TextStyle{}), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(50, 50)), layout.NewSpacer()), + layout.NewSpacer(), + widget.NewHBox(layout.NewSpacer(), saveButton, defaultsButton, cancelButton, layout.NewSpacer()), + widget.NewHBox( + layout.NewSpacer(), + widget.NewLabelWithStyle("Config File Location", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), + settingsFileLabel, + layout.NewSpacer(), + ), + ), + ) + } + updateFieldsFromSettings() + w.Show() +} + +type unlockEntry struct { + widget.Entry + Action func(bool) +} + +func (e *unlockEntry) onEnter() { + e.Action(true) +} + +func newUnlockEntry(f func(bool)) *unlockEntry { + entry := &unlockEntry{ + Entry: widget.Entry{Password: true}, + Action: f, + } + entry.ExtendBaseWidget(entry) + return entry +} + +func (e *unlockEntry) KeyDown(key *fyne.KeyEvent) { + switch key.Name { + case fyne.KeyReturn: + e.onEnter() + default: + e.Entry.KeyDown(key) + } +} + +func PromptForPassword() { + if PasswordVisible { + return + } + PasswordVisible = true + defer func() { PasswordVisible = false }() + ok, fileLen, _, err := LoadEncryptedSettings("") + if !ok && fileLen == 0 && err == nil { + //no config file to load, just continue. + return + } + var serverOverride string + mainnetSelect := widget.NewSelect(MainnetApi, func(s string) { + if s != "" { + serverOverride = s + } + }) + mainnetSelect.PlaceHolder = "Override Saved Server" + deferCheck := widget.NewCheck("defer connect", func(b bool) { + if b { + mainnetSelect.Hide() + return + } + mainnetSelect.Show() + }) + resultLabel := widget.NewLabelWithStyle("", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) + resultBox := widget.NewHBox( + layout.NewSpacer(), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(35, 35)), + canvas.NewImageFromResource(theme.WarningIcon()), + ), + resultLabel, + layout.NewSpacer(), + ) + resultBox.Hide() + passCallBack := func(b bool) {} + pop := &widget.PopUp{} + + passEntry := newUnlockEntry(func(b bool) {}) + passEntry.SetPlaceHolder("Enter your password") + passEntry.OnChanged = func(s string) { + resultBox.Hide() + } + cancelButton := widget.NewButtonWithIcon(" Defaults ", theme.CancelIcon(), func() { + pop.Hide() + }) + submitButton := widget.NewButtonWithIcon(" Load ", theme.ConfirmIcon(), func() { + passCallBack(true) + }) + + link, _ := url.Parse("https://fioprotocol.io/") + contents := widget.NewVBox( + widget.NewLabelWithStyle("Enter your password to load saved settings", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}), + widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(150, 250)), + widget.NewVBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(140, 20)), layout.NewSpacer()), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(140, 140)), + canvas.NewImageFromResource(fioassets.NewFioLogoResource()), + ), + widget.NewHyperlinkWithStyle("fioprotocol.io", link, fyne.TextAlignCenter, fyne.TextStyle{}), + layout.NewSpacer(), + ), + ), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(400, 280)), + widget.NewVBox( + layout.NewSpacer(), + widget.NewHBox(layout.NewSpacer(), resultBox, widget.NewLabel(""), layout.NewSpacer()), + layout.NewSpacer(), + widget.NewHBox(layout.NewSpacer(), passEntry, layout.NewSpacer()), + layout.NewSpacer(), + fyne.NewContainerWithLayout(layout.NewGridLayout(4), + layout.NewSpacer(), cancelButton, submitButton, layout.NewSpacer(), + ), + widget.NewHBox(deferCheck), + widget.NewHBox(mainnetSelect), + widget.NewHBox(widget.NewLabel(" ")), + ), + ), + ), + ) + passCallBack = func(b bool) { + if !b { + return + } + passEntry.Disable() + defer passEntry.Enable() + ok, _, newConfig, err := LoadEncryptedSettings(passEntry.Text) + switch { + case err != nil && err.Error() == "cipher: message authentication failed": + resultLabel.SetText("Incorrect Password") + resultBox.Show() + case err != nil: + resultLabel.SetText(err.Error()) + resultBox.Show() + case !ok: + resultLabel.SetText("Could not decrypt settings") + resultBox.Show() + case ok: + Settings.Server = newConfig.Server + if deferCheck.Checked { + Settings.Server = "" + } else if serverOverride != "" { + Settings.Server = serverOverride + } + Settings.Proxy = newConfig.Proxy + Settings.DefaultKey = newConfig.DefaultKey + Settings.DefaultKeyDesc = newConfig.DefaultKeyDesc + Settings.FavKey2 = newConfig.FavKey2 + Settings.FavKey2Desc = newConfig.FavKey2Desc + Settings.FavKey3 = newConfig.FavKey3 + Settings.FavKey3Desc = newConfig.FavKey3Desc + Settings.FavKey4 = newConfig.FavKey4 + Settings.FavKey4Desc = newConfig.FavKey4Desc + Settings.MsigAccount = newConfig.MsigAccount + Settings.Tpid = newConfig.Tpid + + SettingsLoaded <- Settings + pop.Hide() + return + } + //show() + } + pop = widget.NewModalPopUp(contents, Win.Canvas()) + passEntry.Action = passCallBack +} + +type EnterEntry struct { + widget.Entry + Action func() +} + +func (e *EnterEntry) onEnter() { + e.Action() +} + +func NewEnterEntry(f func()) *EnterEntry { + entry := &EnterEntry{ + Entry: widget.Entry{}, + Action: f, + } + entry.ExtendBaseWidget(entry) + return entry +} + +func (e *EnterEntry) KeyDown(key *fyne.KeyEvent) { + switch key.Name { + case fyne.KeyReturn: + e.onEnter() + default: + e.Entry.KeyDown(key) + } +} + +type EnterSelectEntry struct { + widget.SelectEntry + Action func() +} + +func (e *EnterSelectEntry) onEnter() { + e.Action() +} + +func NewEnterSelectEntry(entries []string, f func()) *EnterSelectEntry { + entry := &EnterSelectEntry{Action: f} + entry.SetOptions(entries) + entry.ExtendBaseWidget(entry) + return entry +} + +func (e *EnterSelectEntry) KeyDown(key *fyne.KeyEvent) { + switch key.Name { + case fyne.KeyReturn: + e.onEnter() + default: + e.Entry.KeyDown(key) + } +} diff --git a/settings.go b/settings.go new file mode 100644 index 0000000..a1b4e25 --- /dev/null +++ b/settings.go @@ -0,0 +1,287 @@ +package cryptonym + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "encoding/gob" + "errors" + "fmt" + errs "github.com/blockpane/cryptonym/errLog" + "golang.org/x/crypto/pbkdf2" + "os" + "time" +) + +const ( + settingsDir = "com.blockpane.cryptonym" + settingsFileName = "cryptonym.dat" +) + +type FioSettings struct { + Server string `json:"server"` + Proxy string `json:"proxy"` + + DefaultKey string `json:"default_key"` + DefaultKeyDesc string `json:"default_key_desc"` + FavKey2 string `json:"fav_key_2"` + FavKey2Desc string `json:"fav_key_2_desc"` + FavKey3 string `json:"fav_key_3"` + FavKey3Desc string `json:"fav_key_3_desc"` + FavKey4 string `json:"fav_key_4"` + FavKey4Desc string `json:"fav_key_4_desc"` + + MsigAccount string `json:"msig_account"` + Tpid string `json:"tpid"` + + AdvancedFeatures bool `json:"advanced_features"` + + // future: + KeosdAddress string `json:"keosd_address"` + KeosdPassword string `json:"keosd_password"` +} + +func DefaultSettings() *FioSettings { + return &FioSettings{ + Server: "http://127.0.0.1:8888", + Proxy: "http://127.0.0.1:8080", + DefaultKey: "5JBbUG5SDpLWxvBKihMeXLENinUzdNKNeozLas23Mj6ZNhz3hLS", + DefaultKeyDesc: "devnet - vote 1", + FavKey2: "5KC6Edd4BcKTLnRuGj2c8TRT9oLuuXLd3ZuCGxM9iNngc3D8S93", + FavKey2Desc: "devnet - vote 2", + FavKey3: "5KQ6f9ZgUtagD3LZ4wcMKhhvK9qy4BuwL3L1pkm6E2v62HCne2R", + FavKey3Desc: "devnet - bp1", + FavKey4: "5HwvMtAEd7kwDPtKhZrwA41eRMdFH5AaBKPRim6KxkTXcg5M9L5", + FavKey4Desc: "devnet - locked 1", + MsigAccount: "eosio", + Tpid: "tpid@blockpane", + } +} + +func EncryptSettings(set *FioSettings, salt []byte, password string) (encrypted []byte, err error) { + if password == "" { + return nil, errors.New("invalid password supplied") + } + + // if a salt isn't supplied, create one, note: using crypto/rand NOT math/rand, has better entropy + if salt == nil || len(salt) != 12 || bytes.Equal(salt, bytes.Repeat([]byte{0}, 12)) { + salt = make([]byte, 12) + if _, e := rand.Read(salt); e != nil { + errs.ErrChan <- "EncryptSettings: " + e.Error() + return nil, err + } + } + + // prepend the salt to our buffer: + crypted := bytes.NewBuffer(nil) + crypted.Write(salt) + + // convert our settings to a binary struct + data := bytes.NewBuffer(nil) + g := gob.NewEncoder(data) + err = g.Encode(set) + if err != nil { + errs.ErrChan <- "EncryptSettings: " + err.Error() + return nil, err + } + + // password-based key derivation, 48 bytes, 1st 32 is aes key, last 12 mac key + key := pbkdf2.Key([]byte(password), salt, 12*1024, 48, sha256.New) + + // aes 256 + cb, err := aes.NewCipher(key[:32]) + if err != nil { + errs.ErrChan <- "EncryptSettings: " + err.Error() + return nil, err + } + + //pkcs7 pad the plaintext + plaintext := append(data.Bytes(), func() []byte { + padLen := cb.BlockSize() - (len(data.Bytes()) % cb.BlockSize()) + pad := make([]byte, padLen) + for i := range pad { + pad[i] = uint8(padLen) + } + return pad + }()...) + + // use an authenticated (Galois) cipher + gcm, err := cipher.NewGCM(cb) + if err != nil { + errs.ErrChan <- "EncryptSettings: " + err.Error() + return nil, err + } + // since the nonce should be secret and is derived via pbkdf, don't save it, write ciphertext directly to the buffer + l, err := crypted.Write(gcm.Seal(nil, key[len(key)-gcm.NonceSize():], plaintext, nil)) + if err != nil { + errs.ErrChan <- "EncryptSettings: " + err.Error() + return nil, err + } else if l < len(plaintext)+gcm.Overhead() { + err = errors.New("unable to encrypt data, did not get correct size") + errs.ErrChan <- "EncryptSettings: " + err.Error() + return nil, err + } + + // final paranoid sanity check that we got the correct amount of data back + if len(crypted.Bytes()) != len(salt)+len(plaintext)+gcm.Overhead() { + return nil, errors.New("unable to encrypt data, resulting ciphertext was wrong size") + } + + return crypted.Bytes(), nil +} + +func DecryptSettings(encrypted []byte, password string) (settings *FioSettings, err error) { + key := pbkdf2.Key([]byte(password), encrypted[:12], 12*1024, 48, sha256.New) + cb, err := aes.NewCipher(key[:32]) + if err != nil { + errs.ErrChan <- "DecryptSettings: " + err.Error() + return nil, err + } + gcm, err := cipher.NewGCM(cb) + if err != nil { + errs.ErrChan <- "DecryptSettings: " + err.Error() + return nil, err + } + plain, err := gcm.Open(nil, key[len(key)-gcm.NonceSize():], encrypted[12:], nil) + if err != nil { + errs.ErrChan <- "DecryptSettings: " + err.Error() + return nil, err + } + padLen := int(plain[len(plain)-1]) + if len(plain) <= padLen { + err = errors.New("invalid padding, plaintext smaller than pkcs7 pad size") + errs.ErrChan <- "DecryptSettings: " + err.Error() + return nil, err + } + g := gob.NewDecoder(bytes.NewReader(plain[:len(plain)-padLen])) + err = g.Decode(&settings) + if err != nil { + errs.ErrChan <- "DecryptSettings: " + err.Error() + return nil, err + } + if settings.AdvancedFeatures { + _ = os.Setenv("ADVANCED", "true") + } + return +} + +const ( + settingsRead uint8 = iota + settingsSave +) + +// will never return a nil settings +func LoadEncryptedSettings(password string) (ok bool, fileLength int, settings *FioSettings, err error) { + ok, encrypted, err := readWriteSettings(settingsRead, nil) + if !ok { + return ok, len(encrypted), DefaultSettings(), err + } + decrypted, err := DecryptSettings(encrypted, password) + if err != nil { + return false, len(encrypted), DefaultSettings(), err + } + if decrypted == nil { + return false, len(encrypted), DefaultSettings(), errors.New("unknown error decrypting config, got empty config") + } + return true, len(encrypted), decrypted, nil +} + +func SaveEncryptedSettings(password string, settings *FioSettings) (ok bool, err error) { + encrypted, err := EncryptSettings(settings, nil, password) + if err != nil { + return false, err + } + ok, _, err = readWriteSettings(settingsSave, encrypted) + return +} + +func MkDir() (ok bool, err error) { + configDir, err := os.UserConfigDir() + if err != nil { + return false, nil + } + dirName := fmt.Sprintf("%s%c%s", configDir, os.PathSeparator, settingsDir) + var createDir bool + dirStat, err := os.Stat(dirName) + if _, ok := err.(*os.PathError); ok { + if e := os.Mkdir(dirName, os.FileMode(0700)); e != nil { + return false, err + } + createDir = true + } else if err != nil { + errs.ErrChan <- "MkDir: " + err.Error() + return false, err + } + if dirStat == nil && !createDir { + return false, errors.New("unknown error creating configuration directory") + } else if !createDir && !dirStat.IsDir() { + err = errors.New("cannot create directory, file already exists") + errs.ErrChan <- "readWriteSettings: " + err.Error() + return false, err + } + return true, nil +} + +func readWriteSettings(action uint8, fileBytes []byte) (ok bool, content []byte, err error) { + if action != settingsRead && action != settingsSave { + return false, nil, errors.New("invalid action") + } + configDir, err := os.UserConfigDir() + if err != nil { + errs.ErrChan <- err.Error() + return false, nil, err + } + _, err = MkDir() + if err != nil { + return false, nil, err + } + + fileName := fmt.Sprintf("%s%c%s%c%s", configDir, os.PathSeparator, settingsDir, os.PathSeparator, settingsFileName) + f := &os.File{} + fileStat, err := os.Stat(fileName) + if _, ok := err.(*os.PathError); ok { + if action == settingsRead { + return false, nil, nil + } + } else if err != nil { + return false, nil, err + } + + // handle request to read file: + if fileStat != nil && fileStat.Size() > 0 && action == settingsRead { + f, err := os.OpenFile(fileName, os.O_RDONLY, 600) + if err != nil { + return false, nil, err + } + defer f.Close() + contents := make([]byte, int(fileStat.Size())) + n, err := f.Read(contents) + if err != nil { + return false, nil, err + } + if int64(n) != fileStat.Size() { + return false, nil, errors.New("could not read file, truncated result") + } + return true, contents, nil + } else if action == settingsRead { + return false, nil, nil + } + + // otherwise write the new config file: + f, err = os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return false, nil, err + } + defer f.Close() + _ = f.SetWriteDeadline(time.Now().Add(time.Second)) + n, err := f.Write(fileBytes) + if err != nil { + return false, nil, err + } + if n != len(fileBytes) { + return false, nil, errors.New("did not write entire file, output truncated") + } + return true, nil, nil +} diff --git a/settings_test.go b/settings_test.go new file mode 100644 index 0000000..9f8b450 --- /dev/null +++ b/settings_test.go @@ -0,0 +1,22 @@ +package cryptonym + +import ( + "fmt" + "testing" +) + +func TestEncryptSettings(t *testing.T) { + s := DefaultSettings() + encrypted, err := EncryptSettings(s, nil, "password") + if err != nil { + t.Error(err) + return + } + decrypted, err := DecryptSettings(encrypted, "password") + if err != nil { + t.Error(err) + return + } + fmt.Printf("%#v\n", decrypted) + +} diff --git a/table.go b/table.go new file mode 100644 index 0000000..fb97484 --- /dev/null +++ b/table.go @@ -0,0 +1,551 @@ +package cryptonym + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "fyne.io/fyne" + "fyne.io/fyne/layout" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go" + "github.com/fioprotocol/fio-go/eos" + "math" + "sort" + "strconv" + "sync" +) + +type FioActions struct { + sync.RWMutex + Index []string + Actions map[string][]string +} + +type contracts struct { + Owner string `json:"owner"` +} + +func GetAccountSummary(api *fio.API) (*FioActions, error) { + + table, err := api.GetTableRows(eos.GetTableRowsRequest{ + Code: "eosio", + Scope: "eosio", + Table: "abihash", + Limit: uint32(math.MaxUint32), + JSON: true, + }) + if err != nil { + return nil, err + } + result := make([]contracts, 0) + err = json.Unmarshal(table.Rows, &result) + if err != nil { + return nil, err + } + // FIXME: reading the abihash table isn't returning everything because of how the chain is boostrapped. + // for now, appending a list of known contracts if not found :( + defaults := []contracts{ + {Owner: "eosio"}, + //{Owner: "eosio.bios"}, + {Owner: "eosio.msig"}, + {Owner: "eosio.wrap"}, + {Owner: "fio.address"}, + //{Owner: "fio.common"}, + {Owner: "fio.fee"}, + {Owner: "fio.foundatn"}, + {Owner: "fio.reqobt"}, + //{Owner: "fio.system"}, + {Owner: "fio.token"}, + {Owner: "fio.tpid"}, + {Owner: "fio.treasury"}, + //{Owner: "fio.whitelst"}, + } + for _, def := range defaults { + func() { + for _, found := range result { + if def.Owner == found.Owner { + return + } + } + result = append(result, def) + }() + } + + actions := FioActions{ + Actions: make(map[string][]string), + } + + // sort by account name + func() { + sorted := make([]string, 0) + resultTemp := make([]contracts, 0) + sortMap := make(map[string]int) + for i, c := range result { + sorted = append(sorted, c.Owner) + sortMap[c.Owner] = i + } + sort.Strings(sorted) + for _, ascending := range sorted { + resultTemp = append(resultTemp, result[sortMap[ascending]]) + } + result = resultTemp + }() + + for _, name := range result { + bi, err := api.GetABI(eos.AccountName(name.Owner)) + if err != nil { + errs.ErrChan <- "problem while loading abi: " + err.Error() + continue + } + actionList := make(map[string]bool, 0) + for _, name := range bi.ABI.Actions { + actionList[string(name.Name)] = true + } + if actions.Actions[name.Owner] == nil { + actions.Actions[name.Owner] = make([]string, 0) + actions.Index = append(actions.Index, name.Owner) + } + for a := range actionList { + actions.Actions[name.Owner] = append(actions.Actions[name.Owner], a) + } + func() { + tableList := make([]string, 0) + for _, table := range bi.ABI.Tables { + tableList = append(tableList, string(table.Name)) + } + if len(tableList) == 0 { + return + } + TableIndex.Add(name.Owner, tableList) + }() + } + return &actions, nil +} + +type TableBrowserIndex struct { + mux sync.RWMutex + tables map[string][]string + created bool +} + +func NewTableIndex() *TableBrowserIndex { + return &TableBrowserIndex{tables: make(map[string][]string)} +} + +func (tb *TableBrowserIndex) IsCreated() bool { + return tb.created +} + +func (tb *TableBrowserIndex) SetCreated(b bool) { + tb.created = b +} + +func (tb *TableBrowserIndex) Add(contract string, tables []string) (ok bool) { + if contract == "" || len(tables) == 0 { + return false + } + tb.mux.Lock() + defer tb.mux.Unlock() + sort.Strings(tables) + tb.tables[contract] = tables + return true +} + +func (tb *TableBrowserIndex) Get(contract string) (tables []string) { + tb.mux.RLock() + defer tb.mux.RUnlock() + if tb.tables[contract] == nil || len(tb.tables[contract]) == 0 { + errs.ErrChan <- contract + " doesn't have any tables?" + return []string{""} + } + return tb.tables[contract] +} + +func (tb *TableBrowserIndex) List() []string { + l := make([]string, 0) + tb.mux.RLock() + defer tb.mux.RUnlock() + for tableName := range tb.tables { + l = append(l, tableName) + } + sort.Strings(l) + return l +} + +func GetTableBrowser(w int, h int, api *fio.API) (tab *widget.Box, ok bool) { + var getRows func() + page := widget.NewEntry() + page.SetText("1") + page.Disable() + rowsPerPage := widget.NewEntry() + rowsPerPage.SetText("10") + + getRowsPerPage := func() uint32 { + i, e := strconv.Atoi(rowsPerPage.Text) + if e != nil { + rowsPerPage.SetText("10") + rowsPerPage.Refresh() + return 10 + } + return uint32(i) + } + + result := widget.NewMultiLineEntry() + submit := widget.NewButtonWithIcon("Query", theme.SearchIcon(), func() { + getRows() + }) + showQueryCheck := widget.NewCheck("show query", func(b bool) {}) + var tables = widget.NewSelect([]string{""}, func(s string) { + result.SetText("") + if !page.Disabled() { + page.SetText("1") + } + if submit.Disabled() { + submit.Enable() + } + getRows() + }) + tables.PlaceHolder = "(table)" + scopeEntry := widget.NewEntry() + advancedCheck := &widget.Check{} + contract := widget.NewSelect(TableIndex.List(), func(s string) { + scopeEntry.SetText(s) + if advancedCheck.Disabled() { + advancedCheck.Enable() + } + t := TableIndex.Get(s) + if len(t) == 0 { + tables.Options = make([]string, 0) + return + } + tables.Options = t + tables.SetSelected(t[0]) + }) + contract.PlaceHolder = "(account)" + next := &widget.Button{} + next = widget.NewButtonWithIcon("next", theme.NavigateNextIcon(), func() { + p, e := strconv.Atoi(page.Text) + if e != nil { + page.SetText("1") + } else { + page.SetText(strconv.Itoa(p + 1)) + } + getRows() + }) + next.Disable() + previous := widget.NewButtonWithIcon("previous", theme.NavigateBackIcon(), func() { + p, e := strconv.Atoi(page.Text) + if e != nil { + page.SetText("1") + } else { + page.SetText(strconv.Itoa(p - 1)) + } + getRows() + }) + previous.Disable() + + indexLabel := widget.NewLabel("index ") + indexLabel.Hide() + indexEntry := widget.NewEntry() + indexEntry.SetText("1") + indexEntry.Hide() + scopeLabel := widget.NewLabel("scope ") + scopeLabel.Hide() + //scopeEntry := widget.NewEntry() // moved above contract select + scopeEntry.SetText("") + scopeEntry.Hide() + typeSelect := widget.NewSelect( + []string{ + "name", + "i64", + "i128", + "i256", + "float64", + "float128", + "ripemd160", + "sha256", + }, + func(s string) {}, + ) + typeSelect.PlaceHolder = "(key type)" + typeSelect.Hide() + lowerLabel := widget.NewLabel("lower bound") + lowerLabel.Hide() + lowerValueEntry := widget.NewEntry() + lowerValueEntry.SetPlaceHolder("lower bound") + lowerValueEntry.Hide() + upperLabel := widget.NewLabel("upper bound") + upperLabel.Hide() + upperValueEntry := widget.NewEntry() + upperValueEntry.SetPlaceHolder("upper bound") + upperValueEntry.Hide() + lowerValueEntry.OnChanged = func(s string) { + upperValueEntry.SetText(s) + upperValueEntry.Refresh() + } + transformSelect := widget.NewSelect( + []string{ + "none", + "name -> i64", + "checksum256", + "hash", + }, + func(s string) {}, + ) + transformSelect.PlaceHolder = "(transform)" + transformSelect.Hide() + reverseCheck := widget.NewCheck("reverse", func(bool) {}) + reverseCheck.Hide() + var lastNext, lastPrev bool + var lastPage string + advancedCheck = widget.NewCheck("Advanced", func(b bool) { + if b { + lastNext = next.Disabled() + lastPrev = previous.Disabled() + lastPage = page.Text + page.SetText("-") + scopeLabel.Show() + scopeEntry.Show() + indexLabel.Show() + indexEntry.Show() + typeSelect.Show() + lowerLabel.Show() + lowerValueEntry.Show() + upperLabel.Show() + upperValueEntry.Show() + transformSelect.Show() + reverseCheck.Show() + page.Disable() + previous.Disable() + next.Disable() + return + } + scopeLabel.Hide() + scopeEntry.Hide() + indexLabel.Hide() + indexEntry.Hide() + typeSelect.Hide() + lowerLabel.Hide() + lowerValueEntry.Hide() + upperLabel.Hide() + upperValueEntry.Hide() + transformSelect.Hide() + reverseCheck.Hide() + page.SetText(lastPage) + page.Enable() + if !lastPrev { + previous.Enable() + } + if !lastNext { + next.Enable() + } + }) + advancedCheck.Disable() + + browseLayout := widget.NewVBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(200, 35)), + widget.NewHBox( + widget.NewLabel(" "), + advancedCheck, + scopeLabel, + scopeEntry, + indexLabel, + indexEntry, + typeSelect, + lowerLabel, + lowerValueEntry, + upperLabel, + upperValueEntry, + transformSelect, + reverseCheck, + ), + ), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(200, 35)), + widget.NewHBox( + widget.NewLabel(" "), + contract, + tables, + previous, + widget.NewLabel("page"), + page, + widget.NewLabel("limit"), + rowsPerPage, + next, + showQueryCheck, + widget.NewLabel(" "), + submit, + ), + ), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(RWidth(), int(math.Round(float64(H)*.62)))), + widget.NewScrollContainer( + widget.NewVBox( + result, + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.Size{ + Width: 20, + Height: 100, + }), layout.NewSpacer()), + )), + )) + getRows = func() { + if contract.Selected == "" || tables.Selected == "" || page.Text == "" { + result.SetText("Invalid table params") + return + } + proposedPage, e := strconv.ParseUint(page.Text, 10, 32) + if e != nil { + page.SetText("1") + proposedPage = 1 + } + var curl string + var out *string + var more bool + switch advancedCheck.Checked { + case true: + // (max uint32, scope string, contract string, table string, index string, keyType string, lower string, upper string, transform string, api *fio.API + out, curl, _ = QueryTableAdvanced(getRowsPerPage(), scopeEntry.Text, contract.Selected, tables.Selected, indexEntry.Text, typeSelect.Selected, lowerValueEntry.Text, upperValueEntry.Text, transformSelect.Selected, reverseCheck.Checked, api) + case false: + out, curl, more = QueryTable((uint32(proposedPage)-1)*getRowsPerPage(), getRowsPerPage(), contract.Selected, tables.Selected, api) + if out == nil { + return + } + if !more && !next.Disabled() { + next.Disable() + } else if more && next.Disabled() { + next.Enable() + } + if proposedPage < 2 && !previous.Disabled() { + previous.Disable() + } else if proposedPage >= 2 && previous.Disabled() { + previous.Enable() + } + if (!next.Disabled() || !previous.Disabled()) && page.Disabled() { + page.Enable() + } else if next.Disabled() && previous.Disabled() && !page.Disabled() { + page.Disable() + } + } + result.SetText("") + var txt string + if out != nil { + if showQueryCheck.Checked { + txt = ` +Query: + +` + curl + ` + +Result + +` + *out + + } else { + txt = *out + } + func(s string) { + result.OnChanged = func(string) { + result.SetText(s) + } + }(txt) // deref + result.SetText(txt) + } + } + TableIndex.SetCreated(true) + return browseLayout, true +} + +func QueryTable(offset uint32, max uint32, contract string, table string, api *fio.API) (out *string, query string, more bool) { + gtr := eos.GetTableRowsRequest{ + Code: contract, + Scope: contract, + Table: table, + LowerBound: strconv.Itoa(int(offset)), + Limit: max, + JSON: true, + } + qs, _ := json.MarshalIndent(gtr, "", " ") + query = string(qs) + resp, err := api.GetTableRows(gtr) + var o string + if err != nil { + o = err.Error() + return &o, query, more + } + more = resp.More + j, err := json.MarshalIndent(resp.Rows, "", " ") + if err != nil { + o = err.Error() + return &o, query, more + } + o = string(j) + return &o, query, more +} + +func QueryTableAdvanced(max uint32, scope string, contract string, table string, index string, keyType string, lower string, upper string, transform string, reverse bool, api *fio.API) (out *string, query string, more bool) { + if keyType == "(key type)" { + keyType = "name" + } + switch transform { + case "name -> i64": + u, err := eos.StringToName(upper) + if err != nil { + e := err.Error() + out = &e + return + } + l, err := eos.StringToName(lower) + if err != nil { + e := err.Error() + out = &e + return + } + upper = fmt.Sprintf("%d", u) + lower = fmt.Sprintf("%d", l) + case "checksum256": + h := sha256.New() + _, err := h.Write([]byte(upper)) + if err != nil { + e := err.Error() + out = &e + return + } + ub := h.Sum(nil) + upper = hex.EncodeToString(ub) + h.Reset() + h.Write([]byte(lower)) + lb := h.Sum(nil) + lower = hex.EncodeToString(lb) + case "hash": + upper = FioDomainNameHash(upper) + lower = FioDomainNameHash(lower) + } + gtr := fio.GetTableRowsOrderRequest{ + Code: contract, + Scope: scope, + Table: table, + LowerBound: lower, + UpperBound: upper, + Limit: max, + KeyType: keyType, + Index: index, + JSON: true, + Reverse: reverse, + } + qs, _ := json.MarshalIndent(gtr, "", " ") + query = string(qs) + resp, err := api.GetTableRowsOrder(gtr) + var o string + if err != nil { + o = err.Error() + return &o, query, more + } + more = resp.More + j, err := json.MarshalIndent(resp.Rows, "", " ") + if err != nil { + o = err.Error() + return &o, query, more + } + o = string(j) + return &o, query, more +} diff --git a/table_test.go b/table_test.go new file mode 100644 index 0000000..b8e66ac --- /dev/null +++ b/table_test.go @@ -0,0 +1,22 @@ +package cryptonym + +import ( + "fmt" + "github.com/fioprotocol/fio-go" + "testing" +) + +func TestGetAccountActions(t *testing.T) { + api, _, err := fio.NewConnection(nil, "http://127.0.0.1:8888") + if err != nil { + t.Error(err) + return + } + api.Header.Set("User-Agent", "fio-cryptonym-wallet") + a, err := GetAccountSummary(api) + if err != nil { + t.Error(err) + return + } + fmt.Println(a) +} diff --git a/theme.go b/theme.go new file mode 100644 index 0000000..fa5a95c --- /dev/null +++ b/theme.go @@ -0,0 +1,280 @@ +package cryptonym + +import ( + "fyne.io/fyne" + "fyne.io/fyne/canvas" + "fyne.io/fyne/layout" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + fioassets "github.com/blockpane/cryptonym/assets" + "github.com/blockpane/prettyfyne" + "image/color" +) + +func ExLightTheme() prettyfyne.PrettyTheme { + lt := prettyfyne.ExampleMaterialLight + lt.TextSize = 14 + lt.IconInlineSize = 20 + lt.FocusColor = lt.HoverColor + lt.Padding = 3 + lt.FocusColor = &color.RGBA{R: 23, G: 11, B: 64, A: 128} + return lt +} + +func ExGreyTheme() prettyfyne.PrettyTheme { + lt := prettyfyne.ExampleCubicleLife + lt.TextSize = 14 + lt.TextColor = &color.RGBA{R: 0, G: 0, B: 0, A: 255} + lt.IconInlineSize = 20 + lt.FocusColor = &color.RGBA{R: 24, G: 24, B: 24, A: 127} + lt.Padding = 3 + lt.BackgroundColor = &color.RGBA{R: 210, G: 210, B: 210, A: 255} + return lt +} + +var ( + fioTertiary = &color.RGBA{R: 46, G: 102, B: 132, A: 255} + fioPrimary = &color.RGBA{R: 30, G: 62, B: 97, A: 255} + fioSecondary = &color.RGBA{R: 0, G: 0, B: 0, A: 162} + lightestGrey = &color.RGBA{R: 200, G: 200, B: 200, A: 255} + lightGrey = &color.RGBA{R: 155, G: 155, B: 155, A: 127} + grey = &color.RGBA{R: 99, G: 99, B: 99, A: 255} + //greyBorder = &color.RGBA{R: 35, G: 35, B: 35, A: 8} + darkGrey = &color.RGBA{R: 28, G: 28, B: 29, A: 255} + darkerGrey = &color.RGBA{R: 24, G: 24, B: 24, A: 255} + darkestGrey = &color.RGBA{R: 15, G: 15, B: 17, A: 255} +) + +// FioCustomTheme is a simple demonstration of a bespoke theme loaded by a Fyne app. +type FioCustomTheme struct { +} + +func (FioCustomTheme) BackgroundColor() color.Color { + return darkGrey +} + +func (FioCustomTheme) ButtonColor() color.Color { + return darkerGrey +} + +func (FioCustomTheme) DisabledButtonColor() color.Color { + //return darkestGrey + return darkGrey +} + +func (FioCustomTheme) HyperlinkColor() color.Color { + return fioTertiary +} + +func (FioCustomTheme) TextColor() color.Color { + return lightestGrey +} + +func (FioCustomTheme) DisabledTextColor() color.Color { + return lightGrey +} + +func (FioCustomTheme) IconColor() color.Color { + return fioTertiary +} + +func (FioCustomTheme) DisabledIconColor() color.Color { + return grey +} + +func (FioCustomTheme) PlaceHolderColor() color.Color { + return fioPrimary +} + +func (FioCustomTheme) PrimaryColor() color.Color { + return fioPrimary +} + +func (FioCustomTheme) HoverColor() color.Color { + return fioSecondary +} + +func (FioCustomTheme) FocusColor() color.Color { + return &color.RGBA{R: 93, G: 93, B: 93, A: 124} +} + +func (FioCustomTheme) ScrollBarColor() color.Color { + //return greyBorder + //return fioPrimary + return &color.RGBA{R: 26, G: 20, B: 60, A: 128} +} + +func (FioCustomTheme) ShadowColor() color.Color { + return &color.RGBA{R: 2, G: 0, B: 4, A: 166} +} + +func (FioCustomTheme) TextSize() int { + return 14 +} + +func (FioCustomTheme) TextFont() fyne.Resource { + return theme.DefaultTextFont() +} + +func (FioCustomTheme) TextBoldFont() fyne.Resource { + return theme.DefaultTextBoldFont() +} + +func (FioCustomTheme) TextItalicFont() fyne.Resource { + return theme.DefaultTextBoldItalicFont() +} + +func (FioCustomTheme) TextBoldItalicFont() fyne.Resource { + return theme.DefaultTextBoldItalicFont() +} + +func (FioCustomTheme) TextMonospaceFont() fyne.Resource { + return theme.DefaultTextMonospaceFont() +} + +func (FioCustomTheme) Padding() int { + return 3 +} + +func (FioCustomTheme) IconInlineSize() int { + return 20 +} + +func (FioCustomTheme) ScrollBarSize() int { + return 12 +} + +func (FioCustomTheme) ScrollBarSmallSize() int { + return 4 +} + +func CustomTheme() fyne.Theme { + return &FioCustomTheme{} +} + +func FioLogoCanvas() fyne.CanvasObject { + i, _, err := fioassets.NewFioLogo() + if err != nil { + return nil + } + image := canvas.NewImageFromImage(i) + return fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(55, 55)), layout.NewSpacer(), image) +} + +type ClickEntry struct { + widget.Entry + Button *widget.Button +} + +func (e *ClickEntry) onEnter() { + e.Button.Tapped(&fyne.PointEvent{}) +} + +func NewClickEntry(b *widget.Button) *ClickEntry { + entry := &ClickEntry{ + Entry: widget.Entry{}, + Button: b, + } + entry.ExtendBaseWidget(entry) + return entry +} + +func (e *ClickEntry) KeyDown(key *fyne.KeyEvent) { + switch key.Name { + case fyne.KeyReturn: + e.onEnter() + default: + e.Entry.KeyDown(key) + } +} + +func DarkerTheme() *prettyfyne.PrettyTheme { + pt, _, err := prettyfyne.UnmarshalYaml([]byte(darkerTheme)) + if err != nil { + return &prettyfyne.ExampleDracula + } + return pt +} + +const darkerTheme = ` +background_color: + r: 16 + g: 16 + b: 16 + a: 255 +button_color: + r: 28 + g: 28 + b: 28 + a: 255 +disabled_button_color: + r: 15 + g: 15 + b: 17 + a: 255 +hyperlink_color: + r: 143 + g: 168 + b: 51 + a: 64 +text_color: + r: 244 + g: 255 + b: 244 + a: 255 +disabled_text_color: + r: 138 + g: 138 + b: 138 + a: 255 +icon_color: + r: 150 + g: 150 + b: 150 + a: 255 +disabled_icon_color: + r: 84 + g: 84 + b: 84 + a: 255 +place_holder_color: + r: 83 + g: 83 + b: 83 + a: 255 +primary_color: + r: 48 + g: 48 + b: 48 + a: 255 +hover_color: + r: 69 + g: 69 + b: 69 + a: 255 +focus_color: + r: 99 + g: 99 + b: 99 + a: 255 +scroll_bar_color: + r: 0 + g: 0 + b: 0 + a: 255 +shadow_color: + r: 21 + g: 21 + b: 21 + a: 32 +text_size: 14 +text_font: NotoSans-Regular.ttf +text_bold_font: NotoSans-Bold.ttf +text_italic_font: NotoSans-Italic.ttf +text_bold_italic_font: NotoSans-BoldItalic.ttf +text_monospace_font: NotoMono-Regular.ttf +padding: 3 +icon_inline_size: 20 +scroll_bar_size: 10 +scroll_bar_small_size: 4 +` diff --git a/tx-result.go b/tx-result.go new file mode 100644 index 0000000..dda6e49 --- /dev/null +++ b/tx-result.go @@ -0,0 +1,795 @@ +package cryptonym + +import ( + "bufio" + "bytes" + "compress/zlib" + "encoding/json" + "fmt" + "fyne.io/fyne" + "fyne.io/fyne/layout" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go" + "github.com/fioprotocol/fio-go/eos" + "gopkg.in/yaml.v3" + "io/ioutil" + "log" + "math" + "regexp" + "sort" + "strconv" + "strings" + "sync" + "time" +) + +var ( + Results = make([]TxResult, 0) + requestText = widget.NewMultiLineEntry() + responseText = widget.NewMultiLineEntry() + stopRequested = make(chan bool) +) + +type TxResult struct { + FullResp []byte + FullReq []byte + Resp []byte + Req []byte + Success bool + Index int + Summary string +} + +type TxSummary struct { + TransactionId string `json:"transaction_id" yaml:"Transaction Id"` + Processed struct { + BlockNum uint32 `json:"block_num" yaml:"Block Number"` + BlockTime string `json:"block_time" yaml:"Block Time"` + Receipt struct { + Status string `json:"status" yaml:"Status"` + } `json:"receipt" yaml:"Receipt,omitempty"` + } `json:"processed" yaml:"Processed,omitempty"` + ErrorCode interface{} `json:"error_code" yaml:"Error,omitempty"` // is this a string, int, varies on context? + TotalBytes int `json:"total_bytes,omitempty" yaml:"TX Size of All Actions,omitempty"` // this is field we calculate later +} + +// to get the *real* size of what was transacted, we need to dig into the action traces and look at the length +// of the hex_data field, which is buried in the response. +type txTraces struct { + Processed struct { + ActionTraces []struct { + Act struct { + HexData string `json:"hex_data"` + } `json:"act"` + } `json:"action_traces"` + } `json:"processed"` +} + +func (tt txTraces) size() int { + if len(tt.Processed.ActionTraces) == 0 { + return 0 + } + var sz int + for _, t := range tt.Processed.ActionTraces { + sz = sz + (len(t.Act.HexData) / 2) + } + return sz +} + +type txResultOpts struct { + repeat int + loop bool + threads string + hideFail bool + hideSucc bool + window fyne.Window + gone bool + msig bool + msigSigners string + msigAccount string + msigName func() string + wrap bool + wrapActor string +} + +func TxResultsWindow(win *txResultOpts, api *fio.API, opts *fio.TxOptions, account *fio.Account) { + //var window fyne.Window + if win.window == nil { + win.window = App.NewWindow("TX Result") + } + + workers, e := strconv.Atoi(win.threads) + if e != nil { + workers = 1 + } + + var ( + grid *fyne.Container + b *widget.Button + stopButton *widget.Button + closeRow *widget.Group + running bool + exit bool + fullResponseIndex int + ) + + successLabel := widget.NewLabel("") + failedLabel := widget.NewLabel("") + successChan := make(chan bool) + failedChan := make(chan bool) + go func(s chan bool, f chan bool) { + time.Sleep(100 * time.Millisecond) + BalanceChan <- true + tick := time.NewTicker(time.Second) + update := false + updateBalance := false + mux := sync.Mutex{} + successCount := 0 + failedCount := 0 + for { + select { + case <-tick.C: + if updateBalance { + mux.Lock() + BalanceChan <- true + updateBalance = false + mux.Unlock() + } + if update { + mux.Lock() + successLabel.SetText(p.Sprintf("%d", successCount)) + failedLabel.SetText(p.Sprintf("%d", failedCount)) + successLabel.Refresh() + failedLabel.Refresh() + update = false + mux.Unlock() + } + case <-f: + mux.Lock() + update = true + failedCount = failedCount + 1 + mux.Unlock() + case <-s: + mux.Lock() + update = true + updateBalance = true + successCount = successCount + 1 + mux.Unlock() + } + } + }(successChan, failedChan) + + run := func() {} + mux := sync.Mutex{} + Results = make([]TxResult, 0) + + summaryGroup := widget.NewGroupWithScroller("Transaction Result") + showFullResponseButton := widget.NewButtonWithIcon("Show Response Details", theme.VisibilityIcon(), func() { + // avoid nil pointer + if len(Results) <= fullResponseIndex { + errs.ErrChan <- "could not show full response: invalid result index - this shouldn't happen!" + return + } + if len(Results[fullResponseIndex].FullResp) == 0 { + errs.ErrChan <- "could not show full response: empty string" + return + } + ShowFullResponse(Results[fullResponseIndex].FullResp, win.window) + }) + showFullRequestButton := widget.NewButtonWithIcon("Show Request JSON", theme.VisibilityIcon(), func() { + // avoid nil pointer + if len(Results) <= fullResponseIndex { + errs.ErrChan <- "could not show full request: invalid result index - this shouldn't happen!" + return + } + if len(Results[fullResponseIndex].FullReq) == 0 { + errs.ErrChan <- "could not show full request: empty string" + return + } + ShowFullRequest(Results[fullResponseIndex].FullReq, win.window) + }) + + setGrid := func() { + grid = fyne.NewContainerWithLayout(layout.NewHBoxLayout(), + fyne.NewContainerWithLayout(layout.NewGridLayoutWithRows(1), + closeRow, + fyne.NewContainerWithLayout(layout.NewMaxLayout(), + summaryGroup, + ), + ), + widget.NewVBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(txW, 30)), + fyne.NewContainerWithLayout(layout.NewGridLayout(2), showFullResponseButton, showFullRequestButton), + ), + widget.NewLabel("Request:"), + requestText, + widget.NewLabel("Response Summary:"), + responseText, + ), + ) + win.window.Resize(fyne.NewSize(txW, txH)) + win.window.SetContent(grid) + //win.window.CenterOnScreen() + } + + clear := func() { + mux.Lock() + Results = make([]TxResult, 0) + summaryGroup = widget.NewGroupWithScroller("Transaction Result") + summaryGroup.Refresh() + responseText.SetText("") + responseText.Refresh() + requestText.SetText("") + requestText.Refresh() + setGrid() + mux.Unlock() + } + + closeButton := widget.NewButtonWithIcon( + "close", + theme.DeleteIcon(), + func() { + if running { + stopRequested <- true + } + win.gone = true + clear() + //Win.RequestFocus() + win.window.Close() + }, + ) + resendButton := widget.NewButtonWithIcon("resend", theme.ViewRefreshIcon(), func() { + if running { + return + } + exit = false + go run() + }) + stopButton = widget.NewButtonWithIcon("stop", theme.CancelIcon(), func() { + if running { + stopRequested <- true + } + }) + + clearButton := widget.NewButtonWithIcon("clear results", theme.ContentRemoveIcon(), func() { + clear() + }) + closeRow = widget.NewGroup(" Control ", + stopButton, + resendButton, + clearButton, + closeButton, + layout.NewSpacer(), + BalanceLabel, + widget.NewLabel("Successful Requests:"), + successLabel, + widget.NewLabel("Failed Requests:"), + failedLabel, + ) + closeRow.Show() + + reqChan := make(chan string) + respChan := make(chan string) + fullRespChan := make(chan int) + + trimDisplayed := func(s string) string { + re := regexp.MustCompile(`[[^:ascii:]]`) + var displayed string + s = s + "\n" + reader := strings.NewReader(s) + buf := bufio.NewReader(reader) + var lines int + for { + lines = lines + 1 + line, err := buf.ReadString('\n') + if err != nil { + break + } + line, _ = strconv.Unquote(strconv.QuoteToASCII(line)) + if len(line) > 128+21 { + line = fmt.Sprintf("%s ... trimmed %d chars ...\n", line[:128], len(line)-128) + } + displayed = displayed + line + if lines > 31 { + displayed = displayed + "\n ... too many lines to display ..." + break + } + } + return re.ReplaceAllString(displayed, "?") + } + + go func(rq chan string, rs chan string, frs chan int) { + for { + select { + case q := <-rq: + mux.Lock() + requestText.SetText(trimDisplayed(q)) + requestText.Refresh() + mux.Unlock() + case s := <-rs: + mux.Lock() + responseText.SetText(trimDisplayed(s)) + responseText.Refresh() + mux.Unlock() + case fullResponseIndex = <-frs: + } + } + }(reqChan, respChan, fullRespChan) + reqChan <- "" + respChan <- "" + + repaint := func() { + mux.Lock() + closeRow.Refresh() + summaryGroup.Refresh() + responseText.Refresh() + requestText.Refresh() + if grid != nil { + grid.Refresh() + } + mux.Unlock() + } + + newButton := func(title string, index int, failed bool) { + if failed { + failedChan <- false + } else { + successChan <- true + } + if (!failed && win.hideSucc) || (failed && win.hideFail) { + return + } + // possible race while clearing the screen + if index > len(Results) { + return + } + deRef := &index + i := *deRef + if i-1 > len(Results) || len(Results) == 0 { + return + } + if len(Results) > 256 { + clear() + } + mux.Lock() + icon := theme.ConfirmIcon() + if failed { + icon = theme.CancelIcon() + } + + b = widget.NewButtonWithIcon(title, icon, func() { + if i >= len(Results) { + return + } + reqChan <- string(Results[i].Req) + respChan <- string(Results[i].Resp) + fullRespChan <- i + repaint() + }) + summaryGroup.Append(b) + mux.Unlock() + repaint() + } + + run = func() { + defer func() { + if running { + stopRequested <- true + } + }() + // give each thread it's own http client pool: + workerApi, workerOpts, err := fio.NewConnection(account.KeyBag, api.BaseURL) + if err != nil { + errs.ErrChan <- err.Error() + errs.ErrChan <- "ERROR: could not get new client connection" + return + } + workerApi.Header.Set("User-Agent", "fio-cryptonym-wallet") + running = true + stopButton.Enable() + bombsAway.Disable() + resendButton.Disable() + closeButton.Disable() + + defer func() { + running = false + stopButton.Disable() + bombsAway.Enable() + resendButton.Enable() + closeButton.Enable() + }() + + var end int + switch { + case win.loop: + end = math.MaxInt32 + case win.repeat > 1: + end = win.repeat + default: + end = 1 + } + finished := make(chan bool) + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer func() { + running = false + finished <- true + wg.Done() + }() + for i := 0; i < end; i++ { + if exit { + return + } + output := TxResult{ + Summary: fmt.Sprintf("%s", time.Now().Format("05.000")), + Index: i, + } + e := FormState.GeneratePayloads(account) + if e != nil { + errs.ErrChan <- e.Error() + errs.ErrChan <- "there was a problem generating dynamic payloads" + output.Resp = []byte(e.Error()) + Results = append(Results, output) + newButton(output.Summary, len(Results)-1, true) + continue + } + if exit { + return + } + raw, tx, err := FormState.PackAndSign(workerApi, workerOpts, account, win.msig) + if tx == nil || tx.PackedTransaction == nil { + errs.ErrChan <- "sending a signed transaction with null action data" + empty := fio.NewAction(eos.AccountName(FormState.Contract), eos.ActionName(FormState.Action), account.Actor, nil) + _, tx, err = workerApi.SignTransaction(fio.NewTransaction([]*fio.Action{empty}, workerOpts), workerOpts.ChainID, fio.CompressionNone) + if err != nil { + errs.ErrChan <- err.Error() + continue + } + } + if win.msig && err == nil { + if tx == nil || tx.PackedTransaction == nil { + errs.ErrChan <- "did not build a valid transaction, refusing to continue." + output.Resp = []byte("could not build the transaction. Don't worry it's me, not you.") + Results = append(Results, output) + newButton(output.Summary, len(Results)-1, true) + continue + } + ntx, err := tx.Unpack() + if err != nil { + errs.ErrChan <- "Problem repacking transaction to embed in msig propose " + err.Error() + output.Resp = []byte(err.Error()) + Results = append(Results, output) + newButton(output.Summary, len(Results)-1, true) + continue + } + // convert to transaction, without signature: + untx := eos.Transaction{} + if win.wrap { + untx = eos.Transaction{ + TransactionHeader: ntx.TransactionHeader, + ContextFreeActions: ntx.ContextFreeActions, + Actions: ntx.Actions, + Extensions: ntx.Extensions, + } + untx.Expiration = eos.JSONTime{Time: time.Unix(0, 0)} + untx.RefBlockNum = 0 + untx.RefBlockPrefix = 0 + for i := range untx.Actions { + untx.Actions[i].Authorization = []eos.PermissionLevel{ + {Actor: eos.AccountName(win.wrapActor), Permission: "active"}, + } + } + } else { + for i := range ntx.Actions { + ntx.Actions[i].Authorization = []eos.PermissionLevel{{ + Actor: eos.AccountName(win.msigAccount), + Permission: "active", + }} + } + } + requested := make([]*fio.PermissionLevel, 0) + signers := strings.Split(win.msigSigners, ",") + sort.Strings(signers) + for _, s := range signers { + requested = append(requested, &fio.PermissionLevel{ + Actor: eos.AccountName(strings.ReplaceAll(s, " ", "")), + Permission: "active", + }) + } + packed, _ := ntx.Pack(fio.CompressionNone) + //propose := fio.MsigPropose{} + //wrapPropose := fio.MsigWrappedPropose{} + var propose interface{} + if win.msig && !win.wrap { + ntx.Expiration = eos.JSONTime{Time: time.Now().Add(60 * time.Minute)} + propose = fio.MsigPropose{ + Proposer: account.Actor, + ProposalName: eos.Name(win.msigName()), + Requested: requested, + MaxFee: fio.Tokens(fio.GetMaxFee(fio.FeeMsigPropose))*uint64(len(packed.PackedTransaction)/1000) + fio.Tokens(1.0), + Trx: ntx, + } + } else if win.wrap { + //wrap := fio.NewWrapExecute(account.Actor, eos.AccountName(win.msigAccount), ntx) + wrap := fio.NewWrapExecute("eosio.wrap", account.Actor, &untx) + wrap.Authorization = []eos.PermissionLevel{ + //{Actor: account.Actor, Permission: "active"}, + {Actor: "eosio.wrap", Permission: "active"}, + } + wTx := fio.NewTransaction([]*fio.Action{wrap}, opts) + wTx.Expiration = eos.JSONTime{Time: time.Now().Add(60 * time.Minute)} + wTx.RefBlockNum = 0 + wTx.RefBlockPrefix = 0 + propose = fio.MsigWrappedPropose{ + Proposer: account.Actor, + ProposalName: eos.Name(win.msigName()), + Requested: requested, + MaxFee: fio.Tokens(fio.GetMaxFee(fio.FeeMsigPropose))*uint64(len(packed.PackedTransaction)/1000) + fio.Tokens(1.0), + Trx: wTx, + } + } + _, tx, err = workerApi.SignTransaction( + fio.NewTransaction( + []*fio.Action{ + fio.NewAction( + "eosio.msig", + "propose", + account.Actor, + propose, + ), + }, + workerOpts), + workerOpts.ChainID, fio.CompressionNone, + ) + if err != nil { + errs.ErrChan <- "Problem signing msig propose " + err.Error() + output.Resp = []byte(err.Error()) + Results = append(Results, output) + newButton(output.Summary, len(Results)-1, true) + continue + } + raw, _ = json.Marshal(propose) + } + j, _ := json.MarshalIndent(raw, "", " ") + packed, _ := json.MarshalIndent(tx, "", " ") + output.Req = append(append(j, []byte("\n\nPacked Tx:\n\n")...), packed...) + if err != nil { + errs.ErrChan <- err.Error() + errs.ErrChan <- "could not marshall into a TX" + output.Resp = []byte(err.Error()) + Results = append(Results, output) + newButton(output.Summary, len(Results)-1, true) + continue + } + if tx == nil || tx.PackedTransaction == nil { + errs.ErrChan <- "did not build a valid transaction, refusing to continue." + output.Resp = []byte("could not build the transaction. Don't worry it's me, not you.") + Results = append(Results, output) + newButton(output.Summary, len(Results)-1, true) + continue + } + output.Req = append(output.Req, []byte(p.Sprintf("\n\nSize of Packed TX (bytes): %d", len(tx.PackedTransaction)))...) + reqBuf := bytes.Buffer{} + reqZWriter, _ := zlib.NewWriterLevel(&reqBuf, zlib.BestCompression) + reqZWriter.Write(j) + reqZWriter.Close() + output.FullReq = reqBuf.Bytes() + if exit { + return + } + result, err := workerApi.PushEndpointRaw(actionEndPointActive, tx) + if err != nil { + errs.ErrChan <- err.Error() + if win.hideFail { + failedChan <- true + continue + } + output.Resp = []byte(err.Error()) + output.Summary = fmt.Sprintf("%s", time.Now().Format("05.000")) + buf := bytes.Buffer{} + zWriter, _ := zlib.NewWriterLevel(&buf, zlib.BestCompression) + if len(result) > 0 { + zWriter.Write(result) + } else { + zWriter.Write([]byte(fmt.Sprintf(`{"error": "%s"}`, err.Error()))) + } + zWriter.Close() + output.FullResp = buf.Bytes() + Results = append(Results, output) + newButton(output.Summary, len(Results)-1, true) + continue + } + if exit { + return + } + // store two responses, the summary -- displayed by default, and zlib compressed full response. + // the full response is huge, and will seriously screw up the display and consume a lot of memory! + summary := &TxSummary{} + err = json.Unmarshal(result, summary) + if err != nil { + errs.ErrChan <- err.Error() + output.Resp = []byte(err.Error()) + output.Summary = fmt.Sprintf("%s", time.Now().Format("05.000")) + if win.hideFail { + failedChan <- true + continue + } + Results = append(Results, output) + newButton(output.Summary, len(Results)-1, true) + continue + } + // get the real tx size, since we got this far assume we have a valid tx result + sz := &txTraces{} + _ = json.Unmarshal(result, sz) + summary.TotalBytes = sz.size() + + if win.hideSucc { + successChan <- true + continue + } + j, _ = yaml.Marshal(summary) + output.Resp = j + buf := bytes.Buffer{} + zWriter, _ := zlib.NewWriterLevel(&buf, zlib.BestCompression) + zWriter.Write(result) + zWriter.Close() + output.FullResp = buf.Bytes() + Results = append(Results, output) + newButton(output.Summary, len(Results)-1, false) + } + }() + for { + select { + case _ = <-stopRequested: + exit = true + case _ = <-finished: + wg.Wait() + return + } + } + } + + for w := 0; w < workers; w++ { + go run() + } + time.Sleep(250 * time.Millisecond) + setGrid() + if len(Results) > 0 && !win.hideFail && !win.hideSucc { + responseText.SetText(trimDisplayed(string(Results[0].Resp))) + requestText.SetText(trimDisplayed(string(Results[0].Req))) + } + if !running { + stopButton.Disable() + } + repaint() + win.window.SetOnClosed(func() { + win.gone = true + exit = true + win.window = App.NewWindow("Tx Results") + win.window.Resize(fyne.NewSize(txW, txH)) + win.window.Hide() + requestText.SetText("") + requestText.Refresh() + responseText.SetText("") + responseText.Refresh() + for i := 0; i < 10; i++ { + if bombsAway.Disabled() { + exit = true + time.Sleep(500 * time.Millisecond) + } else { + Win.RequestFocus() + return + } + } + }) + if win.gone { + win.gone = false + win.window.Show() + } else { + repaint() + } +} + +func ShowFullResponse(b []byte, win fyne.Window) { + FullResponseText := widget.NewMultiLineEntry() + FullActionRespWin := App.NewWindow("Full Response") + FullActionRespWin.Hide() + FullActionRespWin.Resize(fyne.NewSize(W, H)) + FullActionRespWin.SetContent( + fyne.NewContainerWithLayout(layout.NewMaxLayout(), + widget.NewScrollContainer( + FullResponseText, + ), + ), + ) + FullActionRespWin.SetOnClosed(func() { + go func() { + // bug in fyne 1.3 where we need a very short wait to grab a child window + time.Sleep(100 * time.Millisecond) + for _, w := range fyne.CurrentApp().Driver().AllWindows() { + if w.Title() == "Tx Results" { + w.RequestFocus() + log.Println("found parent") + return + } + } + win.RequestFocus() + }() + }) + set := func(s string) { + FullResponseText.SetText(s) + FullResponseText.Refresh() + FullActionRespWin.Show() + } + reader := bufio.NewReader(bytes.NewReader(b)) + zlReader, err := zlib.NewReader(reader) + if err != nil { + set(err.Error()) + return + } + defer zlReader.Close() + j, err := ioutil.ReadAll(zlReader) + if err != nil { + set(err.Error()) + return + } + full, err := json.MarshalIndent(json.RawMessage(j), "", " ") + if err != nil { + set(err.Error()) + return + } + set(string(full)) +} + +func ShowFullRequest(b []byte, win fyne.Window) { + fullRequestText := widget.NewMultiLineEntry() + fullActionRespWin := App.NewWindow("Full Request") + fullActionRespWin.Hide() + fullActionRespWin.Resize(fyne.NewSize(W, H)) + fullActionRespWin.SetContent( + fyne.NewContainerWithLayout(layout.NewMaxLayout(), + widget.NewScrollContainer( + fullRequestText, + ), + ), + ) + fullActionRespWin.SetOnClosed(func() { + go func() { + time.Sleep(100 * time.Millisecond) + for _, w := range fyne.CurrentApp().Driver().AllWindows() { + if w.Title() == "Tx Results" { + w.RequestFocus() + log.Println("found parent") + return + } + } + win.RequestFocus() + }() + }) + set := func(s string) { + fullRequestText.SetText(s) + fullRequestText.Refresh() + fullActionRespWin.Show() + } + reader := bufio.NewReader(bytes.NewReader(b)) + zlReader, err := zlib.NewReader(reader) + if err != nil { + set(err.Error()) + return + } + defer zlReader.Close() + j, err := ioutil.ReadAll(zlReader) + if err != nil { + set(err.Error()) + return + } + full, err := json.MarshalIndent(json.RawMessage(j), "", " ") + if err != nil { + set(err.Error()) + return + } + set(string(full)) +} diff --git a/vanity.go b/vanity.go new file mode 100644 index 0000000..38978e8 --- /dev/null +++ b/vanity.go @@ -0,0 +1,140 @@ +package cryptonym + +import ( + "fmt" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go" + "github.com/fioprotocol/fio-go/eos/ecc" + "golang.org/x/text/language" + "golang.org/x/text/message" + "strings" + "sync" + "time" +) + +func vanityKey(o *vanityOptions, quit chan bool) (*fio.Account, error) { + account := &fio.Account{} + var hit bool + errs.ErrChan <- "vanity search starting for " + o.word + found := func(k *key) { + errs.ErrChan <- fmt.Sprintf("Vanity generator found a match: %s %s", k.actor, k.pub) + } + + statsChan := make(chan bool) + summary := make(chan bool) + go func() { + pp := message.NewPrinter(language.AmericanEnglish) + t := time.NewTicker(time.Minute / 2) + var counter uint64 + var total uint64 + for { + select { + case <-summary: + errs.ErrChan <- pp.Sprintf("... tried %d keys", total) + case <-t.C: + if hit { + return + } + errs.ErrChan <- pp.Sprintf("Key search rate: %d KPS, tried %d so far", counter/30, total) + counter = 0 + case <-statsChan: + counter += 1 + total += 1 + } + } + }() + + finishChan := make(chan bool) + keyChan := make(chan *key) + var err error + go func() { + for { + select { + case <-finishChan: + summary <- true + return + case <-quit: + hit = true + case k := <-keyChan: + if hit { + continue + } + switch o.anywhere { + case false: + if o.actor { + if strings.HasPrefix(k.actor, o.word) { + hit = true + } + } + if o.pub { + if strings.HasPrefix(strings.ToLower(k.pub[4:]), strings.ToLower(o.word)) { + hit = true + } + } + case true: + if o.actor { + if strings.Contains(k.actor, o.word) { + hit = true + } + } + if o.pub { + if strings.Contains(strings.ToLower(k.pub[4:]), strings.ToLower(o.word)) { + hit = true + } + } + } + if hit { + found(k) + account, err = fio.NewAccountFromWif(k.priv) + if err != nil { + errs.ErrChan <- err.Error() + } + } + } + } + }() + + wg := sync.WaitGroup{} + for i := 0; i < o.threads; i++ { + wg.Add(1) + go func() { + for { + if hit { + wg.Done() + return + } + keyChan <- newRandomAccount() + statsChan <- true + } + }() + } + wg.Wait() + finishChan <- true + errs.ErrChan <- "vanity key generator done" + return account, err +} + +type key struct { + actor string + pub string + priv string +} + +func newRandomAccount() *key { + priv, _ := ecc.NewRandomPrivateKey() + pub := "FIO" + priv.PublicKey().String()[3:] + actor, _ := fio.ActorFromPub(pub) + return &key{ + actor: string(actor), + pub: pub, + priv: priv.String(), + } +} + +type vanityOptions struct { + anywhere bool + word string + actor bool + pub bool + threads int +} diff --git a/vote.go b/vote.go new file mode 100644 index 0000000..8a48765 --- /dev/null +++ b/vote.go @@ -0,0 +1,460 @@ +package cryptonym + +import ( + "bytes" + "encoding/json" + "fmt" + "fyne.io/fyne" + "fyne.io/fyne/canvas" + "fyne.io/fyne/dialog" + "fyne.io/fyne/layout" + "fyne.io/fyne/theme" + "fyne.io/fyne/widget" + fioassets "github.com/blockpane/cryptonym/assets" + errs "github.com/blockpane/cryptonym/errLog" + "github.com/fioprotocol/fio-go" + "github.com/fioprotocol/fio-go/eos" + "golang.org/x/text/language" + "golang.org/x/text/message" + "gopkg.in/yaml.v2" + "image" + "io/ioutil" + "log" + "math" + "net/url" + "regexp" + "sort" + "strconv" + "strings" + "sync" + "time" +) + +type existVotes struct { + Producers []string `json:"producers"` +} + +type prodRow struct { + FioAddress string `json:"fio_address"` +} + +func GetCurrentVotes(actor string, api *fio.API) (votes string) { + getVote, err := api.GetTableRows(eos.GetTableRowsRequest{ + Code: "eosio", + Scope: "eosio", + Table: "voters", + + Index: "3", + LowerBound: actor, + UpperBound: actor, + Limit: 1, + KeyType: "name", + JSON: true, + }) + if err != nil { + return + } + v := make([]*existVotes, 0) + err = json.Unmarshal(getVote.Rows, &v) + if err != nil { + return + } + if len(v) == 0 { + return + } + votedFor := make([]string, 0) + for _, row := range v[0].Producers { + if row == "" { + continue + } + gtr, err := api.GetTableRows(eos.GetTableRowsRequest{ + Code: "eosio", + Scope: "eosio", + Table: "producers", + LowerBound: row, + UpperBound: row, + KeyType: "name", + Index: "4", + JSON: true, + }) + if err != nil { + continue + } + p := make([]*prodRow, 0) + err = json.Unmarshal(gtr.Rows, &p) + if err != nil { + continue + } + if len(p) == 1 && p[0].FioAddress != "" { + votedFor = append(votedFor, p[0].FioAddress) + } + } + if len(votedFor) == 0 { + return + } + return strings.Join(votedFor, ", ") +} + +type bpInfo struct { + CurrentVotes float64 + FioAddress string + Actor string + BpJson *fio.BpJson + VoteFor bool + OrigVoteFor bool + Url string + Img *canvas.Image + Top21 bool + Tied bool +} + +var bpInfoCache = make(map[string]*bpInfo) + +func getBpInfo(actor string, api *fio.API) ([]bpInfo, error) { + bpi := make([]bpInfo, 0) + prods, err := api.GetFioProducers() + if err != nil { + return bpi, err + } + curVotes := strings.Split(GetCurrentVotes(actor, api), ",") + for i := range curVotes { + curVotes[i] = strings.TrimSpace(curVotes[i]) + } + hasVoted := func(s string) bool { + for _, v := range curVotes { + if s == v { + return true + } + } + return false + } + isTopProd := make(map[string]bool) + sched, _ := api.GetProducerSchedule() + for _, tp := range sched.Active.Producers { + isTopProd[string(tp.AccountName)] = true + } + voteTies := make(map[string]int) + for _, bp := range prods.Producers { + voteTies[bp.TotalVotes] += 1 + } + bpiMux := sync.Mutex{} + wg := sync.WaitGroup{} + wg.Add(len(prods.Producers)) + // temporarily set a very aggressive timeout, otherwise this can take up to a minute. + // sorry BPs, if your server is slow no icon and bp info will show up. + oldTimout := api.HttpClient.Timeout + api.HttpClient.Timeout = 2 * time.Second + for _, bp := range prods.Producers { + go func(bp fio.Producer) { + defer wg.Done() + + if bp.IsActive == 0 { + return + } + votes, _ := strconv.ParseFloat(bp.TotalVotes, 64) + bpiMux.Lock() + var tied bool + if voteTies[bp.TotalVotes] > 1 { + tied = true + } + + if bpInfoCache[string(bp.FioAddress)] != nil { + bpInfoCache[string(bp.FioAddress)].VoteFor = hasVoted(string(bp.FioAddress)) + bpInfoCache[string(bp.FioAddress)].OrigVoteFor = hasVoted(string(bp.FioAddress)) + bpInfoCache[string(bp.FioAddress)].Tied = tied + bpInfoCache[string(bp.FioAddress)].CurrentVotes = votes + bpInfoCache[string(bp.FioAddress)].Top21 = isTopProd[string(bp.Owner)] + bpi = append(bpi, *bpInfoCache[string(bp.FioAddress)]) + bpiMux.Unlock() + return + } + + bpiMux.Unlock() + bpj, err := api.GetBpJson(bp.Owner) + if err != nil { + log.Printf("could not get bp.json for %s, %s\n", bp.Owner, err.Error()) + } + img := canvas.NewImageFromResource(theme.QuestionIcon()) + if bpj != nil && bpj.Org.Branding.Logo256 != "" { + resp, err := api.HttpClient.Get(bpj.Org.Branding.Logo256) + if err == nil { + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err == nil { + decoded, _, err := image.Decode(bytes.NewReader(body)) + if err == nil { + img = canvas.NewImageFromImage(decoded) + } + } + } + } + info := bpInfo{ + CurrentVotes: votes, + FioAddress: string(bp.FioAddress), + Actor: string(bp.Owner), + Url: bp.Url, + BpJson: bpj, + VoteFor: hasVoted(string(bp.FioAddress)), + OrigVoteFor: hasVoted(string(bp.FioAddress)), + Img: img, + Top21: isTopProd[string(bp.Owner)], + Tied: tied, + } + bpiMux.Lock() + bpi = append(bpi, info) + bpInfoCache[string(bp.FioAddress)] = &info + bpiMux.Unlock() + }(bp) + } + wg.Wait() + api.HttpClient.Timeout = oldTimout + sort.Slice(bpi, func(i, j int) bool { + if bpi[i].CurrentVotes == bpi[j].CurrentVotes { + iName, _ := eos.StringToName(bpi[i].Actor) + jName, _ := eos.StringToName(bpi[j].Actor) + return iName < jName + } + return bpi[i].CurrentVotes > bpi[j].CurrentVotes + }) + return bpi, nil +} + +var RefreshVotesChan = make(chan bool) + +func VoteContent(content chan fyne.CanvasObject, refresh chan bool) { + pp := message.NewPrinter(language.AmericanEnglish) + r := regexp.MustCompile("(?m)^-") + table := func() fyne.CanvasObject { + bpi, err := getBpInfo(string(Account.Actor), Api) + if err != nil { + return widget.NewLabel(err.Error()) + } + + voteRowsBox := widget.NewVBox() + + origVotes := make(map[string]bool) + curVotes := make(map[string]bool) + voteButton := &widget.Button{} + countLabel := widget.NewLabelWithStyle("", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + + myAddrs := func() []string { + names := make([]string, 0) + _, _, _ = Account.GetNames(Api) + for _, n := range Account.Addresses { + names = append(names, n.FioAddress) + } + return names + }() + addrsSelect := widget.NewSelect(myAddrs, func(s string) { + fee := fio.GetMaxFee(`vote_producer`) + if err == nil { + voteButton.SetText(pp.Sprintf("Vote! %s %g", fio.FioSymbol, fee)) + } + }) + voteButton = widget.NewButtonWithIcon("Vote!", fioassets.NewFioLogoResource(), func() { + go func() { + prods := make([]string, 0) + for k, v := range curVotes { + if v { + prods = append(prods, k) + } + } + vp := fio.NewVoteProducer(prods, Account.Actor, addrsSelect.Selected) + var result string + resp, err := Api.SignPushTransaction(fio.NewTransaction( + []*fio.Action{vp}, + Opts, + ), + Opts.ChainID, + fio.CompressionNone, + ) + if err != nil { + result = err.Error() + errs.ErrChan <- err.Error() + } else { + j, err := json.MarshalIndent(resp, "", " ") + if err != nil { + errs.ErrChan <- err.Error() + return + } + result = string(j) + } + content := fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(RWidth()/2, PctHeight()-250/2)), + widget.NewScrollContainer( + widget.NewLabelWithStyle(result, fyne.TextAlignLeading, fyne.TextStyle{Monospace: true}), + )) + dialog.ShowCustom("voteproducer result", "OK", content, Win) + go func() { + time.Sleep(100 * time.Millisecond) + RefreshVotesChan <- true + }() + }() + }) + refreshButton := widget.NewButtonWithIcon("Refresh", theme.ViewRefreshIcon(), func() { + RefreshVotesChan <- true + }) + + if len(myAddrs) > 0 { + addrsSelect.SetSelected(myAddrs[0]) + } + performVoteBox := widget.NewHBox( + layout.NewSpacer(), + countLabel, + widget.NewLabel("vote as:"), + addrsSelect, + voteButton, + refreshButton, + layout.NewSpacer(), + ) + + refreshTopChan := make(chan map[string]bool) + go func() { + for _, gvc := range bpi { + if gvc.VoteFor { + origVotes[gvc.FioAddress] = true + curVotes[gvc.FioAddress] = true + } + } + for { + select { + case votee := <-refreshTopChan: + for k, v := range votee { + curVotes[k] = v + } + var votes, changed int + for k, v := range curVotes { + if origVotes[k] != v { + changed = changed + 1 + } + if v { + votes = votes + 1 + } + } + countLabel.SetText(fmt.Sprintf("%d of 30 votes selected", votes)) + if changed != 0 && votes <= 30 && addrsSelect.Selected != "" && addrsSelect.Selected != "(Select one)" { + voteButton.Enable() + continue + } + voteButton.Disable() + } + } + }() + + moreInfoSize := widget.NewButtonWithIcon("more info", theme.InfoIcon(), func() {}).MinSize() + + for rank, bp := range bpi { + func(rank int, bp bpInfo) { + voteContent := fyne.NewContainerWithLayout(layout.NewGridLayoutWithColumns(2)) + var isTop string + if bp.Top21 { + isTop = "Top 21 " + } + boldName := widget.NewLabelWithStyle(bp.FioAddress, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) + boldName.Hide() + softName := widget.NewLabel(bp.FioAddress) + //selected := canvas.NewImageFromImage(votedImg()) + selected := canvas.NewImageFromResource(theme.NavigateNextIcon()) + selected.Hide() + voteCheck := widget.NewCheck("Vote", func(b bool) { + bp.VoteFor = b + refreshTopChan <- map[string]bool{bp.FioAddress: b} + switch { + case bp.OrigVoteFor && bp.VoteFor: + selected.Resource = theme.NavigateNextIcon() + selected.Show() + case !bp.OrigVoteFor && bp.VoteFor: + selected.Resource = theme.ContentAddIcon() + selected.Show() + case bp.OrigVoteFor && !bp.VoteFor: + selected.Resource = theme.DeleteIcon() + selected.Show() + default: + selected.Hide() + } + selected.Refresh() + if b { + boldName.Show() + softName.Hide() + return + } + boldName.Hide() + softName.Show() + }) + voteCheck.SetChecked(bp.VoteFor) + tied := widget.NewLabelWithStyle(" ", fyne.TextAlignLeading, fyne.TextStyle{Monospace: true}) + if bp.Tied && bp.CurrentVotes > 0 { + tied.SetText(" * ") + } + voteContent.AddObject(widget.NewHBox( + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(40, 40)), selected), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(60, 40)), widget.NewLabel(isTop)), + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(40, 40)), bp.Img), + voteCheck, boldName, softName, layout.NewSpacer(), + widget.NewLabelWithStyle( + pp.Sprintf("%s %d voted - rank #%3d", fio.FioSymbol, int64(math.Round(bp.CurrentVotes))/1_000_000_000, rank+1), + fyne.TextAlignTrailing, + fyne.TextStyle{}), + tied, + )) + moreInfo := widget.NewButtonWithIcon("more info", theme.InfoIcon(), func() { + var yContent string + y, err := yaml.Marshal(bp.BpJson) + if err != nil { + yContent = err.Error() + } else { + yContent = string(y) + } + mi := widget.NewMultiLineEntry() //yContent, fyne.TextAlignLeading, fyne.TextStyle{Monospace: true}) + txt := r.ReplaceAllString(yContent, "\n-") + func(txt string) { + mi.OnChanged = func(string) { + mi.SetText(txt) + } + }(txt) + mi.SetText(txt) + content := fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(RWidth()/2, PctHeight()-250-performVoteBox.MinSize().Height/2)), + widget.NewScrollContainer( + mi, + )) + dialog.ShowCustom(bp.FioAddress, "OK", content, Win) + }) + if bp.BpJson == nil { + moreInfo.Hide() + bp.Img.Hide() + } + + nameBox := widget.NewHBox() + if !strings.HasPrefix(bp.Url, "http") && bp.Url != "" { + bp.Url = "http://" + bp.Url + } + u, err := url.Parse(bp.Url) + nameBox.Append(fyne.NewContainerWithLayout(layout.NewFixedGridLayout(moreInfoSize), moreInfo)) + if err == nil { + nameBox.Append(widget.NewHyperlinkWithStyle(u.Host, u, fyne.TextAlignLeading, fyne.TextStyle{Bold: true})) + } + nameBox.Append(layout.NewSpacer()) + //nameBox.Append(widget.NewLabelWithStyle(" ", fyne.TextAlignTrailing, fyne.TextStyle{Monospace: true})) + voteContent.AddObject(nameBox) + voteRowsBox.Append(voteContent) + }(rank, bp) + } + voteRowsBox.Append(fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.Size{ + Width: 20, + Height: 50, + }))) + return widget.NewVBox( + performVoteBox, + fyne.NewContainerWithLayout(layout.NewFixedGridLayout(fyne.NewSize(RWidth(), PctHeight()-250-performVoteBox.MinSize().Height)), + widget.NewGroupWithScroller("Producer Candidates", voteRowsBox), + )) + } + content <- widget.NewLabel("Please wait, updating producer information ....") + content <- table() + for { + select { + case <-refresh: + content <- widget.NewLabel("Please wait, updating producer information ....") + content <- table() + } + } +}