diff --git a/scrapper_web/.gitignore b/scrapper_web/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/scrapper_web/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/scrapper_web/.vscode/extensions.json b/scrapper_web/.vscode/extensions.json
new file mode 100644
index 0000000..bdef820
--- /dev/null
+++ b/scrapper_web/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["svelte.svelte-vscode"]
+}
diff --git a/scrapper_web/README.md b/scrapper_web/README.md
new file mode 100644
index 0000000..69c2ac5
--- /dev/null
+++ b/scrapper_web/README.md
@@ -0,0 +1,47 @@
+# Svelte + Vite
+
+This template should help get you started developing with Svelte in Vite.
+
+## Recommended IDE Setup
+
+[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
+
+## Need an official Svelte framework?
+
+Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
+
+## Technical considerations
+
+**Why use this over SvelteKit?**
+
+- It brings its own routing solution which might not be preferable for some users.
+- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
+
+This template contains as little as possible to get started with Vite + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
+
+Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
+
+**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
+
+Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
+
+**Why include `.vscode/extensions.json`?**
+
+Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
+
+**Why enable `checkJs` in the JS template?**
+
+It is likely that most cases of changing variable types in runtime are likely to be accidental, rather than deliberate. This provides advanced typechecking out of the box. Should you like to take advantage of the dynamically-typed nature of JavaScript, it is trivial to change the configuration.
+
+**Why is HMR not preserving my local component state?**
+
+HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
+
+If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
+
+```js
+// store.js
+// An extremely simple external store
+import { writable } from 'svelte/store'
+export default writable(0)
+```
diff --git a/scrapper_web/index.html b/scrapper_web/index.html
new file mode 100644
index 0000000..26c29c4
--- /dev/null
+++ b/scrapper_web/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vite + Svelte
+
+
+
+
+
+
diff --git a/scrapper_web/jsconfig.json b/scrapper_web/jsconfig.json
new file mode 100644
index 0000000..2df8094
--- /dev/null
+++ b/scrapper_web/jsconfig.json
@@ -0,0 +1,33 @@
+{
+ "compilerOptions": {
+ "moduleResolution": "Node",
+ "target": "ESNext",
+ "module": "ESNext",
+ /**
+ * svelte-preprocess cannot figure out whether you have
+ * a value or a type, so tell TypeScript to enforce using
+ * `import type` instead of `import` for Types.
+ */
+ "importsNotUsedAsValues": "error",
+ "isolatedModules": true,
+ "resolveJsonModule": true,
+ /**
+ * To have warnings / errors of the Svelte compiler at the
+ * correct position, enable source maps by default.
+ */
+ "sourceMap": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ /**
+ * Typecheck JS in `.svelte` and `.js` files by default.
+ * Disable this if you'd like to use dynamic types.
+ */
+ "checkJs": false
+ },
+ /**
+ * Use global.d.ts instead of compilerOptions.types
+ * to avoid limiting type declarations.
+ */
+ "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
+}
diff --git a/scrapper_web/package.json b/scrapper_web/package.json
new file mode 100644
index 0000000..b66da50
--- /dev/null
+++ b/scrapper_web/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "scrapper_web",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "wasm-pack build ./scrapper -t web && vite build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "@sveltejs/vite-plugin-svelte": "^2.0.2",
+ "@tailwindcss/forms": "^0.5.3",
+ "autoprefixer": "^10.4.13",
+ "cssnano": "^5.1.14",
+ "cssnano-preset-advanced": "^5.3.9",
+ "daisyui": "^2.50.0",
+ "filedrop-svelte": "^0.1.2",
+ "postcss": "^8.4.21",
+ "svelte": "^3.55.1",
+ "svelte-preprocess": "^5.0.1",
+ "tailwindcss": "^3.2.4",
+ "vite": "^4.1.0",
+ "vite-plugin-wasm-pack": "^0.1.12"
+ }
+}
\ No newline at end of file
diff --git a/scrapper_web/pnpm-lock.yaml b/scrapper_web/pnpm-lock.yaml
new file mode 100644
index 0000000..739e35e
--- /dev/null
+++ b/scrapper_web/pnpm-lock.yaml
@@ -0,0 +1,1777 @@
+lockfileVersion: 5.4
+
+specifiers:
+ '@sveltejs/vite-plugin-svelte': ^2.0.2
+ '@tailwindcss/forms': ^0.5.3
+ autoprefixer: ^10.4.13
+ cssnano: ^5.1.14
+ cssnano-preset-advanced: ^5.3.9
+ daisyui: ^2.50.0
+ filedrop-svelte: ^0.1.2
+ postcss: ^8.4.21
+ svelte: ^3.55.1
+ svelte-preprocess: ^5.0.1
+ tailwindcss: ^3.2.4
+ vite: ^4.1.0
+ vite-plugin-wasm-pack: ^0.1.12
+
+devDependencies:
+ '@sveltejs/vite-plugin-svelte': 2.0.2_svelte@3.55.1+vite@4.1.1
+ '@tailwindcss/forms': 0.5.3_tailwindcss@3.2.6
+ autoprefixer: 10.4.13_postcss@8.4.21
+ cssnano: 5.1.14_postcss@8.4.21
+ cssnano-preset-advanced: 5.3.9_postcss@8.4.21
+ daisyui: 2.50.0_gbtt6ss3tbiz4yjtvdr6fbrj44
+ filedrop-svelte: 0.1.2
+ postcss: 8.4.21
+ svelte: 3.55.1
+ svelte-preprocess: 5.0.1_pehl75e5jsy22vp33udjja4soi
+ tailwindcss: 3.2.6_postcss@8.4.21
+ vite: 4.1.1
+ vite-plugin-wasm-pack: 0.1.12
+
+packages:
+
+ /@esbuild/android-arm/0.16.17:
+ resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/android-arm64/0.16.17:
+ resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/android-x64/0.16.17:
+ resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/darwin-arm64/0.16.17:
+ resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/darwin-x64/0.16.17:
+ resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/freebsd-arm64/0.16.17:
+ resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/freebsd-x64/0.16.17:
+ resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-arm/0.16.17:
+ resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-arm64/0.16.17:
+ resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-ia32/0.16.17:
+ resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-loong64/0.16.17:
+ resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-mips64el/0.16.17:
+ resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-ppc64/0.16.17:
+ resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-riscv64/0.16.17:
+ resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-s390x/0.16.17:
+ resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-x64/0.16.17:
+ resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/netbsd-x64/0.16.17:
+ resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/openbsd-x64/0.16.17:
+ resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/sunos-x64/0.16.17:
+ resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/win32-arm64/0.16.17:
+ resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/win32-ia32/0.16.17:
+ resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/win32-x64/0.16.17:
+ resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@jridgewell/sourcemap-codec/1.4.14:
+ resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
+ dev: true
+
+ /@nodelib/fs.scandir/2.1.5:
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+ dev: true
+
+ /@nodelib/fs.stat/2.0.5:
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /@nodelib/fs.walk/1.2.8:
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.15.0
+ dev: true
+
+ /@sveltejs/vite-plugin-svelte/2.0.2_svelte@3.55.1+vite@4.1.1:
+ resolution: {integrity: sha512-xCEan0/NNpQuL0l5aS42FjwQ6wwskdxC3pW1OeFtEKNZwRg7Evro9lac9HesGP6TdFsTv2xMes5ASQVKbCacxg==}
+ engines: {node: ^14.18.0 || >= 16}
+ peerDependencies:
+ svelte: ^3.54.0
+ vite: ^4.0.0
+ dependencies:
+ debug: 4.3.4
+ deepmerge: 4.3.0
+ kleur: 4.1.5
+ magic-string: 0.27.0
+ svelte: 3.55.1
+ svelte-hmr: 0.15.1_svelte@3.55.1
+ vite: 4.1.1
+ vitefu: 0.2.4_vite@4.1.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@tailwindcss/forms/0.5.3_tailwindcss@3.2.6:
+ resolution: {integrity: sha512-y5mb86JUoiUgBjY/o6FJSFZSEttfb3Q5gllE4xoKjAAD+vBrnIhE4dViwUuow3va8mpH4s9jyUbUbrRGoRdc2Q==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1'
+ dependencies:
+ mini-svg-data-uri: 1.4.4
+ tailwindcss: 3.2.6_postcss@8.4.21
+ dev: true
+
+ /@trysound/sax/0.2.0:
+ resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
+ engines: {node: '>=10.13.0'}
+ dev: true
+
+ /@types/node/18.13.0:
+ resolution: {integrity: sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==}
+ dev: true
+
+ /@types/pug/2.0.6:
+ resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
+ dev: true
+
+ /@types/sass/1.43.1:
+ resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
+ dependencies:
+ '@types/node': 18.13.0
+ dev: true
+
+ /acorn-node/1.8.2:
+ resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==}
+ dependencies:
+ acorn: 7.4.1
+ acorn-walk: 7.2.0
+ xtend: 4.0.2
+ dev: true
+
+ /acorn-walk/7.2.0:
+ resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==}
+ engines: {node: '>=0.4.0'}
+ dev: true
+
+ /acorn/7.4.1:
+ resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+ dev: true
+
+ /ansi-styles/4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+ dependencies:
+ color-convert: 2.0.1
+ dev: true
+
+ /anymatch/3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+ dev: true
+
+ /arg/5.0.2:
+ resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+ dev: true
+
+ /autoprefixer/10.4.13_postcss@8.4.21:
+ resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+ dependencies:
+ browserslist: 4.21.5
+ caniuse-lite: 1.0.30001450
+ fraction.js: 4.2.0
+ normalize-range: 0.1.2
+ picocolors: 1.0.0
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /balanced-match/1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ dev: true
+
+ /binary-extensions/2.2.0:
+ resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /boolbase/1.0.0:
+ resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+ dev: true
+
+ /brace-expansion/1.1.11:
+ resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+ dev: true
+
+ /braces/3.0.2:
+ resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
+ engines: {node: '>=8'}
+ dependencies:
+ fill-range: 7.0.1
+ dev: true
+
+ /browserslist/4.21.5:
+ resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+ dependencies:
+ caniuse-lite: 1.0.30001450
+ electron-to-chromium: 1.4.288
+ node-releases: 2.0.10
+ update-browserslist-db: 1.0.10_browserslist@4.21.5
+ dev: true
+
+ /buffer-crc32/0.2.13:
+ resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
+ dev: true
+
+ /camelcase-css/2.0.1:
+ resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
+ engines: {node: '>= 6'}
+ dev: true
+
+ /caniuse-api/3.0.0:
+ resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
+ dependencies:
+ browserslist: 4.21.5
+ caniuse-lite: 1.0.30001450
+ lodash.memoize: 4.1.2
+ lodash.uniq: 4.5.0
+ dev: true
+
+ /caniuse-lite/1.0.30001450:
+ resolution: {integrity: sha512-qMBmvmQmFXaSxexkjjfMvD5rnDL0+m+dUMZKoDYsGG8iZN29RuYh9eRoMvKsT6uMAWlyUUGDEQGJJYjzCIO9ew==}
+ dev: true
+
+ /chalk/4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+ dev: true
+
+ /chokidar/3.5.3:
+ resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
+ engines: {node: '>= 8.10.0'}
+ dependencies:
+ anymatch: 3.1.3
+ braces: 3.0.2
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
+ /color-convert/2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+ dependencies:
+ color-name: 1.1.4
+ dev: true
+
+ /color-name/1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+ dev: true
+
+ /color-string/1.9.1:
+ resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
+ dependencies:
+ color-name: 1.1.4
+ simple-swizzle: 0.2.2
+ dev: true
+
+ /color/4.2.3:
+ resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
+ engines: {node: '>=12.5.0'}
+ dependencies:
+ color-convert: 2.0.1
+ color-string: 1.9.1
+ dev: true
+
+ /colord/2.9.3:
+ resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
+ dev: true
+
+ /commander/7.2.0:
+ resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
+ engines: {node: '>= 10'}
+ dev: true
+
+ /concat-map/0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ dev: true
+
+ /css-declaration-sorter/6.3.1_postcss@8.4.21:
+ resolution: {integrity: sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==}
+ engines: {node: ^10 || ^12 || >=14}
+ peerDependencies:
+ postcss: ^8.0.9
+ dependencies:
+ postcss: 8.4.21
+ dev: true
+
+ /css-select/4.3.0:
+ resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==}
+ dependencies:
+ boolbase: 1.0.0
+ css-what: 6.1.0
+ domhandler: 4.3.1
+ domutils: 2.8.0
+ nth-check: 2.1.1
+ dev: true
+
+ /css-selector-tokenizer/0.8.0:
+ resolution: {integrity: sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==}
+ dependencies:
+ cssesc: 3.0.0
+ fastparse: 1.1.2
+ dev: true
+
+ /css-tree/1.1.3:
+ resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==}
+ engines: {node: '>=8.0.0'}
+ dependencies:
+ mdn-data: 2.0.14
+ source-map: 0.6.1
+ dev: true
+
+ /css-what/6.1.0:
+ resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
+ engines: {node: '>= 6'}
+ dev: true
+
+ /cssesc/3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+ dev: true
+
+ /cssnano-preset-advanced/5.3.9_postcss@8.4.21:
+ resolution: {integrity: sha512-njnh4pp1xCsibJcEHnWZb4EEzni0ePMqPuPNyuWT4Z+YeXmsgqNuTPIljXFEXhxGsWs9183JkXgHxc1TcsahIg==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ autoprefixer: 10.4.13_postcss@8.4.21
+ cssnano-preset-default: 5.2.13_postcss@8.4.21
+ postcss: 8.4.21
+ postcss-discard-unused: 5.1.0_postcss@8.4.21
+ postcss-merge-idents: 5.1.1_postcss@8.4.21
+ postcss-reduce-idents: 5.2.0_postcss@8.4.21
+ postcss-zindex: 5.1.0_postcss@8.4.21
+ dev: true
+
+ /cssnano-preset-default/5.2.13_postcss@8.4.21:
+ resolution: {integrity: sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ css-declaration-sorter: 6.3.1_postcss@8.4.21
+ cssnano-utils: 3.1.0_postcss@8.4.21
+ postcss: 8.4.21
+ postcss-calc: 8.2.4_postcss@8.4.21
+ postcss-colormin: 5.3.0_postcss@8.4.21
+ postcss-convert-values: 5.1.3_postcss@8.4.21
+ postcss-discard-comments: 5.1.2_postcss@8.4.21
+ postcss-discard-duplicates: 5.1.0_postcss@8.4.21
+ postcss-discard-empty: 5.1.1_postcss@8.4.21
+ postcss-discard-overridden: 5.1.0_postcss@8.4.21
+ postcss-merge-longhand: 5.1.7_postcss@8.4.21
+ postcss-merge-rules: 5.1.3_postcss@8.4.21
+ postcss-minify-font-values: 5.1.0_postcss@8.4.21
+ postcss-minify-gradients: 5.1.1_postcss@8.4.21
+ postcss-minify-params: 5.1.4_postcss@8.4.21
+ postcss-minify-selectors: 5.2.1_postcss@8.4.21
+ postcss-normalize-charset: 5.1.0_postcss@8.4.21
+ postcss-normalize-display-values: 5.1.0_postcss@8.4.21
+ postcss-normalize-positions: 5.1.1_postcss@8.4.21
+ postcss-normalize-repeat-style: 5.1.1_postcss@8.4.21
+ postcss-normalize-string: 5.1.0_postcss@8.4.21
+ postcss-normalize-timing-functions: 5.1.0_postcss@8.4.21
+ postcss-normalize-unicode: 5.1.1_postcss@8.4.21
+ postcss-normalize-url: 5.1.0_postcss@8.4.21
+ postcss-normalize-whitespace: 5.1.1_postcss@8.4.21
+ postcss-ordered-values: 5.1.3_postcss@8.4.21
+ postcss-reduce-initial: 5.1.1_postcss@8.4.21
+ postcss-reduce-transforms: 5.1.0_postcss@8.4.21
+ postcss-svgo: 5.1.0_postcss@8.4.21
+ postcss-unique-selectors: 5.1.1_postcss@8.4.21
+ dev: true
+
+ /cssnano-utils/3.1.0_postcss@8.4.21:
+ resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ dev: true
+
+ /cssnano/5.1.14_postcss@8.4.21:
+ resolution: {integrity: sha512-Oou7ihiTocbKqi0J1bB+TRJIQX5RMR3JghA8hcWSw9mjBLQ5Y3RWqEDoYG3sRNlAbCIXpqMoZGbq5KDR3vdzgw==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ cssnano-preset-default: 5.2.13_postcss@8.4.21
+ lilconfig: 2.0.6
+ postcss: 8.4.21
+ yaml: 1.10.2
+ dev: true
+
+ /csso/4.2.0:
+ resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==}
+ engines: {node: '>=8.0.0'}
+ dependencies:
+ css-tree: 1.1.3
+ dev: true
+
+ /daisyui/2.50.0_gbtt6ss3tbiz4yjtvdr6fbrj44:
+ resolution: {integrity: sha512-KiqRvqMXi9rgoH84M8D69gXPg6x+cbdiaHqm8pFHOsXXN1rTl/+OcCKkSnkEwTtIge9VJVDGU6l4B8/n+Juc5g==}
+ peerDependencies:
+ autoprefixer: ^10.0.2
+ postcss: ^8.1.6
+ dependencies:
+ autoprefixer: 10.4.13_postcss@8.4.21
+ color: 4.2.3
+ css-selector-tokenizer: 0.8.0
+ postcss: 8.4.21
+ postcss-js: 4.0.1_postcss@8.4.21
+ tailwindcss: 3.2.6_postcss@8.4.21
+ transitivePeerDependencies:
+ - ts-node
+ dev: true
+
+ /debug/4.3.4:
+ resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.2
+ dev: true
+
+ /deepmerge/4.3.0:
+ resolution: {integrity: sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /defined/1.0.1:
+ resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==}
+ dev: true
+
+ /detect-indent/6.1.0:
+ resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /detective/5.2.1:
+ resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==}
+ engines: {node: '>=0.8.0'}
+ hasBin: true
+ dependencies:
+ acorn-node: 1.8.2
+ defined: 1.0.1
+ minimist: 1.2.7
+ dev: true
+
+ /didyoumean/1.2.2:
+ resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+ dev: true
+
+ /dlv/1.1.3:
+ resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+ dev: true
+
+ /dom-serializer/1.4.1:
+ resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 4.3.1
+ entities: 2.2.0
+ dev: true
+
+ /domelementtype/2.3.0:
+ resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+ dev: true
+
+ /domhandler/4.3.1:
+ resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
+ engines: {node: '>= 4'}
+ dependencies:
+ domelementtype: 2.3.0
+ dev: true
+
+ /domutils/2.8.0:
+ resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
+ dependencies:
+ dom-serializer: 1.4.1
+ domelementtype: 2.3.0
+ domhandler: 4.3.1
+ dev: true
+
+ /electron-to-chromium/1.4.288:
+ resolution: {integrity: sha512-8s9aJf3YiokIrR+HOQzNOGmEHFXVUQzXM/JaViVvKdCkNUjS+lEa/uT7xw3nDVG/IgfxiIwUGkwJ6AR1pTpYsQ==}
+ dev: true
+
+ /entities/2.2.0:
+ resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
+ dev: true
+
+ /es6-promise/3.3.1:
+ resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==}
+ dev: true
+
+ /esbuild/0.16.17:
+ resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==}
+ engines: {node: '>=12'}
+ hasBin: true
+ requiresBuild: true
+ optionalDependencies:
+ '@esbuild/android-arm': 0.16.17
+ '@esbuild/android-arm64': 0.16.17
+ '@esbuild/android-x64': 0.16.17
+ '@esbuild/darwin-arm64': 0.16.17
+ '@esbuild/darwin-x64': 0.16.17
+ '@esbuild/freebsd-arm64': 0.16.17
+ '@esbuild/freebsd-x64': 0.16.17
+ '@esbuild/linux-arm': 0.16.17
+ '@esbuild/linux-arm64': 0.16.17
+ '@esbuild/linux-ia32': 0.16.17
+ '@esbuild/linux-loong64': 0.16.17
+ '@esbuild/linux-mips64el': 0.16.17
+ '@esbuild/linux-ppc64': 0.16.17
+ '@esbuild/linux-riscv64': 0.16.17
+ '@esbuild/linux-s390x': 0.16.17
+ '@esbuild/linux-x64': 0.16.17
+ '@esbuild/netbsd-x64': 0.16.17
+ '@esbuild/openbsd-x64': 0.16.17
+ '@esbuild/sunos-x64': 0.16.17
+ '@esbuild/win32-arm64': 0.16.17
+ '@esbuild/win32-ia32': 0.16.17
+ '@esbuild/win32-x64': 0.16.17
+ dev: true
+
+ /escalade/3.1.1:
+ resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /fast-glob/3.2.12:
+ resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
+ engines: {node: '>=8.6.0'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.5
+ dev: true
+
+ /fastparse/1.1.2:
+ resolution: {integrity: sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==}
+ dev: true
+
+ /fastq/1.15.0:
+ resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
+ dependencies:
+ reusify: 1.0.4
+ dev: true
+
+ /file-selector/0.2.4:
+ resolution: {integrity: sha512-ZDsQNbrv6qRi1YTDOEWzf5J2KjZ9KMI1Q2SGeTkCJmNNW25Jg4TW4UMcmoqcg4WrAyKRcpBXdbWRxkfrOzVRbA==}
+ engines: {node: '>= 10'}
+ dependencies:
+ tslib: 2.5.0
+ dev: true
+
+ /filedrop-svelte/0.1.2:
+ resolution: {integrity: sha512-S0Z+zCO2dy/g3tRjswD8pLEcorj78olkLWvhKEp511gwHUI6x+uWjXacuvFmRQnjbCCPByXzGlC5Asb9nPzUKw==}
+ dependencies:
+ file-selector: 0.2.4
+ pretty-bytes: 6.1.0
+ dev: true
+
+ /fill-range/7.0.1:
+ resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ to-regex-range: 5.0.1
+ dev: true
+
+ /fraction.js/4.2.0:
+ resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
+ dev: true
+
+ /fs-extra/10.1.0:
+ resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
+ engines: {node: '>=12'}
+ dependencies:
+ graceful-fs: 4.2.10
+ jsonfile: 6.1.0
+ universalify: 2.0.0
+ dev: true
+
+ /fs.realpath/1.0.0:
+ resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+ dev: true
+
+ /fsevents/2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /function-bind/1.1.1:
+ resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
+ dev: true
+
+ /glob-parent/5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+ dependencies:
+ is-glob: 4.0.3
+ dev: true
+
+ /glob-parent/6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+ dependencies:
+ is-glob: 4.0.3
+ dev: true
+
+ /glob/7.2.3:
+ resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+ dev: true
+
+ /graceful-fs/4.2.10:
+ resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
+ dev: true
+
+ /has-flag/4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /has/1.0.3:
+ resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
+ engines: {node: '>= 0.4.0'}
+ dependencies:
+ function-bind: 1.1.1
+ dev: true
+
+ /inflight/1.0.6:
+ resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+ dev: true
+
+ /inherits/2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ dev: true
+
+ /is-arrayish/0.3.2:
+ resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
+ dev: true
+
+ /is-binary-path/2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+ dependencies:
+ binary-extensions: 2.2.0
+ dev: true
+
+ /is-core-module/2.11.0:
+ resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
+ dependencies:
+ has: 1.0.3
+ dev: true
+
+ /is-extglob/2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /is-glob/4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-extglob: 2.1.1
+ dev: true
+
+ /is-number/7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+ dev: true
+
+ /jsonfile/6.1.0:
+ resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
+ dependencies:
+ universalify: 2.0.0
+ optionalDependencies:
+ graceful-fs: 4.2.10
+ dev: true
+
+ /kleur/4.1.5:
+ resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /lilconfig/2.0.6:
+ resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /lodash.memoize/4.1.2:
+ resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
+ dev: true
+
+ /lodash.uniq/4.5.0:
+ resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
+ dev: true
+
+ /magic-string/0.27.0:
+ resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
+ engines: {node: '>=12'}
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.4.14
+ dev: true
+
+ /mdn-data/2.0.14:
+ resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
+ dev: true
+
+ /merge2/1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /micromatch/4.0.5:
+ resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
+ engines: {node: '>=8.6'}
+ dependencies:
+ braces: 3.0.2
+ picomatch: 2.3.1
+ dev: true
+
+ /min-indent/1.0.1:
+ resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /mini-svg-data-uri/1.4.4:
+ resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==}
+ hasBin: true
+ dev: true
+
+ /minimatch/3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+ dependencies:
+ brace-expansion: 1.1.11
+ dev: true
+
+ /minimist/1.2.7:
+ resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==}
+ dev: true
+
+ /mkdirp/0.5.6:
+ resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
+ hasBin: true
+ dependencies:
+ minimist: 1.2.7
+ dev: true
+
+ /ms/2.1.2:
+ resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
+ dev: true
+
+ /nanoid/3.3.4:
+ resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+ dev: true
+
+ /narrowing/1.5.0:
+ resolution: {integrity: sha512-DUu4XdKgkfAPTAL28k79pdnshDE2W5T24QAnidSPo2F/W1TX6CjNzmEeXQfE5O1lxQvC0GYI6ZRDsLcyzugEYA==}
+ dev: true
+
+ /node-releases/2.0.10:
+ resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==}
+ dev: true
+
+ /normalize-path/3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /normalize-range/0.1.2:
+ resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /normalize-url/6.1.0:
+ resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /nth-check/2.1.1:
+ resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+ dependencies:
+ boolbase: 1.0.0
+ dev: true
+
+ /object-hash/3.0.0:
+ resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+ engines: {node: '>= 6'}
+ dev: true
+
+ /once/1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+ dependencies:
+ wrappy: 1.0.2
+ dev: true
+
+ /path-is-absolute/1.0.1:
+ resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /path-parse/1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+ dev: true
+
+ /picocolors/1.0.0:
+ resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
+ dev: true
+
+ /picomatch/2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+ dev: true
+
+ /pify/2.3.0:
+ resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /postcss-calc/8.2.4_postcss@8.4.21:
+ resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==}
+ peerDependencies:
+ postcss: ^8.2.2
+ dependencies:
+ postcss: 8.4.21
+ postcss-selector-parser: 6.0.11
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-colormin/5.3.0_postcss@8.4.21:
+ resolution: {integrity: sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ browserslist: 4.21.5
+ caniuse-api: 3.0.0
+ colord: 2.9.3
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-convert-values/5.1.3_postcss@8.4.21:
+ resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ browserslist: 4.21.5
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-discard-comments/5.1.2_postcss@8.4.21:
+ resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ dev: true
+
+ /postcss-discard-duplicates/5.1.0_postcss@8.4.21:
+ resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ dev: true
+
+ /postcss-discard-empty/5.1.1_postcss@8.4.21:
+ resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ dev: true
+
+ /postcss-discard-overridden/5.1.0_postcss@8.4.21:
+ resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ dev: true
+
+ /postcss-discard-unused/5.1.0_postcss@8.4.21:
+ resolution: {integrity: sha512-KwLWymI9hbwXmJa0dkrzpRbSJEh0vVUd7r8t0yOGPcfKzyJJxFM8kLyC5Ev9avji6nY95pOp1W6HqIrfT+0VGw==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ postcss-selector-parser: 6.0.11
+ dev: true
+
+ /postcss-import/14.1.0_postcss@8.4.21:
+ resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ postcss: ^8.0.0
+ dependencies:
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.0
+ resolve: 1.22.1
+ dev: true
+
+ /postcss-js/4.0.1_postcss@8.4.21:
+ resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
+ engines: {node: ^12 || ^14 || >= 16}
+ peerDependencies:
+ postcss: ^8.4.21
+ dependencies:
+ camelcase-css: 2.0.1
+ postcss: 8.4.21
+ dev: true
+
+ /postcss-load-config/3.1.4_postcss@8.4.21:
+ resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
+ engines: {node: '>= 10'}
+ peerDependencies:
+ postcss: '>=8.0.9'
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ postcss:
+ optional: true
+ ts-node:
+ optional: true
+ dependencies:
+ lilconfig: 2.0.6
+ postcss: 8.4.21
+ yaml: 1.10.2
+ dev: true
+
+ /postcss-merge-idents/5.1.1_postcss@8.4.21:
+ resolution: {integrity: sha512-pCijL1TREiCoog5nQp7wUe+TUonA2tC2sQ54UGeMmryK3UFGIYKqDyjnqd6RcuI4znFn9hWSLNN8xKE/vWcUQw==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ cssnano-utils: 3.1.0_postcss@8.4.21
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-merge-longhand/5.1.7_postcss@8.4.21:
+ resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ stylehacks: 5.1.1_postcss@8.4.21
+ dev: true
+
+ /postcss-merge-rules/5.1.3_postcss@8.4.21:
+ resolution: {integrity: sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ browserslist: 4.21.5
+ caniuse-api: 3.0.0
+ cssnano-utils: 3.1.0_postcss@8.4.21
+ postcss: 8.4.21
+ postcss-selector-parser: 6.0.11
+ dev: true
+
+ /postcss-minify-font-values/5.1.0_postcss@8.4.21:
+ resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-minify-gradients/5.1.1_postcss@8.4.21:
+ resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ colord: 2.9.3
+ cssnano-utils: 3.1.0_postcss@8.4.21
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-minify-params/5.1.4_postcss@8.4.21:
+ resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ browserslist: 4.21.5
+ cssnano-utils: 3.1.0_postcss@8.4.21
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-minify-selectors/5.2.1_postcss@8.4.21:
+ resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ postcss-selector-parser: 6.0.11
+ dev: true
+
+ /postcss-nested/6.0.0_postcss@8.4.21:
+ resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==}
+ engines: {node: '>=12.0'}
+ peerDependencies:
+ postcss: ^8.2.14
+ dependencies:
+ postcss: 8.4.21
+ postcss-selector-parser: 6.0.11
+ dev: true
+
+ /postcss-normalize-charset/5.1.0_postcss@8.4.21:
+ resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ dev: true
+
+ /postcss-normalize-display-values/5.1.0_postcss@8.4.21:
+ resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-normalize-positions/5.1.1_postcss@8.4.21:
+ resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-normalize-repeat-style/5.1.1_postcss@8.4.21:
+ resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-normalize-string/5.1.0_postcss@8.4.21:
+ resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-normalize-timing-functions/5.1.0_postcss@8.4.21:
+ resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-normalize-unicode/5.1.1_postcss@8.4.21:
+ resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ browserslist: 4.21.5
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-normalize-url/5.1.0_postcss@8.4.21:
+ resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ normalize-url: 6.1.0
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-normalize-whitespace/5.1.1_postcss@8.4.21:
+ resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-ordered-values/5.1.3_postcss@8.4.21:
+ resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ cssnano-utils: 3.1.0_postcss@8.4.21
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-reduce-idents/5.2.0_postcss@8.4.21:
+ resolution: {integrity: sha512-BTrLjICoSB6gxbc58D5mdBK8OhXRDqud/zodYfdSi52qvDHdMwk+9kB9xsM8yJThH/sZU5A6QVSmMmaN001gIg==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-reduce-initial/5.1.1_postcss@8.4.21:
+ resolution: {integrity: sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ browserslist: 4.21.5
+ caniuse-api: 3.0.0
+ postcss: 8.4.21
+ dev: true
+
+ /postcss-reduce-transforms/5.1.0_postcss@8.4.21:
+ resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ dev: true
+
+ /postcss-selector-parser/6.0.11:
+ resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==}
+ engines: {node: '>=4'}
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+ dev: true
+
+ /postcss-svgo/5.1.0_postcss@8.4.21:
+ resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ postcss-value-parser: 4.2.0
+ svgo: 2.8.0
+ dev: true
+
+ /postcss-unique-selectors/5.1.1_postcss@8.4.21:
+ resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ postcss-selector-parser: 6.0.11
+ dev: true
+
+ /postcss-value-parser/4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+ dev: true
+
+ /postcss-zindex/5.1.0_postcss@8.4.21:
+ resolution: {integrity: sha512-fgFMf0OtVSBR1va1JNHYgMxYk73yhn/qb4uQDq1DLGYolz8gHCyr/sesEuGUaYs58E3ZJRcpoGuPVoB7Meiq9A==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ postcss: 8.4.21
+ dev: true
+
+ /postcss/8.4.21:
+ resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==}
+ engines: {node: ^10 || ^12 || >=14}
+ dependencies:
+ nanoid: 3.3.4
+ picocolors: 1.0.0
+ source-map-js: 1.0.2
+ dev: true
+
+ /pretty-bytes/6.1.0:
+ resolution: {integrity: sha512-Rk753HI8f4uivXi4ZCIYdhmG1V+WKzvRMg/X+M42a6t7D07RcmopXJMDNk6N++7Bl75URRGsb40ruvg7Hcp2wQ==}
+ engines: {node: ^14.13.1 || >=16.0.0}
+ dev: true
+
+ /queue-microtask/1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ dev: true
+
+ /quick-lru/5.1.1:
+ resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /read-cache/1.0.0:
+ resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+ dependencies:
+ pify: 2.3.0
+ dev: true
+
+ /readdirp/3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+ dependencies:
+ picomatch: 2.3.1
+ dev: true
+
+ /resolve/1.22.1:
+ resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
+ hasBin: true
+ dependencies:
+ is-core-module: 2.11.0
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+ dev: true
+
+ /reusify/1.0.4:
+ resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ dev: true
+
+ /rimraf/2.7.1:
+ resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
+ hasBin: true
+ dependencies:
+ glob: 7.2.3
+ dev: true
+
+ /rollup/3.14.0:
+ resolution: {integrity: sha512-o23sdgCLcLSe3zIplT9nQ1+r97okuaiR+vmAPZPTDYB7/f3tgWIYNyiQveMsZwshBT0is4eGax/HH83Q7CG+/Q==}
+ engines: {node: '>=14.18.0', npm: '>=8.0.0'}
+ hasBin: true
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
+ /run-parallel/1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ dependencies:
+ queue-microtask: 1.2.3
+ dev: true
+
+ /sander/0.5.1:
+ resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==}
+ dependencies:
+ es6-promise: 3.3.1
+ graceful-fs: 4.2.10
+ mkdirp: 0.5.6
+ rimraf: 2.7.1
+ dev: true
+
+ /simple-swizzle/0.2.2:
+ resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
+ dependencies:
+ is-arrayish: 0.3.2
+ dev: true
+
+ /sorcery/0.11.0:
+ resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==}
+ hasBin: true
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.4.14
+ buffer-crc32: 0.2.13
+ minimist: 1.2.7
+ sander: 0.5.1
+ dev: true
+
+ /source-map-js/1.0.2:
+ resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /source-map/0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /stable/0.1.8:
+ resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==}
+ deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility'
+ dev: true
+
+ /strip-indent/3.0.0:
+ resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ min-indent: 1.0.1
+ dev: true
+
+ /stylehacks/5.1.1_postcss@8.4.21:
+ resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==}
+ engines: {node: ^10 || ^12 || >=14.0}
+ peerDependencies:
+ postcss: ^8.2.15
+ dependencies:
+ browserslist: 4.21.5
+ postcss: 8.4.21
+ postcss-selector-parser: 6.0.11
+ dev: true
+
+ /supports-color/7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+ dependencies:
+ has-flag: 4.0.0
+ dev: true
+
+ /supports-preserve-symlinks-flag/1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /svelte-hmr/0.15.1_svelte@3.55.1:
+ resolution: {integrity: sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==}
+ engines: {node: ^12.20 || ^14.13.1 || >= 16}
+ peerDependencies:
+ svelte: '>=3.19.0'
+ dependencies:
+ svelte: 3.55.1
+ dev: true
+
+ /svelte-preprocess/5.0.1_pehl75e5jsy22vp33udjja4soi:
+ resolution: {integrity: sha512-0HXyhCoc9rsW4zGOgtInylC6qj259E1hpFnJMJWTf+aIfeqh4O/QHT31KT2hvPEqQfdjmqBR/kO2JDkkciBLrQ==}
+ engines: {node: '>= 14.10.0'}
+ requiresBuild: true
+ peerDependencies:
+ '@babel/core': ^7.10.2
+ coffeescript: ^2.5.1
+ less: ^3.11.3 || ^4.0.0
+ postcss: ^7 || ^8
+ postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0
+ pug: ^3.0.0
+ sass: ^1.26.8
+ stylus: ^0.55.0
+ sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0
+ svelte: ^3.23.0
+ typescript: ^3.9.5 || ^4.0.0
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ coffeescript:
+ optional: true
+ less:
+ optional: true
+ postcss:
+ optional: true
+ postcss-load-config:
+ optional: true
+ pug:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ typescript:
+ optional: true
+ dependencies:
+ '@types/pug': 2.0.6
+ '@types/sass': 1.43.1
+ detect-indent: 6.1.0
+ magic-string: 0.27.0
+ postcss: 8.4.21
+ sorcery: 0.11.0
+ strip-indent: 3.0.0
+ svelte: 3.55.1
+ dev: true
+
+ /svelte/3.55.1:
+ resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /svgo/2.8.0:
+ resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==}
+ engines: {node: '>=10.13.0'}
+ hasBin: true
+ dependencies:
+ '@trysound/sax': 0.2.0
+ commander: 7.2.0
+ css-select: 4.3.0
+ css-tree: 1.1.3
+ csso: 4.2.0
+ picocolors: 1.0.0
+ stable: 0.1.8
+ dev: true
+
+ /tailwindcss/3.2.6_postcss@8.4.21:
+ resolution: {integrity: sha512-BfgQWZrtqowOQMC2bwaSNe7xcIjdDEgixWGYOd6AL0CbKHJlvhfdbINeAW76l1sO+1ov/MJ93ODJ9yluRituIw==}
+ engines: {node: '>=12.13.0'}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.0.9
+ dependencies:
+ arg: 5.0.2
+ chokidar: 3.5.3
+ color-name: 1.1.4
+ detective: 5.2.1
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.2.12
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ lilconfig: 2.0.6
+ micromatch: 4.0.5
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.0.0
+ postcss: 8.4.21
+ postcss-import: 14.1.0_postcss@8.4.21
+ postcss-js: 4.0.1_postcss@8.4.21
+ postcss-load-config: 3.1.4_postcss@8.4.21
+ postcss-nested: 6.0.0_postcss@8.4.21
+ postcss-selector-parser: 6.0.11
+ postcss-value-parser: 4.2.0
+ quick-lru: 5.1.1
+ resolve: 1.22.1
+ transitivePeerDependencies:
+ - ts-node
+ dev: true
+
+ /to-regex-range/5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+ dependencies:
+ is-number: 7.0.0
+ dev: true
+
+ /tslib/2.5.0:
+ resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
+ dev: true
+
+ /universalify/2.0.0:
+ resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
+ engines: {node: '>= 10.0.0'}
+ dev: true
+
+ /update-browserslist-db/1.0.10_browserslist@4.21.5:
+ resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+ dependencies:
+ browserslist: 4.21.5
+ escalade: 3.1.1
+ picocolors: 1.0.0
+ dev: true
+
+ /util-deprecate/1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ dev: true
+
+ /vite-plugin-wasm-pack/0.1.12:
+ resolution: {integrity: sha512-WliYvQp9HXluir4OKGbngkcKxtYtifU11cqLurRRJGsl770Sjr1iIkp5RuvU3IC1poT4A57Z2/YgAKI2Skm7ZA==}
+ dependencies:
+ chalk: 4.1.2
+ fs-extra: 10.1.0
+ narrowing: 1.5.0
+ dev: true
+
+ /vite/4.1.1:
+ resolution: {integrity: sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': '>= 14'
+ less: '*'
+ sass: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ dependencies:
+ esbuild: 0.16.17
+ postcss: 8.4.21
+ resolve: 1.22.1
+ rollup: 3.14.0
+ optionalDependencies:
+ fsevents: 2.3.2
+ dev: true
+
+ /vitefu/0.2.4_vite@4.1.1:
+ resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==}
+ peerDependencies:
+ vite: ^3.0.0 || ^4.0.0
+ peerDependenciesMeta:
+ vite:
+ optional: true
+ dependencies:
+ vite: 4.1.1
+ dev: true
+
+ /wrappy/1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+ dev: true
+
+ /xtend/4.0.2:
+ resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
+ engines: {node: '>=0.4'}
+ dev: true
+
+ /yaml/1.10.2:
+ resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
+ engines: {node: '>= 6'}
+ dev: true
diff --git a/scrapper_web/postcss.config.cjs b/scrapper_web/postcss.config.cjs
new file mode 100644
index 0000000..059077c
--- /dev/null
+++ b/scrapper_web/postcss.config.cjs
@@ -0,0 +1,11 @@
+let cssnano_plugin = {};
+if (process.env.NODE_ENV === "production") {
+ cssnano_plugin = { cssnano: { preset: "advanced" } };
+}
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ ...cssnano_plugin,
+ },
+};
diff --git a/scrapper_web/public/vite.svg b/scrapper_web/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/scrapper_web/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/scrapper_web/scrapper/.gitignore b/scrapper_web/scrapper/.gitignore
new file mode 100644
index 0000000..6985cf1
--- /dev/null
+++ b/scrapper_web/scrapper/.gitignore
@@ -0,0 +1,14 @@
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb
diff --git a/scrapper_web/scrapper/Cargo.toml b/scrapper_web/scrapper/Cargo.toml
new file mode 100644
index 0000000..ee8e4d6
--- /dev/null
+++ b/scrapper_web/scrapper/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "scrapper"
+version = "0.1.0"
+authors = []
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[profile.release]
+lto = true
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+aes = "0.8.2"
+anyhow = "1.0.69"
+binrw = "0.11.1"
+cbc = "0.1.2"
+console_error_panic_hook = "0.1.7"
+derivative = "2.2.0"
+js-sys = "0.3.61"
+pelite = "0.10.0"
+serde = { version = "1.0.152", features = ["derive"] }
+serde-wasm-bindgen = "0.4.5"
+wasm-bindgen = "0.2.83"
+wasm-bindgen-file-reader = "1.0.0"
+web-sys = { version = "0.3.61", features = ["File", "BlobPropertyBag", "Blob", "Url"] }
+
+[package.metadata.wasm-pack.profile.release]
+wasm-opt = ["-O4"]
diff --git a/scrapper_web/scrapper/README.md b/scrapper_web/scrapper/README.md
new file mode 100644
index 0000000..a923c54
--- /dev/null
+++ b/scrapper_web/scrapper/README.md
@@ -0,0 +1,23 @@
+# scrapper
+
+## Usage
+
+[rsw-rs doc](https://github.com/lencx/rsw-rs)
+
+```bash
+# install rsw
+cargo install rsw
+
+# --- help ---
+# rsw help
+rsw -h
+# new help
+rsw new -h
+
+# --- usage ---
+# dev
+rsw watch
+
+# production
+rsw build
+```
diff --git a/scrapper_web/scrapper/src/lib.rs b/scrapper_web/scrapper/src/lib.rs
new file mode 100644
index 0000000..fda2f4b
--- /dev/null
+++ b/scrapper_web/scrapper/src/lib.rs
@@ -0,0 +1,155 @@
+use binrw::{binread, BinReaderExt};
+use serde::Serialize;
+use std::collections::BTreeMap;
+use std::io::{Read, Seek, SeekFrom};
+use wasm_bindgen::prelude::*;
+use wasm_bindgen_file_reader::WebSysFile;
+use web_sys::{Blob, File};
+
+type JsResult = Result;
+
+#[binread]
+#[derive(Serialize, Debug)]
+struct ScrapFile {
+ #[br(temp)]
+ name_len: u32,
+ #[br(count = name_len)]
+ #[br(map = |s: Vec| String::from_utf8_lossy(&s).to_string())]
+ path: String,
+ size: u32,
+ offset: u32,
+}
+
+#[binread]
+#[br(magic = b"BFPK", little)]
+#[derive(Serialize, Debug)]
+struct PackedHeader {
+ version: u32,
+ #[br(temp)]
+ num_files: u32,
+ #[br(count= num_files)]
+ files: Vec,
+}
+
+#[derive(Serialize, Debug)]
+#[serde(tag = "type", rename_all = "snake_case")]
+enum DirectoryTree {
+ File {
+ size: u32,
+ offset: u32,
+ file_index: u8,
+ },
+ Directory {
+ entries: BTreeMap,
+ },
+}
+
+#[wasm_bindgen(inspectable)]
+pub struct MultiPack {
+ files: Vec<(String,WebSysFile)>,
+ tree: DirectoryTree,
+}
+
+fn blob_url(buffer: &[u8]) -> JsResult {
+ let uint8arr =
+ js_sys::Uint8Array::new(&unsafe { js_sys::Uint8Array::view(buffer) }.into());
+ let array = js_sys::Array::new();
+ array.push(&uint8arr.buffer());
+ let blob = Blob::new_with_u8_array_sequence_and_options(
+ &array,
+ web_sys::BlobPropertyBag::new().type_("application/octet-stream"),
+ )
+ .unwrap();
+ web_sys::Url::create_object_url_with_blob(&blob)
+}
+
+#[wasm_bindgen]
+impl MultiPack {
+ #[wasm_bindgen(constructor)]
+ pub fn parse(files: Vec) -> Self {
+ let mut tree = DirectoryTree::default();
+ let mut web_files = vec![];
+ for (file_index, file) in files.into_iter().enumerate() {
+ let file_name = file.name();
+ let mut fh = WebSysFile::new(file);
+ let header = fh.read_le::().unwrap();
+ tree.merge(&header.files, file_index.try_into().unwrap());
+ web_files.push((file_name,fh));
+ }
+ Self {
+ tree,
+ files: web_files,
+ }
+ }
+
+ #[wasm_bindgen]
+ pub fn tree(&self) -> JsValue {
+ serde_wasm_bindgen::to_value(&self.tree).unwrap()
+ }
+
+ #[wasm_bindgen]
+ pub fn download(
+ &mut self,
+ file_index: u8,
+ offset: u32,
+ size: u32,
+ ) -> Result {
+ let Some((_,file)) = self.files.get_mut(file_index as usize) else {
+ return Err("File not found".into());
+ };
+ let mut buffer = vec![0u8; size as usize];
+ file.seek(SeekFrom::Start(offset as u64))
+ .map_err(|e| format!("Failed to seek file: {e}"))?;
+ file.read(&mut buffer)
+ .map_err(|e| format!("Failed to read from file: {e}"))?;
+ Ok(blob_url(&buffer)?.into())
+ }
+}
+
+impl Default for DirectoryTree {
+ fn default() -> Self {
+ Self::Directory {
+ entries: Default::default(),
+ }
+ }
+}
+
+impl DirectoryTree {
+ fn add_child(&mut self, name: &str, node: Self) -> &mut Self {
+ match self {
+ Self::File { .. } => panic!("Can't add child to file!"),
+ Self::Directory {
+ entries
+ } => entries.entry(name.to_owned()).or_insert(node),
+ }
+ }
+
+ fn merge(&mut self, files: &[ScrapFile], file_index: u8) {
+ for file in files {
+ let mut folder = &mut *self;
+ let path: Vec<_> = file.path.split('/').collect();
+ if let Some((filename, path)) = path.as_slice().split_last() {
+ for part in path {
+ let DirectoryTree::Directory { entries } = folder else {
+ unreachable!();
+ };
+ folder = entries.entry(part.to_string()).or_default();
+ }
+ folder.add_child(
+ filename,
+ DirectoryTree::File {
+ size: file.size,
+ offset: file.offset,
+ file_index,
+ },
+ );
+ }
+ }
+ }
+}
+
+#[wasm_bindgen(start)]
+pub fn main() -> Result<(), JsValue> {
+ console_error_panic_hook::set_once();
+ Ok(())
+}
diff --git a/scrapper_web/src/App.svelte b/scrapper_web/src/App.svelte
new file mode 100644
index 0000000..70adfa0
--- /dev/null
+++ b/scrapper_web/src/App.svelte
@@ -0,0 +1,13 @@
+
+
+
+
+
Scrapland .packed explorer
+
+
+
+
+
diff --git a/scrapper_web/src/app.pcss b/scrapper_web/src/app.pcss
new file mode 100644
index 0000000..19ea342
--- /dev/null
+++ b/scrapper_web/src/app.pcss
@@ -0,0 +1,109 @@
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-text-size-adjust: 100%;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+.card {
+ padding: 2em;
+}
+
+#app {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+li {
+ text-align: left;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
+
+.lds-dual-ring {
+ display: inline-block;
+ width: 80px;
+ height: 80px;
+}
+.lds-dual-ring:after {
+ content: " ";
+ display: block;
+ width: 64px;
+ height: 64px;
+ margin: 8px;
+ border-radius: 50%;
+ border: 6px solid #fff;
+ border-color: #fff transparent #fff transparent;
+ animation: lds-dual-ring 1.2s linear infinite;
+}
+@keyframes lds-dual-ring {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/scrapper_web/src/lib/Explorer.svelte b/scrapper_web/src/lib/Explorer.svelte
new file mode 100644
index 0000000..5a7b44b
--- /dev/null
+++ b/scrapper_web/src/lib/Explorer.svelte
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+{#if tree}
+ {#each [...tree.entries] as [name, child]}
+
+ {/each}
+{/if}
diff --git a/scrapper_web/src/lib/TreeView.svelte b/scrapper_web/src/lib/TreeView.svelte
new file mode 100644
index 0000000..f2699f6
--- /dev/null
+++ b/scrapper_web/src/lib/TreeView.svelte
@@ -0,0 +1,56 @@
+
+
+
+
+
+ {#if tree.type == "directory" && tree.entries}
+
+ {#if expanded}
+ [-]
+ {:else}
+ [+]
+ {/if}
+ {label}
+
+ {#if tree.entries && expanded}
+ {#each [...tree.entries] as [name, child]}
+
+ {/each}
+ {/if}
+ {:else}
+
+
+ {label}
+
+ {/if}
+
+
+
+
diff --git a/scrapper_web/src/main.js b/scrapper_web/src/main.js
new file mode 100644
index 0000000..e7acd1d
--- /dev/null
+++ b/scrapper_web/src/main.js
@@ -0,0 +1,6 @@
+import './app.pcss'
+import App from './App.svelte'
+
+export default new App({
+ target: document.getElementById('app'),
+});
diff --git a/scrapper_web/src/scrapper.worker.js b/scrapper_web/src/scrapper.worker.js
new file mode 100644
index 0000000..a1146b1
--- /dev/null
+++ b/scrapper_web/src/scrapper.worker.js
@@ -0,0 +1,28 @@
+import wasm, { MultiPack } from "scrapper";
+
+async function initialize() {
+ await wasm();
+ let pack;
+ let handlers = {
+ parse(data) {
+ pack = new MultiPack(data);
+ return pack.tree();
+ },
+ download(data) {
+ if (pack) {
+ let { label, file_index, offset, size } = data;
+ return [label, pack.download(file_index, offset, size)];
+ }
+ },
+ };
+ self.onmessage = (event) => {
+ for (var [name, func] of Object.entries(handlers)) {
+ let data = event.data[name];
+ if (data) {
+ postMessage(Object.fromEntries([[name, func(data)]]));
+ }
+ }
+ };
+}
+
+initialize();
diff --git a/scrapper_web/src/vite-env.d.ts b/scrapper_web/src/vite-env.d.ts
new file mode 100644
index 0000000..4078e74
--- /dev/null
+++ b/scrapper_web/src/vite-env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/scrapper_web/svelte.config.js b/scrapper_web/svelte.config.js
new file mode 100644
index 0000000..66fa030
--- /dev/null
+++ b/scrapper_web/svelte.config.js
@@ -0,0 +1,6 @@
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
+export default {
+ // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
+ // for more information about preprocessors
+ preprocess: vitePreprocess(),
+}
diff --git a/scrapper_web/tailwind.config.cjs b/scrapper_web/tailwind.config.cjs
new file mode 100644
index 0000000..ad70a37
--- /dev/null
+++ b/scrapper_web/tailwind.config.cjs
@@ -0,0 +1,36 @@
+module.exports = {
+ content: ["./src/**/*.{svelte,js,ts}"],
+ plugins: [require("@tailwindcss/forms"),require("daisyui")],
+ theme: {
+ container: {
+ center: true,
+ },
+ },
+ daisyui: {
+ styled: true,
+ themes: true,
+ base: true,
+ utils: true,
+ logs: true,
+ rtl: false,
+ prefix: "",
+ darkTheme: "scraptool",
+ themes: [
+ {
+ scraptool: {
+ primary: "#F28C18",
+ secondary: "#b45309",
+ accent: "#22d3ee",
+ neutral: "#1B1D1D",
+ "base-100": "#212121",
+ info: "#2463EB",
+ success: "#16A249",
+ warning: "#DB7706",
+ error: "#DC2828",
+ // "--rounded-box": "0.4rem",
+ // "--rounded-btn": "0.2rem"
+ },
+ },
+ ],
+ },
+};
diff --git a/scrapper_web/vite.config.js b/scrapper_web/vite.config.js
new file mode 100644
index 0000000..6935bc3
--- /dev/null
+++ b/scrapper_web/vite.config.js
@@ -0,0 +1,10 @@
+import { defineConfig } from 'vite'
+import { svelte } from '@sveltejs/vite-plugin-svelte'
+import wasmPack from 'vite-plugin-wasm-pack';
+import preprocess from 'svelte-preprocess';
+
+export default defineConfig({
+ plugins: [wasmPack("./scrapper/"),svelte({
+ preprocess: preprocess({ postcss: true })
+ })]
+});
diff --git a/tools/ghidra_scripts/Scrap_analyze.py b/tools/ghidra_scripts/Scrap_analyze.py
new file mode 100644
index 0000000..7c4a7d4
--- /dev/null
+++ b/tools/ghidra_scripts/Scrap_analyze.py
@@ -0,0 +1,134 @@
+
+try:
+ import ghidra_bridge
+ has_bridge=True
+except ImportError:
+ has_bridge=False
+
+from contextlib import contextmanager
+
+if has_bridge:
+ import ghidra_bridge
+ b = ghidra_bridge.GhidraBridge(namespace=globals(), hook_import=True)
+ @contextmanager
+ def transaction():
+ start()
+ try:
+ yield
+ except Exception as e:
+ end(False)
+ raise e
+ end(True)
+else:
+ @contextmanager
+ def transaction():
+ yield
+
+import ghidra.program.model.symbol.SymbolType as SymbolType
+import ghidra.program.model.symbol.SourceType as SourceType
+from ghidra.app.cmd.label import CreateNamespacesCmd
+from ghidra.program.model.data.DataUtilities import createData
+from ghidra.program.model.data.DataUtilities import ClearDataMode
+from ghidra.program.model.listing.CodeUnit import PLATE_COMMENT
+def make_namespace(parts):
+ ns_cmd = CreateNamespacesCmd("::".join(parts), SourceType.USER_DEFINED)
+ ns_cmd.applyTo(currentProgram)
+ return ns_cmd.getNamespace()
+
+
+callback_refs = [ref.fromAddress for ref in getReferencesTo(toAddr(0x590C70)).tolist()]
+engine_var_refs = [
+ ref.fromAddress for ref in getReferencesTo(toAddr(0x5319D0)).tolist()
+]
+
+dtm = currentProgram.getDataTypeManager()
+engine_var_dt = dtm.getDataType("/EngineVar")
+callback_dt = dtm.getDataType("/CCallback")
+
+def create_data(addr,dtype):
+ return createData(currentProgram,addr,dtype,0,False,ClearDataMode.CLEAR_ALL_CONFLICT_DATA)
+
+def create_str(addr):
+ str_len = (findBytes(addr, b"\0").offset - addr.offset) + 1
+ clearListing(addr, addr.add(str_len))
+ return createAsciiString(addr)
+
+
+def make_namespace(parts):
+ ns_cmd = CreateNamespacesCmd("::".join(parts), SourceType.USER_DEFINED)
+ ns_cmd.applyTo(currentProgram)
+ return ns_cmd.getNamespace()
+
+
+def get_call_obj(addr):
+ func = getFunctionContaining(addr)
+ if func is None:
+ disassemble(addr)
+ func = createFunction(addr,None)
+ call_obj = {"this": None, "stack": []}
+ for inst in currentProgram.listing.getInstructions(func.body, True):
+ affected_objs = [r.toString() for r in inst.resultObjects.tolist()]
+ inst_name = inst.getMnemonicString()
+ if inst_name == "PUSH":
+ val=inst.getScalar(0)
+ if val is not None:
+ call_obj["stack"].insert(0, toAddr(val.getValue()).toString())
+ elif inst_name == "MOV" and "ECX" in affected_objs:
+ this = inst.getScalar(1)
+ if this is not None:
+ call_obj["this"] = toAddr(this.getValue()).toString()
+ elif inst_name == "CALL":
+ break
+ return func, call_obj
+
+
+with transaction():
+ for ref in callback_refs:
+ register_callback, call_obj = get_call_obj(ref)
+ name, addr = call_obj["stack"]
+ this = toAddr(call_obj["this"])
+ addr = toAddr(addr)
+ name = create_str(toAddr(name)).getValue()
+ callback_ns = make_namespace(["Callbacks"])
+ ns = make_namespace(["Callbacks", name])
+ clearListing(addr)
+ disassemble(addr)
+ func = createFunction(addr,None)
+ print(name,func)
+ createLabel(addr, name, callback_ns, True, SourceType.USER_DEFINED)
+ createLabel(
+ register_callback.getEntryPoint(),
+ "register",
+ ns,
+ True,
+ SourceType.USER_DEFINED,
+ )
+ createLabel(this, name, None, True, SourceType.USER_DEFINED)
+ create_data(this,callback_dt)
+
+ for ref in engine_var_refs:
+ register_engine_var, call_obj = get_call_obj(ref)
+ engine_var = call_obj['this']
+ try:
+ name,flags,desc = call_obj['stack'][:3]
+ except ValueError:
+ continue
+ name=create_str(toAddr(name)).getValue()
+ desc=create_str(toAddr(desc)).getValue()
+ print(name,ref)
+ ev_ns = make_namespace(["EngineVars"])
+ ns = make_namespace(["EngineVars", name])
+ clearListing(toAddr(engine_var))
+ create_data(toAddr(engine_var),engine_var_dt).setComment(PLATE_COMMENT,desc)
+ createLabel(toAddr(engine_var), name, ev_ns, True, SourceType.USER_DEFINED)
+ clearListing(register_engine_var.getEntryPoint())
+ createLabel(register_engine_var.getEntryPoint(), "register", ns, True, SourceType.USER_DEFINED)
+
+# listing = currentProgram.getListing()
+# codeUnit = listing.getCodeUnitAt(minAddress)
+# codeUnit.setComment(codeUnit.PLATE_COMMENT, "AddCommentToProgramScript - This is an added comment!")
+
+
+# dtm = currentProgram.getDataTypeManager()
+# dt_engine_var = dtm.getDataType("/EngineVar")
+# dt_engine_ptr = dtm.getPointer(dt_engine_var)
diff --git a/tools/ghidra_scripts/mark_up_py.py b/tools/ghidra_scripts/mark_up_py.py
new file mode 100644
index 0000000..5dc9d71
--- /dev/null
+++ b/tools/ghidra_scripts/mark_up_py.py
@@ -0,0 +1,125 @@
+import time
+try:
+ import ghidra_bridge
+ has_bridge=True
+except ImportError:
+ has_bridge=False
+
+from contextlib import contextmanager
+
+if has_bridge:
+ import ghidra_bridge
+ b = ghidra_bridge.GhidraBridge(namespace=globals(), hook_import=True)
+ @contextmanager
+ def transaction():
+ start()
+ try:
+ yield
+ except Exception as e:
+ end(False)
+ raise e
+ end(True)
+else:
+ @contextmanager
+ def transaction():
+ yield
+
+import ghidra.program.model.symbol.SymbolType as SymbolType
+import ghidra.program.model.symbol.SourceType as SourceType
+from ghidra.app.cmd.label import CreateNamespacesCmd
+from ghidra.program.model.data.DataUtilities import createData
+from ghidra.program.model.data.DataUtilities import ClearDataMode
+from ghidra.program.model.listing.CodeUnit import PLATE_COMMENT
+
+listing = currentProgram.getListing()
+dtm = currentProgram.getDataTypeManager()
+py_mod = dtm.getDataType("/PyModuleDef")
+py_meth = dtm.getDataType("/PyMethodDef")
+
+NULL=toAddr(0)
+
+def make_namespace(parts):
+ ns_cmd = CreateNamespacesCmd("::".join(parts), SourceType.USER_DEFINED)
+ ns_cmd.applyTo(currentProgram)
+ return ns_cmd.getNamespace()
+
+def create_data(addr,dtype):
+ return createData(currentProgram,addr,dtype,0,False,ClearDataMode.CLEAR_ALL_CONFLICT_DATA)
+
+def create_str(addr):
+ if addr.equals(NULL):
+ return None
+ str_len = (findBytes(addr, b"\0").offset - addr.offset) + 1
+ clearListing(addr, addr.add(str_len))
+ return createAsciiString(addr)
+
+def get_call_obj(addr):
+ func = getFunctionContaining(addr)
+ if func is None:
+ disassemble(addr)
+ func = createFunction(addr,None)
+ call_obj = {"this": None, "stack": []}
+ for inst in currentProgram.listing.getInstructions(func.body, True):
+ affected_objs = [r.toString() for r in inst.resultObjects.tolist()]
+ inst_name = inst.getMnemonicString()
+ if inst_name == "PUSH":
+ val=inst.getScalar(0)
+ if val is not None:
+ call_obj["stack"].insert(0, toAddr(val.getValue()).toString())
+ elif inst_name == "MOV" and "ECX" in affected_objs:
+ this = inst.getScalar(1)
+ if this is not None:
+ call_obj["this"] = toAddr(this.getValue()).toString()
+ elif inst_name == "CALL":
+ break
+ func=func.symbol.address
+ return func, call_obj
+
+def data_to_dict(data):
+ ret={}
+ for idx in range(data.dataType.getNumComponents()):
+ name=data.dataType.getComponent(idx).getFieldName()
+ value=data.getComponent(idx).getValue()
+ ret[name]=value
+ return ret
+
+def try_create_str(addr):
+ ret=create_str(addr)
+ if ret:
+ return ret.getValue()
+
+with transaction():
+ PyInitModule=getSymbolAt(toAddr("006f31c0"))
+ for ref in getReferencesTo(PyInitModule.address).tolist():
+ func,args=get_call_obj(ref.fromAddress)
+ print(func,args)
+ module_name=create_str(toAddr(args['stack'][0])).getValue()
+ methods=toAddr(args['stack'][1])
+ module_doc=create_str(toAddr(args['stack'][2]))
+ if module_doc:
+ module_doc=module_doc.getValue()
+ print(methods,module_name,module_doc)
+ mod_ns = make_namespace(["Python", module_name])
+ createLabel(func, "__init__", mod_ns, True, SourceType.USER_DEFINED)
+ if module_doc:
+ listing.getCodeUnitAt(func).setComment(PLATE_COMMENT,module_doc)
+ while True:
+ mod_data=data_to_dict(create_data(methods,py_meth))
+ if mod_data['name'] is None:
+ clearListing(methods, methods.add(16))
+ break
+ mod_data['name']=try_create_str(mod_data['name'])
+ try:
+ mod_data['doc']=try_create_str(mod_data['doc'])
+ except:
+ mod_data['doc']=None
+ print(mod_data)
+ createLabel(mod_data['ml_method'], mod_data['name'], mod_ns, True, SourceType.USER_DEFINED)
+ if mod_data['doc']:
+ listing.getCodeUnitAt(mod_data['ml_method']).setComment(PLATE_COMMENT,module_doc)
+ methods=methods.add(16)
+ try:
+ if getBytes(methods,4).tolist()==[0,0,0,0]:
+ break
+ except:
+ break
\ No newline at end of file
diff --git a/tools/remaster/scrap_net/.gitignore b/tools/remaster/scrap_net/.gitignore
new file mode 100644
index 0000000..9623fa0
--- /dev/null
+++ b/tools/remaster/scrap_net/.gitignore
@@ -0,0 +1,2 @@
+/target
+/.history
\ No newline at end of file
diff --git a/tools/remaster/scrap_net/Cargo.lock b/tools/remaster/scrap_net/Cargo.lock
new file mode 100644
index 0000000..11f08a4
--- /dev/null
+++ b/tools/remaster/scrap_net/Cargo.lock
@@ -0,0 +1,1015 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anyhow"
+version = "1.0.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
+
+[[package]]
+name = "array-init"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfb6d71005dc22a708c7496eee5c8dc0300ee47355de6256c3b35b12b5fef596"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "binrw"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "272caaf6e0bfb7d508c0606e541e2c68f85c0d6352b62d0b299924eed59fe384"
+dependencies = [
+ "array-init",
+ "binrw_derive",
+ "bytemuck",
+]
+
+[[package]]
+name = "binrw_derive"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb4b28c1e534d96213c8966bb9240095757aa0909128985f97d16afd2e7257a8"
+dependencies = [
+ "either",
+ "owo-colors",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "block-padding"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a90ec2df9600c28a01c56c4784c9207a96d2451833aeceb8cc97e4c9548bb78"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bytemuck"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da"
+
+[[package]]
+name = "bytes"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chacha20"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7fc89c7c5b9e7a02dfe45cd2367bae382f9ed31c61ca8debe5f827c420a2f08"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "clap"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31c9484ccdc4cb8e7b117cbd0eb150c7c0f04464854e4679aeb50ef03b32d003"
+dependencies = [
+ "atty",
+ "bitflags",
+ "clap_derive",
+ "clap_lex",
+ "once_cell",
+ "strsim",
+ "termcolor",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca689d7434ce44517a12a89456b2be4d1ea1cafcd8f581978c03d45f5a5c12a7"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "console"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c"
+dependencies = [
+ "encode_unicode",
+ "lazy_static",
+ "libc",
+ "terminal_size",
+ "unicode-width",
+ "winapi",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crossterm"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
+dependencies = [
+ "bitflags",
+ "crossterm_winapi",
+ "futures-core",
+ "libc",
+ "mio",
+ "parking_lot",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ae1b35a484aa10e07fe0638d02301c5ad24de82d310ccbd2f3693da5f09bf1c"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "dialoguer"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a92e7e37ecef6857fdc0c0c5d42fd5b0938e46590c2183cc92dd310a6d078eb1"
+dependencies = [
+ "console",
+ "tempfile",
+ "zeroize",
+]
+
+[[package]]
+name = "either"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
+
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
+[[package]]
+name = "fastrand"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56"
+
+[[package]]
+name = "futures-task"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
+
+[[package]]
+name = "futures-util"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "block-padding",
+ "generic-array",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.134"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb"
+
+[[package]]
+name = "lock_api"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "mio"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys",
+]
+
+[[package]]
+name = "modular-bitfield"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74"
+dependencies = [
+ "modular-bitfield-impl",
+ "static_assertions",
+]
+
+[[package]]
+name = "modular-bitfield-impl"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
+[[package]]
+name = "os_str_bytes"
+version = "6.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
+
+[[package]]
+name = "owo-colors"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-sys",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "poly1305"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
+dependencies = [
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "rhexdump"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e9af64574935e39f24d1c0313a997c8b880ca0e087c888bc6af8af31579847"
+
+[[package]]
+name = "rustyline-async"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00e1a02d2c727fac4c73a019de82183d990af0e13d6bed2e3a6f37a525ff591d"
+dependencies = [
+ "crossterm",
+ "futures",
+ "pin-project",
+ "thingbuf",
+ "thiserror",
+ "unicode-segmentation",
+ "unicode-width",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "scrap_net"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "binrw",
+ "chacha20",
+ "clap",
+ "dialoguer",
+ "futures-util",
+ "hex",
+ "itertools",
+ "lazy_static",
+ "modular-bitfield",
+ "poly1305",
+ "rand",
+ "rhexdump",
+ "rustyline-async",
+ "tokio",
+]
+
+[[package]]
+name = "signal-hook"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-mio"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
+dependencies = [
+ "libc",
+ "mio",
+ "signal-hook",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
+
+[[package]]
+name = "socket2"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
+[[package]]
+name = "syn"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "libc",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "terminal_size"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "thingbuf"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bee2d290fe0db0225026350bc1f0bd02b2daad43b09b56780e29e549effcd54a"
+dependencies = [
+ "parking_lot",
+ "pin-project",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio"
+version = "1.21.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "winapi",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "typenum"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "universal-hash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
+dependencies = [
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
+
+[[package]]
+name = "zeroize"
+version = "1.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
diff --git a/tools/remaster/scrap_net/Cargo.toml b/tools/remaster/scrap_net/Cargo.toml
new file mode 100644
index 0000000..0c17ba6
--- /dev/null
+++ b/tools/remaster/scrap_net/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "scrap_net"
+version = "0.1.0"
+edition = "2021"
+authors = ["Daniel Seiller "]
+description = "Scrapland Remastered network sniffer, proxy (and soon hopefully parser)"
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+chacha20 = { version = "0.9", features = ["std"] }
+poly1305 = { version = "0.8", features = ["std"] }
+rhexdump = "0.1"
+tokio = { version = "1.21", features = ["full"] }
+clap = {version = "4.0", features = ["derive"]}
+rand = "0.8"
+dialoguer = "0.10"
+binrw = "0.11"
+modular-bitfield = "0.11"
+hex = "0.4"
+lazy_static = "1.4.0"
+rustyline-async = "0.3"
+futures-util = "0.3.24"
+itertools = "0.10.5"
+anyhow = "1.0.68"
+
+[profile.release]
+lto="fat"
+opt-level = 3
diff --git a/tools/remaster/scrap_net/get_app.py b/tools/remaster/scrap_net/get_app.py
new file mode 100644
index 0000000..2c0cada
--- /dev/null
+++ b/tools/remaster/scrap_net/get_app.py
@@ -0,0 +1,22 @@
+from distutils.command.install_data import install_data
+import winreg as reg
+import vdf
+from pathlib import Path
+import pefile
+app_id="897610"
+try:
+ key = reg.OpenKey(reg.HKEY_LOCAL_MACHINE,"SOFTWARE\\Valve\\Steam")
+except FileNotFoundError:
+ key = reg.OpenKey(reg.HKEY_LOCAL_MACHINE,"SOFTWARE\\Wow6432Node\\Valve\\Steam")
+path=Path(reg.QueryValueEx(key,"InstallPath")[0])
+libraryfolders=vdf.load((path/"steamapps"/"libraryfolders.vdf").open("r"))['libraryfolders']
+for folder in libraryfolders.values():
+ path=Path(folder['path'])
+ if app_id in folder['apps']:
+ install_dir = vdf.load((path/"steamapps"/f"appmanifest_{app_id}.acf").open("r"))['AppState']['installdir']
+ install_dir=path/"steamapps"/"common"/install_dir
+ for file in install_dir.glob("**/*.exe"):
+ pe = pefile.PE(file, fast_load=True)
+ entry = pe.OPTIONAL_HEADER.AddressOfEntryPoint
+ if pe.get_dword_at_rva(entry) == 0xE8:
+ print(file)
\ No newline at end of file
diff --git a/tools/remaster/scrap_net/src/hex_ii.rs b/tools/remaster/scrap_net/src/hex_ii.rs
new file mode 100644
index 0000000..70ddc47
--- /dev/null
+++ b/tools/remaster/scrap_net/src/hex_ii.rs
@@ -0,0 +1,93 @@
+use itertools::Itertools;
+use std::fmt::Display;
+use std::ops::{Deref, DerefMut};
+
+#[derive(Debug, PartialEq, Eq)]
+enum HexII {
+ Ascii(char),
+ Byte(u8),
+ Null,
+ Full,
+ Eof,
+}
+
+impl From<&u8> for HexII {
+ fn from(v: &u8) -> Self {
+ match v {
+ 0x00 => Self::Null,
+ 0xFF => Self::Full,
+ c if c.is_ascii_graphic() => Self::Ascii(*c as char),
+ v => Self::Byte(*v),
+ }
+ }
+}
+
+impl Display for HexII {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ HexII::Ascii(v) => write!(f, ".{}", v)?,
+ HexII::Byte(v) => write!(f, "{:02x}", v)?,
+ HexII::Null => write!(f, " ")?,
+ HexII::Full => write!(f, "##")?,
+ HexII::Eof => write!(f, " ]")?,
+ }
+ Ok(())
+ }
+}
+
+struct HexIILine(Vec);
+
+impl Display for HexIILine {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ for (i, v) in self.0.iter().enumerate() {
+ if i != 0 {
+ write!(f, " ")?;
+ }
+ write!(f, "{}", v)?;
+ }
+ Ok(())
+ }
+}
+
+impl From<&[u8]> for HexIILine {
+ fn from(l: &[u8]) -> Self {
+ Self(l.iter().map(HexII::from).collect())
+ }
+}
+
+impl Deref for HexIILine {
+ type Target = Vec;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for HexIILine {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+pub fn hex_ii_dump>(data: T, base_offset: usize, total: usize) {
+ const CHUNK_SIZE: usize = 0x10;
+ let mut num_digits = (std::mem::size_of_val(&total) * 8) - (total.leading_zeros() as usize);
+ if (num_digits % 8) != 0 {
+ num_digits += 8 - (num_digits % 8)
+ }
+ num_digits >>= 2;
+ for (mut offset, line) in data.chunks(CHUNK_SIZE).into_iter().enumerate() {
+ offset += base_offset;
+ let mut line = HexIILine::from(line.collect::>().as_slice());
+ if line.len() < CHUNK_SIZE {
+ line.push(HexII::Eof);
+ }
+ while line.len() < CHUNK_SIZE {
+ line.push(HexII::Null);
+ }
+ if line.iter().all(|v| v == &HexII::Null) {
+ continue;
+ }
+ let offset = format!("{:digits$x}", offset * CHUNK_SIZE, digits = num_digits);
+ println!("{} | {:<16} |", offset, line);
+ }
+}
diff --git a/tools/remaster/scrap_net/src/main.rs b/tools/remaster/scrap_net/src/main.rs
new file mode 100644
index 0000000..0b8cbd0
--- /dev/null
+++ b/tools/remaster/scrap_net/src/main.rs
@@ -0,0 +1,640 @@
+use anyhow::{bail, ensure, Result};
+use binrw::BinReaderExt;
+use binrw::{BinRead, NullString};
+use chacha20::cipher::KeyInit;
+use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
+use chacha20::ChaCha20;
+use clap::Parser;
+use dialoguer::theme::ColorfulTheme;
+use dialoguer::Select;
+use futures_util::FutureExt;
+use poly1305::Poly1305;
+use rand::{thread_rng, Rng};
+use rhexdump::hexdump;
+use rustyline_async::{Readline, ReadlineError, SharedWriter};
+use std::collections::BTreeMap;
+use std::error::Error;
+use std::fmt::Display;
+use std::io::Cursor;
+use std::io::Write;
+use std::iter;
+use std::net::SocketAddr;
+use std::net::ToSocketAddrs;
+use std::net::{IpAddr, Ipv4Addr};
+use std::path::PathBuf;
+use std::time::{Duration, Instant};
+use tokio::io::AsyncBufReadExt;
+use tokio::net::UdpSocket;
+use tokio::time;
+
+mod hex_ii;
+mod parser;
+
+const KEY: &[u8; 32] = b"\x02\x04\x06\x08\x0a\x0c\x0e\x10\x12\x14\x16\x18\x1a\x1c\x1e\x20\x22\x24\x26\x28\x2a\x2c\x2e\x30\x32\x34\x36\x38\x3a\x3c\x3e\x40";
+const INFO_PACKET: &[u8] = b"\x7f\x01\x00\x00\x07";
+
+#[derive(Debug, Clone)]
+struct ServerFlags {
+ dedicated: bool,
+ force_vehicle: bool,
+ _rest: u8,
+}
+
+impl Display for ServerFlags {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let force_vehicle = if self.force_vehicle { "F" } else { " " };
+ let dedicated = if self.dedicated { "D" } else { " " };
+ write!(f, "{}{}", force_vehicle, dedicated)?;
+ Ok(())
+ }
+}
+
+impl From for ServerFlags {
+ fn from(v: u8) -> Self {
+ ServerFlags {
+ dedicated: v & 0b1 != 0,
+ force_vehicle: v & 0b10 != 0,
+ _rest: (v & 0b11111100) >> 2,
+ }
+ }
+}
+
+#[derive(Debug, Clone, BinRead)]
+#[br(little, magic = b"\xba\xce", import(rtt: Duration, addr: SocketAddr))]
+pub struct Server {
+ #[br(calc=addr)]
+ addr: SocketAddr,
+ #[br(calc=rtt)]
+ rtt: Duration,
+ #[br(map = |v: (u8,u8)| format!("{}.{}",v.0,v.1))]
+ version: String,
+ port: u16,
+ max_players: u16,
+ cur_players: u16,
+ #[br(map = u8::into)]
+ flags: ServerFlags,
+ #[br(pad_size_to(0x20), map = |s :NullString| s.to_string())]
+ name: String,
+ #[br(pad_size_to(0x10), map = |s :NullString| s.to_string())]
+ mode: String,
+ #[br(pad_size_to(0x20), map = |s :NullString| s.to_string())]
+ map: String,
+ _pad: u8,
+}
+
+fn pad_copy(d: &[u8], l: usize) -> Vec {
+ let diff = d.len() % l;
+ if diff != 0 {
+ d.iter()
+ .copied()
+ .chain(iter::repeat(0).take(l - diff))
+ .collect()
+ } else {
+ d.to_vec()
+ }
+}
+
+fn pad(d: &mut Vec, l: usize) {
+ let diff = d.len() % l;
+ if diff != 0 {
+ d.extend(iter::repeat(0).take(l - diff))
+ }
+}
+
+struct Packet {
+ nonce: Vec,
+ data: Vec,
+}
+
+impl Packet {
+ fn encrypt(data: &[u8]) -> Packet {
+ let mut data: Vec = data.to_vec();
+ let mut rng = thread_rng();
+ let mut nonce = vec![0u8; 12];
+ rng.fill(nonce.as_mut_slice());
+ let mut cipher = ChaCha20::new(KEY.into(), nonce.as_slice().into());
+ cipher.seek(KEY.len() + 32);
+ cipher.apply_keystream(&mut data);
+ Packet { nonce, data }
+ }
+
+ fn get_tag(&self) -> Vec {
+ let mut sign_data = vec![];
+ sign_data.extend(pad_copy(&self.nonce, 16).iter());
+ sign_data.extend(pad_copy(&self.data, 16).iter());
+ sign_data.extend((self.nonce.len() as u64).to_le_bytes().iter());
+ sign_data.extend((self.data.len() as u64).to_le_bytes().iter());
+ let mut cipher = ChaCha20::new(KEY.into(), self.nonce.as_slice().into());
+ let mut poly_key = *KEY;
+ cipher.apply_keystream(&mut poly_key);
+ let signer = Poly1305::new(&poly_key.into());
+ signer.compute_unpadded(&sign_data).into_iter().collect()
+ }
+
+ fn bytes(&self) -> Vec {
+ let mut data = vec![];
+ data.extend(pad_copy(&self.nonce, 16).iter());
+ data.extend(pad_copy(&self.data, 16).iter());
+ data.extend((self.nonce.len() as u64).to_le_bytes().iter());
+ data.extend((self.data.len() as u64).to_le_bytes().iter());
+ data.extend(self.get_tag().iter());
+ data
+ }
+
+ fn decrypt(&self) -> Result> {
+ let mut data = self.data.clone();
+ let mut sign_data = data.clone();
+ pad(&mut sign_data, 16);
+ let mut nonce = self.nonce.clone();
+ pad(&mut nonce, 16);
+ let sign_data = nonce
+ .iter()
+ .chain(sign_data.iter())
+ .chain((self.nonce.len() as u64).to_le_bytes().iter())
+ .chain((self.data.len() as u64).to_le_bytes().iter())
+ .copied()
+ .collect::>();
+ let mut poly_key = *KEY;
+ let mut cipher = ChaCha20::new(KEY.into(), self.nonce.as_slice().into());
+ cipher.apply_keystream(&mut poly_key);
+ let signer = Poly1305::new(&poly_key.into());
+ let signature: Vec = signer.compute_unpadded(&sign_data).into_iter().collect();
+
+ if signature != self.get_tag() {
+ bail!("Invalid signature!");
+ };
+ cipher.seek(poly_key.len() + 32);
+ cipher.apply_keystream(&mut data);
+ Ok(data)
+ }
+}
+
+impl TryFrom<&[u8]> for Packet {
+ type Error = anyhow::Error;
+ fn try_from(data: &[u8]) -> Result {
+ let (mut nonce, data) = data.split_at(16);
+ let (mut data, tag) = data.split_at(data.len() - 16);
+ let nonce_len = u64::from_le_bytes(data[data.len() - 16..][..8].try_into()?) as usize;
+ let data_len = u64::from_le_bytes(data[data.len() - 8..].try_into()?) as usize;
+ data = &data[..data_len];
+ nonce = &nonce[..nonce_len];
+ let pkt = Packet {
+ nonce: nonce.into(),
+ data: data.into(),
+ };
+ if pkt.get_tag() != tag {
+ bail!("Invalid signature!");
+ }
+ Ok(pkt)
+ }
+}
+
+#[derive(Debug, Clone)]
+pub enum ServerEntry {
+ Alive(Server),
+ Dead { addr: SocketAddr, reason: String },
+}
+
+impl Display for ServerEntry {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ ServerEntry::Alive(srv) => write!(
+ f,
+ "[{}] {} ({} {}/{} Players on {}) version {} [{}] RTT: {:?}",
+ srv.addr,
+ srv.name,
+ srv.mode,
+ srv.cur_players,
+ srv.max_players,
+ srv.map,
+ srv.version,
+ srv.flags,
+ srv.rtt
+ ),
+ ServerEntry::Dead { addr, reason } => write!(f, "[{}] (error: {})", addr, reason),
+ }
+ }
+}
+
+fn encrypt(data: &[u8]) -> Vec {
+ Packet::encrypt(data).bytes()
+}
+
+fn decrypt(data: &[u8]) -> Result> {
+ Packet::try_from(data)?.decrypt()
+}
+
+async fn recv_from_timeout(
+ sock: &UdpSocket,
+ buf: &mut [u8],
+ timeout: f64,
+) -> Result<(usize, SocketAddr)> {
+ Ok(time::timeout(Duration::from_secs_f64(timeout), sock.recv_from(buf)).await??)
+}
+
+async fn query_server<'a>(addr: SocketAddr) -> Result {
+ let mut buf = [0; 32 * 1024];
+ let socket = UdpSocket::bind("0.0.0.0:0").await?;
+ socket.connect(addr).await?;
+ let msg = encrypt(INFO_PACKET);
+ let t_start = Instant::now();
+ socket.send(&msg).await?;
+ let size = recv_from_timeout(&socket, &mut buf, 5.0).await?.0;
+ let rtt = t_start.elapsed();
+ let data = decrypt(&buf[..size])?;
+ if !data.starts_with(&[0xba, 0xce]) {
+ // Server Info
+ bail!("Invalid response");
+ }
+ let mut cur = Cursor::new(&data);
+ let info: Server = cur.read_le_args((rtt, addr))?;
+ if info.port != addr.port() {
+ eprint!("[WARN] Port differs for {}: {}", addr, info.port);
+ }
+ if cur.position() != (data.len() as u64) {
+ bail!("Leftover data");
+ }
+ Ok(info)
+}
+
+async fn get_servers(master_addr: &str) -> Result<(Duration, Vec)> {
+ let master_addr: SocketAddr = master_addr.to_socket_addrs()?.next().unwrap();
+ let mut rtt = std::time::Duration::from_secs_f32(0.0);
+ let mut servers = vec![];
+ let mut buf = [0; 32 * 1024];
+ let master = UdpSocket::bind("0.0.0.0:0").await?;
+ master.connect(master_addr).await?;
+ for n in 0..(256 / 32) {
+ let data = format!("Brw={},{}\0", n * 32, (n + 1) * 32);
+ let data = &encrypt(data.as_bytes());
+ let t_start = Instant::now();
+ master.send(data).await?;
+ let size = master.recv(&mut buf).await?;
+ rtt += t_start.elapsed();
+ let data = decrypt(&buf[..size])?;
+ if data.starts_with(b"\0\0\0\0}") {
+ for chunk in data[5..].chunks(6) {
+ if chunk.iter().all(|v| *v == 0) {
+ break;
+ }
+ let port = u16::from_le_bytes(chunk[chunk.len() - 2..].try_into()?);
+ let addr = SocketAddr::from(([chunk[0], chunk[1], chunk[2], chunk[3]], port));
+ let server = match query_server(addr).await {
+ Ok(server) => ServerEntry::Alive(server),
+ Err(err) => ServerEntry::Dead {
+ addr,
+ reason: err.to_string(),
+ },
+ };
+ servers.push(server);
+ }
+ }
+ }
+ rtt = Duration::from_secs_f64(rtt.as_secs_f64() / ((256 / 32) as f64));
+ Ok((rtt, servers))
+}
+
+fn indent_hexdump(data: &[u8], indentation: usize, label: &str) -> String {
+ let mut out = String::new();
+ let indent = " ".repeat(indentation);
+ out.push_str(&indent);
+ out.push_str(label);
+ out.push('\n');
+ for line in rhexdump::hexdump(data).lines() {
+ out.push_str(&indent);
+ out.push_str(line);
+ out.push('\n');
+ }
+ out.trim_end().to_owned()
+}
+
+#[derive(Default, Debug)]
+struct State {
+ client: BTreeMap>,
+ server: BTreeMap>,
+}
+
+impl State {
+ fn update_client(&mut self, data: &[u8]) {
+ data.iter().enumerate().for_each(|(pos, b)| {
+ *self.client.entry(pos).or_default().entry(*b).or_default() += 1;
+ });
+ }
+ fn update_server(&mut self, data: &[u8]) {
+ data.iter().enumerate().for_each(|(pos, b)| {
+ *self.server.entry(pos).or_default().entry(*b).or_default() += 1;
+ });
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Copy, Clone)]
+enum Direction {
+ Client,
+ Server,
+ Both,
+}
+
+#[derive(Debug)]
+enum CmdResult {
+ Exit,
+ Packet {
+ data: Vec,
+ direction: Direction,
+ },
+ Fuzz {
+ direction: Direction,
+ start: usize,
+ end: usize,
+ chance: (u32, u32),
+ },
+ NoFuzz,
+ Log(bool),
+}
+
+async fn handle_line(
+ line: &str,
+ state: &State,
+ stdout: &mut SharedWriter,
+) -> Result> {
+ use CmdResult::*;
+ let cmd: Vec<&str> = line.trim().split_ascii_whitespace().collect();
+ match cmd[..] {
+ ["log", "off"] => Ok(Some(Log(false))),
+ ["log", "on"] => Ok(Some(Log(true))),
+ ["state", pos] => {
+ let pos = pos.parse()?;
+ writeln!(stdout, "Client: {:?}", state.client.get(&pos))?;
+ writeln!(stdout, "Server: {:?}", state.server.get(&pos))?;
+ Ok(None)
+ }
+ [dir @ ("client" | "server"), ref args @ ..] => {
+ let mut data: Vec = vec![];
+ for args in args.iter() {
+ let args = hex::decode(args)?;
+ data.extend(args);
+ }
+ Ok(Some(CmdResult::Packet {
+ data,
+ direction: match dir {
+ "client" => Direction::Client,
+ "server" => Direction::Server,
+ _ => unreachable!(),
+ },
+ }))
+ }
+ ["fuzz", dir @ ("client" | "server" | "both"), start, end, chance_num, chance_den] => {
+ let direction = match dir {
+ "client" => Direction::Client,
+ "server" => Direction::Server,
+ "both" => Direction::Both,
+ _ => unreachable!(),
+ };
+ let start = start.parse()?;
+ let end = end.parse()?;
+ if start > end {
+ bail!("Fuzz start>end");
+ }
+ let res = CmdResult::Fuzz {
+ direction,
+ start,
+ end,
+ chance: (chance_num.parse()?, chance_den.parse()?),
+ };
+ Ok(Some(res))
+ }
+ ["fuzz", "off"] => Ok(Some(CmdResult::NoFuzz)),
+ ["exit"] => Ok(Some(CmdResult::Exit)),
+ [""] => Ok(None),
+ _ => bail!("Unknown command: {:?}", line),
+ }
+}
+
+async fn run_proxy(
+ remote_addr: &SocketAddr,
+ local_addr: &SocketAddr,
+ logfile: &Option,
+) -> Result<()> {
+ let mut print_log = false;
+ let mut state = State::default();
+ let mut logfile = match logfile {
+ Some(path) => Some(std::fs::File::create(path)?),
+ None => None,
+ };
+ let mut fuzz = None;
+ let mut rng = thread_rng();
+ let mut client_addr: Option = None;
+ let local = UdpSocket::bind(local_addr).await?;
+ let remote = UdpSocket::bind("0.0.0.0:0").await?;
+ remote.connect(remote_addr).await?;
+ let mut local_buf = vec![0; 32 * 1024];
+ let mut remote_buf = vec![0; 32 * 1024];
+ println!("Proxy listening on {}", local_addr);
+ let (mut rl, mut stdout) = Readline::new(format!("{}> ", remote_addr)).unwrap();
+ loop {
+ tokio::select! {
+ line = rl.readline().fuse() => {
+ match line {
+ Ok(line) => {
+ let line=line.trim();
+ rl.add_history_entry(line.to_owned());
+ match handle_line(line, &state, &mut stdout).await {
+ Ok(Some(result)) => {
+ match result {
+ CmdResult::Packet{data,direction} => {
+ let data=encrypt(&data);
+ match direction {
+ Direction::Client => {
+ if client_addr.is_some() {
+ local
+ .send_to(&data, client_addr.unwrap())
+ .await?;
+ } else {
+ writeln!(stdout,"Error: No client address")?;
+ }
+ },
+ Direction::Server => {
+ remote.send(&data).await?;
+ }
+ Direction::Both => unreachable!()
+ };
+ }
+ CmdResult::Log(log) => {
+ print_log=log;
+ }
+ CmdResult::Exit => break Ok(()),
+ CmdResult::NoFuzz => {
+ fuzz=None;
+ }
+ CmdResult::Fuzz { .. } => {
+ fuzz=Some(result)
+ },
+ }
+ },
+ Ok(None) => (),
+ Err(msg) => {
+ writeln!(stdout, "Error: {}", msg)?;
+ }
+ }
+ },
+ Err(ReadlineError::Eof) =>{ writeln!(stdout, "Exiting...")?; break Ok(()) },
+ Err(ReadlineError::Interrupted) => {
+ writeln!(stdout, "^C")?;
+ break Ok(());
+ },
+ Err(err) => {
+ writeln!(stdout, "Received err: {:?}", err)?;
+ writeln!(stdout, "Exiting...")?;
+ break Ok(());
+ }
+ }
+ }
+ local_res = local.recv_from(&mut local_buf) => {
+ let (size, addr) = local_res?;
+ client_addr.get_or_insert(addr);
+ let mut data = Packet::try_from(&local_buf[..size])?.decrypt()?;
+ state.update_client(&data);
+ if print_log {
+ writeln!(stdout,"{}", indent_hexdump(&data, 0, &format!("OUT: {}", addr)))?;
+ }
+ if let Some(lf) = logfile.as_mut() {
+ writeln!(lf, ">{:?} {} {}", addr, data.len(), hex::encode(&data))?;
+ };
+ if let Some(CmdResult::Fuzz{direction,start,end,chance}) = fuzz {
+ if (direction==Direction::Server || direction==Direction::Both) && rng.gen_ratio(chance.0,chance.1) {
+ rng.fill(&mut data[start..end]);
+ }
+ }
+ remote.send(&encrypt(&data)).await?;
+ }
+ remote_res = remote.recv_from(&mut remote_buf) => {
+ let (size, addr) = remote_res?;
+ let mut data = Packet::try_from(&remote_buf[..size])?.decrypt()?;
+ state.update_server(&data);
+ if print_log {
+ writeln!(stdout,"\r{}", indent_hexdump(&data, 5, &format!("IN: {}", addr)))?;
+ }
+ if let Some(lf) = logfile.as_mut() {
+ writeln!(lf, "<{:?} {} {}", addr, data.len(), hex::encode(&data))?;
+ };
+ if client_addr.is_some() {
+ if let Some(CmdResult::Fuzz{direction,start,end,chance}) = &fuzz {
+ if (*direction==Direction::Client || *direction==Direction::Both) && rng.gen_ratio(chance.0,chance.1) {
+ rng.fill(&mut data[*start..*end]);
+ }
+ }
+ local
+ .send_to(&encrypt(&data), client_addr.unwrap())
+ .await?;
+ }
+ }
+ }
+ }
+}
+
+async fn send_master_cmd(sock: &UdpSocket, cmd: &str) -> Result> {
+ let mut buf = [0; 32 * 1024];
+ let mut data: Vec = cmd.as_bytes().to_vec();
+ data.push(0);
+ let data = &encrypt(&data);
+ sock.send(data).await?;
+ let size = recv_from_timeout(sock, &mut buf, 5.0).await?.0;
+ decrypt(&buf[..size])
+}
+
+async fn run_master_shell(master_addr: &str) -> Result<()> {
+ let master = UdpSocket::bind("0.0.0.0:0").await?;
+ master.connect(master_addr).await?;
+ let (mut rl, mut stdout) = Readline::new(format!("{}> ", master_addr)).unwrap();
+ loop {
+ tokio::select! {
+ line = rl.readline().fuse() => {
+ match line {
+ Ok(line) => {
+ let line=line.trim();
+ rl.add_history_entry(line.to_owned());
+ writeln!(stdout,"[CMD] {line}")?;
+ match send_master_cmd(&master,line).await {
+ Ok(data) => writeln!(stdout,"{}",hexdump(&data))?,
+ Err(e) => writeln!(stdout,"Error: {e}")?
+ }
+ }
+ Err(ReadlineError::Eof) =>{ writeln!(stdout, "Exiting...")?; break Ok(()) },
+ Err(ReadlineError::Interrupted) => {
+ writeln!(stdout, "^C")?;
+ break Ok(());
+ },
+ Err(err) => {
+ writeln!(stdout, "Receive error: {err}")?;
+ break Err(err.into());
+ }
+ }
+ }
+ }
+ }
+}
+
+#[derive(Parser, Debug)]
+#[clap(author, version, about, long_about = None)]
+struct Args {
+ /// Server to connect to (if unspecified will query the master server)
+ server: Option,
+ /// Only list servers without starting proxy
+ #[clap(short, long, action)]
+ list: bool,
+ /// Local Address to bind to
+ #[clap(short,long, default_value_t = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 28086))]
+ addr: SocketAddr,
+ /// Master server to query for running games
+ #[clap(short, long, default_value = "scrapland.mercurysteam.com:5000")]
+ master: String,
+ /// Path of file to log decrypted packets to
+ #[clap(short = 'f', long)]
+ logfile: Option,
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ let args = Args::parse();
+ if args.list && args.server.is_some() {
+ let addr = args.server.unwrap();
+ let server = match query_server(addr).await {
+ Ok(server) => ServerEntry::Alive(server),
+ Err(msg) => ServerEntry::Dead {
+ addr,
+ reason: msg.to_string(),
+ },
+ };
+ println!("{}", server);
+ return Ok(());
+ }
+ if let Some(server) = args.server {
+ run_proxy(&server, &args.addr, &args.logfile).await?;
+ return Ok(());
+ }
+ loop {
+ let (rtt, servers) = get_servers(&args.master).await?;
+ println!("Master RTT: {:?}", rtt);
+ if args.list {
+ for server in servers {
+ println!("{}", server);
+ }
+ return Ok(());
+ }
+ let selection = Select::with_theme(&ColorfulTheme::default())
+ .items(&servers)
+ .with_prompt("Select server (press Esc to drop into master server command shell)")
+ .interact_opt()?
+ .map(|v| &servers[v]);
+ match selection {
+ Some(ServerEntry::Dead { addr, reason }) => {
+ eprintln!("{:?} returned an error: {}", addr, reason)
+ }
+ Some(ServerEntry::Alive(srv)) => {
+ return run_proxy(&srv.addr, &args.addr, &args.logfile).await;
+ }
+ None => {
+ return run_master_shell(&args.master).await;
+ }
+ }
+ }
+}
diff --git a/tools/remaster/scrap_net/src/parser.rs b/tools/remaster/scrap_net/src/parser.rs
new file mode 100644
index 0000000..e8cbcd4
--- /dev/null
+++ b/tools/remaster/scrap_net/src/parser.rs
@@ -0,0 +1,151 @@
+use std::collections::HashMap;
+use std::error::Error;
+
+use crate::hex_ii::hex_ii_dump;
+use crate::ServerFlags;
+use binrw::BinReaderExt;
+use binrw::{binread, BinRead, NullString};
+
+/*
+00000000: 7f 4c 00 00 06 ba ce 01 01 06 63 61 63 6f 74 61 | .L........cacota
+00000010: 10 5b 42 4a 5a 5d 20 45 61 72 74 68 6e 75 6b 65 | .[BJZ].Earthnuke
+00000020: 72 06 53 50 6f 6c 69 31 37 00 08 50 5f 50 6f 6c | r.SPoli17..P_Pol
+00000030: 69 63 65 06 4d 50 4f 4c 49 31 00 00 00 0d 30 2c | ice.MPOLI1....0,
+00000040: 30 2c 30 2c 31 2c 30 2c 30 2c 31 00 00 00 00 | 0,0,1,0,0,1....
+
+00000000: 7f 49 00 00 06 ba ce 01 01 06 63 61 63 6f 74 61 | .I........cacota
+00000010: 0e 55 6e 6e 61 6d 65 64 20 50 6c 61 79 65 72 07 | .Unnamed.Player.
+00000020: 53 42 65 74 74 79 31 50 00 07 50 5f 42 65 74 74 | SBetty1P..P_Bett
+00000030: 79 07 4d 42 65 74 74 79 31 00 00 00 0b 31 2c 31 | y.MBetty1....1,1
+00000040: 2c 30 2c 31 2c 33 2c 30 00 00 00 00 | ,0,1,3,0....
+*/
+
+#[derive(Debug, Clone, BinRead)]
+#[br(big)]
+#[br(magic = b"\xba\xce")]
+struct ServerInfoJoin {
+ #[br(map = |v: (u8,u8)| format!("{}.{}",v.0,v.1))]
+ version: String,
+}
+
+struct Data {
+ player_id: u32,
+ num_vals: u32,
+ pos: [f32; 3],
+ player_index: u32,
+ rtt: u32,
+}
+
+#[binread]
+#[br(big)]
+#[derive(Debug, Clone)]
+enum PacketData {
+ #[br(magic = b"\x7f")]
+ PlayerJoin {
+ data_len: u8,
+ _1: u8,
+ cur_players: u8,
+ max_players: u8,
+ info: ServerInfoJoin,
+ #[br(temp)]
+ pw_len: u8,
+ #[br(count = pw_len, map = |bytes: Vec| String::from_utf8_lossy(&bytes).into_owned())]
+ password: String,
+ #[br(temp)]
+ player_name_len: u8,
+ #[br(count = player_name_len, map = |bytes: Vec| String::from_utf8_lossy(&bytes).into_owned())]
+ player_name: String,
+ #[br(temp)]
+ ship_model_len: u8,
+ #[br(count = ship_model_len, map = |bytes: Vec| String::from_utf8_lossy(&bytes).into_owned())]
+ ship_model: String,
+ #[br(little)]
+ max_health: u16,
+ #[br(temp)]
+ pilot_model_len: u8,
+ #[br(count = pilot_model_len, map = |bytes: Vec| String::from_utf8_lossy(&bytes).into_owned())]
+ pilot_model: String,
+ #[br(temp)]
+ engine_model_r_len: u8,
+ #[br(count = engine_model_r_len, map = |bytes: Vec| String::from_utf8_lossy(&bytes).into_owned())]
+ engine_model_r: String,
+ #[br(temp)]
+ engine_model_l_len: u8,
+ #[br(count = engine_model_r_len, map = |bytes: Vec| String::from_utf8_lossy(&bytes).into_owned())]
+ engine_model_l: String,
+ _2: u16,
+ #[br(temp)]
+ loadout_len: u8,
+ #[br(count = loadout_len, map = |bytes: Vec| String::from_utf8_lossy(&bytes).into_owned())]
+ loadout: String,
+ team_number: u16,
+ padding: [u8; 2],
+ },
+ #[br(magic = b"\x80\x15")]
+ MapInfo {
+ #[br(temp)]
+ map_len: u32,
+ #[br(count = map_len, map = |bytes: Vec| String::from_utf8_lossy(&bytes).into_owned())]
+ map: String,
+ #[br(temp)]
+ mode_len: u8,
+ #[br(count = mode_len, map = |bytes: Vec| String::from_utf8_lossy(&bytes).into_owned())]
+ mode: String,
+ _2: u16,
+ item_count: u8,
+ // _3: u32,
+ // #[br(count = item_count)]
+ // items: Vec<[u8;0x11]>
+ },
+ #[br(magic = b"\xba\xce")]
+ ServerInfo {
+ #[br(map = |v: (u8,u8)| format!("{}.{}",v.1,v.0))]
+ version: String,
+ port: u16,
+ max_players: u16,
+ cur_players: u16,
+ #[br(map = u8::into)]
+ flags: ServerFlags,
+ #[br(pad_size_to(0x20), map=|s: NullString| s.to_string())]
+ name: String,
+ #[br(pad_size_to(0x10), map=|s: NullString| s.to_string())]
+ mode: String,
+ #[br(pad_size_to(0x20), map=|s: NullString| s.to_string())]
+ map: String,
+ _pad: u8,
+ },
+}
+
+fn parse(data: &[u8]) -> Result<(PacketData, Vec), Box> {
+ use std::io::Cursor;
+ let mut rdr = Cursor::new(data);
+ let pkt: PacketData = rdr.read_le()?;
+ let rest = data[rdr.position() as usize..].to_vec();
+ println!("{}", rhexdump::hexdump(data));
+ Ok((pkt, rest))
+}
+
+#[test]
+fn test_parser() {
+ let log = include_str!("../test_.log").lines();
+ let mut hm = HashMap::new();
+ for line in log {
+ let data = line.split_ascii_whitespace().nth(1).unwrap();
+ let data = hex::decode(data).unwrap();
+ *hm.entry(data[0..1].to_vec()).or_insert(0usize) += 1;
+ match parse(&data) {
+ Ok((pkt, rest)) => {
+ println!("{:#x?}", pkt);
+ }
+ Err(e) => (),
+ }
+ }
+ let mut hm: Vec<(_, _)> = hm.iter().collect();
+ hm.sort_by_key(|(_, v)| *v);
+ for (k, v) in hm {
+ let k = k.iter().map(|v| format!("{:02x}", v)).collect::();
+ println!("{} {}", k, v);
+ }
+ // println!("{:#x?}",parse("8015000000094c6576656c732f465a08466c616748756e7400000100000000000000000000000000000000000004105feb0006003e1125f3bc1300000019007e9dfa0404d5f9003f00000000000000000000"));
+ // println!("{:#x?}",parse("8015000000094c6576656c732f465a08466c616748756e7400002000000000000000000000000000000000000004105feb0006003e1125f3bc1300000019007e9dfa0404d5f9003f000000000000000000001f020b0376a8e2475b6e5b467c1e99461e020903982d14c5ec79cb45b2ee96471d020e03b29dbc46caa433464a28a0c71c020603aa80514658b8ab458db025c71b020803ce492f4658b8ab4514d320c71a02070344532f4658b8ab4587cf16c7190205031b3a0d4658b8ab459eaf25c7180206030ac34c4669e1fd469891ca47170208032e8c2a4669e1fd465500cd4716020703a4952a4669e1fd461b02d247150205037b7c084669e1fd460f92ca4714020603da6b7ec714aa3746b77c5a4713020803c87c83c714aa3746305a5f47120207039a7b83c714aa3746bd5d694711020503bfbe87c714aa3746a67d5a4710020803c5c719474ad5d445a7b3d2c60f0206037c5522474ad5d4459a6edcc60e02070323ca19474ad5d4458dacbec60d020503d84311474ad5d445bb6cdcc60c020603a9b16b47d52d974602dd15470b020803f2236347d52d97467bba1a470a02070350266347d52d974608be24470902050305a05a47d52d9746f1dd1547080206031f4066c6384b9c46955bd345070208037e3b84c6384b9c466147fa4506020703c33684c6384b9c46e431254605020503574395c6384b9c461063d34504020603ba349bc77a60294640f387c103020803957b9fc77a602946658f994402020703677a9fc77a60294680006d45010205038cbda3c77a602946807880c1"));
+}
diff --git a/tools/remaster/scrap_parse/.gitignore b/tools/remaster/scrap_parse/.gitignore
new file mode 100644
index 0000000..758f49c
--- /dev/null
+++ b/tools/remaster/scrap_parse/.gitignore
@@ -0,0 +1,177 @@
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+*.pkl.gz
\ No newline at end of file
diff --git a/tools/remaster/scrap_parse/Cargo.lock b/tools/remaster/scrap_parse/Cargo.lock
new file mode 100644
index 0000000..533cb1d
--- /dev/null
+++ b/tools/remaster/scrap_parse/Cargo.lock
@@ -0,0 +1,1275 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6342bd4f5a1205d7f41e94a41a901f5647c938cdfa96036338e8533c9d6c2450"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is-terminal",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+dependencies = [
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
+dependencies = [
+ "anstyle",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
+
+[[package]]
+name = "array-init"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "binrw"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "272caaf6e0bfb7d508c0606e541e2c68f85c0d6352b62d0b299924eed59fe384"
+dependencies = [
+ "array-init",
+ "binrw_derive",
+ "bytemuck",
+]
+
+[[package]]
+name = "binrw_derive"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb4b28c1e534d96213c8966bb9240095757aa0909128985f97d16afd2e7257a8"
+dependencies = [
+ "either",
+ "owo-colors",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
+
+[[package]]
+name = "bytemuck"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-integer",
+ "num-traits",
+ "serde",
+ "time",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "clap"
+version = "4.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+ "once_cell",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "bitflags",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1"
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "configparser"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5458d9d1a587efaf5091602c59d299696a3877a439c8f6d461a2d3cce11df87a"
+dependencies = [
+ "indexmap",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "cxx"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "dirs"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b"
+dependencies = [
+ "libc",
+ "redox_users",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "either"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
+
+[[package]]
+name = "errno"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fs-err"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541"
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+dependencies = [
+ "cxx",
+ "cxx-build",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+ "serde",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
+dependencies = [
+ "hermit-abi",
+ "io-lifetimes",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "iter-read"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c397ca3ea05ad509c4ec451fea28b4771236a376ca1c69fd5143aae0cf8f93c4"
+
+[[package]]
+name = "itoa"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
+
+[[package]]
+name = "js-sys"
+version = "0.3.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "keyvalues-parser"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d990301996c856ea07a84bc291e76f1273db52683663efc05c8d355976897e5"
+dependencies = [
+ "pest",
+ "pest_derive",
+ "thiserror",
+]
+
+[[package]]
+name = "keyvalues-serde"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da419ac133bb3ddf0dbf9c12fcc0ce01d994fcb65f6f1713faf15cc689320b5f"
+dependencies = [
+ "keyvalues-parser",
+ "once_cell",
+ "paste",
+ "regex",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.142"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "modular-bitfield"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74"
+dependencies = [
+ "modular-bitfield-impl",
+ "static_assertions",
+]
+
+[[package]]
+name = "modular-bitfield-impl"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "nom"
+version = "1.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce"
+
+[[package]]
+name = "num-bigint"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "obj"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "059c95245738cdc7b40078cdd51a23200252a4c0a0a6dd005136152b3f467a4a"
+
+[[package]]
+name = "once_cell"
+version = "1.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+
+[[package]]
+name = "owo-colors"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
+
+[[package]]
+name = "paste"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
+
+[[package]]
+name = "pest"
+version = "2.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b1403e8401ad5dedea73c626b99758535b342502f8d1e361f4a2dd952749122"
+dependencies = [
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be99c4c1d2fc2769b1d00239431d711d08f6efedcecb8b6e30707160aee99c15"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e56094789873daa36164de2e822b3888c6ae4b4f9da555a1103587658c805b1e"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6733073c7cff3d8459fda0e42f13a047870242aed8b509fe98000928975f359e"
+dependencies = [
+ "once_cell",
+ "pest",
+ "sha2",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
+dependencies = [
+ "getrandom",
+ "redox_syscall",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
+
+[[package]]
+name = "rhexdump"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e9af64574935e39f24d1c0313a997c8b880ca0e087c888bc6af8af31579847"
+
+[[package]]
+name = "rustix"
+version = "0.37.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scrap_parse"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "binrw",
+ "chrono",
+ "clap",
+ "configparser",
+ "flate2",
+ "fs-err",
+ "indexmap",
+ "modular-bitfield",
+ "obj",
+ "rhexdump",
+ "serde",
+ "serde-pickle",
+ "serde_json",
+ "steamlocate",
+ "walkdir",
+]
+
+[[package]]
+name = "scratch"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
+
+[[package]]
+name = "serde"
+version = "1.0.160"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-pickle"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c762ad136a26407c6a80825813600ceeab5e613660d93d79a41f0ec877171e71"
+dependencies = [
+ "byteorder",
+ "iter-read",
+ "num-bigint",
+ "num-traits",
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.160"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
+dependencies = [
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "steamlocate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec01c74611d14a808cb212d17c6e03f0e30736a15ed1d5736f8a53154cea3ae"
+dependencies = [
+ "dirs",
+ "keyvalues-parser",
+ "keyvalues-serde",
+ "serde",
+ "steamy-vdf",
+ "winreg",
+]
+
+[[package]]
+name = "steamy-vdf"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "533127ad49314bfe71c3d3fd36b3ebac3d24f40618092e70e1cfe8362c7fac79"
+dependencies = [
+ "nom",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.15",
+]
+
+[[package]]
+name = "time"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
+dependencies = [
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+ "winapi",
+]
+
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "walkdir"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.10.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.84"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
+dependencies = [
+ "windows-targets 0.48.0",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.0",
+ "windows_aarch64_msvc 0.48.0",
+ "windows_i686_gnu 0.48.0",
+ "windows_i686_msvc 0.48.0",
+ "windows_x86_64_gnu 0.48.0",
+ "windows_x86_64_gnullvm 0.48.0",
+ "windows_x86_64_msvc 0.48.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+
+[[package]]
+name = "winreg"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
diff --git a/tools/remaster/scrap_parse/Cargo.toml b/tools/remaster/scrap_parse/Cargo.toml
new file mode 100644
index 0000000..77904e2
--- /dev/null
+++ b/tools/remaster/scrap_parse/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "scrap_parse"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.69"
+binrw = "0.11.1"
+chrono = { version = "0.4.23", features = ["serde"] }
+# chrono-humanize = "0.2.2"
+clap = { version = "4.1.6", features = ["derive"] }
+configparser = { version = "3.0.2", features = ["indexmap"] }
+flate2 = "1.0.25"
+fs-err = "2.9.0"
+indexmap = { version = "1.9.2", features = ["serde"] }
+# memmap2 = "0.5.10"
+modular-bitfield = "0.11.2"
+rhexdump = "0.1.1"
+serde = { version = "1.0.152", features = ["derive"] }
+serde-pickle = "1.1.1"
+serde_json = { version = "1.0.95", features = ["preserve_order", "unbounded_depth"] }
+steamlocate = "1.1.0"
+walkdir = "2.3.3"
+obj = "0.10.2"
diff --git a/tools/remaster/scrap_parse/blender_plugin/__init__.py b/tools/remaster/scrap_parse/blender_plugin/__init__.py
new file mode 100644
index 0000000..84c8e51
--- /dev/null
+++ b/tools/remaster/scrap_parse/blender_plugin/__init__.py
@@ -0,0 +1,23 @@
+import pickle
+import subprocess as SP
+
+from . import packed_browser
+from . import level_import
+
+def scrap_bridge(*cmd):
+ cmd=["scrap_parse",*cmd]
+ proc=SP.Popen(cmd,stderr=None,stdin=None,stdout=SP.PIPE,shell=True,text=False)
+ stdout,stderr=proc.communicate()
+ code=proc.wait()
+ if code:
+ raise RuntimeError(str(stderr,"utf8"))
+ return pickle.loads(stdout)
+
+def register():
+ packed_browser.register()
+ level_import.regiser()
+
+def unregister():
+ packed_browser.unregister()
+ level_import.unregister()
+
diff --git a/tools/remaster/scrap_parse/blender_plugin/level_import.py b/tools/remaster/scrap_parse/blender_plugin/level_import.py
new file mode 100644
index 0000000..d0ef765
--- /dev/null
+++ b/tools/remaster/scrap_parse/blender_plugin/level_import.py
@@ -0,0 +1,510 @@
+import bpy
+import sys
+import os
+import re
+import gzip
+import pickle
+import argparse
+from glob import glob
+from mathutils import Vector
+from pathlib import Path
+import numpy as np
+import itertools as ITT
+from pprint import pprint
+# from .. import scrap_bridge
+import bmesh
+from bpy.props import StringProperty, BoolProperty
+from bpy_extras.io_utils import ImportHelper
+from bpy.types import Operator
+
+cmdline = None
+if "--" in sys.argv:
+ args = sys.argv[sys.argv.index("--") + 1 :]
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--save", action="store_true")
+ parser.add_argument("file_list", nargs="+")
+ cmdline = parser.parse_args(args)
+
+class ScrapImporter(object):
+ def __init__(self, options):
+ self.unhandled = set()
+ filepath = options.pop("filepath")
+ self.options = options
+ self.model_scale = 1000.0
+ self.spawn_pos = {}
+ self.objects = {}
+ # print("Loading", filepath)
+ # scrapland_path=scrap_bridge("find-scrapland")
+ # print(scrapland_path)
+ # packed_data=scrap_bridge("parse-packed",scrapland_path)
+ # print(packed_data)
+ # get_output(["scrap_parse","parse-file","--stdout",scrapland_path,"levels/temple"])
+ # raise NotImplementedError()
+ with gzip.open(filepath, "rb") as fh:
+ data = pickle.load(fh)
+ self.path = data.pop("path")
+ self.root = data.pop("root")
+ self.config = data.pop("config")
+ self.dummies = data.pop("dummies")["dummies"]
+ self.moredummies = data.pop("moredummies")
+ self.emi = data.pop("emi")
+ self.sm3 = data.pop("sm3")
+
+ def make_empty(self, name, pos, rot=None):
+ empty = bpy.data.objects.new(name, None)
+ empty.empty_display_type = "PLAIN_AXES"
+ empty.empty_display_size = 0.1
+ empty.location = Vector(pos).xzy / self.model_scale
+ if rot:
+ empty.rotation_euler = Vector(rot).xzy
+ empty.name = name
+ empty.show_name = True
+ bpy.context.scene.collection.objects.link(empty)
+
+ def create_tracks(self):
+ points = {}
+ for dummy in self.dummies:
+ if dummy["name"].startswith("DM_Track"):
+ try:
+ _, name, idx = dummy["name"].split("_")
+ except ValueError:
+ continue
+ pos = Vector(dummy["pos"]).xzy / self.model_scale
+ points.setdefault(name, []).append((int(idx), pos))
+ self.dummies=[d for d in self.dummies if not d["name"].startswith("DM_Track")]
+ for name, points in points.items():
+ crv = bpy.data.curves.new(name, "CURVE")
+ crv.dimensions = "3D"
+ crv.path_duration = (
+ (bpy.context.scene.frame_end - bpy.context.scene.frame_start) + 1
+ )
+ crv.twist_mode = "Z_UP"
+ crv.twist_smooth = 1.0
+ spline = crv.splines.new(type="NURBS")
+ spline.points.add(len(points) - 1)
+ spline.use_endpoint_u = True
+ spline.use_cyclic_u = True
+ spline.use_endpoint_v = True
+ spline.use_cyclic_v = True
+ points.sort()
+ for p, (_, co) in zip(spline.points, points):
+ p.co = list(co) + [1.0]
+ obj = bpy.data.objects.new(name, crv)
+ bpy.context.scene.collection.objects.link(obj)
+
+ def create_dummies(self):
+ for dummy in self.dummies:
+ self.make_empty(dummy["name"], dummy["pos"], dummy["rot"])
+ if dummy["name"].startswith("DM_Player_Spawn"):
+ self.spawn_pos[dummy["name"]] = dummy["pos"]
+ for name, dummy in self.moredummies.items():
+ if not "Pos" in dummy:
+ continue
+ pos = list(float(v) for v in dummy["Pos"])
+ rot = [0, 0, 0]
+ if "Rot" in dummy:
+ rot = list(float(v) for v in dummy["Rot"])
+ self.make_empty(name, pos, rot)
+
+ def add_light(self, name, node):
+ light = bpy.data.lights.new(name, "POINT")
+ light.energy = 100
+ r = node["unk_10"][0] / 255 # *(node['unk_10'][3]/255)
+ g = node["unk_10"][1] / 255 # *(node['unk_10'][3]/255)
+ b = node["unk_10"][2] / 255 # *(node['unk_10'][3]/255)
+ light.color = (r, g, b)
+ light = bpy.data.objects.new(name, light)
+ light.location = Vector(node["pos"]).xzy / self.model_scale
+ light.rotation_euler = Vector(node["rot"]).xzy
+ bpy.context.scene.collection.objects.link(light)
+
+ def create_nodes(self):
+ for node in self.sm3["scene"].get("nodes",[]):
+ node_name = node["name"]
+ node = node.get("content", {})
+ if not node:
+ continue
+ if node["type"] == "Camera":
+ print(f"CAM:{node_name}")
+ pprint(node)
+ elif node["type"] == "Light":
+ print(f"LIGHT:{node_name}")
+ # self.add_light(node_name, node)
+
+ def run(self):
+ self.import_emi()
+ self.join_objects(self.options['merge_objects'])
+ if self.options['create_tracks']:
+ self.create_tracks()
+ if self.options['create_dummies']:
+ self.create_dummies()
+ if self.options['create_nodes']:
+ self.create_nodes()
+ if self.unhandled:
+ print("Unhandled textures:",self.unhandled)
+
+ def join_objects(self, do_merge=False):
+ bpy.ops.object.select_all(action="DESELECT")
+ ctx = {}
+ for name, objs in self.objects.items():
+ if len(objs) <= 1:
+ continue
+ ctx = {
+ "active_object": objs[0],
+ "object": objs[0],
+ "selected_objects": objs,
+ "selected_editable_objects": objs,
+ }
+ with bpy.context.temp_override(**ctx):
+ if do_merge:
+ bpy.ops.object.join()
+ objs[0].name=name
+ else:
+ coll=bpy.data.collections.new(name)
+ bpy.context.scene.collection.children.link(coll)
+ for n,obj in enumerate(objs):
+ obj.name=f"{name}_{n:04}"
+ coll.objects.link(obj)
+ bpy.ops.object.select_all(action="DESELECT")
+
+ def import_emi(self):
+ mats = {0: None}
+ maps = {0: None}
+ for mat in self.emi["materials"]:
+ mats[mat[0]] = mat[1]
+ for tex_map in self.emi["maps"]:
+ maps[tex_map["key"]] = tex_map["data"]
+ for tri in self.emi["tri"]:
+ name = tri["name"]
+ if tri["data"]:
+ tris = tri["data"]["tris"]
+ for n, verts in enumerate(
+ [tri["data"]["verts_1"], tri["data"]["verts_2"]], 1
+ ):
+ if not (tris and verts):
+ continue
+ self.create_mesh(
+ name=f"{name}_{n}",
+ verts=verts,
+ faces=tris,
+ m_map=(tri["data"]["map_key"], maps[tri["data"]["map_key"]]),
+ m_mat=(tri["data"]["mat_key"], mats[tri["data"]["mat_key"]]),
+ )
+
+ def normalize_path(self, path):
+ return path.replace("\\", os.sep).replace("/", os.sep)
+
+ def resolve_path(self, path):
+ file_extensions = [".png", ".bmp", ".dds", ".tga", ".alpha.dds"]
+ root_path = Path(self.normalize_path(self.root).lower())
+ start_folder = Path(self.normalize_path(self.path).lower()).parent
+ try:
+ texture_path = Path(self.config["model"]["texturepath"] + "/")
+ except KeyError:
+ texture_path = None
+ path = Path(path.replace("/", os.sep).lower())
+ if texture_path:
+ folders = ITT.chain(
+ [start_folder],
+ start_folder.parents,
+ [texture_path],
+ texture_path.parents,
+ )
+ else:
+ folders = ITT.chain([start_folder], start_folder.parents)
+ folders=list(folders)
+ print(f"Looking for {path} in {folders}")
+ for folder in folders:
+ for suffix in file_extensions:
+ for dds in [".", "dds"]:
+ resolved_path = (
+ root_path / folder / path.parent / dds / path.name
+ ).with_suffix(suffix)
+ if resolved_path.exists():
+ return str(resolved_path)
+ print(f"Failed to resolve {path}")
+ return None
+
+ def get_input(self, node, name, dtype):
+ return list(filter(lambda i: (i.type, i.name) == (dtype, name), node.inputs))
+
+
+ def build_material(self, mat_key, mat_def, map_def):
+ mat_props = dict(m.groups() for m in re.finditer(r"\(\+(\w+)(?::(\w*))?\)",mat_key))
+ for k,v in mat_props.items():
+ mat_props[k]=v or True
+ skip_names = ["entorno", "noise_trazado", "noise128", "pbasicometal"]
+ overrides = {
+ "zonaautoiluminada-a.dds" : {
+ # "light"
+ },
+ "flecha.000.dds": {
+ "shader": "hologram"
+ },
+ "mayor.000.dds": {
+ "shader": "hologram"
+ },
+ }
+ settings = {
+ "Emission Strength": 10.0,
+ "Specular": 0.0,
+ "Roughness": 0.0,
+ "Metallic": 0.0,
+ }
+ transparent_settings = {
+ "Transmission": 1.0,
+ "Transmission Roughness": 0.0,
+ "IOR": 1.0,
+ }
+ glass_settings = {
+ "Base Color": ( .8, .8, .8, 1.0),
+ "Metallic": 0.2,
+ "Roughness": 0.0,
+ "Specular": 0.2,
+ }
+ tex_slot_map={
+ "base": "Base Color",
+ "metallic":"Metallic",
+ "unk_1":None, # "Clearcoat" ? env map?
+ "bump":"Normal",
+ "glow":"Emission"
+ }
+
+ mat = bpy.data.materials.new(mat_key)
+ mat.use_nodes = True
+ node_tree = mat.node_tree
+ nodes = node_tree.nodes
+ imgs = {}
+ animated_textures={}
+ is_transparent = True
+ print(map_def)
+ if map_def[0]:
+ print("=== MAP[0]:",self.resolve_path(map_def[0]))
+ if map_def[2]:
+ print("=== MAP[2]:",self.resolve_path(map_def[2]))
+ for slot,tex in mat_def["maps"].items():
+ slot=tex_slot_map.get(slot)
+ if (slot is None) and tex:
+ self.unhandled.add(tex["texture"])
+ print(f"Don't know what to do with {tex}")
+ if not (tex and slot):
+ continue
+ tex_file = self.resolve_path(tex["texture"])
+ if tex_file is None:
+ continue
+ tex_name = os.path.basename(tex_file)
+ if ".000." in tex_name:
+ animated_textures[slot]=len(glob(tex_file.replace(".000.",".*.")))
+ mat_props.update(overrides.get(tex_name,{}))
+ if any(
+ tex_name.find(fragment) != -1
+ for fragment in skip_names
+ ):
+ continue
+ else:
+ is_transparent = False
+ imgs[slot]=bpy.data.images.load(tex_file,check_existing=True)
+ for n in nodes:
+ nodes.remove(n)
+ out = nodes.new("ShaderNodeOutputMaterial")
+ out.name = "Output"
+ shader = nodes.new("ShaderNodeBsdfPrincipled")
+ is_transparent|=mat_props.get("shader")=="glass"
+ is_transparent|=mat_props.get("transp") in {"premult","filter"}
+ if is_transparent:
+ settings.update(transparent_settings)
+ if mat_props.get("shader")=="glass":
+ settings.update(glass_settings)
+ for name, value in settings.items():
+ shader.inputs[name].default_value = value
+ for socket,img in imgs.items():
+ img_node = nodes.new("ShaderNodeTexImage")
+ img_node.name = img.name
+ img_node.image = img
+ if socket in animated_textures:
+ img.source="SEQUENCE"
+ num_frames=animated_textures[socket]
+ fps_div = 2 # TODO: read from .emi file
+ drv=img_node.image_user.driver_add("frame_offset")
+ drv.driver.type="SCRIPTED"
+ drv.driver.expression=f"((frame/{fps_div})%{num_frames})-1"
+ img_node.image_user.frame_duration=1
+ img_node.image_user.use_cyclic=True
+ img_node.image_user.use_auto_refresh=True
+ tex_mix_node = nodes.new("ShaderNodeMixRGB")
+ tex_mix_node.blend_type = "MULTIPLY"
+ tex_mix_node.inputs["Fac"].default_value = 0.0
+ node_tree.links.new(
+ img_node.outputs["Color"], tex_mix_node.inputs["Color1"]
+ )
+ node_tree.links.new(
+ img_node.outputs["Alpha"], tex_mix_node.inputs["Color2"]
+ )
+ imgs[socket] = tex_mix_node
+ output_node = tex_mix_node.outputs["Color"]
+ print(img.name, "->", socket)
+ if socket == "Normal":
+ normal_map = nodes.new("ShaderNodeNormalMap")
+ node_tree.links.new(output_node, normal_map.inputs["Color"])
+ output_node = normal_map.outputs["Normal"]
+ normal_map.inputs["Strength"].default_value = 0.4
+ node_tree.links.new(output_node, shader.inputs[socket])
+ shader_out=shader.outputs["BSDF"]
+ if mat_props.get("shader")=="hologram":
+ mix_shader = nodes.new("ShaderNodeMixShader")
+ transp_shader = nodes.new("ShaderNodeBsdfTransparent")
+ mix_in_1 = self.get_input(mix_shader,"Shader","SHADER")[0]
+ mix_in_2 = self.get_input(mix_shader,"Shader","SHADER")[1]
+ node_tree.links.new(transp_shader.outputs["BSDF"], mix_in_1)
+ node_tree.links.new(shader.outputs["BSDF"], mix_in_2)
+ node_tree.links.new(imgs["Base Color"].outputs["Color"],mix_shader.inputs["Fac"])
+ node_tree.links.new(imgs["Base Color"].outputs["Color"],shader.inputs["Emission"])
+ shader.inputs["Emission Strength"].default_value=50.0
+ shader_out=mix_shader.outputs["Shader"]
+ if settings.get("Transmission",0.0)>0.0:
+ light_path = nodes.new("ShaderNodeLightPath")
+ mix_shader = nodes.new("ShaderNodeMixShader")
+ transp_shader = nodes.new("ShaderNodeBsdfTransparent")
+ mix_in_1 = self.get_input(mix_shader,"Shader","SHADER")[0]
+ mix_in_2 = self.get_input(mix_shader,"Shader","SHADER")[1]
+ node_tree.links.new(shader.outputs["BSDF"], mix_in_1)
+ node_tree.links.new(transp_shader.outputs["BSDF"], mix_in_2)
+ node_tree.links.new(light_path.outputs["Is Shadow Ray"], mix_shader.inputs["Fac"])
+ if mat_props.get("transp")=="filter" or mat_props.get("shader")=="glass":
+ node_tree.links.new(imgs["Base Color"].outputs["Color"],transp_shader.inputs["Color"])
+ shader_out=mix_shader.outputs["Shader"]
+ node_tree.links.new(shader_out, out.inputs["Surface"])
+ # try:
+ # bpy.ops.node.button()
+ # except:
+ # pass
+ return mat
+
+ def apply_maps(self, ob, m_mat, m_map):
+ mat_key, m_mat = m_mat
+ map_key, m_map = m_map
+ if mat_key == 0:
+ return
+ mat_name = m_mat.get("name", f"MAT:{mat_key:08X}")
+ if mat_name not in bpy.data.materials:
+ ob.active_material = self.build_material(mat_name, m_mat, m_map)
+ else:
+ ob.active_material = bpy.data.materials[mat_name]
+
+ def create_mesh(self, name, verts, faces, m_mat, m_map):
+ if not verts["inner"]:
+ return
+ me = bpy.data.meshes.new(name)
+ me.use_auto_smooth = True
+ pos = np.array([Vector(v["xyz"]).xzy for v in verts["inner"]["data"]])
+ pos /= self.model_scale
+ me.from_pydata(pos, [], faces)
+ normals = [v["normal"] for v in verts["inner"]["data"]]
+ vcols = [v["diffuse"] for v in verts["inner"]["data"]]
+ if all(normals):
+ normals = np.array(normals)
+ me.vertices.foreach_set("normal", normals.flatten())
+ if not me.vertex_colors:
+ me.vertex_colors.new()
+ for (vcol, vert) in zip(vcols, me.vertex_colors[0].data):
+ vert.color = [vcol["r"], vcol["g"], vcol["b"], vcol["a"]]
+ uvlayers = {}
+ tex = [f"tex_{n+1}" for n in range(8)]
+ for face in me.polygons:
+ for vert_idx, loop_idx in zip(face.vertices, face.loop_indices):
+ vert = verts["inner"]["data"][vert_idx]
+ for tex_name in tex:
+ if not vert[tex_name]:
+ continue
+ if not tex_name in uvlayers:
+ uvlayers[tex_name] = me.uv_layers.new(name=tex_name)
+ u, v = vert[tex_name]
+ uvlayers[tex_name].data[loop_idx].uv = (u, 1.0 - v)
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ if self.options['remove_dup_verts']:
+ bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.0001)
+ bm.to_mesh(me)
+ me.update(calc_edges=True)
+ bm.clear()
+ for poly in me.polygons:
+ poly.use_smooth = True
+ ob = bpy.data.objects.new(name, me)
+ self.apply_maps(ob, m_mat, m_map)
+ bpy.context.scene.collection.objects.link(ob)
+ self.objects.setdefault(name.split("(")[0], []).append(ob)
+ return ob
+
+
+class Scrap_Load(Operator, ImportHelper):
+
+ bl_idname = "scrap_utils.import_pickle"
+ bl_label = "Import Pickle"
+
+ filename_ext = ".pkl.gz"
+ filter_glob: StringProperty(default="*.pkl.gz", options={"HIDDEN"})
+
+ create_dummies: BoolProperty(
+ name="Import dummies",
+ default=True
+ )
+
+ create_nodes: BoolProperty(
+ name="Import nodes (lights, cameras, etc)",
+ default=True
+ )
+
+ create_tracks: BoolProperty(
+ name="Create track curves",
+ default=True
+ )
+
+ merge_objects: BoolProperty(
+ name="Merge objects by name",
+ default=False
+ )
+
+ remove_dup_verts: BoolProperty(
+ name="Remove overlapping vertices\nfor smoother meshes",
+ default=True
+ )
+
+
+ def execute(self, context):
+ bpy.ops.preferences.addon_enable(module = "node_arrange")
+ bpy.ops.outliner.orphans_purge(do_recursive=True)
+ importer = ScrapImporter(self.as_keywords())
+ importer.run()
+ dg = bpy.context.evaluated_depsgraph_get()
+ dg.update()
+ return {"FINISHED"}
+
+
+def register():
+ bpy.utils.register_class(Scrap_Load)
+
+
+def unregister():
+ bpy.utils.unregister_class(Scrap_Load)
+
+
+if __name__ == "__main__":
+ if cmdline is None or not cmdline.file_list:
+ register()
+ bpy.ops.scrap_utils.import_pickle("INVOKE_DEFAULT")
+ else:
+ for file in cmdline.file_list:
+ bpy.context.preferences.view.show_splash = False
+ objs = bpy.data.objects
+ for obj in objs.keys():
+ objs.remove(objs[obj], do_unlink=True)
+ cols=bpy.data.collections
+ for col in cols:
+ cols.remove(col)
+ importer = ScrapImporter(file)
+ importer.run()
+ if cmdline.save:
+ targetpath = Path(file).name.partition(".")[0] + ".blend"
+ targetpath = os.path.abspath(targetpath)
+ print("Saving", targetpath)
+ bpy.ops.wm.save_as_mainfile(filepath=targetpath)
diff --git a/tools/remaster/scrap_parse/blender_plugin/packed_browser.py b/tools/remaster/scrap_parse/blender_plugin/packed_browser.py
new file mode 100644
index 0000000..3981c53
--- /dev/null
+++ b/tools/remaster/scrap_parse/blender_plugin/packed_browser.py
@@ -0,0 +1,118 @@
+import sys
+from .. import scrap_bridge
+from bpy.props import (StringProperty, BoolProperty, CollectionProperty,
+ IntProperty)
+
+bl_info = {
+ "name": "Packed Archive File",
+ "blender": (2, 71, 0),
+ "location": "File > Import",
+ "description": "Import data from Scrapland .packed Archive",
+ "category": "Import-Export"}
+
+
+
+
+class ImportFilearchives(bpy.types.Operator):
+ """Import whole filearchives directory."""
+ bl_idname = "import_scene.packed"
+ bl_label = 'Import Scrapland .packed'
+
+ directory = StringProperty(name="'Scrapland' folder",
+ subtype="DIR_PATH", options={'HIDDEN'})
+ filter_folder = BoolProperty(default=True, options={'HIDDEN'})
+ filter_glob = StringProperty(default="", options={'HIDDEN'})
+
+ def invoke(self, context, event):
+ context.window_manager.fileselect_add(self)
+ return {'RUNNING_MODAL'}
+
+ def execute(self, context):
+ # TODO: Validate filepath
+ bpy.ops.ui.packed_browser('INVOKE_DEFAULT',filepath=self.directory)
+ return {'FINISHED'}
+
+
+class PackedFile(bpy.types.PropertyGroup):
+ path = bpy.props.StringProperty()
+ packed_file = bpy.props.StringProperty()
+ selected = bpy.props.BoolProperty(name="")
+ offset = bpy.props.IntProperty()
+ size = bpy.props.IntProperty()
+
+
+archive = None
+class PackedBrowser(bpy.types.Operator):
+ bl_idname = "ui.packed_browser"
+ bl_label = "Packed Browser"
+ bl_options = {'INTERNAL'}
+
+ files = CollectionProperty(type=PackedFile)
+ selected_index = IntProperty(default=0)
+
+ def invoke(self, context, event):
+ scrapland_path=scrap_bridge("find-scrapland")
+ print(scrapland_path)
+ packed_data=scrap_bridge("parse-packed",scrapland_path)
+ print(packed_data)
+ self.packed_data=packed_data
+ return context.window_manager.invoke_props_dialog(self)
+
+ def draw(self, context):
+ if self.selected_index != -1:
+ print("new selected_index: " + str(self.selected_index))
+ self.files.clear()
+ for packed_name,files in self.archive:
+ for file in files:
+ entry = self.files.add()
+ entry.packed_file = packed_name
+ [entry.path,entry.offset,entry.size]=file
+ self.selected_index = -1
+ self.layout.template_list("PackedDirList", "", self, "current_dir", self, "selected_index")
+
+ def execute(self, context):
+ print("execute")
+ return {'FINISHED'}
+
+
+class PackedDirList(bpy.types.UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ operator = data
+ packed_entry = item
+
+ if self.layout_type in {'DEFAULT', 'COMPACT'}:
+ layout.prop(packed_entry, "name", text="", emboss=False, icon_value=icon)
+ layout.prop(packed_entry, "selected")
+ elif self.layout_type in {'GRID'}:
+ layout.alignment = 'CENTER'
+ layout.label(text="", icon_value=icon)
+
+
+def menu_func_import(self, context):
+ self.layout.operator(ImportFilearchives.bl_idname, text="Scrapland .packed")
+
+classes=[
+ PackedFile,
+ PackedDirList,
+ PackedBrowser,
+ ImportFilearchives,
+]
+
+def register():
+ for cls in classes:
+ bpy.utils.regiser_class(cls)
+ bpy.types.INFO_MT_file_import.append(menu_func_import)
+
+
+def unregister():
+ for cls in reversed(classes):
+ bpy.utils.unregister_class(cls)
+ bpy.types.INFO_MT_file_import.remove(menu_func_import)
+
+
+if __name__ == "__main__":
+ import imp
+ imp.reload(sys.modules[__name__])
+ for cls in classes:
+ bpy.utils.regiser_class(cls)
+
diff --git a/tools/remaster/scrap_parse/get_vertex_size.cpp b/tools/remaster/scrap_parse/get_vertex_size.cpp
new file mode 100644
index 0000000..783eec4
--- /dev/null
+++ b/tools/remaster/scrap_parse/get_vertex_size.cpp
@@ -0,0 +1,66 @@
+
+int _D3DXGetFVFVertexSize(uint fvf)
+
+{
+ uint uVar1;
+ uint uVar2;
+ uint uVar3;
+ int vert_size;
+
+ uVar1 = fvf & 0xe;
+ vert_size = 0;
+ if (uVar1 == 2) {
+ vert_size = 0xc;
+ }
+ else if ((uVar1 == 4) || (uVar1 == 6)) {
+ vert_size = 0x10;
+ }
+ else if (uVar1 == 8) {
+ vert_size = 0x14;
+ }
+ else if (uVar1 == 0xa) {
+ vert_size = 0x18;
+ }
+ else if (uVar1 == 0xc) {
+ vert_size = 0x1c;
+ }
+ else if (uVar1 == 0xe) {
+ vert_size = 0x20;
+ }
+ if ((fvf & 0x10) != 0) {
+ vert_size += 0xc;
+ }
+ if ((fvf & 0x20) != 0) {
+ vert_size += 4;
+ }
+ if ((fvf & 0x40) != 0) {
+ vert_size += 4;
+ }
+ if (fvf < '\0') {
+ vert_size += 4;
+ }
+ uVar1 = fvf >> 8 & 0xf;
+ uVar3 = fvf >> 16;
+ if (uVar3 == 0) {
+ vert_size += uVar1 * 8;
+ }
+ else {
+ for (; uVar1 != 0; uVar1 -= 1) {
+ uVar2 = uVar3 & 3;
+ if (uVar2 == 0) {
+ vert_size += 8;
+ }
+ else if (uVar2 == 1) {
+ vert_size += 0xc;
+ }
+ else if (uVar2 == 2) {
+ vert_size += 0x10;
+ }
+ else if (uVar2 == 3) {
+ vert_size += 4;
+ }
+ uVar3 >>= 2;
+ }
+ }
+ return vert_size;
+}
\ No newline at end of file
diff --git a/tools/remaster/scrap_parse/src/find_scrap.rs b/tools/remaster/scrap_parse/src/find_scrap.rs
new file mode 100644
index 0000000..4c69b3f
--- /dev/null
+++ b/tools/remaster/scrap_parse/src/find_scrap.rs
@@ -0,0 +1,15 @@
+use std::path::PathBuf;
+
+use steamlocate::SteamDir;
+use anyhow::{bail,Result};
+const APP_ID: u32 = 897610;
+
+pub(crate) fn get_executable() -> Result {
+ let Some(mut steam) = SteamDir::locate() else {
+ bail!("Failed to find steam folder");
+ };
+ let Some(app) = steam.app(&APP_ID) else {
+ bail!("App {APP_ID} is not installed!");
+ };
+ Ok(app.path.clone())
+}
\ No newline at end of file
diff --git a/tools/remaster/scrap_parse/src/main.rs b/tools/remaster/scrap_parse/src/main.rs
new file mode 100644
index 0000000..1dfaca3
--- /dev/null
+++ b/tools/remaster/scrap_parse/src/main.rs
@@ -0,0 +1,1068 @@
+#![allow(clippy::upper_case_acronyms, non_camel_case_types)]
+use anyhow::{anyhow, bail, Result};
+use binrw::args;
+use binrw::prelude::*;
+use binrw::until_exclusive;
+use chrono::{DateTime, NaiveDateTime, Utc};
+use clap::Parser;
+use clap::Subcommand;
+use configparser::ini::Ini;
+use flate2::write::GzEncoder;
+use flate2::Compression;
+use fs_err as fs;
+use indexmap::IndexMap;
+use modular_bitfield::bitfield;
+use modular_bitfield::specifiers::B2;
+use modular_bitfield::specifiers::B4;
+use modular_bitfield::BitfieldSpecifier;
+use serde::Serialize;
+use std::collections::HashMap;
+use std::fmt::Debug;
+use std::fs::File;
+use std::io::{BufReader, Cursor, Read, Seek};
+use std::path::Path;
+use std::path::PathBuf;
+use walkdir::WalkDir;
+
+mod find_scrap;
+
+type IniData = IndexMap>>;
+
+#[binread]
+#[derive(Serialize, Debug)]
+struct PackedFile{
+ path: PascalString,
+ size: u32,
+ offset: u32
+}
+
+#[binread]
+#[br(magic = b"BFPK")]
+#[derive(Serialize, Debug)]
+struct PackedHeader {
+ #[br(temp,assert(version==0))]
+ version: u32,
+ #[br(temp)]
+ num_files: u32,
+ #[br(count=num_files)]
+ files: Vec,
+}
+
+#[binread]
+#[derive(Serialize, Debug)]
+#[br(import(msg: &'static str))]
+struct Unparsed {
+ #[br(count=SIZE, try_map=|data: Vec| Err(anyhow!("Unparsed data: {}\n{}", msg, rhexdump::hexdump(&data))))]
+ data: (),
+}
+
+#[binread]
+#[derive(Serialize, Debug)]
+struct RawTable {
+ num_entries: u32,
+ #[br(assert(entry_size==SIZE))]
+ entry_size: u32,
+ #[br(count=num_entries, args {inner: args!{count: entry_size.try_into().unwrap()}})]
+ data: Vec>,
+}
+
+#[binread]
+#[derive(Serialize, Debug)]
+struct Table BinRead = ()> + 'static> {
+ num_entries: u32,
+ entry_size: u32,
+ #[br(count=num_entries)]
+ data: Vec,
+}
+
+// impl BinRead = ()>> Serialize for Table where T: Serialize {
+// fn serialize(&self, serializer: S) -> std::result::Result
+// where
+// S: serde::Serializer {
+// self.data.serialize(serializer)
+// }
+// }
+
+#[binread]
+struct Optional BinRead = ()>> {
+ #[br(temp)]
+ has_value: u32,
+ #[br(if(has_value!=0))]
+ value: Option,
+}
+
+impl BinRead = ()> + Debug> Debug for Optional
+where
+ T: Debug,
+{
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.value.fmt(f)
+ }
+}
+
+impl BinRead = ()> + std::ops::Deref> std::ops::Deref for Optional {
+ type Target = Option;
+
+ fn deref(&self) -> &Self::Target {
+ &self.value
+ }
+}
+
+impl BinRead = ()> + Serialize> Serialize for Optional {
+ fn serialize(&self, serializer: S) -> std::result::Result
+ where
+ S: serde::Serializer,
+ {
+ self.value.serialize(serializer)
+ }
+}
+
+#[binread]
+#[derive(Serialize, Debug)]
+struct Chunk {
+ #[br(map=|c:[u8;4]| c.into_iter().map(|v| v as char).collect())]
+ magic: Vec,
+ size: u32,
+ #[br(temp,count=size)]
+ data: Vec,
+}
+
+#[binread]
+struct PascalString {
+ #[br(temp)]
+ length: u32,
+ #[br(count=length, map=|bytes: Vec| {
+ String::from_utf8_lossy(&bytes.iter().copied().take_while(|&v| v!=0).collect::>()).into_owned()
+ })]
+ string: String,
+}
+
+impl Serialize for PascalString {
+ fn serialize(&self, serializer: S) -> std::result::Result
+ where
+ S: serde::Serializer,
+ {
+ self.string.serialize(serializer)
+ }
+}
+
+impl Debug for PascalString {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ self.string.fmt(f)
+ }
+}
+
+#[binread]
+#[derive(Debug, Serialize)]
+struct IniSection {
+ #[br(temp)]
+ num_lines: u32,
+ #[br(count=num_lines)]
+ sections: Vec,
+}
+
+#[binread]
+#[br(magic = b"INI\0")]
+#[derive(Debug)]
+struct INI {
+ #[br(temp)]
+ size: u32,
+ #[br(temp)]
+ num_sections: u32,
+ #[br(count=num_sections)]
+ sections: Vec,
+}
+
+impl Serialize for INI {
+ fn serialize(&self, serializer: S) -> std::result::Result
+ where
+ S: serde::Serializer,
+ {
+ use serde::ser::Error;
+ let blocks: Vec = self
+ .sections
+ .iter()
+ .flat_map(|s| s.sections.iter())
+ .map(|s| s.string.clone())
+ .collect();
+ Ini::new()
+ .read(blocks.join("\n"))
+ .map_err(Error::custom)?
+ .serialize(serializer)
+ }
+}
+
+#[binread]
+#[derive(Debug, Serialize, Clone)]
+struct RGBA {
+ r: u8,
+ g: u8,
+ b: u8,
+ a: u8,
+}
+
+#[binread]
+#[derive(Debug, Serialize, Clone)]
+#[br(import(n_dims: usize))]
+struct TexCoords(#[br(count=n_dims)] Vec);
+
+#[binread]
+#[derive(Debug, Serialize, Clone)]
+#[br(import(vert_fmt: FVF))]
+// https://github.com/elishacloud/dxwrapper/blob/23ffb74c4c93c4c760bb5f1de347a0b039897210/ddraw/IDirect3DDeviceX.cpp#L2642
+struct Vertex {
+ xyz: [f32; 3],
+ // #[br(if(vert_fmt.pos()==Pos::XYZRHW))] // seems to be unused
+ // rhw: Option,
+ #[br(if(vert_fmt.normal()))]
+ normal: Option<[f32; 3]>,
+ #[br(if(vert_fmt.point_size()))]
+ point_size: Option<[f32; 3]>,
+ #[br(if(vert_fmt.diffuse()))]
+ diffuse: Option,
+ #[br(if(vert_fmt.specular()))]
+ specular: Option,
+ #[br(if(vert_fmt.tex_count()>=1), args (vert_fmt.tex_dims(0),))]
+ tex_1: Option,
+ #[br(if(vert_fmt.tex_count()>=2), args (vert_fmt.tex_dims(1),))]
+ tex_2: Option,
+ #[br(if(vert_fmt.tex_count()>=3), args (vert_fmt.tex_dims(2),))]
+ tex_3: Option,
+ #[br(if(vert_fmt.tex_count()>=4), args (vert_fmt.tex_dims(3),))]
+ tex_4: Option,
+ #[br(if(vert_fmt.tex_count()>=5), args (vert_fmt.tex_dims(4),))]
+ tex_5: Option,
+ #[br(if(vert_fmt.tex_count()>=6), args (vert_fmt.tex_dims(5),))]
+ tex_6: Option,
+ #[br(if(vert_fmt.tex_count()>=7), args (vert_fmt.tex_dims(6),))]
+ tex_7: Option,
+ #[br(if(vert_fmt.tex_count()>=8), args (vert_fmt.tex_dims(7),))]
+ tex_8: Option,
+}
+
+#[derive(Debug, Serialize, PartialEq, Eq, BitfieldSpecifier)]
+#[bits = 3]
+enum Pos {
+ XYZ,
+ XYZRHW,
+ XYZB1,
+ XYZB2,
+ XYZB3,
+ XYZB4,
+ XYZB5,
+}
+
+#[bitfield]
+#[repr(u32)]
+#[derive(Debug, Serialize, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct FVF {
+ reserved: bool,
+ pos: Pos,
+ normal: bool,
+ point_size: bool,
+ diffuse: bool,
+ specular: bool,
+ tex_count: B4,
+ tex_1: B2,
+ tex_2: B2,
+ tex_3: B2,
+ tex_4: B2,
+ tex_5: B2,
+ tex_6: B2,
+ tex_7: B2,
+ tex_8: B2,
+ rest: B4,
+}
+
+impl FVF {
+ fn tex_dims(&self, tex: u8) -> usize {
+ let tex: u8 = match tex {
+ 0 => self.tex_1(),
+ 1 => self.tex_2(),
+ 2 => self.tex_3(),
+ 3 => self.tex_4(),
+ 4 => self.tex_5(),
+ 5 => self.tex_6(),
+ 6 => self.tex_7(),
+ 7 => self.tex_8(),
+ _ => unreachable!(),
+ };
+ match tex {
+ 0 => 2,
+ 1 => 3,
+ 2 => 4,
+ 3 => 1,
+ _ => unreachable!(),
+ }
+ }
+
+ // fn num_w(&self) -> usize {
+ // use Pos::*;
+ // match self.pos() {
+ // XYZ | XYZRHW => 0,
+ // XYZB1 => 1,
+ // XYZB2 => 2,
+ // XYZB3 => 3,
+ // XYZB4 => 4,
+ // XYZB5 => 5,
+ // }
+ // }
+}
+
+fn vertex_size_from_id(fmt_id: u32) -> Result {
+ let fmt_size = match fmt_id {
+ 0 => 0x0,
+ 1 | 8 | 10 => 0x20,
+ 2 => 0x28,
+ 3 | 0xd => 0x1c,
+ 4 | 7 => 0x24,
+ 5 => 0x2c,
+ 6 => 0x34,
+ 0xb => 4,
+ 0xc => 0x18,
+ 0xe => 0x12,
+ 0xf | 0x10 => 0x16,
+ 0x11 => 0x1a,
+ other => bail!("Invalid vertex format id: {other}"),
+ };
+ Ok(fmt_size)
+}
+
+fn vertex_format_from_id(fmt_id: u32, fmt: u32) -> Result {
+ let fvf = match fmt_id {
+ 0 => 0x0,
+ 1 => 0x112,
+ 2 => 0x212,
+ 3 => 0x1c2,
+ 4 => 0x116,
+ 5 => 0x252,
+ 6 => 0x352,
+ 7 => 0x152,
+ 8 => 0x1c4,
+ 10 => 0x242,
+ other => bail!("Invalid vertex format id: {other}"),
+ };
+ if fvf != fmt {
+ bail!("Vertex format mismatch: {fvf}!={fmt}");
+ }
+ Ok(FVF::from(fvf))
+}
+
+#[binread]
+#[br(import(fmt_id: u32))]
+#[derive(Debug, Serialize, Clone)]
+struct LFVFInner {
+ #[br(try_map=|v: u32| vertex_format_from_id(fmt_id,v))]
+ vert_fmt: FVF,
+ #[br(assert(vert_size==vertex_size_from_id(fmt_id).unwrap()))]
+ vert_size: u32,
+ num_verts: u32,
+ #[br(count=num_verts, args {inner: (vert_fmt,)})]
+ data: Vec,
+}
+
+#[binread]
+#[br(magic = b"LFVF")]
+#[derive(Debug, Serialize)]
+struct LFVF {
+ size: u32,
+ #[br(assert(version==1,"invalid LFVF version"))]
+ version: u32,
+ #[br(assert((0..=0x11).contains(&fmt_id),"invalid LFVF format_id"))]
+ fmt_id: u32,
+ #[br(if(fmt_id!=0),args(fmt_id))]
+ inner: Option,
+}
+
+#[binread]
+#[br(magic = b"MD3D")]
+#[derive(Debug, Serialize)]
+struct MD3D {
+ // TODO: mesh
+ size: u32,
+ #[br(assert(version==1,"Invalid MD3D version"))]
+ version: u32,
+ name: PascalString,
+ num_tris: u32,
+ #[br(assert(tri_size==6,"Invalid MD3D tri size"))]
+ tri_size: u32,
+ #[br(count=num_tris)]
+ tris: Vec<[u16; 3]>,
+ mesh_data: LFVF,
+ unk_table_1: RawTable<2>,
+ rest: Unparsed<0x100>
+ // TODO:
+ // ==
+ // unk_t1_count: u32,
+ // #[br(assert(unk_t1_size==2))]
+ // unk_t1_size: u32,
+ // #[br(count=unk_t1_count)]
+ // unk_t1_list: Vec,
+ // // ==
+ // unk_t2_count: u32,
+ // #[br(assert(unk_t1_size==2))]
+ // unk_t2_size: u32,
+ // #[br(count=unk_t1_count)]
+ // unk_t2_list: Vec,
+}
+
+#[binread]
+#[derive(Debug, Serialize)]
+#[serde(tag = "type")]
+enum NodeData {
+ #[br(magic = 0x0u32)]
+ Null,
+ #[br(magic = 0xa1_00_00_01_u32)]
+ TriangleMesh(Unparsed<0x10>), // TODO: Empty or unused?
+ #[br(magic = 0xa1_00_00_02_u32)]
+ Mesh(MD3D),
+ #[br(magic = 0xa2_00_00_04_u32)]
+ Camera(CAM),
+ #[br(magic = 0xa3_00_00_08_u32)]
+ Light(LUZ),
+ #[br(magic = 0xa4_00_00_10_u32)]
+ Ground(SUEL),
+ #[br(magic = 0xa5_00_00_20_u32)]
+ SistPart(Unparsed<0x10>), // TODO: Particles
+ #[br(magic = 0xa6_00_00_40_u32)]
+ Graphic3D(SPR3),
+ #[br(magic = 0xa6_00_00_80_u32)]
+ Flare(Unparsed<0x10>), // TODO: LensFlare?
+ #[br(magic = 0xa7_00_01_00u32)]
+ Portal(PORT),
+}
+
+#[binread]
+#[br(magic = b"SPR3")]
+#[derive(Debug, Serialize)]
+struct SPR3 {
+ size: u32,
+ #[br(assert(version==1,"Invalid SPR3 version"))]
+ version: u32,
+ pos: [f32; 3],
+ unk_1: [u8; 8],
+ name_1: PascalString,
+ name_2: PascalString,
+ unk_2: u32,
+}
+
+#[binread]
+#[br(magic = b"SUEL")]
+#[derive(Debug, Serialize)]
+struct SUEL {
+ size: u32,
+ #[br(assert(version==1,"Invalid SUEL version"))]
+ version: u32,
+ bbox: [[f32; 3]; 2],
+ pos: [f32; 3],
+ unk_3: [u8; 4],
+ num_nodes: u32,
+ unk_4: [u8; 4],
+ bbox_2: [[f32; 3]; 2],
+}
+
+#[binread]
+#[br(magic = b"CAM\0")]
+#[derive(Debug, Serialize)]
+struct CAM {
+ size: u32,
+ #[br(assert(version==1,"Invalid CAM version"))]
+ version: u32,
+ unk_1: [f32; 3],
+ origin: [f32; 3],
+ destination: [f32; 3],
+ unk_4: [u8; 4],
+ unk_5: [u8; 4],
+ unk_6: [u8; 4],
+ unk_7: [u8; 4],
+ unk_8: [u8; 4],
+ unk_9: [u8; 4],
+ unk_10: [u8; 4],
+ unk_11: [u8; 4],
+}
+
+#[binread]
+#[br(magic = b"LUZ\0")]
+#[derive(Debug, Serialize)]
+struct LUZ {
+ size: u32,
+ #[br(assert(version==1,"Invalid LUZ version"))]
+ version: u32,
+ col: u32,
+ brightness: u32,
+ unk_3: u8,
+ pos: [f32; 3],
+ rot: [f32; 3],
+ unk_6: [u8; 8],
+ unk_7: [u8; 4],
+ unk_8: [u8; 4],
+ unk_9: [u8; 4],
+ unk_10: [u8; 4],
+ unk_11: [u8; 4],
+ unk_12: [u8; 4],
+ unk_13: u32,
+}
+
+#[binread]
+#[br(magic = b"PORT")]
+#[derive(Debug, Serialize)]
+struct PORT {
+ size: u32,
+ #[br(assert(version==1,"Invalid PORT version"))]
+ version: u32,
+ width: u32,
+ height: u32,
+ sides: [u32; 2],
+}
+
+#[binread]
+#[derive(Debug, Serialize)]
+struct Node {
+ unk_f17_0x44: u32,
+ unk_f18_0x48: u32,
+ unk_f19_0x4c: u32,
+ flags: u32,
+ unk_f20_0x50: u32,
+ name: PascalString,
+ parent: PascalString,
+ unk_f7_0x1c: [f32; 3], // 0xc
+ unk_f10_0x28: [f32; 4], // 0x10
+ unk_f14_0x38: f32, // 0x4
+ unk_f23_0x5c: [[f32; 4]; 4], // 0x40 4x4 Matrix
+ unk_f39_0x9c: [[f32; 4]; 4], // 0x40 4x4 Matrix
+ unk_f55_0xdc: [f32; 4], // 0x10 Vector?
+ unk_f59_0xec: [f32; 3], // 0xc Vector?
+ node_info: Optional,
+ content: Optional,
+}
+
+#[binread]
+#[br(magic = b"MAP\0")]
+#[derive(Debug, Serialize)]
+struct MAP {
+ size: u32,
+ #[br(assert((2..=3).contains(&version),"invalid MAP version"))]
+ version: u32,
+ texture: PascalString,
+ unk_1: [u8; 7],
+ unk_bbox: [[f32; 2]; 2],
+ unk_2: f32,
+ #[br(if(version==3))]
+ unk_3: Option<[u8; 0xc]>,
+}
+
+#[binread]
+#[derive(Debug, Serialize)]
+struct Textures {
+ base: Optional,
+ metallic: Optional,
+ unk_1: Optional,
+ bump: Optional,
+ glow: Optional
+}
+
+#[binread]
+#[br(magic = b"MAT\0")]
+#[derive(Debug, Serialize)]
+struct MAT {
+ size: u32,
+ #[br(assert((1..=3).contains(&version),"invalid MAT version"))]
+ version: u32,
+ #[br(if(version>1))]
+ name: Option,
+ unk_f: [RGBA; 7],
+ unk_data: [RGBA; 0x18 / 4],
+ maps: Textures
+}
+
+#[binread]
+#[br(magic = b"SCN\0")]
+#[derive(Debug, Serialize)]
+struct SCN {
+ // 0x650220
+ size: u32,
+ #[br(temp,assert(version==1))]
+ version: u32,
+ model_name: PascalString,
+ node_name: PascalString,
+ node_props: Optional,
+ unk_f_1: [f32; (8 + 8) / 4],
+ unk_1: [f32; 0x18 / 4],
+ unk_f_2: f32,
+ user_props: Optional,
+ num_materials: u32,
+ #[br(count=num_materials)]
+ mat: Vec,
+ #[br(temp,assert(unk_3==1))]
+ unk_3: u32,
+ num_nodes: u32,
+ #[br(count = 1)] // 32
+ nodes: Vec,
+ // ani: Optional, // TODO: ?
+}
+
+fn convert_timestamp(dt: u32) -> Result> {
+ let Some(dt) = NaiveDateTime::from_timestamp_opt(dt.into(),0) else {
+ bail!("Invalid timestamp");
+ };
+ Ok(DateTime::from_utc(dt, Utc))
+}
+
+#[binread]
+#[derive(Debug, Serialize)]
+struct VertexAnim {
+ n_tr: u32,
+ maybe_duration: f32,
+ #[br(count=n_tr)]
+ tris: Vec<[u8; 3]>,
+}
+
+#[binread]
+#[br(magic = b"EVA\0")]
+#[derive(Debug, Serialize)]
+struct EVA {
+ size: u32,
+ #[br(assert(version==1,"Invalid EVA version"))]
+ version: u32,
+ num_verts: u32,
+ #[br(count=num_verts)]
+ verts: Vec>,
+}
+
+#[binread]
+#[br(magic = b"NAM\0")]
+#[derive(Debug, Serialize)]
+struct NAM {
+ size: u32,
+ #[br(assert(version==1))]
+ version: u32,
+ primer_frames: u32,
+ frames: u32,
+ #[br(assert(flags&0xffffef60==0,"Invalid NAM flags"))]
+ flags: u32,
+ #[br(assert(opt_flags&0xfff8==0,"Invalid NAM opt_flags"))]
+ opt_flags: u32,
+ #[br(assert(stm_flags&0xfff8==0,"Invalid NAM stm_flags"))]
+ stm_flags: u32,
+ #[br(map=|_:()| flags&(opt_flags|0x8000)&stm_flags)]
+ combined_flags: u32,
+ #[br(if(combined_flags&0x1!=0))]
+ unk_flags_1: Option,
+ #[br(if(combined_flags&0x2!=0))]
+ unk_flags_2: Option,
+ #[br(if(combined_flags&0x4!=0))]
+ unk_flags_3: Option,
+ #[br(if(combined_flags&0x8!=0))]
+ unk_flags_4: Option,
+ #[br(if(combined_flags&0x10!=0))]
+ unk_flags_5: Option,
+ #[br(if(combined_flags&0x80!=0))]
+ unk_flags_6: Option,
+ #[br(if(flags&0x1000!=0))]
+ eva: Option,
+}
+
+#[binread]
+#[br(magic = b"NABK")]
+#[derive(Debug, Serialize)]
+struct NABK {
+ size: u32,
+ #[br(temp,count=size)]
+ data: Vec,
+}
+
+#[binread]
+#[br(magic = b"ANI\0")]
+#[derive(Debug, Serialize)]
+struct ANI {
+ size: u32,
+ #[br(assert(version==2, "Invalid ANI version"))]
+ version: u32,
+ fps: f32,
+ unk_1: u32,
+ unk_2: u32,
+ num_objects: u32,
+ unk_flags: u32,
+ num: u32,
+ #[br(temp,count=num)]
+ data: Vec,
+ nabk: NABK,
+ #[br(count=num_objects)]
+ nam: Vec,
+}
+
+#[binread]
+#[br(magic = b"SM3\0")]
+#[derive(Debug, Serialize)]
+struct SM3 {
+ size: u32,
+ #[br(temp,assert(const_1==0x6515f8,"Invalid timestamp"))]
+ const_1: u32,
+ #[br(try_map=convert_timestamp)]
+ time_1: DateTime,
+ #[br(try_map=convert_timestamp)]
+ time_2: DateTime,
+ scene: SCN,
+}
+
+#[binread]
+#[br(magic = b"CM3\0")]
+#[derive(Debug, Serialize)]
+struct CM3 {
+ size: u32,
+ #[br(temp,assert(const_1==0x6515f8,"Invalid timestamp"))]
+ const_1: u32,
+ #[br(try_map=convert_timestamp)]
+ time_1: DateTime,
+ #[br(try_map=convert_timestamp)]
+ time_2: DateTime,
+ scene: SCN,
+}
+
+#[binread]
+#[derive(Debug, Serialize)]
+struct Dummy {
+ has_next: u32,
+ name: PascalString,
+ pos: [f32; 3],
+ rot: [f32; 3],
+ info: Optional,
+}
+
+#[binread]
+#[br(magic = b"DUM\0")]
+#[derive(Debug, Serialize)]
+struct DUM {
+ size: u32,
+ #[br(assert(version==1, "Invalid DUM version"))]
+ version: u32,
+ num_dummies: u32,
+ #[br(count=num_dummies)]
+ dummies: Vec,
+}
+
+#[binread]
+#[br(magic = b"QUAD")]
+#[derive(Debug, Serialize)]
+struct QUAD {
+ size: u32,
+ #[br(assert(version==1, "Invalid QUAD version"))]
+ version: u32,
+ mesh: u32,
+ table: Table,
+ f_4: [f32; 4],
+ num_children: u32,
+ #[br(count=num_children)]
+ children: Vec,
+}
+
+#[binread]
+#[br(magic = b"CMSH")]
+#[derive(Debug, Serialize)]
+struct CMSH {
+ size: u32,
+ #[br(assert(version==2, "Invalid CMSH version"))]
+ version: u32,
+ #[br(assert(collide_mesh_size==0x34, "Invalid collision mesh size"))]
+ collide_mesh_size: u32,
+ name: PascalString,
+ unk_1: u16,
+ sector: u16,
+ unk_2: u16,
+ index: u8,
+ unk_4: u8,
+ bbox_1: [[f32; 3]; 2],
+ #[br(temp)]
+ t_1: Table<[f32; 3]>,
+ #[br(temp)]
+ t_2: RawTable<0x1c>,
+}
+
+#[binread]
+#[br(magic = b"AMC\0")]
+#[derive(Debug, Serialize)]
+struct AMC {
+ size: u32,
+ #[br(assert(version==100,"Invalid AMC version"))]
+ version: u32,
+ #[br(assert(version_code==0, "Invalid AMC version_code"))]
+ version_code: u32,
+ bbox_1: [[f32; 3]; 2],
+ scale: f32,
+ bbox_2: [[f32; 3]; 2],
+ unk: [f32; 3],
+ cmsh: [CMSH; 2],
+ num_sectors: u32,
+ #[br(count=num_sectors)]
+ sector_col: Vec<[CMSH; 2]>,
+ unk_num_1: u32,
+ unk_num_2: u32,
+ unk_f: [f32; 4],
+ num_quads: u32,
+ #[br(count=num_quads)]
+ quads: Vec,
+}
+
+#[binread]
+#[br(import(version: u32))]
+#[derive(Debug, Serialize)]
+struct TriV104 {
+ #[br(if(version>=0x69))]
+ name_2: Option,
+ mat_key: u32,
+ map_key: u32,
+ num_tris: u32,
+ #[br(count=num_tris)]
+ tris: Vec<[u16; 3]>,
+ verts_1: LFVF,
+ verts_2: LFVF,
+}
+
+#[binread]
+#[br(magic = b"TRI\0", import(version: u32))]
+#[derive(Debug, Serialize)]
+struct TRI {
+ size: u32,
+ unk_int: u32,
+ name: PascalString,
+ unk_int_2: u32, // if 0xffffffff sometimes TriV104 has no name_2 field
+ #[br(args(version))]
+ data: TriV104,
+}
+
+#[binread]
+#[derive(Debug, Serialize)]
+struct EMI_Textures {
+ key: u32,
+ #[br(if(key!=0))]
+ data: Option<(PascalString, u32, PascalString)>,
+}
+
+#[binread]
+#[br(magic = b"EMI\0")]
+#[derive(Debug, Serialize)]
+struct EMI {
+ size: u32,
+ #[br(assert((103..=105).contains(&version)))]
+ version: u32,
+ num_materials: u32,
+ #[br(count=num_materials)]
+ materials: Vec<(u32, MAT)>,
+ #[br(parse_with = until_exclusive(|v: &EMI_Textures| v.key==0))]
+ maps: Vec,
+ num_lists: u32,
+ #[br(count=num_lists,args{inner: (version,)})]
+ tri: Vec,
+}
+
+#[binread]
+#[derive(Debug, Serialize)]
+enum Data {
+ SM3(SM3),
+ CM3(CM3),
+ DUM(DUM),
+ AMC(AMC),
+ EMI(EMI),
+}
+
+fn parse_file(path: &PathBuf) -> Result {
+ let mut rest_size = 0;
+ let mut fh = BufReader::new(fs::File::open(path)?);
+ let ret = fh.read_le()?;
+ let pos = fh
+ .stream_position()
+ .unwrap_or(0)
+ .try_into()
+ .unwrap_or(u32::MAX);
+ eprintln!("Read {} bytes from {}", pos, path.display());
+ let mut buffer = [0u8; 0x1000];
+ if let Ok(n) = fh.read(&mut buffer) {
+ if n != 0 {
+ eprintln!("Rest:\n{}", rhexdump::hexdump_offset(&buffer[..n], pos));
+ }
+ };
+ while let Ok(n) = fh.read(&mut buffer) {
+ if n == 0 {
+ break;
+ }
+ rest_size += n;
+ }
+ eprintln!("+{rest_size} unparsed bytes");
+ Ok(ret)
+}
+
+fn load_ini(path: &PathBuf) -> IniData {
+ Ini::new().load(path).unwrap_or_default()
+}
+
+#[derive(Serialize, Debug)]
+
+struct Level {
+ config: IniData,
+ moredummies: IniData,
+ emi: EMI,
+ sm3: SM3,
+ dummies: DUM,
+ path: PathBuf,
+ root: PathBuf,
+}
+
+impl Level {
+ fn load(root: &Path, path: &Path) -> Result {
+ let full_path = &root.join(path);
+ let emi_path = full_path.join("map").join("map3d.emi");
+ let sm3_path = emi_path.with_extension("sm3");
+ let dum_path = emi_path.with_extension("dum");
+ let config_file = emi_path.with_extension("ini");
+ let moredummies = emi_path.with_file_name("moredummies").with_extension("ini");
+ let config = load_ini(&config_file);
+ let moredummies = load_ini(&moredummies);
+ let Data::EMI(emi) = parse_file(&emi_path)? else {
+ bail!("Failed to parse EMI at {emi_path}", emi_path=emi_path.display());
+ };
+ let Data::SM3(sm3) = parse_file(&sm3_path)? else {
+ bail!("Failed to parse SM3 at {sm3_path}", sm3_path=sm3_path.display());
+ };
+ let Data::DUM(dummies) = parse_file(&dum_path)? else {
+ bail!("Failed to parse DUM at {dum_path}", dum_path=dum_path.display());
+ };
+ Ok(Level {
+ config,
+ moredummies,
+ emi,
+ sm3,
+ dummies,
+ path: path.into(),
+ root: root.into(),
+ })
+ }
+}
+
+#[derive(Subcommand, Debug)]
+enum Commands {
+ FindScrapland,
+ ParsePacked {
+ scrap_path: PathBuf,
+ },
+ ParseFile {
+ #[clap(long)]
+ /// Write to stdout
+ stdout: bool,
+ /// Scrapland root path
+ root: PathBuf,
+ /// Level to parse and convert
+ level: PathBuf,
+ },
+}
+
+#[derive(Parser, Debug)]
+#[command(author, version, about, long_about = None)]
+#[command(propagate_version = true)]
+struct Args {
+ #[arg(long,short)]
+ /// Write data as JSON
+ json: bool,
+ #[command(subcommand)]
+ command: Commands,
+}
+
+fn cmd_parse_packed(root: &Path) -> Result>> {
+ let mut packed_map = HashMap::new();
+ for entry in WalkDir::new(root).into_iter().filter_map(|e| e.ok()) {
+ let path = entry.path();
+ if path
+ .extension()
+ .map(|e| e.to_str() == Some("packed"))
+ .unwrap_or(false)
+ {
+ let path = entry.path().to_owned();
+ let header: PackedHeader = BufReader::new(File::open(&path)?).read_le()?;
+ packed_map.insert(path, header.files);
+ }
+ }
+ Ok(packed_map)
+}
+
+fn to_bytes(data: &T, json: bool) -> Result> where T: Serialize {
+ if json {
+ Ok(serde_json::to_vec_pretty(data)?)
+ } else {
+ Ok(serde_pickle::to_vec(data,Default::default())?)
+ }
+}
+
+fn cmd_parse_file(stdout: bool, root: &Path, path: &Path, json: bool) -> Result<()> {
+ let out_path = PathBuf::from(
+ path
+ .components()
+ .last()
+ .unwrap()
+ .as_os_str()
+ .to_string_lossy()
+ .into_owned(),
+ );
+ let out_path = if json {
+ out_path.with_extension("json.gz")
+ } else {
+ out_path.with_extension("pkl.gz")
+ };
+ let full_path = &root.join(path);
+ let data = if full_path.is_dir() {
+ let level = Level::load(root, path)?;
+ to_bytes(&level,json)?
+ } else {
+ let data = parse_file(full_path)?;
+ to_bytes(&data,json)?
+ };
+ let mut data = Cursor::new(data);
+ if stdout {
+ let mut stdout = std::io::stdout().lock();
+ std::io::copy(&mut data, &mut stdout)?;
+ } else {
+ let mut fh = GzEncoder::new(File::create(&out_path)?, Compression::best());
+ std::io::copy(&mut data, &mut fh)?;
+ eprintln!("Wrote {path}", path = out_path.display());
+ };
+ Ok(())
+}
+
+fn emi_to_obj(emi: EMI) -> ! {
+ // let mut obj_data = obj::ObjData::default();
+
+ // for mesh in emi.tri {
+ // for vert in mesh.data.verts_1.inner.map(|d| d.data).unwrap_or_default() {
+ // obj_data.position.push(vert.xyz);
+ // obj_data.normal.push(vert.normal.unwrap_or_default());
+ // obj_data.texture.push(vert.tex_1.unwrap_or_default().0.try_into().unwrap());
+ // }
+ // for vert in mesh.data.verts_2.inner.map(|d| d.data).unwrap_or_default() {
+ // obj_data.position.push(vert.xyz);
+ // obj_data.normal.push(vert.normal.unwrap_or_default());
+ // }
+ // }
+ todo!("EMI to OBJ converter");
+}
+
+fn main() -> Result<()> {
+ let args = Args::try_parse()?;
+ match args.command {
+ Commands::FindScrapland => {
+ let data = to_bytes(&find_scrap::get_executable()?,args.json)?;
+ let mut stdout = std::io::stdout().lock();
+ std::io::copy(&mut &data[..], &mut stdout)?;
+ }
+ Commands::ParsePacked { scrap_path } => {
+ let data = to_bytes(&cmd_parse_packed(&scrap_path)?,args.json)?;
+ let mut stdout = std::io::stdout().lock();
+ std::io::copy(&mut &data[..], &mut stdout)?;
+ }
+ Commands::ParseFile {
+ stdout,
+ root,
+ level,
+ } => {
+ cmd_parse_file(stdout, &root, &level, args.json)?;
+ }
+ }
+ Ok(())
+}
diff --git a/tools/remaster/scrap_parse/src/pixel_shader.rs b/tools/remaster/scrap_parse/src/pixel_shader.rs
new file mode 100644
index 0000000..1430524
--- /dev/null
+++ b/tools/remaster/scrap_parse/src/pixel_shader.rs
@@ -0,0 +1,30 @@
+// https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx9-graphics-reference-asm-ps-1-x
+//
+// ################################################
+//
+// #[derive(Debug)]
+// enum VecArg {
+// Tex(f32,f32,f32,f32),
+// Reg(f32,f32,f32,f32),
+// Ver(f32,f32,f32,f32),
+// Col(f32,f32,f32,f32),
+// Vec(f32,f32,f32,f32),
+// }
+
+// struct Arg {
+// arg: VecArg,
+// idx: Option
+// }
+
+// #[derive(Debug)]
+// enum Inst {
+// Tex(Arg),
+// Add(Arg,Arg,Arag),
+// Sub(Arg,Arg,Arag),
+// Mul(Arg,Arg,Arag),
+// Mov(Arg,Arg),
+// }
+
+fn parse(path: &str) {
+
+}
\ No newline at end of file
diff --git a/tools/remaster/scraphacks_rs/.cargo/config.toml b/tools/remaster/scraphacks_rs/.cargo/config.toml
new file mode 100644
index 0000000..bf040d8
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/.cargo/config.toml
@@ -0,0 +1,2 @@
+[build]
+target = "i686-pc-windows-msvc"
\ No newline at end of file
diff --git a/tools/remaster/scraphacks_rs/Cargo.lock b/tools/remaster/scraphacks_rs/Cargo.lock
new file mode 100644
index 0000000..b636b23
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/Cargo.lock
@@ -0,0 +1,1624 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
+
+[[package]]
+name = "app_dirs2"
+version = "2.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47a8d2d8dbda5fca0a522259fb88e4f55d2b10ad39f5f03adeebf85031eba501"
+dependencies = [
+ "jni",
+ "ndk-context",
+ "winapi",
+ "xdg",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
+
+[[package]]
+name = "async-trait"
+version = "0.1.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "brownstone"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5839ee4f953e811bfdcf223f509cb2c6a3e1447959b0bff459405575bc17f22"
+dependencies = [
+ "arrayvec",
+]
+
+[[package]]
+name = "bstr"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
+
+[[package]]
+name = "bytecount"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
+
+[[package]]
+name = "bytes"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
+
+[[package]]
+name = "cc"
+version = "1.0.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
+dependencies = [
+ "jobserver",
+]
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "comma"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335"
+
+[[package]]
+name = "const_fn"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935"
+
+[[package]]
+name = "const_format"
+version = "0.2.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7309d9b4d3d2c0641e018d449232f2e28f1b22933c137f157d3dbc14228b8c0e"
+dependencies = [
+ "const_format_proc_macros",
+]
+
+[[package]]
+name = "const_format_proc_macros"
+version = "0.2.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f47bf7270cf70d370f8f98c1abb6d2d4cf60a6845d30e05bfb90c6568650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "custom-print"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb50a9067f6b0c4ecdac7986070032d9d299f69078ed69d6131bb5ede3345bd2"
+
+[[package]]
+name = "cxx"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d1075c37807dcf850c379432f0df05ba52cc30f279c5cfc43cc221ce7f8579"
+dependencies = [
+ "cc",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5044281f61b27bc598f2f6647d480aed48d2bf52d6eb0b627d84c0361b17aa70"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61b50bc93ba22c27b0d31128d2d130a0a6b3d267ae27ef7e4fae2167dfe8781c"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39e61fda7e62115119469c7b3591fd913ecca96fb766cfd3f2e2502ab7bc87a5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "dataview"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50eb3a329e19d78c3a3dfa4ec5a51ecb84fa3a20c06edad04be25356018218f9"
+dependencies = [
+ "derive_pod",
+]
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "derive_pod"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2ea6706d74fca54e15f1d40b5cf7fe7f764aaec61352a9fcec58fe27e042fc8"
+
+[[package]]
+name = "detour3"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0246383803197ff0315ef865f2daf6611f43b5782562e49f08bfa9de8a125ee"
+dependencies = [
+ "cfg-if",
+ "generic-array",
+ "lazy_static",
+ "libc",
+ "libudis86-sys",
+ "mmap-fixed-fixed",
+ "region",
+ "slice-pool",
+]
+
+[[package]]
+name = "dirs"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "discord-sdk"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f0df260a6baf1d63a08dc38a7b75f30d4722a9eb2bf6d1172d33e0221bd72ae"
+dependencies = [
+ "anyhow",
+ "app_dirs2",
+ "async-trait",
+ "base64",
+ "bitflags",
+ "crossbeam-channel",
+ "num-traits",
+ "parking_lot",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "thiserror",
+ "time",
+ "tokio",
+ "tracing",
+ "url",
+ "winreg",
+]
+
+[[package]]
+name = "either"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
+
+[[package]]
+name = "erased-serde"
+version = "0.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4ca605381c017ec7a5fef5e548f1cfaa419ed0f6df6367339300db74c92aa7d"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "form_urlencoded"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
+
+[[package]]
+name = "futures-task"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
+
+[[package]]
+name = "futures-util"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "git2"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf7f68c2995f392c49fffb4f95ae2c873297830eb25c6bc4c114ce8f4562acc"
+dependencies = [
+ "bitflags",
+ "libc",
+ "libgit2-sys",
+ "log",
+ "url",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
+dependencies = [
+ "cxx",
+ "cxx-build",
+]
+
+[[package]]
+name = "iced-x86"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd04b950d75b3498320253b17fb92745b2cc79ead8814aede2f7c1bab858bec"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "idna"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indent_write"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cfe9645a18782869361d9c8732246be7b410ad4e919d3609ebabdac00ba12c3"
+
+[[package]]
+name = "is_debug"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06d198e9919d9822d5f7083ba8530e04de87841eaf21ead9af8f2304efd57c89"
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
+
+[[package]]
+name = "jni"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
+dependencies = [
+ "cesu8",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
+[[package]]
+name = "jobserver"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "joinery"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5"
+
+[[package]]
+name = "js-sys"
+version = "0.3.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.139"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+
+[[package]]
+name = "libgit2-sys"
+version = "0.14.2+1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f3d95f6b51075fe9810a7ae22c7095f12b98005ab364d8544797a825ce946a4"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "pkg-config",
+]
+
+[[package]]
+name = "libudis86-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139bbf9ddb1bfc90c1ac64dd2923d9c957cd433cee7315c018125d72ab08a6b0"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "libz-sys"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "lua-src"
+version = "544.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "708ba3c844d5e9d38def4a09dd871c17c370f519b3c4b7261fbabe4a613a814c"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "luajit-src"
+version = "210.4.5+resty2cf5186"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b7992a40e602786272d84c6f2beca44a588ededcfd57b48ec6f82008a7cb97"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "mach"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "mio"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys",
+]
+
+[[package]]
+name = "mlua"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ee2ad7a9aa69056b148d9d590344bc155d3ce0d2200e3b2838f7034f6ba33c1"
+dependencies = [
+ "bstr",
+ "cc",
+ "erased-serde",
+ "lua-src",
+ "luajit-src",
+ "mlua_derive",
+ "num-traits",
+ "once_cell",
+ "pkg-config",
+ "rustc-hash",
+ "serde",
+]
+
+[[package]]
+name = "mlua_derive"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9214e60d3cf1643013b107330fcd374ccec1e4ba1eef76e7e5da5e8202e71c0"
+dependencies = [
+ "itertools",
+ "once_cell",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "syn",
+]
+
+[[package]]
+name = "mmap-fixed-fixed"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0681853891801e4763dc252e843672faf32bcfee27a0aa3b19733902af450acc"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
+[[package]]
+name = "no-std-compat"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "nom-greedyerror"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73f359007d505b20cd6e4974ff0d5c8e4565f0f9e15823937238221ccb74b516"
+dependencies = [
+ "nom",
+ "nom_locate",
+]
+
+[[package]]
+name = "nom-supreme"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bd3ae6c901f1959588759ff51c95d24b491ecb9ff91aa9c2ef4acc5b1dcab27"
+dependencies = [
+ "brownstone",
+ "indent_write",
+ "joinery",
+ "memchr",
+ "nom",
+]
+
+[[package]]
+name = "nom_locate"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1e299bf5ea7b212e811e71174c5d1a5d065c4c0ad0c8691ecb1f97e3e66025e"
+dependencies = [
+ "bytecount",
+ "memchr",
+ "nom",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "num_threads"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-sys",
+]
+
+[[package]]
+name = "parse_int"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d695b79916a2c08bcff7be7647ab60d1402885265005a6658ffe6d763553c5a"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "pelite"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88dccf4bd32294364aeb7bd55d749604450e9db54605887551f21baea7617685"
+dependencies = [
+ "dataview",
+ "libc",
+ "no-std-compat",
+ "pelite-macros",
+ "winapi",
+]
+
+[[package]]
+name = "pelite-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a7cf3f8ecebb0f4895f4892a8be0a0dc81b498f9d56735cb769dc31bf00815b"
+
+[[package]]
+name = "percent-encoding"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
+dependencies = [
+ "getrandom",
+ "redox_syscall",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
+
+[[package]]
+name = "region"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e"
+dependencies = [
+ "bitflags",
+ "libc",
+ "mach",
+ "winapi",
+]
+
+[[package]]
+name = "rhexdump"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e9af64574935e39f24d1c0313a997c8b880ca0e087c888bc6af8af31579847"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "ryu"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "scraphacks_rs"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "comma",
+ "custom-print",
+ "derivative",
+ "detour3",
+ "discord-sdk",
+ "futures",
+ "hex",
+ "iced-x86",
+ "mlua",
+ "nom",
+ "nom-greedyerror",
+ "nom-supreme",
+ "nom_locate",
+ "num-traits",
+ "once_cell",
+ "parse_int",
+ "pelite",
+ "region",
+ "rhexdump",
+ "rustc-hash",
+ "shadow-rs",
+ "struct_layout",
+ "tokio",
+ "viable",
+ "winsafe",
+]
+
+[[package]]
+name = "scratch"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
+
+[[package]]
+name = "serde"
+version = "1.0.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "shadow-rs"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "427f07ab5f873000cf55324882e12a88c0a7ea7025df4fc1e7e35e688877a583"
+dependencies = [
+ "const_format",
+ "git2",
+ "is_debug",
+ "time",
+ "tzdb",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "slice-pool"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "733fc6e5f1bd3a8136f842c9bdea4e5f17c910c2fcc98c90c3aa7604ef5e2e7a"
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "socket2"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "struct_layout"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4d806456ba83a5a07a5c09f984c7b5339c576d38d9656d464956ec4a74e5d7"
+
+[[package]]
+name = "syn"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
+dependencies = [
+ "itoa",
+ "libc",
+ "num_threads",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
+
+[[package]]
+name = "time-macros"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
+dependencies = [
+ "time-core",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tokio"
+version = "1.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
+dependencies = [
+ "autocfg",
+ "libc",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "socket2",
+ "windows-sys",
+]
+
+[[package]]
+name = "tracing"
+version = "0.1.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
+[[package]]
+name = "tz-rs"
+version = "0.6.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33851b15c848fad2cf4b105c6bb66eb9512b6f6c44a4b13f57c53c73c707e2b4"
+dependencies = [
+ "const_fn",
+]
+
+[[package]]
+name = "tzdb"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b882d864be6a5d7c3c916719944458b1e03c85f86dbc825ec98155117c4408"
+dependencies = [
+ "iana-time-zone",
+ "tz-rs",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
+
+[[package]]
+name = "url"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "viable"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7746c1a6e7b03a755198414f4a364d8515f688f5f2e5fdd1db323891d299cb60"
+dependencies = [
+ "viable-impl",
+]
+
+[[package]]
+name = "viable-impl"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55bccf43d6bb2790e856edc1f250e25d8eeca72b2e6dd659386c85f794d53a6f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
+
+[[package]]
+name = "winreg"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winsafe"
+version = "0.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62a4f20d0e60f34bfbb2624564d0bfda0a6eaee8f7f4494e5c3c883c3a62fdd2"
+
+[[package]]
+name = "xdg"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6"
+dependencies = [
+ "dirs",
+]
diff --git a/tools/remaster/scraphacks_rs/Cargo.toml b/tools/remaster/scraphacks_rs/Cargo.toml
new file mode 100644
index 0000000..a4bf58c
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/Cargo.toml
@@ -0,0 +1,45 @@
+[package]
+name = "scraphacks_rs"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[profile.release]
+debug = 0
+
+[lib]
+crate-type = ["dylib"]
+
+[features]
+console=[]
+
+[dependencies]
+anyhow = "1.0.68"
+comma = "1.0.0"
+custom-print = "0.1.0"
+derivative = "2.2.0"
+detour3 = "0.1.0"
+discord-sdk = "0.3.2"
+futures = "0.3.25"
+hex = "0.4.3"
+iced-x86 = "1.18.0"
+mlua = { version = "0.8.7", features = ["luajit", "vendored", "macros", "serialize", "mlua_derive"] }
+nom = "7.1.3"
+nom-greedyerror = "0.5.0"
+nom-supreme = "0.8.0"
+nom_locate = "4.1.0"
+num-traits = "0.2.15"
+once_cell = "1.17.0"
+parse_int = "0.6.0"
+pelite = "0.10.0"
+region = "3.0.0"
+rhexdump = "0.1.1"
+rustc-hash = "1.1.0"
+shadow-rs = "0.21.0"
+struct_layout = "0.1.0"
+tokio = "1.24.2"
+viable = "0.2.0"
+winsafe = { version = "0.0.15", features = ["kernel", "user", "dshow"] }
+
+[build-dependencies]
+shadow-rs = "0.21.0"
diff --git a/tools/remaster/scraphacks_rs/Pipfile b/tools/remaster/scraphacks_rs/Pipfile
new file mode 100644
index 0000000..71e4f7c
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/Pipfile
@@ -0,0 +1,11 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+
+[dev-packages]
+
+[requires]
+python_version = "3.9"
diff --git a/tools/remaster/scraphacks_rs/Save0.sav.json b/tools/remaster/scraphacks_rs/Save0.sav.json
new file mode 100644
index 0000000..f7d1504
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/Save0.sav.json
@@ -0,0 +1,242 @@
+{
+ "id": "Outskirts - 07:41:13",
+ "title": "Scrapland savegame",
+ "data": {
+ "CTFFinishPlayerLoosesTextCad": "Mission_CTFAgainstBankers_RemoteMessage3",
+ "CTFFinishPlayerWinsTextCad": "Mission_CTFAgainstBankers_RemoteMessage2",
+ "CTFStartTextCad": "Mission_CTFAgainstBankers_RemoteMessage1",
+ "CTFOnDeathSpawnTime": "5.0",
+ "CTFFriendProfile": "BankersCTFFriends",
+ "CTFEnemyProfile": "BankersCTFEnemies",
+ "CTFFriendHead": "Functionary",
+ "CTFFriendType": "Functionary",
+ "CTFEnemyType": "BankDirector",
+ "CTFNumFriends": "5",
+ "CTFNumEnemies": "5",
+ "CTFFlags": "5",
+ "CombatFriendProfile": "ArrangeBankersCombatFriends",
+ "CombatFriendType": "Police",
+ "CombatNumFriends": "4",
+ "CombatNumEnemies": "5",
+ "PlayerWinsCombat": "1",
+ "OnVictory": "import SaveGame; SaveGame.QWayPoint();import MissionsFuncs; MissionsFuncs.SetNextMission(\"Mission_BackFromMortalRace\", \"GamblinDen\");Scrap.SetSaveVar(\"Map\", \"Levels/GamblinDen\");import SaveGame; SaveGame.QLoad(\"Levels/GamblinDen\");Scrap.SetSaveVar('MapPress_BureauExists','1')",
+ "OnAbort": "import MissionsFuncs; MissionsFuncs.EndOfSuperDeal('SuperDeal_Faliure_SystemMessage1')",
+ "CombatFinishPlayerLoosesTextCad": "SuperDeal_First_RemoteMessage3",
+ "CombatFinishPlayerWinsTextCad": "SuperDeal_First_RemoteMessage2",
+ "CombatStartTextCad": "SuperDeal_First_RemoteMessage1",
+ "CombatEnemyProfile": "SuperDealFirstElite",
+ "CombatEnemyTypeHead": "CrazyGambler",
+ "CombatEnemyType": "BankDirector",
+ "CombatDeaths": "5",
+ "SuperDealType": "",
+ "IgorFirstContactMissionState": "TalkToMercenaries",
+ "Stats.MadHunter": "10",
+ "Stats.Nurse.Dazed": "2",
+ "Stats.BankMaster": "6758",
+ "Bank.Circuit.36": "0",
+ "Bank.Circuit.35": "0",
+ "Bank.Circuit.34": "0",
+ "Bank.Circuit.33": "0",
+ "Bank.Circuit.32": "0",
+ "Bank.Circuit.31": "0",
+ "Bank.Circuit.30": "0",
+ "Bank.Circuit.29": "0",
+ "Bank.Circuit.28": "0",
+ "Bank.Circuit.27": "0",
+ "Bank.Circuit.26": "0",
+ "Bank.Circuit.25": "0",
+ "Bank.Circuit.24": "0",
+ "Bank.Circuit.23": "0",
+ "Bank.Circuit.22": "0",
+ "Bank.Circuit.21": "0",
+ "Bank.Circuit.20": "0",
+ "Bank.Circuit.19": "0",
+ "Bank.Circuit.18": "0",
+ "Bank.Circuit.17": "0",
+ "Bank.Circuit.16": "0",
+ "Bank.Circuit.15": "0",
+ "Bank.Circuit.14": "0",
+ "Bank.Circuit.13": "0",
+ "Bank.Circuit.12": "0",
+ "Bank.Circuit.11": "0",
+ "Bank.Circuit.10": "0",
+ "Bank.Circuit.9": "0",
+ "Bank.Circuit.8": "0",
+ "Bank.Circuit.7": "0",
+ "Bank.Circuit.6": "0",
+ "Bank.Circuit.5": "0",
+ "Bank.Circuit.4": "0",
+ "Bank.Circuit.3": "0",
+ "Bank.Circuit.2": "0",
+ "Bank.Circuit.1": "0",
+ "Bank.Circuit.0": "0",
+ "Stats.Mosquito": "116",
+ "PoliceBossAtTownHall": "0",
+ "Stats.Parking": "18",
+ "Police.FicusDeath": "1",
+ "CostumeAtPolice": "0",
+ "Hangar.HangarShip10": "SLifeBoat<-<-<-<-<-0,0,0,0,0,0,1<-30<-0,0,0,0,0,0",
+ "Hangar.iHangarShips": "8",
+ "AutoSaveGameOnLoad": "0",
+ "GameAct": "3rdMurder",
+ "BankDebt": "0",
+ "Map": "Levels/Outskirts",
+ "Mission.Library": "Mission_DestroyBadDebtors",
+ "EnergyBarActive": "1",
+ "SpecialActionActive": "2",
+ "CrazyWing.Status": "1",
+ "Conversor.ActiveConversors": "1",
+ "Hangar.iHangarShip": "0",
+ "Player.NumLives": "100",
+ "Hangar.shipsToEditList": "['SPoli1', 'SPoli2', 'SPoli3', 'SPoli4', 'SPoli5', 'SPoliBoss1', 'SMerc1', 'SMerc2', 'SMerc3', 'SMayor1', 'SBanker1', 'SBankMaster1', 'SBishop1', 'SArchbishop1', 'SFunc1', 'SBerto1', 'SBetty1', 'SHump1', 'SBoss1', 'SPoli4']",
+ "Hangar.availableEnginesList": "['MPOLI4', 'MPOLI5', 'MPOLIBOSS1', 'MPOLI2', 'MBERTO1', 'MBETTY1', 'MPOLI1', 'MMERC1', 'MMERC2', 'MPOLI3', 'MMAYOR1', 'MFUNC1', 'MBANKER1', 'MBANKMASTER1', 'MBISHOP1', 'MARCHBISHOP1', 'MHUMP1', 'MBOSS1', 'MMERC3']",
+ "Hangar.availableWeaponsList": "['Vulcan', 'Devastator', 'Swarm', 'Inferno', 'Tesla', 'ATPC', 'Swarm', 'Devastator']",
+ "Hangar.availableUpgradesList": "[\"VulcanUpgrade1\", \"VulcanUpgrade2\", \"DevastatorUpgrade1\", \"DevastatorUpgrade2\", \"SwarmUpgrade1\", \"SwarmUpgrade2\", \"InfernoUpgrade1\", \"InfernoUpgrade2\", \"TeslaUpgrade1\", \"TeslaUpgrade2\", \"ATPCUpgrade1\", \"ATPCUpgrade2\"]",
+ "JackInTheBox.Active": "1",
+ "Parked.Police": "['SPOLI1', 'SPOLI2', 'SPOLI3', 'SPOLI4']",
+ "Parked.Mercs": "['SMERC1', 'SMERC2']",
+ "Parked.TownHall": "['SFUNC1']",
+ "Parked.Bank": "['SBANKER1']",
+ "Parked.Press": "['SBERTO1', 'SBETTY1', 'SHUMP1']",
+ "Parked.Temple": "['SBISHOP1']",
+ "PoliceBlueprints.Ships": "['SPoli2', 'SPoli3', 'SPoli5', 'SPoliBoss1']",
+ "PoliceBlueprints.Engines": "['MPoli2', 'MPoli3', 'MPoli4', 'MPoli5', 'MPoliBoss1']",
+ "PressBlueprints.Ships": "['SBerto1', 'SBetty1', 'SHump1']",
+ "PressBlueprints.Engines": "['MBerto1', 'MBetty1', 'MHump1']",
+ "MayorAtGambinDen": "0",
+ "PolicesAtGambinDen": "1",
+ "PoliceBossAtPolice": "0",
+ "CrazyGamblerAtGambinDen": "1",
+ "FunctionaryTwinsAtGambinDen": "1",
+ "BankersAtGambinDen": "0",
+ "BishopsAtGambinDen": "0",
+ "GameSkill": "0",
+ "CreateBertos": "0",
+ "MercsHelpDtritus": "0",
+ "RobotsControlledByBoss": "0",
+ "Player.InfiniteLives": "0",
+ "PrevMap": "Levels/GamblinDen",
+ "Spawn": "DM_Player_Spawn_GamblinDen",
+ "Char": "Dtritus",
+ "ComeFrom": "DoorElevator",
+ "AlarmActive": "0",
+ "AlarmStatus": "0.0",
+ "Money": "2147483647",
+ "Challenge": "1",
+ "Challenge.Type": "",
+ "Challenge.Foe": "",
+ "Challenge.NumEnemies": "",
+ "Challenge.Money": "0",
+ "Revenge.Type": "",
+ "Revenge.Foe": "",
+ "Revenge.NumEnemies": "",
+ "Revenge.LastWinner": "",
+ "Mission.Map": "Outskirts",
+ "BadReputation": "3",
+ "GameplayTime": "27673.5857997",
+ "LonelyMercInGamblinDen": "0",
+ "LonelyMercLairActive": "1",
+ "LonelyMercDataActive": "0",
+ "ComSatsMissionsMapsFinished": "[]",
+ "Conversor.AvailableChars": "['Police', 'Nurse', 'BankDirector', 'Desktop', 'Sentinel', 'Gear', 'Bishop', 'Messenger', 'Functionary', 'Betty', 'Berto', 'BankMaster', 'Dtritus']",
+ "Batteries": "5",
+ "AcBatteries": "2",
+ "PrimaryMissionDesc": "Ich muss den Bankdirektor besuchen. Er treibt gerade ausstehende Kreditzahlungen mit seinem Kampfraumschiff ein. Ich sehe mal, ob ich ihm helfen kann.",
+ "SecondaryMissionDesc": "",
+ "TakePhotoMsg": "0",
+ "Race.Num": "3",
+ "Race.FirstTime": "0",
+ "Race.Profile": "Pilot",
+ "Combat.FirstTime": "1",
+ "Combat.Profile": "Rookie",
+ "Traffic.AcShips": "[\"SPoli6\", \"SMerc1\",\"SMerc2\",\"SBanker1\"]",
+ "IsSecondMission": "0",
+ "CrazyDeal.1.Var": "Stats.Dtritus",
+ "CrazyDeal.1.Tgt": "5",
+ "CrazyDeal.2.Var": "Stats.Parking",
+ "CrazyDeal.2.Tgt": "3",
+ "CrazyDeal.3.Var": "Stats.Battery",
+ "CrazyDeal.3.Tgt": "5",
+ "SuperDeal.Map": "FZ",
+ "SuperDeal.Library": "SuperDeal_Second",
+ "SuperDeal.Type": "MortalRace",
+ "CrazyWing.List": "['Sentinel', 'Betty', 'CrazyGambler', 'Functionary', 'Bishop']",
+ "Journalist.Humphrey_Defaut": "NoPlace",
+ "Journalist.Betty_Defaut": "NoPlace",
+ "Journalist.Berto_Defaut": "Press",
+ "Conversor.FirstConversion": "0",
+ "Conversor.FirstPossession": "0",
+ "WindowsError": "0",
+ "Orbit.Decontaminated": "yes",
+ "MercFriends.MercenaryA_Smartie": "0",
+ "MercFriends.MercenaryC_Brutus": "0",
+ "MercFriends.MercenaryB_Dumber": "0",
+ "StdShipAIProfile": "Elite",
+ "usrShip.Ammo00": "1000.0",
+ "usrShip.Ammo01": "500.0",
+ "usrShip.Ammo02": "1500.0",
+ "usrShip.AcWeap": "6",
+ "Parking.Desolate": "0",
+ "Hangar.HangarShip1": "SBoss1<-MBOSS1<-MBOSS1<-<-<-15,15,15,15,15,15,1<-187<-1,8,6,9,11,3",
+ "Hangar.HangarShip2": "",
+ "Hangar.HangarShip3": "",
+ "Hangar.HangarShip4": "",
+ "Hangar.HangarShip5": "",
+ "Hangar.HangarShip6": "",
+ "Hangar.HangarShip7": "",
+ "Hangar.HangarShip8": "",
+ "Hangar.HangarShip9": "",
+ "Hangar.HangarShipAux": "SLifeBoat<-<-<-<-<-0,0,0,0,0,0,1<-50<-0,0,0,0,0,0",
+ "Hangar.DestroyedShips": "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]",
+ "NewBluePrintAvaliable": "11",
+ "DebugSave": "1",
+ "OutMusicRelax": "141",
+ "MayorAtTownHall": "1",
+ "GamblinMusic": "1",
+ "OutMusicAction": "122",
+ "Stats.Traffic": "767",
+ "Stats.OutPolice": "110",
+ "MapPress_HiAlarm": "0",
+ "MapPress_BureauExists": "1",
+ "Stats.PossessBerto": "1",
+ "Stats.ConvertIntoDtritus": "1",
+ "Stats.WinHumphreyRace": "1",
+ "Stats.Possession": "57",
+ "Stats.Betty": "49",
+ "Stats.Killer": "119",
+ "Stats.Jump": "1",
+ "Stats.Jump.Police": "1",
+ "Stats.Bishop": "10",
+ "Stats.Battery": "0",
+ "Stats.Dtritus": "0",
+ "Stats.Race.Press": "1",
+ "Stats.TotalRaces.Press": "1",
+ "BankMasterAtBank": "1",
+ "DM_ExtraLife_00": "-60",
+ "DM_ExtraLife_01": "-60",
+ "DM_ExtraLife_02": "-60",
+ "DM_ExtraLife_03": "-60",
+ "DM_ExtraLife_04": "16294.7919922",
+ "Stats.TempleLife": "4",
+ "Mission_CatchTrurl_Data": "[]",
+ "Mission_CatchTrurl_MapsPos": "[]",
+ "Mission_CatchTrurl_NumMapsDropped": "0",
+ "Mission_CatchTrurl_NumMapsTaken": "0",
+ "GDB.BishopsMsg": "1",
+ "Stats.GDB": "6",
+ "LonelyMercActive": "0",
+ "Stats.Messenger": "2",
+ "MortalRaceRace": "[((71611.59375, 18231.6992188, 93232.796875), 422.337219238), ((45388.4140625, 14599.3476563, 79622.6640625), 400.984222412), ((25194.9804688, 18783.4863281, 59759.296875), 404.390136719), ((-433.664245605, 26340.1289063, 34561.0898438), 409.718261719), ((-38229.3671875, 26457.5292969, 679.068054199), 449.472442627), ((-107464.132813, 19331.875, 3288.328125), 528.452758789), ((-113911.117188, 14331.4462891, 40812.9414063), 558.054199219), ((-102532.132813, 11236.1474609, 75072.375), 630.567077637), ((-58177.6289063, 6282.20654297, 74209.3515625), 673.615478516), ((-24157.5449219, 7054.30419922, 43223.1679688), 630.510681152), ((33550.1445313, 15480.2402344, 41122.5820313), -55.4565696716), ((56201.6054688, 15587.5126953, 24649.8496094), 23.7488441467), ((26343.9511719, 22077.8789063, -32317.0292969), 90.5086135864), ((-13835.4755859, 26276.8730469, -31975.1582031), 145.932754517), ((-29244.3652344, 26745.4667969, -2544.81738281), -892.995666504), ((-23808.9570313, 27246.9980469, 32018.1816406), -819.383483887), ((21584.3066406, 29452.4667969, 41221.6171875), -822.313781738), ((54026.796875, 24611.7421875, 42694.0898438), -802.188171387), ((95732.015625, 16516.8085938, 36323.546875), -872.699890137), ((113450.46875, 12325.5195313, 77796.75), -969.003662109)]",
+ "MortalRaceWaypoints": "20",
+ "MortalRaceRacers": "['ArchBishop', 'BankDirector', 'Functionary', 'MercenaryA', 'MercenaryB']",
+ "MortalRaceRacersProfile": "MortalRace",
+ "MortalRaceFightingFreezeControlAim": "4",
+ "MortalRaceRespawnTime": "5.0",
+ "MortalRaceStartTextCad": "Mission_WinMortalRace_RemoteMessage1",
+ "MortalRaceFinishPlayerLoosesTextCad": "Mission_WinMortalRace_RemoteMessage2",
+ "MortalRaceFinishPlayerLoosesTextFoe": "Messenger",
+ "MortalRaceFinishPlayerWinsTextCad": "Mission_WinMortalRace_RemoteMessage3",
+ "MortalRaceFinishPlayerWinsTextFoe": "Messenger",
+ "MortalRaceAutoRestart": "1"
+ }
+}
\ No newline at end of file
diff --git a/tools/remaster/scraphacks_rs/build.rs b/tools/remaster/scraphacks_rs/build.rs
new file mode 100644
index 0000000..4a0dfc4
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/build.rs
@@ -0,0 +1,3 @@
+fn main() -> shadow_rs::SdResult<()> {
+ shadow_rs::new()
+}
diff --git a/tools/remaster/scraphacks_rs/build.sh b/tools/remaster/scraphacks_rs/build.sh
new file mode 100644
index 0000000..cf4b138
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/build.sh
@@ -0,0 +1,4 @@
+impo
+cargo b -r
+cp D:/devel/Git_Repos/Scrapland-RE/tools/remaster/scraphacks_rs/target/i686-pc-windows-msvc/release/scraphacks_rs.dll E:/Games/Steam/steamapps/common/Scrapland/lib/ScrapHack.pyd
+# x32dbg E:/Games/Steam/steamapps/common/Scrapland/Bin/Scrap.unpacked.exe "-debug:10 -console"
\ No newline at end of file
diff --git a/tools/remaster/scraphacks_rs/notes.md b/tools/remaster/scraphacks_rs/notes.md
new file mode 100644
index 0000000..705b494
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/notes.md
@@ -0,0 +1,16 @@
+# Notes
+
+## Snippets
+
+Map name: `Scrap.GetLangStr("Station_" + Scrap.GetLevelPath()[7:])`
+
+## Run
+
+`steam://run/897610/`
+
+## Signatures
+
+- World pointer: `a3 *{'} e8 ? ? ? ? 6a 00 68 *"World initialized"`
+- print: `6a0068 *{"Scrap engine"} 6a?e8 $'`
+- console handler: `68 *{"import Viewer"} e8 $'`
+- DirectX Device: ``
\ No newline at end of file
diff --git a/tools/remaster/scraphacks_rs/run.py b/tools/remaster/scraphacks_rs/run.py
new file mode 100644
index 0000000..2cda825
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/run.py
@@ -0,0 +1,34 @@
+import subprocess as SP
+import shutil as sh
+import json
+from pathlib import Path
+import psutil
+import os
+import sys
+os.environ['DISCORD_INSTANCE_ID']='1'
+SP.check_call(["cargo","b","-r"])
+info=[json.loads(line) for line in SP.check_output(["cargo","b", "-r" ,"-q","--message-format=json"]).splitlines()]
+dll_path=None
+for line in info:
+ if line.get('reason')=="compiler-artifact" and ("dylib" in line.get("target",{}).get("crate_types",[])):
+ dll_path=Path(line['filenames'][0])
+
+sh.copy(dll_path,"E:/Games/Steam/steamapps/common/Scrapland/lib/ScrapHack.pyd")
+
+if "--run" not in sys.argv[1:]:
+ exit(0)
+
+os.startfile("steam://run/897610/")
+pid=None
+while pid is None:
+ for proc in psutil.process_iter():
+ try:
+ if proc.name()=="Scrap.exe":
+ pid=proc.pid
+ except:
+ pass
+print(f"PID: {pid:x}")
+if "--dbg" in sys.argv[1:]:
+ SP.run(["x32dbg","-p",str(pid)])
+# cp D:/devel/Git_Repos/Scrapland-RE/tools/remaster/scraphacks_rs/target/i686-pc-windows-msvc/release/scraphacks_rs.dll E:/Games/Steam/steamapps/common/Scrapland/lib/ScrapHack.pyd
+# x32dbg E:/Games/Steam/steamapps/common/Scrapland/Bin/Scrap.unpacked.exe "-debug:10 -console"
\ No newline at end of file
diff --git a/tools/remaster/scraphacks_rs/scrap.hpp b/tools/remaster/scraphacks_rs/scrap.hpp
new file mode 100644
index 0000000..a6c95ee
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/scrap.hpp
@@ -0,0 +1,16 @@
+#include
+struct HashTable {
+ uint32_t num_slots;
+ struct HashTableEntry **chains;
+};
+
+struct HashTableEntry {
+ void *data;
+ const char *name;
+ HashTableEntry *next;
+};
+
+struct World {
+ void** VMT;
+ HashTable *entities;
+};
\ No newline at end of file
diff --git a/tools/remaster/scraphacks_rs/src/config.rs b/tools/remaster/scraphacks_rs/src/config.rs
new file mode 100644
index 0000000..95585b7
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/src/config.rs
@@ -0,0 +1,7 @@
+enum FilePatch {
+
+}
+
+pub struct Config {
+ file_patches: FxHashMap
+}
\ No newline at end of file
diff --git a/tools/remaster/scraphacks_rs/src/discord.rs b/tools/remaster/scraphacks_rs/src/discord.rs
new file mode 100644
index 0000000..acc021b
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/src/discord.rs
@@ -0,0 +1,94 @@
+use std::{num::NonZeroU32, thread::JoinHandle, time::SystemTime};
+
+use crate::{cdbg, ceprintln, cprint, cprintln};
+use anyhow::{bail, Result};
+use discord_sdk::{
+ activity::{ActivityBuilder, Assets, PartyPrivacy, Secrets},
+ registration::{register_app, Application, LaunchCommand},
+ wheel::Wheel,
+ Discord, DiscordApp, Subscriptions,
+};
+const APP_ID: discord_sdk::AppId = 1066820570097930342;
+const STEAM_APP_ID: u32 = 897610;
+pub struct Client {
+ pub discord: discord_sdk::Discord,
+ pub user: discord_sdk::user::User,
+ pub wheel: discord_sdk::wheel::Wheel,
+}
+
+impl Client {
+ pub fn run() -> Result>> {
+ let rt = tokio::runtime::Builder::new_multi_thread()
+ .enable_all()
+ .build()?;
+ register_app(Application {
+ id: APP_ID,
+ name: Some("Scrapland Remastered".to_owned()),
+ command: LaunchCommand::Steam(STEAM_APP_ID),
+ })?;
+ Ok(std::thread::spawn(move || rt.block_on(Self::run_async())))
+ }
+ async fn run_async() -> Result<()> {
+ let (wheel, handler) = Wheel::new(Box::new(|err| {
+ ceprintln!("Encountered an error: {}", err);
+ }));
+ let mut user = wheel.user();
+ let discord = Discord::new(
+ DiscordApp::PlainId(APP_ID),
+ Subscriptions::ACTIVITY,
+ Box::new(handler),
+ )?;
+ user.0.changed().await?;
+ let user = match &*user.0.borrow() {
+ discord_sdk::wheel::UserState::Connected(user) => user.clone(),
+ discord_sdk::wheel::UserState::Disconnected(err) => {
+ ceprintln!("Failed to connect to Discord: {err}");
+ bail!("{}", err);
+ }
+ };
+ let uid = user.id;
+ cprintln!(
+ "Logged in as: {user}#{discriminator}",
+ user = user.username,
+ discriminator = user
+ .discriminator
+ .map(|d| d.to_string())
+ .unwrap_or_else(|| "????".to_owned())
+ );
+ let mut activity = ActivityBuilder::new()
+ .state("Testing")
+ .assets(Assets::default().large("scrap_logo", Some("Testing")))
+ .timestamps(Some(SystemTime::now()), Option::::None)
+ .details("Testing ScrapHack");
+ if false {
+ // (SCRAP.is_server()||SCRAP.is_client())
+ let players = 1;
+ let capacity = 32;
+ activity = activity
+ .instance(true)
+ .party(
+ "Testt",
+ NonZeroU32::new(players),
+ NonZeroU32::new(capacity),
+ if false {
+ PartyPrivacy::Private
+ } else {
+ PartyPrivacy::Public
+ }
+ )
+ .secrets(Secrets {
+ r#match: Some("MATCH".to_owned()), // Use server_ip+port
+ join: Some("JOIN".to_owned()), // Use server_ip+port
+ spectate: Some("SPECTATE".to_owned()), // Use server_ip+port
+ });
+ }
+
+ discord.update_activity(activity).await?;
+ loop {
+ if let Ok(req) = wheel.activity().0.try_recv() {
+ cprintln!("Got Join request: {req:?}");
+ }
+ }
+ Ok(())
+ }
+}
diff --git a/tools/remaster/scraphacks_rs/src/lib.rs b/tools/remaster/scraphacks_rs/src/lib.rs
new file mode 100644
index 0000000..c1bca1c
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/src/lib.rs
@@ -0,0 +1,96 @@
+#![feature(abi_thiscall)]
+#![feature(c_variadic)]
+mod discord;
+mod lua;
+mod mem;
+mod parser;
+mod scrap;
+use std::ffi::{c_char, c_void, CString};
+use anyhow::Result;
+use crate::mem::search;
+use crate::scrap::SCRAP;
+use shadow_rs::shadow;
+use winsafe::{co::{MB, CS, WS}, prelude::*, HWND, WNDCLASSEX, RegisterClassEx, WString};
+
+shadow!(build);
+
+custom_print::define_macros!({cprint, cprintln, cdbg}, fmt, |value: &str| {crate::scrap::SCRAP.print(value)});
+custom_print::define_macros!({ceprint, ceprintln}, fmt, |value: &str| {crate::scrap::SCRAP.print_c(0x800000,value)});
+
+#[allow(clippy::single_component_path_imports)]
+pub(crate) use {cdbg, cprint, cprintln};
+#[warn(clippy::single_component_path_imports)]
+pub(crate) use {ceprint, ceprintln};
+
+#[repr(C)]
+#[derive(Debug)]
+struct PyMethodDef {
+ name: *const c_char,
+ func: *const (*const c_void, *const c_void),
+ ml_flags: i32,
+ doc: *const c_char,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+struct PyModuleDef {
+ name: *const c_char,
+ methods: *const PyMethodDef,
+}
+
+fn init_py_mod() {
+ let py_init_module: fn(
+ *const c_char, // name
+ *const PyMethodDef, // methods
+ *const c_char, // doc
+ *const (), // passthrough
+ i32, // module_api_version
+ ) -> *const () =
+ unsafe { std::mem::transmute(search("68 *{\"Scrap\" 00} e8 ${'}", 1, None).unwrap_or_default()) };
+ let name = CString::new("ScrapHack").unwrap_or_default();
+ let desc = CString::new("ScrapHack Rust version").unwrap_or_default();
+ let methods: &[PyMethodDef] = &[PyMethodDef {
+ name: 0 as _,
+ func: 0 as _,
+ ml_flags: 0,
+ doc: 0 as _,
+ }];
+ assert!(
+ !py_init_module(name.as_ptr(), methods.as_ptr(), desc.as_ptr(), 0 as _, 1007).is_null()
+ );
+}
+
+#[no_mangle]
+pub extern "system" fn initScrapHack() {
+ #[cfg(feature = "console")]
+ unsafe {
+ AllocConsole();
+ }
+ std::panic::set_hook(Box::new(|info| {
+ ceprintln!("ScrapHacks: {info}");
+ HWND::DESKTOP
+ .MessageBox(&format!("{info}"), "ScrapHacks error", MB::ICONERROR)
+ .unwrap();
+ std::process::exit(1);
+ }));
+ init_py_mod();
+ print_version_info();
+ cprintln!("{SCRAP:#x?}");
+}
+
+#[no_mangle]
+pub extern "system" fn DllMain(_inst: isize, _reason: u32, _: *const u8) -> u32 {
+ 1
+}
+
+fn print_version_info() {
+ cprintln!(
+ "{} v{} ({} {}), built for {} by {}.",
+ build::PROJECT_NAME,
+ build::PKG_VERSION,
+ build::SHORT_COMMIT,
+ build::BUILD_TIME,
+ build::BUILD_TARGET,
+ build::RUST_VERSION
+ );
+}
diff --git a/tools/remaster/scraphacks_rs/src/lua.rs b/tools/remaster/scraphacks_rs/src/lua.rs
new file mode 100644
index 0000000..9442ece
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/src/lua.rs
@@ -0,0 +1,204 @@
+use std::{path::PathBuf, sync::Arc};
+
+use crate::{
+ cprintln,
+ mem::{get_module, get_modules},
+ parser::Cmd,
+};
+use anyhow::{anyhow, bail, Result};
+use detour3::GenericDetour;
+use mlua::{prelude::*, Variadic};
+use pelite::pattern;
+use pelite::pe32::{Pe, PeObject};
+use rustc_hash::FxHashMap;
+use winsafe::{prelude::*, HINSTANCE};
+
+struct Ptr(*const ());
+
+impl LuaUserData for Ptr {
+ fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
+ methods.add_meta_method(LuaMetaMethod::ToString, |_, this, _: ()| {
+ Ok(format!("{:p}", this.0))
+ });
+ methods.add_method("read", |_, this, (size,): (usize,)| {
+ let addr = this.0 as u32;
+ let ptr = this.0 as *const u8;
+ let info = region::query(ptr).map_err(mlua::Error::external)?;
+ let end = info.as_ptr_range::<()>().end as u32;
+ let size = ((end - addr) as usize).min(size);
+ if !info.is_readable() {
+ return Err(LuaError::external(anyhow!("No read permission on page")));
+ }
+ let data = unsafe { std::slice::from_raw_parts(ptr, size) };
+ Ok(data.to_vec())
+ });
+ methods.add_method("write", |_, this, data: Vec| {
+ let data = data.as_slice();
+ let addr = this.0 as *const u8;
+ unsafe {
+ let handle = region::protect_with_handle(
+ addr,
+ data.len(),
+ region::Protection::READ_WRITE_EXECUTE,
+ )
+ .map_err(mlua::Error::external)?;
+ std::ptr::copy(data.as_ptr(), addr as *mut u8, data.len());
+ drop(handle);
+ };
+ Ok(())
+ });
+ // methods.add_method("hook", |_, this, func: LuaFunction| -> LuaResult<()> {
+ // let addr = this.0;
+ // cprintln!("Hook: {func:?} @ {addr:p}");
+ // let dt = unsafe { GenericDetour:: u32>::new(std::mem::transmute(addr), hook_func) }.unwrap();
+ // Err(LuaError::external(anyhow!("TODO: hook")))
+ // });
+ }
+}
+
+// extern "thiscall" fn hook_func(this: *const (), args: (u32,u32,u32)) -> u32 {
+// return 0;
+// }
+
+pub(crate) fn init() -> Result {
+ let lua = unsafe { Lua::unsafe_new() };
+ {
+ let globals = lua.globals();
+ globals.set("scan", lua.create_function(lua_scan)?)?;
+ globals.set("print", lua.create_function(lua_print)?)?;
+ globals.set("hook", lua.create_function(lua_hook)?)?;
+ globals.set("imports", lua.create_function(lua_imports)?)?;
+ globals.set(
+ "ptr",
+ lua.create_function(|_, addr: u32| Ok(Ptr(addr as _)))?,
+ )?;
+ globals.set(
+ "ptr",
+ lua.create_function(lua_alloc)?,
+ )?;
+ }
+ Ok(lua)
+}
+
+fn lua_val_to_string(val: &LuaValue) -> LuaResult {
+ Ok(match val {
+ LuaNil => "Nil".to_owned(),
+ LuaValue::Boolean(b) => format!("{b}"),
+ LuaValue::LightUserData(u) => format!("{u:?}"),
+ LuaValue::Integer(i) => format!("{i}"),
+ LuaValue::Number(n) => format!("{n}"),
+ LuaValue::String(s) => (s.to_str()?).to_string(),
+ LuaValue::Table(t) => {
+ let mut vals = vec![];
+ for res in t.clone().pairs() {
+ let (k, v): (LuaValue, LuaValue) = res?;
+ vals.push(format!(
+ "{k} = {v}",
+ k = lua_val_to_string(&k)?,
+ v = lua_val_to_string(&v)?
+ ));
+ }
+ format!("{{{vals}}}", vals = vals.join(", "))
+ }
+ LuaValue::Function(f) => format!("{f:?}"),
+ LuaValue::Thread(t) => format!("{t:?}"),
+ LuaValue::UserData(u) => format!("{u:?}"),
+ LuaValue::Error(e) => format!("{e:?}"),
+ })
+}
+
+fn lua_alloc(lua: &Lua, size: usize) -> LuaResult {
+ let data = vec![0u8;size].into_boxed_slice();
+ Ok(Ptr(Box::leak(data).as_ptr() as _))
+}
+
+fn lua_hook(lua: &Lua, (addr, func): (u32, LuaFunction)) -> LuaResult<()> {
+ cprintln!("Hook: {func:?} @ {addr:08x}");
+ Err(LuaError::external(anyhow!("TODO: hook")))
+}
+
+fn lua_imports(lua: &Lua, (): ()) -> LuaResult<()> {
+ Err(LuaError::external(anyhow!("TODO: imports")))
+}
+
+fn lua_print(lua: &Lua, args: Variadic) -> LuaResult<()> {
+ let msg: Vec = args
+ .into_iter()
+ .map(|v| lua_val_to_string(&v))
+ .collect::>>()?;
+ cprintln!("{}", msg.join(" "));
+ Ok(())
+}
+
+#[derive(Debug)]
+enum ScanMode {
+ MainModule,
+ Modules(Vec),
+ All,
+}
+
+impl FromLua<'_> for ScanMode {
+ fn from_lua<'lua>(lua_value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult {
+ match &lua_value {
+ LuaValue::Nil => Ok(ScanMode::MainModule),
+ LuaValue::Boolean(true) => Ok(ScanMode::All),
+ LuaValue::Table(t) => Ok(ScanMode::Modules(FromLua::from_lua(lua_value, lua)?)),
+ _ => Err(LuaError::FromLuaConversionError {
+ from: lua_value.type_name(),
+ to: "scan_mode",
+ message: None,
+ }),
+ }
+ }
+}
+
+fn lua_scan(lua: &Lua, (pattern, scan_mode): (String, ScanMode)) -> LuaResult {
+ let pat = pattern::parse(&pattern).map_err(mlua::Error::external)?;
+ let mut ret = FxHashMap::default();
+ let modules = match scan_mode {
+ ScanMode::MainModule => vec![get_module(None).map_err(mlua::Error::external)?],
+ ScanMode::Modules(modules) => modules
+ .into_iter()
+ .map(|m| get_module(Some(m)))
+ .collect::>()
+ .map_err(mlua::Error::external)?,
+ ScanMode::All => get_modules().map_err(mlua::Error::external)?,
+ };
+ 'outer: for module in modules {
+ let regions = region::query_range(module.image().as_ptr(), module.image().len())
+ .map_err(mlua::Error::external)?;
+ for region in regions {
+ let Ok(region)=region else {
+ continue 'outer;
+ };
+ if !region.is_readable() {
+ continue 'outer;
+ }
+ }
+ let h_module = unsafe { HINSTANCE::from_ptr(module.image().as_ptr() as _) };
+ let module_name = PathBuf::from(
+ h_module
+ .GetModuleFileName()
+ .map_err(mlua::Error::external)?,
+ )
+ .file_name()
+ .unwrap()
+ .to_str()
+ .unwrap()
+ .to_owned();
+ if let Ok(res) = crate::mem::scan(&pat, &module) {
+ if !res.is_empty() {
+ let res: Vec> = res
+ .into_iter()
+ .map(|res| res.into_iter().map(|a| Ptr(a as _)).collect())
+ .collect();
+ ret.insert(module_name, res);
+ }
+ };
+ }
+ lua.create_table_from(ret.into_iter())
+}
+
+pub(crate) fn exec(chunk: &str) -> Result<()> {
+ Ok(init()?.load(chunk).set_name("ScrapLua")?.exec()?)
+}
diff --git a/tools/remaster/scraphacks_rs/src/mem.rs b/tools/remaster/scraphacks_rs/src/mem.rs
new file mode 100644
index 0000000..87ccda5
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/src/mem.rs
@@ -0,0 +1,94 @@
+use std::str::FromStr;
+
+use anyhow::anyhow;
+use anyhow::bail;
+use anyhow::Result;
+use pelite::pattern::parse;
+use pelite::pattern::save_len;
+use pelite::pattern::Atom;
+use pelite::pe32::{Pe, PeView};
+use winsafe::co::TH32CS;
+use winsafe::prelude::*;
+use winsafe::HINSTANCE;
+use winsafe::HPROCESSLIST;
+pub(crate) struct Pattern(Vec, usize);
+
+impl Pattern {
+ pub(crate) fn set_index(mut self, idx: usize) -> Self {
+ self.1 = idx;
+ self
+ }
+}
+
+impl FromStr for Pattern {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result {
+ Ok(Self(parse(s)?, 0))
+ }
+}
+
+impl Pattern {
+ pub(crate) fn scan(&self, module: Option) -> Result {
+ let pe = get_module(module)?;
+ let scan = pe.scanner();
+ let mut save = vec![0u32; save_len(&self.0)];
+ if !scan.finds(&self.0, 0..u32::MAX, &mut save) {
+ bail!("Pattern not found");
+ }
+ save.get(self.1)
+ .ok_or_else(|| anyhow!("Result index out of range"))
+ .and_then(|r| pe.rva_to_va(*r).map_err(|e| e.into()))
+ }
+}
+
+pub(crate) fn get_modules() -> Result>> {
+ let mut res = vec![];
+ let pid = std::process::id();
+ let mut h_snap = HPROCESSLIST::CreateToolhelp32Snapshot(TH32CS::SNAPMODULE, Some(pid))?;
+ for module in h_snap.iter_modules() {
+ res.push(unsafe { PeView::module(module?.hModule.as_ptr() as *const u8) });
+ }
+ Ok(res)
+}
+
+pub(crate) fn get_module(module: Option) -> Result> {
+ let hmodule = HINSTANCE::GetModuleHandle(module.as_deref())?;
+ Ok(unsafe { PeView::module(hmodule.as_ptr() as *const u8) })
+}
+
+pub(crate) fn scan(pat: &[Atom], pe: &PeView) -> Result>> {
+ let mut ret = vec![];
+ let scan = pe.scanner();
+ let mut m = scan.matches(pat, 0..u32::MAX);
+ let mut save = vec![0u32; save_len(pat)];
+ while m.next(&mut save) {
+ ret.push(
+ save.iter()
+ .map(|rva| pe.rva_to_va(*rva).map_err(|e| e.into()))
+ .collect::>>()?,
+ );
+ }
+ Ok(ret)
+}
+
+pub(crate) fn search(pat: &str, idx: usize, module: Option) -> Result {
+ pat.parse::()?.set_index(idx).scan(module)
+}
+
+fn addr_info(addr: u32) -> Result<()> {
+ let pid = std::process::id();
+ let mut h_snap = HPROCESSLIST::CreateToolhelp32Snapshot(TH32CS::SNAPMODULE, Some(pid))?;
+ for module in h_snap.iter_modules() {
+ let module = module?;
+ let module_name = module.szModule();
+ if module_name.to_lowercase() == "kernel32.dll" {
+ continue;
+ }
+ let mod_range =
+ unsafe { module.modBaseAddr..module.modBaseAddr.offset(module.modBaseSize as isize) };
+ println!("{module_name}: {mod_range:?}");
+ // let module = unsafe { PeView::module(module.modBaseAddr as *const u8) };
+ }
+ Ok(())
+}
diff --git a/tools/remaster/scraphacks_rs/src/parser.rs b/tools/remaster/scraphacks_rs/src/parser.rs
new file mode 100644
index 0000000..700c7b2
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/src/parser.rs
@@ -0,0 +1,177 @@
+// use crate::{cdbg, ceprintln, cprint, cprintln};
+use std::path::PathBuf;
+use std::str::FromStr;
+use anyhow::{anyhow, Result};
+use nom::branch::alt;
+use nom::bytes::complete::{take_till, take_while1};
+use nom::character::complete::{digit1, hex_digit1};
+use nom::character::streaming::char;
+use nom::combinator::{eof, opt, rest};
+use nom::sequence::{separated_pair, tuple};
+use nom::{IResult, Parser};
+use nom_locate::LocatedSpan;
+use nom_supreme::error::ErrorTree;
+use nom_supreme::final_parser::final_parser;
+use nom_supreme::tag::complete::{tag, tag_no_case};
+use nom_supreme::ParserExt;
+use pelite::pattern::{self, Atom};
+
+type Span<'a> = LocatedSpan<&'a str>;
+
+type ParseResult<'a, 'b, T> = IResult, T, ErrorTree>>;
+
+#[derive(Debug, Clone)]
+pub enum Cmd {
+ Imports,
+ Read(u32, usize),
+ ReadPE(u32, usize),
+ Write(u32, Vec),
+ Disams(u32, usize),
+ Info(Option),
+ Script(PathBuf),
+ Unload,
+ ScanModule(Vec, Option),
+ Lua(String),
+}
+
+impl FromStr for Cmd {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> Result {
+ match parse(s) {
+ Ok(cmd) => Ok(cmd),
+ Err(err) => Err(anyhow!("{}", err)),
+ }
+ }
+}
+
+fn ws(input: Span) -> ParseResult<()> {
+ take_while1(|c: char| c.is_whitespace())
+ .value(())
+ .context("Whitepace")
+ .parse(input)
+}
+
+//
+
+/// Test
+
+fn hex_bytes(input: Span) -> ParseResult> {
+ hex_digit1
+ .map_res_cut(hex::decode)
+ .context("Hex string")
+ .parse(input)
+}
+
+fn num(input: Span) -> ParseResult {
+ digit1
+ .map_res_cut(|n: Span| parse_int::parse(&n))
+ .context("Number")
+ .parse(input)
+}
+
+fn address(input: Span) -> ParseResult {
+ tag_no_case("0x")
+ .precedes(hex_digit1)
+ .recognize()
+ .map_res_cut(|addr: Span| parse_int::parse::(&addr))
+ .context("Memory address")
+ .parse(input)
+}
+
+fn parse_read_pe(input: Span) -> ParseResult {
+ tag("read_pe")
+ .precedes(ws)
+ .precedes(separated_pair(address, ws, num.opt()))
+ .map(|(addr, size)| Cmd::ReadPE(addr, size.unwrap_or(0x100)))
+ .parse(input)
+}
+
+fn parse_read(input: Span) -> ParseResult {
+ tag("read")
+ .precedes(ws)
+ .precedes(separated_pair(address, ws, num.opt()))
+ .map(|(addr, size)| Cmd::Read(addr, size.unwrap_or(0x100)))
+ .parse(input)
+}
+
+fn parse_disasm(input: Span) -> ParseResult {
+ tag("disasm")
+ .precedes(ws)
+ .precedes(separated_pair(address, ws, num.opt()))
+ .map(|(addr, size)| Cmd::Disams(addr, size.unwrap_or(50)))
+ .parse(input)
+}
+
+fn parse_write(input: Span) -> ParseResult {
+ tag("write")
+ .precedes(ws)
+ .precedes(separated_pair(address, ws, hex_bytes))
+ .map(|(addr, data)| Cmd::Write(addr, data))
+ .parse(input)
+}
+
+fn parse_info(input: Span) -> ParseResult {
+ tag("info")
+ .precedes(eof)
+ .value(Cmd::Info(None))
+ .or(tag("info")
+ .precedes(ws)
+ .precedes(address)
+ .map(|addr| Cmd::Info(Some(addr))))
+ .parse(input)
+}
+
+fn parse_scan(input: Span) -> ParseResult {
+ let (input, _) = tag("scan").parse(input)?;
+ let (input, module) =
+ opt(tuple((char(':'), take_till(|c: char| c.is_whitespace())))).parse(input)?;
+ let module = module.map(|(_, module)| module.fragment().to_string());
+ let (input, _) = ws.parse(input)?;
+ let (input, pattern) = rest
+ .map_res(|pat: Span| pattern::parse(&pat))
+ .parse(input)?;
+ Ok((input, Cmd::ScanModule(pattern, module)))
+}
+
+fn parse_unload(input: Span) -> ParseResult {
+ tag("unload").value(Cmd::Unload).parse(input)
+}
+
+fn parse_imports(input: Span) -> ParseResult {
+ tag("imports").value(Cmd::Imports).parse(input)
+}
+
+fn parse_lua(input: Span) -> ParseResult {
+ tag("lua")
+ .precedes(ws)
+ .precedes(rest)
+ .map(|s| Cmd::Lua(s.fragment().to_string()))
+ .parse(input)
+}
+
+fn parse_script(input: Span) -> ParseResult {
+ tag("script")
+ .precedes(ws)
+ .precedes(rest)
+ .map(|s| Cmd::Script(PathBuf::from(s.fragment())))
+ .parse(input)
+}
+
+fn parse(input: &str) -> Result>> {
+ final_parser(
+ alt((
+ parse_imports,
+ parse_unload,
+ parse_scan,
+ parse_info,
+ parse_write,
+ parse_read,
+ parse_read_pe,
+ parse_script,
+ parse_disasm,
+ parse_lua,
+ ))
+ .context("command"),
+ )(Span::new(input))
+}
diff --git a/tools/remaster/scraphacks_rs/src/scrap.rs b/tools/remaster/scraphacks_rs/src/scrap.rs
new file mode 100644
index 0000000..05790c1
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/src/scrap.rs
@@ -0,0 +1,381 @@
+use crate::{
+ cdbg, ceprintln, cprint, cprintln, lua,
+ mem::{get_module, scan, search},
+ parser::Cmd, discord,
+};
+use anyhow::{bail, Result};
+use derivative::Derivative;
+use detour3::GenericDetour;
+use futures::executor::block_on;
+use iced_x86::{Decoder, DecoderOptions, Formatter, Instruction, NasmFormatter};
+use once_cell::sync::Lazy;
+use pelite::{pe::PeView, pe32::Pe};
+use std::{
+ collections::HashMap,
+ ffi::{c_char, CStr, CString},
+ fmt::Debug,
+ ptr,
+ thread::JoinHandle,
+ time::Duration,
+};
+use winsafe::HINSTANCE;
+use winsafe::{co::TH32CS, prelude::*, HPROCESSLIST};
+
+const POINTER_SIZE: usize = std::mem::size_of::<*const ()>();
+
+#[repr(C)]
+struct VirtualMethodTable(*const *const ());
+
+impl Debug for VirtualMethodTable {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let mut methods = vec![];
+ for idx in 0.. {
+ let ptr = self.get::<()>(idx);
+ if ptr.is_null()
+ || !region::query(ptr)
+ .map(|r| r.is_executable())
+ .unwrap_or(false)
+ {
+ break;
+ }
+ methods.push(ptr);
+ }
+ f.debug_tuple("VMT").field(&methods).finish()
+ }
+}
+
+impl VirtualMethodTable {
+ fn get(&self, offset: usize) -> *const T {
+ unsafe { self.0.add(POINTER_SIZE * offset).read() as *const T }
+ }
+}
+
+#[derive(Derivative)]
+#[derivative(Debug)]
+pub struct Scrap {
+ print: extern "C" fn(u32, *const c_char, u8),
+ console_detour: GenericDetour,
+ world: WorldPointer,
+ discord_thread_handle: JoinHandle>,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+struct Entity {
+ vmt: VirtualMethodTable,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+struct HashTableEntry {
+ data: *const T,
+ name: *const c_char,
+ next: *const Self,
+}
+
+#[repr(C)]
+struct HashTable {
+ num_slots: u32,
+ chains: *const *const HashTableEntry,
+}
+
+fn try_read(ptr: *const T) -> Option {
+ (!ptr.is_null()).then(|| unsafe { ptr.read() })
+}
+
+impl std::fmt::Debug for HashTable {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let mut entries: HashMap> = HashMap::default();
+ for offset in 0..self.num_slots {
+ let offset = offset as _;
+ // let chain=vec![];
+ let mut chain_ptr = unsafe { self.chains.offset(offset).read() };
+ while !chain_ptr.is_null() {
+ let entry = unsafe { chain_ptr.read() };
+ let data = try_read(entry.data);
+ let key = unsafe { CStr::from_ptr(entry.name) }
+ .to_str()
+ .unwrap()
+ .to_owned();
+ chain_ptr = entry.next;
+ entries.insert(key, data);
+ }
+ }
+ f.debug_struct(&format!("HashTable @ {self:p} "))
+ .field("num_slots", &self.num_slots)
+ .field("entries", &entries)
+ .finish()
+ }
+}
+
+#[repr(C)]
+struct World {
+ vmt: VirtualMethodTable,
+ entities: HashTable,
+}
+
+impl Debug for World {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("World")
+ .field("vmt", &self.vmt)
+ .field("entities", &self.entities)
+ .finish()
+ }
+}
+
+struct WorldPointer(u32);
+
+impl std::fmt::Debug for WorldPointer {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let ptr = self.ptr();
+ let world = unsafe { ptr.read() };
+ f.debug_tuple(&format!("WorldPointer @ {ptr:p} "))
+ .field(&world)
+ .finish()
+ }
+}
+
+impl WorldPointer {
+ fn ptr(&self) -> *const World {
+ let ptr = self.0 as *const *const World;
+ unsafe { ptr.read() }
+ }
+
+ fn get_hashtable(&self) {
+ // let ents = unsafe { self.ptr().read().entities.read() };
+ // cprintln!("Ents: {ents:?}");
+ }
+}
+
+pub(crate) static SCRAP: Lazy =
+ Lazy::new(|| Scrap::init().expect("Failed to initialize Scrap data structure"));
+
+impl Scrap {
+ const PRINT_PATTERN: &str = r#"6a0068 *{"Scrap engine"} 6a?e8 $'"#;
+ const PY_EXEC: &str = r#"68 *{"import Viewer"} e8 $'"#;
+ const WORLD_PATTERN: &str = r#"8b 0d *{'} 68 *"CTFFriends""#;
+ fn init() -> Result {
+ let scrap = unsafe {
+ Self {
+ world: WorldPointer(search(Self::WORLD_PATTERN, 1, None)? as _),
+ print: std::mem::transmute(search(Self::PRINT_PATTERN, 1, None)?),
+ console_detour: GenericDetour::::new(
+ std::mem::transmute(search(Self::PY_EXEC, 1, None)?),
+ Self::console_input,
+ )?,
+ discord_thread_handle: discord::Client::run()?,
+ }
+ };
+ unsafe { scrap.console_detour.enable()? }
+ Ok(scrap)
+ }
+
+ extern "C" fn console_input(orig_line: *const c_char) {
+ let line = unsafe { CStr::from_ptr(orig_line) }.to_str();
+ let Ok(line) = line else {
+ return SCRAP.console_detour.call(orig_line);
+ };
+ if let Some(cmd) = line.strip_prefix('$') {
+ let res = cmd.parse().and_then(|cmd: Cmd| cmd.exec());
+ if let Err(err) = res {
+ ceprintln!("Error: {err}");
+ }
+ return;
+ };
+ SCRAP.console_detour.call(orig_line)
+ }
+
+ pub fn println(&self, msg: &str) {
+ self.println_c(0x008000, msg)
+ }
+
+ pub fn print(&self, msg: &str) {
+ self.print_c(0x008000, msg)
+ }
+
+ pub fn print_c(&self, col: u32, msg: &str) {
+ let col = (col & 0xffffff).swap_bytes() >> 8; // 0xRRGGBB -> 0xBBGGRR
+ let msg = CString::new(msg.to_string()).unwrap();
+ (self.print)(col, msg.as_ptr(), 0);
+ }
+
+ pub fn println_c(&self, col: u32, msg: &str) {
+ let msg = msg.to_owned() + "\n";
+ self.print_c(col, &msg)
+ }
+}
+
+impl Cmd {
+ pub(crate) fn exec(&self) -> Result<()> {
+ let pe = get_module(None)?;
+ match self {
+ Cmd::Imports => {
+ for import in pe.imports()? {
+ let iat = import.iat()?;
+ let int = import.int()?;
+ for (func, imp) in iat.zip(int) {
+ let imp = imp?;
+ cprintln!(
+ "{addr:p}: {name} {imp:?}",
+ name = import.dll_name()?,
+ addr = func
+ );
+ }
+ }
+ }
+ Cmd::Read(addr, size) => {
+ let ptr = *addr as *const u8;
+ let info = region::query(ptr)?;
+ let end = info.as_ptr_range::<()>().end as u32;
+ let size = ((end - addr) as usize).min(*size);
+ if !info.is_readable() {
+ bail!("No read permission on page");
+ }
+ let data = unsafe { std::slice::from_raw_parts(ptr, size) };
+ cprintln!("{}", &rhexdump::hexdump_offset(data, *addr));
+ }
+ Cmd::Disams(addr, size) => {
+ let ptr = *addr as *const u8;
+ let info = region::query(ptr)?;
+ let end = info.as_ptr_range::<()>().end as u32;
+ let size = ((end - addr) as usize).min(*size);
+ if !info.is_readable() {
+ bail!("No read permission on page");
+ }
+ let data = unsafe { std::slice::from_raw_parts(ptr, size) };
+ let mut decoder = Decoder::with_ip(32, data, *addr as u64, DecoderOptions::NONE);
+ let mut instruction = Instruction::default();
+ let mut output = String::new();
+ let mut formatter = NasmFormatter::new();
+ while decoder.can_decode() {
+ decoder.decode_out(&mut instruction);
+ output.clear();
+ formatter.format(&instruction, &mut output);
+ cprint!("{:016X} ", instruction.ip());
+ let start_index = (instruction.ip() - (*addr as u64)) as usize;
+ let instr_bytes = &data[start_index..start_index + instruction.len()];
+ for b in instr_bytes.iter() {
+ cprint!("{:02X}", b);
+ }
+ cprintln!(" {}", output);
+ }
+ }
+ Cmd::Write(addr, data) => {
+ let data = data.as_slice();
+ let addr = *addr as *const u8;
+ unsafe {
+ let handle = region::protect_with_handle(
+ addr,
+ data.len(),
+ region::Protection::READ_WRITE_EXECUTE,
+ )?;
+ std::ptr::copy(data.as_ptr(), addr as *mut u8, data.len());
+ drop(handle);
+ };
+ }
+ Cmd::ReadPE(addr, size) => {
+ if !region::query(*addr as *const ())?.is_readable() {
+ bail!("No read permission for 0x{addr:x}");
+ }
+ let data = pe.read_bytes(*addr)?;
+ cprintln!("{}", &rhexdump::hexdump_offset(&data[..*size], *addr));
+ }
+ Cmd::Info(None) => {
+ let regions = region::query_range(ptr::null::<()>(), usize::MAX)?;
+ for region in regions.flatten() {
+ cprintln!(
+ "{:?}: {}",
+ region.as_ptr_range::<*const ()>(),
+ region.protection()
+ );
+ }
+ }
+ Cmd::Info(Some(addr)) => {
+ let info = region::query(*addr as *const ())?;
+ cprintln!(
+ "{:?}: {}",
+ info.as_ptr_range::<*const ()>(),
+ info.protection()
+ );
+ }
+ Cmd::ScanModule(pat, module) => {
+ cprintln!("{:?}", pat);
+ let mut total_hits = 0;
+ let mut modules = vec![];
+ let is_wildcard = matches!(module.as_deref(), Some("*"));
+ if is_wildcard {
+ let pid = std::process::id();
+ let mut h_snap =
+ HPROCESSLIST::CreateToolhelp32Snapshot(TH32CS::SNAPMODULE, Some(pid))?;
+ for module in h_snap.iter_modules() {
+ let module = module?;
+ let module_name = module.szModule();
+ let module_addr = module.hModule.as_ptr() as *const u8;
+ let module = region::query_range(module_addr, module.modBaseSize as usize)?
+ .all(|m| m.ok().map(|m| m.is_readable()).unwrap_or(false))
+ .then(|| unsafe { PeView::module(module_addr) });
+ if let Some(module) = module {
+ modules.push((module_name, module));
+ }
+ }
+ } else {
+ let module = HINSTANCE::GetModuleHandle(module.as_deref())?;
+ let module_name = module.GetModuleFileName()?;
+ let module_addr = module.as_ptr() as *const u8;
+ let module = region::query(module_addr)
+ .map(|m| m.is_readable())
+ .unwrap_or(false)
+ .then(|| unsafe { PeView::module(module_addr) });
+ if let Some(module) = module {
+ modules.push((module_name, module));
+ };
+ }
+ for (module_name, pe) in modules {
+ let res = scan(pat, &pe)?;
+ if res.is_empty() {
+ continue;
+ }
+ total_hits += res.len();
+ cprintln!("Module: {module_name}");
+ let sections = pe.section_headers();
+ for hit in &res {
+ for (idx, addr) in hit.iter().enumerate() {
+ let mut section_name = String::from("");
+ if let Ok(section_rva) = pe.va_to_rva(*addr) {
+ if let Some(section) = sections.by_rva(section_rva) {
+ section_name = match section.name() {
+ Ok(name) => name.to_string(),
+ Err(name_bytes) => format!("{name_bytes:?}"),
+ };
+ } else {
+ section_name = String::from("");
+ }
+ };
+ if let Ok(region) = region::query(addr) {
+ cprintln!(
+ "\t{}: {:?} {} [{}] {:p}",
+ idx,
+ region.as_ptr_range::<()>(),
+ region.protection(),
+ section_name,
+ addr
+ )
+ }
+ }
+ }
+ }
+ cprintln!("Results: {total_hits}");
+ }
+ Cmd::Lua(code) => {
+ lua::exec(code)?;
+ }
+ Cmd::Script(path) => {
+ for line in std::fs::read_to_string(path)?.lines() {
+ line.parse().and_then(|cmd: Cmd| cmd.exec())?;
+ }
+ }
+ other => bail!("Not implemented: {other:?}"),
+ }
+ Ok(())
+ }
+}
diff --git a/tools/remaster/scraphacks_rs/target/.rustc_info.json b/tools/remaster/scraphacks_rs/target/.rustc_info.json
new file mode 100644
index 0000000..c0733dc
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/target/.rustc_info.json
@@ -0,0 +1 @@
+{"rustc_fingerprint":18143952876974389501,"outputs":{"16636649553340150347":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\Earthnuker\\scoop\\persist\\rustup-msvc\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\nfeature=\"cargo-clippy\"\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"32\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.70.0-nightly (9df3a39fb 2023-04-11)\nbinary: rustc\ncommit-hash: 9df3a39fb30575d808e70800f9fad5362aac57a2\ncommit-date: 2023-04-11\nhost: x86_64-pc-windows-msvc\nrelease: 1.70.0-nightly\nLLVM version: 16.0.2\n","stderr":""},"1185988223601034215":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\Earthnuker\\scoop\\persist\\rustup-msvc\\.rustup\\toolchains\\nightly-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\nfeature=\"cargo-clippy\"\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"pc\"\nwindows\n","stderr":""}},"successes":{}}
\ No newline at end of file
diff --git a/tools/remaster/scraphacks_rs/target/CACHEDIR.TAG b/tools/remaster/scraphacks_rs/target/CACHEDIR.TAG
new file mode 100644
index 0000000..20d7c31
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/target/CACHEDIR.TAG
@@ -0,0 +1,3 @@
+Signature: 8a477f597d28d172789f06886806bc55
+# This file is a cache directory tag created by cargo.
+# For information about cache directory tags see https://bford.info/cachedir/
diff --git a/tools/remaster/scraphacks_rs/target/i686-pc-windows-msvc/CACHEDIR.TAG b/tools/remaster/scraphacks_rs/target/i686-pc-windows-msvc/CACHEDIR.TAG
new file mode 100644
index 0000000..20d7c31
--- /dev/null
+++ b/tools/remaster/scraphacks_rs/target/i686-pc-windows-msvc/CACHEDIR.TAG
@@ -0,0 +1,3 @@
+Signature: 8a477f597d28d172789f06886806bc55
+# This file is a cache directory tag created by cargo.
+# For information about cache directory tags see https://bford.info/cachedir/