Initial commit
This commit is contained in:
commit
e71a1939ba
10
.editorconfig
Executable file
10
.editorconfig
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
# editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
20
.github/ISSUE_TEMPLATE/BUG.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/BUG.md
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: '🐛 Bug Report'
|
||||||
|
about: 'Report an unexpected problem or unintended behavior.'
|
||||||
|
title: '[Bug]'
|
||||||
|
labels: 'bug'
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please provide a clear and concise description of what the bug is. Include
|
||||||
|
screenshots if needed. Please make sure your issue has not already been fixed.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Steps To Reproduce
|
||||||
|
|
||||||
|
1. Step 1
|
||||||
|
2. Step 2
|
||||||
|
|
||||||
|
## The current behavior
|
||||||
|
|
||||||
|
## The expected behavior
|
20
.github/ISSUE_TEMPLATE/IMPROVEMENT.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/IMPROVEMENT.md
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: '🔧 Improvement'
|
||||||
|
about: 'Improve structure/format/performance/refactor/tests of the code.'
|
||||||
|
title: '[Improvement]'
|
||||||
|
labels: 'improvement'
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- Please make sure your issue has not already been fixed. -->
|
||||||
|
|
||||||
|
## Type of Improvement
|
||||||
|
|
||||||
|
<!-- Please uncomment the type of improvements this issue address -->
|
||||||
|
|
||||||
|
<!-- Files and Folders Structure -->
|
||||||
|
<!-- Performance -->
|
||||||
|
<!-- Refactoring code -->
|
||||||
|
<!-- Tests -->
|
||||||
|
<!-- Not Sure? -->
|
||||||
|
|
||||||
|
## Proposal
|
29
.github/workflows/lint.yml
vendored
Normal file
29
.github/workflows/lint.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
name: 'Linting code'
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [develop]
|
||||||
|
pull_request:
|
||||||
|
branches: [develop]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
linting:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3.0.0
|
||||||
|
- uses: actions/setup-node@v3.0.0
|
||||||
|
with:
|
||||||
|
node-version: 18.x
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Linting prettier
|
||||||
|
run: yarn lint:prettier
|
||||||
|
|
||||||
|
- name: Linting typescript
|
||||||
|
run: yarn lint:typescript
|
||||||
|
|
||||||
|
- name: Checking editorconfig
|
||||||
|
run: yarn lint:editorconfig
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
dist
|
10
README.md
Normal file
10
README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Good to know
|
||||||
|
|
||||||
|
## 🌌 Magic numbers (5.2 PNG Signature)
|
||||||
|
|
||||||
|
Identifying file formats in binary can be a bit tricky. In fact, they are magic numbers when reading the first bytes:
|
||||||
|
|
||||||
|
- `PNG`: 0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A
|
||||||
|
- `GIF`: 0x47 0x49 0x46 0x38 0x39 0x61
|
||||||
|
- `JPEG`: 0xFF 0xD8
|
||||||
|
- `ERRONEOUS_JPEG`: 0xff, 0xd9, 0xff, 0xd8, 0xff, 0xd8
|
38
package.json
Normal file
38
package.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "@rypidev/shockwave-bundler",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Walidoux",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "ts-node-dev --respawn src/index.ts",
|
||||||
|
"build": "rm -rf dist && tsc -p tsconfig.json",
|
||||||
|
"start": "yarn build && ts-node dist/index.js",
|
||||||
|
"lint:typescript": "eslint \"**/*.ts\" --ignore-path \".gitignore\" && tsc --noemit",
|
||||||
|
"lint:prettier": "prettier \".\" --check --ignore-path \".gitignore\"",
|
||||||
|
"lint:editorconfig": "editorconfig-checker"
|
||||||
|
},
|
||||||
|
"prettier": "@walidoux/prettier-config",
|
||||||
|
"dependencies": {
|
||||||
|
"swf-parser": "0.14.1",
|
||||||
|
"swf-types": "0.14.0",
|
||||||
|
"ts-node": "10.9.1",
|
||||||
|
"ts-node-dev": "1.1.8",
|
||||||
|
"free-tex-packer-core": "^0.3.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tsconfig/recommended": "^1.0.2",
|
||||||
|
"@types/node": "^18.15.11",
|
||||||
|
"@walidoux/eslint-config": "1.0.3",
|
||||||
|
"@walidoux/prettier-config": "1.0.3",
|
||||||
|
"editorconfig-checker": "^5.0.1",
|
||||||
|
"eslint": "8.38.0",
|
||||||
|
"prettier": "2.8.7",
|
||||||
|
"typescript": "^5.0.4"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"@walidoux/eslint-config"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
305
src/Gist.ts
Normal file
305
src/Gist.ts
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
import { readFile } from "fs";
|
||||||
|
import { tags, Tag } from "swf-types";
|
||||||
|
import streamToArray from "stream-to-array";
|
||||||
|
import encoder from "png-stream/encoder";
|
||||||
|
|
||||||
|
import { unzip } from "zlib";
|
||||||
|
import { promisify } from "util";
|
||||||
|
|
||||||
|
export class SWFBuffer {
|
||||||
|
public static RECORDHEADER_LENTH_FULL: number = 0x3f;
|
||||||
|
public static EOS: number = 0x00;
|
||||||
|
public static STYLE_COUNT_EXT: number = 0xff;
|
||||||
|
|
||||||
|
public buffer: Buffer = null;
|
||||||
|
public pointer: number = 0;
|
||||||
|
public position: number = 1;
|
||||||
|
public current: number = 0;
|
||||||
|
public length: number = 0;
|
||||||
|
|
||||||
|
constructor(buffer: Buffer) {
|
||||||
|
if (!Buffer.isBuffer(buffer)) throw new Error("invalid_buffer");
|
||||||
|
|
||||||
|
this.buffer = buffer;
|
||||||
|
this.length = buffer.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readUIntLE(bits: number): number {
|
||||||
|
let value = 0;
|
||||||
|
|
||||||
|
switch (bits) {
|
||||||
|
case 16:
|
||||||
|
value = this.buffer.readUInt16LE(this.pointer);
|
||||||
|
break;
|
||||||
|
case 32:
|
||||||
|
value = this.buffer.readUInt32LE(this.pointer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pointer += bits / 8;
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readUInt8(): number {
|
||||||
|
return this.buffer.readUInt8(this.pointer++);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readEncodedU32(): number {
|
||||||
|
let i = 5;
|
||||||
|
let result = 0;
|
||||||
|
let nb = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
result += nb = this.nextByte();
|
||||||
|
} while (nb & 128 && --i);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readRGB(): [number, number, number] {
|
||||||
|
return [this.readUInt8(), this.readUInt8(), this.readUInt8()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public readRGBA(): [number, number, number, number] {
|
||||||
|
return [...this.readRGB(), this.readUInt8()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public readString(encoding: BufferEncoding = "utf8"): string {
|
||||||
|
const init = this.pointer;
|
||||||
|
|
||||||
|
while (this.readUInt8() !== SWFBuffer.EOS);
|
||||||
|
|
||||||
|
return this.buffer.toString(encoding || "utf8", init, this.pointer - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readStyleArray(buffer: SWFBuffer, next) {
|
||||||
|
let styleArrayCount = buffer.readUInt8();
|
||||||
|
const styles = [];
|
||||||
|
|
||||||
|
if (styleArrayCount === SWFBuffer.STYLE_COUNT_EXT)
|
||||||
|
styleArrayCount = buffer.readUIntLE(16);
|
||||||
|
|
||||||
|
for (let i = 0; i < styleArrayCount; i++) styles.push(next(buffer));
|
||||||
|
|
||||||
|
return styles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readFillStyle(buffer: SWFBuffer): {
|
||||||
|
fillStyleType: number;
|
||||||
|
color?: [number, number, number, number];
|
||||||
|
bitmapId?: number;
|
||||||
|
} {
|
||||||
|
const type = buffer.readUInt8();
|
||||||
|
|
||||||
|
const fillStyle: {
|
||||||
|
fillStyleType: number;
|
||||||
|
color?: [number, number, number, number];
|
||||||
|
bitmapId?: number;
|
||||||
|
} = {
|
||||||
|
fillStyleType: type,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 0x00:
|
||||||
|
fillStyle.color = buffer.readRGBA();
|
||||||
|
break;
|
||||||
|
case 0x10:
|
||||||
|
case 0x12:
|
||||||
|
case 0x13:
|
||||||
|
console.log("Gradient");
|
||||||
|
break;
|
||||||
|
case 0x40:
|
||||||
|
case 0x41:
|
||||||
|
case 0x42:
|
||||||
|
case 0x43:
|
||||||
|
fillStyle.bitmapId = buffer.readUIntLE(16);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fillStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readLineStyle(buffer: SWFBuffer): {
|
||||||
|
width: number;
|
||||||
|
color: [number, number, number, number];
|
||||||
|
} {
|
||||||
|
return {
|
||||||
|
width: buffer.readUIntLE(16) / 20,
|
||||||
|
color: buffer.readRGBA(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public readShapeRecords(buffer: SWFBuffer) {
|
||||||
|
let shapeRecords = null;
|
||||||
|
const typeFlag = buffer.readBits(1);
|
||||||
|
let eos = 0;
|
||||||
|
|
||||||
|
while ((eos = buffer.readBits(5))) {
|
||||||
|
if (0 === typeFlag) {
|
||||||
|
shapeRecords = {
|
||||||
|
type: "STYLECHANGERECORD",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return shapeRecords;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readShapeWithStyle() {
|
||||||
|
return {
|
||||||
|
fillStyles: this.readStyleArray(this, this.readFillStyle),
|
||||||
|
lineStyles: this.readStyleArray(this, this.readLineStyle),
|
||||||
|
numFillBits: this.readBits(4),
|
||||||
|
numLineBits: this.readBits(4),
|
||||||
|
shapeRecords: this.readShapeRecords(this),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public readTagCodeAndLength(): { code: number; length: number } {
|
||||||
|
if (this.pointer === this.length) return null;
|
||||||
|
|
||||||
|
const n = this.readUIntLE(16);
|
||||||
|
const tagType = n >> 6;
|
||||||
|
let tagLength = n & SWFBuffer.RECORDHEADER_LENTH_FULL;
|
||||||
|
|
||||||
|
if (n === 0) return null;
|
||||||
|
|
||||||
|
if (tagLength === SWFBuffer.RECORDHEADER_LENTH_FULL)
|
||||||
|
tagLength = this.readUIntLE(32);
|
||||||
|
|
||||||
|
return { code: tagType, length: tagLength };
|
||||||
|
}
|
||||||
|
|
||||||
|
public readRect(): { x: number; y: number; width: number; height: number } {
|
||||||
|
this.start();
|
||||||
|
|
||||||
|
const NBits = this.readBits(5);
|
||||||
|
const Xmin = this.readBits(NBits, true) / 20;
|
||||||
|
const Xmax = this.readBits(NBits, true) / 20;
|
||||||
|
const Ymin = this.readBits(NBits, true) / 20;
|
||||||
|
const Ymax = this.readBits(NBits, true) / 20;
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: Xmin,
|
||||||
|
y: Ymin,
|
||||||
|
width: Xmax > Xmin ? Xmax - Xmin : Xmin - Xmax,
|
||||||
|
height: Ymax > Ymin ? Ymax - Ymin : Ymin - Ymax,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public seek(pos: number): void {
|
||||||
|
this.pointer = pos % this.buffer.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public start(): void {
|
||||||
|
this.current = this.nextByte();
|
||||||
|
this.position = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public nextByte(): number {
|
||||||
|
return this.pointer > this.buffer.length
|
||||||
|
? null
|
||||||
|
: this.buffer[this.pointer++];
|
||||||
|
}
|
||||||
|
|
||||||
|
public readBits(b: number, signed: boolean = false): number {
|
||||||
|
let n = 0;
|
||||||
|
let r = 0;
|
||||||
|
|
||||||
|
const sign =
|
||||||
|
signed && ++n && (this.current >> (8 - this.position++)) & 1 ? -1 : 1;
|
||||||
|
|
||||||
|
while (n++ < b) {
|
||||||
|
if (this.position > 8) this.start();
|
||||||
|
|
||||||
|
r = (r << 1) + ((this.current >> (8 - this.position++)) & 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sign * r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ReadImage = async (bitmap: tags.DefineBitmap) => {
|
||||||
|
const buffer = new SWFBuffer(Buffer.from(bitmap.data));
|
||||||
|
// const bitmapFormat = buffer[0];
|
||||||
|
|
||||||
|
const bitmapFormat = buffer.readUInt8();
|
||||||
|
const bitmapWidth = buffer.readUIntLE(16);
|
||||||
|
const bitmapHeight = buffer.readUIntLE(16);
|
||||||
|
|
||||||
|
console.log(bitmapFormat);
|
||||||
|
console.log(bitmapWidth);
|
||||||
|
console.log(bitmapHeight);
|
||||||
|
|
||||||
|
const zlibBitmapData = buffer.buffer.slice(buffer.pointer);
|
||||||
|
const dataBuf = await promisify(unzip)(zlibBitmapData);
|
||||||
|
const pngEncoder = new encoder(bitmap.width, bitmap.height, {
|
||||||
|
colorSpace: "rgba",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bitmapFormat !== 5) {
|
||||||
|
// you'll have to figure this out yourself.
|
||||||
|
throw Error("Unsupported bitmap format");
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
let ptr = 0;
|
||||||
|
|
||||||
|
const output = Buffer.alloc(bitmap.width * bitmap.height * 4);
|
||||||
|
|
||||||
|
for (let x = 0; x < bitmap.height; x++) {
|
||||||
|
for (let y = 0; y < bitmap.width; y++) {
|
||||||
|
const alpha = dataBuf[ptr];
|
||||||
|
output[index] = dataBuf[ptr + 1] * (255 / alpha);
|
||||||
|
output[index + 1] = dataBuf[ptr + 2] * (255 / alpha);
|
||||||
|
output[index + 2] = dataBuf[ptr + 3] * (255 / alpha);
|
||||||
|
output[index + 3] = alpha;
|
||||||
|
index += 4;
|
||||||
|
ptr += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pngEncoder.end(output);
|
||||||
|
|
||||||
|
const parts = await streamToArray(pngEncoder);
|
||||||
|
const buffers = parts.map((part) =>
|
||||||
|
Buffer.isBuffer(part) ? part : Buffer.from(part)
|
||||||
|
);
|
||||||
|
|
||||||
|
const base64String = Buffer.concat(buffers).toString("base64");
|
||||||
|
const imgSrc = `data:image/png;base64,${base64String}`;
|
||||||
|
|
||||||
|
console.log(imgSrc);
|
||||||
|
};
|
||||||
|
|
||||||
|
readFile(process.cwd() + "/swfs/bunny.swf", async (_, data) => {
|
||||||
|
// Define type compression from reading the three first bytes of the file
|
||||||
|
// defineTypeCompression(data)
|
||||||
|
|
||||||
|
const SWF = (await import("swf-parser")).parseSwf(data);
|
||||||
|
|
||||||
|
const images = SWF.tags.filter((tag) => {
|
||||||
|
const mediaType = (tag as tags.DefineBitmap).mediaType;
|
||||||
|
|
||||||
|
if (String(mediaType).startsWith("image")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}) as Extract<Tag, tags.DefineBitmap>[];
|
||||||
|
|
||||||
|
const className = (
|
||||||
|
SWF.tags.filter(
|
||||||
|
(tag) => (tag as tags.PlaceObject).name
|
||||||
|
)[0] as tags.PlaceObject
|
||||||
|
).name;
|
||||||
|
const current = images.map(async (img) => ({
|
||||||
|
path: className as string,
|
||||||
|
|
||||||
|
// Convertion from Uint8Array to Buffer: Buffer.from(img.data)
|
||||||
|
// we need to find a way for reading this buffer, can it be read in base64 ?
|
||||||
|
contents: await ReadImage(img),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// unhandled error: "free-tex-packer-core: Error reading, mine must be a string" ?
|
||||||
|
// const packerAtlas = await packAsync(current)
|
||||||
|
});
|
45
src/index.ts
Normal file
45
src/index.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { readFile } from 'node:fs'
|
||||||
|
|
||||||
|
import type { tags } from 'swf-types'
|
||||||
|
|
||||||
|
const defineTypeCompression = (data: Buffer): string => {
|
||||||
|
// Define type compression from reading the three first bytes of the file
|
||||||
|
// defineTypeCompression(data)
|
||||||
|
|
||||||
|
return Array.from({ length: 3 })
|
||||||
|
.map((_, index) => {
|
||||||
|
return String.fromCharCode(data[index])
|
||||||
|
})
|
||||||
|
.join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
readFile(process.cwd() + '/swfs/bunny.swf', async (error, data) => {
|
||||||
|
const typeCompression = defineTypeCompression(data)
|
||||||
|
|
||||||
|
if (error != null) throw new Error(String(error))
|
||||||
|
if (typeCompression !== 'CWS') throw new Error(`New Type Compression detected: ${typeCompression}`)
|
||||||
|
|
||||||
|
const SWF = (await import('swf-parser')).parseSwf(data)
|
||||||
|
|
||||||
|
const images = SWF.tags.filter((tag) => {
|
||||||
|
const mediaType = (tag as tags.DefineBitmap).mediaType
|
||||||
|
return String(mediaType).startsWith('image')
|
||||||
|
}) as tags.DefineBitmap[]
|
||||||
|
|
||||||
|
const className = (
|
||||||
|
SWF.tags.filter((tag) => {
|
||||||
|
return (tag as tags.PlaceObject).name
|
||||||
|
})[0] as tags.PlaceObject
|
||||||
|
).name
|
||||||
|
|
||||||
|
const current = images.map((img) => {
|
||||||
|
return {
|
||||||
|
path: className,
|
||||||
|
contents: img.data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(current)
|
||||||
|
|
||||||
|
// const packerAtlas = await packAsync(current)
|
||||||
|
})
|
BIN
swfs/Ghost.swf
Normal file
BIN
swfs/Ghost.swf
Normal file
Binary file not shown.
BIN
swfs/art_c20_clock.swf
Normal file
BIN
swfs/art_c20_clock.swf
Normal file
Binary file not shown.
BIN
swfs/bunny.swf
Normal file
BIN
swfs/bunny.swf
Normal file
Binary file not shown.
28
tsconfig.json
Normal file
28
tsconfig.json
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"display": "Default config",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "CommonJS",
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"composite": false,
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"moduleResolution": "nodenext",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"removeComments": true,
|
||||||
|
"preserveWatchOutput": true,
|
||||||
|
"allowJs": false,
|
||||||
|
"strict": true,
|
||||||
|
"noEmitOnError": true,
|
||||||
|
"importHelpers": true,
|
||||||
|
"outDir": "dist"
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user