diff --git a/package-lock.json b/package-lock.json index 5725d9255..b3fed7535 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "cookie-parser": "^1.4.6", "cookie-session": "^2.1.0", "cors": "^2.8.5", + "crc": "^4.3.2", "csrf-sync": "^4.0.3", "diff-match-patch": "^1.0.5", "dompurify": "^3.2.4", @@ -76,7 +77,6 @@ "node-persist": "^4.0.4", "open": "^8.4.2", "png-chunk-text": "^1.0.0", - "png-chunks-encode": "^1.0.0", "png-chunks-extract": "^1.0.0", "proxy-agent": "^6.5.0", "rate-limiter-flexible": "^5.0.5", @@ -121,7 +121,6 @@ "@types/node": "^18.19.80", "@types/node-persist": "^3.1.8", "@types/png-chunk-text": "^1.0.3", - "@types/png-chunks-encode": "^1.0.2", "@types/png-chunks-extract": "^1.0.2", "@types/response-time": "^2.3.8", "@types/select2": "^4.0.63", @@ -2033,13 +2032,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/png-chunks-encode": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/png-chunks-encode/-/png-chunks-encode-1.0.2.tgz", - "integrity": "sha512-Dxn0aXEcSg1wVeHjvNlygm/+fKBDzWMCdxJYhjGUTeefFW/jYxWcrg+W7ppLBfH44iJMqeVBHtHBwtYQUeYvgw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/png-chunks-extract": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/png-chunks-extract/-/png-chunks-extract-1.0.2.tgz", @@ -3542,6 +3534,23 @@ "node": ">= 0.10" } }, + "node_modules/crc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/crc/-/crc-4.3.2.tgz", + "integrity": "sha512-uGDHf4KLLh2zsHa8D8hIQ1H/HtFQhyHrc0uhHBcoKGol/Xnb+MPYfUMw7cvON6ze/GUESTudKayDcJC5HnJv1A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "buffer": ">=6.0.3" + }, + "peerDependenciesMeta": { + "buffer": { + "optional": true + } + } + }, "node_modules/crc-32": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-0.3.0.tgz", @@ -6686,16 +6695,6 @@ "integrity": "sha512-DEROKU3SkkLGWNMzru3xPVgxyd48UGuMSZvioErCure6yhOc/pRH2ZV+SEn7nmaf7WNf3NdIpH+UTrRdKyq9Lw==", "license": "MIT" }, - "node_modules/png-chunks-encode": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/png-chunks-encode/-/png-chunks-encode-1.0.0.tgz", - "integrity": "sha512-J1jcHgbQRsIIgx5wxW9UmCymV3wwn4qCCJl6KYgEU/yHCh/L2Mwq/nMOkRPtmV79TLxRZj5w3tH69pvygFkDqA==", - "license": "MIT", - "dependencies": { - "crc-32": "^0.3.0", - "sliced": "^1.0.1" - } - }, "node_modules/png-chunks-extract": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/png-chunks-extract/-/png-chunks-extract-1.0.0.tgz", @@ -7532,12 +7531,6 @@ "dev": true, "license": "ISC" }, - "node_modules/sliced": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==", - "license": "MIT" - }, "node_modules/slidetoggle": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slidetoggle/-/slidetoggle-4.0.0.tgz", diff --git a/package.json b/package.json index 97ea13702..7d1adae9a 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "cookie-parser": "^1.4.6", "cookie-session": "^2.1.0", "cors": "^2.8.5", + "crc": "^4.3.2", "csrf-sync": "^4.0.3", "diff-match-patch": "^1.0.5", "dompurify": "^3.2.4", @@ -66,7 +67,6 @@ "node-persist": "^4.0.4", "open": "^8.4.2", "png-chunk-text": "^1.0.0", - "png-chunks-encode": "^1.0.0", "png-chunks-extract": "^1.0.0", "proxy-agent": "^6.5.0", "rate-limiter-flexible": "^5.0.5", @@ -151,7 +151,6 @@ "@types/node": "^18.19.80", "@types/node-persist": "^3.1.8", "@types/png-chunk-text": "^1.0.3", - "@types/png-chunks-encode": "^1.0.2", "@types/png-chunks-extract": "^1.0.2", "@types/response-time": "^2.3.8", "@types/select2": "^4.0.63", diff --git a/src/character-card-parser.js b/src/character-card-parser.js index 3f9044e83..dd63b00d8 100644 --- a/src/character-card-parser.js +++ b/src/character-card-parser.js @@ -1,7 +1,7 @@ import fs from 'node:fs'; import { Buffer } from 'node:buffer'; -import encode from 'png-chunks-encode'; +import encode from './png/encode.js'; import extract from 'png-chunks-extract'; import PNGtext from 'png-chunk-text'; diff --git a/src/png/encode.js b/src/png/encode.js new file mode 100644 index 000000000..d4cac7540 --- /dev/null +++ b/src/png/encode.js @@ -0,0 +1,68 @@ +import { crc32 } from 'crc'; + +/** + * Encodes PNG chunks into a PNG file format buffer. + * @param {Array<{ name: string; data: Uint8Array }>} chunks Array of PNG chunks + * @returns {Uint8Array} Encoded PNG data + * @copyright Based on https://github.com/hughsk/png-chunks-encode (MIT) + */ +export default function encode(chunks) { + const uint8 = new Uint8Array(4); + const int32 = new Int32Array(uint8.buffer); + const uint32 = new Uint32Array(uint8.buffer); + + let totalSize = 8; + let idx = totalSize; + + for (let i = 0; i < chunks.length; i++) { + totalSize += chunks[i].data.length; + totalSize += 12; + } + + const output = new Uint8Array(totalSize); + + output[0] = 0x89; + output[1] = 0x50; + output[2] = 0x4E; + output[3] = 0x47; + output[4] = 0x0D; + output[5] = 0x0A; + output[6] = 0x1A; + output[7] = 0x0A; + + for (let i = 0; i < chunks.length; i++) { + const { name, data } = chunks[i]; + const size = data.length; + const nameChars = [ + name.charCodeAt(0), + name.charCodeAt(1), + name.charCodeAt(2), + name.charCodeAt(3), + ]; + + uint32[0] = size; + output[idx++] = uint8[3]; + output[idx++] = uint8[2]; + output[idx++] = uint8[1]; + output[idx++] = uint8[0]; + + output[idx++] = nameChars[0]; + output[idx++] = nameChars[1]; + output[idx++] = nameChars[2]; + output[idx++] = nameChars[3]; + + for (let j = 0; j < size;) { + output[idx++] = data[j++]; + } + + const crc = crc32(data, crc32(new Uint8Array(nameChars))); + + int32[0] = crc; + output[idx++] = uint8[3]; + output[idx++] = uint8[2]; + output[idx++] = uint8[1]; + output[idx++] = uint8[0]; + } + + return output; +}