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