├── .gitignore
├── README.md
├── bundler.json
├── cmd.png
├── console.go
├── db
├── localSwitchFilesDB.go
├── persistentDB.go
├── switchTitlesDB.go
└── utils.go
├── dlc_ui.png
├── fileio
└── splitFileUtil.go
├── go.mod
├── go.sum
├── gui.go
├── library_ui.png
├── main.go
├── process
├── incompleteTitleProcessor.go
├── organizefolderStructure.go
└── organizefolderStructure_test.go
├── prod.keys
├── resources
├── app
│ ├── app.css
│ ├── app.html
│ ├── app.js
│ └── lib
│ │ ├── css
│ │ ├── bootstrap
│ │ │ └── bootstrap.min.css
│ │ ├── dark
│ │ │ ├── toggle-bootstrap-dark.min.css
│ │ │ └── toggle-bootstrap.min.css
│ │ └── tabulator
│ │ │ ├── bootstrap
│ │ │ ├── tabulator_bootstrap.css
│ │ │ ├── tabulator_bootstrap.min.css
│ │ │ ├── tabulator_bootstrap.min.css.map
│ │ │ ├── tabulator_bootstrap4.css
│ │ │ ├── tabulator_bootstrap4.min.css
│ │ │ └── tabulator_bootstrap4.min.css.map
│ │ │ ├── bulma
│ │ │ ├── tabulator_bulma.css
│ │ │ ├── tabulator_bulma.min.css
│ │ │ └── tabulator_bulma.min.css.map
│ │ │ ├── materialize
│ │ │ ├── tabulator_materialize.css
│ │ │ ├── tabulator_materialize.min.css
│ │ │ └── tabulator_materialize.min.css.map
│ │ │ ├── semantic-ui
│ │ │ ├── tabulator_semantic-ui.css
│ │ │ ├── tabulator_semantic-ui.min.css
│ │ │ └── tabulator_semantic-ui.min.css.map
│ │ │ ├── tabulator.css
│ │ │ ├── tabulator.min.css
│ │ │ ├── tabulator.min.css.map
│ │ │ ├── tabulator_midnight.css
│ │ │ ├── tabulator_midnight.min.css
│ │ │ ├── tabulator_midnight.min.css.map
│ │ │ ├── tabulator_modern.css
│ │ │ ├── tabulator_modern.min.css
│ │ │ ├── tabulator_modern.min.css.map
│ │ │ ├── tabulator_simple.css
│ │ │ ├── tabulator_simple.min.css
│ │ │ ├── tabulator_simple.min.css.map
│ │ │ ├── tabulator_site.css
│ │ │ ├── tabulator_site.min.css
│ │ │ └── tabulator_site.min.css.map
│ │ └── js
│ │ ├── astiloader
│ │ ├── astiloader.css
│ │ ├── astiloader.js
│ │ └── loader.png
│ │ ├── astimodaler
│ │ ├── astimodaler.css
│ │ ├── astimodaler.js
│ │ └── cross.png
│ │ ├── astinotifier
│ │ ├── astinotifier.css
│ │ ├── astinotifier.js
│ │ └── cross.png
│ │ ├── astiprogresser
│ │ ├── astiprogresser.css
│ │ ├── astiprogresser.js
│ │ └── check.png
│ │ ├── astitools
│ │ └── astitools.js
│ │ ├── astiws
│ │ └── astiws.js
│ │ ├── jquery
│ │ └── jquery-3.5.1.min.js
│ │ ├── jsrender
│ │ └── jsrender.min.js
│ │ ├── moment
│ │ └── moment.min.js
│ │ ├── notify
│ │ └── notify.min.js
│ │ └── tabulator
│ │ ├── jquery_wrapper.js
│ │ ├── jquery_wrapper.min.js
│ │ ├── modules
│ │ ├── accessor.js
│ │ ├── accessor.min.js
│ │ ├── ajax.js
│ │ ├── ajax.min.js
│ │ ├── calculation_colums.js
│ │ ├── calculation_colums.min.js
│ │ ├── clipboard.js
│ │ ├── clipboard.min.js
│ │ ├── data_tree.js
│ │ ├── data_tree.min.js
│ │ ├── download.js
│ │ ├── download.min.js
│ │ ├── edit.js
│ │ ├── edit.min.js
│ │ ├── export.js
│ │ ├── export.min.js
│ │ ├── filter.js
│ │ ├── filter.min.js
│ │ ├── format.js
│ │ ├── format.min.js
│ │ ├── frozen_columns.js
│ │ ├── frozen_columns.min.js
│ │ ├── frozen_rows.js
│ │ ├── frozen_rows.min.js
│ │ ├── group_rows.js
│ │ ├── group_rows.min.js
│ │ ├── history.js
│ │ ├── history.min.js
│ │ ├── html_table_import.js
│ │ ├── html_table_import.min.js
│ │ ├── keybindings.js
│ │ ├── keybindings.min.js
│ │ ├── menu.js
│ │ ├── menu.min.js
│ │ ├── moveable_columns.js
│ │ ├── moveable_columns.min.js
│ │ ├── moveable_rows.js
│ │ ├── moveable_rows.min.js
│ │ ├── mutator.js
│ │ ├── mutator.min.js
│ │ ├── page.js
│ │ ├── page.min.js
│ │ ├── persistence.js
│ │ ├── persistence.min.js
│ │ ├── print.js
│ │ ├── print.min.js
│ │ ├── reactive_data.js
│ │ ├── reactive_data.min.js
│ │ ├── resize_columns.js
│ │ ├── resize_columns.min.js
│ │ ├── resize_rows.js
│ │ ├── resize_rows.min.js
│ │ ├── resize_table.js
│ │ ├── resize_table.min.js
│ │ ├── responsive_layout.js
│ │ ├── responsive_layout.min.js
│ │ ├── select_row.js
│ │ ├── select_row.min.js
│ │ ├── sort.js
│ │ ├── sort.min.js
│ │ ├── validate.js
│ │ └── validate.min.js
│ │ ├── tabulator.js
│ │ ├── tabulator.min.js
│ │ ├── tabulator_core.js
│ │ └── tabulator_core.min.js
├── icon.icns
├── icon.ico
└── icon.png
├── settings
├── keys.go
└── settings.go
├── slm.json
├── switchfs
├── _crypto
│ ├── ecb.go
│ └── xts.go
├── cnmt.go
├── fs.go
├── nacp.go
├── nca.go
├── ncaHeader.go
├── nsp.go
├── pfs0.go
├── romfs.go
├── splitFileReader.go
└── xci.go
└── updates_ui.png
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | resources/app/vendor
3 | output
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Switch library manager
2 | Easily manage your switch game backups
3 |
4 | 
5 |
6 | 
7 |
8 | 
9 |
10 | #### Features:
11 | - Cross platform, works on Windows / Mac / Linux
12 | - GUI and command line interfaces
13 | - Scan your local switch backup library (NSP/NSZ/XCI)
14 | - Read titleId/version by decrypting NSP/XCI/NSZ (requires prod.keys)
15 | - If no prod.keys present, fallback to read titleId/version by parsing file name (example: `Super Mario Odyssey [0100000000010000][v0].nsp`).
16 | - Lists missing update files (for games and DLC)
17 | - Lists missing DLCs
18 | - Automatically organize games per folder
19 | - Rename files based on metadata read from NSP
20 | - Delete old update files (in case you have multiple update files for the same game, only the latest will remain)
21 | - Delete empty folders
22 | - Zero dependencies, all crypto operations implemented in Go.
23 |
24 | ## Keys (optional)
25 | Having a prod.keys file will allow you to ensure the files you have a correctly classified.
26 | The app will look for the "prod.keys" file in the app folder or under ${HOME}/.switch/
27 | You can also specify a custom location in the settings.json (see below)
28 |
29 | Note: Only the header_key, and the key_area_key_application_XX keys are required.
30 |
31 | ## Settings
32 | During the App first launch a "settings.json" file will be created, that allows for granular control over the Apps execution.
33 |
34 | You can customize the folder/file re-naming, as well as turn on/off features.
35 |
36 | ```
37 | {
38 | "versions_etag": "W/\"c3f5ecb3392d61:0\"",
39 | "titles_etag": "W/\"4a4fcc163a92d61:0\"",
40 | "prod_keys": "",
41 | "folder": "",
42 | "scan_folders": [],
43 | "gui": false,
44 | "debug": false, # Deprecated, no longer works
45 | "check_for_missing_updates": true,
46 | "check_for_missing_dlc": true,
47 | "organize_options": {
48 | "create_folder_per_game": false,
49 | "rename_files": false,
50 | "delete_empty_folders": false,
51 | "delete_old_update_files": false,
52 | "folder_name_template": "{TITLE_NAME}",
53 | "switch_safe_file_names": true,
54 | "file_name_template": "{TITLE_NAME} ({DLC_NAME})[{TITLE_ID}][v{VERSION}]"
55 | },
56 | "scan_recursively": true,
57 | "gui_page_size": 100
58 | }
59 | ```
60 |
61 | ## Naming template
62 | The following template elements are supported:
63 | - {TITLE_NAME} - game name
64 | - {TITLE_ID} - title id
65 | - {VERSION} - version id (only applicable to files)
66 | - {VERSION_TXT} - version number (like 1.0.0) (only applicable to files)
67 | - {REGION} - region
68 | - {TYPE} - impacts DLCs/updates, will appear as ["UPD","DLC"]
69 | - {DLC_NAME} - DLC name (only applicable to DLCs)
70 |
71 | ## Reporting issues
72 | Please set debug mode to 'true', and attach the slm.log to allow for quicker resolution.
73 |
74 | ## Usage
75 | ##### Windows
76 | - Extract the zip file
77 | - Double click the Exe file
78 | - If you want to use command line mode, update the settings.json with `'GUI':false`
79 | - Open `cmd`
80 | - Run `switch-library-manager.exe`
81 | - Optionally -f `X:\folder\containing\nsp\files"`
82 | - Optionally add `-r` to recursively scan for nested folders
83 | - Edit the settings.json file for additional options
84 |
85 |
86 | ##### macOS or Linux
87 | - Extract the zip file
88 | - Double click the App file
89 | - If you want to use command line mode, update the settings.json with `'GUI':false`
90 | - Open your Terminal
91 | - `cd` to the folder containing `switch-library-manager`
92 | - `chmod +x switch-library-manager` to make it executable
93 | - Run `./switch-library-manager'
94 | - Optionally -f `X:\folder\containing\nsp\files"`
95 | - Optionally add `-r` to recursively scan for nested folders
96 | - Edit the settings.json file for additional options
97 |
98 | ## Building
99 | - Install and setup Go
100 | - Clone the repo: `git clone https://github.com/giwty/switch-library-manager.git`
101 | - Get the bundler `go get -u github.com/asticode/go-astilectron-bundler/...`
102 | - Install bundler `go install github.com/asticode/go-astilectron-bundler/astilectron-bundler`
103 | - Copy bundler binary to the source folder `cd switch-library-manager` and then `mv $HOME/go/bin/astilectron-bundler .`
104 | - Execute `./astilectron-bundler`
105 | - Binaries will be available under output
106 |
107 | #### Thanks
108 | This program relies on [blawar's titledb](https://github.com/blawar/titledb), to get the latest titles and versions.
109 |
--------------------------------------------------------------------------------
/bundler.json:
--------------------------------------------------------------------------------
1 | {
2 | "app_name": "Switch-Library-Manager",
3 | "icon_path_darwin": "resources/icon.icns",
4 | "icon_path_linux": "resources/icon.png",
5 | "icon_path_windows": "resources/icon.ico",
6 | "environments": [
7 | {
8 | "arch": "amd64",
9 | "os": "darwin",
10 | "env": {
11 | "CGO_ENABLED": "1",
12 | "CGO_CFLAGS": "-mmacosx-version-min=10.11",
13 | "CGO_LDFLAGS": "-mmacosx-version-min=10.11"
14 | }
15 | },
16 | {"arch": "amd64", "os": "linux"},
17 | {"arch": "amd64", "os": "windows"}
18 | ]
19 | }
--------------------------------------------------------------------------------
/cmd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giwty/switch-library-manager/f69daa7e5e3bc9fc2cc17ad839a12ac3bf5e0372/cmd.png
--------------------------------------------------------------------------------
/db/persistentDB.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "bytes"
5 | "encoding/gob"
6 | "fmt"
7 | "github.com/boltdb/bolt"
8 | "github.com/giwty/switch-library-manager/settings"
9 | "go.uber.org/zap"
10 | "log"
11 | "path/filepath"
12 | )
13 |
14 | const (
15 | DB_INTERNAL_TABLENAME = "internal-metadata"
16 | )
17 |
18 | type PersistentDB struct {
19 | db *bolt.DB
20 | }
21 |
22 | func NewPersistentDB(baseFolder string) (*PersistentDB, error) {
23 | // Open the my.db data file in your current directory.
24 | // It will be created if it doesn't exist.
25 | db, err := bolt.Open(filepath.Join(baseFolder, "slm.db"), 0600, &bolt.Options{Timeout: 1 * 60})
26 | if err != nil {
27 | log.Fatal(err)
28 | return nil, err
29 | }
30 |
31 | //set DB version
32 | err = db.View(func(tx *bolt.Tx) error {
33 | b := tx.Bucket([]byte(DB_INTERNAL_TABLENAME))
34 | if b == nil {
35 | b, err := tx.CreateBucket([]byte(DB_INTERNAL_TABLENAME))
36 | if b == nil || err != nil {
37 | return fmt.Errorf("create bucket: %s", err)
38 | }
39 | err = b.Put([]byte("app_version"), []byte(settings.SLM_VERSION))
40 | if err != nil {
41 | zap.S().Warnf("failed to save app_version - %v", err)
42 | return err
43 | }
44 | }
45 | return nil
46 | })
47 |
48 | return &PersistentDB{db: db}, nil
49 | }
50 |
51 | func (pd *PersistentDB) Close() {
52 | pd.db.Close()
53 | }
54 |
55 | func (pd *PersistentDB) ClearTable(tableName string) error {
56 | err := pd.db.Update(func(tx *bolt.Tx) error {
57 | err := tx.DeleteBucket([]byte(tableName))
58 | return err
59 | })
60 | return err
61 | }
62 |
63 | func (pd *PersistentDB) AddEntry(tableName string, key string, value interface{}) error {
64 | var err error
65 | err = pd.db.Update(func(tx *bolt.Tx) error {
66 | b := tx.Bucket([]byte(tableName))
67 | if b == nil {
68 | b, err = tx.CreateBucket([]byte(tableName))
69 | if b == nil || err != nil {
70 | return fmt.Errorf("create bucket: %s", err)
71 | }
72 | }
73 | var bytesBuff bytes.Buffer
74 | encoder := gob.NewEncoder(&bytesBuff)
75 | err := encoder.Encode(value)
76 | if err != nil {
77 | return err
78 | }
79 | err = b.Put([]byte(key), bytesBuff.Bytes())
80 | return err
81 | })
82 | return err
83 | }
84 |
85 | func (pd *PersistentDB) GetEntry(tableName string, key string, value interface{}) error {
86 | err := pd.db.View(func(tx *bolt.Tx) error {
87 |
88 | b := tx.Bucket([]byte(tableName))
89 | if b == nil {
90 | return nil
91 | }
92 | v := b.Get([]byte(key))
93 | if v == nil {
94 | return nil
95 | }
96 | d := gob.NewDecoder(bytes.NewReader(v))
97 |
98 | // Decoding the serialized data
99 | err := d.Decode(value)
100 | if err != nil {
101 | return err
102 | }
103 | return nil
104 | })
105 | return err
106 | }
107 |
108 | /*func (pd *PersistentDB) GetEntries() (map[string]*switchfs.ContentMetaAttributes, error) {
109 | pd.db.View(func(tx *bolt.Tx) error {
110 | // Assume bucket exists and has keys
111 | b := tx.Bucket([]byte(METADATA_TABLENAME))
112 |
113 | c := b.Cursor()
114 |
115 | for k, v := c.First(); k != nil; k, v = c.Next() {
116 | fmt.Printf("key=%s, value=%s\n", k, v)
117 | }
118 |
119 | return nil
120 | })
121 | }*/
122 |
--------------------------------------------------------------------------------
/db/switchTitlesDB.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | "encoding/json"
5 | "io"
6 | "strings"
7 | )
8 |
9 | type TitleAttributes struct {
10 | Id string `json:"id"`
11 | Name string `json:"name,omitempty"`
12 | Version json.Number `json:"version,omitempty"`
13 | Region string `json:"region,omitempty"`
14 | ReleaseDate int `json:"releaseDate,omitempty"`
15 | Publisher string `json:"publisher,omitempty"`
16 | IconUrl string `json:"iconUrl,omitempty"`
17 | Screenshots []string `json:"screenshots,omitempty"`
18 | BannerUrl string `json:"bannerUrl,omitempty"`
19 | Description string `json:"description,omitempty"`
20 | Size int `json:"size,omitempty"`
21 | }
22 |
23 | type SwitchTitle struct {
24 | Attributes TitleAttributes
25 | Updates map[int]string
26 | Dlc map[string]TitleAttributes
27 | }
28 |
29 | type SwitchTitlesDB struct {
30 | TitlesMap map[string]*SwitchTitle
31 | }
32 |
33 | func CreateSwitchTitleDB(titlesFile, versionsFile io.Reader) (*SwitchTitlesDB, error) {
34 | //parse the titles objects
35 | var titles = map[string]TitleAttributes{}
36 | err := decodeToJsonObject(titlesFile, &titles)
37 | if err != nil {
38 | return nil, err
39 | }
40 |
41 | //parse the titles objects
42 | //titleID -> versionId-> release date
43 | var versions = map[string]map[int]string{}
44 | err = decodeToJsonObject(versionsFile, &versions)
45 | if err != nil {
46 | return nil, err
47 | }
48 |
49 | result := SwitchTitlesDB{TitlesMap: map[string]*SwitchTitle{}}
50 | for id, attr := range titles {
51 | id = strings.ToLower(id)
52 |
53 | //TitleAttributes id rules:
54 | //main TitleAttributes ends with 000
55 | //Updates ends with 800
56 | //Dlc have a running counter (starting with 001) in the 4 last chars
57 | idPrefix := id[0 : len(id)-4]
58 | switchTitle := &SwitchTitle{Dlc: map[string]TitleAttributes{}}
59 | if t, ok := result.TitlesMap[idPrefix]; ok {
60 | switchTitle = t
61 | }
62 | result.TitlesMap[idPrefix] = switchTitle
63 |
64 | //process Updates
65 | if strings.HasSuffix(id, "800") {
66 | updates := versions[id[0:len(id)-3]+"000"]
67 | switchTitle.Updates = updates
68 | continue
69 | }
70 |
71 | //process main TitleAttributes
72 | if strings.HasSuffix(id, "000") {
73 | switchTitle.Attributes = attr
74 | continue
75 | }
76 |
77 | //not an update, and not main TitleAttributes, so treat it as a DLC
78 | switchTitle.Dlc[id] = attr
79 |
80 | }
81 |
82 | return &result, nil
83 | }
84 |
--------------------------------------------------------------------------------
/db/utils.go:
--------------------------------------------------------------------------------
1 | package db
2 |
3 | import (
4 | bytes2 "bytes"
5 | "encoding/json"
6 | "errors"
7 | "go.uber.org/zap"
8 | "io"
9 | "io/ioutil"
10 | "net"
11 | "net/http"
12 | "os"
13 | "time"
14 | )
15 |
16 | type ProgressUpdater interface {
17 | UpdateProgress(curr int, total int, message string)
18 | }
19 |
20 | func LoadAndUpdateFile(url string, filePath string, etag string) (*os.File, string, error) {
21 |
22 | //create file if not exist
23 | if _, err := os.Stat(filePath); os.IsNotExist(err) {
24 | _, err = os.Create(filePath)
25 | if err != nil {
26 | zap.S().Errorf("Failed to create file %v - %v\n", filePath, err)
27 | return nil, "", err
28 | }
29 | }
30 |
31 | var file *os.File = nil
32 |
33 | //try to check if there is a new version
34 | //if so, save the file
35 | bytes, newEtag, err := downloadBytesFromUrl(url, etag)
36 | if err == nil {
37 | //validate json structure
38 | var test map[string]interface{}
39 | err = decodeToJsonObject(bytes2.NewReader(bytes), &test)
40 | if err == nil {
41 | file, err = saveFile(bytes, filePath)
42 | etag = newEtag
43 | } else {
44 | zap.S().Infof("ignoring new update [%v], reason - [mailformed json file]", url)
45 | }
46 | } else {
47 | zap.S().Infof("file [%v] was not downloaded, reason - [%v]", url, err)
48 | }
49 |
50 | if file == nil {
51 | //load file
52 | file, err = os.Open(filePath)
53 | if err != nil {
54 | zap.S().Infof("ignoring new update [%v], reason - [mailformed json file]", url)
55 | return nil, "", err
56 | }
57 |
58 | fileInfo, err := os.Stat(filePath)
59 | if err != nil || fileInfo.Size() == 0 {
60 | zap.S().Infof("Local file is empty, or corrupted")
61 | return nil, "", errors.New("unable to download switch titles db")
62 | }
63 | }
64 |
65 | return file, etag, err
66 | }
67 |
68 | func decodeToJsonObject(reader io.Reader, target interface{}) error {
69 | err := json.NewDecoder(reader).Decode(target)
70 | return err
71 | }
72 |
73 | func downloadBytesFromUrl(url string, etag string) ([]byte, string, error) {
74 | req, err := http.NewRequest("GET", url, nil)
75 | if err != nil {
76 | return nil, "", err
77 | }
78 | req.Header.Set("If-None-Match", etag)
79 | transport := &http.Transport{
80 | DialContext: (&net.Dialer{
81 | Timeout: 3 * time.Second,
82 | }).DialContext,
83 | }
84 | client := http.Client{
85 | Transport: transport,
86 | }
87 | resp, err := client.Do(req)
88 | if err != nil {
89 | return nil, "", err
90 | }
91 |
92 | if resp.StatusCode >= 400 {
93 | return nil, "", errors.New("got a non 200 response - " + resp.Status)
94 | }
95 | defer resp.Body.Close()
96 | //getting the new etag
97 | etag = resp.Header.Get("Etag")
98 |
99 | if resp.StatusCode == http.StatusOK {
100 | body, err := ioutil.ReadAll(resp.Body)
101 | if err != nil {
102 | return nil, "", err
103 | }
104 | return body, etag, nil
105 | }
106 |
107 | return nil, "", errors.New("no new updates")
108 | }
109 |
110 | func saveFile(bytes []byte, fileName string) (*os.File, error) {
111 |
112 | err := ioutil.WriteFile(fileName, bytes, 0644)
113 | if err != nil {
114 | return nil, err
115 | }
116 |
117 | file, err := os.Open(fileName)
118 | if err != nil {
119 | return nil, err
120 | }
121 | return file, nil
122 | }
123 |
--------------------------------------------------------------------------------
/dlc_ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giwty/switch-library-manager/f69daa7e5e3bc9fc2cc17ad839a12ac3bf5e0372/dlc_ui.png
--------------------------------------------------------------------------------
/fileio/splitFileUtil.go:
--------------------------------------------------------------------------------
1 | package fileio
2 |
3 | import (
4 | "errors"
5 | "github.com/giwty/switch-library-manager/switchfs"
6 | "os"
7 | )
8 |
9 | func ReadSplitFileMetadata(filePath string) (map[string]*switchfs.ContentMetaAttributes, error) {
10 | //check if this is a NS* or XC* file
11 | _, err := switchfs.ReadPfs0File(filePath)
12 | isXCI := false
13 | if err != nil {
14 | _, err = readXciHeader(filePath)
15 | if err != nil {
16 | return nil, errors.New("split file is not an XCI/XCZ or NSP/NSZ")
17 | }
18 | isXCI = true
19 | }
20 |
21 | if isXCI {
22 | return switchfs.ReadXciMetadata(filePath)
23 | } else {
24 | return switchfs.ReadNspMetadata(filePath)
25 | }
26 | }
27 |
28 | func readXciHeader(filePath string) ([]byte, error) {
29 | file, err := os.Open(filePath)
30 | if err != nil {
31 | return nil, err
32 | }
33 |
34 | defer file.Close()
35 |
36 | header := make([]byte, 0x200)
37 | _, err = file.Read(header)
38 | if err != nil {
39 | return nil, err
40 | }
41 |
42 | if string(header[0x100:0x104]) != "HEAD" {
43 | return nil, errors.New("not an XCI/XCZ file")
44 | }
45 | return header, nil
46 | }
47 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/giwty/switch-library-manager
2 |
3 | go 1.12
4 |
5 | require (
6 | github.com/asticode/go-astikit v0.8.0
7 | github.com/asticode/go-astilectron v0.16.0
8 | github.com/asticode/go-astilectron-bootstrap v0.4.1
9 | github.com/avast/retry-go v2.6.1+incompatible
10 | github.com/boltdb/bolt v1.3.1
11 | github.com/go-openapi/strfmt v0.19.2 // indirect
12 | github.com/jedib0t/go-pretty v4.3.0+incompatible
13 | github.com/magiconair/properties v1.8.1
14 | github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2
15 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
16 | github.com/schollz/progressbar/v3 v3.5.0
17 | github.com/stretchr/testify v1.5.1 // indirect
18 | go.uber.org/zap v1.16.0
19 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
20 | gopkg.in/yaml.v2 v2.2.8 // indirect
21 | robpike.io/nihongo v0.0.0-20200511095354-a985f0929cfa
22 | )
23 |
--------------------------------------------------------------------------------
/library_ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giwty/switch-library-manager/f69daa7e5e3bc9fc2cc17ad839a12ac3bf5e0372/library_ui.png
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/giwty/switch-library-manager/settings"
6 | "go.uber.org/zap"
7 | "net/url"
8 | "os"
9 | "path/filepath"
10 | "runtime"
11 | "strings"
12 | )
13 |
14 | func main() {
15 |
16 | exePath, err := os.Executable()
17 | if err != nil {
18 | fmt.Println("failed to get executable directory, please ensure app has sufficient permissions. aborting")
19 | return
20 | }
21 |
22 | workingFolder := filepath.Dir(exePath)
23 |
24 | if runtime.GOOS == "darwin" {
25 | if strings.Contains(workingFolder, ".app") {
26 | appIndex := strings.Index(workingFolder, ".app")
27 | sepIndex := strings.LastIndex(workingFolder[:appIndex], string(os.PathSeparator))
28 | workingFolder = workingFolder[:sepIndex]
29 | }
30 | }
31 |
32 | appSettings := settings.ReadSettings(workingFolder)
33 |
34 | logger := createLogger(workingFolder, appSettings.Debug)
35 |
36 | defer logger.Sync() // flushes buffer, if any
37 | sugar := logger.Sugar()
38 |
39 | sugar.Info("[SLM starts]")
40 | sugar.Infof("[Executable: %v]", exePath)
41 | sugar.Infof("[Working directory: %v]", workingFolder)
42 |
43 | appSettings.GUI = true
44 |
45 | files, err := AssetDir(workingFolder)
46 | if files == nil && err == nil {
47 | appSettings.GUI = false
48 | }
49 |
50 | if appSettings.GUI {
51 | CreateGUI(workingFolder, sugar).Start()
52 | } else {
53 | CreateConsole(workingFolder, sugar).Start()
54 | }
55 |
56 | }
57 |
58 | func createLogger(workingFolder string, debug bool) *zap.Logger {
59 | var config zap.Config
60 | if debug {
61 | config = zap.NewDevelopmentConfig()
62 | } else {
63 | config = zap.NewDevelopmentConfig()
64 | config.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
65 | }
66 | logPath := filepath.Join(workingFolder, "slm.log")
67 | // delete old file
68 | os.Remove(logPath)
69 |
70 | if runtime.GOOS == "windows" {
71 | zap.RegisterSink("winfile", func(u *url.URL) (zap.Sink, error) {
72 | // Remove leading slash left by url.Parse()
73 | return os.OpenFile(u.Path[1:], os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
74 | })
75 | logPath = "winfile:///" + logPath
76 | }
77 |
78 | config.OutputPaths = []string{logPath}
79 | config.ErrorOutputPaths = []string{logPath}
80 | logger, err := config.Build()
81 | if err != nil {
82 | fmt.Printf("failed to create logger - %v", err)
83 | panic(1)
84 | }
85 | zap.ReplaceGlobals(logger)
86 | return logger
87 | }
88 |
--------------------------------------------------------------------------------
/process/incompleteTitleProcessor.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import (
4 | "fmt"
5 | "github.com/giwty/switch-library-manager/db"
6 | "github.com/giwty/switch-library-manager/switchfs"
7 | "go.uber.org/zap"
8 | "sort"
9 | "strconv"
10 | )
11 |
12 | type IncompleteTitle struct {
13 | Attributes db.TitleAttributes
14 | Meta *switchfs.ContentMetaAttributes
15 | LocalUpdate int `json:"local_update"`
16 | LatestUpdate int `json:"latest_update"`
17 | LatestUpdateDate string `json:"latest_update_date"`
18 | MissingDLC []string `json:"missing_dlc"`
19 | }
20 |
21 | func ScanForMissingUpdates(localDB map[string]*db.SwitchGameFiles,
22 | switchDB map[string]*db.SwitchTitle) map[string]IncompleteTitle {
23 |
24 | result := map[string]IncompleteTitle{}
25 |
26 | //iterate over local files, and compare to remote versions
27 | for idPrefix, switchFile := range localDB {
28 |
29 | if switchFile.BaseExist == false {
30 | zap.S().Infof("missing base for game %v", idPrefix)
31 | continue
32 | }
33 |
34 | if _, ok := switchDB[idPrefix]; !ok {
35 | continue
36 | }
37 |
38 | switchTitle := IncompleteTitle{Attributes: switchDB[idPrefix].Attributes, Meta: switchFile.File.Metadata}
39 | //sort the available local versions
40 | localVersions := make([]int, len(switchFile.Updates))
41 | i := 0
42 | for k := range switchFile.Updates {
43 | localVersions[i] = k
44 | i++
45 | }
46 | sort.Ints(localVersions)
47 |
48 | //sort the available remote versions
49 | remoteVersions := make([]int, len(switchDB[idPrefix].Updates))
50 | i = 0
51 | for k := range switchDB[idPrefix].Updates {
52 | remoteVersions[i] = k
53 | i++
54 | }
55 | sort.Ints(remoteVersions)
56 | switchTitle.LocalUpdate = 0
57 | switchTitle.LatestUpdate = 0
58 | if len(localVersions) != 0 {
59 | switchTitle.LocalUpdate = localVersions[len(localVersions)-1]
60 | }
61 |
62 | //process updates
63 | if len(remoteVersions) != 0 {
64 | switchTitle.LatestUpdate = remoteVersions[len(remoteVersions)-1]
65 | switchTitle.LatestUpdateDate = switchDB[idPrefix].Updates[remoteVersions[len(remoteVersions)-1]]
66 | if switchTitle.LocalUpdate < switchTitle.LatestUpdate {
67 | result[switchDB[idPrefix].Attributes.Id] = switchTitle
68 | }
69 | }
70 |
71 | if len(switchDB[idPrefix].Dlc) == 0 {
72 | continue
73 | }
74 |
75 | //process dlc
76 | for k, availableDlc := range switchDB[idPrefix].Dlc {
77 |
78 | if localDlc, ok := switchFile.Dlc[k]; ok {
79 | latestDlcVersion, err := availableDlc.Version.Int64()
80 | if err != nil {
81 | continue
82 | }
83 |
84 | if localDlc.Metadata == nil {
85 | continue
86 | }
87 | if localDlc.Metadata.Version < int(latestDlcVersion) {
88 | updateDate := "-"
89 | if availableDlc.ReleaseDate != 0 {
90 | updateDate = strconv.Itoa(availableDlc.ReleaseDate)
91 | if len(updateDate) > 7 {
92 | updateDate = updateDate[0:4] + "-" + updateDate[4:6] + "-" + updateDate[6:]
93 | }
94 | }
95 |
96 | result[availableDlc.Id] = IncompleteTitle{
97 | Attributes: availableDlc,
98 | LatestUpdate: int(latestDlcVersion),
99 | LocalUpdate: localDlc.Metadata.Version,
100 | LatestUpdateDate: updateDate,
101 | Meta: localDlc.Metadata}
102 | }
103 | }
104 | }
105 |
106 | }
107 | return result
108 | }
109 |
110 | func ScanForMissingDLC(localDB map[string]*db.SwitchGameFiles,
111 | switchDB map[string]*db.SwitchTitle, ignoreTitleIds map[string]struct{}) map[string]IncompleteTitle {
112 | result := map[string]IncompleteTitle{}
113 |
114 | //iterate over local files, and compare to remote versions
115 | for idPrefix, switchFile := range localDB {
116 |
117 | if switchFile.BaseExist == false {
118 | continue
119 | }
120 |
121 | if _, ok := switchDB[idPrefix]; !ok {
122 | continue
123 | }
124 | switchTitle := IncompleteTitle{Attributes: switchDB[idPrefix].Attributes}
125 |
126 | //process dlc
127 | if len(switchDB[idPrefix].Dlc) != 0 {
128 | for k, v := range switchDB[idPrefix].Dlc {
129 | if _, ok := ignoreTitleIds[k]; ok {
130 | continue
131 | }
132 |
133 | if _, ok := switchFile.Dlc[k]; !ok {
134 | switchTitle.MissingDLC = append(switchTitle.MissingDLC, fmt.Sprintf("%v [%v]", v.Name, v.Id))
135 | }
136 | }
137 | if len(switchTitle.MissingDLC) != 0 {
138 | result[switchDB[idPrefix].Attributes.Id] = switchTitle
139 | }
140 | }
141 | }
142 | return result
143 | }
144 |
145 | func ScanForBrokenFiles(localDB map[string]*db.SwitchGameFiles) []db.SwitchFileInfo {
146 | var result []db.SwitchFileInfo
147 |
148 | //iterate over local files, and compare to remote versions
149 | for _, switchFile := range localDB {
150 |
151 | if switchFile.BaseExist == false {
152 | for _, f := range switchFile.Dlc {
153 | result = append(result, f)
154 | }
155 | for _, f := range switchFile.Updates {
156 | result = append(result, f)
157 | }
158 | }
159 | }
160 | return result
161 | }
162 |
--------------------------------------------------------------------------------
/process/organizefolderStructure_test.go:
--------------------------------------------------------------------------------
1 | package process
2 |
3 | import (
4 | "robpike.io/nihongo"
5 | "strings"
6 | "testing"
7 | )
8 |
9 | //var folderIllegalCharsRegex = regexp.MustCompile(`[./\\?%*:;=|"<>]`)
10 |
11 | func TestRename(t *testing.T) {
12 | name := "Pokémon™: Let’s Go, Eevee! 포탈 나이츠"
13 | name = folderIllegalCharsRegex.ReplaceAllString(name, "")
14 | safe := cjk.FindAllString(name, -1)
15 | name = strings.Join(safe, "")
16 | name = nihongo.RomajiString(name)
17 | }
18 |
--------------------------------------------------------------------------------
/prod.keys:
--------------------------------------------------------------------------------
1 | key_area_key_application_00 =
2 | key_area_key_application_01 =
3 | key_area_key_application_02 =
4 | key_area_key_application_03 =
5 | key_area_key_application_04 =
6 | key_area_key_application_05 =
7 | key_area_key_application_06 =
8 | key_area_key_application_07 =
9 | key_area_key_application_08 =
10 | key_area_key_application_09 =
11 | key_area_key_application_0a =
12 | header_key =
--------------------------------------------------------------------------------
/resources/app/lib/js/astiloader/astiloader.css:
--------------------------------------------------------------------------------
1 | .astiloader {
2 | color: #fff;
3 | display: none;
4 | height: 100%;
5 | left: 0;
6 | position: fixed;
7 | top: 0;
8 | width: 100%;
9 | z-index: 1;
10 | }
11 | .astiloader-background {
12 | background-color: #000;
13 | height: 100%;
14 | left: 0;
15 | opacity: 0.7;
16 | position: absolute;
17 | top: 0;
18 | width: 100%;
19 | }
20 | .astiloader-table {
21 | display: table;
22 | height: 100%;
23 | left: 0;
24 | position: absolute;
25 | top: 0;
26 | width: 100%;
27 | }
28 | .astiloader-content {
29 | display: table-cell;
30 | height: 100%;
31 | text-align: center;
32 | vertical-align: middle;
33 | width: 100%;
34 | }
35 | @keyframes asticode-spin {
36 | 0% {
37 | -webkit-transform:rotate(0deg);
38 | transform:rotate(0deg)
39 | }
40 | to {
41 | -webkit-transform:rotate(1turn);
42 | transform:rotate(1turn)
43 | }
44 | }
45 | .astiloader-content img {
46 | animation: asticode-spin 2s linear infinite;
47 | width: 5em;
48 | }
--------------------------------------------------------------------------------
/resources/app/lib/js/astiloader/astiloader.js:
--------------------------------------------------------------------------------
1 | if (typeof asticode === "undefined") {
2 | var asticode = {};
3 | }
4 | asticode.loader = {
5 | scriptDir: document.currentScript.src.match(/.*\//),
6 | hide: function() {
7 | document.getElementById("astiloader").style.display = "none";
8 | },
9 | init: function() {
10 | document.body.innerHTML = `
11 |
15 | ` + document.body.innerHTML
16 | },
17 | show: function() {
18 | document.getElementById("astiloader").style.display = "block";
19 | }
20 | };
--------------------------------------------------------------------------------
/resources/app/lib/js/astiloader/loader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giwty/switch-library-manager/f69daa7e5e3bc9fc2cc17ad839a12ac3bf5e0372/resources/app/lib/js/astiloader/loader.png
--------------------------------------------------------------------------------
/resources/app/lib/js/astimodaler/astimodaler.css:
--------------------------------------------------------------------------------
1 | .astimodaler {
2 | color: #fff;
3 | display: none;
4 | height: 100%;
5 | left: 0;
6 | position: fixed;
7 | top: 0;
8 | width: 100%;
9 | z-index: 1;
10 | }
11 | .astimodaler-background {
12 | background-color: #000;
13 | height: 100%;
14 | left: 0;
15 | opacity: 0.7;
16 | position: absolute;
17 | top: 0;
18 | width: 100%;
19 | }
20 | .astimodaler-table {
21 | display: table;
22 | height: 100%;
23 | left: 0;
24 | position: absolute;
25 | top: 0;
26 | width: 100%;
27 | }
28 | .astimodaler-wrapper {
29 | display: table-cell;
30 | height: 100%;
31 | padding: 2em;
32 | text-align: center;
33 | vertical-align: middle;
34 | width: 100%;
35 | }
36 | #astimodaler-body {
37 | background-color: #f1f1f1;
38 | border-radius: 5px;
39 | color: #333;
40 | margin: auto;
41 | padding: 2em;
42 | position: relative;
43 | }
44 | .astimodaler-close {
45 | cursor: pointer;
46 | padding: 0.65em;
47 | position: absolute;
48 | right: 0;
49 | top: 0;
50 | width: 2.4em;
51 | }
52 | #astimodaler-content {
53 | text-align: left;
54 | }
55 | .astimodaler-title {
56 | border-bottom: solid 1px #aaa;
57 | font-size: 1.2em;
58 | margin-bottom: 1em;
59 | }
60 | .astimodaler-error {
61 | background-color: #f2dede;
62 | border: solid 1px #dca7a7;
63 | border-radius: 0.5em;
64 | color: #a94442;
65 | display: none;
66 | margin-bottom: 1em;
67 | padding: 1em;
68 | text-align: center;
69 | }
70 | .astimodaler-label {
71 | margin-bottom: 0.3em;
72 | }
73 | .astimodaler-required {
74 | color: red;
75 | }
76 | .astimodaler-field-select {
77 | border: solid 1px #ccc;
78 | font: inherit;
79 | margin-bottom: 0.7em;
80 | width: 100%;
81 | }
82 | .astimodaler-field-text, .astimodaler-field-textarea {
83 | border: solid 1px #ccc;
84 | border-radius: 0.5em;
85 | box-shadow: none;
86 | font: inherit;
87 | margin-bottom: 0.7em;
88 | padding: 0.5em 0.7em;
89 | width: 100%;
90 | }
91 | .astimodaler-field-textarea {
92 | height: 15em;
93 | }
94 | .astimodaler-field-text:focus, .astimodaler-field-select:focus {
95 | outline: none;
96 | }
97 | .astimodaler-field-text, .astimodaler-field-textarea {
98 | display: block;
99 | }
100 | .astimodaler-field-submit {
101 | border-radius: 0.5em;
102 | cursor: pointer;
103 | display: inline-block;
104 | font-size: 1em;
105 | padding: 0.7em 1em;
106 | text-align: center;
107 | vertical-align: middle;
108 | width: 100%;
109 | }
110 | #astimodaler-content > div > div:last-child {
111 | margin-bottom: 0;
112 | }
--------------------------------------------------------------------------------
/resources/app/lib/js/astimodaler/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giwty/switch-library-manager/f69daa7e5e3bc9fc2cc17ad839a12ac3bf5e0372/resources/app/lib/js/astimodaler/cross.png
--------------------------------------------------------------------------------
/resources/app/lib/js/astinotifier/astinotifier.css:
--------------------------------------------------------------------------------
1 | .astinotifier {
2 | right: 0;
3 | position: fixed;
4 | top: 0;
5 | z-index: 1;
6 | width: 100%;
7 | }
8 | @media (min-width: 768px) {
9 | .astinotifier {
10 | max-width: 50%;
11 | }
12 | }
13 | .astinotifier-wrapper {
14 | padding: 0.5em 0.5em 0 0.5em;
15 | }
16 | .astinotifier-item {
17 | width: 100%;
18 | border-radius: 4px;
19 | display: table;
20 | padding: 1.2em 0;
21 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
22 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
23 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
24 | box-sizing: border-box;
25 | }
26 | .astinotifier-item.success {
27 | color: #3c763d;
28 | background-color: #dff0d8;
29 | background-image: -webkit-linear-gradient(top, #dff0d8 0, #c8e5bc 100%);
30 | background-image: -o-linear-gradient(top, #dff0d8 0, #c8e5bc 100%);
31 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
32 | background-image: linear-gradient(to bottom, #dff0d8 0, #c8e5bc 100%);
33 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
34 | background-repeat: repeat-x;
35 | border-color: #b2dba1
36 | }
37 | .astinotifier-item.info {
38 | color: #31708f;
39 | background-color: #d9edf7;
40 | background-image: -webkit-linear-gradient(top, #d9edf7 0, #b9def0 100%);
41 | background-image: -o-linear-gradient(top, #d9edf7 0, #b9def0 100%);
42 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
43 | background-image: linear-gradient(to bottom, #d9edf7 0, #b9def0 100%);
44 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
45 | background-repeat: repeat-x;
46 | border-color: #9acfea
47 | }
48 | .astinotifier-item.warning {
49 | color: #8a6d3b;
50 | background-color: #fcf8e3;
51 | background-image: -webkit-linear-gradient(top, #fcf8e3 0, #f8efc0 100%);
52 | background-image: -o-linear-gradient(top, #fcf8e3 0, #f8efc0 100%);
53 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
54 | background-image: linear-gradient(to bottom, #fcf8e3 0, #f8efc0 100%);
55 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
56 | background-repeat: repeat-x;
57 | border-color: #f5e79e
58 | }
59 | .astinotifier-item.error {
60 | color: #a94442;
61 | background-color: #f2dede;
62 | background-image: -webkit-linear-gradient(top, #f2dede 0, #e7c3c3 100%);
63 | background-image: -o-linear-gradient(top, #f2dede 0, #e7c3c3 100%);
64 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
65 | background-image: linear-gradient(to bottom, #f2dede 0, #e7c3c3 100%);
66 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
67 | background-repeat: repeat-x;
68 | border-color: #dca7a7
69 | }
70 | .astinotifier-label {
71 | display: table-cell;
72 | padding-left: 1.2em;
73 | text-align: center;
74 | vertical-align: middle;
75 | }
76 | .astinotifier-close {
77 | cursor: pointer;
78 | display: table-cell;
79 | text-align: right;
80 | vertical-align: middle;
81 | }
82 | .astinotifier-close img {
83 | padding: 0 1.2em;
84 | width: 3.5em;
85 | }
--------------------------------------------------------------------------------
/resources/app/lib/js/astinotifier/astinotifier.js:
--------------------------------------------------------------------------------
1 | if (typeof asticode === "undefined") {
2 | var asticode = {};
3 | }
4 | asticode.notifier = {
5 | scriptDir: document.currentScript.src.match(/.*\//),
6 | error: function(message) {
7 | this.notify("error", message);
8 | },
9 | info: function(message) {
10 | this.notify("info", message);
11 | },
12 | init: function() {
13 | document.body.innerHTML = `` + document.body.innerHTML
14 | },
15 | notify: function(type, message) {
16 | const wrapper = document.createElement("div");
17 | wrapper.className = "astinotifier-wrapper";
18 | const item = document.createElement("div");
19 | item.className = "astinotifier-item " + type;
20 | const label = document.createElement("div");
21 | label.className = "astinotifier-label";
22 | label.innerHTML = message;
23 | const close = document.createElement("div");
24 | close.className = "astinotifier-close";
25 | close.innerHTML = `
`;
26 | close.onclick = function() {
27 | wrapper.remove();
28 | };
29 | item.appendChild(label);
30 | item.appendChild(close);
31 | wrapper.appendChild(item);
32 | document.getElementById("astinotifier").prepend(wrapper);
33 | setTimeout(function() {
34 | close.click();
35 | }, 5000);
36 | },
37 | success: function(message) {
38 | this.notify("success", message);
39 | },
40 | warning: function(message) {
41 | this.notify("warning", message);
42 | }
43 | };
--------------------------------------------------------------------------------
/resources/app/lib/js/astinotifier/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giwty/switch-library-manager/f69daa7e5e3bc9fc2cc17ad839a12ac3bf5e0372/resources/app/lib/js/astinotifier/cross.png
--------------------------------------------------------------------------------
/resources/app/lib/js/astiprogresser/astiprogresser.css:
--------------------------------------------------------------------------------
1 | .astiprogresser {
2 | display: table;
3 | width: 100%;
4 | }
5 |
6 | .astiprogresser-circle-cell, .astiprogresser-bar-cell {
7 | display: table-cell;
8 | text-align: center;
9 | vertical-align: middle;
10 | }
11 |
12 | .astiprogresser-circle-cell {
13 | max-width: 3em;
14 | width: 3em;
15 | }
16 |
17 | .astiprogresser-circle-wrapper {
18 | height: 3em;
19 | position: relative;
20 | width: 100%;
21 | }
22 |
23 | .astiprogresser-circle-cell:first-child .astiprogresser-circle-bar {
24 | float: right;
25 | width: 50%;
26 | }
27 |
28 | .astiprogresser-circle-bar {
29 | height: 1em;
30 | position: relative;
31 | top: 1em;
32 | width: 100%;
33 | }
34 |
35 | .astiprogresser-circle-bar {
36 | background-color: #62c462;
37 | }
38 |
39 | .astiprogresser.error .astiprogresser-circle-bar {
40 | background-color: #ee5f5b;
41 | }
42 |
43 | .astiprogresser-circle-cell.disabled .astiprogresser-circle-bar {
44 | background-color: #d8dee3;
45 | }
46 |
47 | .astiprogresser-circle-cell:last-child .astiprogresser-circle-bar {
48 | float: left;
49 | width: 50%;
50 | }
51 |
52 | .astiprogresser-circle {
53 | border-radius: 50%;
54 | height: 3em;
55 | left: 0;
56 | position: absolute;
57 | top: 0;
58 | width: 3em;
59 | }
60 |
61 | .astiprogresser-circle-cell.disabled .astiprogresser-circle {
62 | background-color: #ffffff;
63 | border: solid 6px #d8dee3;
64 | }
65 |
66 | .astiprogresser-circle-cell.enabled .astiprogresser-circle {
67 | background-color: #ffffff;
68 | border: solid 6px #62c462;
69 | }
70 |
71 | .astiprogresser.error .astiprogresser-circle-cell.enabled .astiprogresser-circle {
72 | background-color: #ffffff;
73 | border: solid 6px #ee5f5b;
74 | }
75 |
76 | .astiprogresser-circle-cell.done .astiprogresser-circle {
77 | background-color: #62c462;
78 | border: solid 6px #62c462;
79 | }
80 |
81 | .astiprogresser.error .astiprogresser-circle-cell.done .astiprogresser-circle {
82 | background-color: #ee5f5b;
83 | border: solid 6px #ee5f5b;
84 | }
85 |
86 | .astiprogresser-circle-check {
87 | display: none;
88 | height: 1.5em;
89 | left: 0.75em;
90 | position: absolute;
91 | top: 0.75em;
92 | }
93 |
94 | .astiprogresser-circle-cell.done .astiprogresser-circle-check {
95 | display: block;
96 | }
97 |
98 | .astiprogresser-bar-wrapper {
99 | background-color: #d8dee3;
100 | height: 1em;
101 | top: 1em;
102 | width: 100%;
103 | }
104 |
105 | .astiprogresser-label {
106 | height: 1.5em;
107 | }
108 |
109 | .astiprogresser-label {
110 | color: #62c462;
111 | }
112 |
113 | .astiprogresser.error .astiprogresser-label {
114 | color: #ee5f5b;
115 | }
116 |
117 | .astiprogresser-bar-cell.disabled .astiprogresser-label {
118 | color: #c1c1c1;
119 | }
120 |
121 | .astiprogresser-bar {
122 | background-color: #62c462;
123 | height: 100%;
124 | width: 0;
125 | }
126 |
127 | .astiprogresser.error .astiprogresser-bar {
128 | background-color: #ee5f5b;
129 | }
--------------------------------------------------------------------------------
/resources/app/lib/js/astiprogresser/check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giwty/switch-library-manager/f69daa7e5e3bc9fc2cc17ad839a12ac3bf5e0372/resources/app/lib/js/astiprogresser/check.png
--------------------------------------------------------------------------------
/resources/app/lib/js/astitools/astitools.js:
--------------------------------------------------------------------------------
1 | if (typeof asticode === "undefined") {
2 | var asticode = {}
3 | }
4 | asticode.tools = {
5 | sendHttp: function(options) {
6 | const req = new XMLHttpRequest()
7 | req.onreadystatechange = function() {
8 | if (this.readyState === XMLHttpRequest.DONE) {
9 | // Parse data
10 | let data = {responseText: this.responseText, err: null, status: this.status}
11 | if (this.responseText.length > 0 && this.getResponseHeader("content-type").indexOf("application/json") > -1) {
12 | try {
13 | data.responseJSON = JSON.parse(this.responseText)
14 | } catch (e) {
15 | data.err = e
16 | }
17 | }
18 |
19 | // Callbacks
20 | if (data.err === null && this.status >= 200 && this.status < 300) {
21 | if (typeof options.success !== "undefined") options.success(data)
22 | } else {
23 | if (typeof options.error !== "undefined") options.error(data)
24 | }
25 | }
26 | }
27 | const query = Object.assign({}, options.query)
28 | req.open(options.method, options.url + "?" + Object.keys(query).map(k => encodeURIComponent(k) + '=' + encodeURIComponent(query[k])).join('&'), true)
29 | req.send(options.payload)
30 | },
31 | appendSorted: function(rootSelector, data, map) {
32 | // Find proper key
33 | let key;
34 | for (let k in map) {
35 | if (map.hasOwnProperty(k)) {
36 | if (map[k].name > data.name && (typeof key === "undefined" || map[key].name > map[k].name)) {
37 | key = k;
38 | break;
39 | }
40 | }
41 | }
42 |
43 | // Update html
44 | if (typeof key !== "undefined") {
45 | rootSelector.insertBefore(data.html.wrapper, map[key].html.wrapper);
46 | } else {
47 | rootSelector.append(data.html.wrapper);
48 | }
49 | },
50 | removeClass: function(node, name) {
51 | // Get class name funcs
52 | let classNameFuncs = this.classNameFuncs(node)
53 |
54 | // No class name funcs
55 | if (!classNameFuncs) return
56 |
57 | // Remove
58 | let names = classNameFuncs[0]().split(" ")
59 | for (let idx = 0; idx < names.length; idx++) {
60 | if (names[idx] === name) {
61 | names.splice(idx, 1)
62 | idx--
63 | }
64 | }
65 |
66 | // Set class name
67 | classNameFuncs[1](names.join(" "))
68 | },
69 | addClass: function(node, name) {
70 | // Get class name funcs
71 | let classNameFuncs = this.classNameFuncs(node)
72 |
73 | // No class name funcs
74 | if (!classNameFuncs) return
75 |
76 | // Set class name
77 | classNameFuncs[1](classNameFuncs[0]() + " " + name)
78 | },
79 | classNameFuncs: function(node) {
80 | switch (typeof node.className) {
81 | case "string":
82 | return [
83 | function() {
84 | return node.className
85 | },
86 | function(name) {
87 | node.className = name
88 | },
89 | ]
90 | case "object":
91 | switch (node.className.constructor.name) {
92 | case "SVGAnimatedString":
93 | return [
94 | function() {
95 | return node.className.baseVal
96 | },
97 | function(name) {
98 | node.className.baseVal = name
99 | }
100 | ]
101 | default:
102 | return false
103 | }
104 | default:
105 | return false
106 | }
107 | },
108 | scrollDownTo: function(y, maxDuration) {
109 | if (typeof maxDuration === "undefined") maxDuration = 500
110 | let previousAt = (new Date()).getTime()
111 | const intervalDuration = 5
112 | const intervalScroll = (y - window.scrollY) / (maxDuration / intervalDuration)
113 | const i = setInterval(function() {
114 | if (window.scrollY >= y) {
115 | clearInterval(i)
116 | return
117 | }
118 | const now = (new Date()).getTime()
119 | const n = Math.floor((now - previousAt) / intervalDuration)
120 | previousAt = now
121 | window.scrollTo(0, window.scrollY + Math.min(n*intervalScroll, y-window.scrollY))
122 | }, intervalDuration)
123 | },
124 | isEmail: function(text) {
125 | return /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(text)
126 | }
127 | }
--------------------------------------------------------------------------------
/resources/app/lib/js/astiws/astiws.js:
--------------------------------------------------------------------------------
1 | if (typeof asticode === "undefined") {
2 | var asticode = {};
3 | }
4 | asticode.ws = {
5 | init: function(options) {
6 | const self = this
7 |
8 | if (!self.windowUnloadHandled) {
9 | window.onbeforeunload = function() {
10 | self.showOfflineMessage = false;
11 | if (typeof self.s !== "undefined") {
12 | self.s.close();
13 | }
14 | }
15 | self.windowUnloadHandled = true
16 | }
17 |
18 | const okRequest = options.okRequest
19 | okRequest.error = function() {
20 | setTimeout(function() {
21 | self.init(options)
22 | }, 1000)
23 | }
24 | okRequest.success = function() {
25 | // Init websocket
26 | const query = Object.assign({}, options.query)
27 | self.s = new WebSocket(options.url + "?" + Object.keys(query).map(k => encodeURIComponent(k) + '=' + encodeURIComponent(query[k])).join('&'))
28 |
29 | // Declare functions
30 | let intervalPing
31 | self.s.onclose = function() {
32 | if (self.showOfflineMessage) {
33 | self.showOfflineMessage = false
34 | if (typeof options.offline !== "undefined") options.offline()
35 | }
36 | clearInterval(intervalPing)
37 | setTimeout(function() {
38 | self.init(options)
39 | }, 1000)
40 | }
41 | self.s.onopen = function() {
42 | self.showOfflineMessage = true
43 | if (typeof options.pingPeriod !== "undefined") {
44 | let pingFunc = options.pingFunc
45 | if (typeof pingFunc === "undefined") {
46 | pingFunc = function(self) { self.send("ping") }
47 | }
48 | intervalPing = setInterval(function() { pingFunc(self) }, options.pingPeriod / 1e6)
49 | }
50 | if (typeof options.open !== "undefined") options.open()
51 | }
52 | self.s.onmessage = function(event) {
53 | let data = JSON.parse(event.data)
54 | if (typeof options.message !== "undefined") options.message(data.event_name, data.payload)
55 | if (typeof options.messageRaw !== "undefined") options.messageRaw(data)
56 | }
57 | }
58 |
59 | asticode.tools.sendHttp(okRequest)
60 | },
61 | send: function(event_name, payload) {
62 | this.sendJSON({event_name: event_name, payload: payload})
63 | },
64 | sendJSON: function(data) {
65 | this.s.send(JSON.stringify(data))
66 | }
67 | }
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/jquery_wrapper.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 |
3 | /*
4 | * This file is part of the Tabulator package.
5 | *
6 | * (c) Oliver Folkerd
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | *
11 | * Full Documentation & Demos can be found at: http://olifolkerd.github.io/tabulator/
12 | *
13 | */
14 |
15 | (function (factory) {
16 | "use strict";
17 |
18 | if (typeof define === 'function' && define.amd) {
19 | define(['jquery', 'resources/app/lib/js/tabulator/tabulator', 'jquery-ui'], factory);
20 | } else if (typeof module !== 'undefined' && module.exports) {
21 | module.exports = factory(require('jquery'), require('resources/app/lib/js/tabulator/tabulator'), require('jquery-ui'));
22 | } else {
23 | factory(jQuery, Tabulator);
24 | }
25 | })(function ($, Tabulator) {
26 |
27 | $.widget("ui.tabulator", {
28 | _create: function _create() {
29 | var options = Object.assign({}, this.options);
30 |
31 | delete options.create;
32 | delete options.disabled;
33 |
34 | this.table = new Tabulator(this.element[0], options);
35 |
36 | //map tabulator functions to jquery wrapper
37 | for (var key in Tabulator.prototype) {
38 | if (typeof Tabulator.prototype[key] === "function" && key.charAt(0) !== "_") {
39 | this[key] = this.table[key].bind(this.table);
40 | }
41 | }
42 | },
43 |
44 | _setOption: function _setOption(option, value) {
45 | console.error("Tabulator jQuery wrapper does not support setting options after the table has been instantiated");
46 | },
47 |
48 | _destroy: function _destroy(option, value) {
49 | this.table.destroy();
50 | }
51 | });
52 | });
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/jquery_wrapper.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | !function(e){"use strict";"function"==typeof define&&define.amd?define(["jquery",`resources`,"jquery-ui"],e):"undefined"!=typeof module&&module.exports?module.exports=e(require("jquery"),require(`resources`),require("jquery-ui")):e(jQuery,Tabulator)}(function(e, t){e.widget("ui.tabulator",{_create:function(){var e=Object.assign({},this.options);delete e.create,delete e.disabled,this.table=new t(this.element[0],e);for(var o in t.prototype)"function"==typeof t.prototype[o]&&"_"!==o.charAt(0)&&(this[o]=this.table[o].bind(this.table))},_setOption:function(e, t){console.error("Tabulator jQuery wrapper does not support setting options after the table has been instantiated")},_destroy:function(e, t){this.table.destroy()}})});
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/accessor.js:
--------------------------------------------------------------------------------
1 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
2 |
3 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
4 |
5 | var Accessor = function Accessor(table) {
6 | this.table = table; //hold Tabulator object
7 | this.allowedTypes = ["", "data", "download", "clipboard", "print", "htmlOutput"]; //list of accessor types
8 | };
9 |
10 | //initialize column accessor
11 | Accessor.prototype.initializeColumn = function (column) {
12 | var self = this,
13 | match = false,
14 | config = {};
15 |
16 | this.allowedTypes.forEach(function (type) {
17 | var key = "accessor" + (type.charAt(0).toUpperCase() + type.slice(1)),
18 | accessor;
19 |
20 | if (column.definition[key]) {
21 | accessor = self.lookupAccessor(column.definition[key]);
22 |
23 | if (accessor) {
24 | match = true;
25 |
26 | config[key] = {
27 | accessor: accessor,
28 | params: column.definition[key + "Params"] || {}
29 | };
30 | }
31 | }
32 | });
33 |
34 | if (match) {
35 | column.modules.accessor = config;
36 | }
37 | };
38 |
39 | Accessor.prototype.lookupAccessor = function (value) {
40 | var accessor = false;
41 |
42 | //set column accessor
43 | switch (typeof value === "undefined" ? "undefined" : _typeof(value)) {
44 | case "string":
45 | if (this.accessors[value]) {
46 | accessor = this.accessors[value];
47 | } else {
48 | console.warn("Accessor Error - No such accessor found, ignoring: ", value);
49 | }
50 | break;
51 |
52 | case "function":
53 | accessor = value;
54 | break;
55 | }
56 |
57 | return accessor;
58 | };
59 |
60 | //apply accessor to row
61 | Accessor.prototype.transformRow = function (dataIn, type) {
62 | var self = this,
63 | key = "accessor" + (type.charAt(0).toUpperCase() + type.slice(1));
64 |
65 | //clone data object with deep copy to isolate internal data from returned result
66 | var data = Tabulator.prototype.helpers.deepClone(dataIn || {});
67 |
68 | self.table.columnManager.traverse(function (column) {
69 | var value, accessor, params, component;
70 |
71 | if (column.modules.accessor) {
72 |
73 | accessor = column.modules.accessor[key] || column.modules.accessor.accessor || false;
74 |
75 | if (accessor) {
76 | value = column.getFieldValue(data);
77 |
78 | if (value != "undefined") {
79 | component = column.getComponent();
80 | params = typeof accessor.params === "function" ? accessor.params(value, data, type, component) : accessor.params;
81 | column.setFieldValue(data, accessor.accessor(value, data, type, params, component));
82 | }
83 | }
84 | }
85 | });
86 |
87 | return data;
88 | },
89 |
90 | //default accessors
91 | Accessor.prototype.accessors = {};
92 |
93 | Tabulator.prototype.registerModule("accessor", Accessor);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/accessor.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(o){return typeof o}:function(o){return o&&"function"==typeof Symbol&&o.constructor===Symbol&&o!==Symbol.prototype?"symbol":typeof o},Accessor=function(o){this.table=o,this.allowedTypes=["","data","download","clipboard","print","htmlOutput"]};Accessor.prototype.initializeColumn=function(o){var e=this,s=!1,r={};this.allowedTypes.forEach(function(t){var c,a="accessor"+(t.charAt(0).toUpperCase()+t.slice(1));o.definition[a]&&(c=e.lookupAccessor(o.definition[a]))&&(s=!0,r[a]={accessor:c,params:o.definition[a+"Params"]||{}})}),s&&(o.modules.accessor=r)},Accessor.prototype.lookupAccessor=function(o){var e=!1;switch(void 0===o?"undefined":_typeof(o)){case"string":this.accessors[o]?e=this.accessors[o]:console.warn("Accessor Error - No such accessor found, ignoring: ",o);break;case"function":e=o}return e},Accessor.prototype.transformRow=function(o,e){var s=this,r="accessor"+(e.charAt(0).toUpperCase()+e.slice(1)),t=Tabulator.prototype.helpers.deepClone(o||{});return s.table.columnManager.traverse(function(o){var s,c,a,n;o.modules.accessor&&(c=o.modules.accessor[r]||o.modules.accessor.accessor||!1)&&"undefined"!=(s=o.getFieldValue(t))&&(n=o.getComponent(),a="function"==typeof c.params?c.params(s,t,e,n):c.params,o.setFieldValue(t,c.accessor(s,t,e,a,n)))}),t},Accessor.prototype.accessors={},Tabulator.prototype.registerModule("accessor",Accessor);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/clipboard.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Clipboard=function(t){this.table=t,this.mode=!0,this.pasteParser=function(){},this.pasteAction=function(){},this.customSelection=!1,this.rowRange=!1,this.blocked=!0};Clipboard.prototype.initialize=function(){var t=this;this.mode=this.table.options.clipboard,this.rowRange=this.table.options.clipboardCopyRowRange,!0!==this.mode&&"copy"!==this.mode||this.table.element.addEventListener("copy",function(e){var a,o,i;if(!t.blocked){if(e.preventDefault(),t.customSelection)a=t.customSelection,t.table.options.clipboardCopyFormatter&&(a=t.table.options.clipboardCopyFormatter("plain",a));else{var i=t.table.modules.export.generateExportList(t.rowRange,t.table.options.clipboardCopyStyled,t.table.options.clipboardCopyConfig,"clipboard");o=t.table.modules.export.genereateHTMLTable(i),a=o?t.generatePlainContent(i):"",t.table.options.clipboardCopyFormatter&&(a=t.table.options.clipboardCopyFormatter("plain",a),o=t.table.options.clipboardCopyFormatter("html",o))}window.clipboardData&&window.clipboardData.setData?window.clipboardData.setData("Text",a):e.clipboardData&&e.clipboardData.setData?(e.clipboardData.setData("text/plain",a),o&&e.clipboardData.setData("text/html",o)):e.originalEvent&&e.originalEvent.clipboardData.setData&&(e.originalEvent.clipboardData.setData("text/plain",a),o&&e.originalEvent.clipboardData.setData("text/html",o)),t.table.options.clipboardCopied.call(t.table,a,o),t.reset()}}),!0!==this.mode&&"paste"!==this.mode||this.table.element.addEventListener("paste",function(e){t.paste(e)}),this.setPasteParser(this.table.options.clipboardPasteParser),this.setPasteAction(this.table.options.clipboardPasteAction)},Clipboard.prototype.reset=function(){this.blocked=!1,this.originalSelectionText=""},Clipboard.prototype.generatePlainContent=function(t){var e=[];return t.forEach(function(t){var a=[];t.columns.forEach(function(e){var o="";if(e)switch("group"===t.type&&(e.value=e.component.getKey()),_typeof(e.value)){case"object":o=JSON.stringify(e.value);break;case"undefined":case"null":o="";break;default:o=e.value}a.push(o)}),e.push(a.join("\t"))}),e.join("\n")},Clipboard.prototype.copy=function(t,e){var t,a,o;this.blocked=!1,this.customSelection=!1,!0!==this.mode&&"copy"!==this.mode||(this.rowRange=t||this.table.options.clipboardCopyRowRange,void 0!==window.getSelection&&void 0!==document.createRange?(t=document.createRange(),t.selectNodeContents(this.table.element),a=window.getSelection(),a.toString()&&e&&(this.customSelection=a.toString()),a.removeAllRanges(),a.addRange(t)):void 0!==document.selection&&void 0!==document.body.createTextRange&&(o=document.body.createTextRange(),o.moveToElementText(this.table.element),o.select()),document.execCommand("copy"),a&&a.removeAllRanges())},Clipboard.prototype.setPasteAction=function(t){switch(void 0===t?"undefined":_typeof(t)){case"string":this.pasteAction=this.pasteActions[t],this.pasteAction||console.warn("Clipboard Error - No such paste action found:",t);break;case"function":this.pasteAction=t}},Clipboard.prototype.setPasteParser=function(t){switch(void 0===t?"undefined":_typeof(t)){case"string":this.pasteParser=this.pasteParsers[t],this.pasteParser||console.warn("Clipboard Error - No such paste parser found:",t);break;case"function":this.pasteParser=t}},Clipboard.prototype.paste=function(t){var e,a,o;this.checkPaseOrigin(t)&&(e=this.getPasteData(t),a=this.pasteParser.call(this,e),a?(t.preventDefault(),this.table.modExists("mutator")&&(a=this.mutateData(a)),o=this.pasteAction.call(this,a),this.table.options.clipboardPasted.call(this.table,e,a,o)):this.table.options.clipboardPasteError.call(this.table,e))},Clipboard.prototype.mutateData=function(t){var e=this,a=[];return Array.isArray(t)?t.forEach(function(t){a.push(e.table.modules.mutator.transformRow(t,"clipboard"))}):a=t,a},Clipboard.prototype.checkPaseOrigin=function(t){var e=!0;return("DIV"!=t.target.tagName||this.table.modules.edit.currentCell)&&(e=!1),e},Clipboard.prototype.getPasteData=function(t){var e;return window.clipboardData&&window.clipboardData.getData?e=window.clipboardData.getData("Text"):t.clipboardData&&t.clipboardData.getData?e=t.clipboardData.getData("text/plain"):t.originalEvent&&t.originalEvent.clipboardData.getData&&(e=t.originalEvent.clipboardData.getData("text/plain")),e},Clipboard.prototype.pasteParsers={table:function(t){var e=[],a=!0,o=this.table.columnManager.columns,i=[],n=[];return t=t.split("\n"),t.forEach(function(t){e.push(t.split("\t"))}),!(!e.length||1===e.length&&e[0].length<2)&&(!0,e[0].forEach(function(t){var e=o.find(function(e){return t&&e.definition.title&&t.trim()&&e.definition.title.trim()===t.trim()});e?i.push(e):a=!1}),a||(a=!0,i=[],e[0].forEach(function(t){var e=o.find(function(e){return t&&e.field&&t.trim()&&e.field.trim()===t.trim()});e?i.push(e):a=!1}),a||(i=this.table.columnManager.columnsByIndex)),a&&e.shift(),e.forEach(function(t){var e={};t.forEach(function(t,a){i[a]&&(e[i[a].field]=t)}),n.push(e)}),n)}},Clipboard.prototype.pasteActions={replace:function(t){return this.table.setData(t)},update:function(t){return this.table.updateOrAddData(t)},insert:function(t){return this.table.addData(t)}},Tabulator.prototype.registerModule("clipboard",Clipboard);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/download.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Download=function(e){this.table=e};Download.prototype.download=function(e,o,t,n,a){function r(t,n){a?!0===a?l.triggerDownload(t,n,e,o,!0):a(t):l.triggerDownload(t,n,e,o)}var l=this,s=!1;if("function"==typeof e?s=e:l.downloaders[e]?s=l.downloaders[e]:console.warn("Download Error - No such download type found: ",e),s){var i=this.generateExportList(n);s.call(this.table,i,t||{},r)}},Download.prototype.generateExportList=function(e){var o=this.table.modules.export.generateExportList(this.table.options.downloadConfig,!1,e||this.table.options.downloadRowRange,"download"),t=this.table.options.groupHeaderDownload;return t&&!Array.isArray(t)&&(t=[t]),o.forEach(function(e){var o;"group"===e.type&&(o=e.columns[0],t&&t[e.indent]&&(o.value=t[e.indent](o.value,e.component._group.getRowCount(),e.component._group.getData(),e.component)))}),o},Download.prototype.triggerDownload=function(e,o,t,n,a){var r=document.createElement("a"),l=new Blob([e],{type:o}),n=n||"Tabulator."+("function"==typeof t?"txt":t);(l=this.table.options.downloadReady.call(this.table,e,l))&&(a?window.open(window.URL.createObjectURL(l)):navigator.msSaveOrOpenBlob?navigator.msSaveOrOpenBlob(l,n):(r.setAttribute("href",window.URL.createObjectURL(l)),r.setAttribute("download",n),r.style.display="none",document.body.appendChild(r),r.click(),document.body.removeChild(r)),this.table.options.downloadComplete&&this.table.options.downloadComplete())},Download.prototype.commsReceived=function(e,o,t){switch(o){case"intercept":this.download(t.type,"",t.options,t.active,t.intercept)}},Download.prototype.downloaders={csv:function(e,o,t){var n=o&&o.delimiter?o.delimiter:",",a=[],r=[];e.forEach(function(e){var o=[];switch(e.type){case"group":console.warn("Download Warning - CSV downloader cannot process row groups");break;case"calc":console.warn("Download Warning - CSV downloader cannot process column calculations");break;case"header":e.columns.forEach(function(e,o){e&&1===e.depth&&(r[o]=void 0===e.value||"null"==typeof e.value?"":e.value)});break;case"row":e.columns.forEach(function(e){if(e){switch(_typeof(e.value)){case"object":e.value=JSON.stringify(e.value);break;case"undefined":case"null":e.value=""}o.push('"'+String(e.value).split('"').join('""')+'"')}}),a.push(o.join(n))}}),r.length&&(a=[r].concat(a)),a=a.join("\n"),o.bom&&(a="\ufeff"+a),t(a,"text/csv")},json:function(e,o,t){var n=[];e.forEach(function(e){var o={};switch(e.type){case"header":break;case"group":console.warn("Download Warning - JSON downloader cannot process row groups");break;case"calc":console.warn("Download Warning - JSON downloader cannot process column calculations");break;case"row":e.columns.forEach(function(e){e&&(o[e.component.getField()]=e.value)}),n.push(o)}}),n=JSON.stringify(n,null,"\t"),t(n,"application/json")},pdf:function(e,o,t){function n(e,o){var t=[];return e.columns.forEach(function(e){var n;if(e){switch(_typeof(e.value)){case"object":e.value=JSON.stringify(e.value);break;case"undefined":case"null":e.value=""}n={content:e.value,colSpan:e.width,rowSpan:e.height},o&&(n.styles=o),t.push(n)}else t.push("")}),t}var a=[],r=[],l={},s=o.rowGroupStyles||{fontStyle:"bold",fontSize:12,cellPadding:6,fillColor:220},i=o.rowCalcStyles||{fontStyle:"bold",fontSize:10,cellPadding:4,fillColor:232},c=o.jsPDF||{},u=o&&o.title?o.title:"";c.orientation||(c.orientation=o.orientation||"landscape"),c.unit||(c.unit="pt"),e.forEach(function(e){switch(e.type){case"header":a.push(n(e));break;case"group":r.push(n(e,s));break;case"calc":r.push(n(e,i));break;case"row":r.push(n(e))}});var d=new jsPDF(c);o&&o.autoTable&&(l="function"==typeof o.autoTable?o.autoTable(d)||{}:o.autoTable),u&&(l.addPageContent=function(e){d.text(u,40,30)}),l.head=a,l.body=r,d.autoTable(l),o&&o.documentProcessing&&o.documentProcessing(d),t(d.output("arraybuffer"),"application/pdf")},xlsx:function(e,o,t){function n(){var o=[],t=[],n={},a={s:{c:0,r:0},e:{c:e[0]?e[0].columns.reduce(function(e,o){return e+(o&&o.width?o.width:1)},0):0,r:e.length}};return e.forEach(function(e,n){var a=[];e.columns.forEach(function(e,o){e?(a.push(e.value instanceof Date||"object"!==_typeof(e.value)?e.value:JSON.stringify(e.value)),(e.width>1||e.height>-1)&&t.push({s:{r:n,c:o},e:{r:n+e.height-1,c:o+e.width-1}})):a.push("")}),o.push(a)}),XLSX.utils.sheet_add_aoa(n,o),n["!ref"]=XLSX.utils.encode_range(a),t.length&&(n["!merges"]=t),n}var a,r=this,l=o.sheetName||"Sheet1",s=XLSX.utils.book_new();if(s.SheetNames=[],s.Sheets={},o.sheetOnly)return void t(n());if(o.sheets)for(var i in o.sheets)!0===o.sheets[i]?(s.SheetNames.push(i),s.Sheets[i]=n()):(s.SheetNames.push(i),this.table.modules.comms.send(o.sheets[i],"download","intercept",{type:"xlsx",options:{sheetOnly:!0},active:r.active,intercept:function(e){s.Sheets[i]=e}}));else s.SheetNames.push(l),s.Sheets[l]=n();o.documentProcessing&&(s=o.documentProcessing(s)),a=XLSX.write(s,{bookType:"xlsx",bookSST:!0,type:"binary"}),t(function(e){for(var o=new ArrayBuffer(e.length),t=new Uint8Array(o),n=0;n!=e.length;++n)t[n]=255&e.charCodeAt(n);return o}(a),"application/octet-stream")},html:function(e,o,t){this.modExists("export",!0)&&t(this.modules.export.genereateHTMLTable(e),"text/html")}},Tabulator.prototype.registerModule("download",Download);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/frozen_columns.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var FrozenColumns=function(t){this.table=t,this.leftColumns=[],this.rightColumns=[],this.leftMargin=0,this.rightMargin=0,this.rightPadding=0,this.initializationMode="left",this.active=!1,this.scrollEndTimer=!1};FrozenColumns.prototype.reset=function(){this.initializationMode="left",this.leftColumns=[],this.rightColumns=[],this.leftMargin=0,this.rightMargin=0,this.rightMargin=0,this.active=!1,this.table.columnManager.headersElement.style.marginLeft=0,this.table.columnManager.element.style.paddingRight=0},FrozenColumns.prototype.initializeColumn=function(t){var e={margin:0,edge:!1};t.isGroup||(this.frozenCheck(t)?(e.position=this.initializationMode,"left"==this.initializationMode?this.leftColumns.push(t):this.rightColumns.unshift(t),this.active=!0,t.modules.frozen=e):this.initializationMode="right")},FrozenColumns.prototype.frozenCheck=function(t){return t.parent.isGroup&&t.definition.frozen&&console.warn("Frozen Column Error - Parent column group must be frozen, not individual columns or sub column groups"),t.parent.isGroup?this.frozenCheck(t.parent):t.definition.frozen},FrozenColumns.prototype.scrollHorizontal=function(){var t,e=this;this.active&&(clearTimeout(this.scrollEndTimer),this.scrollEndTimer=setTimeout(function(){e.layout()},100),t=this.table.rowManager.getVisibleRows(),this.calcMargins(),this.layoutColumnPosition(),this.layoutCalcRows(),t.forEach(function(t){"row"===t.type&&e.layoutRow(t)}),this.table.rowManager.tableElement.style.marginRight=this.rightMargin)},FrozenColumns.prototype.calcMargins=function(){this.leftMargin=this._calcSpace(this.leftColumns,this.leftColumns.length)+"px",this.table.columnManager.headersElement.style.marginLeft=this.leftMargin,this.rightMargin=this._calcSpace(this.rightColumns,this.rightColumns.length)+"px",this.table.columnManager.element.style.paddingRight=this.rightMargin,this.rightPadding=this.table.rowManager.element.clientWidth+this.table.columnManager.scrollLeft},FrozenColumns.prototype.layoutCalcRows=function(){this.table.modExists("columnCalcs")&&(this.table.modules.columnCalcs.topInitialized&&this.table.modules.columnCalcs.topRow&&this.layoutRow(this.table.modules.columnCalcs.topRow),this.table.modules.columnCalcs.botInitialized&&this.table.modules.columnCalcs.botRow&&this.layoutRow(this.table.modules.columnCalcs.botRow))},FrozenColumns.prototype.layoutColumnPosition=function(t){var e=this,o=[];this.leftColumns.forEach(function(n,l){if(n.modules.frozen.margin=e._calcSpace(e.leftColumns,l)+e.table.columnManager.scrollLeft+"px",l==e.leftColumns.length-1?n.modules.frozen.edge=!0:n.modules.frozen.edge=!1,n.parent.isGroup){var i=e.getColGroupParentElement(n);o.includes(i)||(e.layoutElement(i,n),o.push(i)),n.modules.frozen.edge&&i.classList.add("tabulator-frozen-"+n.modules.frozen.position)}else e.layoutElement(n.getElement(),n);t&&n.cells.forEach(function(t){e.layoutElement(t.getElement(),n)})}),this.rightColumns.forEach(function(o,n){o.modules.frozen.margin=e.rightPadding-e._calcSpace(e.rightColumns,n+1)+"px",n==e.rightColumns.length-1?o.modules.frozen.edge=!0:o.modules.frozen.edge=!1,o.parent.isGroup?e.layoutElement(e.getColGroupParentElement(o),o):e.layoutElement(o.getElement(),o),t&&o.cells.forEach(function(t){e.layoutElement(t.getElement(),o)})})},FrozenColumns.prototype.getColGroupParentElement=function(t){return t.parent.isGroup?this.getColGroupParentElement(t.parent):t.getElement()},FrozenColumns.prototype.layout=function(){var t=this;t.active&&(this.calcMargins(),t.table.rowManager.getDisplayRows().forEach(function(e){"row"===e.type&&t.layoutRow(e)}),this.layoutCalcRows(),this.layoutColumnPosition(!0),this.table.rowManager.tableElement.style.marginRight=this.rightMargin)},FrozenColumns.prototype.layoutRow=function(t){var e=this;t.getElement().style.paddingLeft=this.leftMargin,this.leftColumns.forEach(function(o){var n=t.getCell(o);n&&e.layoutElement(n.getElement(),o)}),this.rightColumns.forEach(function(o){var n=t.getCell(o);n&&e.layoutElement(n.getElement(),o)})},FrozenColumns.prototype.layoutElement=function(t,e){e.modules.frozen&&(t.style.position="absolute",t.style.left=e.modules.frozen.margin,t.classList.add("tabulator-frozen"),e.modules.frozen.edge&&t.classList.add("tabulator-frozen-"+e.modules.frozen.position))},FrozenColumns.prototype._calcSpace=function(t,e){for(var o=0,n=0;n -1) {
41 | output.splice(index, 1);
42 | }
43 | });
44 |
45 | return output;
46 | };
47 |
48 | FrozenRows.prototype.freezeRow = function (row) {
49 | if (!row.modules.frozen) {
50 | row.modules.frozen = true;
51 | this.topElement.appendChild(row.getElement());
52 | row.initialize();
53 | row.normalizeHeight();
54 | this.table.rowManager.adjustTableSize();
55 |
56 | this.rows.push(row);
57 |
58 | this.table.rowManager.refreshActiveData("display");
59 |
60 | this.styleRows();
61 | } else {
62 | console.warn("Freeze Error - Row is already frozen");
63 | }
64 | };
65 |
66 | FrozenRows.prototype.unfreezeRow = function (row) {
67 | var index = this.rows.indexOf(row);
68 |
69 | if (row.modules.frozen) {
70 |
71 | row.modules.frozen = false;
72 |
73 | var rowEl = row.getElement();
74 | rowEl.parentNode.removeChild(rowEl);
75 |
76 | this.table.rowManager.adjustTableSize();
77 |
78 | this.rows.splice(index, 1);
79 |
80 | this.table.rowManager.refreshActiveData("display");
81 |
82 | if (this.rows.length) {
83 | this.styleRows();
84 | }
85 | } else {
86 | console.warn("Freeze Error - Row is already unfrozen");
87 | }
88 | };
89 |
90 | FrozenRows.prototype.styleRows = function (row) {
91 | var self = this;
92 |
93 | this.rows.forEach(function (row, i) {
94 | self.table.rowManager.styleRow(row, i);
95 | });
96 | };
97 |
98 | Tabulator.prototype.registerModule("frozenRows", FrozenRows);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/frozen_rows.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var FrozenRows=function(e){this.table=e,this.topElement=document.createElement("div"),this.rows=[],this.displayIndex=0};FrozenRows.prototype.initialize=function(){this.rows=[],this.topElement.classList.add("tabulator-frozen-rows-holder"),this.table.columnManager.getElement().insertBefore(this.topElement,this.table.columnManager.headersElement.nextSibling)},FrozenRows.prototype.setDisplayIndex=function(e){this.displayIndex=e},FrozenRows.prototype.getDisplayIndex=function(){return this.displayIndex},FrozenRows.prototype.isFrozen=function(){return!!this.rows.length},FrozenRows.prototype.getRows=function(e){var o=e.slice(0);return this.rows.forEach(function(e){var t=o.indexOf(e);t>-1&&o.splice(t,1)}),o},FrozenRows.prototype.freezeRow=function(e){e.modules.frozen?console.warn("Freeze Error - Row is already frozen"):(e.modules.frozen=!0,this.topElement.appendChild(e.getElement()),e.initialize(),e.normalizeHeight(),this.table.rowManager.adjustTableSize(),this.rows.push(e),this.table.rowManager.refreshActiveData("display"),this.styleRows())},FrozenRows.prototype.unfreezeRow=function(e){var o=this.rows.indexOf(e);if(e.modules.frozen){e.modules.frozen=!1;var t=e.getElement();t.parentNode.removeChild(t),this.table.rowManager.adjustTableSize(),this.rows.splice(o,1),this.table.rowManager.refreshActiveData("display"),this.rows.length&&this.styleRows()}else console.warn("Freeze Error - Row is already unfrozen")},FrozenRows.prototype.styleRows=function(e){var o=this;this.rows.forEach(function(e,t){o.table.rowManager.styleRow(e,t)})},Tabulator.prototype.registerModule("frozenRows",FrozenRows);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/history.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 |
3 | var History = function History(table) {
4 | this.table = table; //hold Tabulator object
5 |
6 | this.history = [];
7 | this.index = -1;
8 | };
9 |
10 | History.prototype.clear = function () {
11 | this.history = [];
12 | this.index = -1;
13 | };
14 |
15 | History.prototype.action = function (type, component, data) {
16 |
17 | this.history = this.history.slice(0, this.index + 1);
18 |
19 | this.history.push({
20 | type: type,
21 | component: component,
22 | data: data
23 | });
24 |
25 | this.index++;
26 | };
27 |
28 | History.prototype.getHistoryUndoSize = function () {
29 | return this.index + 1;
30 | };
31 |
32 | History.prototype.getHistoryRedoSize = function () {
33 | return this.history.length - (this.index + 1);
34 | };
35 |
36 | History.prototype.undo = function () {
37 |
38 | if (this.index > -1) {
39 | var action = this.history[this.index];
40 |
41 | this.undoers[action.type].call(this, action);
42 |
43 | this.index--;
44 |
45 | this.table.options.historyUndo.call(this.table, action.type, action.component.getComponent(), action.data);
46 |
47 | return true;
48 | } else {
49 | console.warn("History Undo Error - No more history to undo");
50 | return false;
51 | }
52 | };
53 |
54 | History.prototype.redo = function () {
55 | if (this.history.length - 1 > this.index) {
56 |
57 | this.index++;
58 |
59 | var action = this.history[this.index];
60 |
61 | this.redoers[action.type].call(this, action);
62 |
63 | this.table.options.historyRedo.call(this.table, action.type, action.component.getComponent(), action.data);
64 |
65 | return true;
66 | } else {
67 | console.warn("History Redo Error - No more history to redo");
68 | return false;
69 | }
70 | };
71 |
72 | History.prototype.undoers = {
73 | cellEdit: function cellEdit(action) {
74 | action.component.setValueProcessData(action.data.oldValue);
75 | },
76 |
77 | rowAdd: function rowAdd(action) {
78 | action.component.deleteActual();
79 | },
80 |
81 | rowDelete: function rowDelete(action) {
82 | var newRow = this.table.rowManager.addRowActual(action.data.data, action.data.pos, action.data.index);
83 |
84 | if (this.table.options.groupBy && this.table.modExists("groupRows")) {
85 | this.table.modules.groupRows.updateGroupRows(true);
86 | }
87 |
88 | this._rebindRow(action.component, newRow);
89 | },
90 |
91 | rowMove: function rowMove(action) {
92 | this.table.rowManager.moveRowActual(action.component, this.table.rowManager.rows[action.data.posFrom], !action.data.after);
93 | this.table.rowManager.redraw();
94 | }
95 | };
96 |
97 | History.prototype.redoers = {
98 | cellEdit: function cellEdit(action) {
99 | action.component.setValueProcessData(action.data.newValue);
100 | },
101 |
102 | rowAdd: function rowAdd(action) {
103 | var newRow = this.table.rowManager.addRowActual(action.data.data, action.data.pos, action.data.index);
104 |
105 | if (this.table.options.groupBy && this.table.modExists("groupRows")) {
106 | this.table.modules.groupRows.updateGroupRows(true);
107 | }
108 |
109 | this._rebindRow(action.component, newRow);
110 | },
111 |
112 | rowDelete: function rowDelete(action) {
113 | action.component.deleteActual();
114 | },
115 |
116 | rowMove: function rowMove(action) {
117 | this.table.rowManager.moveRowActual(action.component, this.table.rowManager.rows[action.data.posTo], action.data.after);
118 | this.table.rowManager.redraw();
119 | }
120 | };
121 |
122 | //rebind rows to new element after deletion
123 | History.prototype._rebindRow = function (oldRow, newRow) {
124 | this.history.forEach(function (action) {
125 | if (action.component instanceof Row) {
126 | if (action.component === oldRow) {
127 | action.component = newRow;
128 | }
129 | } else if (action.component instanceof Cell) {
130 | if (action.component.row === oldRow) {
131 | var field = action.component.column.getField();
132 |
133 | if (field) {
134 | action.component = newRow.getCell(field);
135 | }
136 | }
137 | }
138 | });
139 | };
140 |
141 | Tabulator.prototype.registerModule("history", History);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/history.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var History=function(t){this.table=t,this.history=[],this.index=-1};History.prototype.clear=function(){this.history=[],this.index=-1},History.prototype.action=function(t,o,e){this.history=this.history.slice(0,this.index+1),this.history.push({type:t,component:o,data:e}),this.index++},History.prototype.getHistoryUndoSize=function(){return this.index+1},History.prototype.getHistoryRedoSize=function(){return this.history.length-(this.index+1)},History.prototype.undo=function(){if(this.index>-1){var t=this.history[this.index];return this.undoers[t.type].call(this,t),this.index--,this.table.options.historyUndo.call(this.table,t.type,t.component.getComponent(),t.data),!0}return console.warn("History Undo Error - No more history to undo"),!1},History.prototype.redo=function(){if(this.history.length-1>this.index){this.index++;var t=this.history[this.index];return this.redoers[t.type].call(this,t),this.table.options.historyRedo.call(this.table,t.type,t.component.getComponent(),t.data),!0}return console.warn("History Redo Error - No more history to redo"),!1},History.prototype.undoers={cellEdit:function(t){t.component.setValueProcessData(t.data.oldValue)},rowAdd:function(t){t.component.deleteActual()},rowDelete:function(t){var o=this.table.rowManager.addRowActual(t.data.data,t.data.pos,t.data.index);this.table.options.groupBy&&this.table.modExists("groupRows")&&this.table.modules.groupRows.updateGroupRows(!0),this._rebindRow(t.component,o)},rowMove:function(t){this.table.rowManager.moveRowActual(t.component,this.table.rowManager.rows[t.data.posFrom],!t.data.after),this.table.rowManager.redraw()}},History.prototype.redoers={cellEdit:function(t){t.component.setValueProcessData(t.data.newValue)},rowAdd:function(t){var o=this.table.rowManager.addRowActual(t.data.data,t.data.pos,t.data.index);this.table.options.groupBy&&this.table.modExists("groupRows")&&this.table.modules.groupRows.updateGroupRows(!0),this._rebindRow(t.component,o)},rowDelete:function(t){t.component.deleteActual()},rowMove:function(t){this.table.rowManager.moveRowActual(t.component,this.table.rowManager.rows[t.data.posTo],t.data.after),this.table.rowManager.redraw()}},History.prototype._rebindRow=function(t,o){this.history.forEach(function(e){if(e.component instanceof Row)e.component===t&&(e.component=o);else if(e.component instanceof Cell&&e.component.row===t){var i=e.component.column.getField();i&&(e.component=o.getCell(i))}})},Tabulator.prototype.registerModule("history",History);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/html_table_import.js:
--------------------------------------------------------------------------------
1 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
2 |
3 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
4 |
5 | var HtmlTableImport = function HtmlTableImport(table) {
6 | this.table = table; //hold Tabulator object
7 | this.fieldIndex = [];
8 | this.hasIndex = false;
9 | };
10 |
11 | HtmlTableImport.prototype.parseTable = function () {
12 | var self = this,
13 | element = self.table.element,
14 | options = self.table.options,
15 | columns = options.columns,
16 | headers = element.getElementsByTagName("th"),
17 | rows = element.getElementsByTagName("tbody")[0],
18 | data = [],
19 | newTable;
20 |
21 | self.hasIndex = false;
22 |
23 | self.table.options.htmlImporting.call(this.table);
24 |
25 | rows = rows ? rows.getElementsByTagName("tr") : [];
26 |
27 | //check for tablator inline options
28 | self._extractOptions(element, options);
29 |
30 | if (headers.length) {
31 | self._extractHeaders(headers, rows);
32 | } else {
33 | self._generateBlankHeaders(headers, rows);
34 | }
35 |
36 | //iterate through table rows and build data set
37 | for (var index = 0; index < rows.length; index++) {
38 | var row = rows[index],
39 | cells = row.getElementsByTagName("td"),
40 | item = {};
41 |
42 | //create index if the dont exist in table
43 | if (!self.hasIndex) {
44 | item[options.index] = index;
45 | }
46 |
47 | for (var i = 0; i < cells.length; i++) {
48 | var cell = cells[i];
49 | if (typeof this.fieldIndex[i] !== "undefined") {
50 | item[this.fieldIndex[i]] = cell.innerHTML;
51 | }
52 | }
53 |
54 | //add row data to item
55 | data.push(item);
56 | }
57 |
58 | //create new element
59 | var newElement = document.createElement("div");
60 |
61 | //transfer attributes to new element
62 | var attributes = element.attributes;
63 |
64 | // loop through attributes and apply them on div
65 |
66 | for (var i in attributes) {
67 | if (_typeof(attributes[i]) == "object") {
68 | newElement.setAttribute(attributes[i].name, attributes[i].value);
69 | }
70 | }
71 |
72 | // replace table with div element
73 | element.parentNode.replaceChild(newElement, element);
74 |
75 | options.data = data;
76 |
77 | self.table.options.htmlImported.call(this.table);
78 |
79 | // // newElement.tabulator(options);
80 |
81 | this.table.element = newElement;
82 | };
83 |
84 | //extract tabulator attribute options
85 | HtmlTableImport.prototype._extractOptions = function (element, options, defaultOptions) {
86 | var attributes = element.attributes;
87 | var optionsArr = defaultOptions ? Object.assign([], defaultOptions) : Object.keys(options);
88 | var optionsList = {};
89 |
90 | optionsArr.forEach(function (item) {
91 | optionsList[item.toLowerCase()] = item;
92 | });
93 |
94 | for (var index in attributes) {
95 | var attrib = attributes[index];
96 | var name;
97 |
98 | if (attrib && (typeof attrib === "undefined" ? "undefined" : _typeof(attrib)) == "object" && attrib.name && attrib.name.indexOf("tabulator-") === 0) {
99 | name = attrib.name.replace("tabulator-", "");
100 |
101 | if (typeof optionsList[name] !== "undefined") {
102 | options[optionsList[name]] = this._attribValue(attrib.value);
103 | }
104 | }
105 | }
106 | };
107 |
108 | //get value of attribute
109 | HtmlTableImport.prototype._attribValue = function (value) {
110 | if (value === "true") {
111 | return true;
112 | }
113 |
114 | if (value === "false") {
115 | return false;
116 | }
117 |
118 | return value;
119 | };
120 |
121 | //find column if it has already been defined
122 | HtmlTableImport.prototype._findCol = function (title) {
123 | var match = this.table.options.columns.find(function (column) {
124 | return column.title === title;
125 | });
126 |
127 | return match || false;
128 | };
129 |
130 | //extract column from headers
131 | HtmlTableImport.prototype._extractHeaders = function (headers, rows) {
132 | for (var index = 0; index < headers.length; index++) {
133 | var header = headers[index],
134 | exists = false,
135 | col = this._findCol(header.textContent),
136 | width,
137 | attributes;
138 |
139 | if (col) {
140 | exists = true;
141 | } else {
142 | col = { title: header.textContent.trim() };
143 | }
144 |
145 | if (!col.field) {
146 | col.field = header.textContent.trim().toLowerCase().replace(" ", "_");
147 | }
148 |
149 | width = header.getAttribute("width");
150 |
151 | if (width && !col.width) {
152 | col.width = width;
153 | }
154 |
155 | //check for tablator inline options
156 | attributes = header.attributes;
157 |
158 | // //check for tablator inline options
159 | this._extractOptions(header, col, Column.prototype.defaultOptionList);
160 |
161 | this.fieldIndex[index] = col.field;
162 |
163 | if (col.field == this.table.options.index) {
164 | this.hasIndex = true;
165 | }
166 |
167 | if (!exists) {
168 | this.table.options.columns.push(col);
169 | }
170 | }
171 | };
172 |
173 | //generate blank headers
174 | HtmlTableImport.prototype._generateBlankHeaders = function (headers, rows) {
175 | for (var index = 0; index < headers.length; index++) {
176 | var header = headers[index],
177 | col = { title: "", field: "col" + index };
178 |
179 | this.fieldIndex[index] = col.field;
180 |
181 | var width = header.getAttribute("width");
182 |
183 | if (width) {
184 | col.width = width;
185 | }
186 |
187 | this.table.options.columns.push(col);
188 | }
189 | };
190 |
191 | Tabulator.prototype.registerModule("htmlTableImport", HtmlTableImport);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/html_table_import.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},HtmlTableImport=function(t){this.table=t,this.fieldIndex=[],this.hasIndex=!1};HtmlTableImport.prototype.parseTable=function(){var t=this,e=t.table.element,o=t.table.options,a=(o.columns,e.getElementsByTagName("th")),n=e.getElementsByTagName("tbody")[0],l=[];t.hasIndex=!1,t.table.options.htmlImporting.call(this.table),n=n?n.getElementsByTagName("tr"):[],t._extractOptions(e,o),a.length?t._extractHeaders(a,n):t._generateBlankHeaders(a,n);for(var r=0;r-1&&t.pressedKeys.splice(n,1)}},this.table.element.addEventListener("keydown",this.keyupBinding),this.table.element.addEventListener("keyup",this.keydownBinding)},Keybindings.prototype.clearBindings=function(){this.keyupBinding&&this.table.element.removeEventListener("keydown",this.keyupBinding),this.keydownBinding&&this.table.element.removeEventListener("keyup",this.keydownBinding)},Keybindings.prototype.checkBinding=function(t,e){var i=this,n=!0;return t.ctrlKey==e.ctrl&&t.shiftKey==e.shift&&t.metaKey==e.meta&&(e.keys.forEach(function(t){-1==i.pressedKeys.indexOf(t)&&(n=!1)}),n&&e.action.call(i,t),!0)},Keybindings.prototype.bindings={navPrev:"shift + 9",navNext:9,navUp:38,navDown:40,scrollPageUp:33,scrollPageDown:34,scrollToStart:36,scrollToEnd:35,undo:"ctrl + 90",redo:"ctrl + 89",copyToClipboard:"ctrl + 67"},Keybindings.prototype.actions={keyBlock:function(t){t.stopPropagation(),t.preventDefault()},scrollPageUp:function(t){var e=this.table.rowManager,i=e.scrollTop-e.height;e.element.scrollHeight;t.preventDefault(),e.displayRowsCount&&(i>=0?e.element.scrollTop=i:e.scrollToRow(e.getDisplayRows()[0])),this.table.element.focus()},scrollPageDown:function(t){var e=this.table.rowManager,i=e.scrollTop+e.height,n=e.element.scrollHeight;t.preventDefault(),e.displayRowsCount&&(i<=n?e.element.scrollTop=i:e.scrollToRow(e.getDisplayRows()[e.displayRowsCount-1])),this.table.element.focus()},scrollToStart:function(t){var e=this.table.rowManager;t.preventDefault(),e.displayRowsCount&&e.scrollToRow(e.getDisplayRows()[0]),this.table.element.focus()},scrollToEnd:function(t){var e=this.table.rowManager;t.preventDefault(),e.displayRowsCount&&e.scrollToRow(e.getDisplayRows()[e.displayRowsCount-1]),this.table.element.focus()},navPrev:function(t){var e=!1;this.table.modExists("edit")&&(e=this.table.modules.edit.currentCell)&&(t.preventDefault(),e.nav().prev())},navNext:function(t){var e,i=!1,n=this.table.options.tabEndNewRow;this.table.modExists("edit")&&(i=this.table.modules.edit.currentCell)&&(t.preventDefault(),e=i.nav(),e.next()||n&&(i.getElement().firstChild.blur(),n=!0===n?this.table.addRow({}):"function"==typeof n?this.table.addRow(n(i.row.getComponent())):this.table.addRow(Object.assign({},n)),n.then(function(){setTimeout(function(){e.next()})})))},navLeft:function(t){var e=!1;this.table.modExists("edit")&&(e=this.table.modules.edit.currentCell)&&(t.preventDefault(),e.nav().left())},navRight:function(t){var e=!1;this.table.modExists("edit")&&(e=this.table.modules.edit.currentCell)&&(t.preventDefault(),e.nav().right())},navUp:function(t){var e=!1;this.table.modExists("edit")&&(e=this.table.modules.edit.currentCell)&&(t.preventDefault(),e.nav().up())},navDown:function(t){var e=!1;this.table.modExists("edit")&&(e=this.table.modules.edit.currentCell)&&(t.preventDefault(),e.nav().down())},undo:function(t){this.table.options.history&&this.table.modExists("history")&&this.table.modExists("edit")&&(this.table.modules.edit.currentCell||(t.preventDefault(),this.table.modules.history.undo()))},redo:function(t){this.table.options.history&&this.table.modExists("history")&&this.table.modExists("edit")&&(this.table.modules.edit.currentCell||(t.preventDefault(),this.table.modules.history.redo()))},copyToClipboard:function(t){this.table.modules.edit.currentCell||this.table.modExists("clipboard",!0)&&this.table.modules.clipboard.copy(!1,!0)}},Tabulator.prototype.registerModule("keybindings",Keybindings);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/menu.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 |
3 | var Menu = function Menu(table) {
4 | this.table = table; //hold Tabulator object
5 | this.menuEl = false;
6 | this.blurEvent = this.hideMenu.bind(this);
7 | this.escEvent = this.escMenu.bind(this);
8 | this.nestedMenuBlock = false;
9 | };
10 |
11 | Menu.prototype.initializeColumnHeader = function (column) {
12 | var _this = this;
13 |
14 | var headerMenuEl;
15 |
16 | if (column.definition.headerContextMenu) {
17 | column.getElement().addEventListener("contextmenu", function (e) {
18 | var menu = typeof column.definition.headerContextMenu == "function" ? column.definition.headerContextMenu(column.getComponent()) : column.definition.headerContextMenu;
19 |
20 | e.preventDefault();
21 |
22 | _this.loadMenu(e, column, menu);
23 | });
24 | }
25 |
26 | if (column.definition.headerMenu) {
27 |
28 | headerMenuEl = document.createElement("span");
29 | headerMenuEl.classList.add("tabulator-header-menu-button");
30 | headerMenuEl.innerHTML = "⋮";
31 |
32 | headerMenuEl.addEventListener("click", function (e) {
33 | var menu = typeof column.definition.headerMenu == "function" ? column.definition.headerMenu(column.getComponent()) : column.definition.headerMenu;
34 | e.stopPropagation();
35 | e.preventDefault();
36 |
37 | _this.loadMenu(e, column, menu);
38 | });
39 |
40 | column.titleElement.insertBefore(headerMenuEl, column.titleElement.firstChild);
41 | }
42 | };
43 |
44 | Menu.prototype.initializeCell = function (cell) {
45 | var _this2 = this;
46 |
47 | cell.getElement().addEventListener("contextmenu", function (e) {
48 | var menu = typeof cell.column.definition.contextMenu == "function" ? cell.column.definition.contextMenu(cell.getComponent()) : cell.column.definition.contextMenu;
49 |
50 | if (menu) {
51 | e.stopImmediatePropagation();
52 | }
53 |
54 | _this2.loadMenu(e, cell, menu);
55 | });
56 | };
57 |
58 | Menu.prototype.initializeRow = function (row) {
59 | var _this3 = this;
60 |
61 | row.getElement().addEventListener("contextmenu", function (e) {
62 | var menu = typeof _this3.table.options.rowContextMenu == "function" ? _this3.table.options.rowContextMenu(row.getComponent()) : _this3.table.options.rowContextMenu;
63 |
64 | _this3.loadMenu(e, row, menu);
65 | });
66 | };
67 |
68 | Menu.prototype.initializeGroup = function (group) {
69 | var _this4 = this;
70 |
71 | group.getElement().addEventListener("contextmenu", function (e) {
72 | var menu = typeof _this4.table.options.groupContextMenu == "function" ? _this4.table.options.groupContextMenu(group.getComponent()) : _this4.table.options.groupContextMenu;
73 |
74 | _this4.loadMenu(e, group, menu);
75 | });
76 | };
77 |
78 | Menu.prototype.loadMenu = function (e, component, menu) {
79 | var _this5 = this;
80 |
81 | var docHeight = Math.max(document.body.offsetHeight, window.innerHeight);
82 |
83 | e.preventDefault();
84 |
85 | //abort if no menu set
86 | if (!menu || !menu.length) {
87 | return;
88 | }
89 |
90 | if (this.nestedMenuBlock) {
91 | //abort if child menu already open
92 | if (this.isOpen()) {
93 | return;
94 | }
95 | } else {
96 | this.nestedMenuBlock = setTimeout(function () {
97 | _this5.nestedMenuBlock = false;
98 | }, 100);
99 | }
100 |
101 | this.hideMenu();
102 |
103 | this.menuEl = document.createElement("div");
104 | this.menuEl.classList.add("tabulator-menu");
105 |
106 | menu.forEach(function (item) {
107 | var itemEl = document.createElement("div");
108 | var label = item.label;
109 | var disabled = item.disabled;
110 |
111 | if (item.separator) {
112 | itemEl.classList.add("tabulator-menu-separator");
113 | } else {
114 | itemEl.classList.add("tabulator-menu-item");
115 |
116 | if (typeof label == "function") {
117 | label = label(component.getComponent());
118 | }
119 |
120 | if (label instanceof Node) {
121 | itemEl.appendChild(label);
122 | } else {
123 | itemEl.innerHTML = label;
124 | }
125 |
126 | if (typeof disabled == "function") {
127 | disabled = disabled(component.getComponent());
128 | }
129 |
130 | if (disabled) {
131 | itemEl.classList.add("tabulator-menu-item-disabled");
132 | itemEl.addEventListener("click", function (e) {
133 | e.stopPropagation();
134 | });
135 | } else {
136 | itemEl.addEventListener("click", function (e) {
137 | _this5.hideMenu();
138 | item.action(e, component.getComponent());
139 | });
140 | }
141 | }
142 |
143 | _this5.menuEl.appendChild(itemEl);
144 | });
145 |
146 | this.menuEl.style.top = e.pageY + "px";
147 | this.menuEl.style.left = e.pageX + "px";
148 |
149 | document.body.addEventListener("click", this.blurEvent);
150 | this.table.rowManager.element.addEventListener("scroll", this.blurEvent);
151 |
152 | setTimeout(function () {
153 | document.body.addEventListener("contextmenu", _this5.blurEvent);
154 | }, 100);
155 |
156 | document.body.addEventListener("keydown", this.escEvent);
157 |
158 | document.body.appendChild(this.menuEl);
159 |
160 | //move menu to start on right edge if it is too close to the edge of the screen
161 | if (e.pageX + this.menuEl.offsetWidth >= document.body.offsetWidth) {
162 | this.menuEl.style.left = "";
163 | this.menuEl.style.right = document.body.offsetWidth - e.pageX + "px";
164 | }
165 |
166 | //move menu to start on bottom edge if it is too close to the edge of the screen
167 | if (e.pageY + this.menuEl.offsetHeight >= docHeight) {
168 | this.menuEl.style.top = "";
169 | this.menuEl.style.bottom = docHeight - e.pageY + "px";
170 | }
171 | };
172 |
173 | Menu.prototype.isOpen = function () {
174 | return !!this.menuEl.parentNode;
175 | };
176 |
177 | Menu.prototype.escMenu = function (e) {
178 | if (e.keyCode == 27) {
179 | this.hideMenu();
180 | }
181 | };
182 |
183 | Menu.prototype.hideMenu = function () {
184 | if (this.menuEl.parentNode) {
185 | this.menuEl.parentNode.removeChild(this.menuEl);
186 | }
187 |
188 | if (this.escEvent) {
189 | document.body.removeEventListener("keydown", this.escEvent);
190 | }
191 |
192 | if (this.blurEvent) {
193 | document.body.removeEventListener("click", this.blurEvent);
194 | document.body.removeEventListener("contextmenu", this.blurEvent);
195 | this.table.rowManager.element.removeEventListener("scroll", this.blurEvent);
196 | }
197 | };
198 |
199 | //default accessors
200 | Menu.prototype.menus = {};
201 |
202 | Tabulator.prototype.registerModule("menu", Menu);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/menu.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var Menu=function(e){this.table=e,this.menuEl=!1,this.blurEvent=this.hideMenu.bind(this),this.escEvent=this.escMenu.bind(this),this.nestedMenuBlock=!1};Menu.prototype.initializeColumnHeader=function(e){var t,n=this;e.definition.headerContextMenu&&e.getElement().addEventListener("contextmenu",function(t){var o="function"==typeof e.definition.headerContextMenu?e.definition.headerContextMenu(e.getComponent()):e.definition.headerContextMenu;t.preventDefault(),n.loadMenu(t,e,o)}),e.definition.headerMenu&&(t=document.createElement("span"),t.classList.add("tabulator-header-menu-button"),t.innerHTML="⋮",t.addEventListener("click",function(t){var o="function"==typeof e.definition.headerMenu?e.definition.headerMenu(e.getComponent()):e.definition.headerMenu;t.stopPropagation(),t.preventDefault(),n.loadMenu(t,e,o)}),e.titleElement.insertBefore(t,e.titleElement.firstChild))},Menu.prototype.initializeCell=function(e){var t=this;e.getElement().addEventListener("contextmenu",function(n){var o="function"==typeof e.column.definition.contextMenu?e.column.definition.contextMenu(e.getComponent()):e.column.definition.contextMenu;o&&n.stopImmediatePropagation(),t.loadMenu(n,e,o)})},Menu.prototype.initializeRow=function(e){var t=this;e.getElement().addEventListener("contextmenu",function(n){var o="function"==typeof t.table.options.rowContextMenu?t.table.options.rowContextMenu(e.getComponent()):t.table.options.rowContextMenu;t.loadMenu(n,e,o)})},Menu.prototype.initializeGroup=function(e){var t=this;e.getElement().addEventListener("contextmenu",function(n){var o="function"==typeof t.table.options.groupContextMenu?t.table.options.groupContextMenu(e.getComponent()):t.table.options.groupContextMenu;t.loadMenu(n,e,o)})},Menu.prototype.loadMenu=function(e,t,n){var o=this,i=Math.max(document.body.offsetHeight,window.innerHeight);if(e.preventDefault(),n&&n.length){if(this.nestedMenuBlock){if(this.isOpen())return}else this.nestedMenuBlock=setTimeout(function(){o.nestedMenuBlock=!1},100);this.hideMenu(),this.menuEl=document.createElement("div"),this.menuEl.classList.add("tabulator-menu"),n.forEach(function(e){var n=document.createElement("div"),i=e.label,u=e.disabled;e.separator?n.classList.add("tabulator-menu-separator"):(n.classList.add("tabulator-menu-item"),"function"==typeof i&&(i=i(t.getComponent())),i instanceof Node?n.appendChild(i):n.innerHTML=i,"function"==typeof u&&(u=u(t.getComponent())),u?(n.classList.add("tabulator-menu-item-disabled"),n.addEventListener("click",function(e){e.stopPropagation()})):n.addEventListener("click",function(n){o.hideMenu(),e.action(n,t.getComponent())})),o.menuEl.appendChild(n)}),this.menuEl.style.top=e.pageY+"px",this.menuEl.style.left=e.pageX+"px",document.body.addEventListener("click",this.blurEvent),this.table.rowManager.element.addEventListener("scroll",this.blurEvent),setTimeout(function(){document.body.addEventListener("contextmenu",o.blurEvent)},100),document.body.addEventListener("keydown",this.escEvent),document.body.appendChild(this.menuEl),e.pageX+this.menuEl.offsetWidth>=document.body.offsetWidth&&(this.menuEl.style.left="",this.menuEl.style.right=document.body.offsetWidth-e.pageX+"px"),e.pageY+this.menuEl.offsetHeight>=i&&(this.menuEl.style.top="",this.menuEl.style.bottom=i-e.pageY+"px")}},Menu.prototype.isOpen=function(){return!!this.menuEl.parentNode},Menu.prototype.escMenu=function(e){27==e.keyCode&&this.hideMenu()},Menu.prototype.hideMenu=function(){this.menuEl.parentNode&&this.menuEl.parentNode.removeChild(this.menuEl),this.escEvent&&document.body.removeEventListener("keydown",this.escEvent),this.blurEvent&&(document.body.removeEventListener("click",this.blurEvent),document.body.removeEventListener("contextmenu",this.blurEvent),this.table.rowManager.element.removeEventListener("scroll",this.blurEvent))},Menu.prototype.menus={},Tabulator.prototype.registerModule("menu",Menu);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/moveable_columns.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var MoveColumns=function(e){this.table=e,this.placeholderElement=this.createPlaceholderElement(),this.hoverElement=!1,this.checkTimeout=!1,this.checkPeriod=250,this.moving=!1,this.toCol=!1,this.toColAfter=!1,this.startX=0,this.autoScrollMargin=40,this.autoScrollStep=5,this.autoScrollTimeout=!1,this.touchMove=!1,this.moveHover=this.moveHover.bind(this),this.endMove=this.endMove.bind(this)};MoveColumns.prototype.createPlaceholderElement=function(){var e=document.createElement("div");return e.classList.add("tabulator-col"),e.classList.add("tabulator-col-placeholder"),e},MoveColumns.prototype.initializeColumn=function(e){var t,o=this,n={};e.modules.frozen||(t=e.getElement(),n.mousemove=function(n){e.parent===o.moving.parent&&((o.touchMove?n.touches[0].pageX:n.pageX)-Tabulator.prototype.helpers.elOffset(t).left+o.table.columnManager.element.scrollLeft>e.getWidth()/2?o.toCol===e&&o.toColAfter||(t.parentNode.insertBefore(o.placeholderElement,t.nextSibling),o.moveColumn(e,!0)):(o.toCol!==e||o.toColAfter)&&(t.parentNode.insertBefore(o.placeholderElement,t),o.moveColumn(e,!1)))}.bind(o),t.addEventListener("mousedown",function(t){o.touchMove=!1,1===t.which&&(o.checkTimeout=setTimeout(function(){o.startMove(t,e)},o.checkPeriod))}),t.addEventListener("mouseup",function(e){1===e.which&&o.checkTimeout&&clearTimeout(o.checkTimeout)}),o.bindTouchEvents(e)),e.modules.moveColumn=n},MoveColumns.prototype.bindTouchEvents=function(e){var t,o,n,l,i,s,m,r=this,u=e.getElement(),h=!1;u.addEventListener("touchstart",function(u){r.checkTimeout=setTimeout(function(){r.touchMove=!0,t=e,o=e.nextColumn(),l=o?o.getWidth()/2:0,n=e.prevColumn(),i=n?n.getWidth()/2:0,s=0,m=0,h=!1,r.startMove(u,e)},r.checkPeriod)},{passive:!0}),u.addEventListener("touchmove",function(u){var a,c;r.moving&&(r.moveHover(u),h||(h=u.touches[0].pageX),a=u.touches[0].pageX-h,a>0?o&&a-s>l&&(c=o)!==e&&(h=u.touches[0].pageX,c.getElement().parentNode.insertBefore(r.placeholderElement,c.getElement().nextSibling),r.moveColumn(c,!0)):n&&-a-m>i&&(c=n)!==e&&(h=u.touches[0].pageX,c.getElement().parentNode.insertBefore(r.placeholderElement,c.getElement()),r.moveColumn(c,!1)),c&&(t=c,o=c.nextColumn(),s=l,l=o?o.getWidth()/2:0,n=c.prevColumn(),m=i,i=n?n.getWidth()/2:0))},{passive:!0}),u.addEventListener("touchend",function(e){r.checkTimeout&&clearTimeout(r.checkTimeout),r.moving&&r.endMove(e)})},MoveColumns.prototype.startMove=function(e,t){var o=t.getElement();this.moving=t,this.startX=(this.touchMove?e.touches[0].pageX:e.pageX)-Tabulator.prototype.helpers.elOffset(o).left,this.table.element.classList.add("tabulator-block-select"),this.placeholderElement.style.width=t.getWidth()+"px",this.placeholderElement.style.height=t.getHeight()+"px",o.parentNode.insertBefore(this.placeholderElement,o),o.parentNode.removeChild(o),this.hoverElement=o.cloneNode(!0),this.hoverElement.classList.add("tabulator-moving"),this.table.columnManager.getElement().appendChild(this.hoverElement),this.hoverElement.style.left="0",this.hoverElement.style.bottom="0",this.touchMove||(this._bindMouseMove(),document.body.addEventListener("mousemove",this.moveHover),document.body.addEventListener("mouseup",this.endMove)),this.moveHover(e)},MoveColumns.prototype._bindMouseMove=function(){this.table.columnManager.columnsByIndex.forEach(function(e){e.modules.moveColumn.mousemove&&e.getElement().addEventListener("mousemove",e.modules.moveColumn.mousemove)})},MoveColumns.prototype._unbindMouseMove=function(){this.table.columnManager.columnsByIndex.forEach(function(e){e.modules.moveColumn.mousemove&&e.getElement().removeEventListener("mousemove",e.modules.moveColumn.mousemove)})},MoveColumns.prototype.moveColumn=function(e,t){var o=this.moving.getCells();this.toCol=e,this.toColAfter=t,t?e.getCells().forEach(function(e,t){var n=e.getElement();n.parentNode.insertBefore(o[t].getElement(),n.nextSibling)}):e.getCells().forEach(function(e,t){var n=e.getElement();n.parentNode.insertBefore(o[t].getElement(),n)})},MoveColumns.prototype.endMove=function(e){(1===e.which||this.touchMove)&&(this._unbindMouseMove(),this.placeholderElement.parentNode.insertBefore(this.moving.getElement(),this.placeholderElement.nextSibling),this.placeholderElement.parentNode.removeChild(this.placeholderElement),this.hoverElement.parentNode.removeChild(this.hoverElement),this.table.element.classList.remove("tabulator-block-select"),this.toCol&&this.table.columnManager.moveColumnActual(this.moving,this.toCol,this.toColAfter),this.moving=!1,this.toCol=!1,this.toColAfter=!1,this.touchMove||(document.body.removeEventListener("mousemove",this.moveHover),document.body.removeEventListener("mouseup",this.endMove)))},MoveColumns.prototype.moveHover=function(e){var t,o=this,n=o.table.columnManager.getElement(),l=n.scrollLeft,i=(o.touchMove?e.touches[0].pageX:e.pageX)-Tabulator.prototype.helpers.elOffset(n).left+l;o.hoverElement.style.left=i-o.startX+"px",i-lo?s.splice(o,0,e):s.push(e))}),s},Persistence.prototype._findColumn=function(e,t){var i=t.columns?"group":t.field?"field":"object";return e.find(function(e){switch(i){case"group":return e.title===t.title&&e.columns.length===t.columns.length;case"field":return e.field===t.field;case"object":return e===t}})},Persistence.prototype.save=function(e){var t={};switch(e){case"columns":t=this.parseColumns(this.table.columnManager.getColumns());break;case"filter":t=this.table.modules.filter.getFilters();break;case"sort":t=this.validateSorters(this.table.modules.sort.getSort());break;case"group":t=this.getGroupConfig();break;case"page":t=this.getPageConfig()}this.writeFunc&&this.writeFunc(this.id,e,t)},Persistence.prototype.validateSorters=function(e){return e.forEach(function(e){e.column=e.field,delete e.field}),e},Persistence.prototype.getGroupConfig=function(){return this.config.group&&((!0===this.config.group||this.config.group.groupBy)&&(data.groupBy=this.table.options.groupBy),(!0===this.config.group||this.config.group.groupStartOpen)&&(data.groupStartOpen=this.table.options.groupStartOpen),(!0===this.config.group||this.config.group.groupHeader)&&(data.groupHeader=this.table.options.groupHeader)),data},Persistence.prototype.getPageConfig=function(){var e={};return this.config.page&&((!0===this.config.page||this.config.page.size)&&(e.paginationSize=this.table.modules.page.getPageSize()),(!0===this.config.page||this.config.page.page)&&(e.paginationInitialPage=this.table.modules.page.getPage())),e},Persistence.prototype.parseColumns=function(e){var t=this,i=[];return e.forEach(function(e){var s,o={},n=e.getDefinition();e.isGroup?(o.title=n.title,o.columns=t.parseColumns(e.getColumns())):(o.field=e.getField(),!0===t.config.columns||void 0==t.config.columns?(s=Object.keys(n),s.push("width")):s=t.config.columns,s.forEach(function(t){switch(t){case"width":o.width=e.getWidth();break;case"visible":o.visible=e.visible;break;default:o[t]=n[t]}})),i.push(o)}),i},Persistence.prototype.readers={local:function(e,t){var i=localStorage.getItem(e+"-"+t);return!!i&&JSON.parse(i)},cookie:function(e,t){var i,s,o=document.cookie,n=e+"-"+t,r=o.indexOf(n+"=");return r>-1&&(o=o.substr(r),i=o.indexOf(";"),i>-1&&(o=o.substr(0,i)),s=o.replace(n+"=","")),!!s&&JSON.parse(s)}},Persistence.prototype.writers={local:function(e,t,i){localStorage.setItem(e+"-"+t,JSON.stringify(i))},cookie:function(e,t,i){var s=new Date;s.setDate(s.getDate()+1e4),document.cookie=e+"-"+t+"="+JSON.stringify(i)+"; expires="+s.toUTCString()}},Tabulator.prototype.registerModule("persistence",Persistence);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/print.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 |
3 | var Print = function Print(table) {
4 | this.table = table; //hold Tabulator object
5 | this.element = false;
6 | this.manualBlock = false;
7 | };
8 |
9 | Print.prototype.initialize = function () {
10 | window.addEventListener("beforeprint", this.replaceTable.bind(this));
11 | window.addEventListener("afterprint", this.cleanup.bind(this));
12 | };
13 |
14 | Print.prototype.replaceTable = function () {
15 | if (!this.manualBlock) {
16 | this.element = document.createElement("div");
17 | this.element.classList.add("tabulator-print-table");
18 |
19 | this.element.appendChild(this.table.modules.export.genereateTable(this.table.options.printConfig, this.table.options.printStyled, this.table.options.printRowRange, "print"));
20 |
21 | this.table.element.style.display = "none";
22 |
23 | this.table.element.parentNode.insertBefore(this.element, this.table.element);
24 | }
25 | };
26 |
27 | Print.prototype.cleanup = function () {
28 | document.body.classList.remove("tabulator-print-fullscreen-hide");
29 |
30 | if (this.element && this.element.parentNode) {
31 | this.element.parentNode.removeChild(this.element);
32 | this.table.element.style.display = "";
33 | }
34 | };
35 |
36 | Print.prototype.printFullscreen = function (visible, style, config) {
37 | var scrollX = window.scrollX,
38 | scrollY = window.scrollY,
39 | headerEl = document.createElement("div"),
40 | footerEl = document.createElement("div"),
41 | tableEl = this.table.modules.export.genereateTable(typeof config != "undefined" ? config : this.table.options.printConfig, typeof style != "undefined" ? style : this.table.options.printStyled, visible, "print"),
42 | headerContent,
43 | footerContent;
44 |
45 | this.manualBlock = true;
46 |
47 | this.element = document.createElement("div");
48 | this.element.classList.add("tabulator-print-fullscreen");
49 |
50 | if (this.table.options.printHeader) {
51 | headerEl.classList.add("tabulator-print-header");
52 |
53 | headerContent = typeof this.table.options.printHeader == "function" ? this.table.options.printHeader.call(this.table) : this.table.options.printHeader;
54 |
55 | if (typeof headerContent == "string") {
56 | headerEl.innerHTML = headerContent;
57 | } else {
58 | headerEl.appendChild(headerContent);
59 | }
60 |
61 | this.element.appendChild(headerEl);
62 | }
63 |
64 | this.element.appendChild(tableEl);
65 |
66 | if (this.table.options.printFooter) {
67 | footerEl.classList.add("tabulator-print-footer");
68 |
69 | footerContent = typeof this.table.options.printFooter == "function" ? this.table.options.printFooter.call(this.table) : this.table.options.printFooter;
70 |
71 | if (typeof footerContent == "string") {
72 | footerEl.innerHTML = footerContent;
73 | } else {
74 | footerEl.appendChild(footerContent);
75 | }
76 |
77 | this.element.appendChild(footerEl);
78 | }
79 |
80 | document.body.classList.add("tabulator-print-fullscreen-hide");
81 | document.body.appendChild(this.element);
82 |
83 | if (this.table.options.printFormatter) {
84 | this.table.options.printFormatter(this.element, tableEl);
85 | }
86 |
87 | window.print();
88 |
89 | this.cleanup();
90 |
91 | window.scrollTo(scrollX, scrollY);
92 |
93 | this.manualBlock = false;
94 | };
95 |
96 | Tabulator.prototype.registerModule("print", Print);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/print.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var Print=function(t){this.table=t,this.element=!1,this.manualBlock=!1};Print.prototype.initialize=function(){window.addEventListener("beforeprint",this.replaceTable.bind(this)),window.addEventListener("afterprint",this.cleanup.bind(this))},Print.prototype.replaceTable=function(){this.manualBlock||(this.element=document.createElement("div"),this.element.classList.add("tabulator-print-table"),this.element.appendChild(this.table.modules.export.genereateTable(this.table.options.printConfig,this.table.options.printStyled,this.table.options.printRowRange,"print")),this.table.element.style.display="none",this.table.element.parentNode.insertBefore(this.element,this.table.element))},Print.prototype.cleanup=function(){document.body.classList.remove("tabulator-print-fullscreen-hide"),this.element&&this.element.parentNode&&(this.element.parentNode.removeChild(this.element),this.table.element.style.display="")},Print.prototype.printFullscreen=function(t,e,i){var n,l,o=window.scrollX,a=window.scrollY,s=document.createElement("div"),r=document.createElement("div"),p=this.table.modules.export.genereateTable(void 0!==i?i:this.table.options.printConfig,void 0!==e?e:this.table.options.printStyled,t,"print");this.manualBlock=!0,this.element=document.createElement("div"),this.element.classList.add("tabulator-print-fullscreen"),this.table.options.printHeader&&(s.classList.add("tabulator-print-header"),n="function"==typeof this.table.options.printHeader?this.table.options.printHeader.call(this.table):this.table.options.printHeader,"string"==typeof n?s.innerHTML=n:s.appendChild(n),this.element.appendChild(s)),this.element.appendChild(p),this.table.options.printFooter&&(r.classList.add("tabulator-print-footer"),l="function"==typeof this.table.options.printFooter?this.table.options.printFooter.call(this.table):this.table.options.printFooter,"string"==typeof l?r.innerHTML=l:r.appendChild(l),this.element.appendChild(r)),document.body.classList.add("tabulator-print-fullscreen-hide"),document.body.appendChild(this.element),this.table.options.printFormatter&&this.table.options.printFormatter(this.element,p),window.print(),this.cleanup(),window.scrollTo(o,a),this.manualBlock=!1},Tabulator.prototype.registerModule("print",Print);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/reactive_data.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 |
3 | var ReactiveData = function ReactiveData(table) {
4 | this.table = table; //hold Tabulator object
5 | this.data = false;
6 | this.blocked = false; //block reactivity while performing update
7 | this.origFuncs = {}; // hold original data array functions to allow replacement after data is done with
8 | this.currentVersion = 0;
9 | };
10 |
11 | ReactiveData.prototype.watchData = function (data) {
12 | var self = this,
13 | pushFunc,
14 | version;
15 |
16 | this.currentVersion++;
17 |
18 | version = this.currentVersion;
19 |
20 | self.unwatchData();
21 |
22 | self.data = data;
23 |
24 | //override array push function
25 | self.origFuncs.push = data.push;
26 |
27 | Object.defineProperty(self.data, "push", {
28 | enumerable: false,
29 | configurable: true,
30 | value: function value() {
31 | var args = Array.from(arguments);
32 |
33 | if (!self.blocked && version === self.currentVersion) {
34 | args.forEach(function (arg) {
35 | self.table.rowManager.addRowActual(arg, false);
36 | });
37 | }
38 |
39 | return self.origFuncs.push.apply(data, arguments);
40 | }
41 | });
42 |
43 | //override array unshift function
44 | self.origFuncs.unshift = data.unshift;
45 |
46 | Object.defineProperty(self.data, "unshift", {
47 | enumerable: false,
48 | configurable: true,
49 | value: function value() {
50 | var args = Array.from(arguments);
51 |
52 | if (!self.blocked && version === self.currentVersion) {
53 | args.forEach(function (arg) {
54 | self.table.rowManager.addRowActual(arg, true);
55 | });
56 | }
57 |
58 | return self.origFuncs.unshift.apply(data, arguments);
59 | }
60 | });
61 |
62 | //override array shift function
63 | self.origFuncs.shift = data.shift;
64 |
65 | Object.defineProperty(self.data, "shift", {
66 | enumerable: false,
67 | configurable: true,
68 | value: function value() {
69 | var row;
70 |
71 | if (!self.blocked && version === self.currentVersion) {
72 | if (self.data.length) {
73 | row = self.table.rowManager.getRowFromDataObject(self.data[0]);
74 |
75 | if (row) {
76 | row.deleteActual();
77 | }
78 | }
79 | }
80 |
81 | return self.origFuncs.shift.call(data);
82 | }
83 | });
84 |
85 | //override array pop function
86 | self.origFuncs.pop = data.pop;
87 |
88 | Object.defineProperty(self.data, "pop", {
89 | enumerable: false,
90 | configurable: true,
91 | value: function value() {
92 | var row;
93 | if (!self.blocked && version === self.currentVersion) {
94 | if (self.data.length) {
95 | row = self.table.rowManager.getRowFromDataObject(self.data[self.data.length - 1]);
96 |
97 | if (row) {
98 | row.deleteActual();
99 | }
100 | }
101 | }
102 | return self.origFuncs.pop.call(data);
103 | }
104 | });
105 |
106 | //override array splice function
107 | self.origFuncs.splice = data.splice;
108 |
109 | Object.defineProperty(self.data, "splice", {
110 | enumerable: false,
111 | configurable: true,
112 | value: function value() {
113 | var args = Array.from(arguments),
114 | start = args[0] < 0 ? data.length + args[0] : args[0],
115 | end = args[1],
116 | newRows = args[2] ? args.slice(2) : false,
117 | startRow;
118 |
119 | if (!self.blocked && version === self.currentVersion) {
120 |
121 | //add new rows
122 | if (newRows) {
123 | startRow = data[start] ? self.table.rowManager.getRowFromDataObject(data[start]) : false;
124 |
125 | if (startRow) {
126 | newRows.forEach(function (rowData) {
127 | self.table.rowManager.addRowActual(rowData, true, startRow, true);
128 | });
129 | } else {
130 | newRows = newRows.slice().reverse();
131 |
132 | newRows.forEach(function (rowData) {
133 | self.table.rowManager.addRowActual(rowData, true, false, true);
134 | });
135 | }
136 | }
137 |
138 | //delete removed rows
139 | if (end !== 0) {
140 | var oldRows = data.slice(start, typeof args[1] === "undefined" ? args[1] : start + end);
141 |
142 | oldRows.forEach(function (rowData, i) {
143 | var row = self.table.rowManager.getRowFromDataObject(rowData);
144 |
145 | if (row) {
146 | row.deleteActual(i !== oldRows.length - 1);
147 | }
148 | });
149 | }
150 |
151 | if (newRows || end !== 0) {
152 | self.table.rowManager.reRenderInPosition();
153 | }
154 | }
155 |
156 | return self.origFuncs.splice.apply(data, arguments);
157 | }
158 | });
159 | };
160 |
161 | ReactiveData.prototype.unwatchData = function () {
162 | if (this.data !== false) {
163 | for (var key in this.origFuncs) {
164 | Object.defineProperty(this.data, key, {
165 | enumerable: true,
166 | configurable: true,
167 | writable: true,
168 | value: this.origFuncs.key
169 | });
170 | }
171 | }
172 | };
173 |
174 | ReactiveData.prototype.watchRow = function (row) {
175 | var self = this,
176 | data = row.getData();
177 |
178 | this.blocked = true;
179 |
180 | for (var key in data) {
181 | this.watchKey(row, data, key);
182 | }
183 |
184 | this.blocked = false;
185 | };
186 |
187 | ReactiveData.prototype.watchKey = function (row, data, key) {
188 | var self = this,
189 | props = Object.getOwnPropertyDescriptor(data, key),
190 | value = data[key],
191 | version = this.currentVersion;
192 |
193 | Object.defineProperty(data, key, {
194 | set: function set(newValue) {
195 | value = newValue;
196 | if (!self.blocked && version === self.currentVersion) {
197 | var update = {};
198 | update[key] = newValue;
199 | row.updateData(update);
200 | }
201 |
202 | if (props.set) {
203 | props.set(newValue);
204 | }
205 | },
206 | get: function get() {
207 |
208 | if (props.get) {
209 | props.get();
210 | }
211 |
212 | return value;
213 | }
214 | });
215 | };
216 |
217 | ReactiveData.prototype.unwatchRow = function (row) {
218 | var data = row.getData();
219 |
220 | for (var key in data) {
221 | Object.defineProperty(data, key, {
222 | value: data[key]
223 | });
224 | }
225 | };
226 |
227 | ReactiveData.prototype.block = function () {
228 | this.blocked = true;
229 | };
230 |
231 | ReactiveData.prototype.unblock = function () {
232 | this.blocked = false;
233 | };
234 |
235 | Tabulator.prototype.registerModule("reactiveData", ReactiveData);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/reactive_data.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var ReactiveData=function(e){this.table=e,this.data=!1,this.blocked=!1,this.origFuncs={},this.currentVersion=0};ReactiveData.prototype.watchData=function(e){var t,a=this;this.currentVersion++,t=this.currentVersion,a.unwatchData(),a.data=e,a.origFuncs.push=e.push,Object.defineProperty(a.data,"push",{enumerable:!1,configurable:!0,value:function(){var r=Array.from(arguments);return a.blocked||t!==a.currentVersion||r.forEach(function(e){a.table.rowManager.addRowActual(e,!1)}),a.origFuncs.push.apply(e,arguments)}}),a.origFuncs.unshift=e.unshift,Object.defineProperty(a.data,"unshift",{enumerable:!1,configurable:!0,value:function(){var r=Array.from(arguments);return a.blocked||t!==a.currentVersion||r.forEach(function(e){a.table.rowManager.addRowActual(e,!0)}),a.origFuncs.unshift.apply(e,arguments)}}),a.origFuncs.shift=e.shift,Object.defineProperty(a.data,"shift",{enumerable:!1,configurable:!0,value:function(){var r;return a.blocked||t!==a.currentVersion||a.data.length&&(r=a.table.rowManager.getRowFromDataObject(a.data[0]))&&r.deleteActual(),a.origFuncs.shift.call(e)}}),a.origFuncs.pop=e.pop,Object.defineProperty(a.data,"pop",{enumerable:!1,configurable:!0,value:function(){var r;return a.blocked||t!==a.currentVersion||a.data.length&&(r=a.table.rowManager.getRowFromDataObject(a.data[a.data.length-1]))&&r.deleteActual(),a.origFuncs.pop.call(e)}}),a.origFuncs.splice=e.splice,Object.defineProperty(a.data,"splice",{enumerable:!1,configurable:!0,value:function(){var r,o=Array.from(arguments),n=o[0]<0?e.length+o[0]:o[0],c=o[1],i=!!o[2]&&o.slice(2);if(!a.blocked&&t===a.currentVersion){if(i&&(r=!!e[n]&&a.table.rowManager.getRowFromDataObject(e[n]),r?i.forEach(function(e){a.table.rowManager.addRowActual(e,!0,r,!0)}):(i=i.slice().reverse(),i.forEach(function(e){a.table.rowManager.addRowActual(e,!0,!1,!0)}))),0!==c){var u=e.slice(n,void 0===o[1]?o[1]:n+c);u.forEach(function(e,t){var r=a.table.rowManager.getRowFromDataObject(e);r&&r.deleteActual(t!==u.length-1)})}(i||0!==c)&&a.table.rowManager.reRenderInPosition()}return a.origFuncs.splice.apply(e,arguments)}})},ReactiveData.prototype.unwatchData=function(){if(!1!==this.data)for(var e in this.origFuncs)Object.defineProperty(this.data,e,{enumerable:!0,configurable:!0,writable:!0,value:this.origFuncs.key})},ReactiveData.prototype.watchRow=function(e){var t=e.getData();this.blocked=!0;for(var a in t)this.watchKey(e,t,a);this.blocked=!1},ReactiveData.prototype.watchKey=function(e,t,a){var r=this,o=Object.getOwnPropertyDescriptor(t,a),n=t[a],c=this.currentVersion;Object.defineProperty(t,a,{set:function(t){if(n=t,!r.blocked&&c===r.currentVersion){var i={};i[a]=t,e.updateData(i)}o.set&&o.set(t)},get:function(){return o.get&&o.get(),n}})},ReactiveData.prototype.unwatchRow=function(e){var t=e.getData();for(var a in t)Object.defineProperty(t,a,{value:t[a]})},ReactiveData.prototype.block=function(){this.blocked=!0},ReactiveData.prototype.unblock=function(){this.blocked=!1},Tabulator.prototype.registerModule("reactiveData",ReactiveData);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/resize_columns.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 |
3 | var ResizeColumns = function ResizeColumns(table) {
4 | this.table = table; //hold Tabulator object
5 | this.startColumn = false;
6 | this.startX = false;
7 | this.startWidth = false;
8 | this.handle = null;
9 | this.prevHandle = null;
10 | };
11 |
12 | ResizeColumns.prototype.initializeColumn = function (type, column, element) {
13 | var self = this,
14 | variableHeight = false,
15 | mode = this.table.options.resizableColumns;
16 |
17 | //set column resize mode
18 | if (type === "header") {
19 | variableHeight = column.definition.formatter == "textarea" || column.definition.variableHeight;
20 | column.modules.resize = { variableHeight: variableHeight };
21 | }
22 |
23 | if (mode === true || mode == type) {
24 |
25 | var handle = document.createElement('div');
26 | handle.className = "tabulator-col-resize-handle";
27 |
28 | var prevHandle = document.createElement('div');
29 | prevHandle.className = "tabulator-col-resize-handle prev";
30 |
31 | handle.addEventListener("click", function (e) {
32 | e.stopPropagation();
33 | });
34 |
35 | var handleDown = function handleDown(e) {
36 | var nearestColumn = column.getLastColumn();
37 |
38 | if (nearestColumn && self._checkResizability(nearestColumn)) {
39 | self.startColumn = column;
40 | self._mouseDown(e, nearestColumn, handle);
41 | }
42 | };
43 |
44 | handle.addEventListener("mousedown", handleDown);
45 | handle.addEventListener("touchstart", handleDown, { passive: true });
46 |
47 | //reszie column on double click
48 | handle.addEventListener("dblclick", function (e) {
49 | var col = column.getLastColumn();
50 |
51 | if (col && self._checkResizability(col)) {
52 | e.stopPropagation();
53 | col.reinitializeWidth(true);
54 | }
55 | });
56 |
57 | prevHandle.addEventListener("click", function (e) {
58 | e.stopPropagation();
59 | });
60 |
61 | var prevHandleDown = function prevHandleDown(e) {
62 | var nearestColumn, colIndex, prevColumn;
63 |
64 | nearestColumn = column.getFirstColumn();
65 |
66 | if (nearestColumn) {
67 | colIndex = self.table.columnManager.findColumnIndex(nearestColumn);
68 | prevColumn = colIndex > 0 ? self.table.columnManager.getColumnByIndex(colIndex - 1) : false;
69 |
70 | if (prevColumn && self._checkResizability(prevColumn)) {
71 | self.startColumn = column;
72 | self._mouseDown(e, prevColumn, prevHandle);
73 | }
74 | }
75 | };
76 |
77 | prevHandle.addEventListener("mousedown", prevHandleDown);
78 | prevHandle.addEventListener("touchstart", prevHandleDown, { passive: true });
79 |
80 | //resize column on double click
81 | prevHandle.addEventListener("dblclick", function (e) {
82 | var nearestColumn, colIndex, prevColumn;
83 |
84 | nearestColumn = column.getFirstColumn();
85 |
86 | if (nearestColumn) {
87 | colIndex = self.table.columnManager.findColumnIndex(nearestColumn);
88 | prevColumn = colIndex > 0 ? self.table.columnManager.getColumnByIndex(colIndex - 1) : false;
89 |
90 | if (prevColumn && self._checkResizability(prevColumn)) {
91 | e.stopPropagation();
92 | prevColumn.reinitializeWidth(true);
93 | }
94 | }
95 | });
96 |
97 | element.appendChild(handle);
98 | element.appendChild(prevHandle);
99 | }
100 | };
101 |
102 | ResizeColumns.prototype._checkResizability = function (column) {
103 | return typeof column.definition.resizable != "undefined" ? column.definition.resizable : this.table.options.resizableColumns;
104 | };
105 |
106 | ResizeColumns.prototype._mouseDown = function (e, column, handle) {
107 | var self = this;
108 |
109 | self.table.element.classList.add("tabulator-block-select");
110 |
111 | function mouseMove(e) {
112 | // self.table.columnManager.tempScrollBlock();
113 |
114 | column.setWidth(self.startWidth + ((typeof e.screenX === "undefined" ? e.touches[0].screenX : e.screenX) - self.startX));
115 |
116 | if (!self.table.browserSlow && column.modules.resize && column.modules.resize.variableHeight) {
117 | column.checkCellHeights();
118 | }
119 | }
120 |
121 | function mouseUp(e) {
122 |
123 | //block editor from taking action while resizing is taking place
124 | if (self.startColumn.modules.edit) {
125 | self.startColumn.modules.edit.blocked = false;
126 | }
127 |
128 | if (self.table.browserSlow && column.modules.resize && column.modules.resize.variableHeight) {
129 | column.checkCellHeights();
130 | }
131 |
132 | document.body.removeEventListener("mouseup", mouseUp);
133 | document.body.removeEventListener("mousemove", mouseMove);
134 |
135 | handle.removeEventListener("touchmove", mouseMove);
136 | handle.removeEventListener("touchend", mouseUp);
137 |
138 | self.table.element.classList.remove("tabulator-block-select");
139 |
140 | if (self.table.options.persistence && self.table.modExists("persistence", true) && self.table.modules.persistence.config.columns) {
141 | self.table.modules.persistence.save("columns");
142 | }
143 |
144 | self.table.options.columnResized.call(self.table, column.getComponent());
145 | }
146 |
147 | e.stopPropagation(); //prevent resize from interfereing with movable columns
148 |
149 | //block editor from taking action while resizing is taking place
150 | if (self.startColumn.modules.edit) {
151 | self.startColumn.modules.edit.blocked = true;
152 | }
153 |
154 | self.startX = typeof e.screenX === "undefined" ? e.touches[0].screenX : e.screenX;
155 | self.startWidth = column.getWidth();
156 |
157 | document.body.addEventListener("mousemove", mouseMove);
158 | document.body.addEventListener("mouseup", mouseUp);
159 | handle.addEventListener("touchmove", mouseMove, { passive: true });
160 | handle.addEventListener("touchend", mouseUp);
161 | };
162 |
163 | Tabulator.prototype.registerModule("resizeColumns", ResizeColumns);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/resize_columns.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var ResizeColumns=function(e){this.table=e,this.startColumn=!1,this.startX=!1,this.startWidth=!1,this.handle=null,this.prevHandle=null};ResizeColumns.prototype.initializeColumn=function(e,t,n){var o=this,i=!1,s=this.table.options.resizableColumns;if("header"===e&&(i="textarea"==t.definition.formatter||t.definition.variableHeight,t.modules.resize={variableHeight:i}),!0===s||s==e){var a=document.createElement("div");a.className="tabulator-col-resize-handle";var l=document.createElement("div");l.className="tabulator-col-resize-handle prev",a.addEventListener("click",function(e){e.stopPropagation()});var r=function(e){var n=t.getLastColumn();n&&o._checkResizability(n)&&(o.startColumn=t,o._mouseDown(e,n,a))};a.addEventListener("mousedown",r),a.addEventListener("touchstart",r,{passive:!0}),a.addEventListener("dblclick",function(e){var n=t.getLastColumn();n&&o._checkResizability(n)&&(e.stopPropagation(),n.reinitializeWidth(!0))}),l.addEventListener("click",function(e){e.stopPropagation()});var d=function(e){var n,i,s;(n=t.getFirstColumn())&&(i=o.table.columnManager.findColumnIndex(n),(s=i>0&&o.table.columnManager.getColumnByIndex(i-1))&&o._checkResizability(s)&&(o.startColumn=t,o._mouseDown(e,s,l)))};l.addEventListener("mousedown",d),l.addEventListener("touchstart",d,{passive:!0}),l.addEventListener("dblclick",function(e){var n,i,s;(n=t.getFirstColumn())&&(i=o.table.columnManager.findColumnIndex(n),(s=i>0&&o.table.columnManager.getColumnByIndex(i-1))&&o._checkResizability(s)&&(e.stopPropagation(),s.reinitializeWidth(!0)))}),n.appendChild(a),n.appendChild(l)}},ResizeColumns.prototype._checkResizability=function(e){return void 0!==e.definition.resizable?e.definition.resizable:this.table.options.resizableColumns},ResizeColumns.prototype._mouseDown=function(e,t,n){function o(e){t.setWidth(s.startWidth+((void 0===e.screenX?e.touches[0].screenX:e.screenX)-s.startX)),!s.table.browserSlow&&t.modules.resize&&t.modules.resize.variableHeight&&t.checkCellHeights()}function i(e){s.startColumn.modules.edit&&(s.startColumn.modules.edit.blocked=!1),s.table.browserSlow&&t.modules.resize&&t.modules.resize.variableHeight&&t.checkCellHeights(),document.body.removeEventListener("mouseup",i),document.body.removeEventListener("mousemove",o),n.removeEventListener("touchmove",o),n.removeEventListener("touchend",i),s.table.element.classList.remove("tabulator-block-select"),s.table.options.persistence&&s.table.modExists("persistence",!0)&&s.table.modules.persistence.config.columns&&s.table.modules.persistence.save("columns"),s.table.options.columnResized.call(s.table,t.getComponent())}var s=this;s.table.element.classList.add("tabulator-block-select"),e.stopPropagation(),s.startColumn.modules.edit&&(s.startColumn.modules.edit.blocked=!0),s.startX=void 0===e.screenX?e.touches[0].screenX:e.screenX,s.startWidth=t.getWidth(),document.body.addEventListener("mousemove",o),document.body.addEventListener("mouseup",i),n.addEventListener("touchmove",o,{passive:!0}),n.addEventListener("touchend",i)},Tabulator.prototype.registerModule("resizeColumns",ResizeColumns);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/resize_rows.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 |
3 | var ResizeRows = function ResizeRows(table) {
4 | this.table = table; //hold Tabulator object
5 | this.startColumn = false;
6 | this.startY = false;
7 | this.startHeight = false;
8 | this.handle = null;
9 | this.prevHandle = null;
10 | };
11 |
12 | ResizeRows.prototype.initializeRow = function (row) {
13 | var self = this,
14 | rowEl = row.getElement();
15 |
16 | var handle = document.createElement('div');
17 | handle.className = "tabulator-row-resize-handle";
18 |
19 | var prevHandle = document.createElement('div');
20 | prevHandle.className = "tabulator-row-resize-handle prev";
21 |
22 | handle.addEventListener("click", function (e) {
23 | e.stopPropagation();
24 | });
25 |
26 | var handleDown = function handleDown(e) {
27 | self.startRow = row;
28 | self._mouseDown(e, row, handle);
29 | };
30 |
31 | handle.addEventListener("mousedown", handleDown);
32 | handle.addEventListener("touchstart", handleDown, { passive: true });
33 |
34 | prevHandle.addEventListener("click", function (e) {
35 | e.stopPropagation();
36 | });
37 |
38 | var prevHandleDown = function prevHandleDown(e) {
39 | var prevRow = self.table.rowManager.prevDisplayRow(row);
40 |
41 | if (prevRow) {
42 | self.startRow = prevRow;
43 | self._mouseDown(e, prevRow, prevHandle);
44 | }
45 | };
46 |
47 | prevHandle.addEventListener("mousedown", prevHandleDown);
48 | prevHandle.addEventListener("touchstart", prevHandleDown, { passive: true });
49 |
50 | rowEl.appendChild(handle);
51 | rowEl.appendChild(prevHandle);
52 | };
53 |
54 | ResizeRows.prototype._mouseDown = function (e, row, handle) {
55 | var self = this;
56 |
57 | self.table.element.classList.add("tabulator-block-select");
58 |
59 | function mouseMove(e) {
60 | row.setHeight(self.startHeight + ((typeof e.screenY === "undefined" ? e.touches[0].screenY : e.screenY) - self.startY));
61 | }
62 |
63 | function mouseUp(e) {
64 |
65 | // //block editor from taking action while resizing is taking place
66 | // if(self.startColumn.modules.edit){
67 | // self.startColumn.modules.edit.blocked = false;
68 | // }
69 |
70 | document.body.removeEventListener("mouseup", mouseMove);
71 | document.body.removeEventListener("mousemove", mouseMove);
72 |
73 | handle.removeEventListener("touchmove", mouseMove);
74 | handle.removeEventListener("touchend", mouseUp);
75 |
76 | self.table.element.classList.remove("tabulator-block-select");
77 |
78 | self.table.options.rowResized.call(this.table, row.getComponent());
79 | }
80 |
81 | e.stopPropagation(); //prevent resize from interfereing with movable columns
82 |
83 | //block editor from taking action while resizing is taking place
84 | // if(self.startColumn.modules.edit){
85 | // self.startColumn.modules.edit.blocked = true;
86 | // }
87 |
88 | self.startY = typeof e.screenY === "undefined" ? e.touches[0].screenY : e.screenY;
89 | self.startHeight = row.getHeight();
90 |
91 | document.body.addEventListener("mousemove", mouseMove);
92 | document.body.addEventListener("mouseup", mouseUp);
93 |
94 | handle.addEventListener("touchmove", mouseMove, { passive: true });
95 | handle.addEventListener("touchend", mouseUp);
96 | };
97 |
98 | Tabulator.prototype.registerModule("resizeRows", ResizeRows);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/resize_rows.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var ResizeRows=function(e){this.table=e,this.startColumn=!1,this.startY=!1,this.startHeight=!1,this.handle=null,this.prevHandle=null};ResizeRows.prototype.initializeRow=function(e){var t=this,o=e.getElement(),s=document.createElement("div");s.className="tabulator-row-resize-handle";var n=document.createElement("div");n.className="tabulator-row-resize-handle prev",s.addEventListener("click",function(e){e.stopPropagation()});var a=function(o){t.startRow=e,t._mouseDown(o,e,s)};s.addEventListener("mousedown",a),s.addEventListener("touchstart",a,{passive:!0}),n.addEventListener("click",function(e){e.stopPropagation()});var r=function(o){var s=t.table.rowManager.prevDisplayRow(e);s&&(t.startRow=s,t._mouseDown(o,s,n))};n.addEventListener("mousedown",r),n.addEventListener("touchstart",r,{passive:!0}),o.appendChild(s),o.appendChild(n)},ResizeRows.prototype._mouseDown=function(e,t,o){function s(e){t.setHeight(a.startHeight+((void 0===e.screenY?e.touches[0].screenY:e.screenY)-a.startY))}function n(e){document.body.removeEventListener("mouseup",s),document.body.removeEventListener("mousemove",s),o.removeEventListener("touchmove",s),o.removeEventListener("touchend",n),a.table.element.classList.remove("tabulator-block-select"),a.table.options.rowResized.call(this.table,t.getComponent())}var a=this;a.table.element.classList.add("tabulator-block-select"),e.stopPropagation(),a.startY=void 0===e.screenY?e.touches[0].screenY:e.screenY,a.startHeight=t.getHeight(),document.body.addEventListener("mousemove",s),document.body.addEventListener("mouseup",n),o.addEventListener("touchmove",s,{passive:!0}),o.addEventListener("touchend",n)},Tabulator.prototype.registerModule("resizeRows",ResizeRows);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/resize_table.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 |
3 | var ResizeTable = function ResizeTable(table) {
4 | this.table = table; //hold Tabulator object
5 | this.binding = false;
6 | this.observer = false;
7 | this.containerObserver = false;
8 |
9 | this.tableHeight = 0;
10 | this.tableWidth = 0;
11 | this.containerHeight = 0;
12 | this.containerWidth = 0;
13 |
14 | this.autoResize = false;
15 | };
16 |
17 | ResizeTable.prototype.initialize = function (row) {
18 | var _this = this;
19 |
20 | var table = this.table,
21 | tableStyle;
22 |
23 | this.tableHeight = table.element.clientHeight;
24 | this.tableWidth = table.element.clientWidth;
25 |
26 | if (table.element.parentNode) {
27 | this.containerHeight = table.element.parentNode.clientHeight;
28 | this.containerWidth = table.element.parentNode.clientWidth;
29 | }
30 |
31 | if (typeof ResizeObserver !== "undefined" && table.rowManager.getRenderMode() === "virtual") {
32 |
33 | this.autoResize = true;
34 |
35 | this.observer = new ResizeObserver(function (entry) {
36 | if (!table.browserMobile || table.browserMobile && !table.modules.edit.currentCell) {
37 |
38 | var nodeHeight = Math.floor(entry[0].contentRect.height);
39 | var nodeWidth = Math.floor(entry[0].contentRect.width);
40 |
41 | if (_this.tableHeight != nodeHeight || _this.tableWidth != nodeWidth) {
42 | _this.tableHeight = nodeHeight;
43 | _this.tableWidth = nodeWidth;
44 |
45 | if (table.element.parentNode) {
46 | _this.containerHeight = table.element.parentNode.clientHeight;
47 | _this.containerWidth = table.element.parentNode.clientWidth;
48 | }
49 |
50 | table.redraw();
51 | }
52 | }
53 | });
54 |
55 | this.observer.observe(table.element);
56 |
57 | tableStyle = window.getComputedStyle(table.element);
58 |
59 | if (this.table.element.parentNode && !this.table.rowManager.fixedHeight && (tableStyle.getPropertyValue("max-height") || tableStyle.getPropertyValue("min-height"))) {
60 |
61 | this.containerObserver = new ResizeObserver(function (entry) {
62 | if (!table.browserMobile || table.browserMobile && !table.modules.edit.currentCell) {
63 |
64 | var nodeHeight = Math.floor(entry[0].contentRect.height);
65 | var nodeWidth = Math.floor(entry[0].contentRect.width);
66 |
67 | if (_this.containerHeight != nodeHeight || _this.containerWidth != nodeWidth) {
68 | _this.containerHeight = nodeHeight;
69 | _this.containerWidth = nodeWidth;
70 | _this.tableHeight = table.element.clientHeight;
71 | _this.tableWidth = table.element.clientWidth;
72 |
73 | table.redraw();
74 | }
75 |
76 | table.redraw();
77 | }
78 | });
79 |
80 | this.containerObserver.observe(this.table.element.parentNode);
81 | }
82 | } else {
83 | this.binding = function () {
84 | if (!table.browserMobile || table.browserMobile && !table.modules.edit.currentCell) {
85 | table.redraw();
86 | }
87 | };
88 |
89 | window.addEventListener("resize", this.binding);
90 | }
91 | };
92 |
93 | ResizeTable.prototype.clearBindings = function (row) {
94 | if (this.binding) {
95 | window.removeEventListener("resize", this.binding);
96 | }
97 |
98 | if (this.observer) {
99 | this.observer.unobserve(this.table.element);
100 | }
101 |
102 | if (this.containerObserver) {
103 | this.containerObserver.unobserve(this.table.element.parentNode);
104 | }
105 | };
106 |
107 | Tabulator.prototype.registerModule("resizeTable", ResizeTable);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/resize_table.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var ResizeTable=function(e){this.table=e,this.binding=!1,this.observer=!1,this.containerObserver=!1,this.tableHeight=0,this.tableWidth=0,this.containerHeight=0,this.containerWidth=0,this.autoResize=!1};ResizeTable.prototype.initialize=function(e){var t,i=this,n=this.table;this.tableHeight=n.element.clientHeight,this.tableWidth=n.element.clientWidth,n.element.parentNode&&(this.containerHeight=n.element.parentNode.clientHeight,this.containerWidth=n.element.parentNode.clientWidth),"undefined"!=typeof ResizeObserver&&"virtual"===n.rowManager.getRenderMode()?(this.autoResize=!0,this.observer=new ResizeObserver(function(e){if(!n.browserMobile||n.browserMobile&&!n.modules.edit.currentCell){var t=Math.floor(e[0].contentRect.height),r=Math.floor(e[0].contentRect.width);i.tableHeight==t&&i.tableWidth==r||(i.tableHeight=t,i.tableWidth=r,n.element.parentNode&&(i.containerHeight=n.element.parentNode.clientHeight,i.containerWidth=n.element.parentNode.clientWidth),n.redraw())}}),this.observer.observe(n.element),t=window.getComputedStyle(n.element),this.table.element.parentNode&&!this.table.rowManager.fixedHeight&&(t.getPropertyValue("max-height")||t.getPropertyValue("min-height"))&&(this.containerObserver=new ResizeObserver(function(e){if(!n.browserMobile||n.browserMobile&&!n.modules.edit.currentCell){var t=Math.floor(e[0].contentRect.height),r=Math.floor(e[0].contentRect.width);i.containerHeight==t&&i.containerWidth==r||(i.containerHeight=t,i.containerWidth=r,i.tableHeight=n.element.clientHeight,i.tableWidth=n.element.clientWidth,n.redraw()),n.redraw()}}),this.containerObserver.observe(this.table.element.parentNode))):(this.binding=function(){(!n.browserMobile||n.browserMobile&&!n.modules.edit.currentCell)&&n.redraw()},window.addEventListener("resize",this.binding))},ResizeTable.prototype.clearBindings=function(e){this.binding&&window.removeEventListener("resize",this.binding),this.observer&&this.observer.unobserve(this.table.element),this.containerObserver&&this.containerObserver.unobserve(this.table.element.parentNode)},Tabulator.prototype.registerModule("resizeTable",ResizeTable);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/responsive_layout.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var ResponsiveLayout=function(e){this.table=e,this.columns=[],this.hiddenColumns=[],this.mode="",this.index=0,this.collapseFormatter=[],this.collapseStartOpen=!0,this.collapseHandleColumn=!1};ResponsiveLayout.prototype.initialize=function(){var e=this,t=[];this.mode=this.table.options.responsiveLayout,this.collapseFormatter=this.table.options.responsiveLayoutCollapseFormatter||this.formatCollapsedData,this.collapseStartOpen=this.table.options.responsiveLayoutCollapseStartOpen,this.hiddenColumns=[],this.table.columnManager.columnsByIndex.forEach(function(o,n){o.modules.responsive&&o.modules.responsive.order&&o.modules.responsive.visible&&(o.modules.responsive.index=n,t.push(o),o.visible||"collapse"!==e.mode||e.hiddenColumns.push(o))}),t=t.reverse(),t=t.sort(function(e,t){return t.modules.responsive.order-e.modules.responsive.order||t.modules.responsive.index-e.modules.responsive.index}),this.columns=t,"collapse"===this.mode&&this.generateCollapsedContent();for(var o=this.table.columnManager.columnsByIndex,n=Array.isArray(o),s=0,o=n?o:o[Symbol.iterator]();;){var i;if(n){if(s>=o.length)break;i=o[s++]}else{if(s=o.next(),s.done)break;i=s.value}var l=i;if("responsiveCollapse"==l.definition.formatter){this.collapseHandleColumn=l;break}}this.collapseHandleColumn&&(this.hiddenColumns.length?this.collapseHandleColumn.show():this.collapseHandleColumn.hide())},ResponsiveLayout.prototype.initializeColumn=function(e){var t=e.getDefinition();e.modules.responsive={order:void 0===t.responsive?1:t.responsive,visible:!1!==t.visible}},ResponsiveLayout.prototype.initializeRow=function(e){var t;"calc"!==e.type&&(t=document.createElement("div"),t.classList.add("tabulator-responsive-collapse"),e.modules.responsiveLayout={element:t,open:this.collapseStartOpen},this.collapseStartOpen||(t.style.display="none"))},ResponsiveLayout.prototype.layoutRow=function(e){var t=e.getElement();e.modules.responsiveLayout&&(t.appendChild(e.modules.responsiveLayout.element),this.generateCollapsedRowContent(e))},ResponsiveLayout.prototype.updateColumnVisibility=function(e,t){e.modules.responsive&&(e.modules.responsive.visible=t,this.initialize())},ResponsiveLayout.prototype.hideColumn=function(e){var t=this.hiddenColumns.length;e.hide(!1,!0),"collapse"===this.mode&&(this.hiddenColumns.unshift(e),this.generateCollapsedContent(),this.collapseHandleColumn&&!t&&this.collapseHandleColumn.show())},ResponsiveLayout.prototype.showColumn=function(e){var t;e.show(!1,!0),e.setWidth(e.getWidth()),"collapse"===this.mode&&(t=this.hiddenColumns.indexOf(e),t>-1&&this.hiddenColumns.splice(t,1),this.generateCollapsedContent(),this.collapseHandleColumn&&!this.hiddenColumns.length&&this.collapseHandleColumn.hide())},ResponsiveLayout.prototype.update=function(){for(var e=this,t=!0;t;){var o="fitColumns"==e.table.modules.layout.getMode()?e.table.columnManager.getFlexBaseWidth():e.table.columnManager.getWidth(),n=(e.table.options.headerVisible?e.table.columnManager.element.clientWidth:e.table.element.clientWidth)-o;if(n<0){var s=e.columns[e.index];s?(e.hideColumn(s),e.index++):t=!1}else{var i=e.columns[e.index-1];i&&n>0&&n>=i.getWidth()?(e.showColumn(i),e.index--):t=!1}e.table.rowManager.activeRowsCount||e.table.rowManager.renderEmptyScroll()}},ResponsiveLayout.prototype.generateCollapsedContent=function(){var e=this;this.table.rowManager.getDisplayRows().forEach(function(t){e.generateCollapsedRowContent(t)})},ResponsiveLayout.prototype.generateCollapsedRowContent=function(e){var t,o;if(e.modules.responsiveLayout){for(t=e.modules.responsiveLayout.element;t.firstChild;)t.removeChild(t.firstChild);o=this.collapseFormatter(this.generateCollapsedRowData(e)),o&&t.appendChild(o)}},ResponsiveLayout.prototype.generateCollapsedRowData=function(e){var t,o=this,n=e.getData(),s=[];return this.hiddenColumns.forEach(function(i){var l=i.getFieldValue(n);i.definition.title&&i.field&&(i.modules.format&&o.table.options.responsiveLayoutCollapseUseFormatters?(t={value:!1,data:{},getValue:function(){return l},getData:function(){return n},getElement:function(){return document.createElement("div")},getRow:function(){return e.getComponent()},getColumn:function(){return i.getComponent()}},s.push({title:i.definition.title,value:i.modules.format.formatter.call(o.table.modules.format,t,i.modules.format.params)})):s.push({title:i.definition.title,value:l}))}),s},ResponsiveLayout.prototype.formatCollapsedData=function(e){var t=document.createElement("table"),o="";return e.forEach(function(e){var t=document.createElement("div");e.value instanceof Node&&(t.appendChild(e.value),e.value=t.innerHTML),o+=""+e.title+" | "+e.value+" |
"}),t.innerHTML=o,Object.keys(e).length?t:""},Tabulator.prototype.registerModule("responsiveLayout",ResponsiveLayout);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/select_row.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},SelectRow=function(e){this.table=e,this.selecting=!1,this.lastClickedRow=!1,this.selectPrev=[],this.selectedRows=[],this.headerCheckboxElement=null};SelectRow.prototype.clearSelectionData=function(e){this.selecting=!1,this.lastClickedRow=!1,this.selectPrev=[],this.selectedRows=[],e||this._rowSelectionChanged()},SelectRow.prototype.initializeRow=function(e){var t=this,o=e.getElement(),l=function e(){setTimeout(function(){t.selecting=!1},50),document.body.removeEventListener("mouseup",e)};e.modules.select={selected:!1},t.table.options.selectableCheck.call(this.table,e.getComponent())?(o.classList.add("tabulator-selectable"),o.classList.remove("tabulator-unselectable"),t.table.options.selectable&&"highlight"!=t.table.options.selectable&&("click"===t.table.options.selectableRangeMode?o.addEventListener("click",function(o){if(o.shiftKey){t.table._clearSelection(),t.lastClickedRow=t.lastClickedRow||e;var l=t.table.rowManager.getDisplayRowIndex(t.lastClickedRow),s=t.table.rowManager.getDisplayRowIndex(e),c=l<=s?l:s,i=l>=s?l:s,n=t.table.rowManager.getDisplayRows().slice(0),a=n.splice(c,i-c+1);o.ctrlKey||o.metaKey?(a.forEach(function(o){o!==t.lastClickedRow&&(!0===t.table.options.selectable||t.isRowSelected(e)?t.toggleRow(o):t.selectedRows.lengtht.table.options.selectable&&(a=a.slice(0,t.table.options.selectable)),t.selectRows(a)),t.table._clearSelection()}else o.ctrlKey||o.metaKey?(t.toggleRow(e),t.lastClickedRow=e):(t.deselectRows(void 0,!0),t.selectRows(e),t.lastClickedRow=e)}):(o.addEventListener("click",function(o){t.table.modExists("edit")&&t.table.modules.edit.getCurrentCell()||t.table._clearSelection(),t.selecting||t.toggleRow(e)}),o.addEventListener("mousedown",function(o){if(o.shiftKey)return t.table._clearSelection(),t.selecting=!0,t.selectPrev=[],document.body.addEventListener("mouseup",l),document.body.addEventListener("keyup",l),t.toggleRow(e),!1}),o.addEventListener("mouseenter",function(o){t.selecting&&(t.table._clearSelection(),t.toggleRow(e),t.selectPrev[1]==e&&t.toggleRow(t.selectPrev[0]))}),o.addEventListener("mouseout",function(o){t.selecting&&(t.table._clearSelection(),t.selectPrev.unshift(e))})))):(o.classList.add("tabulator-unselectable"),o.classList.remove("tabulator-selectable"))},SelectRow.prototype.toggleRow=function(e){this.table.options.selectableCheck.call(this.table,e.getComponent())&&(e.modules.select&&e.modules.select.selected?this._deselectRow(e):this._selectRow(e))},SelectRow.prototype.selectRows=function(e){var t,o=this;switch(void 0===e?"undefined":_typeof(e)){case"undefined":this.table.rowManager.rows.forEach(function(e){o._selectRow(e,!0,!0)}),this._rowSelectionChanged();break;case"string":t=this.table.rowManager.findRow(e),t?this._selectRow(t,!0,!0):this.table.rowManager.getRows(e).forEach(function(e){o._selectRow(e,!0,!0)}),this._rowSelectionChanged();break;default:Array.isArray(e)?(e.forEach(function(e){o._selectRow(e,!0,!0)}),this._rowSelectionChanged()):this._selectRow(e,!1,!0)}},SelectRow.prototype._selectRow=function(e,t,o){if(!isNaN(this.table.options.selectable)&&!0!==this.table.options.selectable&&!o&&this.selectedRows.length>=this.table.options.selectable){if(!this.table.options.selectableRollingSelection)return!1;this._deselectRow(this.selectedRows[0])}var l=this.table.rowManager.findRow(e);l?-1==this.selectedRows.indexOf(l)&&(l.modules.select||(l.modules.select={}),l.modules.select.selected=!0,l.modules.select.checkboxEl&&(l.modules.select.checkboxEl.checked=!0),l.getElement().classList.add("tabulator-selected"),this.selectedRows.push(l),this.table.options.dataTreeSelectPropagate&&this.childRowSelection(l,!0),t||this.table.options.rowSelected.call(this.table,l.getComponent()),this._rowSelectionChanged(t)):t||console.warn("Selection Error - No such row found, ignoring selection:"+e)},SelectRow.prototype.isRowSelected=function(e){return-1!==this.selectedRows.indexOf(e)},SelectRow.prototype.deselectRows=function(e,t){var o,l=this;if(void 0===e){o=l.selectedRows.length;for(var s=0;s-1&&(s.modules.select||(s.modules.select={}),s.modules.select.selected=!1,s.modules.select.checkboxEl&&(s.modules.select.checkboxEl.checked=!1),s.getElement().classList.remove("tabulator-selected"),l.selectedRows.splice(o,1),this.table.options.dataTreeSelectPropagate&&this.childRowSelection(s,!1),t||l.table.options.rowDeselected.call(this.table,s.getComponent()),l._rowSelectionChanged(t)):t||console.warn("Deselection Error - No such row found, ignoring selection:"+e)},SelectRow.prototype.getSelectedData=function(){var e=[];return this.selectedRows.forEach(function(t){e.push(t.getData())}),e},SelectRow.prototype.getSelectedRows=function(){var e=[];return this.selectedRows.forEach(function(t){e.push(t.getComponent())}),e},SelectRow.prototype._rowSelectionChanged=function(e){this.headerCheckboxElement&&(0===this.selectedRows.length?(this.headerCheckboxElement.checked=!1,this.headerCheckboxElement.indeterminate=!1):this.table.rowManager.rows.length===this.selectedRows.length?(this.headerCheckboxElement.checked=!0,this.headerCheckboxElement.indeterminate=!1):(this.headerCheckboxElement.indeterminate=!0,this.headerCheckboxElement.checked=!1)),e||this.table.options.rowSelectionChanged.call(this.table,this.getSelectedData(),this.getSelectedRows())},SelectRow.prototype.registerRowSelectCheckbox=function(e,t){e._row.modules.select||(e._row.modules.select={}),e._row.modules.select.checkboxEl=t},SelectRow.prototype.registerHeaderSelectCheckbox=function(e){this.headerCheckboxElement=e},SelectRow.prototype.childRowSelection=function(e,t){var o=this.table.modules.dataTree.getChildren(e);if(t)for(var l=o,s=Array.isArray(l),c=0,l=s?l:l[Symbol.iterator]();;){var i;if(s){if(c>=l.length)break;i=l[c++]}else{if(c=l.next(),c.done)break;i=c.value}var n=i;this._selectRow(n,!0)}else for(var a=o,r=Array.isArray(a),d=0,a=r?a:a[Symbol.iterator]();;){var h;if(r){if(d>=a.length)break;h=a[d++]}else{if(d=a.next(),d.done)break;h=d.value}var w=h;this._deselectRow(w,!0)}},Tabulator.prototype.registerModule("selectRow",SelectRow);
--------------------------------------------------------------------------------
/resources/app/lib/js/tabulator/modules/validate.min.js:
--------------------------------------------------------------------------------
1 | /* Tabulator v4.7.1 (c) Oliver Folkerd */
2 | var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Validate=function(t){this.table=t,this.invalidCells=[]};Validate.prototype.initializeColumn=function(t){var i,n=this,e=[];t.definition.validator&&(Array.isArray(t.definition.validator)?t.definition.validator.forEach(function(t){(i=n._extractValidator(t))&&e.push(i)}):(i=this._extractValidator(t.definition.validator))&&e.push(i),t.modules.validate=!!e.length&&e)},Validate.prototype._extractValidator=function(t){var i,n,e;switch(void 0===t?"undefined":_typeof(t)){case"string":return e=t.indexOf(":"),e>-1?(i=t.substring(0,e),n=t.substring(e+1)):i=t,this._buildValidator(i,n);case"function":return this._buildValidator(t);case"object":return this._buildValidator(t.type,t.parameters)}},Validate.prototype._buildValidator=function(t,i){var n="function"==typeof t?t:this.validators[t];return n?{type:"function"==typeof t?"function":t,func:n,params:i}:(console.warn("Validator Setup Error - No matching validator found:",t),!1)},Validate.prototype.validate=function(t,i,n){var e=this,a=[],o=this.invalidCells.indexOf(i);return t&&t.forEach(function(t){t.func.call(e,i.getComponent(),n,t.params)||a.push({type:t.type,parameters:t.params})}),a=!a.length||a,i.modules.validate||(i.modules.validate={}),!0===a?(i.modules.validate.invalid=!1,i.getElement().classList.remove("tabulator-validation-fail"),o>-1&&this.invalidCells.splice(o,1)):(i.modules.validate.invalid=!0,"manual"!==this.table.options.validationMode&&i.getElement().classList.add("tabulator-validation-fail"),-1==o&&this.invalidCells.push(i)),a},Validate.prototype.getInvalidCells=function(){var t=[];return this.invalidCells.forEach(function(i){t.push(i.getComponent())}),t},Validate.prototype.clearValidation=function(t){var i;t.modules.validate&&t.modules.validate.invalid&&(t.element.classList.remove("tabulator-validation-fail"),t.modules.validate.invalid=!1,(i=this.invalidCells.indexOf(t))>-1&&this.invalidCells.splice(i,1))},Validate.prototype.validators={integer:function(t,i,n){return""===i||null===i||void 0===i||"number"==typeof(i=Number(i))&&isFinite(i)&&Math.floor(i)===i},float:function(t,i,n){return""===i||null===i||void 0===i||"number"==typeof(i=Number(i))&&isFinite(i)&&i%1!=0},numeric:function(t,i,n){return""===i||null===i||void 0===i||!isNaN(i)},string:function(t,i,n){return""===i||null===i||void 0===i||isNaN(i)},max:function(t,i,n){return""===i||null===i||void 0===i||parseFloat(i)<=n},min:function(t,i,n){return""===i||null===i||void 0===i||parseFloat(i)>=n},starts:function(t,i,n){return""===i||null===i||void 0===i||String(i).toLowerCase().startsWith(String(n).toLowerCase())},ends:function(t,i,n){return""===i||null===i||void 0===i||String(i).toLowerCase().endsWith(String(n).toLowerCase())},minLength:function(t,i,n){return""===i||null===i||void 0===i||String(i).length>=n},maxLength:function(t,i,n){return""===i||null===i||void 0===i||String(i).length<=n},in:function(t,i,n){return""===i||null===i||void 0===i||("string"==typeof n&&(n=n.split("|")),""===i||n.indexOf(i)>-1)},regex:function(t,i,n){return""===i||null===i||void 0===i||new RegExp(n).test(i)},unique:function(t,i,n){if(""===i||null===i||void 0===i)return!0;var e=!0,a=t.getData(),o=t.getColumn()._getSelf();return this.table.rowManager.rows.forEach(function(t){var n=t.getData();n!==a&&i==o.getFieldValue(n)&&(e=!1)}),e},required:function(t,i,n){return""!==i&&null!==i&&void 0!==i}},Tabulator.prototype.registerModule("validate",Validate);
--------------------------------------------------------------------------------
/resources/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giwty/switch-library-manager/f69daa7e5e3bc9fc2cc17ad839a12ac3bf5e0372/resources/icon.icns
--------------------------------------------------------------------------------
/resources/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giwty/switch-library-manager/f69daa7e5e3bc9fc2cc17ad839a12ac3bf5e0372/resources/icon.ico
--------------------------------------------------------------------------------
/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giwty/switch-library-manager/f69daa7e5e3bc9fc2cc17ad839a12ac3bf5e0372/resources/icon.png
--------------------------------------------------------------------------------
/settings/keys.go:
--------------------------------------------------------------------------------
1 | package settings
2 |
3 | import (
4 | "errors"
5 | "github.com/magiconair/properties"
6 | "path/filepath"
7 | )
8 |
9 | var (
10 | keysInstance *switchKeys
11 | )
12 |
13 | type switchKeys struct {
14 | keys map[string]string
15 | }
16 |
17 | func (k *switchKeys) GetKey(keyName string) string {
18 | return k.keys[keyName]
19 | }
20 |
21 | func SwitchKeys() (*switchKeys, error) {
22 | return keysInstance, nil
23 | }
24 |
25 | func InitSwitchKeys(baseFolder string) (*switchKeys, error) {
26 |
27 | // init from a file
28 | path := filepath.Join(baseFolder, "prod.keys")
29 | p, err := properties.LoadFile(path, properties.UTF8)
30 | if err != nil {
31 | path = "${HOME}/.switch/prod.keys"
32 | p, err = properties.LoadFile(path, properties.UTF8)
33 | }
34 | settings := ReadSettings(baseFolder)
35 | if err != nil {
36 | path := settings.Prodkeys
37 | if path != "" {
38 | p, err = properties.LoadFile(filepath.Join(path, "prod.keys"), properties.UTF8)
39 | }
40 | }
41 | if err != nil {
42 | return nil, errors.New("Error trying to read prod.keys [reason:" + err.Error() + "]")
43 | }
44 | settings.Prodkeys = path
45 | SaveSettings(settings, baseFolder)
46 | keysInstance = &switchKeys{keys: map[string]string{}}
47 | for _, key := range p.Keys() {
48 | value, _ := p.Get(key)
49 | keysInstance.keys[key] = value
50 | }
51 |
52 | return keysInstance, nil
53 | }
54 |
--------------------------------------------------------------------------------
/settings/settings.go:
--------------------------------------------------------------------------------
1 | package settings
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/mcuadros/go-version"
7 | "go.uber.org/zap"
8 | "io/ioutil"
9 | "net/http"
10 | "os"
11 | "path/filepath"
12 | )
13 |
14 | var (
15 | settingsInstance *AppSettings
16 | )
17 |
18 | const (
19 | SETTINGS_FILENAME = "settings.json"
20 | TITLE_JSON_FILENAME = "titles.json"
21 | VERSIONS_JSON_FILENAME = "versions.json"
22 | SLM_VERSION = "1.4.0"
23 | TITLES_JSON_URL = "https://tinfoil.media/repo/db/titles.json"
24 | //TITLES_JSON_URL = "https://raw.githubusercontent.com/blawar/titledb/master/titles.US.en.json"
25 | VERSIONS_JSON_URL = "https://tinfoil.media/repo/db/versions.json"
26 | //VERSIONS_JSON_URL = "https://raw.githubusercontent.com/blawar/titledb/master/versions.json"
27 | SLM_VERSION_URL = "https://raw.githubusercontent.com/giwty/switch-library-manager/master/slm.json"
28 | )
29 |
30 | const (
31 | TEMPLATE_TITLE_ID = "TITLE_ID"
32 | TEMPLATE_TITLE_NAME = "TITLE_NAME"
33 | TEMPLATE_DLC_NAME = "DLC_NAME"
34 | TEMPLATE_VERSION = "VERSION"
35 | TEMPLATE_REGION = "REGION"
36 | TEMPLATE_VERSION_TXT = "VERSION_TXT"
37 | TEMPLATE_TYPE = "TYPE"
38 | )
39 |
40 | type OrganizeOptions struct {
41 | CreateFolderPerGame bool `json:"create_folder_per_game"`
42 | RenameFiles bool `json:"rename_files"`
43 | DeleteEmptyFolders bool `json:"delete_empty_folders"`
44 | DeleteOldUpdateFiles bool `json:"delete_old_update_files"`
45 | FolderNameTemplate string `json:"folder_name_template"`
46 | SwitchSafeFileNames bool `json:"switch_safe_file_names"`
47 | FileNameTemplate string `json:"file_name_template"`
48 | }
49 |
50 | type AppSettings struct {
51 | VersionsEtag string `json:"versions_etag"`
52 | TitlesEtag string `json:"titles_etag"`
53 | Prodkeys string `json:"prod_keys"`
54 | Folder string `json:"folder"`
55 | ScanFolders []string `json:"scan_folders"`
56 | GUI bool `json:"gui"`
57 | Debug bool `json:"debug"`
58 | CheckForMissingUpdates bool `json:"check_for_missing_updates"`
59 | CheckForMissingDLC bool `json:"check_for_missing_dlc"`
60 | OrganizeOptions OrganizeOptions `json:"organize_options"`
61 | ScanRecursively bool `json:"scan_recursively"`
62 | GuiPagingSize int `json:"gui_page_size"`
63 | IgnoreDLCTitleIds []string `json:"ignore_dlc_title_ids"`
64 | }
65 |
66 | func ReadSettingsAsJSON(baseFolder string) string {
67 | if _, err := os.Stat(filepath.Join(baseFolder, SETTINGS_FILENAME)); err != nil {
68 | saveDefaultSettings(baseFolder)
69 | }
70 | file, _ := os.Open(filepath.Join(baseFolder, SETTINGS_FILENAME))
71 | bytes, _ := ioutil.ReadAll(file)
72 | return string(bytes)
73 | }
74 |
75 | func ReadSettings(baseFolder string) *AppSettings {
76 | if settingsInstance != nil {
77 | return settingsInstance
78 | }
79 | settingsInstance = &AppSettings{Debug: false, GuiPagingSize: 100, ScanFolders: []string{},
80 | OrganizeOptions: OrganizeOptions{SwitchSafeFileNames: true}, Prodkeys: "", IgnoreDLCTitleIds: []string{"01007F600B135007"}}
81 | if _, err := os.Stat(filepath.Join(baseFolder, SETTINGS_FILENAME)); err == nil {
82 | file, err := os.Open(filepath.Join(baseFolder, SETTINGS_FILENAME))
83 | if err != nil {
84 | zap.S().Warnf("Missing or corrupted config file, creating a new one")
85 | return saveDefaultSettings(baseFolder)
86 | } else {
87 | _ = json.NewDecoder(file).Decode(&settingsInstance)
88 | return settingsInstance
89 | }
90 | } else {
91 | return saveDefaultSettings(baseFolder)
92 | }
93 | }
94 |
95 | func saveDefaultSettings(baseFolder string) *AppSettings {
96 | settingsInstance = &AppSettings{
97 | TitlesEtag: "W/\"a5b02845cf6bd61:0\"",
98 | VersionsEtag: "W/\"2ef50d1cb6bd61:0\"",
99 | Folder: "",
100 | ScanFolders: []string{},
101 | IgnoreDLCTitleIds: []string{},
102 | GUI: true,
103 | GuiPagingSize: 100,
104 | CheckForMissingUpdates: true,
105 | CheckForMissingDLC: true,
106 | ScanRecursively: true,
107 | Debug: false,
108 | OrganizeOptions: OrganizeOptions{
109 | RenameFiles: false,
110 | CreateFolderPerGame: false,
111 | FolderNameTemplate: fmt.Sprintf("{%v}", TEMPLATE_TITLE_NAME),
112 | FileNameTemplate: fmt.Sprintf("{%v} ({%v})[{%v}][v{%v}]", TEMPLATE_TITLE_NAME, TEMPLATE_DLC_NAME,
113 | TEMPLATE_TITLE_ID, TEMPLATE_VERSION),
114 | DeleteEmptyFolders: false,
115 | SwitchSafeFileNames: true,
116 | DeleteOldUpdateFiles: false,
117 | },
118 | }
119 | return SaveSettings(settingsInstance, baseFolder)
120 | }
121 |
122 | func SaveSettings(settings *AppSettings, baseFolder string) *AppSettings {
123 | file, _ := json.MarshalIndent(settings, "", " ")
124 | _ = ioutil.WriteFile(filepath.Join(baseFolder, SETTINGS_FILENAME), file, 0644)
125 | settingsInstance = settings
126 | return settings
127 | }
128 |
129 | func CheckForUpdates() (bool, error) {
130 |
131 | localVer := SLM_VERSION
132 |
133 | res, err := http.Get(SLM_VERSION_URL)
134 | if err != nil {
135 | return false, err
136 | }
137 | defer res.Body.Close()
138 |
139 | body, err := ioutil.ReadAll(res.Body)
140 | if err != nil {
141 | return false, err
142 | }
143 |
144 | remoteValues := map[string]string{}
145 | err = json.Unmarshal(body, &remoteValues)
146 | if err != nil {
147 | return false, err
148 | }
149 |
150 | remoteVer := remoteValues["version"]
151 |
152 | if version.CompareSimple(remoteVer, localVer) > 0 {
153 | return true, nil
154 | }
155 |
156 | return false, nil
157 | }
158 |
--------------------------------------------------------------------------------
/slm.json:
--------------------------------------------------------------------------------
1 | {
2 | "version":"1.4.0"
3 | }
--------------------------------------------------------------------------------
/switchfs/_crypto/ecb.go:
--------------------------------------------------------------------------------
1 | package _crypto
2 |
3 | import "crypto/aes"
4 |
5 | func DecryptAes128Ecb(data, key []byte) []byte {
6 |
7 | cipher, _ := aes.NewCipher([]byte(key))
8 | decrypted := make([]byte, len(data))
9 | size := 16
10 |
11 | for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
12 | cipher.Decrypt(decrypted[bs:be], data[bs:be])
13 | }
14 |
15 | return decrypted
16 | }
17 |
--------------------------------------------------------------------------------
/switchfs/_crypto/xts.go:
--------------------------------------------------------------------------------
1 | /// Modified xts to support custom tweak
2 |
3 | // Copyright 2012 The Go Authors. All rights reserved.
4 | // Use of this source code is governed by a BSD-style
5 | // license that can be found in the LICENSE file.
6 |
7 | // Package xts implements the XTS cipher mode as specified in IEEE P1619/D16.
8 | //
9 | // XTS mode is typically used for disk encryption, which presents a number of
10 | // novel problems that make more common modes inapplicable. The disk is
11 | // conceptually an array of sectors and we must be able to encrypt and decrypt
12 | // a sector in isolation. However, an attacker must not be able to transpose
13 | // two sectors of plaintext by transposing their ciphertext.
14 | //
15 | // XTS wraps a block cipher with Rogaway's XEX mode in order to build a
16 | // tweakable block cipher. This allows each sector to have a unique tweak and
17 | // effectively create a unique key for each sector.
18 | //
19 | // XTS does not provide any authentication. An attacker can manipulate the
20 | // ciphertext and randomise a block (16 bytes) of the plaintext. This package
21 | // does not implement ciphertext-stealing so sectors must be a multiple of 16
22 | // bytes.
23 | //
24 | // Note that XTS is usually not appropriate for any use besides disk encryption.
25 | // Most users should use an AEAD mode like GCM (from crypto/cipher.NewGCM) instead.
26 | package _crypto
27 |
28 | import (
29 | "crypto/cipher"
30 | "encoding/binary"
31 | "errors"
32 | "sync"
33 | "unsafe"
34 | )
35 |
36 | // Cipher contains an expanded key structure. It is safe for concurrent use if
37 | // the underlying block cipher is safe for concurrent use.
38 | type Cipher struct {
39 | k1, k2 cipher.Block
40 | }
41 |
42 | // blockSize is the block size that the underlying cipher must have. XTS is
43 | // only defined for 16-byte ciphers.
44 | const blockSize = 16
45 |
46 | var tweakPool = sync.Pool{
47 | New: func() interface{} {
48 | return new([blockSize]byte)
49 | },
50 | }
51 |
52 | // NewCipher creates a Cipher given a function for creating the underlying
53 | // block cipher (which must have a block size of 16 bytes). The key must be
54 | // twice the length of the underlying cipher's key.
55 | func NewCipher(cipherFunc func([]byte) (cipher.Block, error), key []byte) (c *Cipher, err error) {
56 | c = new(Cipher)
57 | if c.k1, err = cipherFunc(key[:len(key)/2]); err != nil {
58 | return
59 | }
60 | c.k2, err = cipherFunc(key[len(key)/2:])
61 |
62 | if c.k1.BlockSize() != blockSize {
63 | err = errors.New("xts: cipher does not have a block size of 16")
64 | }
65 |
66 | return
67 | }
68 |
69 | // Encrypt encrypts a sector of plaintext and puts the result into ciphertext.
70 | // Plaintext and ciphertext must overlap entirely or not at all.
71 | // Sectors must be a multiple of 16 bytes and less than 2²⁴ bytes.
72 | func (c *Cipher) Encrypt(ciphertext, plaintext []byte, sectorNum uint64) {
73 | if len(ciphertext) < len(plaintext) {
74 | panic("xts: ciphertext is smaller than plaintext")
75 | }
76 | if len(plaintext)%blockSize != 0 {
77 | panic("xts: plaintext is not a multiple of the block size")
78 | }
79 | if InexactOverlap(ciphertext[:len(plaintext)], plaintext) {
80 | panic("xts: invalid buffer overlap")
81 | }
82 |
83 | tweak := tweakPool.Get().(*[blockSize]byte)
84 | for i := range tweak {
85 | tweak[i] = 0
86 | }
87 | binary.LittleEndian.PutUint64(tweak[:8], sectorNum)
88 |
89 | c.k2.Encrypt(tweak[:], tweak[:])
90 |
91 | for len(plaintext) > 0 {
92 | for j := range tweak {
93 | ciphertext[j] = plaintext[j] ^ tweak[j]
94 | }
95 | c.k1.Encrypt(ciphertext, ciphertext)
96 | for j := range tweak {
97 | ciphertext[j] ^= tweak[j]
98 | }
99 | plaintext = plaintext[blockSize:]
100 | ciphertext = ciphertext[blockSize:]
101 |
102 | mul2(tweak)
103 | }
104 |
105 | tweakPool.Put(tweak)
106 | }
107 |
108 | // Decrypt decrypts a sector of ciphertext and puts the result into plaintext.
109 | // Plaintext and ciphertext must overlap entirely or not at all.
110 | // Sectors must be a multiple of 16 bytes and less than 2²⁴ bytes.
111 | func (c *Cipher) Decrypt(plaintext, ciphertext []byte, tweak *[16]byte) {
112 | if len(plaintext) < len(ciphertext) {
113 | panic("xts: plaintext is smaller than ciphertext")
114 | }
115 | if len(ciphertext)%blockSize != 0 {
116 | panic("xts: ciphertext is not a multiple of the block size")
117 | }
118 | if InexactOverlap(plaintext[:len(ciphertext)], ciphertext) {
119 | panic("xts: invalid buffer overlap")
120 | }
121 |
122 | /*
123 | tweak := tweakPool.Get().(*[blockSize]byte)
124 | for i := range tweak {
125 | tweak[i] = 0
126 | }
127 | binary.LittleEndian.PutUint64(tweak[:8], sectorNum)
128 | */
129 | c.k2.Encrypt(tweak[:], tweak[:])
130 | for len(ciphertext) > 0 {
131 | for j := range tweak {
132 | plaintext[j] = ciphertext[j] ^ tweak[j]
133 | }
134 | c.k1.Decrypt(plaintext, plaintext)
135 | for j := range tweak {
136 | plaintext[j] ^= tweak[j]
137 | }
138 | plaintext = plaintext[blockSize:]
139 | ciphertext = ciphertext[blockSize:]
140 |
141 | mul2(tweak)
142 | }
143 |
144 | //tweakPool.Put(tweak)
145 | }
146 |
147 | // mul2 multiplies tweak by 2 in GF(2¹²⁸) with an irreducible polynomial of
148 | // x¹²⁸ + x⁷ + x² + x + 1.
149 | func mul2(tweak *[blockSize]byte) {
150 | var carryIn byte
151 | for j := range tweak {
152 | carryOut := tweak[j] >> 7
153 | tweak[j] = (tweak[j] << 1) + carryIn
154 | carryIn = carryOut
155 | }
156 | if carryIn != 0 {
157 | // If we have a carry bit then we need to subtract a multiple
158 | // of the irreducible polynomial (x¹²⁸ + x⁷ + x² + x + 1).
159 | // By dropping the carry bit, we're subtracting the x^128 term
160 | // so all that remains is to subtract x⁷ + x² + x + 1.
161 | // Subtraction (and addition) in this representation is just
162 | // XOR.
163 | tweak[0] ^= 1<<7 | 1<<2 | 1<<1 | 1
164 | }
165 | }
166 |
167 | func AnyOverlap(x, y []byte) bool {
168 | return len(x) > 0 && len(y) > 0 &&
169 | uintptr(unsafe.Pointer(&x[0])) <= uintptr(unsafe.Pointer(&y[len(y)-1])) &&
170 | uintptr(unsafe.Pointer(&y[0])) <= uintptr(unsafe.Pointer(&x[len(x)-1]))
171 | }
172 |
173 | // InexactOverlap reports whether x and y share memory at any non-corresponding
174 | // index. The memory beyond the slice length is ignored. Note that x and y can
175 | // have different lengths and still not have any inexact overlap.
176 | //
177 | // InexactOverlap can be used to implement the requirements of the crypto/cipher
178 | // AEAD, Block, BlockMode and Stream interfaces.
179 | func InexactOverlap(x, y []byte) bool {
180 | if len(x) == 0 || len(y) == 0 || &x[0] == &y[0] {
181 | return false
182 | }
183 | return AnyOverlap(x, y)
184 | }
185 |
--------------------------------------------------------------------------------
/switchfs/cnmt.go:
--------------------------------------------------------------------------------
1 | package switchfs
2 |
3 | import (
4 | "encoding/binary"
5 | "encoding/xml"
6 | "errors"
7 | "fmt"
8 | "strings"
9 | )
10 |
11 | const (
12 | ContentMetaType_SystemProgram = 1
13 | ContentMetaType_SystemData = 2
14 | ContentMetaType_SystemUpdate = 3
15 | ContentMetaType_BootImagePackage = 4
16 | ContentMetaType_BootImagePackageSafe = 5
17 | ContentMetaType_Application = 0x80
18 | ContentMetaType_Patch = 0x81
19 | ContentMetaType_AddOnContent = 0x82
20 | ContentMetaType_Delta = 0x83
21 | )
22 |
23 | type Content struct {
24 | Text string `xml:",chardata"`
25 | Type string `xml:"Type"`
26 | ID string `xml:"Id"`
27 | Size string `xml:"Size"`
28 | Hash string `xml:"Hash"`
29 | KeyGeneration string `xml:"KeyGeneration"`
30 | }
31 |
32 | type ContentMetaAttributes struct {
33 | TitleId string `json:"title_id"`
34 | Version int `json:"version"`
35 | Type string `json:"type"`
36 | Contents map[string]Content
37 | Ncap *Nacp
38 | }
39 |
40 | type ContentMeta struct {
41 | XMLName xml.Name `xml:"ContentMeta"`
42 | Text string `xml:",chardata"`
43 | Type string `xml:"Type"`
44 | ID string `xml:"Id"`
45 | Version int `xml:"Version"`
46 | RequiredDownloadSystemVersion string `xml:"RequiredDownloadSystemVersion"`
47 | Content []struct {
48 | Text string `xml:",chardata"`
49 | Type string `xml:"Type"`
50 | ID string `xml:"Id"`
51 | Size string `xml:"Size"`
52 | Hash string `xml:"Hash"`
53 | KeyGeneration string `xml:"KeyGeneration"`
54 | } `xml:"Content"`
55 | Digest string `xml:"Digest"`
56 | KeyGenerationMin string `xml:"KeyGenerationMin"`
57 | RequiredSystemVersion string `xml:"RequiredSystemVersion"`
58 | OriginalId string `xml:"OriginalId"`
59 | }
60 |
61 | func readBinaryCnmt(pfs0 *PFS0, data []byte) (*ContentMetaAttributes, error) {
62 | if pfs0 == nil || len(pfs0.Files) != 1 {
63 | return nil, errors.New("unexpected pfs0")
64 | }
65 | cnmtFile := pfs0.Files[0]
66 | cnmt := data[int64(cnmtFile.StartOffset):]
67 | titleId := binary.LittleEndian.Uint64(cnmt[0:0x8])
68 | version := binary.LittleEndian.Uint32(cnmt[0x8:0xC])
69 | tableOffset := binary.LittleEndian.Uint16(cnmt[0xE:0x10])
70 | contentEntryCount := binary.LittleEndian.Uint16(cnmt[0x10:0x12])
71 | //metaEntryCount := binary.LittleEndian.Uint16(cnmt[0x12:0x14])
72 | contents := map[string]Content{}
73 | for i := uint16(0); i < contentEntryCount; i++ {
74 | position := 0x20 /*size of cnmt header*/ + tableOffset + (i * uint16(0x38))
75 | ncaId := cnmt[position+0x20 : position+0x20+0x10]
76 | //fmt.Println(fmt.Sprintf("0%x", ncaId))
77 | contentType := ""
78 | switch cnmt[position+0x36 : position+0x36+1][0] {
79 | case 0:
80 | contentType = "Meta"
81 | case 1:
82 | contentType = "Program"
83 | case 2:
84 | contentType = "Data"
85 | case 3:
86 | contentType = "Control"
87 | case 4:
88 | contentType = "HtmlDocument"
89 | case 5:
90 | contentType = "LegalInformation"
91 | case 6:
92 | contentType = "DeltaFragment"
93 | }
94 | contents[contentType] = Content{ID: fmt.Sprintf("%x", ncaId)}
95 | }
96 | metaType := ""
97 | switch cnmt[0xC:0xD][0] {
98 | case ContentMetaType_Application:
99 | metaType = "BASE"
100 | case ContentMetaType_AddOnContent:
101 | metaType = "DLC"
102 | case ContentMetaType_Patch:
103 | metaType = "UPD"
104 | }
105 |
106 | return &ContentMetaAttributes{Contents: contents, Version: int(version), TitleId: fmt.Sprintf("0%x", titleId), Type: metaType}, nil
107 | }
108 |
109 | func readXmlCnmt(xmlBytes []byte) (*ContentMetaAttributes, error) {
110 | cmt := &ContentMeta{}
111 | err := xml.Unmarshal(xmlBytes, &cmt)
112 | if err != nil {
113 | return nil, err
114 | }
115 | titleId := strings.Replace(cmt.ID, "0x", "", 1)
116 | return &ContentMetaAttributes{Version: cmt.Version, TitleId: titleId, Type: cmt.Type}, nil
117 | }
118 |
--------------------------------------------------------------------------------
/switchfs/fs.go:
--------------------------------------------------------------------------------
1 | package switchfs
2 |
3 | import (
4 | "bytes"
5 | "crypto/sha256"
6 | "encoding/binary"
7 | "errors"
8 | )
9 |
10 | type fsHeader struct {
11 | encType byte //(0 = Auto, 1 = None, 2 = AesCtrOld, 3 = AesCtr, 4 = AesCtrEx)
12 | fsType byte //(0 = RomFs, 1 = PartitionFs)
13 | hashType byte // (0 = Auto, 2 = HierarchicalSha256, 3 = HierarchicalIntegrity (Ivfc))
14 | fsHeaderBytes []byte
15 | generation uint32
16 | }
17 |
18 | type fsEntry struct {
19 | StartOffset uint32
20 | EndOffset uint32
21 | Size uint32
22 | }
23 |
24 | type hashInfo struct {
25 | pfs0HeaderOffset uint64
26 | pfs0size uint64
27 | }
28 |
29 | func getFsEntry(ncaHeader *ncaHeader, index int) fsEntry {
30 | fsEntryOffset := 0x240 + 0x10*index
31 | fsEntryBytes := ncaHeader.headerBytes[fsEntryOffset : fsEntryOffset+0x10]
32 |
33 | entryStartOffset := binary.LittleEndian.Uint32(fsEntryBytes[0x0:0x4]) * 0x200
34 | entryEndOffset := binary.LittleEndian.Uint32(fsEntryBytes[0x4:0x8]) * 0x200
35 |
36 | return fsEntry{StartOffset: entryStartOffset, EndOffset: entryEndOffset, Size: entryEndOffset - entryStartOffset}
37 | }
38 |
39 | func getFsHeader(ncaHeader *ncaHeader, index int) (*fsHeader, error) {
40 | fsHeaderHashOffset := /*hash pfs0HeaderOffset*/ 0x280 + /*hash pfs0size*/ 0x20*index
41 | fsHeaderHash := ncaHeader.headerBytes[fsHeaderHashOffset : fsHeaderHashOffset+0x20]
42 |
43 | fsHeaderOffset := 0x400 + 0x200*index
44 | fsHeaderBytes := ncaHeader.headerBytes[fsHeaderOffset : fsHeaderOffset+0x200]
45 |
46 | actualHash := sha256.Sum256(fsHeaderBytes)
47 |
48 | if bytes.Compare(actualHash[:], fsHeaderHash) != 0 {
49 | return nil, errors.New("fs headerBytes hash mismatch")
50 | }
51 |
52 | result := fsHeader{fsHeaderBytes: fsHeaderBytes}
53 |
54 | result.fsType = fsHeaderBytes[0x2:0x3][0]
55 | result.hashType = fsHeaderBytes[0x3:0x4][0]
56 | result.encType = fsHeaderBytes[0x4:0x5][0]
57 |
58 | generationBytes := fsHeaderBytes[0x140 : 0x140+0x4] //generation
59 | result.generation = binary.LittleEndian.Uint32(generationBytes)
60 |
61 | return &result, nil
62 | }
63 |
64 | func (fh *fsHeader) getHashInfo() (*hashInfo, error) {
65 | hashInfoBytes := fh.fsHeaderBytes[0x8:0x100]
66 | result := hashInfo{}
67 | if fh.hashType == 2 {
68 |
69 | result.pfs0HeaderOffset = binary.LittleEndian.Uint64(hashInfoBytes[0x38 : 0x38+0x8])
70 | result.pfs0size = binary.LittleEndian.Uint64(hashInfoBytes[0x40 : 0x40+0x8])
71 | return &result, nil
72 | } else if fh.hashType == 3 {
73 | result.pfs0HeaderOffset = binary.LittleEndian.Uint64(hashInfoBytes[0x88 : 0x88+0x8])
74 | result.pfs0size = binary.LittleEndian.Uint64(hashInfoBytes[0x90 : 0x90+0x8])
75 | return &result, nil
76 | }
77 | return nil, errors.New("non supported hash type")
78 | }
79 |
--------------------------------------------------------------------------------
/switchfs/nacp.go:
--------------------------------------------------------------------------------
1 | package switchfs
2 |
3 | import (
4 | "encoding/binary"
5 | "errors"
6 | "io"
7 | )
8 |
9 | type Language int
10 |
11 | const (
12 | AmericanEnglish = iota
13 | BritishEnglish
14 | Japanese
15 | French
16 | German
17 | LatinAmericanSpanish
18 | Spanish
19 | Italian
20 | Dutch
21 | CanadianFrench
22 | Portuguese
23 | Russian
24 | Korean
25 | Taiwanese
26 | Chinese
27 | )
28 |
29 | type NacpTitle struct {
30 | Language Language
31 | Title string
32 | }
33 |
34 | type Nacp struct {
35 | TitleName map[string]NacpTitle
36 | Isbn string
37 | DisplayVersion string
38 | SupportedLanguageFlag uint32
39 | }
40 |
41 | func (l Language) String() string {
42 | return [...]string{
43 | "AmericanEnglish",
44 | "BritishEnglish",
45 | "Japanese",
46 | "French",
47 | "German",
48 | "LatinAmericanSpanish",
49 | "Spanish",
50 | "Italian",
51 | "Dutch",
52 | "CanadianFrench",
53 | "Portuguese",
54 | "Russian",
55 | "Korean",
56 | "Taiwanese",
57 | "Chinese",
58 | "Chinese"}[l]
59 | }
60 |
61 | func ExtractNacp(cnmt *ContentMetaAttributes, file io.ReaderAt, securePartition *PFS0, securePartitionOffset int64) (*Nacp, error) {
62 | if control, ok := cnmt.Contents["Control"]; ok {
63 | controlNca := getNcaById(securePartition, control.ID)
64 | if controlNca != nil {
65 | fsHeader, section, err := openMetaNcaDataSection(file, securePartitionOffset+int64(controlNca.StartOffset))
66 | if err != nil {
67 | return nil, err
68 | }
69 | if fsHeader.fsType == 0 {
70 | romFsHeader, err := readRomfsHeader(section)
71 | if err != nil {
72 | return nil, err
73 | }
74 | fEntries, err := readRomfsFileEntry(section, romFsHeader)
75 | if err != nil {
76 | return nil, err
77 | }
78 |
79 | if entry, ok := fEntries["control.nacp"]; ok {
80 | nacp, err := readNacp(section, romFsHeader, entry)
81 | if err != nil {
82 | return nil, err
83 | }
84 | return &nacp, nil
85 | }
86 | } else {
87 | return nil, errors.New("unsupported type " + control.ID)
88 | }
89 | } else {
90 | return nil, errors.New("unable to find control.nacp by id " + control.ID)
91 | }
92 |
93 | }
94 | return nil, errors.New("no control.nacp found")
95 | }
96 |
97 | /*https://switchbrew.org/wiki/NACP_Format*/
98 | func readNacp(data []byte, romFsHeader RomfsHeader, fileEntry RomfsFileEntry) (Nacp, error) {
99 | offset := romFsHeader.DataOffset + fileEntry.offset
100 | titles := map[string]NacpTitle{}
101 | for i := 0; i < 16; i++ {
102 | //lang := i
103 | appTitleBytes := data[offset+(uint64(i)*0x300) : offset+(uint64(i)*0x300)+0x200]
104 | nameBytes := readBytesUntilZero(appTitleBytes)
105 | titles[Language(i).String()] = NacpTitle{Language: Language(i), Title: string(nameBytes)}
106 | }
107 |
108 | isbn := readBytesUntilZero(data[offset+0x3000 : offset+0x3000+0x25])
109 | displayVersion := readBytesUntilZero(data[offset+0x3060 : offset+0x3060+0x10])
110 | supportedLanguageFlag := binary.BigEndian.Uint32(data[offset+0x302C : offset+0x302C+0x4])
111 |
112 | return Nacp{TitleName: titles, Isbn: string(isbn), DisplayVersion: string(displayVersion), SupportedLanguageFlag: supportedLanguageFlag}, nil
113 | /*
114 |
115 |
116 | Isbn = reader.ReadUtf8Z(37);
117 | reader.BaseStream.Position = start + 0x3025;
118 | StartupUserAccount = reader.ReadByte();
119 | UserAccountSwitchLock = reader.ReadByte();
120 | AocRegistrationType = reader.ReadByte();
121 | AttributeFlag = reader.ReadInt32();
122 | supportedLanguageFlag = reader.ReadUInt32();
123 | ParentalControlFlag = reader.ReadUInt32();
124 | Screenshot = reader.ReadByte();
125 | VideoCapture = reader.ReadByte();
126 | DataLossConfirmation = reader.ReadByte();
127 | PlayLogPolicy = reader.ReadByte();
128 | PresenceGroupId = reader.ReadUInt64();
129 |
130 | for (int i = 0; i < RatingAge.Length; i++)
131 | {
132 | RatingAge[i] = reader.ReadSByte();
133 | }
134 |
135 | DisplayVersion = reader.ReadUtf8Z(16);
136 | reader.BaseStream.Position = start + 0x3070;
137 | AddOnContentBaseId = reader.ReadUInt64();
138 | SaveDataOwnerId = reader.ReadUInt64();
139 | UserAccountSaveDataSize = reader.ReadInt64();
140 | UserAccountSaveDataJournalSize = reader.ReadInt64();
141 | DeviceSaveDataSize = reader.ReadInt64();
142 | DeviceSaveDataJournalSize = reader.ReadInt64();
143 | BcatDeliveryCacheStorageSize = reader.ReadInt64();
144 | ApplicationErrorCodeCategory = reader.ReadUtf8Z(8);
145 | reader.BaseStream.Position = start + 0x30B0;
146 |
147 | for (int i = 0; i < LocalCommunicationId.Length; i++)
148 | {
149 | LocalCommunicationId[i] = reader.ReadUInt64();
150 | }
151 |
152 | LogoType = reader.ReadByte();
153 | LogoHandling = reader.ReadByte();
154 | RuntimeAddOnContentInstall = reader.ReadByte();
155 | Reserved00 = reader.ReadBytes(3);
156 | CrashReport = reader.ReadByte();
157 | Hdcp = reader.ReadByte();
158 | SeedForPseudoDeviceId = reader.ReadUInt64();
159 | BcatPassphrase = reader.ReadUtf8Z(65);
160 |
161 | reader.BaseStream.Position = start + 0x3141;
162 | Reserved01 = reader.ReadByte();
163 | Reserved02 = reader.ReadBytes(6);
164 |
165 | UserAccountSaveDataSizeMax = reader.ReadInt64();
166 | UserAccountSaveDataJournalSizeMax = reader.ReadInt64();
167 | DeviceSaveDataSizeMax = reader.ReadInt64();
168 | DeviceSaveDataJournalSizeMax = reader.ReadInt64();
169 | TemporaryStorageSize = reader.ReadInt64();
170 | CacheStorageSize = reader.ReadInt64();
171 | CacheStorageJournalSize = reader.ReadInt64();
172 | CacheStorageDataAndJournalSizeMax = reader.ReadInt64();
173 | CacheStorageIndex = reader.ReadInt16();
174 | Reserved03 = reader.ReadBytes(6);
175 |
176 | for (int i = 0; i < 16; i++)
177 | {
178 | ulong value = reader.ReadUInt64();
179 | if (value != 0) PlayLogQueryableApplicationId.Add(value);
180 | }
181 |
182 | PlayLogQueryCapability = reader.ReadByte();
183 | RepairFlag = reader.ReadByte();
184 | ProgramIndex = reader.ReadByte();
185 |
186 | UserTotalSaveDataSize = UserAccountSaveDataSize + UserAccountSaveDataJournalSize;
187 | DeviceTotalSaveDataSize = DeviceSaveDataSize + DeviceSaveDataJournalSize;
188 | TotalSaveDataSize = UserTotalSaveDataSize + DeviceTotalSaveDataSize;
189 | */
190 | }
191 |
192 | func readBytesUntilZero(appTitleBytes []byte) []byte {
193 | var nameBytes []byte
194 | for _, b := range appTitleBytes {
195 | if b == 0x0 {
196 | break
197 | } else {
198 | nameBytes = append(nameBytes, b)
199 | }
200 | }
201 | return nameBytes
202 | }
203 |
--------------------------------------------------------------------------------
/switchfs/nca.go:
--------------------------------------------------------------------------------
1 | package switchfs
2 |
3 | import (
4 | "crypto/aes"
5 | "crypto/cipher"
6 | "encoding/binary"
7 | "encoding/hex"
8 | "errors"
9 | "fmt"
10 | "github.com/giwty/switch-library-manager/settings"
11 | "github.com/giwty/switch-library-manager/switchfs/_crypto"
12 | "io"
13 | )
14 |
15 | const (
16 | NcaSectionType_Code = iota
17 | NcaSectionType_Data
18 | NcaSectionType_Logo
19 | )
20 | const (
21 | NcaContentType_Program = iota
22 | NcaContentType_Meta
23 | NcaContentType_Control
24 | NcaContentType_Manual
25 | NcaContentType_Data
26 | NcaContentType_PublicData
27 | )
28 |
29 | func openMetaNcaDataSection(reader io.ReaderAt, ncaOffset int64) (*fsHeader, []byte, error) {
30 | //read the NCA headerBytes
31 | encNcaHeader := make([]byte, 0xC00)
32 | n, err := reader.ReadAt(encNcaHeader, ncaOffset)
33 |
34 | if err != nil {
35 | return nil, nil, errors.New("failed to read NCA header " + err.Error())
36 | }
37 | if n != 0xC00 {
38 | return nil, nil, errors.New("failed to read NCA header")
39 | }
40 |
41 | keys, err := settings.SwitchKeys()
42 | if err != nil {
43 | return nil, nil, err
44 | }
45 | headerKey := keys.GetKey("header_key")
46 | if headerKey == "" {
47 | return nil, nil, errors.New("missing key - header_key")
48 | }
49 | ncaHeader, err := DecryptNcaHeader(headerKey, encNcaHeader)
50 | if err != nil {
51 | return nil, nil, err
52 | }
53 |
54 | if ncaHeader.HasRightsId() {
55 | //fail - need title keys
56 | return nil, nil, errors.New("non standard encryption is not supported")
57 | }
58 |
59 | /*if ncaHeader.contentType != NcaContentType_Meta {
60 | return nil, errors.New("not a meta NCA")
61 | }*/
62 |
63 | dataSectionIndex := 0
64 |
65 | fsHeader, err := getFsHeader(ncaHeader, dataSectionIndex)
66 | if err != nil {
67 | return nil, nil, err
68 | }
69 |
70 | entry := getFsEntry(ncaHeader, dataSectionIndex)
71 |
72 | if entry.Size == 0 {
73 | return nil, nil, errors.New("empty section")
74 | }
75 |
76 | encodedEntryContent := make([]byte, entry.Size)
77 | entryOffset := ncaOffset + int64(entry.StartOffset)
78 | _, err = reader.ReadAt(encodedEntryContent, entryOffset)
79 | if err != nil {
80 | return nil, nil, err
81 | }
82 | if fsHeader.encType != 3 {
83 | return nil, nil, errors.New("non supported encryption type [encryption type:" + string(fsHeader.encType))
84 | }
85 |
86 | /*if fsHeader.hashType != 2 { //Sha256 (FS_TYPE_PFS0)
87 | return nil, errors.New("non FS_TYPE_PFS0")
88 | }*/
89 | decoded, err := decryptAesCtr(ncaHeader, fsHeader, entry.StartOffset, entry.Size, encodedEntryContent)
90 | if err != nil {
91 | return nil, nil, err
92 | }
93 | hashInfo, err := fsHeader.getHashInfo()
94 | if err != nil {
95 | return nil, nil, err
96 | }
97 |
98 | return fsHeader, decoded[hashInfo.pfs0HeaderOffset:], nil
99 | }
100 |
101 | func decryptAesCtr(ncaHeader *ncaHeader, fsHeader *fsHeader, offset uint32, size uint32, encoded []byte) ([]byte, error) {
102 | keyRevision := ncaHeader.getKeyRevision()
103 | cryptoType := ncaHeader.cryptoType
104 |
105 | if cryptoType != 0 {
106 | return []byte{}, errors.New("unsupported crypto type")
107 | }
108 |
109 | keys, _ := settings.SwitchKeys()
110 |
111 | keyName := fmt.Sprintf("key_area_key_application_0%x", keyRevision)
112 | KeyString := keys.GetKey(keyName)
113 | if KeyString == "" {
114 | return nil, errors.New(fmt.Sprintf("missing Key_area_key[%v]", keyName))
115 | }
116 | key, _ := hex.DecodeString(KeyString)
117 |
118 | decKey := _crypto.DecryptAes128Ecb(ncaHeader.encryptedKeys[0x20:0x30], key)
119 |
120 | counter := make([]byte, 0x10)
121 | binary.BigEndian.PutUint64(counter, uint64(fsHeader.generation))
122 | binary.BigEndian.PutUint64(counter[8:], uint64(offset/0x10))
123 |
124 | c, _ := aes.NewCipher(decKey)
125 |
126 | decContent := make([]byte, size)
127 |
128 | s := cipher.NewCTR(c, counter)
129 | s.XORKeyStream(decContent, encoded[0:size])
130 |
131 | return decContent, nil
132 | }
133 |
--------------------------------------------------------------------------------
/switchfs/ncaHeader.go:
--------------------------------------------------------------------------------
1 | package switchfs
2 |
3 | import (
4 | "crypto/aes"
5 | "encoding/binary"
6 | "encoding/hex"
7 | "github.com/giwty/switch-library-manager/switchfs/_crypto"
8 | "strconv"
9 | )
10 |
11 | //https://switchbrew.org/wiki/NCA_Format
12 |
13 | type ncaHeader struct {
14 | headerBytes []byte
15 | rightsId []byte
16 | titleId []byte
17 | distribution byte
18 | contentType byte // (0x00 = Program, 0x01 = Meta, 0x02 = Control, 0x03 = Manual, 0x04 = Data, 0x05 = PublicData)
19 | keyGeneration2 byte
20 | keyGeneration1 byte
21 | encryptedKeys []byte // 4 * 0x10
22 | cryptoType byte //(0x00 = Application, 0x01 = Ocean, 0x02 = System)
23 |
24 | }
25 |
26 | func (n *ncaHeader) HasRightsId() bool {
27 | for i := 0; i < 0x10; i++ {
28 | if n.rightsId[i] != 0 {
29 | return true
30 | }
31 | }
32 | return false
33 | }
34 |
35 | func (n *ncaHeader) getKeyRevision() int {
36 | keyGeneration := max(n.keyGeneration1, n.keyGeneration2)
37 | keyRevision := keyGeneration - 1
38 | if keyGeneration == 0 {
39 | keyRevision = 0
40 | }
41 | return int(keyRevision)
42 | }
43 |
44 | func max(a byte, b byte) byte {
45 | if a > b {
46 | return a
47 | }
48 | return b
49 | }
50 |
51 | func DecryptNcaHeader(key string, encHeader []byte) (*ncaHeader, error) {
52 | headerKey, _ := hex.DecodeString(key)
53 | c, err := _crypto.NewCipher(aes.NewCipher, headerKey)
54 | if err != nil {
55 | return nil, err
56 | }
57 | sector := 0
58 | sectorSize := 0x200
59 | endOffset := 0x400
60 | decryptNcaHeader, err := _decryptNcaHeader(c, encHeader, endOffset, sectorSize, sector)
61 | if err != nil {
62 | return nil, err
63 | }
64 |
65 | magic := string(decryptNcaHeader[0x200:0x204])
66 |
67 | if magic == "NCA3" {
68 | endOffset = 0xC00
69 | decryptNcaHeader, err = _decryptNcaHeader(c, encHeader, endOffset, sectorSize, sector)
70 | }
71 |
72 | result := ncaHeader{headerBytes: decryptNcaHeader}
73 |
74 | result.distribution = decryptNcaHeader[0x204:0x205][0]
75 | result.contentType = decryptNcaHeader[0x205:0x206][0]
76 | result.rightsId = decryptNcaHeader[0x230 : 0x230+0x10]
77 |
78 | title_id_dec := binary.LittleEndian.Uint64(decryptNcaHeader[0x210 : 0x210+0x8])
79 | result.titleId = []byte(strconv.FormatInt(int64(title_id_dec), 16))
80 | result.keyGeneration1 = decryptNcaHeader[0x206:0x207][0]
81 | result.keyGeneration2 = decryptNcaHeader[0x220:0x221][0]
82 |
83 | encryptedKeysAreaOffset := 0x300
84 | result.encryptedKeys = decryptNcaHeader[encryptedKeysAreaOffset : encryptedKeysAreaOffset+(0x10*4)]
85 |
86 | result.cryptoType = decryptNcaHeader[0x207:0x208][0]
87 |
88 | return &result, nil
89 | }
90 |
91 | func _decryptNcaHeader(c *_crypto.Cipher, header []byte, end int, sectorSize int, sectorNum int) ([]byte, error) {
92 | decrypted := make([]byte, len(header))
93 | for pos := 0; pos < end; pos += sectorSize {
94 | /* Workaround for Nintendo's custom sector...manually generate the tweak. */
95 | tweak := getNintendoTweak(sectorNum)
96 |
97 | pos := sectorSize * sectorNum
98 | c.Decrypt(decrypted[pos:pos+sectorSize], header[pos:pos+sectorSize], &tweak)
99 | sectorNum++
100 | }
101 | return decrypted, nil
102 | }
103 |
104 | func getNintendoTweak(sector int) [16]byte {
105 | tweak := [16]byte{}
106 | for i := 0xF; i >= 0; i-- { /* Nintendo LE custom tweak... */
107 | tweak[i] = uint8(sector & 0xFF)
108 | sector >>= 8
109 | }
110 | return tweak
111 | }
112 |
--------------------------------------------------------------------------------
/switchfs/nsp.go:
--------------------------------------------------------------------------------
1 | package switchfs
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "go.uber.org/zap"
7 | "strings"
8 | )
9 |
10 | func ReadNspMetadata(filePath string) (map[string]*ContentMetaAttributes, error) {
11 |
12 | pfs0, err := ReadPfs0File(filePath)
13 | if err != nil {
14 | return nil, errors.New("Invalid NSP file, reason - [" + err.Error() + "]")
15 | }
16 |
17 | file, err := OpenFile(filePath)
18 | if err != nil {
19 | return nil, err
20 | }
21 |
22 | defer file.Close()
23 |
24 | contentMap := map[string]*ContentMetaAttributes{}
25 |
26 | for _, pfs0File := range pfs0.Files {
27 |
28 | fileOffset := int64(pfs0File.StartOffset)
29 |
30 | if strings.Contains(pfs0File.Name, "cnmt.nca") {
31 | _, section, err := openMetaNcaDataSection(file, fileOffset)
32 | if err != nil {
33 | return nil, err
34 | }
35 | currpfs0, err := readPfs0(bytes.NewReader(section), 0x0)
36 | if err != nil {
37 | return nil, err
38 | }
39 | currCnmt, err := readBinaryCnmt(currpfs0, section)
40 | if err != nil {
41 | return nil, err
42 | }
43 | if currCnmt.Type != "DLC" {
44 | nacp, err := ExtractNacp(currCnmt, file, pfs0, 0)
45 | if err != nil {
46 | zap.S().Debug("Failed to extract nacp [%v]\n", err.Error())
47 | }
48 | currCnmt.Ncap = nacp
49 | }
50 |
51 | contentMap[currCnmt.TitleId] = currCnmt
52 |
53 | } /*else if strings.Contains(pfs0File.Name, ".cnmt.xml") {
54 | xmlBytes := make([]byte, pfs0File.Size)
55 | _, err = file.ReadAt(xmlBytes, fileOffset)
56 | if err != nil {
57 | return nil, err
58 | }
59 |
60 | currCnmt, err := readXmlCnmt(xmlBytes)
61 | if err != nil {
62 | return nil, err
63 | }
64 | contentMap[currCnmt.TitleId] = currCnmt
65 | }*/
66 | }
67 | return contentMap, nil
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/switchfs/pfs0.go:
--------------------------------------------------------------------------------
1 | package switchfs
2 |
3 | import (
4 | "encoding/binary"
5 | "errors"
6 | "io"
7 | )
8 |
9 | const (
10 | PfsfileEntryTableSize = 0x18
11 | HfsfileEntryTableSize = 0x40
12 | pfs0Magic = "PFS0"
13 | hfs0Magic = "HFS0"
14 | )
15 |
16 | type fileEntry struct {
17 | StartOffset uint64
18 | Size uint64
19 | Name string
20 | }
21 |
22 | // PFS0 struct to represent PFS0 filesystem of NSP
23 | type PFS0 struct {
24 | Filepath string
25 | Size uint64
26 | HeaderLen uint16
27 | Files []fileEntry
28 | }
29 |
30 | // https://wiki.oatmealdome.me/PFS0_(File_Format)
31 | func ReadPfs0File(filePath string) (*PFS0, error) {
32 |
33 | file, err := OpenFile(filePath)
34 | if err != nil {
35 | return nil, err
36 | }
37 |
38 | defer file.Close()
39 |
40 | p, err := readPfs0(file, 0x0)
41 | if err != nil {
42 | return nil, err
43 | }
44 | p.Filepath = filePath
45 | return p, nil
46 | }
47 |
48 | func readPfs0(reader io.ReaderAt, offset int64) (*PFS0, error) {
49 |
50 | header := make([]byte, 0xC)
51 | n, err := reader.ReadAt(header, offset)
52 | if err != nil {
53 | return nil, err
54 | }
55 | if n != 0xC {
56 | return nil, errors.New("failed to read file")
57 | }
58 | var fileEntryTableSize uint16
59 | if string(header[:0x4]) == pfs0Magic {
60 | fileEntryTableSize = PfsfileEntryTableSize
61 | } else if string(header[:0x4]) == hfs0Magic {
62 | fileEntryTableSize = HfsfileEntryTableSize
63 | } else {
64 | return nil, errors.New("Invalid NSP headerBytes. Expected 'PFS0'/'HFS0', got '" + string(header[:0x4]) + "'")
65 | }
66 | p := &PFS0{}
67 |
68 | fileCount := binary.LittleEndian.Uint16(header[0x4:0x8])
69 |
70 | fileEntryTableOffset := 0x10 + (fileEntryTableSize * fileCount)
71 |
72 | stringsLen := binary.LittleEndian.Uint16(header[0x8:0xC])
73 | p.HeaderLen = fileEntryTableOffset + stringsLen
74 | fileNamesBuffer := make([]byte, stringsLen)
75 | _, err = reader.ReadAt(fileNamesBuffer, offset+int64(fileEntryTableOffset))
76 | if err != nil {
77 | return nil, err
78 | }
79 |
80 | p.Files = make([]fileEntry, fileCount)
81 | // go over the fileEntries
82 | for i := uint16(0); i < fileCount; i++ {
83 | fileEntryTable := make([]byte, fileEntryTableSize)
84 | _, err = reader.ReadAt(fileEntryTable, offset+int64(0x10+(fileEntryTableSize*i)))
85 | if err != nil {
86 | return nil, err
87 | }
88 |
89 | fileOffset := binary.LittleEndian.Uint64(fileEntryTable[0:8])
90 | fileSize := binary.LittleEndian.Uint64(fileEntryTable[8:16])
91 | var nameBytes []byte
92 | for _, b := range fileNamesBuffer[binary.LittleEndian.Uint16(fileEntryTable[16:20]):] {
93 | if b == 0x0 {
94 | break
95 | } else {
96 | nameBytes = append(nameBytes, b)
97 | }
98 | }
99 |
100 | p.Files[i] = fileEntry{fileOffset + uint64(p.HeaderLen), fileSize, string(nameBytes)}
101 | }
102 | p.HeaderLen += stringsLen
103 |
104 | return p, nil
105 | }
106 |
--------------------------------------------------------------------------------
/switchfs/romfs.go:
--------------------------------------------------------------------------------
1 | package switchfs
2 |
3 | import (
4 | "encoding/binary"
5 | "errors"
6 | )
7 |
8 | type RomfsHeader struct {
9 | HeaderSize uint64
10 | DirHashTableOffset uint64
11 | DirHashTableSize uint64
12 | DirMetaTableOffset uint64
13 | DirMetaTableSize uint64
14 | FileHashTableOffset uint64
15 | FileHashTableSize uint64
16 | FileMetaTableOffset uint64
17 | FileMetaTableSize uint64
18 | DataOffset uint64
19 | }
20 |
21 | type RomfsFileEntry struct {
22 | parent uint32
23 | sibling uint32
24 | offset uint64
25 | size uint64
26 | hash uint32
27 | name_size uint32
28 | name string
29 | }
30 |
31 | func readRomfsHeader(data []byte) (RomfsHeader, error) {
32 | header := RomfsHeader{}
33 | header.HeaderSize = binary.LittleEndian.Uint64(data[0x0+(0x8*0) : 0x0+(0x8*1)])
34 | header.DirHashTableOffset = binary.LittleEndian.Uint64(data[0x0+(0x8*1) : 0x0+(0x8*2)])
35 | header.DirHashTableSize = binary.LittleEndian.Uint64(data[0x0+(0x8*2) : 0x0+(0x8*3)])
36 | header.DirMetaTableOffset = binary.LittleEndian.Uint64(data[0x0+(0x8*3) : 0x0+(0x8*4)])
37 | header.DirMetaTableSize = binary.LittleEndian.Uint64(data[0x0+(0x8*4) : 0x0+(0x8*5)])
38 | header.FileHashTableOffset = binary.LittleEndian.Uint64(data[0x0+(0x8*5) : 0x0+(0x8*6)])
39 | header.FileHashTableSize = binary.LittleEndian.Uint64(data[0x0+(0x8*6) : 0x0+(0x8*7)])
40 | header.FileMetaTableOffset = binary.LittleEndian.Uint64(data[0x0+(0x8*7) : 0x0+(0x8*8)])
41 | header.FileMetaTableSize = binary.LittleEndian.Uint64(data[0x0+(0x8*8) : 0x0+(0x8*9)])
42 | header.DataOffset = binary.LittleEndian.Uint64(data[0x0+(0x8*9) : 0x0+(0x8*10)])
43 | return header, nil
44 | }
45 |
46 | func readRomfsFileEntry(data []byte, header RomfsHeader) (map[string]RomfsFileEntry, error) {
47 | if header.FileMetaTableOffset+header.FileMetaTableSize > uint64(len(data)) {
48 | return nil, errors.New("failed to read romfs")
49 | }
50 | dirBytes := data[header.FileMetaTableOffset : header.FileMetaTableOffset+header.FileMetaTableSize]
51 | result := map[string]RomfsFileEntry{}
52 | offset := uint32(0x0)
53 | for offset < uint32(header.FileHashTableSize) {
54 | entry := RomfsFileEntry{}
55 | entry.parent = binary.LittleEndian.Uint32(dirBytes[offset : offset+0x4])
56 | entry.sibling = binary.LittleEndian.Uint32(dirBytes[offset+0x4 : offset+0x8])
57 | entry.offset = binary.LittleEndian.Uint64(dirBytes[offset+0x8 : offset+0x10])
58 | entry.size = binary.LittleEndian.Uint64(dirBytes[offset+0x10 : offset+0x18])
59 | entry.hash = binary.LittleEndian.Uint32(dirBytes[offset+0x18 : offset+0x1C])
60 | entry.name_size = binary.LittleEndian.Uint32(dirBytes[offset+0x1C : offset+0x20])
61 | entry.name = string(dirBytes[offset+0x20 : (offset+0x20)+entry.name_size])
62 | result[entry.name] = entry
63 | offset = offset + 0x20 + entry.name_size
64 | }
65 | return result, nil
66 |
67 | //fmt.Println(string(section[DataOffset+offset+0x3060:DataOffset+offset+0x3060 +0x10]))
68 | }
69 |
--------------------------------------------------------------------------------
/switchfs/splitFileReader.go:
--------------------------------------------------------------------------------
1 | package switchfs
2 |
3 | import (
4 | "errors"
5 | "github.com/avast/retry-go"
6 | "io"
7 | "io/ioutil"
8 | "os"
9 | "path"
10 | "strconv"
11 | "strings"
12 | )
13 |
14 | type ReadAtCloser interface {
15 | io.ReaderAt
16 | io.Closer
17 | }
18 |
19 | type splitFile struct {
20 | info []os.FileInfo
21 | files []ReadAtCloser
22 | path string
23 | chunkSize int64
24 | }
25 |
26 | type fileWrapper struct {
27 | file ReadAtCloser
28 | path string
29 | }
30 |
31 | func NewFileWrapper(filePath string) (*fileWrapper, error) {
32 | result := fileWrapper{}
33 | result.path = filePath
34 | file, err := _openFile(filePath)
35 | if err != nil {
36 | return nil, err
37 | }
38 | result.file = file
39 | return &result, nil
40 | }
41 |
42 | func (sp *fileWrapper) ReadAt(p []byte, off int64) (n int, err error) {
43 | if sp.file != nil {
44 | return sp.file.ReadAt(p, off)
45 | }
46 | return 0, errors.New("file is not opened")
47 | }
48 |
49 | func (sp *fileWrapper) Close() error {
50 |
51 | if sp.file != nil {
52 | return sp.file.Close()
53 | }
54 |
55 | return nil
56 | }
57 |
58 | func NewSplitFileReader(filePath string) (*splitFile, error) {
59 | result := splitFile{}
60 | index := strings.LastIndex(filePath, string(os.PathSeparator))
61 | splitFileFolder := filePath[:index]
62 | files, err := ioutil.ReadDir(splitFileFolder)
63 | if err != nil {
64 | return nil, err
65 | }
66 | result.path = splitFileFolder
67 | result.chunkSize = files[0].Size()
68 | result.info = make([]os.FileInfo, 0, len(files))
69 | result.files = make([]ReadAtCloser, len(files))
70 | for _, file := range files {
71 | if _, err := strconv.Atoi(file.Name()[len(file.Name())-1:]); err == nil {
72 | result.info = append(result.info, file)
73 | }
74 | }
75 | return &result, nil
76 | }
77 |
78 | func (sp *splitFile) ReadAt(p []byte, off int64) (n int, err error) {
79 | //calculate the part containing the offset
80 | part := int(off / sp.chunkSize)
81 |
82 | if len(sp.info) < part {
83 | return 0, errors.New("missing part " + strconv.Itoa(part))
84 | }
85 |
86 | if len(sp.files) == 0 || sp.files[part] == nil {
87 | file, _ := _openFile(path.Join(sp.path, sp.info[part].Name()))
88 | sp.files[part] = file
89 | }
90 | off = off - sp.chunkSize*int64(part)
91 |
92 | if off < 0 || off > sp.info[part].Size() {
93 | return 0, errors.New("offset is out of bounds")
94 | }
95 | return sp.files[part].ReadAt(p, off)
96 | }
97 |
98 | func _openFile(path string) (*os.File, error) {
99 | var file *os.File
100 | var err error
101 | retry.Attempts(5)
102 | err = retry.Do(
103 | func() error {
104 | file, err = os.Open(path)
105 | return err
106 | },
107 | )
108 | return file, err
109 | }
110 |
111 | func (sp *splitFile) Close() error {
112 | for _, file := range sp.files {
113 | if file != nil {
114 | file.Close()
115 | }
116 | }
117 | return nil
118 | }
119 |
120 | func OpenFile(filePath string) (ReadAtCloser, error) {
121 | //check if it's a split file
122 | if _, err := strconv.Atoi(filePath[len(filePath)-1:]); err == nil {
123 | return NewSplitFileReader(filePath)
124 | } else {
125 | return NewFileWrapper(filePath)
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/switchfs/xci.go:
--------------------------------------------------------------------------------
1 | package switchfs
2 |
3 | import (
4 | "bytes"
5 | "encoding/binary"
6 | "errors"
7 | "go.uber.org/zap"
8 | "io"
9 | "strings"
10 | )
11 |
12 | func ReadXciMetadata(filePath string) (map[string]*ContentMetaAttributes, error) {
13 | file, err := OpenFile(filePath)
14 | if err != nil {
15 | return nil, err
16 | }
17 |
18 | defer file.Close()
19 |
20 | header := make([]byte, 0x200)
21 | _, err = file.ReadAt(header, 0)
22 | if err != nil {
23 | return nil, err
24 | }
25 |
26 | if string(header[0x100:0x104]) != "HEAD" {
27 | return nil, errors.New("Invalid XCI headerBytes. Expected 'HEAD', got '" + string(header[:0x4]) + "'")
28 | }
29 |
30 | rootPartitionOffset := binary.LittleEndian.Uint64(header[0x130:0x138])
31 | //rootPartitionSize := binary.LittleEndian.Uint64(header[0x138:0x140])
32 |
33 | rootHfs0, err := readPfs0(file, int64(rootPartitionOffset))
34 | if err != nil {
35 | return nil, err
36 | }
37 |
38 | secureHfs0, secureOffset, err := readSecurePartition(file, rootHfs0, rootPartitionOffset)
39 | if err != nil {
40 | return nil, err
41 | }
42 |
43 | contentMap := map[string]*ContentMetaAttributes{}
44 |
45 | for _, pfs0File := range secureHfs0.Files {
46 |
47 | fileOffset := secureOffset + int64(pfs0File.StartOffset)
48 |
49 | if strings.Contains(pfs0File.Name, "cnmt.nca") {
50 | _, section, err := openMetaNcaDataSection(file, fileOffset)
51 | if err != nil {
52 | return nil, err
53 | }
54 | currPfs0, err := readPfs0(bytes.NewReader(section), 0x0)
55 | if err != nil {
56 | return nil, err
57 | }
58 | currCnmt, err := readBinaryCnmt(currPfs0, section)
59 | if err != nil {
60 | return nil, err
61 | }
62 |
63 | if currCnmt.Type == "BASE" || currCnmt.Type == "UPD" {
64 | nacp, err := ExtractNacp(currCnmt, file, secureHfs0, secureOffset)
65 | if err != nil {
66 | zap.S().Debug("Failed to extract nacp [%v]\n", err.Error())
67 | }
68 | currCnmt.Ncap = nacp
69 | }
70 |
71 | contentMap[currCnmt.TitleId] = currCnmt
72 |
73 | } /* else if strings.Contains(pfs0File.Name, ".cnmt.xml") {
74 | xmlBytes := make([]byte, pfs0File.Size)
75 | _, err = file.ReadAt(xmlBytes, fileOffset)
76 | if err != nil {
77 | return nil, err
78 | }
79 |
80 | currCnmt, err := readXmlCnmt(xmlBytes)
81 | if err != nil {
82 | return nil, err
83 | }
84 | contentMap[currCnmt.TitleId] = currCnmt
85 | }*/
86 | }
87 |
88 | return contentMap, nil
89 | }
90 |
91 | func getNcaById(hfs0 *PFS0, id string) *fileEntry {
92 | for _, fileEntry := range hfs0.Files {
93 | if strings.Contains(fileEntry.Name, id) {
94 | return &fileEntry
95 | }
96 | }
97 | return nil
98 | }
99 |
100 | func readSecurePartition(file io.ReaderAt, hfs0 *PFS0, rootPartitionOffset uint64) (*PFS0, int64, error) {
101 | for _, hfs0File := range hfs0.Files {
102 | offset := int64(rootPartitionOffset) + int64(hfs0File.StartOffset)
103 |
104 | if hfs0File.Name == "secure" {
105 | securePartition, err := readPfs0(file, offset)
106 | if err != nil {
107 | return nil, 0, err
108 | }
109 | return securePartition, offset, nil
110 | }
111 | }
112 | return nil, 0, nil
113 | }
114 |
--------------------------------------------------------------------------------
/updates_ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/giwty/switch-library-manager/f69daa7e5e3bc9fc2cc17ad839a12ac3bf5e0372/updates_ui.png
--------------------------------------------------------------------------------