* CFFA

* CFFA multi-disk and write functionaliity.

* Clean up multi-devices/partition behavior.

* ProDOS WIP

* Update against refactored codebase.

* WIP

* Wait until disks load, show progress.

* Don't wait so long to boot floppies.

* Forgot to save :|.

* Credit.
This commit is contained in:
Will Scullin 2020-01-02 11:11:04 -08:00 committed by GitHub
parent 8ea7cf2abe
commit b647b3c2bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 2189 additions and 67 deletions

View File

@ -13,12 +13,16 @@
"unix"
],
"semi": [
2,
1,
"always"
],
"no-use-before-define": [
2,
{ "functions": false }
],
"no-console": [
2,
{ "allow": ["info", "warn", "error"] }
]
},
"env": {

View File

@ -149,6 +149,7 @@ then
* [jQuery](https://jquery.com) and [jQuery UI](https://jqueryui.com)
* Base64 Utilities via [KvZ](http://kevin.vanzonneveld.net/)
* LED graphics from [Modern Life](http://modernl.com/).
* [CFFA2 Firmware](http://dreher.net/?s=projects/CFforAppleII&c=projects/CFforAppleII/downloads1.php) by Chris Schumann, Rich Dreher and Dave Lyons
* I heavily referenced:

View File

@ -139,10 +139,15 @@
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="Loading" >
<header class="modal__header">
<span class="modal__title" id="loading-modal-title">
<div class="modal__title" id="loading-modal-title">
Loading...
</span>
</div>
</header>
<main class="modal__content" id="loading-modal-content">
<div class="meter">
<div class="progress"></div>
</div>
</div>
</div>
</div>
</div>
@ -378,6 +383,28 @@
</div>
</div>
</div>
<div class="modal" id="alert-modal" aria-hidden="true">
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="Printer">
<header class="modal__header">
<span class="modal__title" id="printer-modal-title">
Alert
</span>
<button class="modal__close" aria-label="Close modal" data-micromodal-close>
</button>
</header>
<main class="modal__content" id="alert-modal-content">
<div class=".message">
</div>
</main>
<footer class="modal__footer">
<button class="modal__btn" data-micromodal-close aria-label="Close this dialog window">Close</button>
</footer>
</div>
</div>
</div>
<script src="dist/main2.js"></script>
</body>
</html>

View File

@ -120,10 +120,15 @@
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="Loading" >
<header class="modal__header">
<span class="modal__title" id="loading-modal-title">
<div class="modal__title" id="loading-modal-title">
Loading...
</span>
</div>
</header>
<main class="modal__content" id="loading-modal-content">
<div class="meter">
<div class="progress"></div>
</div>
</div>
</div>
</div>
</div>
@ -354,6 +359,28 @@
</div>
</div>
</div>
<div class="modal" id="alert-modal" aria-hidden="true">
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
<div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="Printer">
<header class="modal__header">
<span class="modal__title" id="printer-modal-title">
Alert
</span>
<button class="modal__close" aria-label="Close modal" data-micromodal-close>
</button>
</header>
<main class="modal__content" id="alert-modal-content">
<div class="message">
</div>
</main>
<footer class="modal__footer">
<button class="modal__btn" data-micromodal-close aria-label="Close this dialog window">Close</button>
</footer>
</div>
</div>
</div>
<script src="dist/main2e.js"></script>
</body>
</html>

View File

@ -230,6 +230,19 @@ canvas {
line-height: 14px;
}
#loading-modal .meter {
width: 200px;
height: 15px;
background-color: black;
border: 1px inset #888;
}
#loading-modal .progress {
position: absolute;
height: 15px;
background-color: #0f0;
}
.modal {
display: none;
}
@ -609,3 +622,8 @@ button:focus {
font-family: monospace;
white-space: pre;
}
#alert-modal-content {
min-width: 200px;
min-height: 50px;
}

411
js/cards/cffa.js Normal file
View File

@ -0,0 +1,411 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { debug, toHex } from '../util';
import { rom } from '../roms/cards/cffa';
import _2MG from '../formats/2mg';
import { ProDOSVolume } from '../formats/prodos';
import BlockVolume from '../formats/block';
import { dump } from '../formats/prodos/utils';
export default function CFFA() {
var COMMANDS = {
ATACRead: 0x20,
ATACWrite: 0x30,
ATAIdentify: 0xEC
};
// CFFA Card Settings
var SETTINGS = {
Max32MBPartitionsDev0: 0x800,
Max32MBPartitionsDev1: 0x801,
DefaultBootDevice: 0x802,
DefaultBootPartition: 0x803,
Reserved: 0x804, // 4 bytes
WriteProtectBits: 0x808,
MenuSnagMask: 0x809,
MenuSnagKey: 0x80A,
BootTimeDelayTenths: 0x80B,
BusResetSeconds: 0x80C,
CheckDeviceTenths: 0x80D,
ConfigOptionBits: 0x80E,
BlockOffsetDev0: 0x80F, // 3 bytes
BlockOffsetDev1: 0x812, // 3 bytes
Unused: 0x815
};
// CFFA ATA Register Locations
var LOC = {
ATADataHigh: 0x80,
SetCSMask: 0x81,
ClearCSMask: 0x82,
WriteEEPROM: 0x83,
NoWriteEEPROM: 0x84,
ATADevCtrl: 0x86,
ATAAltStatus: 0x86,
ATADataLow: 0x88,
AError: 0x89,
ASectorCnt: 0x8a,
ASector: 0x8b,
ATACylinder: 0x8c,
ATACylinderH: 0x8d,
ATAHead: 0x8e,
ATACommand: 0x8f,
ATAStatus: 0x8f
};
// ATA Status Bits
var STATUS = {
BSY: 0x80, // Busy
DRDY: 0x40, // Drive ready. 1 when ready
DWF: 0x20, // Drive write fault. 1 when fault
DSC: 0x10, // Disk seek complete. 1 when not seeking
DRQ: 0x08, // Data request. 1 when ready to write
CORR: 0x04, // Correct data. 1 on correctable error
IDX: 0x02, // 1 once per revolution
ERR: 0x01 // Error. 1 on error
};
// ATA Identity Block Locations
var IDENTITY = {
SectorCountLow: 58,
SectorCountHigh: 57
};
// CFFA internal Flags
var _disableSignalling = false;
var _writeEEPROM = true;
var _lba = true;
// LBA/CHS registers
var _sectorCnt = 1;
var _sector = 0;
var _cylinder = 0;
var _cylinderH = 0;
var _head = 0;
var _drive = 0;
// CFFA Data High register
var _dataHigh = 0;
// Current Sector
var _curSector = [];
var _curWord = 0;
// ATA Status registers
var _interruptsEnabled = false;
var _altStatus = 0;
var _error = 0;
var _identity = [[], []];
// Disk data
var _partitions = [
// Drive 1
[],
// Drive 2
[]
];
var _sectors = [
// Drive 1
[],
// Drive 2
[]
];
function _init() {
debug('CFFA');
for (var idx = 0; idx < 0x100; idx++) {
_identity[0][idx] = 0;
_identity[1][idx] = 0;
}
rom[SETTINGS.Max32MBPartitionsDev0] = 0x1;
rom[SETTINGS.Max32MBPartitionsDev1] = 0x0;
rom[SETTINGS.BootTimeDelayTenths] = 0x14; // 2 seconds
rom[SETTINGS.CheckDeviceTenths] = 0x14; // 2 seconds
}
// Verbose debug method
function _debug() {
// debug.apply(this, arguments);
}
function _reset() {
_debug('reset');
_sectorCnt = 1;
_sector = 0;
_cylinder = 0;
_cylinderH = 0;
_head = 0;
_drive = 0;
_dataHigh = 0;
}
// Convert status register into readable string
function _statusString(status) {
var statusArray = [];
for (var flag in STATUS) {
if(status & STATUS[flag]) {
statusArray.push(flag);
}
}
return statusArray.join('|');
}
// Dump sector as hex and ascii
function _dumpSector(sector) {
if (sector >= _sectors[_drive].length) {
_debug('dump sector out of range', sector);
return;
}
for (var idx = 0; idx < 16; idx++) {
var row = [];
var charRow = [];
for (var jdx = 0; jdx < 16; jdx++) {
var val = _sectors[_drive][sector][idx * 16 + jdx];
row.push(toHex(val, 4));
var low = val & 0x7f;
var hi = val >> 8 & 0x7f;
charRow.push(low > 0x1f ? String.fromCharCode(low) : '.');
charRow.push(hi > 0x1f ? String.fromCharCode(hi) : '.');
}
_debug(row.join(' '), ' ', charRow.join(''));
}
}
// Card I/O access
function _access(off, val) {
var readMode = val === undefined;
var retVal = readMode ? 0 : undefined;
var sector;
if (readMode) {
switch (off & 0x8f) {
case LOC.ATADataHigh: // 0x00
retVal = _dataHigh;
break;
case LOC.SetCSMask: // 0x01
_disableSignalling = true;
break;
case LOC.ClearCSMask: // 0x02
_disableSignalling = false;
break;
case LOC.WriteEEPROM: // 0x03
_writeEEPROM = true;
break;
case LOC.NoWriteEEPROM: // 0x04
_writeEEPROM = false;
break;
case LOC.ATAAltStatus: // 0x06
retVal = _altStatus;
break;
case LOC.ATADataLow: // 0x08
_dataHigh = _curSector[_curWord] >> 8;
retVal = _curSector[_curWord] & 0xff;
if (!_disableSignalling) {
_curWord++;
}
break;
case LOC.AError: // 0x09
retVal = _error;
break;
case LOC.ASectorCnt: // 0x0A
retVal = _sectorCnt;
break;
case LOC.ASector: // 0x0B
retVal = _sector;
break;
case LOC.ATACylinder: // 0x0C
retVal = _cylinder;
break;
case LOC.ATACylinderH: // 0x0D
retVal = _cylinderH;
break;
case LOC.ATAHead: // 0x0E
retVal = _head | (_lba ? 0x40 : 0) | (_drive ? 0x10 : 0) | 0xA0;
break;
case LOC.ATAStatus: // 0x0F
retVal = _sectors[_drive].length > 0 ? STATUS.DRDY | STATUS.DSC : 0;
_debug('returning status', _statusString(retVal));
break;
default:
debug('read unknown soft switch', toHex(off));
}
if (off & 0x7) { // Anything but data high/low
_debug('read soft switch', toHex(off), toHex(retVal));
}
} else {
if (off & 0x7) { // Anything but data high/low
_debug('write soft switch', toHex(off), toHex(val));
}
switch (off & 0x8f) {
case LOC.ATADataHigh: // 0x00
_dataHigh = val;
break;
case LOC.SetCSMask: // 0x01
_disableSignalling = true;
break;
case LOC.ClearCSMask: // 0x02
_disableSignalling = false;
break;
case LOC.WriteEEPROM: // 0x03
_writeEEPROM = true;
break;
case LOC.NoWriteEEPROM: // 0x04
_writeEEPROM = false;
break;
case LOC.ATADevCtrl: // 0x06
_debug('devCtrl:', toHex(val));
_interruptsEnabled = (val & 0x04) ? true : false;
_debug('Interrupts', _interruptsEnabled ? 'enabled' : 'disabled');
if (val & 0x02) {
_reset();
}
break;
case LOC.ATADataLow: // 0x08
_curSector[_curWord] = _dataHigh << 8 | val;
_curWord++;
break;
case LOC.ASectorCnt: // 0x0a
_debug('setting sector count', val);
_sectorCnt = val;
break;
case LOC.ASector: // 0x0b
_debug('setting sector', toHex(val));
_sector = val;
break;
case LOC.ATACylinder: // 0x0c
_debug('setting cylinder', toHex(val));
_cylinder = val;
break;
case LOC.ATACylinderH: // 0x0d
_debug('setting cylinder high', toHex(val));
_cylinderH = val;
break;
case LOC.ATAHead:
_head = val & 0xf;
_lba = val & 0x40 ? true : false;
_drive = val & 0x10 ? 1 : 0;
_debug('setting head', toHex(val & 0xf), 'drive', _drive);
if (!_lba) {
console.error('CHS mode not supported');
}
break;
case LOC.ATACommand: // 0x0f
_debug('command:', toHex(val));
sector = _head << 24 | _cylinderH << 16 | _cylinder << 8 | _sector;
_dumpSector(sector);
switch (val) {
case COMMANDS.ATAIdentify:
_debug('ATA identify');
_curSector = _identity[_drive];
_curWord = 0;
break;
case COMMANDS.ATACRead:
_debug('ATA read sector', toHex(_cylinderH), toHex(_cylinder), toHex(_sector), sector);
_curSector = _sectors[_drive][sector];
_curWord = 0;
break;
case COMMANDS.ATACWrite:
_debug('ATA write sector', toHex(_cylinderH), toHex(_cylinder), toHex(_sector), sector);
_curSector = _sectors[_drive][sector];
_curWord = 0;
break;
default:
debug('unknown command', toHex(val));
}
break;
default:
debug('write unknown soft switch', toHex(off), toHex(val));
}
}
return retVal;
}
_init();
return {
ioSwitch: function (off, val) {
return _access(off, val);
},
read: function(page, off) {
return rom[(page - 0xc0) << 8 | off];
},
write: function(page, off, val) {
if (_writeEEPROM) {
_debug('writing', toHex(page << 8 | off), toHex(val));
rom[(page - 0xc0) << 8 | off] - val;
}
},
// Assign a raw disk image to a drive. Must be 2mg or raw PO image.
setBinary: function(drive, name, ext, rawData) {
drive = drive - 1;
var disk;
var options = {
rawData,
name
};
if (ext === '2mg') {
disk = new _2MG(options);
} else {
disk = new BlockVolume(options);
}
// Convert 512 byte blocks into 256 word sectors
_sectors[drive] = disk.blocks.map(function(block) {
return new Uint16Array(block.buffer);
});
_identity[drive][IDENTITY.SectorCountHigh] = _sectors[0].length & 0xffff;
_identity[drive][IDENTITY.SectorCountLow] = _sectors[0].length >> 16;
var prodos = new ProDOSVolume(disk);
dump(prodos);
_partitions[drive] = prodos;
if (drive) {
rom[SETTINGS.Max32MBPartitionsDev1] = 0x1;
} else {
rom[SETTINGS.Max32MBPartitionsDev0] = 0x1;
}
}
};
}

View File

@ -9,45 +9,74 @@
* implied warranty.
*/
import BlockVolume from './block';
import DOS from './do';
import Nibble from './nib';
import ProDOS from './po';
import { numToString, debug } from '../util';
export default function _2MG(options) {
var { rawData } = options;
var OFFSETS = {
CREATOR: 0x04,
FLAGS: 0x0A,
FORMAT: 0x0C,
BLOCKS: 0x14,
DATA_OFFSET: 0x18,
BYTES: 0x1C,
};
var FLAGS = {
READ_ONLY: 0x80000000,
VOLUME_VALID: 0x00000100,
VOLUME_MASK: 0x000000FF
};
var { rawData, arrayConstructor } = options;
var disk;
var volume = 254;
// Standard header size is 64 bytes. Make assumptions.
var prefix = new Uint8Array(rawData.slice(0, 64));
rawData = rawData.slice(64);
var flags =
prefix[0x10] |
(prefix[0x11] << 8) |
(prefix[0x12] << 16) |
(prefix[0x13] << 24);
var readOnly = (flags & 0x80000000) !== 0;
if ((flags & 0x10) !== 0) {
volume = flags & 0xff;
var prefix = new DataView(rawData);
var signature = numToString(prefix.getInt32(0x0, true));
if (signature !== '2IMG') {
throw new Error('Unrecognized 2mg signature: ' + signature);
}
var creator = numToString(prefix.getInt32(OFFSETS.CREATOR, true));
var format = prefix.getInt32(OFFSETS.FORMAT, true);
var bytes = prefix.getInt32(OFFSETS.BYTES, true);
var offset = prefix.getInt32(OFFSETS.DATA_OFFSET, true);
var flags = prefix.getInt32(OFFSETS.FLAGS, true);
var readOnly = (flags & FLAGS.READ_ONLY) !== 0;
if (flags & FLAGS.VOLUME_VALID) {
volume = flags & FLAGS.VOLUME_MASK;
}
options = { rawData, readOnly, volume };
debug('created by', creator);
rawData = rawData.slice(offset, offset + bytes);
// Check image format.
// Sure, it's really 64 bits. But only 2 are actually used.
switch (prefix[0xc]) {
case 1: // PO
disk = new ProDOS(options);
break;
case 2: // NIB
disk = new Nibble(options);
break;
case 0: // dsk
default: // Something hinky, assume 'dsk'
disk = new DOS(options);
break;
var blockVolume = options.blockVolume || rawData.byteLength >= (800 * 1024);
options = { rawData, readOnly, volume, arrayConstructor };
if (blockVolume) {
disk = new BlockVolume(options);
} else {
// Check image format.
// Sure, it's really 64 bits. But only 2 are actually used.
switch (format) {
case 1: // PO
disk = new ProDOS(options);
break;
case 2: // NIB
disk = new Nibble(options);
break;
case 0: // dsk
default: // Something hinky, assume 'dsk'
disk = new DOS(options);
break;
}
}
return disk;
}

31
js/formats/block.js Normal file
View File

@ -0,0 +1,31 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
export default function BlockVolume(options) {
var { rawData, readOnly, name } = options;
var disk;
var blocks = [];
blocks = [];
var offset = 0;
while (offset < rawData.byteLength) {
blocks.push(new Uint8Array(rawData.slice(offset, offset + 0x200)));
offset += 0x200;
}
disk = {
blocks,
name,
readOnly,
};
return disk;
}

View File

@ -0,0 +1,57 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
export function BitMap(volume) {
var vdh = volume.vdh();
var blocks = volume.blocks();
var BLOCK_ENTRIES = 4096;
function _init() {
}
_init();
return {
allocBlock: function () {
for (var idx = 0; idx < vdh.totalBlocks; idx++) {
var blockOffset = vdh.bitMapPointer + Math.floor(idx / BLOCK_ENTRIES);
var bitMapBlock = blocks[blockOffset];
var byteOffset = (idx - blockOffset * BLOCK_ENTRIES) >> 8;
var bits = bitMapBlock[byteOffset];
if (bits !== 0xff) {
var mask = 0x01;
for (var bitOffset = 0; bitOffset < 8; bitOffset++) {
if (!(bits & mask)) {
bitMapBlock[byteOffset] |= mask;
return idx;
}
mask <<= 1;
}
}
}
throw new Error('Disk full');
},
freeBlock: function (block) {
if (block >= vdh.totalBlocks) {
throw new Error('Block out of range');
}
var blockOffset = vdh.bitMapPointer + Math.floor(block / BLOCK_ENTRIES);
var byteOffset = (block - blockOffset * BLOCK_ENTRIES) >> 8;
var bitOffset = block & 0x7;
var bitMapBlock = blocks[blockOffset];
bitMapBlock[byteOffset] &= 0xff ^ (0x01 << bitOffset);
}
};
}

View File

@ -0,0 +1,65 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
export var BLOCK_SIZE = 512;
export var STORAGE_TYPES = {
DELETED: 0x0,
SEEDLING: 0x1,
SAPLING: 0x2,
TREE: 0x3,
DIRECTORY: 0xD,
SUBDIRECTORY_HEADER: 0xE,
VDH_HEADER: 0xF
};
export var ACCESS_TYPES = {
DELETE: 0x80,
RENAME: 0x40,
BACKUP: 0x20,
WRITE: 0x02,
READ: 0x01,
ALL: 0xE3
};
export var FILE_TYPES = {
0x00: 'UNK', // Typeless file (SOS and ProDOS)
0x01: 'BAD', // Bad block file
0x02: 'PDC', // Pascal code file
0x03: 'PTX', // Pascal text file
0x04: 'TXT', // ASCII text file (SOS and ProDOS)
0x05: 'PDA', // Pascal data file
0x06: 'BIN', // General binary file (SOS and ProDOS)
0x07: 'FNT', // Font file
0x08: 'FOT', // Graphics screen file
0x09: 'BA3', // Business BASIC program file
0x0A: 'DA3', // Business BASIC data file
0x0B: 'WPF', // Word Processor file
0x0C: 'SOS', // SOS system file
0x0F: 'DIR', // Directory file (SOS and ProDOS)
0x10: 'RPD', // RPS data file
0x11: 'RPI', // RPS index file
0x12: 'AFD', // AppleFile discard file
0x13: 'AFM', // AppleFile model file
0x14: 'ARF', // AppleFile report format file
0x15: 'SCL', // Screen Library file
0x19: 'ADB', // AppleWorks Data Base file
0x1A: 'AWP', // AppleWorks Word Processor file
0x1B: 'ASP', // AppleWorks Spreadsheet file
0xEF: 'PAR', // Pascal area
0xF0: 'CMD', // ProDOS CI added command file
0xFA: 'INT', // Integer BASIC program file
0xFB: 'IVR', // Integer BASIC variable file
0xFC: 'BAS', // Applesoft program file
0xFD: 'VAR', // Applesoft variables file
0xFE: 'REL', // Relocatable code file (EDASM)
0xFF: 'SYS' // ProDOS system file
};

View File

@ -0,0 +1,98 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { dateToUint32, readFileName, writeFileName, uint32ToDate } from './utils';
import { readEntries, writeEntries } from './file_entry';
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
export function Directory(volume, fileEntry) {
var _fileEntry = fileEntry;
var DIRECTORY_OFFSETS = {
PREV: 0x00,
NEXT: 0x02,
STORAGE_TYPE: 0x04,
NAME_LENGTH: 0x04,
VOLUME_NAME: 0x05,
RESERVED_1: 0x14,
CREATION: 0x1C,
VERSION: 0x20,
MIN_VERSION: 0x21,
ACCESS: 0x22,
ENTRY_LENGTH: 0x23,
ENTRIES_PER_BLOCK: 0x24,
FILE_COUNT: 0x25,
PARENT: 0x27,
PARENT_ENTRY_NUMBER: 0x29,
PARENT_ENTRY_LENGTH: 0x2A
};
var blocks = volume.blocks();
return {
prev: 0,
next: 0,
storageType: STORAGE_TYPES.DIRECTORY,
name: 'Untitled',
creation: new Date(),
access: ACCESS_TYPES.ALL,
entryLength: 0x27,
entriesPerBlock: 23,
fileCount: 0,
parent: 0,
parentEntryLength: 0,
parentEntryNumber: 0,
entries: [],
read: function(fileEntry) {
fileEntry = fileEntry || _fileEntry;
var block = new DataView(blocks[fileEntry.keyPointer].buffer);
this.prev = block.getUint16(DIRECTORY_OFFSETS.PREV, true);
this.next = block.getUint16(DIRECTORY_OFFSETS.NEXT, true);
this.storageType = block.getUint8(DIRECTORY_OFFSETS.STORAGE_TYPE) >> 4;
var nameLength = block.getUint8(DIRECTORY_OFFSETS.NAME_LENGTH) & 0xF;
var caseBits = block.getUint8(DIRECTORY_OFFSETS.CASE_BITS);
this.name = readFileName(block, DIRECTORY_OFFSETS.VOLUME_NAME, nameLength, caseBits);
this.creation = uint32ToDate(block.getUint32(DIRECTORY_OFFSETS.CREATION, true));
this.access = block.getUint8(DIRECTORY_OFFSETS.ACCESS);
this.entryLength = block.getUint8(DIRECTORY_OFFSETS.ENTRY_LENGTH);
this.entriesPerBlock = block.getUint8(DIRECTORY_OFFSETS.ENTRIES_PER_BLOCK);
this.fileCount = block.getUint16(DIRECTORY_OFFSETS.FILE_COUNT, true);
this.parent = block.getUint16(DIRECTORY_OFFSETS.PARENT, true);
this.parentEntryNumber = block.getUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_NUMBER);
this.parentEntryLength = block.getUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_LENGTH);
this.entries = readEntries(volume, block, this);
},
write: function() {
var block = new DataView(blocks[fileEntry.keyPointer].buffer);
var nameLength = name.length & 0x0f;
block.setUint8(DIRECTORY_OFFSETS.STORAGE_TYPE, this.storageType << 4 & nameLength);
var caseBits = writeFileName(block, DIRECTORY_OFFSETS.FILE_NAME, this.name);
block.setUint32(DIRECTORY_OFFSETS.CREATION, dateToUint32(this.creation), true);
block.setUint16(DIRECTORY_OFFSETS.CASE_BITS, caseBits);
block.setUint8(DIRECTORY_OFFSETS.ACCESS, this.access);
block.setUint8(DIRECTORY_OFFSETS.ENTRY_LENGTH, this.entryLength);
block.setUint8(DIRECTORY_OFFSETS.ENTRIES_PER_BLOCK, this.entriesPerBlock);
block.setUint16(DIRECTORY_OFFSETS.FILE_COUNT, this.fileCount, true);
block.setUint16(DIRECTORY_OFFSETS.PARENT, this.parent, true);
block.setUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_NUMBER, this.parentEntryNumber);
block.setUint8(DIRECTORY_OFFSETS.PARENT_ENTRY_LENGTH, this.parentEntryLength);
writeEntries(volume, block, this);
}
};
}

View File

@ -0,0 +1,150 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { dateToUint32, readFileName, writeFileName, uint32ToDate } from './utils';
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
export function FileEntry () {
var ENTRY_OFFSETS = {
STORAGE_TYPE: 0x00,
NAME_LENGTH: 0x00,
FILE_NAME: 0x01,
FILE_TYPE: 0x10,
KEY_POINTER: 0x11,
BLOCKS_USED: 0x13,
EOF: 0x15,
CREATION: 0x18,
CASE_BITS: 0x1C,
VERSION: 0x1C,
MIN_VERSION: 0x1D,
ACCESS: 0x1E,
AUX_TYPE: 0x1F,
LAST_MOD: 0x21,
HEADER_POINTER: 0x25
};
var _block;
var _offset;
return {
storageType: STORAGE_TYPES.SEEDLING,
name: 'Untitled',
fileType: 0,
auxType: 0,
blocksUsed: 0,
eof: 0,
access: ACCESS_TYPES.ALL,
creation: new Date(),
lastMod: new Date(),
keyPointer: 0,
headerPointer: 0,
read: function(block, offset) {
_block = block;
_offset = offset;
this.storageType = block.getUint8(offset + ENTRY_OFFSETS.STORAGE_TYPE) >> 4;
var nameLength = block.getUint8(offset + ENTRY_OFFSETS.NAME_LENGTH) & 0xF;
var caseBits = block.getUint16(offset + ENTRY_OFFSETS.CASE_BITS, true);
this.name = readFileName(block, offset + ENTRY_OFFSETS.FILE_NAME, nameLength, caseBits);
this.fileType = block.getUint8(offset + ENTRY_OFFSETS.FILE_TYPE);
this.keyPointer = block.getUint16(offset + ENTRY_OFFSETS.KEY_POINTER, true);
this.blocksUsed = block.getUint16(offset + ENTRY_OFFSETS.BLOCKS_USED, true);
this.eof =
block.getUint8(offset + ENTRY_OFFSETS.EOF) |
block.getUint8(offset + ENTRY_OFFSETS.EOF + 1) << 8 |
block.getUint8(offset + ENTRY_OFFSETS.EOF + 2) << 16;
this.creation = uint32ToDate(block.getUint32(offset + ENTRY_OFFSETS.CREATION, true));
this.access = block.getUint8(offset + ENTRY_OFFSETS.ACCESS);
this.auxType = block.getUint16(offset + ENTRY_OFFSETS.AUX_TYPE, true);
this.lastMod = uint32ToDate(block.getUint32(offset + ENTRY_OFFSETS.LAST_MOD, true));
this.headerPointer = block.getUint16(offset + ENTRY_OFFSETS.HEADER_POINTER, true);
},
write: function(block, offset) {
block = block || _block;
offset = offset || _offset;
var nameLength = name.length & 0x0f;
block.setUint8(offset + ENTRY_OFFSETS.STORAGE_TYPE, this.storageType << 4 & nameLength);
var caseBits = writeFileName(block, offset + ENTRY_OFFSETS.FILE_NAME, this.name);
block.setUint16(offset + ENTRY_OFFSETS.CASE_BITS, caseBits);
block.setUint8(offset + ENTRY_OFFSETS.FILE_TYPE, this.fileType);
block.setUint16(offset + ENTRY_OFFSETS.KEY_POINTER, this.keyPointer, true);
block.setUint16(offset + ENTRY_OFFSETS.BLOCKS_USED, this.blocksUsed, true);
block.setUint8(offset + ENTRY_OFFSETS.EOF, this.eof & 0xff);
block.setUint8(offset + ENTRY_OFFSETS.EOF + 1, (this.eof && 0xff00) >> 8);
block.setUint8(offset + ENTRY_OFFSETS.EOF + 2, this.eof >> 16);
block.setUint32(offset + ENTRY_OFFSETS.CREATION, dateToUint32(this.creation), true);
block.setUint8(offset + ENTRY_OFFSETS.ACCESS, this.access);
block.setUint16(offset + ENTRY_OFFSETS.AUX_TYPE, this.auxType, true);
block.setUint32(offset + ENTRY_OFFSETS.LAST_MOD, dateToUint32(this.lastMod), true);
block.setUint16(offset + ENTRY_OFFSETS.HEADER_POINTER, this.headerPointer, true);
}
};
}
export function readEntries(volume, block, header) {
var blocks = volume.blocks();
var entries = [];
var offset = header.entryLength + 0x4;
var count = 2;
var next = header.next;
for (var idx = 0; idx < header.fileCount; idx++) {
var fileEntry = new FileEntry();
fileEntry.read(block, offset);
entries.push(fileEntry);
offset += header.entryLength;
count++;
if (count >= header.entriesPerBlock) {
block = new DataView(blocks[next].buffer);
next = block.getUint16(0x02, true);
offset = 0x4;
count = 0;
}
}
return entries;
}
export function writeEntries(volume, block, header) {
var blocks = volume.blocks();
var bitMap = volume.bitmap();
var offset = header.entryLength + 0x4;
var count = 2;
var next = header.next;
for (var idx = 0; idx < header.fileCount; idx++) {
var fileEntry = new header.entries[idx];
fileEntry.write(block, offset);
offset += header.entryLength;
count++;
if (count >= header.entriesPerBlock) {
var prev = next;
if (!next) {
next = bitMap.allocBlock();
}
block = new DataView(blocks[next].buffer);
block.setUint16(0x00, prev, true);
next = block.getUint16(0x02, true);
offset = 0x4;
count = 0;
}
}
next = block.getUint16(0x02, true);
block.setUint16(0x02, 0, true);
while (next) {
block = new DataView(blocks[next].buffer);
bitMap.freeLock(next);
next = block.getUint16(0x02, true);
}
}

View File

@ -0,0 +1,40 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { VDH } from './vdh';
import { BitMap } from './bit_map';
export function ProDOSVolume(disk) {
var _disk = disk;
var _vdh;
var _bitMap;
return {
blocks() {
return _disk.blocks;
},
vdh() {
if (!_vdh) {
_vdh = new VDH(this);
_vdh.read();
}
return _vdh;
},
bitMap() {
if (!_bitMap) {
_bitMap = new BitMap(this);
}
return _bitMap;
}
};
}

View File

@ -0,0 +1,87 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { BLOCK_SIZE, STORAGE_TYPES } from './constants';
export function SaplingFile (volume, fileEntry) {
var blocks = volume.blocks();
var bitMap = volume.bitMap();
return {
getBlockPointers() {
var saplingBlock = blocks[fileEntry.keyPointer];
var seedlingPointers = new DataView(saplingBlock);
var pointers = [fileEntry.keyPointer];
for (var idx = 0; idx < 256; idx++) {
var seedlingPointer = seedlingPointers.getUint16(idx * 2);
if (seedlingPointer) {
pointers.push(seedlingPointer);
}
}
return pointers;
},
read: function() {
var saplingBlock = blocks[fileEntry.keyPointer];
var seedlingPointers = new DataView(saplingBlock);
var remainingLength = fileEntry.oef;
var data = new Uint8Array(remainingLength);
var offset = 0;
var idx = 0;
while (remainingLength > 0) {
var seedlingPointer = seedlingPointers.getUint16(idx * 2);
if (seedlingPointer) {
var seedlingBlock = blocks[seedlingPointer];
var bytes = seedlingBlock.slice(0, Math.min(BLOCK_SIZE, remainingLength));
data.set(bytes, offset);
}
idx++;
offset += BLOCK_SIZE;
remainingLength -= BLOCK_SIZE;
}
return data;
},
write: function(data) {
fileEntry.storageType = STORAGE_TYPES.SAPLING;
fileEntry.keyPointer = bitMap.allocBlock();
fileEntry.eof = data.byteLength;
var saplingBlock = blocks[fileEntry.keyPointer];
var seedlingPointers = new DataView(saplingBlock);
var remainingLength = data.byteLength;
var offset = 0;
var idx = 0;
while (remainingLength > 0) {
var seedlingPointer = bitMap.allocBlock();
seedlingPointers.setUint16(idx * 2, seedlingPointer, true);
var seedlingBlock = blocks[seedlingPointer];
seedlingBlock.set(data.slice(offset, Math.min(BLOCK_SIZE, remainingLength)));
idx++;
offset += BLOCK_SIZE;
remainingLength -= BLOCK_SIZE;
}
fileEntry.write();
},
delete: function() {
var pointers = this.getBlockPointers();
for (var idx; idx < pointers.length; idx++) {
bitMap.freeBlock(pointers[idx]);
}
}
};
}

View File

@ -0,0 +1,50 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { STORAGE_TYPES } from './constants';
export function SeedlingFile (volume, fileEntry) {
var blocks = volume.blocks();
var bitMap = volume.bitMap();
return {
getBlockPointers() {
var pointers = [fileEntry.keyPointer];
return pointers;
},
read: function () {
var seedlingBlock = blocks[fileEntry.keyPointer];
var data = new Uint8Array(fileEntry.eof);
data.set(seedlingBlock.slice(0, fileEntry.eof));
return data;
},
write: function(data) {
if (fileEntry.keyPointer) {
this.delete();
}
fileEntry.storageType = STORAGE_TYPES.SEEDLING;
fileEntry.keyPointer = bitMap.allocBlock();
fileEntry.eof = data.byteLength;
var seedlingBlock = blocks[fileEntry.keyPointer];
seedlingBlock.set(data);
fileEntry.write();
},
delete: function() {
var pointers = this.getBlockPointers();
for (var idx; idx < pointers.length; idx++) {
bitMap.freeBlock(pointers[idx]);
}
}
};
}

View File

@ -0,0 +1,116 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { BLOCK_SIZE, STORAGE_TYPES } from './constants';
export function TreeFile (volume, fileEntry) {
var blocks = volume.blocks();
var bitMap = volume.bitMap();
return {
getBlockPointers() {
var treeBlock = blocks[fileEntry.keyPointer];
var saplingPointers = new DataView(treeBlock);
var pointers = [];
for (var idx = 0; idx < 256; idx++) {
var saplingPointer = saplingPointers.getUint16(idx * 2);
if (saplingPointer) {
pointers.push(saplingPointer);
var seedlingPointers = new DataView(blocks[saplingPointer]);
for (var jdx = 0; jdx < 256; jdx++) {
var seedlingPointer = seedlingPointers.getUint16(idx * 2);
if (seedlingPointer) {
pointers.push(seedlingPointer);
}
}
}
}
return pointers;
},
read: function() {
var treeBlock = blocks[fileEntry.keyPointer];
var saplingPointers = new DataView(treeBlock);
var remainingLength = fileEntry.eof;
var data = new Uint8Array(remainingLength);
var offset = 0;
var idx = 0;
while (remainingLength > 0) {
var saplingPointer = saplingPointers.getUint16(idx * 2, true);
var jdx = 0;
if (saplingPointer) {
var saplingBlock = blocks[saplingPointer];
var seedlingPointers = new DataView(saplingBlock);
while (jdx < 256 && remainingLength > 0) {
var seedlingPointer = seedlingPointers.getUint16(idx * 2, true);
if (seedlingPointer) {
var seedlingBlock = blocks[seedlingPointer];
var bytes = seedlingBlock.slice(Math.min(BLOCK_SIZE, remainingLength));
data.set(bytes, offset);
}
jdx++;
offset += BLOCK_SIZE;
remainingLength -= BLOCK_SIZE;
}
} else {
offset += BLOCK_SIZE * 256;
remainingLength -= BLOCK_SIZE * 256;
}
idx++;
}
return data;
},
write: function(data) {
fileEntry.storageType = STORAGE_TYPES.TREE;
fileEntry.keyPointer = bitMap.allocBlock();
fileEntry.eof = data.byteLength;
var treeBlock = blocks[fileEntry.keyPointer];
var saplingPointers = new DataView(treeBlock);
var remainingLength = fileEntry.eof;
var offset = 0;
var idx = 0;
while (remainingLength > 0) {
var saplingPointer = bitMap.allocBlock();
var saplingBlock = blocks[saplingPointer];
saplingPointers.setUint16(idx * 2, saplingPointer, true);
var seedlingPointers = new DataView(saplingBlock);
var jdx = 0;
while (jdx < 256 && remainingLength > 0) {
var seedlingPointer = bitMap.allocBlock();
seedlingPointers.setUint16(idx * 2, seedlingPointer, true);
var seedlingBlock = blocks[seedlingPointer];
seedlingBlock.set(data.slice(offset, Math.min(BLOCK_SIZE, remainingLength)));
jdx++;
offset += BLOCK_SIZE;
remainingLength -= BLOCK_SIZE;
}
idx++;
}
fileEntry.write();
},
delete: function() {
var pointers = this.getBlockPointers();
for (var idx; idx < pointers.length; idx++) {
bitMap.freeBlock(pointers[idx]);
}
}
};
}

108
js/formats/prodos/utils.js Normal file
View File

@ -0,0 +1,108 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { debug } from '../../util';
import { STORAGE_TYPES } from './constants';
import { Directory } from './directory';
export function uint32ToDate(val) {
// yyyyyyy m|mmmm ddddd|000hhhhh|00mmmmmm
if (val) {
var yearMonthDay = val & 0xffff;
var hourMinute = val >> 16;
var year = yearMonthDay >> 9;
var month = (yearMonthDay & 0x01E0) >> 5;
var day = yearMonthDay & 0x001F;
var hour = hourMinute >> 8;
var min = hourMinute & 0xff;
return new Date(1900 + year, month - 1, day, hour, min);
}
return null;
}
export function dateToUint32(date) {
// yyyyyyy m|mmmm ddddd|000hhhhh|00mmmmmm
var val = 0;
if (date) {
var year = date.getYear() - 1900;
var month = date.getMonth() + 1;
var day = date.getDate();
var hour = date.getHour();
var min = date.getMinute();
var yearMonthDay = year << 9 | month << 5 | day;
var hourMinute = hour << 8 | min;
val = hourMinute << 16 | yearMonthDay;
}
return val;
}
export function readFileName(block, offset, nameLength, caseBits) {
var name = '';
if (!(caseBits & 0x8000)) {
caseBits = 0;
}
for (var idx = 0; idx < nameLength; idx++) {
caseBits <<= 1;
var char = String.fromCharCode(block.getUint8(offset + idx));
name += caseBits & 0x8000 ? char.toLowerCase() : char;
}
return name;
}
export function writeFileName(block, offset, name) {
var caseBits = 0;
for (var idx = 0; idx < name.length; idx++) {
caseBits <<= 1;
var charCode = name.charCodeAt(idx);
if (charCode > 0x60 && charCode < 0x7B) {
caseBits |= 0x1;
charCode -= 0x20;
}
block.setUint8(offset + idx, charCode);
}
return caseBits;
}
export function dumpDirectory(volume, dirEntry, depth) {
var dir = new Directory(volume, dirEntry);
dir.read();
for (var idx = 0; idx < dir.entries.length; idx++) {
var fileEntry = dir.entries[idx];
if (fileEntry.storageType !== STORAGE_TYPES.DELETED) {
debug(depth, fileEntry.name);
if (fileEntry.storageType === STORAGE_TYPES.DIRECTORY) {
dumpDirectory(volume, fileEntry, depth + ' ');
}
}
}
}
export function dump(volume) {
var vdh = volume.vdh();
debug(vdh.name);
for (var idx = 0; idx < vdh.entries.length; idx++) {
var fileEntry = vdh.entries[idx];
debug(fileEntry.name);
if (fileEntry.storageType === STORAGE_TYPES.DIRECTORY) {
dumpDirectory(volume, fileEntry, ' ');
}
}
}

91
js/formats/prodos/vdh.js Normal file
View File

@ -0,0 +1,91 @@
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. No representations are made about the suitability of this
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*/
import { dateToUint32, readFileName, writeFileName, uint32ToDate } from './utils';
import { readEntries, writeEntries } from './file_entry';
import { STORAGE_TYPES, ACCESS_TYPES } from './constants';
export function VDH(volume) {
var blocks = volume.blocks();
var VDH_BLOCK = 2;
var VDH_OFFSETS = {
PREV: 0x00,
NEXT: 0x02,
STORAGE_TYPE: 0x04,
NAME_LENGTH: 0x04,
VOLUME_NAME: 0x05,
RESERVED_1: 0x14,
CASE_BITS: 0x1A,
CREATION: 0x1C,
VERSION: 0x20,
MIN_VERSION: 0x21,
ACCESS: 0x22,
ENTRY_LENGTH: 0x23,
ENTRIES_PER_BLOCK: 0x24,
FILE_COUNT: 0x25,
BIT_MAP_POINTER: 0x27,
TOTAL_BLOCKS: 0x29,
};
return {
prev: 0,
next: 0,
storageType: STORAGE_TYPES.VDH_HEADER,
name: '',
creation: new Date(),
access: ACCESS_TYPES.ALL,
entryLength: 0x27,
entriesPerBlock: 23,
fileCount: 0,
bitMapPointer: 0,
totalBlocks: 0,
entries: [],
read: function () {
var block = new DataView(blocks[VDH_BLOCK].buffer);
this.next = block.getUint16(VDH_OFFSETS.NEXT, true);
this.storageType = block.getUint8(VDH_OFFSETS.STORAGE_TYPE) >> 4;
var nameLength = block.getUint8(VDH_OFFSETS.NAME_LENGTH) & 0xF;
var caseBits = block.getUint8(VDH_OFFSETS.CASE_BITS);
this.name = readFileName(block, VDH_OFFSETS.VOLUME_NAME, nameLength, caseBits);
this.creation = uint32ToDate(block.getUint32(VDH_OFFSETS.CREATION, true));
this.access = block.getUint8(VDH_OFFSETS.ACCESS);
this.entryLength = block.getUint8(VDH_OFFSETS.ENTRY_LENGTH);
this.entriesPerBlock = block.getUint8(VDH_OFFSETS.ENTRIES_PER_BLOCK);
this.fileCount = block.getUint16(VDH_OFFSETS.FILE_COUNT, true);
this.bitMapPointer = block.getUint16(VDH_OFFSETS.BIT_MAP_POINTER, true);
this.totalBlocks = block.getUint16(VDH_OFFSETS.TOTAL_BLOCKS, true);
this.entries = readEntries(volume, block, this);
},
write: function() {
var block = new DataView(blocks[VDH_BLOCK].buffer);
var nameLength = name.length & 0x0f;
block.setUint8(VDH_OFFSETS.STORAGE_TYPE, this.storageType << 4 & nameLength);
var caseBits = writeFileName(block, VDH_OFFSETS.FILE_NAME, this.name);
block.setUint32(VDH_OFFSETS.CREATION, dateToUint32(this.creation), true);
block.setUint16(VDH_OFFSETS.CASE_BITS, caseBits);
block.setUint8(VDH_OFFSETS.ACCESS, this.access);
block.setUint8(VDH_OFFSETS.ENTRY_LENGTH, this.entryLength);
block.setUint8(VDH_OFFSETS.ENTRIES_PER_BLOCK, this.entriesPerBlock);
block.setUint16(VDH_OFFSETS.FILE_COUNT, this.fileCount, true);
block.setUint16(VDH_OFFSETS.BIT_MAP_POINTER, this.bitMapPointer, true);
block.setUint16(VDH_OFFSETS.TOTAL_BLOCKS, this.totalBlocks, true);
writeEntries(blocks, block, this);
}
};
}

View File

@ -4,6 +4,7 @@ import { driveLights, initUI, updateUI } from './ui/apple2';
import Printer from './ui/printer';
import DiskII from './cards/disk2';
import CFFA from './cards/cffa';
import LanguageCard from './cards/langcard';
import Parallel from './cards/parallel';
import RAMFactor from './cards/ramfactor';
@ -97,8 +98,9 @@ var videoTerm = new VideoTerm(io, options.screen[0]);
var slinky = new RAMFactor(io, 1024 * 1024);
var disk2 = new DiskII(io, driveLights);
var clock = new Thunderclock(io);
var cffa = new CFFA(io);
initUI(apple2, disk2, false);
initUI(apple2, disk2, cffa, false);
io.setSlot(0, lc);
io.setSlot(1, parallel);

View File

@ -3,6 +3,7 @@ import Prefs from './prefs';
import { driveLights, initUI, updateUI } from './ui/apple2';
import Printer from './ui/printer';
import CFFA from './cards/cffa';
import DiskII from './cards/disk2';
import Parallel from './cards/parallel';
import RAMFactor from './cards/ramfactor';
@ -82,12 +83,14 @@ var parallel = new Parallel(io, printer);
var slinky = new RAMFactor(io, 1024 * 1024);
var disk2 = new DiskII(io, driveLights);
var clock = new Thunderclock(io);
var cffa = new CFFA(io);
initUI(apple2, disk2, options.e);
initUI(apple2, disk2, cffa, options.e);
io.setSlot(1, parallel);
io.setSlot(2, slinky);
io.setSlot(4, clock);
io.setSlot(5, clock);
io.setSlot(6, disk2);
io.setSlot(7, cffa);

519
js/roms/cards/cffa.js Normal file
View File

@ -0,0 +1,519 @@
/*
* http://dreher.net/?s=projects/CFforAppleII&c=projects/CFforAppleII/downloads1.php
*/
export var rom = [
0x43, 0x46, 0x46, 0x41, 0x20, 0x46, 0x69, 0x72,
0x6d, 0x77, 0x61, 0x72, 0x65, 0x0d, 0x0d, 0x0d,
0x53, 0x65, 0x65, 0x20, 0x3c, 0x68, 0x74, 0x74,
0x70, 0x3a, 0x2f, 0x2f, 0x64, 0x72, 0x65, 0x68,
0x65, 0x72, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x43,
0x46, 0x66, 0x6f, 0x72, 0x41, 0x70, 0x70, 0x6c,
0x65, 0x49, 0x49, 0x2f, 0x3e, 0x2e, 0x0d, 0x0d,
0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20,
0x32, 0x2e, 0x30, 0x20, 0x66, 0x6f, 0x72, 0x20,
0x36, 0x35, 0x30, 0x32, 0x20, 0x6f, 0x72, 0x20,
0x6c, 0x61, 0x74, 0x65, 0x72, 0x2e, 0x0d, 0x0d,
0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73,
0x20, 0x43, 0x46, 0x46, 0x41, 0x20, 0x77, 0x69,
0x74, 0x68, 0x20, 0x45, 0x45, 0x50, 0x52, 0x4f,
0x4d, 0x2c, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x45,
0x50, 0x52, 0x4f, 0x4d, 0x2e, 0x0d, 0x0d, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xa9, 0x20, 0xa2, 0x00, 0xa9, 0x03, 0xa9, 0x3c,
0x10, 0x0a, 0x18, 0x90, 0x01, 0x38, 0x20, 0xa1,
0xc1, 0x4c, 0x28, 0xc8, 0x20, 0xa1, 0xc1, 0x86,
0x43, 0xac, 0x0b, 0xc8, 0xa9, 0xc5, 0x20, 0xa8,
0xfc, 0x88, 0xd0, 0xf8, 0xad, 0x00, 0xc0, 0x2d,
0x09, 0xc8, 0xcd, 0x0a, 0xc8, 0xd0, 0x07, 0xea,
0x20, 0xa1, 0xc1, 0x20, 0x2b, 0xc8, 0xa0, 0x01,
0x84, 0x42, 0x88, 0x84, 0x46, 0x84, 0x47, 0xa9,
0x08, 0x85, 0x45, 0x84, 0x44, 0x20, 0xa1, 0xc1,
0x20, 0x37, 0xc8, 0x85, 0xff, 0xb0, 0x10, 0xac,
0x00, 0x08, 0x88, 0xd0, 0x0a, 0xad, 0x01, 0x08,
0xf0, 0x05, 0xa6, 0x43, 0x4c, 0x01, 0x08, 0xa5,
0x00, 0xd0, 0x0a, 0xa5, 0x01, 0xcd, 0xf8, 0x07,
0xd0, 0x03, 0x4c, 0xba, 0xfa, 0x20, 0x2e, 0xc8,
0xa0, 0x00, 0xa6, 0xff, 0xd0, 0x02, 0xa0, 0x0e,
0x20, 0x95, 0xc1, 0x8a, 0xf0, 0x0c, 0xad, 0x79,
0x07, 0x0a, 0x0a, 0xa9, 0xb0, 0x69, 0x00, 0x20,
0xed, 0xfd, 0xa0, 0x1c, 0x20, 0x95, 0xc1, 0x20,
0xa1, 0xc1, 0x4c, 0x34, 0xc8, 0xb9, 0xbc, 0xc1,
0xf0, 0x06, 0x20, 0x31, 0xc8, 0xc8, 0xd0, 0xf5,
0x60, 0x8d, 0x92, 0xc0, 0xa9, 0xc1, 0x8d, 0xf8,
0x07, 0x2c, 0xff, 0xcf, 0xad, 0x20, 0xc8, 0x49,
0xcf, 0xd0, 0x05, 0xa0, 0x01, 0xa2, 0x10, 0x60,
0x20, 0x93, 0xfe, 0x00, 0x43, 0x68, 0x65, 0x63,
0x6b, 0x20, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65,
0x20, 0x00, 0x4e, 0x6f, 0x20, 0x62, 0x6f, 0x6f,
0x74, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x00,
0x2e, 0x0d, 0x0d, 0x43, 0x6f, 0x75, 0x6c, 0x64,
0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x6f, 0x6f,
0x74, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x20, 0x00, 0xff, 0x43, 0x46,
0x46, 0x41, 0xff, 0x00, 0x00, 0x00, 0x17, 0x0a,
0xa9, 0x20, 0xa2, 0x00, 0xa9, 0x03, 0xa9, 0x3c,
0x10, 0x0a, 0x18, 0x90, 0x01, 0x38, 0x20, 0xa1,
0xc2, 0x4c, 0x28, 0xc8, 0x20, 0xa1, 0xc2, 0x86,
0x43, 0xac, 0x0b, 0xc8, 0xa9, 0xc5, 0x20, 0xa8,
0xfc, 0x88, 0xd0, 0xf8, 0xad, 0x00, 0xc0, 0x2d,
0x09, 0xc8, 0xcd, 0x0a, 0xc8, 0xd0, 0x07, 0xea,
0x20, 0xa1, 0xc2, 0x20, 0x2b, 0xc8, 0xa0, 0x01,
0x84, 0x42, 0x88, 0x84, 0x46, 0x84, 0x47, 0xa9,
0x08, 0x85, 0x45, 0x84, 0x44, 0x20, 0xa1, 0xc2,
0x20, 0x37, 0xc8, 0x85, 0xff, 0xb0, 0x10, 0xac,
0x00, 0x08, 0x88, 0xd0, 0x0a, 0xad, 0x01, 0x08,
0xf0, 0x05, 0xa6, 0x43, 0x4c, 0x01, 0x08, 0xa5,
0x00, 0xd0, 0x0a, 0xa5, 0x01, 0xcd, 0xf8, 0x07,
0xd0, 0x03, 0x4c, 0xba, 0xfa, 0x20, 0x2e, 0xc8,
0xa0, 0x00, 0xa6, 0xff, 0xd0, 0x02, 0xa0, 0x0e,
0x20, 0x95, 0xc2, 0x8a, 0xf0, 0x0c, 0xad, 0x7a,
0x07, 0x0a, 0x0a, 0xa9, 0xb0, 0x69, 0x00, 0x20,
0xed, 0xfd, 0xa0, 0x1c, 0x20, 0x95, 0xc2, 0x20,
0xa1, 0xc2, 0x4c, 0x34, 0xc8, 0xb9, 0xbc, 0xc2,
0xf0, 0x06, 0x20, 0x31, 0xc8, 0xc8, 0xd0, 0xf5,
0x60, 0x8d, 0xa2, 0xc0, 0xa9, 0xc2, 0x8d, 0xf8,
0x07, 0x2c, 0xff, 0xcf, 0xad, 0x20, 0xc8, 0x49,
0xcf, 0xd0, 0x05, 0xa0, 0x02, 0xa2, 0x20, 0x60,
0x20, 0x93, 0xfe, 0x00, 0x43, 0x68, 0x65, 0x63,
0x6b, 0x20, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65,
0x20, 0x00, 0x4e, 0x6f, 0x20, 0x62, 0x6f, 0x6f,
0x74, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x00,
0x2e, 0x0d, 0x0d, 0x43, 0x6f, 0x75, 0x6c, 0x64,
0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x6f, 0x6f,
0x74, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x20, 0x00, 0xff, 0x43, 0x46,
0x46, 0x41, 0xff, 0x00, 0x00, 0x00, 0x17, 0x0a,
0xa9, 0x20, 0xa2, 0x00, 0xa9, 0x03, 0xa9, 0x3c,
0x10, 0x0a, 0x18, 0x90, 0x01, 0x38, 0x20, 0xa1,
0xc3, 0x4c, 0x28, 0xc8, 0x20, 0xa1, 0xc3, 0x86,
0x43, 0xac, 0x0b, 0xc8, 0xa9, 0xc5, 0x20, 0xa8,
0xfc, 0x88, 0xd0, 0xf8, 0xad, 0x00, 0xc0, 0x2d,
0x09, 0xc8, 0xcd, 0x0a, 0xc8, 0xd0, 0x07, 0xea,
0x20, 0xa1, 0xc3, 0x20, 0x2b, 0xc8, 0xa0, 0x01,
0x84, 0x42, 0x88, 0x84, 0x46, 0x84, 0x47, 0xa9,
0x08, 0x85, 0x45, 0x84, 0x44, 0x20, 0xa1, 0xc3,
0x20, 0x37, 0xc8, 0x85, 0xff, 0xb0, 0x10, 0xac,
0x00, 0x08, 0x88, 0xd0, 0x0a, 0xad, 0x01, 0x08,
0xf0, 0x05, 0xa6, 0x43, 0x4c, 0x01, 0x08, 0xa5,
0x00, 0xd0, 0x0a, 0xa5, 0x01, 0xcd, 0xf8, 0x07,
0xd0, 0x03, 0x4c, 0xba, 0xfa, 0x20, 0x2e, 0xc8,
0xa0, 0x00, 0xa6, 0xff, 0xd0, 0x02, 0xa0, 0x0e,
0x20, 0x95, 0xc3, 0x8a, 0xf0, 0x0c, 0xad, 0x7b,
0x07, 0x0a, 0x0a, 0xa9, 0xb0, 0x69, 0x00, 0x20,
0xed, 0xfd, 0xa0, 0x1c, 0x20, 0x95, 0xc3, 0x20,
0xa1, 0xc3, 0x4c, 0x34, 0xc8, 0xb9, 0xbc, 0xc3,
0xf0, 0x06, 0x20, 0x31, 0xc8, 0xc8, 0xd0, 0xf5,
0x60, 0x8d, 0xb2, 0xc0, 0xa9, 0xc3, 0x8d, 0xf8,
0x07, 0x2c, 0xff, 0xcf, 0xad, 0x20, 0xc8, 0x49,
0xcf, 0xd0, 0x05, 0xa0, 0x03, 0xa2, 0x30, 0x60,
0x20, 0x93, 0xfe, 0x00, 0x43, 0x68, 0x65, 0x63,
0x6b, 0x20, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65,
0x20, 0x00, 0x4e, 0x6f, 0x20, 0x62, 0x6f, 0x6f,
0x74, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x00,
0x2e, 0x0d, 0x0d, 0x43, 0x6f, 0x75, 0x6c, 0x64,
0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x6f, 0x6f,
0x74, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x20, 0x00, 0xff, 0x43, 0x46,
0x46, 0x41, 0xff, 0x00, 0x00, 0x00, 0x17, 0x0a,
0xa9, 0x20, 0xa2, 0x00, 0xa9, 0x03, 0xa9, 0x3c,
0x10, 0x0a, 0x18, 0x90, 0x01, 0x38, 0x20, 0xa1,
0xc4, 0x4c, 0x28, 0xc8, 0x20, 0xa1, 0xc4, 0x86,
0x43, 0xac, 0x0b, 0xc8, 0xa9, 0xc5, 0x20, 0xa8,
0xfc, 0x88, 0xd0, 0xf8, 0xad, 0x00, 0xc0, 0x2d,
0x09, 0xc8, 0xcd, 0x0a, 0xc8, 0xd0, 0x07, 0xea,
0x20, 0xa1, 0xc4, 0x20, 0x2b, 0xc8, 0xa0, 0x01,
0x84, 0x42, 0x88, 0x84, 0x46, 0x84, 0x47, 0xa9,
0x08, 0x85, 0x45, 0x84, 0x44, 0x20, 0xa1, 0xc4,
0x20, 0x37, 0xc8, 0x85, 0xff, 0xb0, 0x10, 0xac,
0x00, 0x08, 0x88, 0xd0, 0x0a, 0xad, 0x01, 0x08,
0xf0, 0x05, 0xa6, 0x43, 0x4c, 0x01, 0x08, 0xa5,
0x00, 0xd0, 0x0a, 0xa5, 0x01, 0xcd, 0xf8, 0x07,
0xd0, 0x03, 0x4c, 0xba, 0xfa, 0x20, 0x2e, 0xc8,
0xa0, 0x00, 0xa6, 0xff, 0xd0, 0x02, 0xa0, 0x0e,
0x20, 0x95, 0xc4, 0x8a, 0xf0, 0x0c, 0xad, 0x7c,
0x07, 0x0a, 0x0a, 0xa9, 0xb0, 0x69, 0x00, 0x20,
0xed, 0xfd, 0xa0, 0x1c, 0x20, 0x95, 0xc4, 0x20,
0xa1, 0xc4, 0x4c, 0x34, 0xc8, 0xb9, 0xbc, 0xc4,
0xf0, 0x06, 0x20, 0x31, 0xc8, 0xc8, 0xd0, 0xf5,
0x60, 0x8d, 0xc2, 0xc0, 0xa9, 0xc4, 0x8d, 0xf8,
0x07, 0x2c, 0xff, 0xcf, 0xad, 0x20, 0xc8, 0x49,
0xcf, 0xd0, 0x05, 0xa0, 0x04, 0xa2, 0x40, 0x60,
0x20, 0x93, 0xfe, 0x00, 0x43, 0x68, 0x65, 0x63,
0x6b, 0x20, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65,
0x20, 0x00, 0x4e, 0x6f, 0x20, 0x62, 0x6f, 0x6f,
0x74, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x00,
0x2e, 0x0d, 0x0d, 0x43, 0x6f, 0x75, 0x6c, 0x64,
0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x6f, 0x6f,
0x74, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x20, 0x00, 0xff, 0x43, 0x46,
0x46, 0x41, 0xff, 0x00, 0x00, 0x00, 0x17, 0x0a,
0xa9, 0x20, 0xa2, 0x00, 0xa9, 0x03, 0xa9, 0x00,
0x10, 0x0a, 0x18, 0x90, 0x01, 0x38, 0x20, 0xa1,
0xc5, 0x4c, 0x28, 0xc8, 0x20, 0xa1, 0xc5, 0x86,
0x43, 0xac, 0x0b, 0xc8, 0xa9, 0xc5, 0x20, 0xa8,
0xfc, 0x88, 0xd0, 0xf8, 0xad, 0x00, 0xc0, 0x2d,
0x09, 0xc8, 0xcd, 0x0a, 0xc8, 0xd0, 0x07, 0xea,
0x20, 0xa1, 0xc5, 0x20, 0x2b, 0xc8, 0xa0, 0x01,
0x84, 0x42, 0x88, 0x84, 0x46, 0x84, 0x47, 0xa9,
0x08, 0x85, 0x45, 0x84, 0x44, 0x20, 0xa1, 0xc5,
0x20, 0x37, 0xc8, 0x85, 0xff, 0xb0, 0x10, 0xac,
0x00, 0x08, 0x88, 0xd0, 0x0a, 0xad, 0x01, 0x08,
0xf0, 0x05, 0xa6, 0x43, 0x4c, 0x01, 0x08, 0xa5,
0x00, 0xd0, 0x0a, 0xa5, 0x01, 0xcd, 0xf8, 0x07,
0xd0, 0x03, 0x4c, 0xba, 0xfa, 0x20, 0x2e, 0xc8,
0xa0, 0x00, 0xa6, 0xff, 0xd0, 0x02, 0xa0, 0x0e,
0x20, 0x95, 0xc5, 0x8a, 0xf0, 0x0c, 0xad, 0x7d,
0x07, 0x0a, 0x0a, 0xa9, 0xb0, 0x69, 0x00, 0x20,
0xed, 0xfd, 0xa0, 0x1c, 0x20, 0x95, 0xc5, 0x20,
0xa1, 0xc5, 0x4c, 0x34, 0xc8, 0xb9, 0xbc, 0xc5,
0xf0, 0x06, 0x20, 0x31, 0xc8, 0xc8, 0xd0, 0xf5,
0x60, 0x8d, 0xd2, 0xc0, 0xa9, 0xc5, 0x8d, 0xf8,
0x07, 0x2c, 0xff, 0xcf, 0xad, 0x20, 0xc8, 0x49,
0xcf, 0xd0, 0x05, 0xa0, 0x05, 0xa2, 0x50, 0x60,
0x20, 0x93, 0xfe, 0x00, 0x43, 0x68, 0x65, 0x63,
0x6b, 0x20, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65,
0x20, 0x00, 0x4e, 0x6f, 0x20, 0x62, 0x6f, 0x6f,
0x74, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x00,
0x2e, 0x0d, 0x0d, 0x43, 0x6f, 0x75, 0x6c, 0x64,
0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x6f, 0x6f,
0x74, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x20, 0x00, 0xff, 0x43, 0x46,
0x46, 0x41, 0xff, 0x00, 0x00, 0x00, 0x17, 0x0a,
0xa9, 0x20, 0xa2, 0x00, 0xa9, 0x03, 0xa9, 0x3c,
0x10, 0x0a, 0x18, 0x90, 0x01, 0x38, 0x20, 0xa1,
0xc6, 0x4c, 0x28, 0xc8, 0x20, 0xa1, 0xc6, 0x86,
0x43, 0xac, 0x0b, 0xc8, 0xa9, 0xc5, 0x20, 0xa8,
0xfc, 0x88, 0xd0, 0xf8, 0xad, 0x00, 0xc0, 0x2d,
0x09, 0xc8, 0xcd, 0x0a, 0xc8, 0xd0, 0x07, 0xea,
0x20, 0xa1, 0xc6, 0x20, 0x2b, 0xc8, 0xa0, 0x01,
0x84, 0x42, 0x88, 0x84, 0x46, 0x84, 0x47, 0xa9,
0x08, 0x85, 0x45, 0x84, 0x44, 0x20, 0xa1, 0xc6,
0x20, 0x37, 0xc8, 0x85, 0xff, 0xb0, 0x10, 0xac,
0x00, 0x08, 0x88, 0xd0, 0x0a, 0xad, 0x01, 0x08,
0xf0, 0x05, 0xa6, 0x43, 0x4c, 0x01, 0x08, 0xa5,
0x00, 0xd0, 0x0a, 0xa5, 0x01, 0xcd, 0xf8, 0x07,
0xd0, 0x03, 0x4c, 0xba, 0xfa, 0x20, 0x2e, 0xc8,
0xa0, 0x00, 0xa6, 0xff, 0xd0, 0x02, 0xa0, 0x0e,
0x20, 0x95, 0xc6, 0x8a, 0xf0, 0x0c, 0xad, 0x7e,
0x07, 0x0a, 0x0a, 0xa9, 0xb0, 0x69, 0x00, 0x20,
0xed, 0xfd, 0xa0, 0x1c, 0x20, 0x95, 0xc6, 0x20,
0xa1, 0xc6, 0x4c, 0x34, 0xc8, 0xb9, 0xbc, 0xc6,
0xf0, 0x06, 0x20, 0x31, 0xc8, 0xc8, 0xd0, 0xf5,
0x60, 0x8d, 0xe2, 0xc0, 0xa9, 0xc6, 0x8d, 0xf8,
0x07, 0x2c, 0xff, 0xcf, 0xad, 0x20, 0xc8, 0x49,
0xcf, 0xd0, 0x05, 0xa0, 0x06, 0xa2, 0x60, 0x60,
0x20, 0x93, 0xfe, 0x00, 0x43, 0x68, 0x65, 0x63,
0x6b, 0x20, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65,
0x20, 0x00, 0x4e, 0x6f, 0x20, 0x62, 0x6f, 0x6f,
0x74, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x00,
0x2e, 0x0d, 0x0d, 0x43, 0x6f, 0x75, 0x6c, 0x64,
0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x6f, 0x6f,
0x74, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x20, 0x00, 0xff, 0x43, 0x46,
0x46, 0x41, 0xff, 0x00, 0x00, 0x00, 0x17, 0x0a,
0xa9, 0x20, 0xa2, 0x00, 0xa9, 0x03, 0xa9, 0x3c,
0x10, 0x0a, 0x18, 0x90, 0x01, 0x38, 0x20, 0xa1,
0xc7, 0x4c, 0x28, 0xc8, 0x20, 0xa1, 0xc7, 0x86,
0x43, 0xac, 0x0b, 0xc8, 0xa9, 0xc5, 0x20, 0xa8,
0xfc, 0x88, 0xd0, 0xf8, 0xad, 0x00, 0xc0, 0x2d,
0x09, 0xc8, 0xcd, 0x0a, 0xc8, 0xd0, 0x07, 0xea,
0x20, 0xa1, 0xc7, 0x20, 0x2b, 0xc8, 0xa0, 0x01,
0x84, 0x42, 0x88, 0x84, 0x46, 0x84, 0x47, 0xa9,
0x08, 0x85, 0x45, 0x84, 0x44, 0x20, 0xa1, 0xc7,
0x20, 0x37, 0xc8, 0x85, 0xff, 0xb0, 0x10, 0xac,
0x00, 0x08, 0x88, 0xd0, 0x0a, 0xad, 0x01, 0x08,
0xf0, 0x05, 0xa6, 0x43, 0x4c, 0x01, 0x08, 0xa5,
0x00, 0xd0, 0x0a, 0xa5, 0x01, 0xcd, 0xf8, 0x07,
0xd0, 0x03, 0x4c, 0xba, 0xfa, 0x20, 0x2e, 0xc8,
0xa0, 0x00, 0xa6, 0xff, 0xd0, 0x02, 0xa0, 0x0e,
0x20, 0x95, 0xc7, 0x8a, 0xf0, 0x0c, 0xad, 0x7f,
0x07, 0x0a, 0x0a, 0xa9, 0xb0, 0x69, 0x00, 0x20,
0xed, 0xfd, 0xa0, 0x1c, 0x20, 0x95, 0xc7, 0x20,
0xa1, 0xc7, 0x4c, 0x34, 0xc8, 0xb9, 0xbc, 0xc7,
0xf0, 0x06, 0x20, 0x31, 0xc8, 0xc8, 0xd0, 0xf5,
0x60, 0x8d, 0xf2, 0xc0, 0xa9, 0xc7, 0x8d, 0xf8,
0x07, 0x2c, 0xff, 0xcf, 0xad, 0x20, 0xc8, 0x49,
0xcf, 0xd0, 0x05, 0xa0, 0x07, 0xa2, 0x70, 0x60,
0x20, 0x93, 0xfe, 0x00, 0x43, 0x68, 0x65, 0x63,
0x6b, 0x20, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65,
0x20, 0x00, 0x4e, 0x6f, 0x20, 0x62, 0x6f, 0x6f,
0x74, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x00,
0x2e, 0x0d, 0x0d, 0x43, 0x6f, 0x75, 0x6c, 0x64,
0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x6f, 0x6f,
0x74, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x20, 0x00, 0xff, 0x43, 0x46,
0x46, 0x41, 0xff, 0x00, 0x00, 0x00, 0x17, 0x0a,
0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0xdf, 0xcd, 0x05, 0x1f, 0x64, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0xcf, 0xfa, 0x20, 0x11, 0xc0, 0x00, 0x00, 0x07,
0x4c, 0xb9, 0xca, 0x4c, 0x1e, 0xce, 0x4c, 0x59,
0xc8, 0x4c, 0x84, 0xcf, 0x4c, 0x3a, 0xc8, 0x4c,
0xbe, 0xca, 0xb9, 0xf8, 0x07, 0x18, 0x69, 0x01,
0x20, 0x6d, 0xcf, 0x20, 0x8e, 0xfd, 0x20, 0xdd,
0xfb, 0xa6, 0xff, 0xf0, 0x09, 0xa0, 0x15, 0x20,
0x61, 0xcf, 0x8a, 0x20, 0xda, 0xfd, 0x4c, 0x00,
0xe0, 0x20, 0x2f, 0xfb, 0x20, 0x84, 0xfe, 0x20,
0x58, 0xfc, 0x20, 0x93, 0xfe, 0x20, 0x89, 0xfe,
0x20, 0x81, 0xc8, 0xa0, 0x00, 0x20, 0x61, 0xcf,
0xa9, 0x22, 0x85, 0x24, 0xa0, 0x0e, 0x20, 0x61,
0xcf, 0xad, 0xf8, 0x07, 0x49, 0x70, 0x20, 0xed,
0xfd, 0xa0, 0x28, 0xa9, 0xad, 0x20, 0xed, 0xfd,
0x88, 0xd0, 0xf8, 0x60, 0xa0, 0x0c, 0xb9, 0x40,
0x00, 0x48, 0x88, 0x10, 0xf9, 0x86, 0x41, 0xba,
0xbd, 0x0e, 0x01, 0x85, 0x48, 0x18, 0x69, 0x03,
0x9d, 0x0e, 0x01, 0xbd, 0x0f, 0x01, 0x85, 0x49,
0x69, 0x00, 0x9d, 0x0f, 0x01, 0xad, 0xf8, 0x07,
0x29, 0x0f, 0x85, 0x40, 0xa8, 0xa6, 0x41, 0x20,
0xfb, 0xcb, 0xa0, 0x01, 0xb1, 0x48, 0x85, 0x42,
0xc8, 0xb1, 0x48, 0xaa, 0xc8, 0xb1, 0x48, 0x85,
0x49, 0x86, 0x48, 0xa9, 0x01, 0xa6, 0x42, 0xe0,
0x0a, 0xb0, 0x1c, 0xa0, 0x00, 0xb1, 0x48, 0xdd,
0x10, 0xc9, 0xd0, 0x27, 0xa0, 0x01, 0xb1, 0x48,
0xa4, 0x40, 0x99, 0xf8, 0x04, 0x8a, 0x0a, 0xaa,
0x20, 0x07, 0xc9, 0xb0, 0x02, 0xa9, 0x00, 0xaa,
0xa0, 0x00, 0x68, 0x99, 0x40, 0x00, 0xc8, 0xc0,
0x0d, 0x90, 0xf7, 0x8a, 0xa0, 0x02, 0xa2, 0x00,
0xc9, 0x01, 0x60, 0xa9, 0x04, 0xd0, 0xe8, 0xbd,
0x1b, 0xc9, 0x48, 0xbd, 0x1a, 0xc9, 0x48, 0x60,
0x03, 0x03, 0x03, 0x01, 0x03, 0x01, 0x01, 0x01,
0x04, 0x04, 0x2d, 0xc9, 0x34, 0xcb, 0x2c, 0xcb,
0xa7, 0xca, 0x2e, 0xca, 0x50, 0xca, 0x5a, 0xca,
0x5a, 0xca, 0xef, 0xc9, 0xef, 0xc9, 0x20, 0x5f,
0xca, 0xb0, 0x37, 0xa4, 0x40, 0xb9, 0xf8, 0x04,
0xd0, 0x38, 0xa5, 0x4c, 0xf0, 0x14, 0xc9, 0x49,
0xf0, 0x09, 0xc9, 0x48, 0xd0, 0x08, 0xa9, 0x40,
0x99, 0x78, 0x07, 0x4c, 0xdb, 0xc9, 0xa9, 0x21,
0x38, 0x60, 0xa4, 0x40, 0x18, 0xb9, 0x78, 0x06,
0x79, 0xf8, 0x06, 0xa0, 0x00, 0x91, 0x4a, 0xa0,
0x07, 0xb9, 0x6a, 0xc9, 0x91, 0x4a, 0x88, 0xd0,
0xf8, 0x18, 0x60, 0x40, 0x00, 0xcc, 0x00, 0x20,
0x00, 0x00, 0x38, 0xe9, 0x01, 0x20, 0xb3, 0xcd,
0xb0, 0x0e, 0xa6, 0x4c, 0xf0, 0x18, 0xca, 0xf0,
0x08, 0xca, 0xca, 0xf0, 0x11, 0xa9, 0x21, 0x38,
0x60, 0xa9, 0x01, 0xa0, 0x00, 0x91, 0x4a, 0xa8,
0xa9, 0x00, 0x91, 0x4a, 0x18, 0x60, 0xa9, 0xf8,
0xa0, 0x00, 0x91, 0x4a, 0xa6, 0x41, 0xa4, 0x40,
0x20, 0xe6, 0xca, 0xb0, 0xc5, 0x98, 0xa0, 0x02,
0x91, 0x4a, 0x88, 0x8a, 0x91, 0x4a, 0xa0, 0x03,
0xa9, 0x00, 0x91, 0x4a, 0xa5, 0x4c, 0xf0, 0x0c,
0xa0, 0x04, 0xb9, 0xc2, 0xc9, 0x91, 0x4a, 0xc8,
0xc0, 0x19, 0x90, 0xf6, 0x18, 0x60, 0x10, 0x43,
0x4f, 0x4d, 0x50, 0x41, 0x43, 0x54, 0x20, 0x46,
0x4c, 0x41, 0x53, 0x48, 0x20, 0x20, 0x20, 0x02,
0x20, 0x00, 0x20, 0xa4, 0x40, 0xa6, 0x41, 0x20,
0x99, 0xcd, 0xd0, 0x04, 0xa9, 0x28, 0x38, 0x60,
0x20, 0x2b, 0xcd, 0x20, 0x10, 0xcd, 0xd0, 0x04,
0xa9, 0x27, 0x38, 0x60, 0xa0, 0x01, 0x84, 0x42,
0x88, 0xbd, 0x88, 0xc0, 0x91, 0x4a, 0xc8, 0xbd,
0x80, 0xc0, 0x91, 0x4a, 0xc8, 0xd0, 0xf2, 0xe6,
0x4b, 0xc6, 0x42, 0x10, 0xec, 0xc6, 0x4b, 0xc6,
0x4b, 0xa0, 0x2e, 0xa2, 0x18, 0x20, 0x1c, 0xca,
0xa0, 0x14, 0xa2, 0x0a, 0xb1, 0x4a, 0x48, 0xc8,
0xb1, 0x4a, 0x88, 0x91, 0x4a, 0xc8, 0x68, 0x91,
0x4a, 0xc8, 0xca, 0xd0, 0xef, 0x18, 0x60, 0x20,
0x5f, 0xca, 0xb0, 0x18, 0x20, 0xa8, 0xca, 0xb0,
0x13, 0xa6, 0x4c, 0xf0, 0x0f, 0xca, 0xf0, 0x0c,
0xca, 0xf0, 0x06, 0xca, 0xf0, 0x07, 0xca, 0xf0,
0x03, 0xa9, 0x21, 0x38, 0x60, 0xa9, 0x1f, 0x38,
0x60, 0xb9, 0xf8, 0x04, 0x18, 0xf0, 0x03, 0xa9,
0x11, 0x38, 0x60, 0xa9, 0x01, 0x38, 0x60, 0xa0,
0x02, 0xb1, 0x48, 0x85, 0x4a, 0xc8, 0xb1, 0x48,
0x85, 0x4b, 0xc8, 0xb1, 0x48, 0x85, 0x4c, 0x18,
0x60, 0xa0, 0x02, 0xb1, 0x48, 0x85, 0x44, 0xc8,
0xb1, 0x48, 0x85, 0x45, 0xc8, 0xb1, 0x48, 0x85,
0x46, 0xc8, 0xb1, 0x48, 0x85, 0x47, 0xa4, 0x40,
0xb9, 0xf8, 0x04, 0x20, 0xa8, 0xca, 0xb0, 0x17,
0xa0, 0x06, 0xb1, 0x48, 0xd0, 0x0e, 0xa5, 0x47,
0x25, 0x46, 0xc9, 0xff, 0xf0, 0x06, 0xa4, 0x40,
0xa6, 0x41, 0x18, 0x60, 0xa9, 0x2d, 0x38, 0x60,
0xa4, 0x40, 0xb9, 0xf8, 0x04, 0x38, 0xf0, 0x06,
0x38, 0xe9, 0x01, 0x20, 0xb3, 0xcd, 0xa9, 0x11,
0x60, 0x90, 0x03, 0x4c, 0x8c, 0xc8, 0x20, 0xfb,
0xcb, 0x8a, 0x45, 0x43, 0x29, 0x70, 0xf0, 0x02,
0xa9, 0x02, 0x24, 0x43, 0x10, 0x03, 0x18, 0x69,
0x01, 0x20, 0xb3, 0xcd, 0xb0, 0x0e, 0xa5, 0x42,
0xf0, 0x0c, 0xc9, 0x01, 0xf0, 0x5c, 0xc9, 0x02,
0xf0, 0x50, 0xa9, 0x27, 0x38, 0x60, 0xb9, 0x78,
0x07, 0x0a, 0x30, 0x0d, 0xb9, 0xf8, 0x04, 0x18,
0x69, 0x01, 0xd9, 0x78, 0x06, 0xf0, 0x15, 0xd0,
0x0b, 0xb9, 0xf8, 0x04, 0x18, 0x69, 0x01, 0xd9,
0xf8, 0x06, 0xf0, 0x08, 0xa2, 0xff, 0xa0, 0xff,
0xa9, 0x00, 0x18, 0x60, 0xa5, 0x08, 0x48, 0xa5,
0x07, 0x48, 0xa5, 0x06, 0x48, 0x20, 0x3b, 0xcd,
0xa4, 0x07, 0xa6, 0x06, 0x68, 0x85, 0x06, 0x68,
0x85, 0x07, 0x68, 0x85, 0x08, 0xb0, 0x03, 0xa9,
0x00, 0x60, 0xa9, 0x27, 0x60, 0x20, 0x71, 0xca,
0xb0, 0xfa, 0x38, 0xb0, 0x06, 0x20, 0x71, 0xca,
0xb0, 0xf2, 0x18, 0xa5, 0x47, 0x48, 0xa5, 0x46,
0x48, 0xa5, 0x45, 0x48, 0xa5, 0xef, 0x48, 0x08,
0x20, 0xbd, 0xcb, 0x28, 0xa9, 0x20, 0x90, 0x02,
0xa9, 0x30, 0x08, 0x20, 0x12, 0xcd, 0xf0, 0x60,
0x28, 0xa0, 0x01, 0x84, 0xef, 0x88, 0xb0, 0x17,
0xbd, 0x88, 0xc0, 0x91, 0x44, 0xc8, 0xbd, 0x80,
0xc0, 0x91, 0x44, 0xc8, 0xd0, 0xf2, 0xe6, 0x45,
0xc6, 0xef, 0x10, 0xec, 0x4c, 0xa0, 0xcb, 0xa5,
0x44, 0x18, 0x69, 0x01, 0x85, 0x46, 0xa5, 0x45,
0x69, 0x00, 0x85, 0x47, 0xbd, 0x81, 0xc0, 0xb1,
0x46, 0x9d, 0x80, 0xc0, 0xb1, 0x44, 0x9d, 0x88,
0xc0, 0xc8, 0xc8, 0xd0, 0xf2, 0xe6, 0x45, 0xe6,
0x47, 0xc6, 0xef, 0x10, 0xea, 0xbd, 0x82, 0xc0,
0x20, 0x21, 0xcd, 0xf0, 0x14, 0xa9, 0x00, 0xc9,
0x01, 0xaa, 0x68, 0x85, 0xef, 0x68, 0x85, 0x45,
0x68, 0x85, 0x46, 0x68, 0x85, 0x47, 0x8a, 0x60,
0x28, 0xa9, 0x27, 0xd0, 0xea, 0x20, 0x2b, 0xcd,
0xa9, 0x01, 0x9d, 0x8a, 0xc0, 0xb9, 0x78, 0x07,
0x0a, 0x30, 0x1a, 0xa5, 0x46, 0x6d, 0x0f, 0xc8,
0x9d, 0x8b, 0xc0, 0xa5, 0x47, 0x6d, 0x10, 0xc8,
0x9d, 0x8c, 0xc0, 0xad, 0x11, 0xc8, 0x79, 0xf8,
0x04, 0x9d, 0x8d, 0xc0, 0x60, 0xa5, 0x46, 0x6d,
0x12, 0xc8, 0x9d, 0x8b, 0xc0, 0xa5, 0x47, 0x6d,
0x13, 0xc8, 0x9d, 0x8c, 0xc0, 0xad, 0x14, 0xc8,
0x4c, 0xde, 0xcb, 0xb9, 0x78, 0x04, 0xc9, 0xa5,
0xf0, 0x49, 0xad, 0x03, 0xc8, 0x38, 0xe9, 0x01,
0x99, 0xf8, 0x07, 0xad, 0x02, 0xc8, 0x99, 0x78,
0x05, 0xad, 0x00, 0xc8, 0x99, 0x78, 0x06, 0xad,
0x01, 0xc8, 0x99, 0xf8, 0x06, 0x19, 0x78, 0x06,
0xf0, 0x24, 0xbd, 0x82, 0xc0, 0xa9, 0x00, 0x9d,
0x80, 0xc0, 0xad, 0x0e, 0xc8, 0x30, 0x14, 0xa9,
0x06, 0x9d, 0x86, 0xc0, 0xa9, 0x04, 0x20, 0xa7,
0xcd, 0xa9, 0x02, 0x9d, 0x86, 0xc0, 0xa9, 0x7c,
0x20, 0xa7, 0xcd, 0x20, 0x51, 0xcc, 0xa9, 0xa5,
0x99, 0x78, 0x04, 0xa9, 0x00, 0x99, 0x78, 0x07,
0x60, 0xbd, 0x82, 0xc0, 0x20, 0x87, 0xcc, 0x90,
0x09, 0xa9, 0x00, 0x99, 0x78, 0x06, 0x99, 0xf8,
0x06, 0x60, 0xb9, 0x78, 0x06, 0xf0, 0x0d, 0xa9,
0xe0, 0x20, 0xa9, 0xcc, 0xd9, 0x78, 0x06, 0xb0,
0x03, 0x99, 0x78, 0x06, 0xb9, 0xf8, 0x06, 0xf0,
0x0d, 0xa9, 0xf0, 0x20, 0xa9, 0xcc, 0xd9, 0xf8,
0x06, 0xb0, 0x03, 0x99, 0xf8, 0x06, 0x60, 0x98,
0x48, 0xac, 0x0c, 0xc8, 0xbd, 0x8f, 0xc0, 0x10,
0x14, 0xa9, 0x0a, 0x48, 0xa9, 0xc5, 0x20, 0xa7,
0xcd, 0x68, 0x38, 0xe9, 0x01, 0xd0, 0xf4, 0x88,
0xd0, 0xea, 0x38, 0xb0, 0x01, 0x18, 0x68, 0xa8,
0x60, 0x9d, 0x8e, 0xc0, 0x99, 0xf8, 0x05, 0x98,
0x48, 0xac, 0x0d, 0xc8, 0xbd, 0x8f, 0xc0, 0x29,
0xd0, 0xc9, 0x50, 0xf0, 0x15, 0xa9, 0xc5, 0x20,
0xa7, 0xcd, 0x88, 0xd0, 0xef, 0x68, 0xa8, 0xb9,
0xf8, 0x05, 0x49, 0x10, 0x9d, 0x8e, 0xc0, 0xa9,
0x00, 0x60, 0x68, 0xa8, 0xbd, 0x8e, 0xc0, 0x29,
0x10, 0x0a, 0x0a, 0x99, 0x78, 0x07, 0xa5, 0x08,
0x48, 0xa5, 0x07, 0x48, 0xa5, 0x06, 0x48, 0x20,
0x3b, 0xcd, 0xa9, 0x00, 0xb0, 0x0a, 0xa5, 0x06,
0x05, 0x07, 0xc9, 0x01, 0xa5, 0x08, 0x69, 0x00,
0x99, 0xf8, 0x05, 0x68, 0x85, 0x06, 0x68, 0x85,
0x07, 0x68, 0x85, 0x08, 0xb9, 0xf8, 0x05, 0x60,
0xbd, 0x8f, 0xc0, 0x30, 0xfb, 0x29, 0x08, 0x60,
0xa9, 0xec, 0x48, 0xbd, 0x8f, 0xc0, 0x30, 0xfb,
0xa9, 0x00, 0x9d, 0x80, 0xc0, 0x68, 0x9d, 0x8f,
0xc0, 0xbd, 0x8f, 0xc0, 0x30, 0xfb, 0x29, 0x09,
0xc9, 0x01, 0x60, 0x20, 0x08, 0xcd, 0xb9, 0x78,
0x07, 0x4a, 0x4a, 0x29, 0x10, 0x09, 0xe0, 0x9d,
0x8e, 0xc0, 0x60, 0x20, 0x2b, 0xcd, 0x20, 0x10,
0xcd, 0xd0, 0x04, 0xa9, 0x27, 0x38, 0x60, 0x98,
0x48, 0xa0, 0x00, 0xbd, 0x88, 0xc0, 0xc8, 0xc0,
0x39, 0xd0, 0xf8, 0x68, 0xa8, 0xb9, 0x78, 0x07,
0x0a, 0x0a, 0x98, 0x48, 0xa0, 0x00, 0x90, 0x02,
0xa0, 0x03, 0x38, 0xbd, 0x88, 0xc0, 0xf9, 0x0f,
0xc8, 0x85, 0x06, 0xbd, 0x80, 0xc0, 0xf9, 0x10,
0xc8, 0x85, 0x07, 0xbd, 0x88, 0xc0, 0xf9, 0x11,
0xc8, 0x85, 0x08, 0xbd, 0x80, 0xc0, 0xe9, 0x00,
0xf0, 0x08, 0xa9, 0xff, 0x85, 0x06, 0x85, 0x07,
0x85, 0x08, 0x68, 0xa8, 0x20, 0x08, 0xcd, 0xf0,
0x06, 0xbd, 0x88, 0xc0, 0x4c, 0x8c, 0xcd, 0x18,
0x60, 0xb9, 0x78, 0x07, 0x0a, 0x30, 0x04, 0xb9,
0x78, 0x06, 0x60, 0xb9, 0xf8, 0x06, 0x60, 0x38,
0x48, 0xe9, 0x01, 0xd0, 0xfc, 0x68, 0xe9, 0x01,
0xd0, 0xf6, 0x60, 0x48, 0xb9, 0x78, 0x05, 0xd0,
0x22, 0x68, 0xf0, 0x09, 0xd9, 0xf8, 0x07, 0xd0,
0x07, 0xa9, 0x00, 0xf0, 0x03, 0xb9, 0xf8, 0x07,
0x99, 0xf8, 0x04, 0x38, 0xf9, 0x78, 0x06, 0x90,
0x32, 0x99, 0xf8, 0x04, 0xd9, 0xf8, 0x06, 0xb0,
0x22, 0x90, 0x23, 0x68, 0xf0, 0x09, 0xd9, 0xf8,
0x07, 0xd0, 0x07, 0xa9, 0x00, 0xf0, 0x03, 0xb9,
0xf8, 0x07, 0x99, 0xf8, 0x04, 0x38, 0xf9, 0xf8,
0x06, 0x90, 0x0b, 0x99, 0xf8, 0x04, 0xd9, 0x78,
0x06, 0x90, 0x08, 0xa9, 0x28, 0x60, 0xa9, 0x40,
0x99, 0x78, 0x07, 0xb9, 0xf8, 0x04, 0x60, 0x0a,
0x0b, 0x0d, 0x0e, 0x15, 0x03, 0x0e, 0x0a, 0x09,
0x08, 0x1c, 0x2b, 0x2f, 0x37, 0x40, 0x00, 0x00,
0x00, 0x01, 0x0e, 0x0e, 0x02, 0x0e, 0x86, 0x41,
0x84, 0x40, 0x84, 0x01, 0xa9, 0x00, 0x8d, 0xf2,
0x03, 0xa9, 0xe0, 0x8d, 0xf3, 0x03, 0x20, 0x6f,
0xfb, 0x20, 0x59, 0xc8, 0xa6, 0x41, 0xa4, 0x40,
0x20, 0x02, 0xcc, 0x8d, 0x10, 0xc0, 0xa4, 0x40,
0xb9, 0x78, 0x05, 0x85, 0x87, 0xb9, 0xf8, 0x07,
0x18, 0x69, 0x01, 0x85, 0x88, 0xa2, 0x01, 0xd0,
0x02, 0xa2, 0x03, 0xbd, 0x00, 0xc8, 0x95, 0x85,
0xca, 0x10, 0xf8, 0xa9, 0x03, 0x85, 0x82, 0xa9,
0x00, 0x85, 0x83, 0xa2, 0x04, 0xbd, 0x07, 0xce,
0x20, 0x5b, 0xfb, 0xbd, 0x0c, 0xce, 0x85, 0x24,
0xbd, 0x11, 0xce, 0xa8, 0x20, 0x61, 0xcf, 0xe0,
0x04, 0xf0, 0x22, 0xa0, 0x13, 0x20, 0x61, 0xcf,
0xb5, 0x85, 0xdd, 0x00, 0xc8, 0xf0, 0x0a, 0xe0,
0x02, 0xb0, 0x02, 0xe6, 0x83, 0xa0, 0x3f, 0x84,
0x32, 0x20, 0x6d, 0xcf, 0xa0, 0xff, 0x84, 0x32,
0xa9, 0xa0, 0x20, 0xed, 0xfd, 0xca, 0x10, 0xc5,
0xa4, 0x40, 0xa5, 0x87, 0x99, 0x78, 0x05, 0xa5,
0x88, 0x38, 0xe9, 0x01, 0x99, 0xf8, 0x07, 0xa6,
0x82, 0xbd, 0x07, 0xce, 0x20, 0x5b, 0xfb, 0xa9,
0x16, 0x85, 0x24, 0xd0, 0x03, 0x20, 0xdd, 0xfb,
0x20, 0x0c, 0xfd, 0xc9, 0x9b, 0xf0, 0x8a, 0xc9,
0x88, 0xd0, 0x12, 0xa9, 0xfe, 0x75, 0x85, 0xdd,
0x16, 0xce, 0x90, 0xe9, 0xdd, 0x1a, 0xce, 0xb0,
0xe4, 0x95, 0x85, 0x90, 0x82, 0xc9, 0x95, 0xd0,
0x04, 0xa9, 0x00, 0xf0, 0xe8, 0xc9, 0x8d, 0xf0,
0x04, 0xc9, 0x8a, 0xd0, 0x0f, 0x8a, 0x18, 0x69,
0x01, 0xc9, 0x04, 0x90, 0x02, 0xa9, 0x00, 0x85,
0x82, 0x4c, 0xaf, 0xce, 0xc9, 0x8b, 0xd0, 0x08,
0xc6, 0x82, 0x10, 0xf5, 0xa9, 0x03, 0xd0, 0xef,
0x29, 0xdf, 0xc9, 0xc2, 0xd0, 0x0f, 0xa5, 0x83,
0xd0, 0xab, 0x20, 0x58, 0xfc, 0xad, 0xf8, 0x07,
0x85, 0x84, 0x6c, 0x83, 0x00, 0xc9, 0xd3, 0xf0,
0x0a, 0xc9, 0xd1, 0xd0, 0xd4, 0x20, 0x58, 0xfc,
0x4c, 0x00, 0xe0, 0xa2, 0x0e, 0xbd, 0xf0, 0xcf,
0x95, 0x90, 0xca, 0x10, 0xf8, 0xa6, 0x41, 0x9d,
0x83, 0xc0, 0xa2, 0x03, 0xb5, 0x85, 0xdd, 0x00,
0xc8, 0xf0, 0x0e, 0xe0, 0x02, 0xb0, 0x05, 0xa4,
0x40, 0x99, 0x78, 0x04, 0x20, 0x90, 0x00, 0xf0,
0x0a, 0xca, 0x10, 0xe8, 0xa6, 0x41, 0x9d, 0x84,
0xc0, 0xd0, 0x03, 0x20, 0xdd, 0xfb, 0x4c, 0x5f,
0xce, 0xb9, 0x9a, 0xcf, 0x48, 0x20, 0x31, 0xc8,
0xc8, 0x68, 0x10, 0xf5, 0x60, 0xc9, 0x0a, 0x90,
0x11, 0xc9, 0x14, 0x90, 0x04, 0xa9, 0x3f, 0xd0,
0x0b, 0xe9, 0x09, 0x48, 0xa9, 0xb1, 0x20, 0xed,
0xfd, 0x68, 0x09, 0xb0, 0x09, 0x80, 0xc9, 0xe1,
0x90, 0x0d, 0x48, 0xad, 0xb3, 0xfb, 0x49, 0x06,
0xc9, 0x01, 0x68, 0x90, 0x02, 0x29, 0xdf, 0x4c,
0xed, 0xfd, 0x43, 0x46, 0x46, 0x41, 0x20, 0x36,
0x35, 0x30, 0x32, 0x20, 0x76, 0x32, 0x2e, 0xb0,
0x53, 0x6c, 0x6f, 0x74, 0xa0, 0x3a, 0xa0, 0x0d,
0x0d, 0x45, 0x72, 0x72, 0x20, 0xa4, 0x50, 0x61,
0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x20, 0x44, 0x65, 0x76, 0xb0, 0x44, 0x65, 0x76,
0xb1, 0x42, 0x6f, 0x6f, 0x74, 0x20, 0x44, 0x65,
0xf6, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69,
0x6f, 0xee, 0x42, 0x29, 0x4f, 0x4f, 0x54, 0x20,
0x53, 0x29, 0x41, 0x56, 0x45, 0x20, 0x51, 0x29,
0x55, 0x49, 0xd4, 0x77, 0x77, 0x77, 0x77, 0xff,
0x9d, 0x00, 0xc8, 0xa0, 0x00, 0x88, 0xf0, 0x05,
0xdd, 0x00, 0xc8, 0xd0, 0xf8, 0x98, 0x60, 0xff
];

View File

@ -13,13 +13,15 @@ import ApplesoftCompiler from '../applesoft/compiler';
import { debug, gup, hup } from '../util';
import Prefs from '../prefs';
var paused = false;
var focused = false;
var startTime = Date.now();
var lastCycles = 0;
var lastFrames = 0;
var lastRenderedFrames = 0;
var hashtag;
var hashtag = document.location.hash;
var disk_categories = {'Local Saves': []};
var disk_sets = {};
@ -32,6 +34,7 @@ var stats;
var vm;
var tape;
var _disk2;
var _cffa;
var audio;
var keyboard;
var io;
@ -81,6 +84,17 @@ export function openSave(drive, event) {
}
}
export function openAlert(msg) {
var el = document.querySelector('#alert-modal .message');
el.innerText = msg;
MicroModal.show('alert-modal');
}
/********************************************************************
*
* Drag and Drop
*/
export function handleDragOver(drive, event) {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
@ -130,8 +144,31 @@ export function handleDrop(drive, event) {
}
}
export function loadAjax(drive, url) {
function loadingStart () {
var meter = document.querySelector('#loading-modal .meter');
meter.style.display = 'none';
MicroModal.show('loading-modal');
}
function loadingProgress (current, total) {
if (total) {
var meter = document.querySelector('#loading-modal .meter');
var progress = document.querySelector('#loading-modal .progress');
meter.style.display = 'block';
progress.style.width = current / total * meter.clientWidth + 'px';
}
}
function loadingStop () {
MicroModal.close('loading-modal');
if (!paused) {
_apple2.run();
}
}
export function loadAjax(drive, url) {
loadingStart();
fetch(url).then(function(response) {
if (response.ok) {
@ -146,10 +183,10 @@ export function loadAjax(drive, url) {
loadDisk(drive, data);
}
initGamepad(data.gamepad);
MicroModal.close('loading-modal');
loadingStop();
}).catch(function(error) {
MicroModal.close('loading-modal');
window.alert(error.message);
loadingStop();
openAlert(error.message);
});
}
@ -211,32 +248,67 @@ function doLoadLocal(drive, file) {
} else if (TAPE_TYPES.indexOf(ext) > -1) {
tape.doLoadLocalTape(file);
} else {
window.alert('Unknown file type: ' + ext);
openAlert('Unknown file type: ' + ext);
}
}
function doLoadLocalDisk(drive, file) {
MicroModal.show('loading-modal');
loadingStart();
var fileReader = new FileReader();
fileReader.onload = function() {
var parts = file.name.split('.');
var ext = parts.pop().toLowerCase();
var name = parts.join('.');
if (_disk2.setBinary(drive, name, ext, this.result)) {
driveLights.label(drive, name);
MicroModal.close('loading-modal');
initGamepad();
if (this.result.byteLength >= 800 * 1024) {
if (_cffa.setBinary(drive, name, ext, this.result)) {
driveLights.label(drive, name);
initGamepad();
}
} else {
if (_disk2.setBinary(drive, name, ext, this.result)) {
driveLights.label(drive, name);
initGamepad();
}
}
loadingStop();
};
fileReader.readAsArrayBuffer(file);
}
export function doLoadHTTP(drive, _url) {
if (!_url) {
MicroModal.close('http-modal');
}
loadingStart();
var url = _url || document.querySelector('#http_url').value;
if (url) {
fetch(url).then(function(response) {
if (response.ok) {
return response.arrayBuffer();
var reader = response.body.getReader();
var received = 0;
var chunks = [];
var contentLength = parseInt(response.headers.get('content-length'), 10);
return reader.read().then(function readChunk(result) {
if (result.done) {
var data = new Uint8Array(received);
var offset = 0;
for (var idx = 0; idx < chunks.length; idx++) {
data.set(chunks[idx], offset);
offset += chunks[idx].length;
}
return data.buffer;
}
received += result.value.length;
if (contentLength) {
loadingProgress(received, contentLength);
}
chunks.push(result.value);
return reader.read().then(readChunk);
});
} else {
throw new Error('Error loading: ' + response.statusText);
}
@ -246,14 +318,21 @@ export function doLoadHTTP(drive, _url) {
var fileParts = file.split('.');
var ext = fileParts.pop().toLowerCase();
var name = decodeURIComponent(fileParts.join('.'));
if (_disk2.setBinary(drive, name, ext, data)) {
driveLights.label(drive, name);
initGamepad();
if (data.byteLength >= 800 * 1024) {
if (_cffa.setBinary(drive, name, ext, data)) {
driveLights.label(drive, name);
initGamepad();
}
} else {
if (_disk2.setBinary(drive, name, ext, data)) {
driveLights.label(drive, name);
initGamepad();
}
}
if (!_url) { MicroModal.close('http-modal'); }
loadingStop();
}).catch(function(error) {
window.alert(error.message);
if (!_url) { MicroModal.close('http-modal'); }
loadingStop();
openAlert(error.message);
});
}
}
@ -432,7 +511,7 @@ function saveLocalStorage(drive, name) {
window.localStorage.diskIndex = JSON.stringify(diskIndex);
window.alert('Saved');
openAlert('Saved');
driveLights.label(drive, name);
driveLights.dirty(drive, false);
@ -443,7 +522,7 @@ function deleteLocalStorage(name) {
var diskIndex = JSON.parse(window.localStorage.diskIndex || '{}');
if (diskIndex[name]) {
delete diskIndex[name];
window.alert('Deleted');
openAlert('Deleted');
}
window.localStorage.diskIndex = JSON.stringify(diskIndex);
updateLocalStorage();
@ -652,8 +731,6 @@ function _mousemove(evt) {
io.paddle(1, flipY ? 1 - y : y);
}
var paused = false;
export function pauseRun() {
var label = document.querySelector('#pause-run i');
if (paused) {
@ -682,7 +759,7 @@ export function openPrinterModal() {
MicroModal.show('printer-modal');
}
export function initUI(apple2, disk2, e) {
export function initUI(apple2, disk2, cffa, e) {
_apple2 = apple2;
cpu = _apple2.getCPU();
io = _apple2.getIO();
@ -690,6 +767,7 @@ export function initUI(apple2, disk2, e) {
vm = apple2.getVideoModes();
tape = new Tape(io);
_disk2 = disk2;
_cffa = cffa;
keyboard = new KeyBoard(cpu, io, e);
keyboard.create('#keyboard');
@ -747,6 +825,10 @@ export function initUI(apple2, disk2, e) {
});
}
if (navigator.standalone) {
document.body.classList.add('standalone');
}
cpu.reset();
setInterval(updateKHz, 1000);
updateSound();
@ -758,12 +840,9 @@ export function initUI(apple2, disk2, e) {
var hash = gup('disk') || hup();
if (hash) {
_apple2.stop();
processHash(hash);
} else {
_apple2.run();
}
if (navigator.standalone) {
document.body.classList.add('standalone');
}
_apple2.run();
}

View File

@ -51,9 +51,7 @@ export function bytify(ary) {
}
export function debug() {
if (typeof console != 'undefined' && 'log' in console) {
console.log.apply(console, arguments);
}
console.log.apply(console, arguments);
}
export function toHex(v, n) {
@ -112,3 +110,12 @@ export function keys(obj) {
export function each(obj, fn) {
keys(obj).forEach(fn);
}
export function numToString(num) {
let result = '';
for (let idx = 0; idx < 4; idx++) {
result += String.fromCharCode(num & 0xff);
num >>= 8;
}
return result;
}

80
package-lock.json generated
View File

@ -4115,6 +4115,46 @@
"flat-cache": "^2.0.1"
}
},
"file-loader": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-5.0.2.tgz",
"integrity": "sha512-QMiQ+WBkGLejKe81HU8SZ9PovsU/5uaLo0JdTCEXOYv7i7jfAjHZi1tcwp9tSASJPOmmHZtbdCervFmXMH/Dcg==",
"dev": true,
"requires": {
"loader-utils": "^1.2.3",
"schema-utils": "^2.5.0"
},
"dependencies": {
"ajv": {
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
"dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ajv-keywords": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
"integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==",
"dev": true
},
"schema-utils": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.5.0.tgz",
"integrity": "sha512-32ISrwW2scPXHUSusP8qMg5dLUawKkyV+/qIEV9JdXKx+rsM6mi8vZY8khg2M69Qom16rtroWXD3Ybtiws38gQ==",
"dev": true,
"requires": {
"ajv": "^6.10.2",
"ajv-keywords": "^3.4.1"
}
}
}
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@ -8940,6 +8980,46 @@
}
}
},
"raw-loader": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.0.tgz",
"integrity": "sha512-iINUOYvl1cGEmfoaLjnZXt4bKfT2LJnZZib5N/LLyAphC+Dd11vNP9CNVb38j+SAJpFI1uo8j9frmih53ASy7Q==",
"dev": true,
"requires": {
"loader-utils": "^1.2.3",
"schema-utils": "^2.5.0"
},
"dependencies": {
"ajv": {
"version": "6.10.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
"integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
"dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ajv-keywords": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz",
"integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==",
"dev": true
},
"schema-utils": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.5.0.tgz",
"integrity": "sha512-32ISrwW2scPXHUSusP8qMg5dLUawKkyV+/qIEV9JdXKx+rsM6mi8vZY8khg2M69Qom16rtroWXD3Ybtiws38gQ==",
"dev": true,
"requires": {
"ajv": "^6.10.2",
"ajv-keywords": "^3.4.1"
}
}
}
},
"react-is": {
"version": "16.8.6",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz",

View File

@ -29,8 +29,10 @@
"ajv": "^6.9.2",
"babel-jest": "^24.5.0",
"eslint": "^5.16.0",
"file-loader": "^5.0.2",
"handlebars": "^4.5.3",
"jest": "^24.8.0",
"raw-loader": "^4.0.0",
"webpack": "^4.29.6",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.2.1"

View File

@ -17,5 +17,25 @@ module.exports =
compress: true,
publicPath: '/dist/',
watchContentBase: true
}
},
module: {
rules: [
{
test: /\.2mg$/i,
use: [
{
loader: 'file-loader',
},
],
},
{
test: /\.rom$/i,
use: [
{
loader: 'raw-loader',
},
],
},
],
},
};