├── .gitignore ├── LICENSE ├── README.md ├── disks ├── DeluxePaintIII.adf ├── VogueMusicDisk.adf └── spudmine7.adf ├── img └── amiga.svg ├── index.html ├── script ├── adf.js ├── file.js ├── filehandlers │ ├── amos.js │ ├── detect.js │ ├── dms.js │ ├── icon.js │ ├── iff.js │ └── mod.js ├── lib │ ├── filesaver.js │ └── zlib.js └── main.js ├── style └── main.css └── tosec ├── amiga.png ├── index.html ├── script ├── main.js └── md5.js └── style └── main.css /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea/workspace.xml 8 | .idea/tasks.xml 9 | 10 | # Sensitive or high-churn files: 11 | .idea/dataSources/ 12 | .idea/dataSources.ids 13 | .idea/dataSources.xml 14 | .idea/dataSources.local.xml 15 | .idea/sqlDataSources.xml 16 | .idea/dynamic.xml 17 | .idea/uiDesigner.xml 18 | 19 | # Gradle: 20 | .idea/gradle.xml 21 | .idea/libraries 22 | 23 | # Mongo Explorer plugin: 24 | .idea/mongoSettings.xml 25 | 26 | ## File-based project format: 27 | *.iws 28 | 29 | ## Plugin-specific files: 30 | 31 | # IntelliJ 32 | /out/ 33 | 34 | # mpeltonen/sbt-idea plugin 35 | .idea_modules/ 36 | 37 | # JIRA plugin 38 | atlassian-ide-plugin.xml 39 | 40 | # Crashlytics plugin (for Android Studio and IntelliJ) 41 | com_crashlytics_export_strings.xml 42 | crashlytics.properties 43 | crashlytics-build.properties 44 | fabric.properties 45 | 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Steffest 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ADF reader/writer 2 | This is an implementation of the Amiga Filesystem in plain javascript. 3 | It can be used to read, extract and write files from/to Amiga Disk Format (*.adf and *.hdf) files. 4 | 5 | 6 | Both Original (OFS) and Fast File System (FFS) are supported in both ADF floppy disk images and HDF hard disk images. 7 | Hard disk images with multiple partitions are not supported yet. 8 | 9 | The main module is [adf.js](https://github.com/steffest/ADF-reader/blob/master/script/adf.js) 10 | It uses a binary file wrapper at [file.js](https://github.com/steffest/ADF-reader/blob/master/script/file.js) for easy parsing binary data. 11 | 12 | The rest of the package is a small demo, providing a simple user interface to 13 | - browse the disk 14 | - extract/view files 15 | - create folders and files 16 | Live demo at [http://www.stef.be/adfviewer/](http://www.stef.be/adfviewer/) 17 | 18 | It can disregard all block checksums and file attributes, which makes it quite useful to salvage files from corrupt disks. 19 | For further digging, you can also extract raw sectors for reconstructing deleted files etc. 20 | 21 | I mainly wrote it to quickly inspect .adf files for Amiga music tracker files or [Emerald Mine disks](http://www.emeraldmines.net/) without the need to fire up an Amiga Emulator. 22 | Basic writing support was added for interaction with the [Scripted Amiga Emulator](https://github.com/naTmeg/ScriptedAmigaEmulator) 23 | 24 | ### Main API: 25 | 26 | #### adf.loadDisk(source) 27 | > Loads a disk from an adf or hdf file. When source is a string, it's considered as a URI, otherwise you can pass an ArrayBuffer. 28 | > All future actions will be done on this disk. 29 | 30 | #### adf.getInfo() 31 | > Returns some basic info on the disk. 32 | 33 | #### adf.getFreeSize() 34 | > Returns the used and free space of the disk (in blocks and bytes). 35 | 36 | #### adf.readRootFolder() 37 | > Returns the files and directories of the root folder of the disk. 38 | > Each file and folder has a *sector* parameters which points to the start sector of the file or folder. 39 | 40 | #### adf.readFolderAtSector(sector) 41 | > Returns the files and directories of a specific folder that starts at *sector*. 42 | > The starting sector is usually obtained from listing the root folder. 43 | > Each file and folder has a *sector* parameters which points to the start sector of the file or folder. 44 | > Each file and folder also has a *parent* parameter indicating the parent folder so you can traverse back up. 45 | 46 | #### adf.readFileAtSector(sector,includeContent) 47 | > Returns the file info (and optional content) of the file starting at *sector* 48 | > The starting sector is usually obtained from listing a folder. 49 | > If *includeContent* is true then the *content* parameter contains the binary content of the file. 50 | 51 | #### adf.writeFile(name,buffer,folderSector) 52 | > Creates a new file into a specific folder. 53 | > It returns the sector of the new file on succes or *False* on failure. (e.g. because diskspace is insufficient) 54 | > Buffer is an ArrayBuffer with the binary content 55 | > Sequential datablocks are used as much as possible to speed up reading on an actual (or emulated) Amiga." 56 | 57 | #### adf.deleteFileAtSector(sector) 58 | > Deletes a file 59 | > Just as on the Amiga only the entry of the file in its folder is removed, all the header and datablocks are left intact, 60 | > so it's possible to reconstruct the file as long as no new data is written to the disk. 61 | 62 | #### adf.createFolder(name,folderSector) 63 | > Creates a folder. 64 | > it returns the sector of the new folder 65 | 66 | #### adf.deleteFolderAtSector(sector) 67 | > Deletes a folder 68 | > Please note that the folder must me empty so for recursive deletion you should first list the folder, 69 | > then delete all files and finally delete the folder 70 | 71 | #### adf.renameFileOrFolderAtSector(sector,newname) 72 | > Renames a file or a folder. 73 | > the maximum length of a name is 30 chars. 74 | > the characters / and : are not allowed 75 | 76 | 77 | ### additional API 78 | The following methods are available for low level disk reading 79 | 80 | #### readSector(sector) 81 | > Returns a raw sector from the disk. 82 | > A sector of a standard Amiga is 512 bytes 83 | 84 | #### getSectorType(sector) 85 | > Returns the type of the sector (headerBlock, dataBlock, extentionBlock, ...) 86 | 87 | #### readHeaderBlock(sector) 88 | > Returns a parsed headerBlock 89 | 90 | #### readDataBlock(sector) 91 | > Returns a parsed dataBlock 92 | 93 | #### readExtensionBlock(sector) 94 | > Returns a parsed extentionBlock 95 | 96 | #### readBitmapblock(sector) 97 | > Returns a parsed bitmapBlock 98 | 99 | #### readBitmapExtensionBlock(sector) 100 | > Returns a parsed extended bitmapBlock 101 | 102 | #### getDisk() 103 | > Returns the current disk structure. the "buffer" property contains the binary data of the disk 104 | 105 | ### Notes 106 | **Writing support is still a bit experimental**. 107 | Don't use it for important stuff, it certainly is **not** production ready. 108 | When writing, all dates are ignore for the time being, so "last changed" and "last accessed" dates will not be updated. 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /disks/DeluxePaintIII.adf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steffest/ADF-reader-writer/f60b0167bc5a99fbc71cef387f811135cf9aec58/disks/DeluxePaintIII.adf -------------------------------------------------------------------------------- /disks/VogueMusicDisk.adf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steffest/ADF-reader-writer/f60b0167bc5a99fbc71cef387f811135cf9aec58/disks/VogueMusicDisk.adf -------------------------------------------------------------------------------- /disks/spudmine7.adf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steffest/ADF-reader-writer/f60b0167bc5a99fbc71cef387f811135cf9aec58/disks/spudmine7.adf -------------------------------------------------------------------------------- /img/amiga.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 320 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |A)for(;0<
15 | A--;)K[I++]=0,Q[0]++;else for(;0A?A:138,G>A-3&&G=G?(K[I++]=17,K[I++]=G-3,Q[17]++):(K[I++]=18,K[I++]=G-11,Q[18]++),A-=G;else if(K[I++]=L[y],Q[L[y]]++,A--,3>A)for(;0' + f.name + '
';
191 | content += "
";
200 |
201 |
202 | if (f.typeString == "FILE"){
203 | currentFile=f;
204 | content += " ";
192 |
193 | if (f.size) content += "Type: " + f.typeString + " ";
194 | if (f.comment) content += "Size: " + formatSize(f.size) + " ";
195 | if (f.lastChangeDays && f.lastChangeDays>1000){
196 | content += "Comment: " + f.comment + " ";
197 | }
198 |
199 | content += "LastChanged: " + formatDateTime(f.lastChangeDays,f.lastChangeMinutes,f.lastChangeTicks) + " Actions
";
205 | content += 'Folder
";
214 | content += '
" + fileType.info;
233 | var intro = "This is a";
234 | if (["a","e","i","o","u"].indexOf(info.substr(0,1).toLowerCase())>=0) intro+="n";
235 |
236 | container.innerHTML = intro + " " + info;
237 | if (fileType.actions){
238 | fileType.actions.forEach(function(action){
239 | var div = document.createElement("div");
240 | div.innerHTML = action;
241 | div.className = "action";
242 | div.onclick = function(){fileType.handler.handle(fileType.file,action)};
243 | container.appendChild(div);
244 | });
245 | }
246 | }
247 | }
248 | }
249 |
250 | function updateFreeSize(){
251 | let sizes = adf.getFreeSize();
252 | let free = formatDiskSize(sizes.free);
253 | let used = formatDiskSize(sizes.used);
254 | el("diskspace").innerHTML = "Used: " + used + ", Free: " + free;
255 | }
256 |
257 | function el(id){
258 | return document.getElementById(id);
259 | }
260 |
261 | function formatSize(size){
262 | let n = "bytes";
263 | if (size >= 1024){
264 | size = Math.round(size / 1024);
265 | n = "kb";
266 | if (size >= 1024){
267 | size = Math.round(size / 1024);
268 | n = "mb";
269 | if (size >= 1024){
270 | size = Math.round(size / 1024);
271 | n = "gb";
272 | }
273 | }
274 | }
275 | if (size === 0) size = 1;
276 | return size + " " + n;
277 | }
278 |
279 | function formatDiskSize(size){
280 | let s = formatSize(size).split(" ");
281 | return "" + s[0] + " " + s[1];
282 | }
283 |
284 | function formatDateTime(days,minutes,ticks){
285 | var start = 252457200000; // 1 jan 1978;
286 | var monthNames = ["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"];
287 |
288 | // ticks = 50 ticks a second
289 | var d = new Date(start + (days * 86400000) + (minutes * 3600000) + (ticks * 50000));
290 | var m = d.getMinutes();
291 | var h = d.getHours();
292 | if (m<10) m = "0" + m;
293 | if (h<10) m = "0" + h;
294 | return d.getDate() + " " + monthNames[d.getMonth()] + " " + d.getFullYear() + " " + h + ":" + m;
295 | }
296 |
297 | me.showAscii = function(sector){
298 | showFile(sector,true);
299 | };
300 |
301 | me.showHex = function(sector){
302 | showFile(sector,false);
303 | };
304 |
305 | me.showImage = function(image){
306 | showImage(image);
307 | };
308 |
309 | me.detectFileType = function(sector){
310 | var file = adf.readFileAtSector(sector,true);
311 | return FileType.detect(file.content);
312 | };
313 |
314 | function showFile(sector,asAscii){
315 | currentSector = sector;
316 | var file = adf.readFileAtSector(sector,true);
317 |
318 | el("filelabel").innerHTML = file.name;
319 |
320 | el("file").style.display = "block";
321 | el("folder").style.display = "none";
322 | el("canvas").className = "hidden";
323 |
324 | var hex = el("hex");
325 | var ascii = el("ascii");
326 | hex.className = asAscii ? "ascii" : "hex";
327 | ascii.className = asAscii ? "ascii" : "hex";
328 |
329 | var s = "";
330 | var a = "";
331 |
332 | for (var i = 1; i<= file.size; i++){
333 | var eol = "";
334 | if (i%16 == 0) eol = "\n";
335 | var b = file.content[i-1];
336 | s += formatHex(b) + " " + eol;
337 | a += String.fromCharCode(b);
338 | if (!asAscii) (a += " " + eol);
339 | }
340 |
341 | hex.value = s;
342 | ascii.value = a;
343 | }
344 |
345 | function showImage(image){
346 | el("filelabel").innerHTML = currentFile.name;
347 |
348 | el("file").style.display = "block";
349 | el("folder").style.display = "none";
350 | el("hex").className = "hidden";
351 | el("ascii").className = "hidden";
352 |
353 | if (image){
354 | var canvas = el("canvas");
355 | var ctx = canvas.getContext("2d");
356 | ctx.fillStyle = "black";
357 | ctx.fillRect(0,0,canvas.width,canvas.height);
358 | el("canvas").className = "";
359 |
360 | var w = canvas.width;
361 | var h = w * (image.height/image.width);
362 |
363 | if (h>canvas.height){
364 | h = canvas.height;
365 | w = h * (image.width/image.height);
366 | }
367 | var x = (canvas.width - w)>>1;
368 | var y = (canvas.height - h)>>1;
369 |
370 | ctx.imageSmoothingEnabled= false;
371 | ctx.drawImage(image,x,y,w,h);
372 | }
373 | }
374 |
375 |
376 | me.showFolder = function(){
377 | el("file").style.display = "none";
378 | el("folder").style.display = "block";
379 | el("raw").style.display = "none";
380 | };
381 |
382 | me.showRoot = function(){
383 | listFolder(adf.readRootFolder());
384 |
385 | var disk = adf.getDisk();
386 | var container = el("fileinfo");
387 | container.innerHTML += "" ;
388 |
389 | el("filemanager").classList.add("disk");
390 |
391 | var diskspace = el("diskspace");
392 |
393 | let free = formatDiskSize(disk.free);
394 | let used = formatDiskSize(disk.used);
395 | diskspace.innerHTML = "Used: " + used + ", Free: " + free;
396 | };
397 |
398 | me.download = function(sector){
399 | sector = sector || currentSector;
400 | var file = adf.readFileAtSector(sector,true);
401 |
402 |
403 | var b = new Blob([file.content], {type: "application/octet-stream"});
404 |
405 | var fileName = file.name;
406 | saveAs(b,fileName);
407 | };
408 |
409 | me.delete = function(sector){
410 | sector = sector || currentSector;
411 | adf.deleteFileAtSector(sector,true);
412 | updateFreeSize();
413 | refreshFolder();
414 | };
415 |
416 | me.rename = function(sector,type){
417 |
418 | if (!sector && type === TYPE_FOLDER) sector = currentFolder.sector;
419 | sector = sector || currentSector;
420 |
421 | var name = adf.readHeaderBlock(sector).name;
422 |
423 | me.showDialog({
424 | title:"Rename " + (type === TYPE_FILE ? "file":"folder"),
425 | message:"Please enter the new name",
426 | inputValue: name,
427 | callback:function(confirm){
428 | if (confirm){
429 | var value = document.getElementById("dialoginput").value;
430 | if (value){
431 | adf.renameFileOrFolderAtSector(sector,value);
432 | refreshFolder();
433 | }
434 | }
435 | }
436 | });
437 | };
438 |
439 | me.upload = function(){
440 | var input = document.createElement('input');
441 | input.type = 'file';
442 | input.onchange = function(e){
443 | console.log("file uploaded");
444 | var files = e.target.files;
445 | if (files.length){
446 | var file = files[0];
447 |
448 | var reader = new FileReader();
449 | reader.onload = function(){
450 | let sector = adf.writeFile(file.name,reader.result,currentFolder.sector);
451 | if (!sector){
452 | alert("Sorry, not enough space on this disk.");
453 | return;
454 | }
455 | console.log("File written to sector",sector);
456 | updateFreeSize();
457 | refreshFolder();
458 | };
459 | reader.readAsArrayBuffer(file);
460 | }
461 | };
462 | input.click();
463 | };
464 |
465 | me.createFolder = function(){
466 |
467 | me.showDialog({
468 | title:"Create New Folder" ,
469 | message:"Please enter the name of the new folder",
470 | inputValue: "new folder",
471 | callback:function(confirm){
472 | if (confirm){
473 | var value = document.getElementById("dialoginput").value;
474 | if (value){
475 | adf.createFolder(value,currentFolder.sector);
476 | updateFreeSize();
477 | refreshFolder();
478 | }
479 | }
480 | }
481 | });
482 | };
483 |
484 | me.deleteFolder = function(){
485 | var deleted = adf.deleteFolderAtSector(currentFolder.sector);
486 | if (deleted){
487 | var up = document.getElementById("list").querySelectorAll(".DIR")[0];
488 | if (up) up.click();
489 | updateFreeSize();
490 | }else{
491 | alert("can't delete folder, it's not empty");
492 | }
493 | };
494 |
495 | me.showSector = function(sector){
496 | currentSector = sector || 0;
497 | let disk = adf.getDisk();
498 |
499 | if (isNaN(currentSector)) currentSector = 0;
500 | if (currentSector<0) currentSector=0;
501 | if (currentSector>=disk.sectorCount) currentSector = disk.sectorCount-1;
502 |
503 | el("sector").value = currentSector;
504 | el("sectorinfo").innerHTML = adf.getSectorType(sector);
505 |
506 | el("file").style.display = "none";
507 | el("folder").style.display = "none";
508 | el("raw").style.display = "block";
509 |
510 | var content = adf.readSector(currentSector);
511 |
512 | var hex = el("sectorhex");
513 | var ascii = el("sectorascii");
514 |
515 | var s = "";
516 | var a = "";
517 |
518 | for (var i = 1; i<= content.length; i++){
519 | var eol = "";
520 | if (i%16 == 0) eol = "\n";
521 | var b = content[i-1];
522 | s += formatHex(b) + " " + eol;
523 | a += String.fromCharCode(b) + " " + eol;
524 | }
525 |
526 | hex.value = s;
527 | ascii.value = a;
528 |
529 | };
530 |
531 | me.nextSector = function(){
532 | me.showSector(++currentSector);
533 | };
534 | me.prevSector = function(){
535 | me.showSector(--currentSector);
536 | };
537 | me.onSectorUpdate = function(){
538 | var value = el("sector").value;
539 | if (isNaN(value)) value = 0;
540 | if (value != currentSector) me.showSector(value);
541 | };
542 |
543 | me.handleDragEnter = function(e){
544 | e.stopPropagation();
545 | e.preventDefault();
546 | el("dropzone").className = "over";
547 | };
548 |
549 | me.handleDragOver = function(e){
550 | e.stopPropagation();
551 | e.preventDefault();
552 | };
553 |
554 | me.handleDragLeave = function(e){
555 | e.stopPropagation();
556 | e.preventDefault();
557 | el("dropzone").className = "";
558 | }
559 |
560 | me.handleDrop = function(e){
561 | e.stopPropagation();
562 | e.preventDefault();
563 | el("dropzone").className = "";
564 |
565 | var dt = e.dataTransfer;
566 | var files = dt.files;
567 |
568 | if (files.length){
569 | var file = files[0];
570 |
571 | var reader = new FileReader();
572 | reader.onload = function(){
573 | me.load(reader.result);
574 | };
575 | reader.onerror = function(){
576 | console.error("Failed to read file!",reader.error);
577 | el("feedback").innerHTML = "Sorry, something went wrong reading this file.";
578 | el("feedback").style.display = "block";
579 | reader.abort();
580 | };
581 | reader.readAsArrayBuffer(file);
582 | }
583 | };
584 |
585 | me.dialog = function(confirm){
586 | if (dialogFunction) dialogFunction(confirm);
587 | dialogFunction = false;
588 |
589 | var dialog=document.getElementById("modaldialog");
590 | dialog.className = "";
591 | };
592 |
593 | me.showDialog = function(config){
594 | var dialog=document.getElementById("modaldialog");
595 | document.getElementById("dialogtitle").innerHTML = config.title || "Please confirm";
596 | document.getElementById("dialogcontent").innerHTML = config.message || "";
597 | document.getElementById("dialoginput").value = config.inputValue || "";
598 | dialogFunction = config.callback;
599 | dialog.className = "active";
600 | };
601 |
602 | function formatHex(nr){
603 | var result = nr.toString(16).toUpperCase();
604 | if (result.length<2) result = "0" + result;
605 | return result;
606 | }
607 |
608 | return me;
609 | }();
610 |
611 |
612 |
613 | function getUrlParameter(param){
614 | if (window.location.getParameter){
615 | return window.location.getParameter(param);
616 | } else if (location.search) {
617 | var parts = location.search.substring(1).split('&');
618 | for (var i = 0; i < parts.length; i++) {
619 | var nv = parts[i].split('=');
620 | if (!nv[0]) continue;
621 | if (nv[0] == param) {
622 | return nv[1] || true;
623 | }
624 | }
625 | }
626 | }
627 |
628 | function loadScript(url,next){
629 | var s = document.createElement('script');
630 | s.type = 'application/javascript';
631 | s.src = url;
632 | s.addEventListener('error', function(){
633 | console.error("Failed loading script " + url);
634 | }, false);
635 | s.addEventListener('load', function(){
636 | if (next) next();
637 | }, false);
638 | document.getElementsByTagName('head')[0].appendChild(s);
639 | }
640 |
--------------------------------------------------------------------------------
/style/main.css:
--------------------------------------------------------------------------------
1 | html,body{
2 | margin: 0;
3 | padding: 0;
4 | font-family: Helvetica, Arial, sans-serif;
5 | font-size: 14px;
6 | box-sizing: border-box;
7 | }
8 |
9 | *, *:before, *:after {
10 | box-sizing: inherit;
11 | }
12 |
13 | .clear{
14 | clear: both;
15 | }
16 |
17 | .hidden{
18 | display: none;
19 | }
20 |
21 | #container{
22 | width: 800px;
23 | margin: auto;
24 | }
25 |
26 | textarea{
27 | font-family: "Courier New",monospace;
28 | font-size: 13px;
29 | }
30 |
31 | #dropzone{
32 | border: 1px dashed #427fb7;
33 | margin: 10px 0;
34 | padding: 10px;
35 | }
36 |
37 | #dropzone.over{
38 | border: 1px dashed #fb9b00;
39 | box-shadow: 0 0 10px rgba(251, 155, 0, 0.5);
40 | }
41 |
42 | #dropzone.over .selector{
43 | pointer-events: none;
44 | }
45 |
46 | #dropzone .selector{
47 | float: right;
48 | }
49 |
50 | #filemanager{
51 | position: relative;
52 | }
53 |
54 | #filemanager .panel{
55 | border: 1px solid silver;
56 | }
57 |
58 | .caption{
59 | background-color: #427fb7;
60 | color: white;
61 | padding: 4px;
62 | }
63 |
64 | .caption.amiga{
65 | background-image: url("../img/amiga.svg");
66 | background-size: 20px 16px;
67 | background-position: 2px 4px;
68 | background-repeat: no-repeat;
69 |
70 | padding: 4px 4px 4px 24px;
71 | }
72 |
73 | #filemanager.disk .caption .diskmenu{
74 | display: initial;
75 | }
76 |
77 | .caption .diskmenu{
78 | float: right;
79 | color: rgba(255, 255, 255, 0.7);
80 | padding-right: 4px;
81 | display: none;
82 | }
83 |
84 | .caption .diskmenu b{
85 | color: white;
86 | font-weight: normal;
87 | }
88 |
89 | .caption .diskmenu .fa-download{
90 | padding-left: 10px;
91 | }
92 |
93 | .caption .diskmenu .fa-download:hover{
94 | color: white;
95 | cursor: pointer;
96 | }
97 |
98 | .diskheader{
99 | position: relative;
100 | border-bottom: 1px solid silver;
101 | min-height: 24px;
102 | }
103 |
104 | .diskheader .button{
105 | display: inline-block;
106 | position: absolute;
107 | padding: 4px;
108 | background-color: #c0d2dc;
109 | }
110 |
111 | .diskheader .button:hover{
112 | cursor: pointer;
113 | background-color: #d5e8f2;
114 | }
115 |
116 | .diskheader .itemlabel{
117 | padding: 4px 4px 4px 44px;
118 | min-height: 24px;
119 | }
120 |
121 | .diskheader .button.right{
122 | right: 0;
123 | }
124 |
125 | .diskheader .inline,
126 | .diskheader .button.inline{
127 | display: inline-block;
128 | padding: 2px 4px;
129 | position: relative;
130 | }
131 |
132 | .diskheader input{
133 | width: 50px;
134 | text-align: center;
135 | border: 0;
136 | display: inline-block;
137 | }
138 |
139 | #list,
140 | #hex,
141 | #sectorhex{
142 | height: 400px;
143 | width: 500px;
144 | overflow: scroll;
145 | position: absolute;
146 | }
147 |
148 |
149 | #fileinfo,
150 | #ascii,
151 | #sectorascii{
152 | width: 300px;
153 | height: 400px;
154 | float: right;
155 | background-color: #f8f8f8;
156 | border-left: 1px solid silver;
157 | padding: 4px;
158 | font-size: 12px;
159 | }
160 |
161 | #hex.ascii{
162 | display: none;
163 | }
164 | #ascii.ascii{
165 | width: 800px;
166 | }
167 |
168 | #fileinfo h3{
169 | margin: 0;
170 | padding: 0;
171 | font-size: 14px;
172 | }
173 |
174 | #fileinfo h4{
175 | margin: 10px 0 4px 0;
176 | padding: 0 0 2px 0;
177 | font-size: 12px;
178 | border-bottom: 1px solid silver;
179 | }
180 |
181 | #fileinfo .info{
182 | padding: 4px 4px 4px 20px;
183 | }
184 |
185 | #fileinfo td{
186 | font-size: 12px;
187 | border-bottom: 1px solid silver;
188 | }
189 |
190 | #fileinfo .action{
191 | padding: 2px 0;
192 | color: #427fb7;
193 | }
194 |
195 | #fileinfo .action:hover{
196 | cursor: pointer;
197 | color: #079bd7;
198 | text-decoration: underline;
199 | }
200 |
201 | .listitem{
202 | padding: 4px 8px;
203 | border-bottom: 1px solid silver;
204 | }
205 |
206 | .listitem i{
207 | display: inline-block;
208 | width: 20px;
209 | color: #427fb7;
210 | }
211 |
212 | .listitem.DIR i{
213 | color: #dcc200;
214 | }
215 |
216 | .listitem .label{
217 | display: inline-block;
218 | width: 200px;
219 | }
220 |
221 | .listitem.DIR{
222 | background-color: #F5f5f5;
223 | }
224 |
225 | .listitem:hover{
226 | cursor: pointer;
227 | border-bottom: 1px solid #485f74;
228 | background-color: #deebf5;
229 | }
230 |
231 | #feedback{
232 | border: 1px dashed #ff7405;
233 | margin: 10px 0;
234 | padding: 10px;
235 | }
236 |
237 | .footer{
238 | margin-top: 20px;
239 | text-align: center;
240 | font-size: 12px;
241 | color: #427fb7;
242 | }
243 |
244 | .footer a{
245 | color: #427fb7;
246 | text-decoration: underline;
247 | }
248 |
249 | #feedback,
250 | #folder,
251 | #file,
252 | #raw{
253 | display: none;
254 | }
255 |
256 | #filetypeactions{
257 | padding: 4px 0;
258 | margin: 4px 0;
259 | border-top: 1px solid rgba(0, 0, 0, 0.15);
260 | }
261 |
262 | #filetypeactions .action{
263 | text-transform: capitalize;
264 | }
265 |
266 |
267 | #filemanager #modaldialog{
268 | position: absolute;
269 | left: 0;
270 | top: 0;
271 | bottom: 0;
272 | right: 0;
273 | background-color: rgba(0, 0, 0, 0.2);
274 | display: none;
275 | z-index: 1000;
276 | }
277 |
278 | #filemanager #modaldialog.active{
279 | display: block;
280 | }
281 |
282 | #filemanager #modaldialog .dialog{
283 | margin: 20px auto;
284 | border: 1px solid silver;
285 | box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.2);
286 | width: 300px;
287 | max-width: 100%;
288 | }
289 |
290 | #filemanager #modaldialog .dialog #dialogtitle{
291 | background-color: #e8e8e8;
292 | padding: 4px;
293 | font-size: 14px;
294 | margin: 0;
295 | }
296 |
297 | #filemanager #modaldialog .dialog .content{
298 | background-color: #fafafa;
299 | padding: 20px;
300 | min-height: 100px;
301 | }
302 |
303 | #filemanager #modaldialog .dialog .inputbox{
304 | width: 100%;
305 | padding: 8px;
306 | border: 1px solid silver;
307 | text-align: center;
308 | margin-top: 10px;
309 | }
310 |
311 | #filemanager #modaldialog .dialog .buttons{
312 | text-align: center;
313 | padding: 8px;
314 | background-color: #f6f6f6;
315 | }
316 |
317 | #filemanager #modaldialog .dialog .buttons .btn{
318 | border: 1px solid silver;
319 | text-align: center;
320 | padding: 10px 20px;
321 | background-color: #eeeeee;
322 | margin: 2px 12px;
323 | display: inline-block;
324 | min-width: 100px;
325 | }
326 |
327 | #filemanager #modaldialog .dialog .buttons .btn:hover{
328 | background-color: white;
329 | cursor: pointer;
330 | }
331 |
332 |
333 |
--------------------------------------------------------------------------------
/tosec/amiga.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/steffest/ADF-reader-writer/f60b0167bc5a99fbc71cef387f811135cf9aec58/tosec/amiga.png
--------------------------------------------------------------------------------
/tosec/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
';
26 | el("feedback").style.display = "block";
27 | }
28 | }else{
29 | el("feedback").innerHTML = 'This does not seem to be a standard Amiga DD disk file
';
30 | el("feedback").style.display = "block";
31 | }
32 | });
33 | };
34 |
35 | function listFolder(folder){
36 |
37 | var container = el("list");
38 | container.innerHTML = "";
39 |
40 | var path = adf.getInfo().label;
41 | if (folder.parent){
42 | container.appendChild(createListItem({
43 | sector: folder.parent,
44 | name: "..",
45 | type: "DIR"
46 | }));
47 | path += "/" + folder.name;
48 | }
49 |
50 | el("disklabel").innerHTML = path;
51 |
52 |
53 | function sortByName(a,b) {
54 | if (a.name < b.name)
55 | return -1;
56 | if (a.name > b.name)
57 | return 1;
58 | return 0;
59 | }
60 | folder.folders.sort(sortByName);
61 | folder.files.sort(sortByName);
62 |
63 |
64 | folder.folders.forEach(function(f){
65 | container.appendChild(createListItem(f));
66 | });
67 |
68 | folder.files.forEach(function(f){
69 | container.appendChild(createListItem(f));
70 | });
71 |
72 | showInfo(folder);
73 | }
74 |
75 | function createListItem(f){
76 | var item = document.createElement("div");
77 | item.className = "listitem " + f.type;
78 |
79 | var icon = document.createElement("i");
80 | icon.className = "fa fa-folder";
81 |
82 | var label = document.createElement("span");
83 | label.className = "label";
84 | label.innerHTML = f.name;
85 |
86 | var size;
87 | if (f.type == "FILE"){
88 | icon.className = "fa fa-file-o";
89 |
90 | size = document.createElement("span");
91 | size.className = "size";
92 | size.innerHTML = formatSize(f.size);
93 | }
94 |
95 | item.onclick = function(){
96 | if (f.type == "FILE"){
97 | showInfo(f);
98 | }else{
99 | var dir = adf.readFolderAtSector(f.sector);
100 | listFolder(dir);
101 | }
102 |
103 | };
104 |
105 | item.appendChild(icon);
106 | item.appendChild(label);
107 | if (size) item.appendChild(size);
108 |
109 | return item;
110 | }
111 |
112 | function showInfo(f){
113 | var container = el("fileinfo");
114 |
115 | var content = "";
116 | content += '' + f.name + '
';
117 | content += "
";
126 |
127 |
128 | if (f.type == "FILE"){
129 | content += " ";
118 |
119 | if (f.size) content += "Type: " + f.type + " ";
120 | if (f.comment) content += "Size: " + formatSize(f.size) + " ";
121 | if (f.lastChangeDays && f.lastChangeDays>1000){
122 | content += "Comment: " + f.comment + " ";
123 | }
124 |
125 | content += "LastChanged: " + formatDateTime(f.lastChangeDays,f.lastChangeMinutes,f.lastChangeTicks) + " Actions
";
130 | content += '