|
1 | | -/// <reference path="./util/implode-decoder.d.ts" /> |
2 | | - |
3 | 1 | import { promises as fs } from 'fs'; |
4 | 2 | import winston from 'winston'; |
5 | | -import { FirstDifference } from './util/BufferFirstDifference'; |
6 | 3 | import { UnexpectedValue } from './util/UnexpectedValue'; |
7 | | -import decoder from 'implode-decoder'; |
8 | | -import { decompress, constants } from 'node-pkware'; |
9 | | -import { WritableStreamBuffer } from 'stream-buffers'; |
10 | | -import { promisify } from 'util'; |
11 | | - |
12 | | -const compressedBlocks: Buffer[] = []; |
13 | | -const decompressedBlocks: string[] = []; |
14 | | - |
15 | | -async function decodeBlock(block: Buffer, expectedLength: number): Promise<string> { |
16 | | - const d = decoder(); |
17 | | - |
18 | | - const res = new WritableStreamBuffer({ initialSize: expectedLength }); |
19 | | - d.pipe(res); |
20 | | - d.end(block); |
21 | | - |
22 | | - const newDecoder = decompress({ debug: true, outputBufferSize: expectedLength, inputBufferSize: block.length }); |
23 | | - |
24 | | - const newDecoderPromise = promisify(newDecoder); |
25 | | - |
26 | | - const resNew = new WritableStreamBuffer({ initialSize: expectedLength }); |
27 | | - resNew.write(await newDecoderPromise(block, 'binary')); |
28 | | - |
29 | | - resNew.end( |
30 | | - await new Promise<Buffer>((resolve, reject) => { |
31 | | - newDecoder._state.onInputFinished((err, data) => { |
32 | | - if (err) reject(err); |
33 | | - else resolve(data); |
34 | | - }); |
35 | | - }), |
| 4 | +import { explode, stream } from 'node-pkware'; |
| 5 | +import { Readable } from 'stream'; |
| 6 | +const { streamToBuffer, through } = stream; |
| 7 | + |
| 8 | +async function decodeBlock(block: Buffer, expectedLength: number): Promise<Buffer> { |
| 9 | + const ret = await new Promise<Buffer>(resolve => |
| 10 | + Readable.from(block) |
| 11 | + .pipe(through(explode({ inputBufferSize: block.length, outputBufferSize: expectedLength }))) |
| 12 | + .pipe(streamToBuffer(resolve)), |
36 | 13 | ); |
37 | 14 |
|
38 | | - const ret = res.getContentsAsString('ascii'); |
39 | | - const retNew = resNew.getContentsAsString('ascii'); |
40 | | - |
41 | | - if (!ret) throw new Error('Decoder returned null'); |
42 | | - if (!retNew) throw new Error('Decoder returned null'); |
43 | | - |
44 | 15 | if (ret.length !== expectedLength) |
45 | 16 | throw new Error(`Decoder returned wrong length. Expected: ${expectedLength}. Got: ${ret.length}.`); |
46 | 17 |
|
47 | | - if (retNew.length !== expectedLength) { |
48 | | - console.log('ret:', ret.substring(retNew.length - 100, retNew.length)); |
49 | | - console.log('New:', retNew.substring(retNew.length - 100)); |
50 | | - |
51 | | - console.log(newDecoder._state); |
52 | | - |
53 | | - throw new Error(`New Decoder returned wrong length. Expected: ${expectedLength}. Got: ${retNew.length}.`); |
54 | | - } |
55 | | - |
56 | 18 | return ret; |
57 | 19 | } |
58 | 20 |
|
59 | | -function analyzeSection(compressed: Buffer, section: number, logger: winston.Logger): void { |
60 | | - return; |
61 | | - |
62 | | - if (section === 0) { |
63 | | - for (let result in compressedBlocks) { |
64 | | - const diff = FirstDifference(compressedBlocks[result], compressed); |
65 | | - logger.verbose(`Difference from #${result} at: ${diff ?? 'None!'}`); |
66 | | - } |
67 | | - |
68 | | - compressedBlocks.push(compressed); |
69 | | - logger.info('pushed' + compressedBlocks.length); |
70 | | - } |
71 | | - |
72 | | - const header = Buffer.allocUnsafe(8); |
73 | | - |
74 | | - header.writeUInt32LE(length, 0); |
75 | | - header.writeUInt32LE(compressed.length, 4); |
76 | | - |
77 | | - const full = Buffer.concat([header, compressed]); |
78 | | - |
79 | | - const size = section === 0 ? 20 : 160; |
80 | | - |
81 | | - logger.verbose(compressed.slice(0, size).toString('hex')); |
82 | | - if (!section) logger.verbose(compressed.slice(0, size).toString()); |
83 | | - |
84 | | - if (!section) logger.verbose('Matches: ' + compressed.slice(0, 103).toString('hex')); |
85 | | -} |
86 | | - |
87 | | -export async function decode(filename: string, logger: winston.Logger) { |
| 21 | +export async function decode(filename: string, logger: winston.Logger, outFile = filename + '.xml'): Promise<void> { |
88 | 22 | if (!filename) throw new Error('No filename provided'); |
89 | 23 |
|
90 | 24 | let expectedHeader: Buffer; |
91 | 25 |
|
92 | 26 | if (filename.endsWith('.ewprj')) { |
93 | | - logger.verbose(`Opening EWPRJ: ${filename}`); |
| 27 | + logger.silly(`Opening EWPRJ: ${filename}`); |
94 | 28 | expectedHeader = Buffer.from('CompressedElectronicsWorkbenchXML'); |
95 | 29 | } else if (filename.endsWith('.ms14')) { |
96 | | - logger.verbose(`Opening MultiSIM: ${filename}`); |
| 30 | + logger.silly(`Opening MultiSIM: ${filename}`); |
97 | 31 | expectedHeader = Buffer.from('MSMCompressedElectronicsWorkbenchXML'); |
98 | 32 | } else { |
99 | 33 | throw new Error(`I don't know how to parse: ${filename}`); |
@@ -136,51 +70,59 @@ export async function decode(filename: string, logger: winston.Logger) { |
136 | 70 | return buffer.readUIntLE(0, size); |
137 | 71 | } |
138 | 72 |
|
| 73 | + let written = 0; |
| 74 | + |
139 | 75 | try { |
140 | | - let header = await read(expectedHeader.length); |
| 76 | + logger.silly(`Opening ${outFile} for output`); |
| 77 | + const outputFile = await fs.open(outFile, 'w'); |
141 | 78 |
|
142 | | - logger.verbose('Read header successfully'); |
| 79 | + try { |
| 80 | + let header = await read(expectedHeader.length); |
143 | 81 |
|
144 | | - if (!header.equals(expectedHeader)) { |
145 | | - throw new UnexpectedValue('File header does not match', expectedHeader, header); |
146 | | - } |
| 82 | + logger.silly('Read header successfully'); |
147 | 83 |
|
148 | | - logger.verbose('Header matches as expected'); |
| 84 | + if (!header.equals(expectedHeader)) { |
| 85 | + throw new UnexpectedValue('File header does not match', expectedHeader, header); |
| 86 | + } |
149 | 87 |
|
150 | | - const finalLength = await readNumber(8); |
| 88 | + logger.silly('Header matches as expected'); |
151 | 89 |
|
152 | | - if (typeof finalLength == 'bigint') throw new Error('Cannot handle files this large'); |
| 90 | + const finalLength = await readNumber(8); |
153 | 91 |
|
154 | | - logger.verbose(`Full size: ${finalLength}`); |
| 92 | + if (typeof finalLength == 'bigint') throw new Error('Cannot handle files this large'); |
155 | 93 |
|
156 | | - let section = -1; |
| 94 | + logger.silly(`Full size: ${finalLength}`); |
157 | 95 |
|
158 | | - let decompressedBytesRead = 0; |
| 96 | + let section = -1; |
159 | 97 |
|
160 | | - while (decompressedBytesRead < finalLength) { |
161 | | - section++; |
162 | | - const length = await readNumber(4); |
163 | | - const blockSize = await readNumber(4); |
| 98 | + let decompressedBytesRead = 0; |
164 | 99 |
|
165 | | - logger.verbose(`Section #${section} read ${blockSize} bytes, decompresses to ${length}`); |
| 100 | + while (decompressedBytesRead < finalLength) { |
| 101 | + section++; |
| 102 | + const length = await readNumber(4); |
| 103 | + const blockSize = await readNumber(4); |
166 | 104 |
|
167 | | - const compressedData = await read(blockSize); |
| 105 | + logger.silly(`Section #${section} read ${blockSize} bytes, decompresses to ${length}`); |
168 | 106 |
|
169 | | - decompressedBytesRead += length; |
| 107 | + const compressedData = await read(blockSize); |
170 | 108 |
|
171 | | - compressedBlocks.push(compressedData); |
| 109 | + decompressedBytesRead += length; |
172 | 110 |
|
173 | | - const decodedBlock = decodeBlock(compressedData, length); |
| 111 | + const decodedBlock = await decodeBlock(compressedData, length); |
174 | 112 |
|
175 | | - decompressedBlocks.push(await decodedBlock); |
| 113 | + written += (await outputFile.write(decodedBlock)).bytesWritten; |
| 114 | + } |
176 | 115 |
|
177 | | - analyzeSection(compressedData, section, logger); |
| 116 | + await Promise.all([outputFile.close(), file.close()]); |
| 117 | + |
| 118 | + logger.verbose(`Wrote ${written} bytes to ${outFile}`); |
| 119 | + logger.silly(`Finished reading file: ${filename}`); |
| 120 | + } catch (e) { |
| 121 | + await outputFile.close(); |
| 122 | + throw e; |
178 | 123 | } |
179 | 124 | } catch (e) { |
180 | | - throw e; |
181 | | - } finally { |
182 | 125 | await file.close(); |
| 126 | + throw e; |
183 | 127 | } |
184 | | - |
185 | | - logger.verbose(`Finished reading file: ${filename}`); |
186 | 128 | } |
0 commit comments