UNPKG

mz700-js

Version:
305 lines (302 loc) 9.11 kB
"use strict"; import MZ_TapeHeader from './mz-tape-header'; import NumberUtil from "./number-util"; const { HEX } = NumberUtil; /* tslint:disable: class-name no-console no-bitwise no-string-throw */ export default class MZ_Tape { _index:number; _tapeData:boolean[]; constructor(tapeData:boolean[]) { this._index = 0; this._tapeData = tapeData; } isThereSignal(signal:boolean, n:number):boolean { for (let i = 0; i < n; i++) { if (this._tapeData[this._index + i] !== signal) { return false; } } this._index += n; return true; } recognizeStartingMark():boolean { // START MARK if (!this.isThereSignal(false, 11000)) { return false; } if (!this.isThereSignal(true, 40)) { return false; } if (!this.isThereSignal(false, 40)) { return false; } if (!this.isThereSignal(true, 1)) { return false; } return true; } recognizeStarting2Mark():boolean { // START MARK if (!this.isThereSignal(false, 2750)) { return false; } if (!this.isThereSignal(true, 20)) { return false; } if (!this.isThereSignal(false, 20)) { return false; } if (!this.isThereSignal(true, 1)) { return false; } return true; } readSignal():boolean|null { if (this._index < this._tapeData.length) { return this._tapeData[this._index++]; } return null; } writeSignal(signal:boolean):void { this._tapeData.push(signal); } writeByte(data:number):void { this.writeSignal(true); for (let j = 0; j < 8; j++) { if ((data & (0x01 << (7 - j))) !== 0) { this.writeSignal(true); } else { this.writeSignal(false); } } } writeBlock(data:number[]):void { data.forEach(function (d) { this.writeByte(d); }, this); const cs = this.countOnBit(data); this.writeByte((cs >> 8) & 0xff); this.writeByte((cs >> 0) & 0xff); this.writeSignal(true); } writeDuplexBlock(data:number[]):void { this.writeBlock(data); for (let i = 0; i < 256; i++) { this.writeSignal(false); } this.writeBlock(data); } readByte():number { // fast forward to starting bit let startBit = null; do { startBit = this.readSignal(); if (startBit == null) { return null; // End Of Stream } if (!startBit) { throw "NO START BIT"; } } while (!startBit); // Read 8 bits and build 1 byte. // The bits are read from MSB to LSB. let buf = 0x00; for (let i = 0; i < 8; i++) { const bit = this.readSignal(); if (bit == null) { return null; } else if (bit) { buf |= (0x01 << (7 - i)); } } return buf; } readBytes(n:number):number[] { const buf = []; for (let i = 0; i < n; i++) { const data = this.readByte(); if (data == null) { break; } buf.push(data); } return buf; } countOnBit(blockBytes:number[]):number { let onBitCount = 0; const bitno = [0, 1, 2, 3, 4, 5, 6, 7]; blockBytes.forEach((data) => { bitno.forEach((n) => { if ((data & (1 << n)) !== 0) { onBitCount++; } }); }); onBitCount &= 0xffff; return onBitCount; } readBlock(n:number):number[] { // Read block bytes const blockBytes = this.readBytes(n); // read 2 bytes of checksum const checkBytes = this.readBytes(2); if (checkBytes.length !== 2) { throw "NO BLOCK CHECKSUM"; } const checksum = (checkBytes[0] * 256) + checkBytes[1]; // Read block end signal(long) if (!this.isThereSignal(true, 1)) { throw "NO BLOCK END BIT"; } const onBitCount = this.countOnBit(blockBytes); if (onBitCount !== checksum) { throw "CHECKSUM ERROR"; } return blockBytes; } readDuplexBlocks(n:number):number[] { const bytes = this.readBlock(n); if (bytes == null) { throw "FAIL TO READ BLOCK[1]"; } // Block delimitor if (!this.isThereSignal(false, 256)) { throw "NO DELIMITOR: Short x 256."; } const bytes2 = this.readBlock(n); if (bytes2 == null) { throw "FAIL TO READ BLOCK[2]"; } // Check each bytes for (let i = 0; i < bytes.length; i++) { if (bytes[i] !== bytes2[i]) { throw "FAIL TO VERIFY BLOCK 1 and 2"; } } return bytes; } readHeader():MZ_TapeHeader { // Header starting block if (!this.recognizeStartingMark()) { throw "NO STARTING MARK recognized"; } // MZT header const mztBytes = this.readDuplexBlocks(128); if (mztBytes == null) { throw "CANNOT READ MZT HEADER"; } return new MZ_TapeHeader(mztBytes, 0); } readDataBlock(n:number):number[] { // Data starting mark if (!this.recognizeStarting2Mark()) { throw "NO STARTING MARK 2 recognized"; } // Read duplexed data bytes return this.readDuplexBlocks(n); } outputStartingMark():void { let i; // START MARK for (i = 0; i < 11000; i++) { this.writeSignal(false); } for (i = 0; i < 40; i++) { this.writeSignal(true); } for (i = 0; i < 40; i++) { this.writeSignal(false); } this.writeSignal(true); } writeHeader(buffer:number[]):void { this.outputStartingMark(); this.writeDuplexBlock(buffer); } writeStarting2Mark():void { let i; // Body mark for (i = 0; i < 2750; i++) { this.writeSignal(false); } for (i = 0; i < 20; i++) { this.writeSignal(true); } for (i = 0; i < 20; i++) { this.writeSignal(false); } this.writeSignal(true); } writeDataBlock(buffer:number[]):void { // Data starting mark this.writeStarting2Mark(); this.writeDuplexBlock(buffer); } static toBytes(bits:boolean[]):number[] { try { const reader = new MZ_Tape(bits); const header = reader.readHeader(); if (header == null) { throw "FAIL TO READ HEADER"; } const body = reader.readDataBlock(header.fileSize); if (body == null) { throw "FAIL TO READ DATA"; } const extra = []; let nonZeroRead = true; let extraByte; while (nonZeroRead) { extraByte = reader.readByte(); nonZeroRead = (extraByte ? true : false); if (nonZeroRead) { console.warn("MZ_Tape.toBytes rest bytes[" + extraByte.length + "] =", HEX(extraByte, 2)); extra.push(extraByte); } } // MZT + body return header.buffer.concat(body); } catch (err) { console.log("MZ_Tape.toBytes:Error " + err); } return []; } static fromBytes(bytes:number[]):boolean[] { if (bytes.length < 128) { throw "FAIL TO WRITE HEADER"; } const header = new MZ_TapeHeader(bytes.slice(0, 128), 0); const writer = new MZ_Tape([]); writer.writeHeader(header.buffer); writer.writeDataBlock(bytes.slice(128)); return writer._tapeData; } static parseMZT(buf:number[]):{header:MZ_TapeHeader, body:{buffer:number[]}}[] { const sections:{header:MZ_TapeHeader, body:{buffer:number[]}}[] = []; let offset = 0; while (offset + 128 <= buf.length) { const header = new MZ_TapeHeader(buf, offset); offset += 128; if(offset + header.fileSize > buf.length) { return null; } const bodyBuffer = []; for (let i = 0; i < header.fileSize; i++) { bodyBuffer.push(buf[offset + i]); } offset += header.fileSize; sections.push({ "header": header, "body": { "buffer": bodyBuffer } }); } return sections; } } module.exports = MZ_Tape;