diff --git a/docs/types/ExtractionOptions.json b/docs/types/ExtractionOptions.json index 136c84e..89d821c 100644 --- a/docs/types/ExtractionOptions.json +++ b/docs/types/ExtractionOptions.json @@ -131,6 +131,14 @@ } } }, + { + "displayType": "property", + "name": "usePackSubfolders", + "description": "Whether or not to use the pack id (e.g. BG, GP01, SP24) as the first subfolder for all files. False by default.", + "type": { + "name": "boolean" + } + }, { "displayType": "property", "name": "usePrimarySubfolders", @@ -139,6 +147,14 @@ "name": "boolean" } }, + { + "displayType": "property", + "name": "useRawPrimarySubfolderNames", + "description": "For usePrimarySubfolders, whether to format the tuning types as they appear in tunings, instead of the more friendly PascalCase. False by default.", + "type": { + "name": "boolean" + } + }, { "displayType": "property", "name": "useSecondarySubfolders", diff --git a/docs/types/NamingConvention.json b/docs/types/NamingConvention.json index 9963305..85d9253 100644 --- a/docs/types/NamingConvention.json +++ b/docs/types/NamingConvention.json @@ -26,14 +26,14 @@ }, { "title": "Previews", - "description": "These are what the resulting filenames will look like. T = type, G = group, I = instance, and the name comes from either tuning or SimData.", + "description": "These are what the resulting filenames will look like. T = type, G = group, I = instance, name comes from the \"n\" attribute (or the Instance's \"name\" for SimData), and TuningType is SimData or the type as it appears in TuningTypeResource, plus Tuning if it did not already end with that.", "content": [ { "displayType": "enum", "values": [ { "name": "s4s", - "value": "TTTTTTTT!GGGGGGGG!IIIIIIIIIIIIIIII.name.xml" + "value": "TTTTTTTT!GGGGGGGG!IIIIIIIIIIIIIIII.name.TuningType.xml" }, { "name": "s4pi", diff --git a/package-lock.json b/package-lock.json index 0373224..b2212ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,13 +16,46 @@ "glob": "^10.2.3" }, "devDependencies": { - "@types/node": "^16.11.7" + "@types/node": "^16.11.7", + "ts-node": "^10.5.0", + "typescript": "^4.4.4" }, "funding": { "type": "patreon", "url": "https://www.patreon.com/bePatron?u=40823163" } }, + "../models/dst": { + "name": "@s4tk/models", + "version": "0.6.9", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@s4tk/compression": "^0.2.0", + "@s4tk/encoding": "^0.1.4", + "@s4tk/hashing": "^0.2.1", + "@s4tk/images": "^0.2.4", + "@s4tk/xml-dom": "^0.2.6", + "just-clone": "^5.0.1", + "just-compare": "^2.0.1" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/bePatron?u=40823163" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -45,46 +78,45 @@ } }, "node_modules/@jimp/core": { - "version": "0.22.7", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.7.tgz", - "integrity": "sha512-lg4z+pw23v2Gp9LWQur0NqYtnmoNWnyN/Or96elhJgeEJskrDGwROdajortHCCOI1xDnUZSirg8sFvStC8BIlg==", + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.10.tgz", + "integrity": "sha512-ZKyrehVy6wu1PnBXIUpn/fXmyMRQiVSbvHDubgXz4bfTOao3GiOurKHjByutQIgozuAN6ZHWiSge1dKA+dex3w==", "dependencies": { - "@jimp/utils": "^0.22.7", + "@jimp/utils": "^0.22.10", "any-base": "^1.1.0", "buffer": "^5.2.0", "exif-parser": "^0.1.12", "file-type": "^16.5.4", "isomorphic-fetch": "^3.0.0", - "mkdirp": "^2.1.3", "pixelmatch": "^4.0.2", "tinycolor2": "^1.6.0" } }, "node_modules/@jimp/custom": { - "version": "0.22.7", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.7.tgz", - "integrity": "sha512-n+1+ZVDNumB1E+sL7KdGKAJ6MbgniX1/v/xOEFEQ46WDZ4cRTqP4+tXjHTuHSlOXiANH+K9zD6qgzqmgO6mCVw==", + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.10.tgz", + "integrity": "sha512-sPZkUYe1hu0iIgNisjizxPJqq2vaaKvkCkPoXq2U6UV3ZA1si/WVdrg25da3IcGIEV+83AoHgM8TvqlLgrCJsg==", "dependencies": { - "@jimp/core": "^0.22.7" + "@jimp/core": "^0.22.10" } }, "node_modules/@jimp/plugin-resize": { - "version": "0.22.7", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.7.tgz", - "integrity": "sha512-pg7i0JIYt7x7ag+CoD/yG70Xvwm1sKRfcFjQh954yestiin14uppPgXchAmTBmctecBjLNdsVlqSXbPvU4Jvxw==", + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.10.tgz", + "integrity": "sha512-ixomxVcnAONXDgaq0opvAx4UAOiEhOA/tipuhFFOvPKFd4yf1BAnEviB5maB0SBHHkJXPUSzDp/73xVTMGSe7g==", "dependencies": { - "@jimp/utils": "^0.22.7" + "@jimp/utils": "^0.22.10" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/png": { - "version": "0.22.7", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.7.tgz", - "integrity": "sha512-LxD3O9FKEwVv+j+HcUV7ez72Miy+823EjhtFZbBYXNp9qjHtHFBpgcSJBftUOCei8OlmmVgULYn9XjyfPsDgGw==", + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.10.tgz", + "integrity": "sha512-RYinU7tZToeeR2g2qAMn42AU+8OUHjXPKZZ9RkmoL4bguA1xyZWaSdr22/FBkmnHhOERRlr02KPDN1OTOYHLDQ==", "dependencies": { - "@jimp/utils": "^0.22.7", + "@jimp/utils": "^0.22.10", "pngjs": "^6.0.0" }, "peerDependencies": { @@ -92,13 +124,38 @@ } }, "node_modules/@jimp/utils": { - "version": "0.22.7", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.7.tgz", - "integrity": "sha512-4ax4IOWLIERx4yz9y3fNXKvQaPOY23yJF5h4sizxVkQUObkZHWE0kL0TVHodBt3rS8ksdbCL8Jkz4GeNP/Katg==", + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.10.tgz", + "integrity": "sha512-ztlOK9Mm2iLG2AMoabzM4i3WZ/FtshcgsJCbZCRUs/DKoeS2tySRJTnQZ1b7Roq0M4Ce+FUAxnCAcBV0q7PH9w==", "dependencies": { "regenerator-runtime": "^0.13.3" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz", @@ -262,6 +319,30 @@ "node": ">= 10" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, "node_modules/@types/node": { "version": "16.18.28", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.28.tgz", @@ -273,6 +354,27 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -353,6 +455,12 @@ "node": ">=10" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -548,6 +656,12 @@ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -598,6 +712,15 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -999,6 +1122,12 @@ "semver": "bin/semver.js" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/make-fetch-happen": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", @@ -1196,20 +1325,6 @@ "node": ">=8" } }, - "node_modules/mkdirp": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", - "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -1954,6 +2069,62 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/unique-filename": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", @@ -1981,6 +2152,12 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/wbasenodejscpp": { "version": "0.3.134", "resolved": "https://registry.npmjs.org/wbasenodejscpp/-/wbasenodejscpp-0.3.134.tgz", @@ -1992,9 +2169,9 @@ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + "version": "3.6.19", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz", + "integrity": "sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==" }, "node_modules/whatwg-url": { "version": "5.0.0", @@ -2157,6 +2334,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } } } } diff --git a/package.json b/package.json index c4b3d04..99bd78f 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,12 @@ }, "main": "./extraction.js", "scripts": { - "clean": "rm -rf ./dst/**", + "clean": "rm -rf ./dst/** || true", "clean:out": "rm -rf ./out/**", - "dev": "npm run clean ; tsc --watch", - "build": "npm run clean ; tsc ; node scripts/prepublish.cjs", - "publish": "npm run build && sh scripts/publish.sh" + "dev": "npm run clean && tsc --watch", + "build": "npm run clean && tsc && node scripts/prepublish.cjs", + "publish": "npm run build && sh scripts/publish.sh", + "xml-extractor": "npm run build && ts-node \"./scripts/xml-extractor.ts\"" }, "author": "frankkulak", "funding": { @@ -25,7 +26,9 @@ }, "license": "MIT", "devDependencies": { - "@types/node": "^16.11.7" + "@types/node": "^16.11.7", + "ts-node": "^10.5.0", + "typescript": "^4.4.4" }, "dependencies": { "@s4tk/hashing": "^0.2.1", @@ -34,4 +37,4 @@ "@s4tk/xml-dom": "^0.2.6", "glob": "^10.2.3" } -} \ No newline at end of file +} diff --git a/scripts/xml-extractor.ts b/scripts/xml-extractor.ts new file mode 100644 index 0000000..bef0d5f --- /dev/null +++ b/scripts/xml-extractor.ts @@ -0,0 +1,41 @@ +/** Emulates Scumbumbo's XMLExtractor */ +import { extractFiles } from "../dst/extraction"; +import { ManifestType, NamingConvention } from "../dst/lib/types"; +import { StringTableLocale } from "@s4tk/models/enums"; + +/** THE USER SHOULD EDIT THESE: */ + +// Directories to scan for the game's files +const directories = [ + '/Applications/The Sims 4 Packs', + '/Applications/The Sims 4.app', + 'C:/Program Files/EA Games/The Sims 4', +]; +// The output directory +const outDir = 'D:/S4TKXmlExtractor'; +// The language used to restore string comments. +const targetLocale = StringTableLocale.English; +// How to format the names of the output files. "s4s", "s4pi", "tgi-name", "tgi-only", or "name-only" +const namingConvention: NamingConvention = "s4s"; +// The format of the string manifest to generate. "properties", "json", "xml, or undefined for no manifest. +const stringManifest: ManifestType = "properties"; +// The format of the string manifest to generate. "properties", "json", "xml, or undefined for no manifest. +const tuningManifest: ManifestType = "properties"; + +/** END OF USER EDIT SECTION */ + +extractFiles(directories, outDir, + { + extractSimData: false, + insertGroupComment: false, + namingConvention, + stringManifest, + targetLocale, + tuningManifest, + usePackSubfolders: true, + usePrimarySubfolders: true, + useRawPrimarySubfolderNames: true, + useSecondarySubfolders: false, + useTuningFoldersForSimData: true + } +); diff --git a/src/lib/extract-files.ts b/src/lib/extract-files.ts index ecdb724..a99fa7a 100644 --- a/src/lib/extract-files.ts +++ b/src/lib/extract-files.ts @@ -3,7 +3,7 @@ import path from "path"; import { XmlCommentNode } from "@s4tk/xml-dom"; import { formatResourceGroup, formatResourceKey } from "@s4tk/hashing/formatting"; import { CombinedTuningResource, Package, SimDataResource, XmlResource } from "@s4tk/models"; -import { SimDataGroup, TuningResourceType } from "@s4tk/models/enums"; +import { BinaryResourceType, SimDataGroup, TuningResourceType } from "@s4tk/models/enums"; import type { ResourceKey, ResourceKeyPair } from "@s4tk/models/types"; import { locateSimulationPackages, locateStringTablePackages } from "./locate-packages"; import { indexSimulationPackages, indexStringTablePackages } from "./index-packages"; @@ -60,9 +60,8 @@ export function extractFiles( if (options.restoreComments) { commentMap = stringMap; if (options.extractTuning) stringMap.forEach((value, key) => { - if (!key.startsWith("0x0")) return; const trimmedKey = key.replace(/^0x0+/, "0x"); - commentMap.set(trimmedKey, value); + commentMap.set(trimmedKey, '"' + value + '"'); }); } } @@ -97,6 +96,7 @@ export function extractFiles( writeTuningFile( outDir, entry.key.group, + simPaths.source.get(filepath) ?? simPaths.delta.get(filepath), tuning, tuningDirs, options as ExtractionOptions @@ -130,6 +130,7 @@ export function extractFiles( writeSimDataFile( outDir, entry, + simPaths.source.get(filepath) ?? simPaths.delta.get(filepath), tuningDirs, options as ExtractionOptions ); @@ -155,6 +156,7 @@ export function extractFiles( * * @param outDir Directory to output this file to * @param group Group of combined tuning that this tuning was found in + * @param pack Code of the pack the tuning was found in * @param tuning Tuning file to write * @param tuningDirs Mapping of tuning instances to the folders they are written to * @param options User options @@ -162,6 +164,7 @@ export function extractFiles( function writeTuningFile( outDir: string, group: number, + pack: string, tuning: XmlResource, tuningDirs: Map, options: ExtractionOptions @@ -174,8 +177,13 @@ function writeTuningFile( dom.children.unshift(new XmlCommentNode(`S4TK Group: ${formatResourceGroup(group)}`)); }); let subfolders = outDir; - if (options.usePrimarySubfolders) - subfolders = path.join(subfolders, TuningResourceType[type] ?? "Unknown"); + if (options.usePackSubfolders) + subfolders = path.join(subfolders, pack); + if (options.usePrimarySubfolders) { + let sub = TuningResourceType[type]; + if (options.useRawPrimarySubfolderNames) sub = TuningResourceType.getAttr(type) || "tun"; + subfolders = path.join(subfolders, sub ?? "Unknown"); + } if (options.useSecondarySubfolders) subfolders = path.join(subfolders, tuning.root.attributes.c ?? "Unknown"); if (options.useTuningFoldersForSimData) @@ -190,12 +198,14 @@ function writeTuningFile( * * @param outDir Directory to output this file to * @param simdata SimData entry to write + * @param pack Code of the pack the SimData was found in * @param tuningDirs Mapping of tuning instances to the folders they are written to * @param options User options */ function writeSimDataFile( outDir: string, simdata: ResourceKeyPair, + pack: string, tuningDirs: Map, options: ExtractionOptions ) { @@ -205,6 +215,8 @@ function writeSimDataFile( : undefined; if (!subfolders) { subfolders = outDir; + if (options.usePackSubfolders) + subfolders = path.join(subfolders, pack); if (options.usePrimarySubfolders) subfolders = path.join(subfolders, "SimData"); if (options.useSecondarySubfolders) @@ -229,7 +241,10 @@ function getFileName( ): string { switch (options.namingConvention) { case "s4s": - return formatResourceKey(key, "!") + "." + filename + ".xml"; + let type = TuningResourceType[key.type]; + if (!type) type = BinaryResourceType[key.type]; + else if (!type.endsWith("Tuning")) type += "Tuning"; + return formatResourceKey(key, "!") + "." + filename + "." + type + ".xml"; case "s4pi": return "S4_" + formatResourceKey(key, "_") + ".xml"; case "tgi-name": diff --git a/src/lib/index-packages.ts b/src/lib/index-packages.ts index b5b47fc..768550f 100644 --- a/src/lib/index-packages.ts +++ b/src/lib/index-packages.ts @@ -18,7 +18,7 @@ export function indexSimulationPackages( const latestCombineds = new Map(); const latestSimDatas = new Map(); - const indexPackage = (filepath: string) => { + const indexPackage = (_: string, filepath: string) => { const resources = Package.indexResources(filepath, { limit: options.extractSimData ? undefined : 1, keepDeletedRecords: true, @@ -65,7 +65,7 @@ export function indexSimulationPackages( export function indexStringTablePackages(filepaths: PackagePaths): FileMap { const latestStbls = new Map(); - const indexPackage = (filepath: string) => { + const indexPackage = (_: string, filepath: string) => { Package.indexResources(filepath, { keepDeletedRecords: true, resourceFilter(type) { diff --git a/src/lib/locate-packages.ts b/src/lib/locate-packages.ts index d30b33a..ecb9006 100644 --- a/src/lib/locate-packages.ts +++ b/src/lib/locate-packages.ts @@ -1,39 +1,50 @@ +import path from "path"; import { StringTableLocale } from "@s4tk/models/enums"; import { PackagePaths } from "./types"; import { ExtractionOptions } from "./options"; import { safeGlob } from "./helpers"; +function withPackCode(dir: string, filepath: string): [string, string] { + const subpath = path.dirname(path.relative(dir, filepath)); + for (const folder of subpath.split(path.sep)) { + if (/\d\d$/.test(folder)) { + return [filepath, folder]; + } + } + return [filepath, "BG"]; +} + /** * Finds paths to all packages containing simulation files. * - * @param dirs Array of diractories to search in + * @param dirs Array of directories to search in * @param options Options to configure */ export function locateSimulationPackages( dirs: string[], options?: ExtractionOptions ): PackagePaths { - const source: string[] = [] - const delta: string[] = []; + const source = new Map() + const delta = new Map() dirs.forEach(dir => { if (options?.includeFullBuilds ?? true) { safeGlob(dir, "**", "SimulationFullBuild*.package") .forEach(fullBuildPath => { - source.push(fullBuildPath); + source.set(...withPackCode(dir, fullBuildPath)); }); } if (options?.includeDeltas ?? true) { safeGlob(dir, "**", "SimulationDeltaBuild*.package") .forEach(deltaBuildPath => { - delta.push(deltaBuildPath); + delta.set(...withPackCode(dir, deltaBuildPath)); }); // "SimulationContentDeltaBuild" is SDX safeGlob(dir, "**", "SimulationContentDeltaBuild*.package") .forEach(deltaBuildPath => { - delta.push(deltaBuildPath); + delta.set(...withPackCode(dir, deltaBuildPath)); }); } }); @@ -45,7 +56,7 @@ export function locateSimulationPackages( * Finds paths to all packages containing string tables. * * @param locale Locale of string tables to find - * @param dirs Array of diractories to search in + * @param dirs Array of directories to search in * @param options Options to configure */ export function locateStringTablePackages( @@ -53,8 +64,8 @@ export function locateStringTablePackages( dirs: string[], options?: ExtractionOptions ): PackagePaths { - const source: string[] = [] - const delta: string[] = []; + const source = new Map() + const delta = new Map(); const localeCode = StringTableLocale.getLocaleCode(locale); const packagePattern = `Strings_${localeCode}.package`; @@ -62,9 +73,9 @@ export function locateStringTablePackages( dirs.forEach(dir => { safeGlob(dir, "**", packagePattern).forEach(packagePath => { if (packagePath.includes("Delta")) { - if (options?.includeDeltas ?? true) delta.push(packagePath); + if (options?.includeDeltas ?? true) delta.set(...withPackCode(dir, packagePath)); } else if (options?.includeFullBuilds ?? true) { - source.push(packagePath); + source.set(...withPackCode(dir, packagePath)); } }); }); diff --git a/src/lib/options.ts b/src/lib/options.ts index 02d5fca..0160850 100644 --- a/src/lib/options.ts +++ b/src/lib/options.ts @@ -85,6 +85,12 @@ export interface ExtractionOptions { */ tuningManifest?: ManifestType; + /** + * Whether or not to use the pack id (e.g. BG, GP01, SP24) as the first + * subfolder for all files. False by default. + */ + usePackSubfolders: boolean; + /** * Whether or not to use primary subfolders when writing the files. This * means tuning type for tuning files, and "SimData" for SimData files. @@ -95,6 +101,13 @@ export interface ExtractionOptions { */ usePrimarySubfolders: boolean; + /** + * For usePrimarySubfolders, whether to format the tuning types as they + * appear in tunings, instead of the more friendly PascalCase. + * False by default. + */ + useRawPrimarySubfolderNames: boolean; + /** * Whether or not to use secondary subfolders when writing the files. This * means class for tuning files, and group for SimData files. True by @@ -134,7 +147,9 @@ export function setDefaultOptions( stringManifest: options?.stringManifest, targetLocale: options?.targetLocale ?? StringTableLocale.English, tuningManifest: options?.tuningManifest, + usePackSubfolders: options?.usePackSubfolders ?? false, usePrimarySubfolders: options?.usePrimarySubfolders ?? true, + useRawPrimarySubfolderNames: options?.useRawPrimarySubfolderNames ?? false, useSecondarySubfolders: options?.useSecondarySubfolders ?? true, useTuningFoldersForSimData: options?.useTuningFoldersForSimData ?? true, }; diff --git a/src/lib/types.ts b/src/lib/types.ts index 8533f44..d36106e 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -39,12 +39,13 @@ export type ExtractionEventListener export type FileMap = Map; /** - * A pairing of source and delta package paths. Source packages should be read - * first, and overriden by delta packages if they contain the same resources. + * A pair of maps of source and delta package paths to their pack codes. + * Source packages should be read first, and overriden by delta packages + * if they contain the same resources. */ export interface PackagePaths { - source: string[]; - delta: string[]; + source: Map; + delta: Map; } /**