roxify 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 RoxCompressor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,368 @@
1
+ # RoxCompressor Transform
2
+
3
+ > Encode binary data into PNG images and decode them back. Fast, efficient, with optional encryption.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/roxify.svg)](https://www.npmjs.com/package/roxify)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+
8
+ ## Features
9
+
10
+ - 🚀 **Fast**: Optimized Brotli compression (quality 1 by default) — encode 3.8 MB in ~1 second
11
+ - 🔒 **Secure**: AES-256-GCM encryption support with PBKDF2 key derivation
12
+ - 🎨 **Multiple modes**: Compact, chunk, pixel, and screenshot modes
13
+ - 📦 **CLI & API**: Use as command-line tool or JavaScript library
14
+ - 🔄 **Lossless**: Perfect roundtrip encoding/decoding
15
+ - 📊 **Efficient**: Typically 20-30% of original size with compression
16
+ - 📖 **Full TSDoc**: Complete TypeScript documentation
17
+
18
+ ## Documentation
19
+
20
+ - 📘 **[CLI Documentation](./CLI.md)** - Complete command-line usage guide
21
+ - 📗 **[JavaScript SDK](./JAVASCRIPT_SDK.md)** - Programmatic API reference with examples
22
+ - 📙 **[Quick Start](#quick-start)** - Get started in 2 minutes
23
+
24
+ ## Installation
25
+
26
+ ### As CLI tool (npx)
27
+
28
+ No installation needed! Use directly with npx:
29
+
30
+ ```bash
31
+ npx rox encode input.zip output.png
32
+ npx rox decode output.png original.zip
33
+ ```
34
+
35
+ ### As library
36
+
37
+ ```bash
38
+ npm install roxify
39
+ ```
40
+
41
+ ## CLI Usage
42
+
43
+ ### Quick Start
44
+
45
+ ```bash
46
+ # Encode a file
47
+ npx rox encode document.pdf document.png
48
+
49
+ # Decode it back
50
+ npx rox decode document.png document.pdf
51
+
52
+ # With encryption
53
+ npx rox encode secret.zip secret.png -p mypassword
54
+ npx rox decode secret.png secret.zip -p mypassword
55
+ ```
56
+
57
+ ### CLI Commands
58
+
59
+ #### `encode` - Encode file to PNG
60
+
61
+ ```bash
62
+ npx rox encode <input> [output] [options]
63
+ ```
64
+
65
+ **Options:**
66
+
67
+ - `-p, --passphrase <pass>` - Encrypt with passphrase (AES-256-GCM)
68
+ - `-m, --mode <mode>` - Encoding mode: `compact|chunk|pixel|screenshot` (default: `screenshot`)
69
+ - `-q, --quality <0-11>` - Brotli compression quality (default: `1`)
70
+ - `0` = fastest, largest
71
+ - `11` = slowest, smallest
72
+ - `-e, --encrypt <type>` - Encryption: `auto|aes|xor|none` (default: `aes` if passphrase)
73
+ - `--no-compress` - Disable compression
74
+ - `-o, --output <path>` - Output file path
75
+
76
+ **Examples:**
77
+
78
+ ```bash
79
+ # Basic encoding
80
+ npx rox encode data.bin output.png
81
+
82
+ # Fast compression for large files
83
+ npx rox encode large-video.mp4 output.png -q 0
84
+
85
+ # High compression for small files
86
+ npx rox encode config.json output.png -q 11
87
+
88
+ # With encryption
89
+ npx rox encode secret.pdf secure.png -p "my secure password"
90
+
91
+ # Compact mode (smallest PNG)
92
+ npx rox encode data.bin tiny.png -m compact
93
+
94
+ # Screenshot mode (recommended, looks like a real image)
95
+ npx rox encode archive.tar.gz screenshot.png -m screenshot
96
+ ```
97
+
98
+ #### `decode` - Decode PNG to file
99
+
100
+ ```bash
101
+ npx rox decode <input> [output] [options]
102
+ ```
103
+
104
+ **Options:**
105
+
106
+ - `-p, --passphrase <pass>` - Decryption passphrase
107
+ - `-o, --output <path>` - Output file path (auto-detected from metadata if not provided)
108
+
109
+ **Examples:**
110
+
111
+ ```bash
112
+ # Basic decoding
113
+ npx rox decode encoded.png output.bin
114
+
115
+ # Auto-detect filename from metadata
116
+ npx rox decode encoded.png
117
+
118
+ # With decryption
119
+ npx rox decode encrypted.png output.pdf -p "my secure password"
120
+ ```
121
+
122
+ ## JavaScript API
123
+
124
+ ### Basic Usage
125
+
126
+ ```typescript
127
+ import { encodeBinaryToPng, decodePngToBinary } from 'roxify';
128
+ import { readFileSync, writeFileSync } from 'fs';
129
+
130
+ // Encode
131
+ const input = readFileSync('input.zip');
132
+ const png = await encodeBinaryToPng(input, {
133
+ mode: 'screenshot',
134
+ name: 'input.zip',
135
+ });
136
+ writeFileSync('output.png', png);
137
+
138
+ // Decode
139
+ const encoded = readFileSync('output.png');
140
+ const result = await decodePngToBinary(encoded);
141
+ writeFileSync(result.meta?.name || 'output.bin', result.buf);
142
+ ```
143
+
144
+ ### With Encryption
145
+
146
+ ```typescript
147
+ // Encode with AES-256-GCM
148
+ const png = await encodeBinaryToPng(input, {
149
+ mode: 'screenshot',
150
+ passphrase: 'my-secret-password',
151
+ encrypt: 'aes',
152
+ name: 'secret.zip',
153
+ });
154
+
155
+ // Decode with passphrase
156
+ const result = await decodePngToBinary(encoded, {
157
+ passphrase: 'my-secret-password',
158
+ });
159
+ ```
160
+
161
+ ### Fast Compression
162
+
163
+ ```typescript
164
+ // Optimize for speed (recommended for large files)
165
+ const png = await encodeBinaryToPng(largeBuffer, {
166
+ mode: 'screenshot',
167
+ brQuality: 0, // Fastest
168
+ name: 'large-file.bin',
169
+ });
170
+
171
+ // Optimize for size (recommended for small files)
172
+ const png = await encodeBinaryToPng(smallBuffer, {
173
+ mode: 'compact',
174
+ brQuality: 11, // Best compression
175
+ name: 'config.json',
176
+ });
177
+ ```
178
+
179
+ ### Encoding Modes
180
+
181
+ #### `screenshot` (Recommended)
182
+
183
+ Encodes data as RGB pixel values, optimized for screenshot-like appearance. Best balance of size and compatibility.
184
+
185
+ ```typescript
186
+ const png = await encodeBinaryToPng(data, { mode: 'screenshot' });
187
+ ```
188
+
189
+ #### `compact` (Smallest)
190
+
191
+ Minimal 1x1 PNG with data in custom chunk. Fastest and smallest.
192
+
193
+ ```typescript
194
+ const png = await encodeBinaryToPng(data, { mode: 'compact' });
195
+ ```
196
+
197
+ #### `pixel`
198
+
199
+ Encodes data as RGB pixel values without screenshot optimization.
200
+
201
+ ```typescript
202
+ const png = await encodeBinaryToPng(data, { mode: 'pixel' });
203
+ ```
204
+
205
+ #### `chunk`
206
+
207
+ Standard PNG with data in custom rXDT chunk.
208
+
209
+ ```typescript
210
+ const png = await encodeBinaryToPng(data, { mode: 'chunk' });
211
+ ```
212
+
213
+ ## API Reference
214
+
215
+ ### `encodeBinaryToPng(input, options)`
216
+
217
+ Encodes binary data into a PNG image.
218
+
219
+ **Parameters:**
220
+
221
+ - `input: Buffer` - The binary data to encode
222
+ - `options?: EncodeOptions` - Encoding options
223
+
224
+ **Returns:** `Promise<Buffer>` - The encoded PNG
225
+
226
+ **Options:**
227
+
228
+ ```typescript
229
+ interface EncodeOptions {
230
+ // Compression algorithm ('br' = Brotli, 'none' = no compression)
231
+ compression?: 'br' | 'none';
232
+
233
+ // Passphrase for encryption
234
+ passphrase?: string;
235
+
236
+ // Original filename to embed
237
+ name?: string;
238
+
239
+ // Encoding mode
240
+ mode?: 'compact' | 'chunk' | 'pixel' | 'screenshot';
241
+
242
+ // Encryption method
243
+ encrypt?: 'auto' | 'aes' | 'xor' | 'none';
244
+
245
+ // Output format
246
+ output?: 'auto' | 'png' | 'rox';
247
+
248
+ // Include filename in metadata (default: true)
249
+ includeName?: boolean;
250
+
251
+ // Brotli quality 0-11 (default: 1)
252
+ brQuality?: number;
253
+ }
254
+ ```
255
+
256
+ ### `decodePngToBinary(pngBuf, options)`
257
+
258
+ Decodes a PNG image back to binary data.
259
+
260
+ **Parameters:**
261
+
262
+ - `pngBuf: Buffer` - The PNG image to decode
263
+ - `options?: DecodeOptions` - Decoding options
264
+
265
+ **Returns:** `Promise<DecodeResult>` - The decoded data and metadata
266
+
267
+ **Options:**
268
+
269
+ ```typescript
270
+ interface DecodeOptions {
271
+ // Passphrase for decryption
272
+ passphrase?: string;
273
+ }
274
+ ```
275
+
276
+ **Result:**
277
+
278
+ ```typescript
279
+ interface DecodeResult {
280
+ // Decoded binary data
281
+ buf: Buffer;
282
+
283
+ // Extracted metadata
284
+ meta?: {
285
+ // Original filename
286
+ name?: string;
287
+ };
288
+ }
289
+ ```
290
+
291
+ ## Performance Tips
292
+
293
+ ### For Large Files (>10 MB)
294
+
295
+ ```bash
296
+ # Use quality 0 for fastest encoding
297
+ npx rox encode large.bin output.png -q 0
298
+ ```
299
+
300
+ ```typescript
301
+ const png = await encodeBinaryToPng(largeFile, {
302
+ mode: 'screenshot',
303
+ brQuality: 0, // 10-20x faster than default
304
+ });
305
+ ```
306
+
307
+ ### For Small Files (<1 MB)
308
+
309
+ ```bash
310
+ # Use quality 11 for best compression
311
+ npx rox encode small.json output.png -q 11 -m compact
312
+ ```
313
+
314
+ ```typescript
315
+ const png = await encodeBinaryToPng(smallFile, {
316
+ mode: 'compact',
317
+ brQuality: 11, // Best compression ratio
318
+ });
319
+ ```
320
+
321
+ ### Benchmark Results
322
+
323
+ File: 3.8 MB binary
324
+
325
+ - **Quality 0**: ~500-800ms, output ~1.2 MB
326
+ - **Quality 1** (default): ~1-2s, output ~800 KB
327
+ - **Quality 5**: ~8-12s, output ~750 KB
328
+ - **Quality 11**: ~20-30s, output ~720 KB
329
+
330
+ ## Error Handling
331
+
332
+ ```typescript
333
+ try {
334
+ const result = await decodePngToBinary(encoded, {
335
+ passphrase: 'wrong-password',
336
+ });
337
+ } catch (err) {
338
+ if (err.message.includes('Incorrect passphrase')) {
339
+ console.error('Wrong password!');
340
+ } else if (err.message.includes('Invalid ROX format')) {
341
+ console.error('Not a valid RoxCompressor PNG');
342
+ } else {
343
+ console.error('Decode failed:', err.message);
344
+ }
345
+ }
346
+ ```
347
+
348
+ ## Security
349
+
350
+ - **AES-256-GCM**: Authenticated encryption with 100,000 PBKDF2 iterations
351
+ - **XOR cipher**: Simple obfuscation (not cryptographically secure)
352
+ - **No encryption**: Data is compressed but not encrypted
353
+
354
+ ⚠️ **Warning**: Use strong passphrases for sensitive data. The `xor` encryption mode is not secure and should only be used for obfuscation.
355
+
356
+ ## License
357
+
358
+ MIT © RoxCompressor
359
+
360
+ ## Contributing
361
+
362
+ Contributions welcome! Please open an issue or PR on GitHub.
363
+
364
+ ## Links
365
+
366
+ - [GitHub Repository](https://github.com/RoxasYTB/RoxCompressor)
367
+ - [npm Package](https://www.npmjs.com/package/roxify)
368
+ - [Report Issues](https://github.com/RoxasYTB/RoxCompressor/issues)
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, writeFileSync } from 'fs';
3
+ import { basename, resolve } from 'path';
4
+ import { cropAndReconstitute, DataFormatError, decodePngToBinary, encodeBinaryToPng, IncorrectPassphraseError, PassphraseRequiredError, } from './index.js';
5
+ const VERSION = '1.0.4';
6
+ function showHelp() {
7
+ console.log(`
8
+ ROX CLI — Encode/decode binary in PNG
9
+
10
+ Usage:
11
+ npx rox <command> [options]
12
+
13
+ Commands:
14
+ encode <input> [output] Encode file to PNG
15
+ decode <input> [output] Decode PNG to original file
16
+
17
+ Options:
18
+ -p, --passphrase <pass> Use passphrase (AES-256-GCM)
19
+ -m, --mode <mode> Mode: compact|chunk|pixel|screenshot (default: screenshot)
20
+ -q, --quality <0-11> Brotli quality (default: 11)
21
+ -e, --encrypt <type> auto|aes|xor|none
22
+ --no-compress Disable compression
23
+ -o, --output <path> Output file path
24
+ --view-reconst Export the reconstituted PNG for debugging
25
+ -v, --verbose Show detailed errors
26
+
27
+ Run "npx rox help" for this message.
28
+ `);
29
+ }
30
+ function parseArgs(args) {
31
+ const parsed = { _: [] };
32
+ let i = 0;
33
+ while (i < args.length) {
34
+ const arg = args[i];
35
+ if (arg.startsWith('--')) {
36
+ const key = arg.slice(2);
37
+ if (key === 'no-compress') {
38
+ parsed.noCompress = true;
39
+ i++;
40
+ }
41
+ else if (key === 'verbose') {
42
+ parsed.verbose = true;
43
+ i++;
44
+ }
45
+ else if (key === 'view-reconst') {
46
+ parsed.viewReconst = true;
47
+ i++;
48
+ }
49
+ else if (key === 'debug-dir') {
50
+ parsed.debugDir = args[i + 1];
51
+ i += 2;
52
+ }
53
+ else {
54
+ const value = args[i + 1];
55
+ parsed[key] = value;
56
+ i += 2;
57
+ }
58
+ }
59
+ else if (arg.startsWith('-')) {
60
+ const flag = arg.slice(1);
61
+ const value = args[i + 1];
62
+ switch (flag) {
63
+ case 'p':
64
+ parsed.passphrase = value;
65
+ i += 2;
66
+ break;
67
+ case 'm':
68
+ parsed.mode = value;
69
+ i += 2;
70
+ break;
71
+ case 'q':
72
+ parsed.quality = parseInt(value, 10);
73
+ i += 2;
74
+ break;
75
+ case 'e':
76
+ parsed.encrypt = value;
77
+ i += 2;
78
+ break;
79
+ case 'o':
80
+ parsed.output = value;
81
+ i += 2;
82
+ break;
83
+ case 'v':
84
+ parsed.verbose = true;
85
+ i += 1;
86
+ break;
87
+ case 'd':
88
+ parsed.debugDir = value;
89
+ i += 2;
90
+ break;
91
+ default:
92
+ console.error(`Unknown option: ${arg}`);
93
+ process.exit(1);
94
+ }
95
+ }
96
+ else {
97
+ parsed._.push(arg);
98
+ i++;
99
+ }
100
+ }
101
+ return parsed;
102
+ }
103
+ async function encodeCommand(args) {
104
+ const parsed = parseArgs(args);
105
+ const [inputPath, outputPath] = parsed._;
106
+ if (!inputPath) {
107
+ console.error('Error: Input file required');
108
+ console.log('Usage: npx rox encode <input> [output] [options]');
109
+ process.exit(1);
110
+ }
111
+ const resolvedInput = resolve(inputPath);
112
+ const resolvedOutput = parsed.output || outputPath || inputPath.replace(/(\.[^.]+)?$/, '.png');
113
+ try {
114
+ console.log(`Reading: ${resolvedInput}`);
115
+ const startRead = Date.now();
116
+ const inputBuffer = readFileSync(resolvedInput);
117
+ const readTime = Date.now() - startRead;
118
+ console.log(`Read ${(inputBuffer.length / 1024 / 1024).toFixed(2)} MB in ${readTime}ms`);
119
+ const options = {
120
+ mode: parsed.mode || 'screenshot',
121
+ name: basename(resolvedInput),
122
+ brQuality: parsed.quality !== undefined ? parsed.quality : 11,
123
+ };
124
+ if (parsed.noCompress) {
125
+ options.compression = 'none';
126
+ }
127
+ if (parsed.passphrase) {
128
+ options.passphrase = parsed.passphrase;
129
+ options.encrypt = parsed.encrypt || 'aes';
130
+ }
131
+ console.log(`Encoding ${basename(resolvedInput)} -> ${resolvedOutput}`);
132
+ const startEncode = Date.now();
133
+ const output = await encodeBinaryToPng(inputBuffer, options);
134
+ const encodeTime = Date.now() - startEncode;
135
+ writeFileSync(resolvedOutput, output);
136
+ const outputSize = (output.length / 1024 / 1024).toFixed(2);
137
+ const inputSize = (inputBuffer.length / 1024 / 1024).toFixed(2);
138
+ const ratio = ((output.length / inputBuffer.length) * 100).toFixed(1);
139
+ console.log(`\nSuccess!`);
140
+ console.log(` Input: ${inputSize} MB`);
141
+ console.log(` Output: ${outputSize} MB (${ratio}% of original)`);
142
+ console.log(` Time: ${encodeTime}ms`);
143
+ console.log(` Saved: ${resolvedOutput}`);
144
+ }
145
+ catch (err) {
146
+ console.error('Error: Failed to encode file. Use --verbose for details.');
147
+ if (parsed.verbose)
148
+ console.error('Details:', err.stack || err.message);
149
+ process.exit(1);
150
+ }
151
+ }
152
+ async function decodeCommand(args) {
153
+ const parsed = parseArgs(args);
154
+ const [inputPath, outputPath] = parsed._;
155
+ if (!inputPath) {
156
+ console.error('Error: Input PNG file required');
157
+ console.log('Usage: npx rox decode <input> [output] [options]');
158
+ process.exit(1);
159
+ }
160
+ const resolvedInput = resolve(inputPath);
161
+ try {
162
+ let inputBuffer = readFileSync(resolvedInput);
163
+ let resolvedInputPath = resolvedInput;
164
+ try {
165
+ const reconst = await cropAndReconstitute(inputBuffer);
166
+ inputBuffer = reconst;
167
+ resolvedInputPath = resolvedInput.replace(/(\.\w+)?$/, '_reconst.png');
168
+ if (parsed.viewReconst) {
169
+ writeFileSync(resolvedInputPath, reconst);
170
+ console.log(`Reconst PNG: ${resolvedInputPath}`);
171
+ }
172
+ }
173
+ catch (e) {
174
+ console.log('Could not generate reconst PNG:', e.message);
175
+ }
176
+ console.log(`Reading: ${resolvedInputPath}`);
177
+ const options = {};
178
+ if (parsed.passphrase) {
179
+ options.passphrase = parsed.passphrase;
180
+ }
181
+ if (parsed.debugDir) {
182
+ options.debugDir = parsed.debugDir;
183
+ }
184
+ console.log(`Decoding...`);
185
+ const startDecode = Date.now();
186
+ if (parsed.verbose)
187
+ options.verbose = true;
188
+ const result = await decodePngToBinary(inputBuffer, options);
189
+ const decodeTime = Date.now() - startDecode;
190
+ const resolvedOutput = parsed.output || outputPath || result.meta?.name || 'decoded.bin';
191
+ writeFileSync(resolvedOutput, result.buf);
192
+ const outputSize = (result.buf.length / 1024 / 1024).toFixed(2);
193
+ console.log(`\nSuccess!`);
194
+ if (result.meta?.name) {
195
+ console.log(` Original name: ${result.meta.name}`);
196
+ }
197
+ console.log(` Output size: ${outputSize} MB`);
198
+ console.log(` Time: ${decodeTime}ms`);
199
+ console.log(` Saved: ${resolvedOutput}`);
200
+ }
201
+ catch (err) {
202
+ if (err instanceof PassphraseRequiredError ||
203
+ (err.message && err.message.includes('passphrase') && !parsed.passphrase)) {
204
+ console.error('File appears to be encrypted. Provide a passphrase with -p');
205
+ }
206
+ else if (err instanceof IncorrectPassphraseError ||
207
+ (err.message && err.message.includes('Incorrect passphrase'))) {
208
+ console.error('Incorrect passphrase');
209
+ }
210
+ else if (err instanceof DataFormatError ||
211
+ (err.message &&
212
+ (err.message.includes('decompression failed') ||
213
+ err.message.includes('missing ROX1') ||
214
+ err.message.includes('Pixel payload truncated') ||
215
+ err.message.includes('Marker START not found') ||
216
+ err.message.includes('Brotli decompression failed')))) {
217
+ console.error('Data corrupted or unsupported format. Use --verbose for details.');
218
+ }
219
+ else {
220
+ console.error('Failed to decode file. Use --verbose for details.');
221
+ }
222
+ if (parsed.verbose)
223
+ console.error('Details:', err.stack || err.message);
224
+ process.exit(1);
225
+ }
226
+ }
227
+ async function main() {
228
+ const args = process.argv.slice(2);
229
+ if (args.length === 0 || args[0] === 'help' || args[0] === '--help') {
230
+ showHelp();
231
+ return;
232
+ }
233
+ if (args[0] === 'version' || args[0] === '--version') {
234
+ console.log(VERSION);
235
+ return;
236
+ }
237
+ const command = args[0];
238
+ const commandArgs = args.slice(1);
239
+ switch (command) {
240
+ case 'encode':
241
+ await encodeCommand(commandArgs);
242
+ break;
243
+ case 'decode':
244
+ await decodeCommand(commandArgs);
245
+ break;
246
+ default:
247
+ console.error(`Unknown command: ${command}`);
248
+ console.log('Run "npx rox help" for usage information');
249
+ process.exit(1);
250
+ }
251
+ }
252
+ main().catch((err) => {
253
+ console.error('Fatal error:', err);
254
+ process.exit(1);
255
+ });