4881 lines
184 KiB
C
Executable file
4881 lines
184 KiB
C
Executable file
/*******************************************************************************
|
|
* Copyright (c) 2008-2022 JackAsser, Krill, Claus, Björn Esser
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*******************************************************************************/
|
|
|
|
#define VERSION "4.0"
|
|
|
|
#define _CRT_SECURE_NO_WARNINGS /* avoid security warnings for MSVC */
|
|
|
|
#include <ctype.h>
|
|
#include <locale.h>
|
|
#include <math.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <wchar.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#define min(a, b) (((a) < (b)) ? (a) : (b))
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#define FILESEPARATOR '\\'
|
|
#else
|
|
#define FILESEPARATOR '/'
|
|
#endif
|
|
|
|
#define DIRENTRIESPERBLOCK 8
|
|
#define DIRTRACK_D41_D71 18
|
|
#define DIRTRACK_D81 40
|
|
#define SECTORSPERTRACK_D81 40
|
|
#define MAXNUMFILES_D81 ((SECTORSPERTRACK_D81 - 3) * DIRENTRIESPERBLOCK)
|
|
#define DIRENTRYSIZE 32
|
|
#define BLOCKSIZE 256
|
|
#define BLOCKOVERHEAD 2
|
|
#define TRACKLINKOFFSET 0
|
|
#define SECTORLINKOFFSET 1
|
|
#define FILETYPEOFFSET 2
|
|
#define FILETYPEDEL 0
|
|
#define FILETYPESEQ 1
|
|
#define FILETYPEPRG 2
|
|
#define FILETYPEUSR 3
|
|
#define FILETYPEREL 4
|
|
#define FILETYPETRANSWARPMASK 0x100
|
|
#define FILETRACKOFFSET 3
|
|
#define FILESECTOROFFSET 4
|
|
#define FILENAMEOFFSET 5
|
|
#define FILENAMEMAXSIZE 16
|
|
#define FILENAMEEMPTYCHAR (' ' | 0x80)
|
|
#define BAMMESSAGEOFFSET 0xab
|
|
#define BAMMESSAGEMAXSIZE 0x100-BAMMESSAGEOFFSET
|
|
#define TRANSWARPSIGNATROFFSLO 21
|
|
#define TRANSWARPSIGNATURELO 'T'
|
|
#define TRANSWARPSIGNATROFFSHI 22
|
|
#define TRANSWARPSIGNATUREHI 'W'
|
|
#define DIRDATACHECKSUMOFFSET 23
|
|
#define TRANSWARPTRACKOFFSET 24
|
|
#define FILECHECKSUMOFFSET 25
|
|
#define LOADADDRESSLOOFFSET 26
|
|
#define LOADADDRESSHIOFFSET 27
|
|
#define ENDADDRESSLOOFFSET 28
|
|
#define ENDADDRESSHIOFFSET 29
|
|
#define FILEBLOCKSLOOFFSET 30
|
|
#define FILEBLOCKSHIOFFSET 31
|
|
#define D64NUMBLOCKS (664 + 19)
|
|
#define D64SIZE (D64NUMBLOCKS * BLOCKSIZE)
|
|
#define D64SIZE_EXTENDED (D64SIZE + 5 * 17 * BLOCKSIZE)
|
|
#define D71SIZE (D64SIZE * 2)
|
|
#define D81SIZE (D81NUMTRACKS * SECTORSPERTRACK_D81 * BLOCKSIZE)
|
|
#define D64NUMTRACKS 35
|
|
#define D64NUMTRACKS_EXTENDED (D64NUMTRACKS + 5)
|
|
#define D71NUMTRACKS (D64NUMTRACKS * 2)
|
|
#define D81NUMTRACKS 80
|
|
#define BAM_OFFSET_SPEED_DOS 0xc0
|
|
#define BAM_OFFSET_DOLPHIN_DOS 0xac
|
|
#define DIRSLOTEXISTS 0
|
|
#define DIRSLOTFOUND 1
|
|
#define DIRSLOTNOTFOUND 2
|
|
/* for sector chain analysis */
|
|
#define UNALLOCATED 0 /* unused as of now */
|
|
#define ALLOCATED 1 /* part of a valid sector chain */
|
|
#define FILESTART 2 /* analysed to be the start of a sector chain */
|
|
#define FILESTART_TRUNCATED 3 /* analysed to be the start of a sector chain, was truncated */
|
|
#define POTENTIALLYALLOCATED 4 /* currently being analysed */
|
|
/* error codes for sector chain validation */
|
|
#define VALID 0 /* valid chain */
|
|
#define ILLEGAL_TRACK 1 /* ends with illegal track pointer */
|
|
#define ILLEGAL_SECTOR 2 /* ends with illegal sector pointer */
|
|
#define LOOP 3 /* loop in current chain */
|
|
#define COLLISION 4 /* collision with other file */
|
|
#define CHAINED 5 /* ends at another file start */
|
|
#define CHAINED_TRUNCATED 6 /* ends at start of a truncated file */
|
|
#define FIRST_BROKEN 7 /* issue already in first sector */
|
|
/* undelete levels */
|
|
#define RESTORE_DIR_ONLY 0 /* Only restore all dir entries without touching any t/s links */
|
|
#define RESTORE_VALID_FILES 1 /* Fix dir entries for files with valid t/s chains */
|
|
#define RESTORE_VALID_CHAINS 2 /* Also add wild sector chains with valid t/s chains */
|
|
#define RESTORE_INVALID_FILES 3 /* Also fix dir entries with invalid t/s chains */
|
|
#define RESTORE_INVALID_CHAINS 4 /* Also add and fix wild invalid t/s chains */
|
|
#define RESTORE_INVALID_SINGLES 5 /* Also include single block files */
|
|
/* error codes for directory */
|
|
#define DIR_OK 0
|
|
#define DIR_ILLEGAL_TS 1
|
|
#define DIR_CYCLIC_TS 2
|
|
|
|
#define TRANSWARP "TRANSWARP"
|
|
#define TRANSWARPBASEBLOCKSIZE 0xc0
|
|
#define TRANSWARPBUFFERBLOCKSIZE 0x1f
|
|
#define TRANSWARPBLOCKSIZE (TRANSWARPBASEBLOCKSIZE + TRANSWARPBUFFERBLOCKSIZE)
|
|
#define TRANSWARPKEYSIZE 29 /* 232 bits */
|
|
#define TRANSWARPKEYHASHROUNDS 33
|
|
|
|
/* Table for conversion of uppercase PETSCII to Unicode */
|
|
static unsigned int p2u_uppercase_tab[256] = {
|
|
'.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.',
|
|
'.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.',
|
|
' ', '!', '\"', '#', '$', '%', '&', 0x2019, '(', ')', '*', '+', ',', '-', '.', '/',
|
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
|
|
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
|
|
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', 0xa3, ']', 0x2191, 0x2190,
|
|
0x1fb79, 0x2660, 0x1fb72, 0x1fb78, 0x1fb77, 0x1fb76, 0x1fb7a, 0x1fb71, 0x1fb74, 0x256e, 0x2570, 0x256f, 0x1fb7c, 0x2572, 0x2571, 0x1fb7d,
|
|
0x1fb7e, 0x25cf, 0x1fb7b, 0x2665, 0x1fb70, 0x256d, 0x2573, 0x25cb, 0x2663, 0x1fb75, 0x2666, 0x253c, 0x1fb8c, 0x2502, 0x3c0, 0x25e5,
|
|
'.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.',
|
|
'.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.',
|
|
' ', 0x258c, 0x2584, 0x2594, 0x2581, 0x258e, 0x1fb95, 0x1fb75, 0x1fb8f, 0x25e4, 0x1fb75, 0x251c, 0x2597, 0x2514, 0x2510, 0x2581,
|
|
0x250c, 0x2534, 0x252c, 0x2524, 0x258e, 0x258d, 0x2590, 0x2594, 0x2580, 0x2583, 0x1fb7f, 0x2596, 0x259d, 0x2518, 0x2598, 0x259a,
|
|
0x2500, 0x2660, 0x1fb72, 0x2500, 0x1fb77, 0x1fb76, 0x1fb7a, 0x1fb71, 0x1fb74, 0x256e, 0x2570, 0x256f, 0x1fb7c, 0x2572, 0x2571, 0x1fb7d,
|
|
0x1fb7e, 0x25cf, 0x1fb7b, 0x2665, 0x1fb70, 0x256d, 0x2573, 0x25cb, 0x2663, 0x1fb75, 0x2666, 0x253c, 0x1fb8c, 0x2502, 0x3c0, 0x25e5,
|
|
' ', 0x258c, 0x2584, 0x2594, 0x2581, 0x258e, 0x1fb95, 0x1fb75, 0x1fb8f, 0x25e4, 0x1fb75, 0x251c, 0x2597, 0x2514, 0x2510, 0x2581,
|
|
0x250c, 0x2534, 0x252c, 0x2524, 0x258e, 0x258d, 0x1fb88, 0x1fb82, 0x1fb83, 0x2583, 0x1fb7f, 0x2596, 0x259d, 0x2518, 0x2598, 0x3c0
|
|
};
|
|
|
|
/* Table for conversion of lowercase PETSCII to Unicode */
|
|
static unsigned int p2u_lowercase_tab[256] = {
|
|
'.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.',
|
|
'.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.',
|
|
' ', '!', '\"', '#', '$', '%', '&', 0x2019, '(', ')', '*', '+', ',', '-', '.', '/',
|
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
|
|
'@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
|
|
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '[', 0xa3, ']', 0x2191, 0x2190,
|
|
0x1fb79, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
|
|
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0x253c, 0x1fb8c, 0x2502, 0x1fb95, 0x1fb98,
|
|
'.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.',
|
|
'.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.',
|
|
' ', 0x258c, 0x2584, 0x2594, 0x2581, 0x258e, 0x1fb95, 0x1fb75, 0x1fb8f, 0x1fb99, 0x1fb75, 0x251c, 0x2597, 0x2514, 0x2510, 0x2581,
|
|
0x250c, 0x2534, 0x252c, 0x2524, 0x258e, 0x258d, 0x2590, 0x2594, 0x2580, 0x2583, 0x1fb7f, 0x2713, 0x259d, 0x2518, 0x2598, 0x259a,
|
|
0x2500, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
|
|
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0x253c, 0x1fb8c, 0x2502, 0x1fb95, 0x1fb98,
|
|
' ', 0x258c, 0x2584, 0x2594, 0x2581, 0x258e, 0x1fb95, 0x1fb75, 0x1fb8f, 0x1fb99, 0x1fb75, 0x251c, 0x2597, 0x2514, 0x2510, 0x2581,
|
|
0x250c, 0x2534, 0x252c, 0x2524, 0x258e, 0x258d, 0x1fb88, 0x1fb82, 0x1fb83, 0x2583, 0x2713, 0x2596, 0x259d, 0x2518, 0x2598, 0x1fb96
|
|
};
|
|
|
|
typedef struct {
|
|
const unsigned char* alocalname; /* local file name or name of loop file in ASCII */
|
|
unsigned char plocalname[FILENAMEMAXSIZE]; /* loop file in PETSCII */
|
|
unsigned char pfilename[FILENAMEMAXSIZE]; /* disk file name in PETSCII */
|
|
int direntryindex;
|
|
int direntrysector;
|
|
int direntryoffset;
|
|
int sectorInterleave;
|
|
int first_sector_new_track;
|
|
int track;
|
|
int sector;
|
|
int nrSectors;
|
|
int nrSectorsShown;
|
|
int filetype;
|
|
int mode;
|
|
int force_new;
|
|
int size;
|
|
int last_track;
|
|
bool have_key;
|
|
unsigned char key[TRANSWARPKEYSIZE];
|
|
} imagefile;
|
|
|
|
enum mode {
|
|
MODE_BEGINNING_SECTOR_MASK = 0x003f, /* 6 bits */
|
|
MODE_MIN_TRACK_MASK = 0x0fc0, /* 6 bits */
|
|
MODE_MIN_TRACK_SHIFT = 6,
|
|
MODE_SAVETOEMPTYTRACKS = 0x1000,
|
|
MODE_FITONSINGLETRACK = 0x2000,
|
|
MODE_SAVECLUSTEROPTIMIZED = 0x4000,
|
|
MODE_LOOPFILE = 0x8000,
|
|
MODE_TRANSWARPBOOTFILE = 0x10000,
|
|
MODE_NOFILE = 0x20000
|
|
};
|
|
|
|
typedef enum {
|
|
IMAGE_D64,
|
|
IMAGE_D64_EXTENDED_SPEED_DOS,
|
|
IMAGE_D64_EXTENDED_DOLPHIN_DOS,
|
|
IMAGE_D71,
|
|
IMAGE_D81
|
|
} image_type;
|
|
|
|
static const char *filetypename_uc[] = {
|
|
"DEL", "SEQ", "PRG", "USR", "REL", "CBM", "???", "???",
|
|
"???", "???", "???", "???", "???", "???", "???", "???"
|
|
};
|
|
|
|
static const char *filetypename_lc[] = {
|
|
"del", "seq", "prg", "usr", "rel", "cbm", "???", "???",
|
|
"???", "???", "???", "???", "???", "???", "???", "???"
|
|
};
|
|
|
|
static const char *error_name[] = {
|
|
"no error", "illegal track", "illegal sector", "loop", "collision", "chained", "chained", "first block broken"
|
|
};
|
|
|
|
static const char *dir_error_string[] = {
|
|
"no error", "illegal track or sector in directory sector chain", "cycle in directory sector chain"
|
|
};
|
|
|
|
static const int
|
|
sectors_per_track[] = {
|
|
/* 1-17 */ 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,
|
|
/* 18-24 */ 19,19,19,19,19,19,19,
|
|
/* 25-30 */ 18,18,18,18,18,18,
|
|
/* 31-35 */ 17,17,17,17,17,
|
|
/* 36-52 */ 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,
|
|
/* 53-59 */ 19,19,19,19,19,19,19,
|
|
/* 60-65 */ 18,18,18,18,18,18,
|
|
/* 66-70 */ 17,17,17,17,17
|
|
};
|
|
|
|
static const int
|
|
sectors_per_track_extended[] = {
|
|
/* 1-17 */ 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,
|
|
/* 18-24 */ 19,19,19,19,19,19,19,
|
|
/* 25-30 */ 18,18,18,18,18,18,
|
|
/* 31-35 */ 17,17,17,17,17,
|
|
/* 36-40 */ 17,17,17,17,17,
|
|
/* 41-57 */ 21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,
|
|
/* 58-64 */ 19,19,19,19,19,19,19,
|
|
/* 65-70 */ 18,18,18,18,18,18,
|
|
/* 71-75 */ 17,17,17,17,17,
|
|
/* 76-80 */ 17,17,17,17,17
|
|
};
|
|
|
|
static int quiet = 0; /* global quiet flag */
|
|
static int verbose = 0; /* global verbose flag */
|
|
static int num_files = 0; /* number of files to be written provided by the user */
|
|
static int max_hash_length = 16; /* number of bytes of the filenames to calculate the hash over */
|
|
static int unicode = 0; /* which unicode mapping to use: 0 = none, 1 = upper case, 2 = lower case */
|
|
static int modified = 0; /* image needs to be written */
|
|
static int dir_error = DIR_OK; /* directory has an error */
|
|
|
|
/* Prints the command line help */
|
|
static void
|
|
usage()
|
|
{
|
|
printf("\n*** This is cc1541 version " VERSION " built on " __DATE__ " ***\n\n");
|
|
printf("Usage: cc1541 [options] image.[d64|d71|d81]\n\n");
|
|
printf("-n diskname Disk name, default='cc1541'.\n");
|
|
printf("-i id Disk ID, default='00 2a'.\n");
|
|
printf("-H message Hidden BAM message. Only for D64 (up to 85 chars) or SPEED DOS\n");
|
|
printf(" (up to 20 chars).\n");
|
|
printf("-w localname Write local file to disk, if filename is not set then the\n");
|
|
printf(" local name is used. After file written, the filename is unset.\n");
|
|
printf("-W localname Like -w, but encode file in Transwarp format.\n");
|
|
printf(" Provide Transwarp bootfile as last file using\n");
|
|
printf(" \"-f 'transwarp vX.YZ' -w 'transwarp vX.YZ.prg'\"\n");
|
|
printf("-K key Set an encryption key for Transwarp files, a string of up to 29\n");
|
|
printf(" characters.\n");
|
|
printf("-f filename Use filename as name when writing next file, use prefix # to\n");
|
|
printf(" include arbitrary PETSCII characters (e.g. -f \"start#a0,8,1\").\n");
|
|
printf("-o Do not overwrite if file with same name exists already.\n");
|
|
printf("-V Do not modify image unless it is in valid CBM DOS format.\n");
|
|
printf("-T filetype Filetype for next file, allowed parameters are PRG, SEQ, USR, REL\n");
|
|
printf(" and DEL, or a decimal number between 0 and 255. Default is PRG.\n");
|
|
printf("-P Set write protect flag for next file.\n");
|
|
printf("-O Set open flag for next file.\n");
|
|
printf("-N Force creation of a new directory entry, even if a file with the\n");
|
|
printf(" same name exists already.\n");
|
|
printf("-l filename Write loop file (an additional dir entry) to existing file to\n");
|
|
printf(" disk, set filename with -f.\n");
|
|
printf("-L Add dir entry without writing file (track and sector will be 0),\n");
|
|
printf(" requires a filename given with -f.\n");
|
|
printf("-B numblocks Write the given value as file size in blocks to the directory for\n");
|
|
printf(" the next file. Not applicable for Transwarp files.\n");
|
|
printf("-M numchars Hash computation maximum filename length, this must\n");
|
|
printf(" match loader option FILENAME_MAXLENGTH in Krill's loader.\n");
|
|
printf(" Default is 16.\n");
|
|
printf("-m Ignore filename hash collisions, without this switch a collision\n");
|
|
printf(" results in an error.\n");
|
|
printf("-d track Maintain a shadow directory (copy of the actual directory without\n");
|
|
printf(" a valid BAM).\n");
|
|
printf("-t Use directory track to also store files (makes -x useless)\n");
|
|
printf(" (default no).\n");
|
|
printf("-u numblocks When using -t, amount of dir blocks to leave free (default=2).\n");
|
|
printf("-x Don't split files over directory track hole (default split files).\n");
|
|
printf("-F value Next file first sector on a new track (default=0).\n");
|
|
printf(" Any negative value assumes aligned tracks and uses current\n");
|
|
printf(" sector + interleave - value. After each file, the value falls\n");
|
|
printf(" back to the default. Not applicable for D81.\n");
|
|
printf("-S value Default sector interleave, default=10. Not applicable for D81.\n");
|
|
printf("-s value Next file sector interleave, valid after each file.\n");
|
|
printf(" The interleave value falls back to the default value set by -S\n");
|
|
printf(" after the first sector of the next file. Not applicable for D81.\n");
|
|
printf("-e Start next file on an empty track (default start sector is\n");
|
|
printf(" current sector plus interleave).\n");
|
|
printf("-E Try to fit file on a single track.\n");
|
|
printf("-r track Restrict next file blocks to the specified track or higher.\n");
|
|
printf("-b sector Set next file beginning sector to the specified value.\n");
|
|
printf(" Not applicable for D81.\n");
|
|
printf("-c Save next file cluster-optimized (d71 only).\n");
|
|
printf("-4 Use tracks 35-40 with SPEED DOS BAM formatting.\n");
|
|
printf("-5 Use tracks 35-40 with DOLPHIN DOS BAM formatting.\n");
|
|
printf("-R level Try to restore deleted and formatted files.\n");
|
|
printf(" level 0: Only restore dir entries without touching any t/s links.\n");
|
|
printf(" level 1: Fix dir entries for files with valid t/s chains.\n");
|
|
printf(" level 2: Also add wild sector chains with valid t/s chains.\n");
|
|
printf(" level 3: Also fix dir entries with invalid t/s chains.\n");
|
|
printf(" level 4: Also add and fix wild invalid t/s chains.\n");
|
|
printf(" level 5: Also add reasonable wild single blocks.\n");
|
|
printf("-g filename Write additional g64 output file with given name.\n");
|
|
printf("-a Print command line options that would create the same directory as\n");
|
|
printf(" the one in the given image (for directory art import).\n");
|
|
printf("-U mapping Print PETSCII as Unicode (requires Unicode 13.0 font, e.g.\n");
|
|
printf(" UNSCII). Use mapping 0 for ASCII output, 1 for upper case, 2 for\n");
|
|
printf(" lower case, default is 0.\n");
|
|
printf("-q Be quiet.\n");
|
|
printf("-v Be verbose.\n");
|
|
printf("-h Print this command line help.\n");
|
|
printf("\n");
|
|
|
|
exit(-1);
|
|
}
|
|
|
|
/* Returns a pointer to the filename in a path */
|
|
static const unsigned char*
|
|
basename(const unsigned char* path)
|
|
{
|
|
const unsigned char* name;
|
|
(name = (unsigned char*)strrchr((char *)path, FILESEPARATOR)) ? ++name : (name = path);
|
|
return name;
|
|
}
|
|
|
|
/* Calculates a hash from a filename to be used by Krill's loader */
|
|
static unsigned int
|
|
filenamehash(const unsigned char *filename)
|
|
{
|
|
int pos = min(max_hash_length, (int)strlen((char *)filename));
|
|
while ((pos > 0) && (((unsigned char) filename[pos - 1]) == FILENAMEEMPTYCHAR)) {
|
|
--pos;
|
|
}
|
|
|
|
unsigned char hashlo = pos;
|
|
unsigned char hashhi = pos;
|
|
int carry = 1;
|
|
|
|
for (int i = pos - 1; i >= 0; --i) {
|
|
unsigned int sum = hashlo + filename[i] + carry;
|
|
carry = (sum >= 256) ? 1 : 0;
|
|
sum &= 0xff;
|
|
hashlo = sum;
|
|
sum += hashhi + carry;
|
|
carry = (sum >= 256) ? 1 : 0;
|
|
hashhi = sum;
|
|
}
|
|
|
|
return (hashhi << 8) | hashlo;
|
|
}
|
|
|
|
/* Returns the size of an image in bytes */
|
|
static unsigned int
|
|
image_size(image_type type)
|
|
{
|
|
switch (type) {
|
|
case IMAGE_D64:
|
|
return D64SIZE;
|
|
|
|
case IMAGE_D64_EXTENDED_SPEED_DOS:
|
|
/* fall through */
|
|
case IMAGE_D64_EXTENDED_DOLPHIN_DOS:
|
|
return D64SIZE_EXTENDED;
|
|
|
|
case IMAGE_D71:
|
|
return D71SIZE;
|
|
|
|
case IMAGE_D81:
|
|
return D81SIZE;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Returns the number of tracks in an image */
|
|
static unsigned int
|
|
image_num_tracks(image_type type)
|
|
{
|
|
switch (type) {
|
|
case IMAGE_D64:
|
|
return D64NUMTRACKS;
|
|
|
|
case IMAGE_D64_EXTENDED_SPEED_DOS:
|
|
/* fall through */
|
|
case IMAGE_D64_EXTENDED_DOLPHIN_DOS:
|
|
return D64NUMTRACKS_EXTENDED;
|
|
|
|
case IMAGE_D71:
|
|
return D71NUMTRACKS;
|
|
|
|
case IMAGE_D81:
|
|
return D81NUMTRACKS;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* Returns the number of sectors for a given track */
|
|
static int
|
|
num_sectors(image_type type, int track)
|
|
{
|
|
return (type == IMAGE_D81) ? SECTORSPERTRACK_D81
|
|
: (((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) ? sectors_per_track_extended[track - 1]
|
|
: sectors_per_track[track - 1]);
|
|
}
|
|
|
|
/* Returns the number of blocks in an image */
|
|
static unsigned int
|
|
image_num_blocks(image_type type)
|
|
{
|
|
int num_blocks = 0;
|
|
for (unsigned int t = 1; t <= image_num_tracks(type); t++) {
|
|
num_blocks += num_sectors(type, t);
|
|
}
|
|
return num_blocks;
|
|
}
|
|
|
|
/* Returns the directory track of an image */
|
|
static int
|
|
dirtrack(image_type type)
|
|
{
|
|
return (type == IMAGE_D81) ? DIRTRACK_D81 : DIRTRACK_D41_D71;
|
|
}
|
|
|
|
/* Converts an ASCII character to PETSCII */
|
|
static unsigned char
|
|
a2p(unsigned char a)
|
|
{
|
|
switch (a) {
|
|
case '\n':
|
|
return 0x0d;
|
|
case '_':
|
|
return 0xa4;
|
|
case 0x7e:
|
|
return 0xff;
|
|
default:
|
|
if ((a >= 0x5b) && (a < 0x5f)) {
|
|
return a;
|
|
}
|
|
if ((a >= 0x60) && (a <= 0x7e)) {
|
|
return a ^ 0x20;
|
|
}
|
|
if ((a >= 'A') && (a <= 'Z')) {
|
|
return a | 0x80;
|
|
}
|
|
return a;
|
|
}
|
|
}
|
|
|
|
/* Converts a PETSCII character to ASCII */
|
|
static unsigned char
|
|
p2a(unsigned char p)
|
|
{
|
|
switch (p) {
|
|
case 0x0a:
|
|
case 0x0d:
|
|
return '\n';
|
|
case 0x40:
|
|
case 0x60:
|
|
return p;
|
|
case 0xa0:
|
|
case 0xe0:
|
|
return ' ';
|
|
case 0xa4:
|
|
case 0xe4:
|
|
return '_';
|
|
default:
|
|
switch (p & 0xe0) {
|
|
case 0x40:
|
|
case 0x60:
|
|
p ^= 0x20;
|
|
break;
|
|
case 0xc0:
|
|
p ^= 0x80;
|
|
break;
|
|
}
|
|
}
|
|
return ((isprint(p) ? p : '.'));
|
|
}
|
|
|
|
/* Converts an ASCII string to PETSCII filled up with Shift-Space to length */
|
|
static void
|
|
ascii2petscii(const unsigned char* ascii, unsigned char* petscii, int len)
|
|
{
|
|
int pos = 0;
|
|
while (ascii[pos] != '\0' && pos < len) {
|
|
petscii[pos] = a2p(ascii[pos]);
|
|
++pos;
|
|
}
|
|
while (pos < len) {
|
|
petscii[pos] = FILENAMEEMPTYCHAR;
|
|
++pos;
|
|
}
|
|
}
|
|
|
|
/* Prints a PETSCII filename as ASCII with escapes */
|
|
static void
|
|
print_filename_with_escapes(const unsigned char* petscii, int len)
|
|
{
|
|
/* find end of petscii string */
|
|
while(len > 1 && petscii[len-1] == 0xa0) {
|
|
len--;
|
|
}
|
|
for(int ppos = 0; ppos < len; ppos++) {
|
|
unsigned char c = petscii[ppos];
|
|
if((c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c == 32) {
|
|
switch (c & 0xe0) {
|
|
case 0x40:
|
|
case 0x60:
|
|
putchar(c ^ 0x20);
|
|
break;
|
|
default:
|
|
putchar(c);
|
|
}
|
|
} else {
|
|
printf("#%02x", c);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Determines length of 0xa0 terminated PETSCII string */
|
|
static int
|
|
pstrlen(const unsigned char* string)
|
|
{
|
|
int len = 0;
|
|
while(string[len] != 0xa0 && len < FILENAMEMAXSIZE) {
|
|
len++;
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/* Writes digit decimal number into PETSCII string */
|
|
#define PPUTNUM_BUF_LEN 17
|
|
static void
|
|
pputnum(unsigned char* string, unsigned int num)
|
|
{
|
|
char buffer[PPUTNUM_BUF_LEN];
|
|
snprintf(buffer, PPUTNUM_BUF_LEN, "%d", num);
|
|
for(unsigned int len = 0; len < strlen(buffer); len++) {
|
|
string[len] = a2p(buffer[len]);
|
|
}
|
|
}
|
|
|
|
/* Writes 2 digit decimal number into PETSCII string */
|
|
#define PPUTNUM2_BUF_LEN 3
|
|
static void
|
|
pputnum2(unsigned char* string, unsigned int num)
|
|
{
|
|
char buffer[PPUTNUM2_BUF_LEN];
|
|
snprintf(buffer, PPUTNUM2_BUF_LEN, "%02d", num);
|
|
for(unsigned int len = 0; len < strlen(buffer); len++) {
|
|
string[len] = a2p(buffer[len]);
|
|
}
|
|
}
|
|
|
|
/* Writes 4 digit hex number into PETSCII string */
|
|
#define PPUTHEX_BUF_LEN 5
|
|
static void
|
|
pputhex(unsigned char* string, unsigned int num)
|
|
{
|
|
char buffer[PPUTHEX_BUF_LEN];
|
|
snprintf(buffer, PPUTHEX_BUF_LEN, "%04x", num);
|
|
for(int len = 0; len < 4; len++) {
|
|
string[len] = a2p(buffer[len]);
|
|
}
|
|
}
|
|
|
|
/* Converts a two digit hex string to an int */
|
|
static unsigned int
|
|
hex2int(char hex)
|
|
{
|
|
if ((hex < '0' || hex > '9') && (hex < 'a' || hex > 'f')) {
|
|
fprintf(stderr, "ERROR: Invalid hex string in filename\n");
|
|
|
|
exit(-1);
|
|
}
|
|
if (hex <= '9') {
|
|
hex -= '0';
|
|
} else {
|
|
hex -= 'a' - 10;
|
|
}
|
|
return (unsigned int)hex;
|
|
}
|
|
|
|
/* Converts an ASCII string to PETSCII with escape evaluation filled up with emptychar to length */
|
|
static void
|
|
evalhexescape(const unsigned char* ascii, unsigned char* petscii, int len, unsigned char emptychar)
|
|
{
|
|
int read = 0, write = 0;
|
|
|
|
while (ascii[read] != '\0' && write < len) {
|
|
if (ascii[read] == '#') {
|
|
unsigned int hi = hex2int(ascii[++read]);
|
|
unsigned int lo = hex2int(ascii[++read]);
|
|
petscii[write] = (unsigned char)(16 * hi + lo);
|
|
} else {
|
|
petscii[write] = a2p(ascii[read]);
|
|
}
|
|
read++;
|
|
write++;
|
|
}
|
|
while (write < len) {
|
|
petscii[write] = emptychar;
|
|
++write;
|
|
}
|
|
}
|
|
|
|
/* Converts a unicode character to utf8 */
|
|
char* utf8_encode(int c, char* out)
|
|
{
|
|
if (c < 0x80)
|
|
*out++ =(c & 0xff);
|
|
else if (c < 0x800) {
|
|
*out++ = (0xC0 | ((c >> 6) & 0x1f));
|
|
*out++ = (0x80 | (c & 0x3f));
|
|
} else if (c < 0x10000) {
|
|
*out++ = (0xE0 | ((c >> 12) & 0xf));
|
|
*out++ = (0x80 | ((c >> 6) & 0x3f));
|
|
*out++ = (0x80 | (c & 0x3f));
|
|
} else {
|
|
*out++ = (0xF0 | ((c >> 18) & 0x07));
|
|
*out++ = (0x80 | ((c >> 12) & 0x3f));
|
|
*out++ = (0x80 | ((c >> 6) & 0x3f));
|
|
*out++ = (0x80 | (c & 0x3f));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
/* Enables console formatting under Windows if possible */
|
|
static bool
|
|
EnableVTMode()
|
|
{
|
|
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
if (hOut == INVALID_HANDLE_VALUE) {
|
|
return false;
|
|
}
|
|
DWORD dwMode = 0;
|
|
if (!GetConsoleMode(hOut, &dwMode)) {
|
|
return false;
|
|
}
|
|
dwMode |= 0x0004; /* ENABLE_VIRTUAL_TERMINAL_PROCESSING not defined for older SDKs */
|
|
if (!SetConsoleMode(hOut, dwMode)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
/* Enables reverse printing to console */
|
|
void reverse_print_on()
|
|
{
|
|
#ifdef _WIN32
|
|
/* Avoid escape values for inverse printing under Windows if they are not supported by the console */
|
|
if (EnableVTMode()) {
|
|
printf("\033[7m");
|
|
}
|
|
#else
|
|
printf("\033[7m");
|
|
#endif
|
|
}
|
|
|
|
/* Disables reverse printing to console */
|
|
void reverse_print_off()
|
|
{
|
|
#ifdef _WIN32
|
|
/* Avoid escape values for inverse printing under Windows if they are not supported by the console */
|
|
if (EnableVTMode()) {
|
|
printf("\033[m");
|
|
}
|
|
#else
|
|
printf("\033[m");
|
|
#endif
|
|
}
|
|
|
|
/* Prints a PETSCII character */
|
|
static void
|
|
putp(unsigned char petscii, FILE *file)
|
|
{
|
|
if (unicode) {
|
|
int u;
|
|
int reverse = 0;
|
|
|
|
if (petscii < 0x20 || (petscii >= 0x80 && petscii <= 0x9f)) {
|
|
reverse = 1;
|
|
reverse_print_on();
|
|
petscii += 0x40;
|
|
}
|
|
|
|
if (unicode == 1) {
|
|
u = p2u_uppercase_tab[petscii];
|
|
} else {
|
|
u = p2u_lowercase_tab[petscii];
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
_setmode(_fileno(file), _O_U16TEXT);
|
|
putwc(u, file);
|
|
_setmode(_fileno(file), _O_TEXT);
|
|
#else
|
|
{
|
|
char temp[5];
|
|
*utf8_encode(u, temp) = 0; /* fancy way to zero-terminate temp after conversion */
|
|
fputs(temp, file);
|
|
}
|
|
#endif
|
|
|
|
if(reverse) {
|
|
reverse_print_off();
|
|
}
|
|
} else {
|
|
putc(p2a(petscii), file);
|
|
}
|
|
}
|
|
|
|
/* Prints a PETSCII string */
|
|
static void
|
|
print_petscii(unsigned char *petscii, int len)
|
|
{
|
|
for(int i = 0; i < len; i++) {
|
|
putp(petscii[i], stdout);
|
|
}
|
|
}
|
|
|
|
/* Prints the given PETSCII filename like the C64 when listing the directory */
|
|
static void
|
|
print_dirfilename(unsigned char* pfilename)
|
|
{
|
|
int ended = 0;
|
|
putc('\"', stdout);
|
|
for (int pos = 0; pos < FILENAMEMAXSIZE; pos++) {
|
|
unsigned char c = pfilename[pos];
|
|
if (c == FILENAMEEMPTYCHAR) {
|
|
if (!ended) {
|
|
putc('\"', stdout);
|
|
ended = 1;
|
|
} else {
|
|
putc(' ', stdout);
|
|
}
|
|
} else {
|
|
putp(c, stdout);
|
|
}
|
|
}
|
|
if (!ended) {
|
|
putc('\"', stdout);
|
|
} else {
|
|
putc(' ', stdout);
|
|
}
|
|
}
|
|
|
|
/* Prints the given PETSCII filename */
|
|
static void
|
|
print_filename(FILE *file, unsigned char* pfilename)
|
|
{
|
|
putc('\"', file);
|
|
for (int pos = 0; pos < FILENAMEMAXSIZE; pos++) {
|
|
if (pfilename[pos] == FILENAMEEMPTYCHAR) {
|
|
break;
|
|
}
|
|
putp(pfilename[pos], file);
|
|
}
|
|
putc('\"', file);
|
|
}
|
|
|
|
/* Calculates the overall sector index from a given track and sector, returns -1 for invalid track/sector */
|
|
static int
|
|
linear_sector(image_type type, int track, int sector)
|
|
{
|
|
if ((track < 1) || (track > ((type == IMAGE_D81) ? D81NUMTRACKS : ((type == IMAGE_D64) ? D64NUMTRACKS : (type == IMAGE_D71 ? D71NUMTRACKS : D64NUMTRACKS_EXTENDED))))) {
|
|
return -1;
|
|
}
|
|
|
|
int numsectors = num_sectors(type, track);
|
|
if ((sector < 0) || (sector >= numsectors)) {
|
|
return -1;
|
|
}
|
|
|
|
int linear_sector = 0;
|
|
for (int i = 0; i < track - 1; i++) {
|
|
linear_sector += num_sectors(type, i + 1);
|
|
}
|
|
linear_sector += sector;
|
|
|
|
return linear_sector;
|
|
}
|
|
|
|
/* Returns the image offset of the bam entry for a given track */
|
|
static int
|
|
get_bam_offset(image_type type, unsigned int track)
|
|
{
|
|
int bam;
|
|
unsigned int offset;
|
|
|
|
if (type == IMAGE_D81) {
|
|
if (track <= 40) {
|
|
bam = linear_sector(type, dirtrack(type), 1 /* sector */) * BLOCKSIZE;
|
|
offset = bam + (track * 6) + 11;
|
|
} else {
|
|
bam = linear_sector(type, dirtrack(type), 2 /* sector */) * BLOCKSIZE;
|
|
offset = bam + ((track - 40) * 6) + 11;
|
|
}
|
|
} else if ((type == IMAGE_D71) && (track > D64NUMTRACKS)) {
|
|
/* access second side bam */
|
|
bam = linear_sector(type, dirtrack(type) + D64NUMTRACKS, 0) * BLOCKSIZE;
|
|
offset = bam + (track - D64NUMTRACKS - 1) * 3;
|
|
} else {
|
|
if (((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) && (track > D64NUMTRACKS)) {
|
|
track -= D64NUMTRACKS;
|
|
bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE + ((type == IMAGE_D64_EXTENDED_SPEED_DOS) ? BAM_OFFSET_SPEED_DOS : BAM_OFFSET_DOLPHIN_DOS);
|
|
} else {
|
|
bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE;
|
|
}
|
|
offset = bam + track * 4 + 1;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
/* Checks if a given sector is marked as free in the BAM and also not used by directory */
|
|
static int
|
|
is_sector_free(image_type type, const unsigned char* image, int track, int sector, int numdirblocks, int dir_sector_interleave)
|
|
{
|
|
int bam;
|
|
const unsigned char* bitmap;
|
|
|
|
if (sector < 0) {
|
|
fprintf(stderr, "ERROR: Illegal sector %d for track %d\n", sector, track);
|
|
|
|
exit(-1);
|
|
}
|
|
|
|
if (type == IMAGE_D81) {
|
|
if (track <= 40) {
|
|
bam = linear_sector(type, dirtrack(type), 1 /* sector */) * BLOCKSIZE;
|
|
bitmap = image + bam + (track * 6) + 11;
|
|
} else {
|
|
bam = linear_sector(type, dirtrack(type), 2 /* sector */) * BLOCKSIZE;
|
|
bitmap = image + bam + ((track - 40) * 6) + 11;
|
|
}
|
|
} else if ((type == IMAGE_D71) && (track > D64NUMTRACKS)) {
|
|
/* access second side bam */
|
|
bam = linear_sector(type, dirtrack(type) + D64NUMTRACKS, 0) * BLOCKSIZE;
|
|
bitmap = image + bam + (track - D64NUMTRACKS - 1) * 3;
|
|
} else {
|
|
if (((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) && (track > D64NUMTRACKS)) {
|
|
track -= D64NUMTRACKS+1;
|
|
bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE + ((type == IMAGE_D64_EXTENDED_SPEED_DOS) ? BAM_OFFSET_SPEED_DOS : BAM_OFFSET_DOLPHIN_DOS);
|
|
} else {
|
|
bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE;
|
|
}
|
|
bitmap = image + bam + (track * 4) + 1;
|
|
}
|
|
|
|
int byte = sector >> 3;
|
|
int bit = sector & 7;
|
|
|
|
int is_not_dir_block = 1;
|
|
if ((track == dirtrack(type)) && (numdirblocks > 0)) {
|
|
int dirsector = 0;
|
|
int s = 2;
|
|
for (int i = 0; is_not_dir_block && (i < numdirblocks); i++) {
|
|
switch (i) {
|
|
case 0:
|
|
dirsector = 0;
|
|
break;
|
|
|
|
case 1:
|
|
dirsector = 1;
|
|
break;
|
|
|
|
default:
|
|
dirsector += dir_sector_interleave;
|
|
if (dirsector >= num_sectors(type, track)) {
|
|
dirsector = s;
|
|
s++;
|
|
}
|
|
break;
|
|
}
|
|
is_not_dir_block = (sector != dirsector);
|
|
}
|
|
}
|
|
|
|
return is_not_dir_block && ((bitmap[byte] & (1 << bit)) != 0);
|
|
}
|
|
|
|
/* Marks given sector in BAM */
|
|
static void
|
|
mark_sector(image_type type, unsigned char* image, int track, int sector, int free)
|
|
{
|
|
if (free != is_sector_free(type, image, track, sector, 0, 0)) {
|
|
int bam;
|
|
unsigned char* bitmap;
|
|
if (type == IMAGE_D81) {
|
|
if (track <= 40) {
|
|
bam = linear_sector(type, dirtrack(type), 1 /* sector */) * BLOCKSIZE;
|
|
bitmap = image + bam + (track * 6) + 11;
|
|
} else {
|
|
bam = linear_sector(type, dirtrack(type), 2 /* sector */) * BLOCKSIZE;
|
|
bitmap = image + bam + ((track - 40) * 6) + 11;
|
|
}
|
|
|
|
/* update number of free sectors on track */
|
|
if (free) {
|
|
++bitmap[-1];
|
|
} else {
|
|
--bitmap[-1];
|
|
}
|
|
} else if ((type == IMAGE_D71) && (track > D64NUMTRACKS)) {
|
|
/* access second side bam */
|
|
bam = linear_sector(type, dirtrack(type) + D64NUMTRACKS, 0) * BLOCKSIZE;
|
|
bitmap = image + bam + (track - D64NUMTRACKS - 1) * 3;
|
|
|
|
/* update number of free sectors on track */
|
|
if (free) {
|
|
image[bam + 0xdd + track - D64NUMTRACKS - 1]++;
|
|
} else {
|
|
image[bam + 0xdd + track - D64NUMTRACKS - 1]--;
|
|
}
|
|
} else {
|
|
if (((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) && (track > D64NUMTRACKS)) {
|
|
track -= D64NUMTRACKS+1;
|
|
bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE + ((type == IMAGE_D64_EXTENDED_SPEED_DOS) ? BAM_OFFSET_SPEED_DOS : BAM_OFFSET_DOLPHIN_DOS);
|
|
} else {
|
|
bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE;
|
|
}
|
|
bitmap = image + bam + (track * 4) + 1;
|
|
|
|
/* update number of free sectors on track */
|
|
if (free) {
|
|
++image[bam + (track * 4)];
|
|
} else {
|
|
--image[bam + (track * 4)];
|
|
}
|
|
}
|
|
|
|
/* update bitmap */
|
|
int byte = sector >> 3;
|
|
int bit = sector & 7;
|
|
|
|
if (free) {
|
|
bitmap[byte] |= 1 << bit;
|
|
} else {
|
|
bitmap[byte] &= ~(1 << bit);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Returns offset for header on directory track */
|
|
static int
|
|
get_header_offset(image_type type)
|
|
{
|
|
int offset;
|
|
|
|
if (type == IMAGE_D81) {
|
|
offset = 4;
|
|
} else {
|
|
offset = 0x90;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
/* Returns offset for id on directory track */
|
|
static int
|
|
get_id_offset(image_type type)
|
|
{
|
|
int offset;
|
|
|
|
if (type == IMAGE_D81) {
|
|
offset = 0x16;
|
|
} else {
|
|
offset = 0xa2;
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
/* Updates the directory with the given header, id and BAM message */
|
|
static void
|
|
update_directory(image_type type, unsigned char* image, unsigned char* header, unsigned char* id, unsigned char *bam_message, int shadowdirtrack)
|
|
{
|
|
unsigned int bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE;
|
|
|
|
if (type != IMAGE_D81) {
|
|
image[bam + 0x03] = (type == IMAGE_D71) ? 0x80 : 0x00;
|
|
}
|
|
|
|
/* Set header and ID */
|
|
unsigned char pheader[FILENAMEMAXSIZE];
|
|
unsigned char pid[5];
|
|
evalhexescape(header, pheader, FILENAMEMAXSIZE, FILENAMEEMPTYCHAR);
|
|
evalhexescape(id, pid, 5, FILENAMEEMPTYCHAR);
|
|
memcpy(image + bam + get_header_offset(type), pheader, FILENAMEMAXSIZE);
|
|
memcpy(image + bam + get_id_offset(type), pid, 5);
|
|
|
|
/* Set BAM message */
|
|
if(bam_message != NULL) {
|
|
unsigned char pbam_message[BAMMESSAGEMAXSIZE];
|
|
int bam_message_len = BAMMESSAGEMAXSIZE;
|
|
if(type == IMAGE_D64_EXTENDED_SPEED_DOS) {
|
|
bam_message_len = 0xbf - BAMMESSAGEOFFSET; /* avoid conflict with extended BAM and allow for a 0 at the end*/
|
|
}
|
|
evalhexescape(bam_message, pbam_message, bam_message_len, 0);
|
|
memcpy(image + bam + BAMMESSAGEOFFSET, pbam_message, bam_message_len);
|
|
}
|
|
|
|
if (type == IMAGE_D81) {
|
|
unsigned int bam = linear_sector(type, dirtrack(type), 1 /* sector */) * BLOCKSIZE;
|
|
image[bam + 0x04] = id[0];
|
|
image[bam + 0x05] = id[1];
|
|
|
|
bam = linear_sector(type, dirtrack(type), 2 /* sector */) * BLOCKSIZE;
|
|
image[bam + 0x04] = id[0];
|
|
image[bam + 0x05] = id[1];
|
|
}
|
|
|
|
if (shadowdirtrack > 0) {
|
|
unsigned int shadowbam = linear_sector(type, shadowdirtrack, 0 /* sector */) * BLOCKSIZE;
|
|
memcpy(image + shadowbam, image + bam, BLOCKSIZE);
|
|
|
|
image[shadowbam + 0x00] = shadowdirtrack;
|
|
}
|
|
}
|
|
|
|
/* Writes an empty directory and BAM */
|
|
static void
|
|
initialize_directory(image_type type, unsigned char* image, unsigned char* header, unsigned char* id, unsigned char * bam_message, int shadowdirtrack)
|
|
{
|
|
unsigned int dir = linear_sector(type, dirtrack(type), 0 /* sector */) * BLOCKSIZE;
|
|
|
|
/* Clear image */
|
|
memset(image, 0, image_size(type));
|
|
|
|
/* Write initial BAM */
|
|
if (type == IMAGE_D81) {
|
|
image[dir + 0x00] = dirtrack(type);
|
|
image[dir + 0x01] = 3;
|
|
image[dir + 0x02] = 0x44;
|
|
|
|
image[dir + 0x14] = FILENAMEEMPTYCHAR;
|
|
image[dir + 0x15] = FILENAMEEMPTYCHAR;
|
|
|
|
image[dir + 0x1b] = FILENAMEEMPTYCHAR;
|
|
image[dir + 0x1c] = FILENAMEEMPTYCHAR;
|
|
|
|
unsigned int bam = linear_sector(type, dirtrack(type), 1 /* sector */) * BLOCKSIZE;
|
|
image[bam + 0x00] = dirtrack(type);
|
|
image[bam + 0x01] = 2;
|
|
image[bam + 0x02] = 0x44;
|
|
image[bam + 0x03] = 0xbb;
|
|
image[bam + 0x06] = 0xc0;
|
|
|
|
bam = linear_sector(type, dirtrack(type), 2 /* sector */) * BLOCKSIZE;
|
|
image[bam + 0x00] = 0;
|
|
image[bam + 0x01] = 255;
|
|
image[bam + 0x02] = 0x44;
|
|
image[bam + 0x03] = 0xbb;
|
|
image[bam + 0x06] = 0xc0;
|
|
} else {
|
|
image[dir + 0x00] = dirtrack(type);
|
|
image[dir + 0x01] = 1;
|
|
image[dir + 0x02] = 0x41;
|
|
image[dir + 0x03] = (type == IMAGE_D71) ? 0x80 : 0x00;
|
|
|
|
image[dir + 0xa0] = FILENAMEEMPTYCHAR;
|
|
image[dir + 0xa1] = FILENAMEEMPTYCHAR;
|
|
|
|
image[dir + 0xa7] = FILENAMEEMPTYCHAR;
|
|
image[dir + 0xa8] = FILENAMEEMPTYCHAR;
|
|
image[dir + 0xa9] = FILENAMEEMPTYCHAR;
|
|
image[dir + 0xaa] = FILENAMEEMPTYCHAR;
|
|
}
|
|
|
|
/* Mark all sectors unused */
|
|
for (unsigned int t = 1; t <= image_num_tracks(type); t++) {
|
|
for (int s = 0; s < num_sectors(type, t); s++) {
|
|
mark_sector(type, image, t, s, 1 /* free */);
|
|
}
|
|
}
|
|
|
|
/* Reserve space for BAM */
|
|
mark_sector(type, image, dirtrack(type), 0 /* sector */, 0 /* not free */);
|
|
if (type == IMAGE_D71) {
|
|
mark_sector(type, image, dirtrack(type) + D64NUMTRACKS, 0 /* sector */, 0 /* not free */);
|
|
} else if (type == IMAGE_D81) {
|
|
mark_sector(type, image, dirtrack(type), 1 /* sector */, 0 /* not free */);
|
|
mark_sector(type, image, dirtrack(type), 2 /* sector */, 0 /* not free */);
|
|
}
|
|
|
|
/* first dir block */
|
|
unsigned int dirblock = linear_sector(type, dirtrack(type), (type == IMAGE_D81) ? 3 : 1) * BLOCKSIZE;
|
|
image[dirblock + SECTORLINKOFFSET] = 255;
|
|
mark_sector(type, image, dirtrack(type), (type == IMAGE_D81) ? 3 : 1 /* sector */, 0 /* not free */);
|
|
|
|
if (shadowdirtrack > 0) {
|
|
dirblock = linear_sector(type, shadowdirtrack, (type == IMAGE_D81) ? 3 : 1 /* sector */) * BLOCKSIZE;
|
|
image[dirblock + SECTORLINKOFFSET] = 255;
|
|
|
|
mark_sector(type, image, shadowdirtrack, 0 /* sector */, 0 /* not free */);
|
|
mark_sector(type, image, shadowdirtrack, (type == IMAGE_D81) ? 3 : 1 /* sector */, 0 /* not free */);
|
|
}
|
|
|
|
update_directory(type, image, header, id, bam_message, shadowdirtrack);
|
|
}
|
|
|
|
/* Computes Transwarp dirdata checksum */
|
|
static unsigned char
|
|
transwarp_dirdata_checksum(const unsigned char *image, int dir_entry_offset)
|
|
{
|
|
int dirdata_checksum = 0;
|
|
int carry = 1;
|
|
for (int offset = DIRDATACHECKSUMOFFSET; offset <= FILEBLOCKSLOOFFSET; ++offset) {
|
|
dirdata_checksum += (image[dir_entry_offset + offset] + carry);
|
|
carry = (dirdata_checksum >= 0x0100) ? 1 : 0;
|
|
dirdata_checksum &= 0xff;
|
|
}
|
|
|
|
return dirdata_checksum;
|
|
}
|
|
|
|
/* Checks if a given dir entry points to a Transwarp file */
|
|
static bool
|
|
is_transwarp_file(const unsigned char *image, int dir_entry_offset)
|
|
{
|
|
return (image[dir_entry_offset + TRANSWARPSIGNATROFFSLO] == TRANSWARPSIGNATURELO)
|
|
&& (image[dir_entry_offset + TRANSWARPSIGNATROFFSHI] == TRANSWARPSIGNATUREHI)
|
|
&& (transwarp_dirdata_checksum(image, dir_entry_offset) == 0);
|
|
}
|
|
|
|
/* Checks if a given dir entry points to the Transwarp bootfile */
|
|
static bool
|
|
is_transwarp_bootfile(const unsigned char *image, int dir_entry_offset)
|
|
{
|
|
return memcmp(image + dir_entry_offset + FILENAMEOFFSET, TRANSWARP, strlen(TRANSWARP)) == 0;
|
|
}
|
|
|
|
/* Return Transwarp file stat */
|
|
static int
|
|
transwarp_stat(image_type type, const unsigned char *image, int dir_entry_offset, int *start_track, int *end_track, int *low_track, int *high_track)
|
|
{
|
|
*start_track = 0;
|
|
*end_track = 0;
|
|
*low_track = 0;
|
|
*high_track = 0;
|
|
|
|
int filesize = (image[dir_entry_offset + ENDADDRESSLOOFFSET] | (image[dir_entry_offset + ENDADDRESSHIOFFSET] << 8))
|
|
- (image[dir_entry_offset + LOADADDRESSLOOFFSET] | (image[dir_entry_offset + LOADADDRESSHIOFFSET] << 8));
|
|
if (filesize <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
*start_track = image[dir_entry_offset + TRANSWARPTRACKOFFSET];
|
|
*end_track = *start_track;
|
|
|
|
int size = filesize;
|
|
|
|
while (filesize > 0) {
|
|
filesize -= (TRANSWARPBLOCKSIZE * num_sectors(type, *end_track));
|
|
if (filesize > 0) {
|
|
*end_track = (*end_track < DIRTRACK_D41_D71) ? (*end_track - 1) : (*end_track + 1);
|
|
}
|
|
}
|
|
|
|
*low_track = (*start_track < *end_track) ? *start_track : *end_track;
|
|
*high_track = (*start_track > *end_track) ? *start_track : *end_track;
|
|
|
|
return size;
|
|
}
|
|
|
|
/* Return Transwarp file stat */
|
|
static int
|
|
transwarp_size(image_type type, int start_track, int end_track, int filesize,
|
|
int *transwarp_blocks, int *nonredundant_blocks, int *redundant_blocks, int *nonredundant_blocks_on_last_track)
|
|
{
|
|
*transwarp_blocks = 0;
|
|
|
|
int last_track_sectors;
|
|
if (start_track <= end_track) {
|
|
for (int track = start_track; track <= end_track; ++track) {
|
|
last_track_sectors = num_sectors(type, track);
|
|
*transwarp_blocks += last_track_sectors;
|
|
}
|
|
} else {
|
|
for (int track = start_track; track >= end_track; --track) {
|
|
last_track_sectors = num_sectors(type, track);
|
|
*transwarp_blocks += last_track_sectors;
|
|
}
|
|
}
|
|
|
|
int spare_bytes = TRANSWARPBLOCKSIZE - (filesize % TRANSWARPBLOCKSIZE);
|
|
if (spare_bytes == TRANSWARPBLOCKSIZE) {
|
|
spare_bytes = 0;
|
|
}
|
|
|
|
*nonredundant_blocks = (filesize / TRANSWARPBLOCKSIZE) + ((spare_bytes == 0) ? 0 : 1);
|
|
*redundant_blocks = *transwarp_blocks - *nonredundant_blocks;
|
|
*nonredundant_blocks_on_last_track = last_track_sectors - *redundant_blocks;
|
|
|
|
return spare_bytes;
|
|
}
|
|
|
|
/* Deletes a file from disk and BAM, but leaves the directory entry */
|
|
static void
|
|
wipe_file(image_type type, unsigned char* image, imagefile* file)
|
|
{
|
|
int b = linear_sector(type, dirtrack(type), file->direntrysector) * BLOCKSIZE + file->direntryoffset;
|
|
|
|
if (is_transwarp_file(image, b)) {
|
|
int start_track;
|
|
int end_track;
|
|
int low_track;
|
|
int high_track;
|
|
int filesize = transwarp_stat(type, image, b, &start_track, &end_track, &low_track, &high_track);
|
|
if (filesize <= 0) {
|
|
fprintf(stderr, "ERROR: Cannot overwrite Transwarp file ");
|
|
print_filename(stderr, file->pfilename);
|
|
fprintf(stderr, "\n");
|
|
|
|
exit(-1);
|
|
}
|
|
|
|
for (int track = low_track; track <= high_track; ++track) {
|
|
for (int sector = 0; sector < num_sectors(type, track); ++sector) {
|
|
int block_offset = linear_sector(type, track, sector) * BLOCKSIZE;
|
|
memset(image + block_offset, 0, BLOCKSIZE);
|
|
mark_sector(type, image, track, sector, 1 /* free */);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
unsigned int track = image[b + FILETRACKOFFSET];
|
|
unsigned int sector = image[b + FILESECTOROFFSET];
|
|
|
|
if (sector >= 0x80) {
|
|
return; /* loop file */
|
|
}
|
|
|
|
while (track != 0) {
|
|
int block_offset = linear_sector(type, track, sector) * BLOCKSIZE;
|
|
int next_track = image[block_offset + TRACKLINKOFFSET];
|
|
int next_sector = image[block_offset + SECTORLINKOFFSET];
|
|
memset(image + block_offset, 0, BLOCKSIZE); /* this also fixes any cyclic t/s chain */
|
|
mark_sector(type, image, track, sector, 1 /* free */);
|
|
track = next_track;
|
|
sector = next_sector;
|
|
}
|
|
}
|
|
|
|
/* Sets image offset to the next DIR entry, returns false when the DIR end was reached */
|
|
static bool
|
|
next_dir_entry(image_type type, const unsigned char* image, int *track, int *sector, int *offset, char *blockmap)
|
|
{
|
|
int b = linear_sector(type, *track, *sector); /* assuming here that the given t/s is valid */
|
|
blockmap[b] = 1;
|
|
if (*offset % BLOCKSIZE == 7 * DIRENTRYSIZE) {
|
|
/* last entry in sector */
|
|
int next_track = image[b * BLOCKSIZE + TRACKLINKOFFSET];
|
|
if (next_track == 0) {
|
|
/* this was the last DIR sector */
|
|
return false;
|
|
}
|
|
int next_sector = image[b * BLOCKSIZE + SECTORLINKOFFSET];
|
|
b = linear_sector(type, next_track, next_sector);
|
|
if(b < 0) {
|
|
dir_error = DIR_ILLEGAL_TS;
|
|
return false;
|
|
}
|
|
if(blockmap[b]) {
|
|
dir_error = DIR_CYCLIC_TS;
|
|
return false;
|
|
}
|
|
*track = next_track;
|
|
*sector = next_sector;
|
|
*offset = 0;
|
|
} else {
|
|
*offset += DIRENTRYSIZE;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Finds all filenames with the given hash */
|
|
static int
|
|
count_hashes(image_type type, const unsigned char* image, unsigned int hash, bool print)
|
|
{
|
|
int num = 0;
|
|
char *blockmap = calloc(image_num_blocks(type), sizeof(char));
|
|
if(blockmap == NULL) {
|
|
fprintf(stderr, "ERROR: Memory allocation error\n");
|
|
exit(-1);
|
|
}
|
|
|
|
int ds = (type == IMAGE_D81) ? 3 : 1;
|
|
int dt = dirtrack(type);
|
|
int offset = 0;
|
|
do {
|
|
int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE + offset;
|
|
int filetype = image[dirblock + FILETYPEOFFSET];
|
|
|
|
if (filetype != FILETYPEDEL) {
|
|
unsigned char *filename = (unsigned char *) image + dirblock + FILENAMEOFFSET;
|
|
if (hash == filenamehash(filename)) {
|
|
++num;
|
|
|
|
if (print) {
|
|
printf(" [$%04x] ", filenamehash(filename));
|
|
print_filename(stdout, filename);
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
} while (next_dir_entry(type, image, &dt, &ds, &offset, blockmap));
|
|
free(blockmap);
|
|
|
|
return num;
|
|
}
|
|
|
|
/* Checks if multiple filenames have the same hash */
|
|
static bool
|
|
check_hashes(image_type type, const unsigned char* image)
|
|
{
|
|
bool collision = false;
|
|
char *blockmap = calloc(image_num_blocks(type), sizeof(char));
|
|
if(blockmap == NULL) {
|
|
fprintf(stderr, "ERROR: Memory allocation error\n");
|
|
exit(-1);
|
|
}
|
|
printf("\n");
|
|
|
|
int ds = (type == IMAGE_D81) ? 3 : 1;
|
|
int dt = dirtrack(type);
|
|
int offset = 0;
|
|
do {
|
|
int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE + offset;
|
|
int filetype = image[dirblock + FILETYPEOFFSET];
|
|
|
|
if (filetype != FILETYPEDEL) {
|
|
unsigned char *filename = (unsigned char *) image + dirblock + FILENAMEOFFSET;
|
|
if (count_hashes(type, image, filenamehash(filename), false /* print */) > 1) {
|
|
collision = 1;
|
|
fprintf(stderr, "Hash of filename ");
|
|
print_filename(stderr, filename);
|
|
fprintf(stderr, " [$%04x] is not unique\n", filenamehash(filename));
|
|
count_hashes(type, image, filenamehash(filename), true /* print */);
|
|
}
|
|
}
|
|
} while (next_dir_entry(type, image, &dt, &ds, &offset, blockmap));
|
|
free(blockmap);
|
|
return collision;
|
|
}
|
|
|
|
/* Searches for an existing DIR entry with the given name, returns false if it does not exist */
|
|
static bool
|
|
find_existing_file(image_type type, unsigned char* image, unsigned char* filename, int *index, int *track, int *sector, int *offset)
|
|
{
|
|
*track = dirtrack(type);
|
|
*sector = (type == IMAGE_D81) ? 3 : 1;
|
|
*offset = 0;
|
|
*index = 0;
|
|
char *blockmap = calloc(image_num_blocks(type), sizeof(char));
|
|
if(blockmap == NULL) {
|
|
fprintf(stderr, "ERROR: Memory allocation error\n");
|
|
exit(-1);
|
|
}
|
|
|
|
do {
|
|
int b = linear_sector(type, *track, *sector) * BLOCKSIZE + *offset;
|
|
int filetype = image[b + FILETYPEOFFSET];
|
|
if(filetype != 0 && memcmp(image + b + FILENAMEOFFSET, filename, FILENAMEMAXSIZE) == 0) {
|
|
return true;
|
|
}
|
|
++(*index);
|
|
} while (next_dir_entry(type, image, track, sector, offset, blockmap));
|
|
|
|
free(blockmap);
|
|
return false;
|
|
}
|
|
|
|
/* Returns an empty DIR slot, allocates a new DIR sector if required */
|
|
static void
|
|
new_dir_slot(image_type type, unsigned char* image, int dir_sector_interleave, int shadowdirtrack, int *index, int *dirsector, int *entry_offset, imagefile files[])
|
|
{
|
|
int track = dirtrack(type);
|
|
*dirsector = (type == IMAGE_D81) ? 3 : 1;
|
|
*entry_offset = 0;
|
|
int lindex = 0;
|
|
|
|
char *blockmap = calloc(image_num_blocks(type), sizeof(char));
|
|
if(blockmap == NULL) {
|
|
fprintf(stderr, "ERROR: Memory allocation error\n");
|
|
exit(-1);
|
|
}
|
|
|
|
do {
|
|
int b = linear_sector(type, track, *dirsector) * BLOCKSIZE + *entry_offset;
|
|
if (image[b + FILETYPEOFFSET] == FILETYPEDEL) {
|
|
/* Check if the apparently free slot is used by a new filetype 0 file */
|
|
bool used = false;
|
|
for(int f = 0; f < num_files; f++) {
|
|
if(files[f].direntryindex == lindex) {
|
|
used = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!used) {
|
|
*index = lindex;
|
|
free(blockmap);
|
|
return; /* found an empty slot */
|
|
}
|
|
}
|
|
++lindex;
|
|
} while (next_dir_entry(type, image, &track, dirsector, entry_offset, blockmap));
|
|
free(blockmap);
|
|
|
|
/* allocate new dir block */
|
|
int last_sector = *dirsector;
|
|
int next_sector = -1;
|
|
for (int s = 1; s < num_sectors(type, dirtrack(type)); s++) {
|
|
int sector = (last_sector + s * dir_sector_interleave) % num_sectors(type, dirtrack(type));
|
|
if (is_sector_free(type, image, dirtrack(type), sector, 0, 0)) {
|
|
next_sector = sector;
|
|
break;
|
|
}
|
|
}
|
|
if (next_sector == -1) {
|
|
fprintf(stderr, "ERROR: Dir track full\n");
|
|
exit(-1);
|
|
}
|
|
int b = linear_sector(type, dirtrack(type), last_sector) * BLOCKSIZE;
|
|
image[b + TRACKLINKOFFSET] = dirtrack(type);
|
|
image[b + SECTORLINKOFFSET] = next_sector;
|
|
|
|
mark_sector(type, image, dirtrack(type), next_sector, 0 /* not free */);
|
|
b = linear_sector(type, dirtrack(type), next_sector) * BLOCKSIZE;
|
|
memset(image + b, 0, BLOCKSIZE);
|
|
image[b + SECTORLINKOFFSET] = 255;
|
|
*dirsector = next_sector;
|
|
*entry_offset = 0;
|
|
|
|
if (shadowdirtrack > 0) {
|
|
b = linear_sector(type, shadowdirtrack, last_sector) * BLOCKSIZE;
|
|
image[b + TRACKLINKOFFSET] = shadowdirtrack;
|
|
image[b + SECTORLINKOFFSET] = next_sector;
|
|
mark_sector(type, image, shadowdirtrack, next_sector, 0 /* not free */);
|
|
|
|
b = linear_sector(type, shadowdirtrack, next_sector) * BLOCKSIZE;
|
|
memset(image + b, 0, BLOCKSIZE);
|
|
image[b + SECTORLINKOFFSET] = 255;
|
|
}
|
|
*index = lindex;
|
|
}
|
|
|
|
/* Returns suitable index and offset for given filename (either existing slot when overwriting, first free slot or slot in newly allocated segment) */
|
|
static bool
|
|
find_dir_slot(image_type type, unsigned char* image, unsigned char* filename, int dir_sector_interleave, int shadowdirtrack, int *index, int *dirsector, int *entry_offset, imagefile files[])
|
|
{
|
|
int track;
|
|
if(find_existing_file(type, image, filename, index, &track, dirsector, entry_offset)) {
|
|
return true;
|
|
}
|
|
new_dir_slot(type, image, dir_sector_interleave, shadowdirtrack, index, dirsector, entry_offset, files);
|
|
return false;
|
|
}
|
|
|
|
/* Adds the specified new entries to the directory */
|
|
static void
|
|
create_dir_entries(image_type type, unsigned char* image, imagefile* files, int num_files, int dir_sector_interleave, unsigned int shadowdirtrack, int nooverwrite)
|
|
{
|
|
/* this does not check for uniqueness of filenames */
|
|
|
|
int num_overwritten_files = 0;
|
|
|
|
if (verbose && num_files > 0) {
|
|
printf("\nCreating dir entries:\n");
|
|
}
|
|
|
|
for (int i = 0; i < num_files; i++) {
|
|
/* find or create slot */
|
|
imagefile *file = files + i;
|
|
|
|
if (verbose) {
|
|
printf(" ");
|
|
print_dirfilename(file->pfilename);
|
|
}
|
|
|
|
if(file->force_new) {
|
|
new_dir_slot(type, image, dir_sector_interleave, shadowdirtrack, &file->direntryindex, &file->direntrysector, &file->direntryoffset, files);
|
|
} else if (find_dir_slot(type, image, file->pfilename, dir_sector_interleave, shadowdirtrack, &file->direntryindex, &file->direntrysector, &file->direntryoffset, files)) {
|
|
if (nooverwrite) {
|
|
fprintf(stderr, "ERROR: Filename exists on disk image already and -o was set\n");
|
|
exit(-1);
|
|
}
|
|
|
|
wipe_file(type, image, file);
|
|
num_overwritten_files++;
|
|
}
|
|
|
|
int file_entry_offset = linear_sector(type, dirtrack(type), file->direntrysector) * BLOCKSIZE + file->direntryoffset;
|
|
image[file_entry_offset + FILETYPEOFFSET] = file->filetype & 0xff;
|
|
if (verbose && (file->filetype & FILETYPETRANSWARPMASK)) {
|
|
printf(" [Transwarp]");
|
|
}
|
|
memcpy(image + file_entry_offset + FILENAMEOFFSET, file->pfilename, FILENAMEMAXSIZE);
|
|
|
|
if (is_transwarp_bootfile(image, file_entry_offset)) {
|
|
if (file->filetype & FILETYPETRANSWARPMASK) {
|
|
if (verbose) {
|
|
printf("\n");
|
|
}
|
|
|
|
fprintf(stderr, "ERROR: Attempt to write Transwarp bootfile as Transwarp file\n");
|
|
|
|
exit(-1);
|
|
}
|
|
|
|
file->mode |= MODE_TRANSWARPBOOTFILE;
|
|
if (verbose) {
|
|
printf(" [Transwarp bootfile]");
|
|
}
|
|
|
|
if (i != 0) {
|
|
// allocate Transwarp bootfile first
|
|
if ((files[0].mode & MODE_TRANSWARPBOOTFILE) != 0) {
|
|
if (verbose) {
|
|
printf("\n");
|
|
}
|
|
|
|
fprintf(stderr, "ERROR: Multiple Transwarp bootfiles\n");
|
|
exit(-1);
|
|
}
|
|
|
|
imagefile transwarp_bootfile = *file;
|
|
for (int j = i; j > 0; --j) {
|
|
files[j] = files[j - 1];
|
|
}
|
|
files[0] = transwarp_bootfile;
|
|
file = files;
|
|
}
|
|
}
|
|
|
|
if (shadowdirtrack > 0) {
|
|
file_entry_offset = linear_sector(type, shadowdirtrack, file->direntrysector) * BLOCKSIZE + file->direntryoffset;
|
|
image[file_entry_offset + FILETYPEOFFSET] = file->filetype;
|
|
memcpy(image + file_entry_offset + FILENAMEOFFSET, file->pfilename, FILENAMEMAXSIZE);
|
|
}
|
|
|
|
if (verbose) {
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
if (!quiet && (num_overwritten_files > 0)) {
|
|
printf("%d out of %d files exist and will be overwritten\n", num_overwritten_files, num_files);
|
|
}
|
|
}
|
|
|
|
/* Prints the allocated tracks and sectors for every file */
|
|
static void
|
|
print_file_allocation(image_type type, const unsigned char* image, imagefile* files, int num_files)
|
|
{
|
|
if (num_files <= 0) {
|
|
imagefile existing_files[144];
|
|
memset(existing_files, 0, sizeof existing_files);
|
|
|
|
int t = dirtrack(type);
|
|
int s = (type == IMAGE_D81) ? 3 : 1;
|
|
int o = 0;
|
|
char *blockmap = calloc(image_num_blocks(type), sizeof(char));
|
|
if(blockmap == NULL) {
|
|
fprintf(stderr, "ERROR: Memory allocation error\n");
|
|
exit(-1);
|
|
}
|
|
|
|
do {
|
|
int b = linear_sector(type, t, s) * BLOCKSIZE + o;
|
|
int filetype = image[b + FILETYPEOFFSET] & 0xf;
|
|
switch (filetype) {
|
|
case FILETYPEPRG: {
|
|
int track = image[b + FILETRACKOFFSET];
|
|
int sector = image[b + FILESECTOROFFSET];
|
|
const unsigned char *filename = image + b + FILENAMEOFFSET;
|
|
b = linear_sector(type, track, sector);
|
|
if (b >= 0) {
|
|
memcpy(existing_files[num_files].pfilename, filename, FILENAMEMAXSIZE);
|
|
existing_files[num_files].track = track;
|
|
existing_files[num_files].sector = sector;
|
|
existing_files[num_files].direntryindex = num_files;
|
|
existing_files[num_files].direntrysector = s;
|
|
existing_files[num_files].direntryoffset = o;
|
|
existing_files[num_files].nrSectors = image[b + FILEBLOCKSLOOFFSET] + 256 * image[b + FILEBLOCKSHIOFFSET];
|
|
|
|
if (is_transwarp_file(image, b)) {
|
|
existing_files[num_files].filetype = filetype | FILETYPETRANSWARPMASK;
|
|
existing_files[num_files].sectorInterleave = 1;
|
|
|
|
int start_track;
|
|
int end_track;
|
|
int low_track;
|
|
int high_track;
|
|
int filesize = transwarp_stat(type, image, b, &start_track, &end_track, &low_track, &high_track);
|
|
|
|
existing_files[num_files].size = filesize;
|
|
|
|
existing_files[num_files].track = start_track;
|
|
existing_files[num_files].last_track = end_track;
|
|
|
|
++num_files;
|
|
|
|
break;
|
|
}
|
|
|
|
while (true) {
|
|
b = linear_sector(type, track, sector);
|
|
if(b < 0) {
|
|
break; // TODO: print info about illegal t/s
|
|
}
|
|
int offset = b * BLOCKSIZE;
|
|
int next_track = image[offset + 0];
|
|
int next_sector = image[offset + 1];
|
|
if ((track == 0) || (next_track == 0)) {
|
|
break;
|
|
}
|
|
if ((track == next_track) && (next_sector > sector)) {
|
|
existing_files[num_files].sectorInterleave = next_sector - sector;
|
|
break;
|
|
}
|
|
track = next_track;
|
|
sector = next_sector;
|
|
}
|
|
|
|
++num_files;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
} while (next_dir_entry(type, image, &t, &s, &o, blockmap));
|
|
free(blockmap);
|
|
files = existing_files;
|
|
}
|
|
|
|
if(num_files > 0) {
|
|
printf("\nFile allocation:\n");
|
|
}
|
|
|
|
for (int i = 0; i < num_files; i++) {
|
|
printf("%02d/%02d %3d (0x%02x 0x%02x:0x%02x) ", files[i].track, files[i].sector, files[i].nrSectors & 0xffff,
|
|
files[i].direntryindex, files[i].direntrysector, files[i].direntryoffset);
|
|
if (files[i].alocalname) {
|
|
printf("\"%s\" => ", files[i].alocalname);
|
|
}
|
|
print_filename(stdout, files[i].pfilename);
|
|
printf(" (SL: %d)", files[i].sectorInterleave);
|
|
|
|
if ((files[i].mode & MODE_LOOPFILE) && (files[i].sectorInterleave != 0)) {
|
|
|
|
continue;
|
|
}
|
|
|
|
if (files[i].filetype & FILETYPETRANSWARPMASK) {
|
|
int transwarp_blocks;
|
|
int nonredundant_blocks;
|
|
int redundant_blocks;
|
|
int nonredundant_blocks_on_last_track;
|
|
int spare_bytes = transwarp_size(type, files[i].track, files[i].last_track, files[i].size, &transwarp_blocks, &nonredundant_blocks, &redundant_blocks, &nonredundant_blocks_on_last_track);
|
|
|
|
int num_blocks = 0;
|
|
int filesize = files[i].size + 2;
|
|
while (filesize > 0) {
|
|
++num_blocks;
|
|
filesize -= 254;
|
|
}
|
|
|
|
printf("\n Transwarp: %d total/%d actual (%d standard) blocks, tracks %d-%d, %d used and %d redundant block%s on last track, 0x%x spare bytes in last block, size 0x%x\n",
|
|
transwarp_blocks, nonredundant_blocks, num_blocks,
|
|
(files[i].track < files[i].last_track) ? files[i].track : files[i].last_track,
|
|
(files[i].track >= files[i].last_track) ? files[i].track : files[i].last_track,
|
|
nonredundant_blocks_on_last_track, redundant_blocks, (redundant_blocks == 1) ? "" : "s",
|
|
spare_bytes, files[i].size);
|
|
|
|
continue;
|
|
}
|
|
|
|
int track = files[i].track;
|
|
int sector = files[i].sector;
|
|
|
|
bool firsttrack = true;
|
|
int firstsector = sector;
|
|
bool fileblocks[SECTORSPERTRACK_D81];
|
|
memset(fileblocks, 0, sizeof fileblocks);
|
|
fileblocks[sector] = true;
|
|
|
|
int j = 0;
|
|
while (track != 0) {
|
|
if (j == 0) {
|
|
printf("\n ");
|
|
}
|
|
printf("%02d/%02d", track, sector);
|
|
int b = linear_sector(type, track, sector);
|
|
if(b < 0) {
|
|
break; // TODO: print info about illegal t/s link?
|
|
}
|
|
int offset = b * BLOCKSIZE;
|
|
int next_track = image[offset + 0];
|
|
int next_sector = image[offset + 1];
|
|
if ((track != next_track) && (next_track != 0)) {
|
|
/* track change */
|
|
if (next_sector != 0) {
|
|
/* interleave violation */
|
|
printf("!-");
|
|
} else {
|
|
printf(" -");
|
|
}
|
|
} else if ((next_sector < sector) && (next_track != 0)) {
|
|
/* sector wrap */
|
|
int expected_next_sector = ((sector + abs(files[i].sectorInterleave)) % num_sectors(type, track));
|
|
bool on_nonempty_firsttrack = (expected_next_sector < next_sector) && firsttrack && (firstsector != 0);
|
|
if ((expected_next_sector != next_sector) && (!on_nonempty_firsttrack)) {
|
|
while ((expected_next_sector < next_sector) && fileblocks[expected_next_sector]) {
|
|
++expected_next_sector;
|
|
}
|
|
if (expected_next_sector != next_sector) {
|
|
/* interleave violation */
|
|
printf("!.");
|
|
} else {
|
|
printf(" .");
|
|
}
|
|
} else {
|
|
printf(" .");
|
|
}
|
|
} else if (((next_sector - sector) != abs(files[i].sectorInterleave)) && (next_track != 0)) {
|
|
/* interleave violation */
|
|
printf(" !");
|
|
} else {
|
|
printf(" ");
|
|
}
|
|
|
|
if (track != next_track) {
|
|
memset(fileblocks, 0, sizeof fileblocks);
|
|
firsttrack = false;
|
|
}
|
|
track = next_track;
|
|
sector = next_sector;
|
|
if (next_track != 0) {
|
|
fileblocks[sector] = true;
|
|
}
|
|
|
|
j++;
|
|
if (j == 10) {
|
|
j = 0;
|
|
}
|
|
}
|
|
printf("\n");
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
|
|
static void
|
|
assign_blocktags(image_type type, const unsigned char *image, int(*blocktags)[SECTORSPERTRACK_D81])
|
|
{
|
|
char c = '@';
|
|
|
|
char *blockmap = calloc(image_num_blocks(type), sizeof(char));
|
|
if(blockmap == NULL) {
|
|
fprintf(stderr, "ERROR: Memory allocation error\n");
|
|
exit(-1);
|
|
}
|
|
int ds = (type == IMAGE_D81) ? 3 : 1;
|
|
int dt = dirtrack(type);
|
|
int offset = 0;
|
|
do {
|
|
int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE + offset;
|
|
int filetype = image[dirblock + FILETYPEOFFSET] & 0xf;
|
|
if (filetype != 0) {
|
|
int filetrack = image[dirblock + FILETRACKOFFSET];
|
|
int filesector = image[dirblock + FILESECTOROFFSET];
|
|
|
|
if (is_transwarp_file(image, dirblock)) {
|
|
int start_track;
|
|
int end_track;
|
|
int low_track;
|
|
int high_track;
|
|
int filesize = transwarp_stat(type, image, dirblock, &start_track, &end_track, &low_track, &high_track);
|
|
if (filesize <= 0) {
|
|
continue;
|
|
}
|
|
|
|
int transwarp_blocks;
|
|
int nonredundant_blocks;
|
|
int redundant_blocks;
|
|
int nonredundant_blocks_on_last_track;
|
|
transwarp_size(type, start_track, end_track, filesize, &transwarp_blocks, &nonredundant_blocks, &redundant_blocks, &nonredundant_blocks_on_last_track);
|
|
|
|
for (int track = low_track; track <= high_track; ++track) {
|
|
for (int sector = 0; sector < SECTORSPERTRACK_D81; ++sector) {
|
|
blocktags[track][sector] = c + (((track == end_track) && (sector >= nonredundant_blocks_on_last_track)) ? 256 : 0);
|
|
}
|
|
}
|
|
} else {
|
|
bool new_track = true;
|
|
while (filetrack != 0) {
|
|
int b = linear_sector(type, filetrack, filesector);
|
|
if(b < 0) {
|
|
break;
|
|
}
|
|
int block_offset = b * BLOCKSIZE;
|
|
int next_track = image[block_offset + TRACKLINKOFFSET];
|
|
int next_sector = image[block_offset + SECTORLINKOFFSET];
|
|
|
|
blocktags[filetrack][filesector] = c + (new_track ? 256 : 0);
|
|
new_track = (filetrack != next_track);
|
|
|
|
filetrack = next_track;
|
|
filesector = next_sector;
|
|
}
|
|
}
|
|
|
|
switch (c) {
|
|
default:
|
|
++c;
|
|
break;
|
|
case 'Z':
|
|
c = '0';
|
|
break;
|
|
case '9':
|
|
c = 'a';
|
|
break;
|
|
case 'z':
|
|
c = '@';
|
|
break;
|
|
}
|
|
}
|
|
|
|
} while (next_dir_entry(type, image, &dt, &ds, &offset, blockmap));
|
|
free(blockmap);
|
|
}
|
|
|
|
/* Returns true if the file starting on the given filetrack and filesector uses the given track */
|
|
static bool
|
|
fileontrack(image_type type, const unsigned char *image, int track, int filetrack, int filesector)
|
|
{
|
|
while (filetrack != 0) {
|
|
if (filetrack == track) {
|
|
return true;
|
|
}
|
|
|
|
int b = linear_sector(type, filetrack, filesector);
|
|
if(b < 0) {
|
|
return false;
|
|
}
|
|
int block_offset = b * BLOCKSIZE;
|
|
int next_track = image[block_offset + TRACKLINKOFFSET];
|
|
int next_sector = image[block_offset + SECTORLINKOFFSET];
|
|
filetrack = next_track;
|
|
filesector = next_sector;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Prints all filenames of files that use the given track */
|
|
static void
|
|
print_track_usage(image_type type, const unsigned char *image, int(*blocktags)[SECTORSPERTRACK_D81], int track)
|
|
{
|
|
|
|
char *blockmap = calloc(image_num_blocks(type), sizeof(char));
|
|
if(blockmap == NULL) {
|
|
fprintf(stderr, "ERROR: Memory allocation error\n");
|
|
exit(-1);
|
|
}
|
|
int ds = (type == IMAGE_D81) ? 3 : 1;
|
|
int dt = dirtrack(type);
|
|
int offset = 0;
|
|
do {
|
|
int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE + offset;
|
|
int filetype = image[dirblock + FILETYPEOFFSET] & 0xf;
|
|
if (filetype != 0) {
|
|
int filetrack = image[dirblock + FILETRACKOFFSET];
|
|
int filesector = image[dirblock + FILESECTOROFFSET];
|
|
bool ontrack = (type == IMAGE_D71) ? fileontrack(type, image, track, (filetrack > D64NUMTRACKS) ? filetrack - D64NUMTRACKS : filetrack + D64NUMTRACKS, filesector) : false;
|
|
|
|
if (is_transwarp_file(image, dirblock)) {
|
|
int start_track;
|
|
int end_track;
|
|
int low_track;
|
|
int high_track;
|
|
int filesize = transwarp_stat(type, image, dirblock, &start_track, &end_track, &low_track, &high_track);
|
|
if (filesize <= 0) {
|
|
continue;
|
|
}
|
|
|
|
ontrack = (low_track <= track) && (track <= high_track);
|
|
if (ontrack == false) {
|
|
continue;
|
|
}
|
|
|
|
filetrack = start_track;
|
|
}
|
|
|
|
if (ontrack || fileontrack(type, image, track, filetrack, filesector)) {
|
|
unsigned char *filename = (unsigned char *) image + dirblock + FILENAMEOFFSET;
|
|
if (track == filetrack) {
|
|
reverse_print_on();
|
|
}
|
|
printf("%c", blocktags[filetrack][filesector]);
|
|
if (track == filetrack) {
|
|
reverse_print_off();
|
|
}
|
|
printf(": ");
|
|
print_filename(stdout, filename);
|
|
printf(" ");
|
|
}
|
|
}
|
|
} while (next_dir_entry(type, image, &dt, &ds, &offset, blockmap));
|
|
free(blockmap);
|
|
}
|
|
|
|
/* Prints the BAM allocation map and returns the number of free blocks */
|
|
static int
|
|
check_bam(image_type type, const unsigned char* image)
|
|
{
|
|
int sectorsFree = 0;
|
|
int sectorsFreeOnDirTrack = 0;
|
|
int sectorsOccupied = 0;
|
|
int sectorsOccupiedOnDirTrack = 0;
|
|
|
|
int blocktags[D81NUMTRACKS][SECTORSPERTRACK_D81];
|
|
|
|
if (verbose) {
|
|
memset(blocktags, 0, sizeof blocktags);
|
|
assign_blocktags(type, image, blocktags);
|
|
|
|
printf("Block allocation:\n");
|
|
}
|
|
|
|
int max_track = (type == IMAGE_D81) ? D81NUMTRACKS
|
|
: (((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) ? D64NUMTRACKS_EXTENDED
|
|
: D64NUMTRACKS);
|
|
for (int t = 1; t <= max_track; t++) {
|
|
|
|
if (verbose) {
|
|
printf(" %2d: ", t);
|
|
}
|
|
for (int s = 0; s < num_sectors(type, t); s++) {
|
|
if (is_sector_free(type, image, t, s, 0, 0)) {
|
|
if (verbose) {
|
|
printf(".");
|
|
}
|
|
if (t != dirtrack(type)) {
|
|
sectorsFree++;
|
|
} else {
|
|
sectorsFreeOnDirTrack++;
|
|
}
|
|
} else {
|
|
if (verbose) {
|
|
int blocktag = blocktags[t][s];
|
|
if (blocktag == 0) {
|
|
blocktag = '#';
|
|
}
|
|
if (blocktag >= 256) {
|
|
reverse_print_on();
|
|
}
|
|
printf("%c", blocktag);
|
|
if (blocktag >= 256) {
|
|
reverse_print_off();
|
|
}
|
|
}
|
|
if (t != dirtrack(type)) {
|
|
sectorsOccupied++;
|
|
} else {
|
|
sectorsOccupiedOnDirTrack++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type == IMAGE_D71) {
|
|
for (int i = num_sectors(type, t); i < 23; i++) {
|
|
if (verbose) {
|
|
printf(" ");
|
|
}
|
|
}
|
|
int t2 = t + D64NUMTRACKS;
|
|
|
|
if (verbose) {
|
|
printf("%2d: ", t2);
|
|
}
|
|
for (int s = 0; s < num_sectors(type, t2); s++) {
|
|
if (is_sector_free(type, image, t2, s, 0, 0)) {
|
|
if (verbose) {
|
|
printf(".");
|
|
}
|
|
if (t2 != dirtrack(type)) {
|
|
sectorsFree++;
|
|
} else {
|
|
/* track 53 is usually empty except the extra BAM block */
|
|
sectorsFreeOnDirTrack++;
|
|
}
|
|
} else {
|
|
if (verbose) {
|
|
printf("#");
|
|
}
|
|
sectorsOccupied++;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = ((type == IMAGE_D81) ? 42 : 23) - num_sectors(type, t); i > 0; --i) {
|
|
if (verbose) {
|
|
printf(" ");
|
|
}
|
|
}
|
|
if (verbose) {
|
|
print_track_usage(type, image, blocktags, t);
|
|
printf("\n");
|
|
}
|
|
}
|
|
if (verbose) {
|
|
printf("%3d/%3d blocks free (%d/%d including dir track)\n", sectorsFree, sectorsFree + sectorsOccupied,
|
|
sectorsFree + sectorsFreeOnDirTrack, sectorsFree + sectorsFreeOnDirTrack + sectorsOccupied + sectorsOccupiedOnDirTrack);
|
|
}
|
|
|
|
return sectorsFree;
|
|
}
|
|
|
|
/* Prints the filetype like the C64 when listing the directory */
|
|
static void
|
|
print_filetype(int filetype)
|
|
{
|
|
if ((filetype & 0x80) == 0) {
|
|
printf("*");
|
|
} else {
|
|
printf(" ");
|
|
}
|
|
if(unicode == 1) {
|
|
printf("%s", filetypename_uc[filetype & 0xf]);
|
|
} else {
|
|
printf("%s", filetypename_lc[filetype & 0xf]);
|
|
}
|
|
if ((filetype & 0x40) != 0) {
|
|
printf("<");
|
|
} else {
|
|
printf(" ");
|
|
}
|
|
}
|
|
|
|
/* Prints the directory like the C64 when listing the directory */
|
|
static void
|
|
print_directory(image_type type, unsigned char* image, int blocks_free)
|
|
{
|
|
unsigned char* bam = image + linear_sector(type, dirtrack(type), 0) * BLOCKSIZE;
|
|
char *blockmap = calloc(image_num_blocks(type), sizeof(char));
|
|
if(blockmap == NULL) {
|
|
fprintf(stderr, "ERROR: Memory allocation error\n");
|
|
exit(-1);
|
|
}
|
|
|
|
printf("\n0 ");
|
|
reverse_print_on();
|
|
printf("\"");
|
|
print_petscii(bam + get_header_offset(type), 16);
|
|
printf("\" ");
|
|
print_petscii(bam + get_id_offset(type), 5);
|
|
reverse_print_off();
|
|
|
|
if (verbose) {
|
|
printf(" fn hash");
|
|
}
|
|
printf("\n");
|
|
|
|
int ds = (type == IMAGE_D81) ? 3 : 1;
|
|
int dt = dirtrack(type);
|
|
int offset = 0;
|
|
do {
|
|
int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE + offset;
|
|
int filetype = image[dirblock + FILETYPEOFFSET];
|
|
int blocks = image[dirblock + FILEBLOCKSLOOFFSET] + 256 * image[dirblock + FILEBLOCKSHIOFFSET];
|
|
|
|
if (filetype != FILETYPEDEL) {
|
|
unsigned char* filename = (unsigned char*)image + dirblock + FILENAMEOFFSET;
|
|
printf("%-3d ", blocks);
|
|
print_dirfilename(filename);
|
|
print_filetype(filetype);
|
|
if (verbose) {
|
|
printf(" [$%04x]", filenamehash(filename));
|
|
}
|
|
printf("\n");
|
|
}
|
|
} while (next_dir_entry(type, image, &dt, &ds, &offset, blockmap));
|
|
free(blockmap);
|
|
if(unicode == 1) {
|
|
printf("%d BLOCKS FREE.\n", blocks_free);
|
|
} else {
|
|
printf("%d blocks free.\n", blocks_free);
|
|
}
|
|
|
|
/* detect and print bam message */
|
|
if((type == IMAGE_D64 || type == IMAGE_D64_EXTENDED_SPEED_DOS) && bam[BAMMESSAGEOFFSET] != 0) {
|
|
printf("\nBAM message: \"");
|
|
/* only checking the first byte and hoping for the best... */
|
|
int b = BAMMESSAGEOFFSET;
|
|
unsigned char c;
|
|
while(b < 256 && ((c = bam[b]) != 0)) {
|
|
putp(c, stdout);
|
|
b++;
|
|
}
|
|
printf("\"\n");
|
|
}
|
|
}
|
|
|
|
/* Performs GCR encoding on 32 bit value */
|
|
|
|
static const unsigned char NIBBLE_TO_GCR[] = {
|
|
0x0a, 0x0b, 0x12, 0x13,
|
|
0x0e, 0x0f, 0x16, 0x17,
|
|
0x09, 0x19, 0x1a, 0x1b,
|
|
0x0d, 0x1d, 0x1e, 0x15
|
|
};
|
|
|
|
static void
|
|
encode_4_bytes_gcr(char* in, char* out)
|
|
{
|
|
out[0] = (NIBBLE_TO_GCR[(in[0] >> 4) & 0xf] << 3) | (NIBBLE_TO_GCR[ in[0] & 0xf] >> 2); /* 11111222 */
|
|
out[1] = (NIBBLE_TO_GCR[ in[0] & 0xf] << 6) | (NIBBLE_TO_GCR[(in[1] >> 4) & 0xf] << 1) | (NIBBLE_TO_GCR[ in[1] & 0xf] >> 4); /* 22333334 */
|
|
out[2] = (NIBBLE_TO_GCR[ in[1] & 0xf] << 4) | (NIBBLE_TO_GCR[(in[2] >> 4) & 0xf] >> 1); /* 44445555 */
|
|
out[3] = (NIBBLE_TO_GCR[(in[2] >> 4) & 0xf] << 7) | (NIBBLE_TO_GCR[ in[2] & 0xf] << 2) | (NIBBLE_TO_GCR[(in[3] >> 4) & 0xf] >> 3); /* 56666677 */
|
|
out[4] = (NIBBLE_TO_GCR[(in[3] >> 4) & 0xf] << 5) | NIBBLE_TO_GCR[ in[3] & 0xf]; /* 77788888 */
|
|
}
|
|
|
|
/* Transwarp encoding utility functions */
|
|
|
|
static void
|
|
generate_gcr_decoding_table(const unsigned char nibble_to_gcr[], int8_t gcr_to_nibble[])
|
|
{
|
|
for (int i = 0; i < 32; ++i) {
|
|
gcr_to_nibble[i] = -(i + 1);
|
|
}
|
|
|
|
for (int i = 0; i < 16; ++i) {
|
|
gcr_to_nibble[nibble_to_gcr[i]] = i;
|
|
}
|
|
}
|
|
|
|
static unsigned char
|
|
even_bits(unsigned char value)
|
|
{
|
|
return (((value >> 6) & 1) << 3)
|
|
| (((value >> 4) & 1) << 2)
|
|
| (((value >> 2) & 1) << 1)
|
|
| (((value >> 0) & 1) << 0);
|
|
}
|
|
|
|
static unsigned char
|
|
odd_bits(unsigned char value)
|
|
{
|
|
return (((value >> 7) & 1) << 3)
|
|
| (((value >> 5) & 1) << 2)
|
|
| (((value >> 3) & 1) << 1)
|
|
| (((value >> 1) & 1) << 0);
|
|
}
|
|
|
|
typedef struct transwarp_encode_context {
|
|
unsigned int version;
|
|
unsigned char previous;
|
|
unsigned char previous1;
|
|
unsigned char previous2;
|
|
unsigned char accu;
|
|
unsigned char carry;
|
|
unsigned char recvcarry;
|
|
unsigned char carry2;
|
|
unsigned char sendaccu;
|
|
unsigned char sendcarry;
|
|
} transwarp_encode_context;
|
|
|
|
static const int ENCODE[5][64] = {
|
|
{
|
|
0xf6, 0xee, 0xf5, 0xed, 0x9a, 0xde, 0x96, 0xda, 0xf3, 0xea, 0xf2, 0x9e, 0x93, 0xd6, 0x92, 0xd3,
|
|
0xd2, 0xca, 0xce, 0xbe, 0xb3, 0x7e, 0xb2, 0x7d, 0xcd, 0xba, 0xcb, 0xb6, 0xae, 0x7b, 0xaa, 0x7a,
|
|
0x76, 0x6e, 0x75, 0x6d, 0x5e, 0x5b, 0x5d, 0x5a, 0x73, 0x6b, 0x72, 0x6a, 0xdb, 0x9d, 0xeb, 0xd5,
|
|
0x56, 0x4e, 0x55, 0x4d, 0xbd, 0xb5, 0xbb, 0xad, 0x53, 0x4b, 0x52, 0xdd, 0xab, 0x4a, 0x9b, 0x95
|
|
}, {
|
|
0xf6, 0xee, 0xf5, 0xed, 0x9a, 0xde, 0x96, 0xda, 0xf3, 0xea, 0xf2, 0x9e, 0x93, 0xd6, 0x92, 0xef,
|
|
0xe7, 0x7c, 0x9f, 0x74, 0xe6, 0x6c, 0xdf, 0xa6, 0x9c, 0xba, 0x94, 0xb6, 0xae, 0x7b, 0xaa, 0x7a,
|
|
0x76, 0x6e, 0x75, 0x6d, 0x5e, 0x5b, 0x5d, 0x5a, 0x73, 0x6b, 0x72, 0x6a, 0xdb, 0xd7, 0xeb, 0xe5,
|
|
0x56, 0x64, 0x55, 0x5c, 0xbd, 0xb5, 0xbb, 0xad, 0x65, 0x54, 0x5f, 0xdd, 0xab, 0xa7, 0x9b, 0xa5
|
|
}, {
|
|
0xa5, 0xa7, 0xa9, 0xab, 0xd5, 0xd7, 0xd9, 0xdb, 0x95, 0x99, 0x9b, 0x97, 0xe5, 0x9d, 0xeb, 0xe9
|
|
}, {
|
|
0xf6, 0xee, 0xf5, 0xed, 0x69, 0xde, 0x59, 0xda, 0xb9, 0xea, 0xb7, 0x6f, 0x57, 0xd6, 0x4f, 0xef,
|
|
0xe7, 0xca, 0xce, 0xbe, 0xe6, 0xbf, 0xdf, 0xa6, 0xcd, 0xba, 0xcb, 0xb6, 0xae, 0x7b, 0xaa, 0x7a,
|
|
0x76, 0x6e, 0x75, 0x6d, 0x5e, 0x5b, 0x5d, 0x5a, 0x67, 0x6b, 0x66, 0x6a, 0xe9, 0xd7, 0xeb, 0xe5,
|
|
0x56, 0x4e, 0x55, 0x4d, 0xbd, 0xb5, 0xbb, 0xad, 0x65, 0x4b, 0x5f, 0xdd, 0xab, 0xa7, 0xa9, 0xa5
|
|
}, {
|
|
0xcf, 0xaf, 0xc9, 0x79, 0x69, 0xde, 0x59, 0xda, 0xb9, 0x77, 0xb7, 0x6f, 0x57, 0xd6, 0x4f, 0xd3,
|
|
0xd2, 0xca, 0xce, 0xbe, 0xb3, 0x7e, 0xb2, 0x7d, 0xcd, 0xba, 0xcb, 0xb6, 0xae, 0x7b, 0xaa, 0x7a,
|
|
0x76, 0x6e, 0x75, 0x6d, 0x5e, 0x5b, 0x5d, 0x5a, 0x73, 0x6b, 0x72, 0x6a, 0xdb, 0xd7, 0xd9, 0xd5,
|
|
0x56, 0x4e, 0x55, 0x4d, 0xbd, 0xb5, 0xbb, 0xad, 0x53, 0x4b, 0x52, 0xdd, 0xab, 0x4a, 0xa9, 0x49
|
|
}
|
|
};
|
|
|
|
static const int DECODE[256] = {
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, 0x00, 0x02, 0x12, -1, 0x14, 0x16, 0x68,
|
|
-1, -1, 0x18, 0x1a, 0x12, 0x1c, 0x1e, 0x6a, -1, 0x6c, 0x24, 0x26, 0x14, 0x2c, 0x2e, 0x18,
|
|
-1, -1, -1, -1, 0x16, 0x1a, 0x38, 0x3a, -1, 0x6e, 0x30, 0x32, 0x46, 0x34, 0x36, 0x70,
|
|
-1, -1, 0x38, 0x3a, 0x54, 0x3c, 0x3e, 0x72, -1, 0x74, 0x40, 0x42, 0x56, 0x44, 0x46, -1,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
|
-1, -1, 0x68, 0x6a, 0x58, 0x80, 0x6c, 0x8a, -1, 0x82, 0x6e, 0x88, 0x5a, 0xa2, 0x70, 0x5c,
|
|
-1, -1, -1, -1, -1, 0x00, 0x44, 0x02, -1, 0x08, 0x48, 0x0a, -1, 0x04, 0x4a, 0x76,
|
|
-1, -1, 0x4c, 0x4e, -1, 0x06, 0x50, 0x78, -1, 0x7a, 0x52, 0x0c, -1, 0x0e, 0x54, 0x46,
|
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, 0x7c, 0x56, 0x58, -1, 0x5a, 0x5c, 0x7e,
|
|
-1, -1, 0x5e, 0x60, -1, 0x20, 0x62, 0x22, -1, 0x28, 0x64, 0x2a, -1, 0x10, 0x66, 0x4c,
|
|
-1, -1, -1, -1, -1, 0xa0, 0x4e, 0x5e, -1, 0xaa, 0x72, 0xa8, -1, 0x74, 0x76, 0x60,
|
|
-1, -1, 0x78, 0x7a, -1, 0x7c, 0x7e, -1, -1, -1, -1, -1, -1, -1, -1, -1
|
|
};
|
|
|
|
static unsigned char
|
|
encode_read_diff(const int encode[64], unsigned char *accu, unsigned char *carry, unsigned char value)
|
|
{
|
|
unsigned char value_to_encode;
|
|
|
|
unsigned char target = DECODE[encode[value]] & 0x7e;
|
|
for (value_to_encode = 0; value_to_encode < 64; ++value_to_encode) {
|
|
unsigned char val = DECODE[encode[value_to_encode]] + *accu + *carry;
|
|
if ((val & 0x7e) == target) {
|
|
break;
|
|
}
|
|
}
|
|
if (value_to_encode >= 64) {
|
|
printf("Encoding error, 0x%x = 0x%x + %d + ?\n", target, *accu, *carry);
|
|
for (value_to_encode = 0; value_to_encode < 64; ++value_to_encode) {
|
|
unsigned char val = DECODE[encode[value_to_encode]] + *accu + *carry;
|
|
printf("%d: 0x%x <- 0x%x\n", value_to_encode, val & 0x7e, DECODE[encode[value_to_encode]]);
|
|
}
|
|
|
|
fprintf(stderr, "ERROR: Transwarp encoding error\n");
|
|
exit(-2);
|
|
}
|
|
|
|
int sum = DECODE[encode[value_to_encode]] + *accu + *carry;
|
|
*accu = sum;
|
|
*carry = sum >= 256;
|
|
|
|
unsigned char check = DECODE[encode[value]];
|
|
if ((*accu & 0x7e) != (check & 0x7e)) {
|
|
fprintf(stderr, "ERROR: Transwarp encoding error, 0x%x: actual 0x%x != 0x%x expected\n", DECODE[encode[check & 0x3f]], *accu, check);
|
|
exit(-3);
|
|
}
|
|
|
|
unsigned char temp = (*carry << 7) | (*accu >> 1);
|
|
*carry = *accu & 1;
|
|
*accu = (*carry << 7) | ((temp & 0xfb) >> 1);
|
|
*carry = (*accu >> 6) & 1;
|
|
|
|
return encode[value_to_encode];
|
|
}
|
|
|
|
unsigned char
|
|
encode_send_diff(unsigned char value, unsigned char *accu, unsigned char *carry)
|
|
{
|
|
value = (value & ~((1 << 3) | (1 << 0)))
|
|
| (((value >> 3) & 1) << 0)
|
|
| (((value >> 0) & 1) << 3);
|
|
value ^= 0xff;
|
|
|
|
int diff = value - *accu - *carry;
|
|
*carry = (diff < 0);
|
|
|
|
*accu = value;
|
|
|
|
*accu = (((*accu >> 7) & 1) << 7)
|
|
| (((*accu >> 1) & 1) << 6)
|
|
| (((*accu >> 7) & 1) << 5)
|
|
| (*carry << 4)
|
|
| (((*accu >> 6) & 1) << 2)
|
|
| (((*accu >> 5) & 1) << 1);
|
|
*carry = (*accu >> 6) & 1;
|
|
|
|
return diff;
|
|
}
|
|
|
|
static unsigned char
|
|
encode_receive_diff(const transwarp_encode_context *ctx, unsigned char in, unsigned char *previous, unsigned char *carry)
|
|
{
|
|
int offset = (ctx->version <= 84) ? 0 : 2;
|
|
|
|
int out = in - *carry - offset;
|
|
int diff = out - ((*previous & 0xc0) | (out & 0x3f));
|
|
*carry = (diff < 0);
|
|
out = ((out ^ *previous) & 0x3f) | diff;
|
|
*previous = in;
|
|
|
|
return out;
|
|
}
|
|
|
|
static unsigned char
|
|
encode_buffer_byte(const int encode[][64], unsigned char previous, unsigned char *carry, unsigned char in, unsigned char *out)
|
|
{
|
|
unsigned char even = (previous >> 1) ^ in;
|
|
unsigned char odd = ((*carry << 7) | (previous >> 1)) ^ in;
|
|
|
|
*carry = previous & 1;
|
|
|
|
out[(31 * 5) + 4] = encode[2][even_bits(even)];
|
|
out[4] = encode[2][odd_bits(odd)];
|
|
|
|
return in;
|
|
}
|
|
|
|
static void
|
|
encode_base_bytes(const unsigned char scramble[][256],
|
|
transwarp_encode_context *ctx, const unsigned char in[3], unsigned char *out)
|
|
{
|
|
unsigned char in0 = encode_receive_diff(ctx, in[0], &(ctx->previous), &(ctx->recvcarry));
|
|
unsigned char in1 = encode_receive_diff(ctx, in[1], &(ctx->previous), &(ctx->recvcarry));
|
|
unsigned char in2 = encode_receive_diff(ctx, in[2], &(ctx->previous), &(ctx->recvcarry));
|
|
|
|
in0 = scramble[0][in0];
|
|
in1 = scramble[1][in1];
|
|
in2 = scramble[2][in2];
|
|
|
|
unsigned char val3 = in0 & 0x3f;
|
|
out[0] = encode_read_diff(ENCODE[3], &(ctx->accu), &(ctx->carry), val3);
|
|
|
|
unsigned char val4 = ((in0 >> 6)
|
|
| (in1 << 2)) & 0x3f;
|
|
out[1] = encode_read_diff(ENCODE[4], &(ctx->accu), &(ctx->carry), val4);
|
|
|
|
unsigned char val0 = ((in1 >> 4)
|
|
| (in2 << 4)) & 0x3f;
|
|
out[2] = encode_read_diff(ENCODE[0], &(ctx->accu), &(ctx->carry), val0);
|
|
|
|
unsigned char val1 = in2 >> 2;
|
|
out[3] = encode_read_diff(ENCODE[1], &(ctx->accu), &(ctx->carry), val1);
|
|
}
|
|
|
|
static unsigned char
|
|
crc8(unsigned char value)
|
|
{
|
|
for (int i = 0; i < 8; ++i) {
|
|
value = (value & 0x80) ? ((value << 1) ^ 0x31) : (value << 1);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static bool
|
|
decode_5_bytes_gcr(const int8_t decoding_map[], const unsigned char *in, unsigned char *out)
|
|
{
|
|
unsigned char gcr_hi;
|
|
unsigned char gcr_lo;
|
|
|
|
gcr_hi = in[0] >> 3;
|
|
gcr_lo = ((in[0] & 0x7) << 2) | (in[1] >> 6);
|
|
int out0 = (decoding_map[gcr_hi] << 4) | decoding_map[gcr_lo];
|
|
|
|
gcr_hi = (in[1] & 0x3e) >> 1;
|
|
gcr_lo = ((in[1] & 0x1) << 4) | (in[2] >> 4);
|
|
int out1 = (decoding_map[gcr_hi] << 4) | decoding_map[gcr_lo];
|
|
|
|
gcr_hi = ((in[2] & 0xf) << 1) | (in[3] >> 7);
|
|
gcr_lo = (in[3] & 0x7c) >> 2;
|
|
int out2 = (decoding_map[gcr_hi] << 4) | decoding_map[gcr_lo];
|
|
|
|
gcr_hi = ((in[3] & 0x3) << 3) | (in[4] >> 5);
|
|
gcr_lo = in[4] & 0x1f;
|
|
int out3 = (decoding_map[gcr_hi] << 4) | decoding_map[gcr_lo];
|
|
|
|
out[0] = out0;
|
|
out[1] = out1;
|
|
out[2] = out2;
|
|
out[3] = out3;
|
|
|
|
return (out0 | out1 | out2 | out3) >= 0;
|
|
}
|
|
|
|
static int
|
|
decode_gcr_block(const int8_t decoding_map[], const unsigned char *encoded, unsigned char decoded[])
|
|
{
|
|
bool ok = decode_5_bytes_gcr(decoding_map, encoded, decoded);
|
|
decoded[0] = decoded[1];
|
|
decoded[1] = decoded[2];
|
|
decoded[2] = decoded[3];
|
|
|
|
int computed_checksum = decoded[0] ^ decoded[1] ^ decoded[2];
|
|
|
|
int i = 5;
|
|
for (int j = 3; i < 320; i += 5, j += 4) {
|
|
ok &= decode_5_bytes_gcr(decoding_map, encoded + i, decoded + j);
|
|
computed_checksum ^= decoded[j] ^ decoded[j + 1] ^ decoded[j + 2] ^ decoded[j + 3];
|
|
}
|
|
|
|
unsigned char last_byte[4];
|
|
ok &= decode_5_bytes_gcr(decoding_map, encoded + i, last_byte);
|
|
decoded[255] = last_byte[0];
|
|
|
|
computed_checksum ^= last_byte[0];
|
|
computed_checksum = ok ? computed_checksum : -1;
|
|
|
|
return ok ? computed_checksum : -1;
|
|
}
|
|
|
|
static int
|
|
encode_transwarp_block(const unsigned char scramble[][256], const int8_t gcr_to_nibble[32],
|
|
transwarp_encode_context* ctx, const unsigned char *indata, int filepos, unsigned char encoded[325])
|
|
{
|
|
const unsigned char *unencoded = indata + filepos;
|
|
|
|
unsigned char data[TRANSWARPBLOCKSIZE];
|
|
if (filepos < 2) {
|
|
for (int i = filepos, j = 0; j < TRANSWARPBLOCKSIZE; ++i, ++j) {
|
|
data[j] = (i < 0) ? ' ' : ((i < 2) ? 0 : indata[i]);
|
|
}
|
|
|
|
unencoded = data;
|
|
}
|
|
|
|
unsigned char semiencoded[TRANSWARPBUFFERBLOCKSIZE];
|
|
|
|
for (int i = TRANSWARPBUFFERBLOCKSIZE - 1; i >= 0; --i) {
|
|
unsigned char value = encode_receive_diff(ctx, unencoded[TRANSWARPBASEBLOCKSIZE + i], &(ctx->previous2), &(ctx->carry2));
|
|
|
|
value = scramble[3][value];
|
|
|
|
semiencoded[i] = encode_send_diff(value, &(ctx->sendaccu), &(ctx->sendcarry));
|
|
}
|
|
|
|
unsigned char buffer_previous = ctx->previous1;
|
|
unsigned char buffer_carry = 0;
|
|
for (int i = 0; i < TRANSWARPBUFFERBLOCKSIZE; ++i) {
|
|
int shuffle = (TRANSWARPBUFFERBLOCKSIZE - 1) - (i / 2) - ((i & 1) ? ((TRANSWARPBUFFERBLOCKSIZE / 2) + 1) : 0);
|
|
unsigned char value = semiencoded[shuffle];
|
|
buffer_previous = encode_buffer_byte(ENCODE, buffer_previous, &buffer_carry, value, encoded + 3 + (5 * (TRANSWARPBUFFERBLOCKSIZE - 1 - shuffle)));
|
|
}
|
|
|
|
unsigned char previous = unencoded[TRANSWARPBASEBLOCKSIZE - 1];
|
|
|
|
const int CRCSTEP = 8;
|
|
|
|
for (int i = 0; i < TRANSWARPBASEBLOCKSIZE; i += CRCSTEP) {
|
|
previous = crc8(previous);
|
|
previous ^= unencoded[i];
|
|
}
|
|
previous = crc8(previous);
|
|
|
|
if (filepos > ((21 * TRANSWARPBLOCKSIZE) + 2)) {
|
|
const int BACKCHECKOFFS = (21 * TRANSWARPBLOCKSIZE) + TRANSWARPBUFFERBLOCKSIZE;
|
|
previous ^= indata[filepos - BACKCHECKOFFS];
|
|
previous = crc8(previous);
|
|
previous ^= indata[filepos - BACKCHECKOFFS + TRANSWARPBUFFERBLOCKSIZE - 1];
|
|
previous = crc8(previous);
|
|
}
|
|
|
|
char head_data[4] = { 7, 0, 0, 0 };
|
|
encode_4_bytes_gcr(head_data, (char *) encoded);
|
|
char tail_data[4] = { 0, 0, 0, 0 };
|
|
encode_4_bytes_gcr(tail_data, (char *) encoded + 320);
|
|
|
|
unsigned char accu = ctx->previous;
|
|
unsigned char carry = 0;
|
|
for (int i = 0; i < TRANSWARPBASEBLOCKSIZE; ++i) {
|
|
encode_receive_diff(ctx, unencoded[i], &accu, &carry);
|
|
}
|
|
|
|
unsigned char receive_checksum = (-previous - carry);
|
|
|
|
unsigned char checksum = receive_checksum ^ (buffer_previous >> 1);
|
|
|
|
unsigned char odd = odd_bits(checksum);
|
|
|
|
odd ^= (buffer_carry << 3);
|
|
|
|
static const unsigned char ENCODE_TOP[] = {
|
|
0x05,
|
|
0x07,
|
|
0x0d,
|
|
0x0b
|
|
};
|
|
encoded[2] = (encoded[2] & 0xf0) | ENCODE_TOP[odd & 0x3];
|
|
|
|
encoded[317] = ENCODE[2][even_bits(checksum)];
|
|
|
|
encoded[322] = ENCODE[2][odd & 0xc];
|
|
|
|
unsigned char top_2_bits = encoded[2];
|
|
unsigned char middle_4_bits = encoded[317];
|
|
unsigned char bottom_2_bits = encoded[322];
|
|
|
|
ctx->recvcarry = 0;
|
|
|
|
ctx->accu = 0;
|
|
ctx->carry = 0;
|
|
for (int i = 0, j = 0; i < TRANSWARPBASEBLOCKSIZE; i += 3, j += 5) {
|
|
encode_base_bytes(scramble, ctx, unencoded + i, encoded + 3 + j);
|
|
|
|
ctx->accu = 8;
|
|
ctx->carry = 0;
|
|
|
|
unsigned char target = encoded[3 + j + 4];
|
|
unsigned char target_accu = DECODE[target];
|
|
unsigned char target_check = ENCODE[2][odd_bits(target_accu)];
|
|
|
|
if (target != target_check) {
|
|
fprintf(stderr, "ERROR: Transwarp encoding error, [%d] 0x%x != 0x%x <- 0x%x\n", i, target, target_check, DECODE[target]);
|
|
|
|
exit(-4);
|
|
}
|
|
|
|
unsigned char store = ctx->accu ^ target_accu;
|
|
encoded[3 + j + 4] = ENCODE[2][odd_bits(store)];
|
|
|
|
unsigned char stored = DECODE[encoded[3 + j + 4]];
|
|
ctx->accu ^= stored;
|
|
|
|
if (ctx->accu != target_accu) {
|
|
fprintf(stderr, "ERROR: Transwarp encoding error, [%d] actual 0x%x != 0x%x expected <- 0x%x\n", i, ctx->accu, target_accu, stored);
|
|
|
|
exit(-5);
|
|
}
|
|
}
|
|
if (carry != ctx->recvcarry) {
|
|
fprintf(stderr, "ERROR: Transwarp encoding error, carry %d != %d recvcarry\n", carry, ctx->recvcarry);
|
|
|
|
exit(-6);
|
|
}
|
|
|
|
unsigned char top_fix = encoded[2] ^ (DECODE[encoded[322]] & 0xf);
|
|
top_fix = ((top_fix >> 2) & 2) | ((top_fix >> 1) & 1);
|
|
encoded[2] = (encoded[2] & 0xf0) | ENCODE_TOP[top_fix];
|
|
encoded[322] = (encoded[322] & 0xf0) | 0x5;
|
|
|
|
unsigned char block_checksum;
|
|
block_checksum = DECODE[middle_4_bits];
|
|
block_checksum = (block_checksum >> 1) | (buffer_carry << 7);
|
|
block_checksum ^= DECODE[bottom_2_bits];
|
|
block_checksum ^= (top_2_bits & 0xa);
|
|
|
|
if (block_checksum != checksum) {
|
|
fprintf(stderr, "ERROR: Transwarp encoding error, actual 0x%x != 0x%x expected, 0x%x -> [0x%x] -> 0x%x -> 0x%x -> [0x%x]\n", block_checksum, checksum, checksum & 0xaa, ((checksum & 0xaa) >> 1) | (checksum & 0xaa), odd, ENCODE[2][odd], DECODE[encoded[317]]);
|
|
|
|
exit(-1);
|
|
}
|
|
|
|
unsigned char gcr_decoded[4];
|
|
bool ok = decode_5_bytes_gcr(gcr_to_nibble, encoded + 320, gcr_decoded);
|
|
int gcr_checksum = ok ? gcr_decoded[1] : -1;
|
|
|
|
unsigned char decoded[256];
|
|
int computed_checksum = decode_gcr_block(gcr_to_nibble, encoded, decoded);
|
|
|
|
ok &= decode_5_bytes_gcr(gcr_to_nibble, encoded, gcr_decoded);
|
|
gcr_decoded[1] ^= gcr_checksum ^ computed_checksum;
|
|
|
|
encode_4_bytes_gcr((char *) gcr_decoded, (char *) encoded);
|
|
|
|
return ok == 0;
|
|
}
|
|
|
|
static void
|
|
permute(unsigned char *key, int len, int *set)
|
|
{
|
|
for (int i = 0; i < (len - 1); ++i) {
|
|
int divisor = len - i;
|
|
|
|
int remainder = 0;
|
|
for (int i = TRANSWARPKEYSIZE - 12; i >= 0; --i) {
|
|
int dividend = (remainder << 8) | key[i];
|
|
remainder = dividend % divisor;
|
|
key[i] = dividend / divisor;
|
|
}
|
|
|
|
int n = set[remainder];
|
|
set[remainder] = set[divisor - 1];
|
|
set[divisor - 1] = n;
|
|
}
|
|
}
|
|
|
|
/* Write file to disk using Transwarp encoding */
|
|
static unsigned long long
|
|
write_transwarp_file(image_type type, unsigned char *image, imagefile *file, unsigned char *filedata, int *filesize, unsigned int version, bool transwarp_bootfile_fits_on_dir_track)
|
|
{
|
|
file->size = *filesize - 2;
|
|
|
|
unsigned int track = DIRTRACK_D41_D71 - 1;
|
|
|
|
if ((file->mode & MODE_MIN_TRACK_MASK) > 0) {
|
|
/* for Transwarp files, a set minimum track is the file's starting track */
|
|
track = (file->mode & MODE_MIN_TRACK_MASK) >> MODE_MIN_TRACK_SHIFT;
|
|
} else {
|
|
/* allocate */
|
|
bool free_tracks[40];
|
|
for (unsigned int t = 1; t <= image_num_tracks(type); ++t) {
|
|
bool track_free = true;
|
|
for (int sector = 0; sector < num_sectors(type, t); ++sector) {
|
|
if (is_sector_free(type, image, t, sector, 0 /* numdirblocks */, 0 /* dir_sector_interleave */) == false) {
|
|
track_free = false;
|
|
break;
|
|
}
|
|
}
|
|
free_tracks[t - 1] = track_free;
|
|
}
|
|
|
|
/* below dir track */
|
|
while (track > 0) {
|
|
if (free_tracks[track - 1]) {
|
|
int filesize = file->size;
|
|
int t = track;
|
|
while ((filesize > 0)
|
|
&& (t > 0)) {
|
|
filesize -= (TRANSWARPBLOCKSIZE * num_sectors(type, t));
|
|
if (filesize >= 0) {
|
|
if (free_tracks[t - 1] == false) {
|
|
break;
|
|
}
|
|
--t;
|
|
}
|
|
}
|
|
|
|
if (filesize <= 0) {
|
|
break;
|
|
}
|
|
}
|
|
--track;
|
|
}
|
|
|
|
if (track <= 0) {
|
|
/* above dir track */
|
|
track = DIRTRACK_D41_D71 + (transwarp_bootfile_fits_on_dir_track ? 1 : 2);
|
|
while (track <= image_num_tracks(type)) {
|
|
if (free_tracks[track - 1]) {
|
|
int filesize = file->size;
|
|
int t = track;
|
|
while (filesize > 0) {
|
|
filesize -= (TRANSWARPBLOCKSIZE * num_sectors(type, t));
|
|
if (filesize >= 0) {
|
|
if (free_tracks[t - 1] == false) {
|
|
break;
|
|
}
|
|
++t;
|
|
}
|
|
}
|
|
|
|
if (filesize <= 0) {
|
|
break;
|
|
}
|
|
}
|
|
++track;
|
|
}
|
|
}
|
|
}
|
|
|
|
file->track = track;
|
|
file->sector = 0;
|
|
|
|
int8_t gcr_to_nibble[32];
|
|
generate_gcr_decoding_table(NIBBLE_TO_GCR, gcr_to_nibble);
|
|
|
|
unsigned char key[TRANSWARPKEYSIZE];
|
|
memset(key, 0, sizeof key);
|
|
|
|
if (file->have_key != 0) {
|
|
if ((filedata[0] == 0x01)
|
|
&& (filedata[1] == 0x08)) {
|
|
srand((unsigned int) time(NULL));
|
|
|
|
unsigned int linelink = ((filedata[3] << 8) | filedata[2]) - 0x0801 + 2;
|
|
if ((linelink > 0)
|
|
&& ((linelink - 2) < (unsigned int) file->size)
|
|
&& ((filedata[linelink - 1] | filedata[linelink] | filedata[linelink + 1]) == 0)) {
|
|
while (filedata[linelink] == 0) {
|
|
filedata[linelink] = rand();
|
|
}
|
|
}
|
|
|
|
filedata[2] = rand();
|
|
while ((filedata[3] == 0)
|
|
|| (filedata[3] == 8)) {
|
|
filedata[3] = rand();
|
|
}
|
|
|
|
int file_size = file->size;
|
|
int filetrack = track;
|
|
while (file_size > 0) {
|
|
file_size -= (TRANSWARPBLOCKSIZE * num_sectors(type, filetrack));
|
|
if (file_size > 0) {
|
|
filetrack = (filetrack < DIRTRACK_D41_D71) ? (filetrack - 1) : (filetrack + 1);
|
|
}
|
|
}
|
|
int spare_blocks = (0 - file_size) / TRANSWARPBLOCKSIZE;
|
|
int spare_bytes = TRANSWARPBLOCKSIZE - (file->size % TRANSWARPBLOCKSIZE);
|
|
spare_bytes = (spare_blocks * TRANSWARPBLOCKSIZE) + ((spare_bytes != TRANSWARPBLOCKSIZE) ? spare_bytes : 0);
|
|
while (spare_bytes > (0x0801 - 0x0400)) {
|
|
spare_bytes -= TRANSWARPBLOCKSIZE;
|
|
}
|
|
if (spare_bytes > 0) {
|
|
int loadaddress = (filedata[1] << 8) | filedata[0];
|
|
loadaddress -= spare_bytes;
|
|
filedata[0] = loadaddress;
|
|
filedata[1] = loadaddress >> 8;
|
|
memmove(filedata + 2 + spare_bytes, filedata + 2, file->size);
|
|
filedata[spare_bytes + 1] = 0;
|
|
for (int i = 2; i <= spare_bytes; ++i) {
|
|
filedata[i] = rand();
|
|
}
|
|
file->size += spare_bytes;
|
|
*filesize += spare_bytes;
|
|
}
|
|
}
|
|
|
|
memcpy(key, file->key, sizeof key);
|
|
|
|
for (int round = TRANSWARPKEYHASHROUNDS; round > 0; --round) {
|
|
for (int i = 0; i < (TRANSWARPKEYSIZE - 1); ++i) {
|
|
key[i] ^= key[i + 1];
|
|
}
|
|
|
|
for (int i = TRANSWARPKEYSIZE - 1; i >= 0; --i) {
|
|
int product = key[i] * 0x6b;
|
|
key[i] = product;
|
|
int msb = product >> 8;
|
|
for (int j = i + 1; j < TRANSWARPKEYSIZE; ++j) {
|
|
msb += key[j];
|
|
key[j] = msb;
|
|
msb >>= 8;
|
|
}
|
|
}
|
|
|
|
int sum = round;
|
|
for (int i = 0; i < TRANSWARPKEYSIZE; ++i) {
|
|
sum += key[i];
|
|
key[i] = sum;
|
|
sum >>= 8;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsigned long long dirdatakey = (key[TRANSWARPKEYSIZE - 1] * (1ULL << 56))
|
|
+ (key[TRANSWARPKEYSIZE - 2] * (1ULL << 48))
|
|
+ (key[TRANSWARPKEYSIZE - 3] * (1ULL << 40))
|
|
+ (key[TRANSWARPKEYSIZE - 4] * (1ULL << 32))
|
|
+ (key[TRANSWARPKEYSIZE - 5] * (1ULL << 24))
|
|
+ (key[TRANSWARPKEYSIZE - 6] * (1ULL << 16))
|
|
+ (key[TRANSWARPKEYSIZE - 7] * (1ULL << 8))
|
|
+ key[TRANSWARPKEYSIZE - 8];
|
|
|
|
int initial_buffer_store_value = key[TRANSWARPKEYSIZE - 9];
|
|
int initial_buffer_recvaccu_value = key[TRANSWARPKEYSIZE - 10];
|
|
int initial_block_recvaccu_value = key[TRANSWARPKEYSIZE - 11];
|
|
|
|
unsigned char scramble[4][256];
|
|
for (int i = 0; i < 4; ++i) {
|
|
int set[] = { 0, 2, 4, 1, 3, 5 };
|
|
if (file->have_key) {
|
|
permute(key, 6, set);
|
|
}
|
|
|
|
int set2[3][4];
|
|
for (int j = 0; j < 3; ++j) {
|
|
int set3[] = { 0, 1, 2, 3 };
|
|
if (file->have_key) {
|
|
permute(key, 4, set3);
|
|
}
|
|
for (int k = 0; k < 4; ++k) {
|
|
for (int l = 0; l < 4; ++l) {
|
|
if (k == set3[l]) {
|
|
set2[j][k] = l;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < 256; ++j) {
|
|
unsigned char scrambled = (set2[0][(((j >> set[0]) & 1) << 0)
|
|
| (((j >> set[3]) & 1) << 1)] << 0)
|
|
| (set2[1][(((j >> set[1]) & 1) << 0)
|
|
| (((j >> set[4]) & 1) << 1)] << 2)
|
|
| (set2[2][(((j >> set[2]) & 1) << 0)
|
|
| (((j >> set[5]) & 1) << 1)] << 4)
|
|
| (j & 0xc0);
|
|
scramble[i][j] = scrambled;
|
|
}
|
|
}
|
|
|
|
int sectors[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
|
|
if (file->have_key != 0) {
|
|
permute(key, 17, sectors);
|
|
}
|
|
|
|
transwarp_encode_context ctx;
|
|
memset(&ctx, 0, sizeof ctx);
|
|
ctx.version = version;
|
|
|
|
ctx.previous1 = initial_buffer_store_value;
|
|
|
|
bool done = false;
|
|
int block_index = 0;
|
|
int total_blocks = 0;
|
|
|
|
int filepos = 2;
|
|
|
|
for (; !done; (track >= DIRTRACK_D41_D71) ? ++track : --track) {
|
|
if ((track < 1)
|
|
|| (track > image_num_tracks(type))) {
|
|
fprintf(stderr, "ERROR: Disk full (track %d out of range) while writing Transwarp file ", track);
|
|
print_filename(stderr, file->pfilename);
|
|
fprintf(stderr, "\n");
|
|
exit(-4);
|
|
}
|
|
|
|
int next_track_pos = filepos + (num_sectors(type, track) * TRANSWARPBLOCKSIZE);
|
|
bool last_track = (next_track_pos >= *filesize);
|
|
if (last_track) {
|
|
int sector = 0;
|
|
for (int s = 0; s < num_sectors(type, track); ++s) {
|
|
sectors[s] = sector;
|
|
if ((filepos + ((sector + 1) * TRANSWARPBLOCKSIZE)) >= *filesize) {
|
|
sector = 0;
|
|
} else {
|
|
++sector;
|
|
}
|
|
}
|
|
}
|
|
int next_track_block_index = block_index + num_sectors(type, track);
|
|
int trackpos = filepos;
|
|
|
|
transwarp_encode_context trackctx = ctx;
|
|
|
|
for (int sector = 0; sector < num_sectors(type, track); ++sector) {
|
|
if (is_sector_free(type, image, track, sector, 0 /* numdirblocks */, 0 /* dir_sector_interleave */) == false) {
|
|
fprintf(stderr, "ERROR: t%d/s%d not free for Transwarp file ", track, sector);
|
|
print_filename(stderr, file->pfilename);
|
|
fprintf(stderr, "\n");
|
|
check_bam(type, image);
|
|
|
|
exit(-5);
|
|
}
|
|
|
|
bool last_block = false;
|
|
int pos = trackpos + (sectors[sector] * TRANSWARPBLOCKSIZE);
|
|
if ((pos + TRANSWARPBLOCKSIZE) >= *filesize) {
|
|
pos = *filesize - TRANSWARPBLOCKSIZE;
|
|
last_block = true;
|
|
}
|
|
|
|
unsigned char encoded[320 + 5];
|
|
unsigned char previous = block_index + sectors[sector];
|
|
previous ^= initial_block_recvaccu_value;
|
|
|
|
ctx.previous = previous;
|
|
ctx.previous2 = initial_buffer_recvaccu_value;
|
|
|
|
int error = encode_transwarp_block((const unsigned char (*)[256]) scramble, gcr_to_nibble, &ctx, filedata, pos, encoded);
|
|
if (error) {
|
|
fprintf(stderr, "ERROR: encoding error on t%d/s%d\n", track, sector);
|
|
|
|
exit(-6);
|
|
}
|
|
|
|
unsigned char decoded[256];
|
|
int checksum = decode_gcr_block(gcr_to_nibble, encoded, decoded);
|
|
if (checksum < 0) {
|
|
fprintf(stderr, "ERROR: decoding error on t%d/s%d\n", track, sector);
|
|
|
|
exit(-7);
|
|
}
|
|
|
|
int offset = linear_sector(type, track, sector) * BLOCKSIZE;
|
|
memcpy(image + offset, decoded, BLOCKSIZE);
|
|
|
|
++total_blocks;
|
|
|
|
if (last_block) {
|
|
ctx = trackctx;
|
|
done = true;
|
|
}
|
|
|
|
mark_sector(type, image, track, sector, 0 /* not free */);
|
|
}
|
|
|
|
filepos = next_track_pos;
|
|
block_index = next_track_block_index;
|
|
file->last_track = track;
|
|
}
|
|
|
|
file->nrSectors = total_blocks;
|
|
|
|
return dirdatakey;
|
|
}
|
|
|
|
/* Write files to disk */
|
|
static void
|
|
write_files(image_type type, unsigned char *image, imagefile *files, int num_files, int usedirtrack, int dirtracksplit, int shadowdirtrack, int numdirblocks, int dir_sector_interleave)
|
|
{
|
|
unsigned char track = 1;
|
|
unsigned char sector = 0;
|
|
int bytes_to_write = 0;
|
|
int lastTrack = track;
|
|
int lastSector = sector;
|
|
int lastOffset = linear_sector(type, lastTrack, lastSector) * BLOCKSIZE;
|
|
int lastMinTrack = track;
|
|
bool transwarp_bootfile_fits_on_dir_track = false;
|
|
|
|
/* make sure the first file already takes first sector per track into account */
|
|
if (num_files > 0) {
|
|
sector = (type == IMAGE_D81) ? 0 : files[0].first_sector_new_track;
|
|
}
|
|
|
|
int transwarp_version = 100;
|
|
|
|
for (int i = 0; i < num_files; i++) {
|
|
imagefile *file = files + i;
|
|
if ((file->mode & MODE_TRANSWARPBOOTFILE) != 0) {
|
|
int fileSize = 0;
|
|
|
|
struct stat st;
|
|
if (stat((char*)files[i].alocalname, &st) == 0) {
|
|
fileSize = (int)st.st_size;
|
|
}
|
|
|
|
int version_major;
|
|
int version_minor;
|
|
if (sscanf((char *) basename(files[i].alocalname), "transwarp v%d.%d", &version_major, &version_minor) == 2) {
|
|
transwarp_version = (version_major * 100) + version_minor;
|
|
}
|
|
|
|
int num_blocks = (fileSize / 254) + ((fileSize % 254 == 0) ? 0 : 1);
|
|
transwarp_bootfile_fits_on_dir_track = (num_blocks <= 17);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < num_files; i++) {
|
|
imagefile *file = files + i;
|
|
if (type == IMAGE_D81) {
|
|
file->sectorInterleave = 1; /* caught in command line parsing anyway, but does not hurt */
|
|
}
|
|
|
|
int file_usedirtrack = usedirtrack;
|
|
int file_numdirblocks = numdirblocks;
|
|
|
|
if ((file->mode & MODE_NOFILE)) {
|
|
if (file->nrSectorsShown == -1) {
|
|
file->nrSectorsShown = file->nrSectors;
|
|
}
|
|
file->track = 0;
|
|
file->sector = 0;
|
|
int entryOffset = linear_sector(type, dirtrack(type), file->direntrysector) * BLOCKSIZE + file->direntryoffset;
|
|
image[entryOffset + FILETRACKOFFSET] = file->track;
|
|
image[entryOffset + FILESECTOROFFSET] = file->sector;
|
|
image[entryOffset + FILEBLOCKSLOOFFSET] = file->nrSectorsShown & 255;
|
|
image[entryOffset + FILEBLOCKSHIOFFSET] = file->nrSectorsShown >> 8;
|
|
if (shadowdirtrack > 0) {
|
|
entryOffset = linear_sector(type, shadowdirtrack, file->direntrysector) * BLOCKSIZE + file->direntryoffset;
|
|
image[entryOffset + FILETRACKOFFSET] = file->track;
|
|
image[entryOffset + FILESECTOROFFSET] = file->sector;
|
|
image[entryOffset + FILEBLOCKSLOOFFSET] = file->nrSectors & 255;
|
|
image[entryOffset + FILEBLOCKSHIOFFSET] = file->nrSectors >> 8;
|
|
}
|
|
} else if (!(file->mode & MODE_LOOPFILE)) { /* loop files are handled later */
|
|
|
|
int fileSize = 0;
|
|
|
|
struct stat st;
|
|
if (stat((char*)files[i].alocalname, &st) == 0) {
|
|
fileSize = (int)st.st_size;
|
|
}
|
|
|
|
if ((file->mode & MODE_TRANSWARPBOOTFILE) != 0) {
|
|
file_usedirtrack = true;
|
|
file_numdirblocks = transwarp_bootfile_fits_on_dir_track ? 2 : 4;
|
|
file->sectorInterleave = -4;
|
|
file->mode = (file->mode & ~MODE_BEGINNING_SECTOR_MASK) | (10 + 1);
|
|
file->first_sector_new_track = 10;
|
|
track = DIRTRACK_D41_D71;
|
|
}
|
|
|
|
unsigned char* filedata = (unsigned char*)calloc(fileSize + ((file->filetype & FILETYPETRANSWARPMASK) ? (21 * TRANSWARPBLOCKSIZE) : 0), sizeof(unsigned char));
|
|
if (filedata == NULL) {
|
|
fprintf(stderr, "ERROR: Memory allocation error\n");
|
|
|
|
exit(-1);
|
|
}
|
|
FILE* f = fopen((char*)file->alocalname, "rb");
|
|
if (f == NULL) {
|
|
fprintf(stderr, "ERROR: Could not open file \"%s\" for reading\n", file->alocalname);
|
|
|
|
exit(-1);
|
|
}
|
|
if (fread(filedata, fileSize, 1, f) != 1) {
|
|
fprintf(stderr, "ERROR: Unexpected filesize when reading %s\n", file->alocalname);
|
|
exit(-1);
|
|
}
|
|
fclose(f);
|
|
|
|
if ((!(file->filetype & FILETYPETRANSWARPMASK))
|
|
&& ((file->mode & MODE_MIN_TRACK_MASK) > 0)) {
|
|
int minTrack = (file->mode & MODE_MIN_TRACK_MASK) >> MODE_MIN_TRACK_SHIFT;
|
|
if (lastMinTrack != minTrack) {
|
|
lastMinTrack = minTrack;
|
|
track = minTrack;
|
|
/* note that track may be smaller than lastTrack now */
|
|
if (track > image_num_tracks(type)) {
|
|
fprintf(stderr, "ERROR: Invalid minimum track %u for file %s (", track, file->alocalname);
|
|
print_filename(stderr, file->pfilename);
|
|
fprintf(stderr, ") specified\n");
|
|
|
|
exit(-1);
|
|
}
|
|
while ((!file_usedirtrack)
|
|
&& ((track == dirtrack(type))
|
|
|| (track == shadowdirtrack)
|
|
|| ((type == IMAGE_D71) && (track == (D64NUMTRACKS + dirtrack(type)))))) { /* .d71 track 53 is usually empty except the extra BAM block */
|
|
++track; /* skip dir track */
|
|
}
|
|
if (abs(((int) track) - lastTrack) > 1) {
|
|
/* previous file's last track and this file's beginning track have tracks in between */
|
|
sector = (type == IMAGE_D81) ? 0 : file->first_sector_new_track;
|
|
}
|
|
}
|
|
} else {
|
|
lastMinTrack = 0;
|
|
}
|
|
|
|
if ((file->mode & MODE_BEGINNING_SECTOR_MASK) > 0) {
|
|
sector = (file->mode & MODE_BEGINNING_SECTOR_MASK) - 1;
|
|
}
|
|
|
|
if ((!(file->filetype & FILETYPETRANSWARPMASK))
|
|
&& (((file->mode & MODE_SAVETOEMPTYTRACKS) != 0)
|
|
|| ((file->mode & MODE_FITONSINGLETRACK) != 0))) {
|
|
|
|
/* find first empty track */
|
|
int found = 0;
|
|
while (!found) {
|
|
for (int s = 0; s < num_sectors(type, track); s++) {
|
|
if (is_sector_free(type, image, track, s, file_usedirtrack ? file_numdirblocks : 0, dir_sector_interleave)) {
|
|
if (s == (num_sectors(type, track) - 1)) {
|
|
found = 1;
|
|
/* In first pass, use sector as left by previous file (or as set by -b) to reach first file block quickly. */
|
|
/* Claus: according to Krill, on real HW tracks are not aligned anyway, so it does not make a difference. */
|
|
/* Emulators tend to reset the disk angle on track changes, so this should rather be 3. */
|
|
if (sector >= num_sectors(type, track)) {
|
|
if ((file->mode & MODE_BEGINNING_SECTOR_MASK) > 0) {
|
|
fprintf(stderr, "ERROR: Invalid beginning sector %u on track %u for file %s (", sector, track, file->alocalname);
|
|
print_filename(stderr, file->pfilename);
|
|
fprintf(stderr, ") specified\n");
|
|
|
|
exit(-1);
|
|
}
|
|
|
|
sector %= num_sectors(type, track);
|
|
}
|
|
}
|
|
} else {
|
|
int prev_track = track;
|
|
if (file->mode & MODE_SAVECLUSTEROPTIMIZED) {
|
|
if (track > D64NUMTRACKS) {
|
|
int next_track = track - D64NUMTRACKS + 1; /* to next track on first side */
|
|
if (next_track < D64NUMTRACKS) {
|
|
track = next_track;
|
|
} else {
|
|
++track; /* disk full */
|
|
}
|
|
} else {
|
|
track += D64NUMTRACKS; /* to same track on second side */
|
|
}
|
|
} else {
|
|
++track;
|
|
}
|
|
while ((!file_usedirtrack)
|
|
&& ((track == dirtrack(type))
|
|
|| (track == shadowdirtrack)
|
|
|| ((type == IMAGE_D71) && (track == D64NUMTRACKS + dirtrack(type))))) { /* .d71 track 53 is usually empty except the extra BAM block */
|
|
++track; /* skip dir track */
|
|
}
|
|
if (file->mode & MODE_FITONSINGLETRACK) {
|
|
int file_size = fileSize;
|
|
int first_sector = -1;
|
|
for (int s = 0; s < num_sectors(type, prev_track); s++) {
|
|
if (is_sector_free(type, image, prev_track, s, file_usedirtrack ? file_numdirblocks : 0, dir_sector_interleave)) {
|
|
if (first_sector < 0) {
|
|
first_sector = s;
|
|
}
|
|
file_size -= BLOCKSIZE + BLOCKOVERHEAD;
|
|
if (file_size <= 0) {
|
|
found = 1;
|
|
track = prev_track;
|
|
sector = first_sector;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (track > image_num_tracks(type)) {
|
|
fprintf(stderr, "ERROR: Disk full, file %s (", file->alocalname);
|
|
print_filename(stderr, file->pfilename);
|
|
fprintf(stderr, ")\n");
|
|
|
|
exit(-1);
|
|
}
|
|
break;
|
|
}
|
|
} /* for each sector on track */
|
|
|
|
if ((track == (lastTrack + 2))
|
|
&& ((file->mode & MODE_BEGINNING_SECTOR_MASK) == 0)) {
|
|
/* previous file's last track and this file's beginning track have tracks in between now */
|
|
sector = 0;
|
|
}
|
|
} /* while not found */
|
|
}
|
|
|
|
if ((file->mode & MODE_BEGINNING_SECTOR_MASK) > 0) {
|
|
if (sector != ((file->mode & MODE_BEGINNING_SECTOR_MASK) - 1)) {
|
|
fprintf(stderr, "ERROR: Specified beginning sector of file %s (", file->alocalname);
|
|
print_filename(stderr, file->pfilename);
|
|
fprintf(stderr, ") not free on track %u\n", track);
|
|
|
|
exit(-1);
|
|
}
|
|
}
|
|
|
|
/* found start track, now save file */
|
|
if (type == IMAGE_D81) {
|
|
sector = 0;
|
|
}
|
|
|
|
int byteOffset = 0;
|
|
int bytesLeft = fileSize;
|
|
|
|
unsigned long long key0 = 0;
|
|
if (file->filetype & FILETYPETRANSWARPMASK) {
|
|
key0 = write_transwarp_file(type, image, file, filedata, &fileSize, transwarp_version, transwarp_bootfile_fits_on_dir_track);
|
|
|
|
bytesLeft = 0;
|
|
}
|
|
|
|
while (bytesLeft > 0) {
|
|
/* Find free track & sector, starting from current T/S forward one revolution, then the next track etc... skip dirtrack (unless -t is active) */
|
|
/* If the file didn't fit before dirtrack then restart on dirtrack + 1 and try again (unless -t is active). */
|
|
/* If the file didn't fit before track 36/41/71 then the disk is full. */
|
|
|
|
int blockfound = 0;
|
|
int findSector = 0;
|
|
|
|
while (!blockfound) {
|
|
/* find spare block on the current track */
|
|
for (int s = sector; s < sector + num_sectors(type, track); s++) {
|
|
findSector = s % num_sectors(type, track);
|
|
|
|
if (is_sector_free(type, image, track, findSector, file_usedirtrack ? file_numdirblocks : 0, dir_sector_interleave)) {
|
|
blockfound = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!blockfound) {
|
|
/* find next track, use some magic to make up for track seek delay */
|
|
int seek_delay = 1;
|
|
if (file->mode & MODE_SAVECLUSTEROPTIMIZED) {
|
|
if (track > D64NUMTRACKS) {
|
|
track = track - D64NUMTRACKS + 1;
|
|
} else {
|
|
track += D64NUMTRACKS;
|
|
seek_delay = 0; /* switching to the other side, no head movement */
|
|
}
|
|
} else {
|
|
++track;
|
|
}
|
|
if (type == IMAGE_D81) {
|
|
sector = 0;
|
|
} else if (file->first_sector_new_track < 0) {
|
|
sector -= file->first_sector_new_track;
|
|
} else if ((file->sectorInterleave < 0)
|
|
&& ((file->mode & MODE_TRANSWARPBOOTFILE) == 0)) {
|
|
sector += seek_delay;
|
|
} else {
|
|
sector = file->first_sector_new_track;
|
|
}
|
|
sector %= num_sectors(type, track);
|
|
findSector = sector;
|
|
|
|
while ((!file_usedirtrack)
|
|
&& ((track == dirtrack(type))
|
|
|| (track == shadowdirtrack)
|
|
|| ((type == IMAGE_D71) && (track == D64NUMTRACKS + dirtrack(type))))) { /* .d71 track 53 is usually empty except the extra BAM block */
|
|
/* Delete old fragments and restart file */
|
|
if (!dirtracksplit) {
|
|
if (file->nrSectors > 0) {
|
|
int deltrack = file->track;
|
|
int delsector = file->sector;
|
|
while (deltrack != 0) {
|
|
int b = linear_sector(type, deltrack, delsector);
|
|
if(b < 0) {
|
|
break;
|
|
}
|
|
int offset = b * BLOCKSIZE;
|
|
mark_sector(type, image, deltrack, delsector, 1 /* free */);
|
|
deltrack = image[offset + 0];
|
|
delsector = image[offset + 1];
|
|
memset(image + offset, 0, BLOCKSIZE);
|
|
}
|
|
}
|
|
|
|
bytesLeft = fileSize;
|
|
byteOffset = 0;
|
|
file->nrSectors = 0;
|
|
}
|
|
++track;
|
|
}
|
|
|
|
if (track > image_num_tracks(type)) {
|
|
if (verbose) {
|
|
print_file_allocation(type, image, files, num_files);
|
|
check_bam(type, image);
|
|
}
|
|
|
|
fprintf(stderr, "ERROR: Disk full, file %s (", file->alocalname);
|
|
print_filename(stderr, file->pfilename);
|
|
fprintf(stderr, ")\n");
|
|
free(filedata);
|
|
|
|
exit(-1);
|
|
}
|
|
}
|
|
} /* while not block found */
|
|
|
|
sector = findSector;
|
|
int offset = linear_sector(type, track, sector) * BLOCKSIZE;
|
|
|
|
if (bytesLeft == fileSize) {
|
|
file->track = track;
|
|
file->sector = sector;
|
|
lastTrack = track;
|
|
lastSector = sector;
|
|
lastOffset = offset;
|
|
} else {
|
|
image[lastOffset + 0] = track;
|
|
image[lastOffset + 1] = sector;
|
|
}
|
|
|
|
/* write sector */
|
|
bytes_to_write = min(BLOCKSIZE - BLOCKOVERHEAD, bytesLeft);
|
|
memset(image + offset + 2, 0, 254);
|
|
memcpy(image + offset + 2, filedata + byteOffset, bytes_to_write);
|
|
|
|
bytesLeft -= bytes_to_write;
|
|
byteOffset += bytes_to_write;
|
|
|
|
lastTrack = track;
|
|
lastSector = sector;
|
|
lastOffset = offset;
|
|
|
|
mark_sector(type, image, track, sector, 0 /* not free */);
|
|
|
|
if (num_sectors(type, track) <= abs(file->sectorInterleave)) {
|
|
fprintf(stderr, "ERROR: Invalid interleave %d on track %u (%d sectors), file %s (", file->sectorInterleave, track, num_sectors(type, track), file->alocalname);
|
|
print_filename(stderr, file->pfilename);
|
|
fprintf(stderr, ")\n");
|
|
|
|
exit(-1);
|
|
}
|
|
|
|
sector += abs(file->sectorInterleave);
|
|
sector %= num_sectors(type, track);
|
|
|
|
file->nrSectors++;
|
|
} /* while bytes left */
|
|
|
|
if (!(file->filetype & FILETYPETRANSWARPMASK)) {
|
|
image[lastOffset + 0] = 0x00;
|
|
image[lastOffset + 1] = bytes_to_write + 1;
|
|
}
|
|
|
|
/* update directory entry */
|
|
int entryOffset = linear_sector(type, dirtrack(type), file->direntrysector) * BLOCKSIZE + file->direntryoffset;
|
|
image[entryOffset + FILETRACKOFFSET] = file->track;
|
|
image[entryOffset + FILESECTOROFFSET] = file->sector;
|
|
|
|
if (file->filetype & FILETYPETRANSWARPMASK) {
|
|
image[entryOffset + FILETRACKOFFSET] = 0;
|
|
image[entryOffset + FILESECTOROFFSET] = 0;
|
|
|
|
image[entryOffset + TRANSWARPSIGNATROFFSLO] = TRANSWARPSIGNATURELO;
|
|
image[entryOffset + TRANSWARPSIGNATROFFSHI] = TRANSWARPSIGNATUREHI;
|
|
|
|
image[entryOffset + TRANSWARPTRACKOFFSET] = file->track;
|
|
|
|
image[entryOffset + FILEBLOCKSLOOFFSET] = file->nrSectors;
|
|
image[entryOffset + FILEBLOCKSHIOFFSET] = (file->nrSectors >> 8);
|
|
if (image[entryOffset + FILEBLOCKSHIOFFSET] > 0) {
|
|
fprintf(stderr, "ERROR: Transwarp file \"%s\" is %d > 255 blocks big\n", file->alocalname, file->nrSectors);
|
|
|
|
exit(-8);
|
|
}
|
|
|
|
int loadaddress = (filedata[1] << 8) | filedata[0];
|
|
image[entryOffset + LOADADDRESSLOOFFSET] = loadaddress;
|
|
image[entryOffset + LOADADDRESSHIOFFSET] = (loadaddress >> 8);
|
|
int endaddress = loadaddress + fileSize - 2;
|
|
image[entryOffset + ENDADDRESSLOOFFSET] = endaddress;
|
|
image[entryOffset + ENDADDRESSHIOFFSET] = (endaddress >> 8);
|
|
|
|
unsigned char file_checksum = 0xff;
|
|
for (int i = 2; i < fileSize; ++i) {
|
|
file_checksum ^= filedata[i];
|
|
file_checksum = crc8(file_checksum);
|
|
}
|
|
image[entryOffset + FILECHECKSUMOFFSET] = file_checksum;
|
|
|
|
image[entryOffset + DIRDATACHECKSUMOFFSET] = 0;
|
|
image[entryOffset + DIRDATACHECKSUMOFFSET] = (0x0100 - transwarp_dirdata_checksum(image, entryOffset));
|
|
unsigned char dirdata_checksum = transwarp_dirdata_checksum(image, entryOffset);
|
|
if (dirdata_checksum != 0) {
|
|
if (dirdata_checksum == 1) {
|
|
--image[entryOffset + DIRDATACHECKSUMOFFSET];
|
|
}
|
|
dirdata_checksum = transwarp_dirdata_checksum(image, entryOffset);
|
|
if (dirdata_checksum != 0) {
|
|
fprintf(stderr, "ERROR: Encoding error with \"%s\", 0x%x\n", file->alocalname, dirdata_checksum);
|
|
|
|
exit(-9);
|
|
}
|
|
}
|
|
|
|
if (file->have_key != 0) {
|
|
for (int offset = DIRDATACHECKSUMOFFSET; offset <= FILEBLOCKSLOOFFSET; ++offset) {
|
|
image[entryOffset + offset] ^= key0;
|
|
key0 >>= 8;
|
|
}
|
|
|
|
file->nrSectors = image[entryOffset + FILEBLOCKSLOOFFSET];
|
|
}
|
|
}
|
|
|
|
if (file->nrSectorsShown < 0) {
|
|
file->nrSectorsShown = file->nrSectors;
|
|
}
|
|
image[entryOffset + FILEBLOCKSLOOFFSET] = file->nrSectorsShown & 255;
|
|
image[entryOffset + FILEBLOCKSHIOFFSET] = file->nrSectorsShown >> 8;
|
|
|
|
if (shadowdirtrack > 0) {
|
|
entryOffset = linear_sector(type, shadowdirtrack, file->direntrysector) * BLOCKSIZE + file->direntryoffset;
|
|
image[entryOffset + FILETRACKOFFSET] = file->track;
|
|
image[entryOffset + FILESECTOROFFSET] = file->sector;
|
|
|
|
image[entryOffset + FILEBLOCKSLOOFFSET] = file->nrSectors & 255;
|
|
image[entryOffset + FILEBLOCKSHIOFFSET] = file->nrSectors >> 8;
|
|
}
|
|
|
|
free(filedata);
|
|
}
|
|
} /* for each file */
|
|
|
|
/* Set track/sector of Transwarp file entries to Transwarp bootfile */
|
|
int transwarp_boot_track = 0;
|
|
int transwarp_boot_sector = 0;
|
|
char *blockmap = calloc(image_num_blocks(type), sizeof(char));
|
|
if(blockmap == NULL) {
|
|
fprintf(stderr, "ERROR: Memory allocation error\n");
|
|
exit(-1);
|
|
}
|
|
|
|
for (int i = 0; i < num_files; i++) {
|
|
imagefile *file = files + i;
|
|
if (file->filetype & FILETYPETRANSWARPMASK) {
|
|
if (transwarp_boot_track == 0) {
|
|
/* find Transwarp bootfile */
|
|
|
|
int t = dirtrack(type);
|
|
int s = (type == IMAGE_D81) ? 3 : 1;
|
|
int o = 0;
|
|
|
|
do {
|
|
int b = linear_sector(type, t, s) * BLOCKSIZE + o;
|
|
int filetype = image[b + FILETYPEOFFSET] & 0xf;
|
|
if (filetype == FILETYPEDEL) {
|
|
continue;
|
|
}
|
|
|
|
if (is_transwarp_file(image, b)) {
|
|
continue;
|
|
}
|
|
|
|
if (!is_transwarp_bootfile(image, b)) {
|
|
continue;
|
|
}
|
|
|
|
int filetrack = image[b + FILETRACKOFFSET];
|
|
int filesector = image[b + FILESECTOROFFSET];
|
|
if (filetrack == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (verbose) {
|
|
printf("\nTranswarp bootfile at T%d/S%d\n", filetrack, filesector);
|
|
}
|
|
|
|
transwarp_boot_track = filetrack;
|
|
transwarp_boot_sector = filesector;
|
|
|
|
break;
|
|
} while (next_dir_entry(type, image, &t, &s, &o, blockmap));
|
|
}
|
|
|
|
if (transwarp_boot_track == 0) {
|
|
fprintf(stderr, "ERROR: No Transwarp bootfile provided\n");
|
|
|
|
exit(-10);
|
|
}
|
|
|
|
int b = linear_sector(type, dirtrack(type), file->direntrysector) * BLOCKSIZE + file->direntryoffset;
|
|
image[b + FILETRACKOFFSET] = transwarp_boot_track;
|
|
image[b + FILESECTOROFFSET] = transwarp_boot_sector;
|
|
}
|
|
}
|
|
free(blockmap);
|
|
|
|
/* update loop files */
|
|
for (int i = 0; i < num_files; i++) {
|
|
imagefile *file = files + i;
|
|
if (((file->filetype & 0xf) != FILETYPEDEL) && (file->mode & MODE_LOOPFILE)) {
|
|
int track, sector, offset;
|
|
int index;
|
|
if (find_existing_file(type, image, file->plocalname, &index, &track, §or, &offset)) {
|
|
/* read track/sector and nrSectors from disk image */
|
|
int b = linear_sector(type, track, sector) * BLOCKSIZE + offset;
|
|
file->track = image[b + FILETRACKOFFSET];
|
|
file->sector = image[b + FILESECTOROFFSET];
|
|
file->nrSectors = image[b + FILEBLOCKSLOOFFSET] + (image[b + FILEBLOCKSHIOFFSET] << 8);
|
|
|
|
/* update directory entry */
|
|
b = linear_sector(type, dirtrack(type), file->direntrysector) * BLOCKSIZE + file->direntryoffset;
|
|
image[b + FILETRACKOFFSET] = file->track;
|
|
image[b + FILESECTOROFFSET] = file->sector;
|
|
|
|
if (file->nrSectorsShown == -1) {
|
|
file->nrSectorsShown = file->nrSectors;
|
|
}
|
|
image[b + FILEBLOCKSLOOFFSET] = file->nrSectorsShown & 255;
|
|
image[b + FILEBLOCKSHIOFFSET] = file->nrSectorsShown >> 8;
|
|
|
|
if (shadowdirtrack > 0) {
|
|
b = linear_sector(type, shadowdirtrack, file->direntrysector) * BLOCKSIZE + file->direntryoffset;
|
|
image[b + FILETRACKOFFSET] = file->track;
|
|
image[b + FILESECTOROFFSET] = file->sector;
|
|
|
|
image[b + FILEBLOCKSLOOFFSET] = file->nrSectors & 255;
|
|
image[b + FILEBLOCKSHIOFFSET] = file->nrSectors >> 8;
|
|
}
|
|
|
|
for (int j = 0; j < num_files; j++) {
|
|
imagefile *other_file = files + j;
|
|
if ((i != j)
|
|
&& (file->track == other_file->track)
|
|
&& (file->sector == other_file->sector)) {
|
|
file->sectorInterleave = other_file->sectorInterleave;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
continue;
|
|
} else {
|
|
fprintf(stderr, "ERROR: Loop source file '%s' (%d) not found\n", file->alocalname, i + 1);
|
|
exit(-1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Writes 16 bit value to file */
|
|
static size_t
|
|
write16(unsigned int value, FILE* f)
|
|
{
|
|
char byte = value & 0xff;
|
|
size_t bytes_written = fwrite(&byte, 1, 1, f);
|
|
|
|
byte = (value >> 8) & 0xff;
|
|
bytes_written += fwrite(&byte, 1, 1, f);
|
|
|
|
return bytes_written;
|
|
}
|
|
|
|
/* Writes 32 bit value to file */
|
|
static size_t
|
|
write32(unsigned int value, FILE* f)
|
|
{
|
|
size_t bytes_written = write16(value, f);
|
|
bytes_written += write16(value >> 16, f);
|
|
|
|
return bytes_written;
|
|
}
|
|
|
|
/* Writes image as G64 file */
|
|
static int
|
|
generate_uniformat_g64(unsigned char* image, const char *imagepath)
|
|
{
|
|
FILE* f = fopen(imagepath, "wb");
|
|
|
|
size_t filepos = 0;
|
|
|
|
static const char signature[] = "GCR-1541";
|
|
filepos += fwrite(signature, 1, sizeof signature - 1, f);
|
|
|
|
const char version = 0;
|
|
filepos += fwrite(&version, 1, 1, f);
|
|
|
|
const char num_tracks = 35 * 2;
|
|
filepos += fwrite(&num_tracks, 1, 1, f);
|
|
|
|
const unsigned int track_size = 7692; /* = track_bytes on tracks 1..17 */
|
|
filepos += write16(track_size, f);
|
|
|
|
const unsigned int table_size = num_tracks * 4;
|
|
const unsigned int tracks_offset = (int)filepos + (table_size * 2);
|
|
|
|
for (int track = 0; track < num_tracks; ++track) {
|
|
unsigned int track_offset = 0;
|
|
|
|
if ((track & 1) == 0) {
|
|
track_offset = tracks_offset + ((track >> 1) * (2 + track_size));
|
|
}
|
|
|
|
filepos += write32(track_offset, f);
|
|
}
|
|
|
|
for (int track = 0; track < num_tracks; ++track) {
|
|
unsigned int bit_rate = 0;
|
|
|
|
if ((track & 1) == 0) {
|
|
switch (sectors_per_track[track >> 1]) {
|
|
case 21:
|
|
bit_rate = 3;
|
|
break;
|
|
case 19:
|
|
bit_rate = 2;
|
|
break;
|
|
case 18:
|
|
bit_rate = 1;
|
|
break;
|
|
case 17:
|
|
bit_rate = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
filepos += write32(bit_rate, f);
|
|
}
|
|
|
|
const unsigned char sync[] = { 0xff, 0xff, 0xff, 0xff, 0xff };
|
|
const char gap[] = { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 };
|
|
char header_gcr[10];
|
|
|
|
const unsigned int block_size =
|
|
(sizeof sync)
|
|
+ (sizeof header_gcr)
|
|
+ (sizeof gap)
|
|
+ (sizeof sync)
|
|
+ 325; /* data */
|
|
|
|
const char id[3] = { '2', 'A', '\0' };
|
|
|
|
bool is_uniform = true;
|
|
|
|
for (int track = 0; track < (num_tracks >> 1); ++track) {
|
|
int track_bytes = 0;
|
|
int num_sectors = sectors_per_track[track];
|
|
switch (num_sectors) {
|
|
case 21:
|
|
track_bytes = 7692;
|
|
break; /* = track_size */
|
|
case 19:
|
|
track_bytes = 7142;
|
|
break;
|
|
case 18:
|
|
track_bytes = 6666;
|
|
break;
|
|
case 17:
|
|
track_bytes = 6250;
|
|
break;
|
|
}
|
|
|
|
filepos += write16(track_bytes, f);
|
|
size_t track_begin = filepos;
|
|
|
|
int data_bytes = num_sectors * block_size;
|
|
int gap_size = (track_bytes - data_bytes) / num_sectors;
|
|
if (gap_size < 0) {
|
|
printf("\nERROR: Track too small for G64 output\n");
|
|
fclose(f);
|
|
|
|
return -1;
|
|
}
|
|
|
|
float average_gap_remainder = (((float) (track_bytes - data_bytes)) / num_sectors) - gap_size;
|
|
if (average_gap_remainder >= 1.0f) {
|
|
average_gap_remainder = 0.0f; /* 0..1 */
|
|
}
|
|
|
|
float remainder = 0.0f;
|
|
for (int sector = 0; sector < num_sectors; ++sector) {
|
|
unsigned int gap_bytes = gap_size;
|
|
remainder += average_gap_remainder;
|
|
if (remainder >= 0.5f) {
|
|
remainder -= 1.0f;
|
|
++gap_bytes;
|
|
}
|
|
|
|
filepos += fwrite(sync, 1, sizeof sync, f);
|
|
char header[8] = {
|
|
0x08, /* header ID */
|
|
(char) (sector ^ (track + 1) ^ id[1] ^ id[0]), /* checksum */
|
|
(char) sector,
|
|
(char) (track + 1),
|
|
id[1],
|
|
id[0],
|
|
0x0f, 0x0f
|
|
};
|
|
|
|
encode_4_bytes_gcr(header, header_gcr);
|
|
encode_4_bytes_gcr(header + 4, header_gcr + 5);
|
|
|
|
filepos += fwrite(header_gcr, 1, sizeof header_gcr, f);
|
|
filepos += fwrite(gap, 1, sizeof gap, f);
|
|
|
|
filepos += fwrite(sync, 1, sizeof sync, f);
|
|
|
|
char group[5];
|
|
|
|
char checksum = image[0] ^ image[1] ^ image[2];
|
|
char data[4] = { 0x07, (char) image[0], (char) image[1], (char) image[2] };
|
|
encode_4_bytes_gcr(data, group);
|
|
filepos += fwrite(group, 1, sizeof group, f);
|
|
for (int i = 0; i < 0x3f; ++i) {
|
|
data[0] = image[(i * 4) + 3];
|
|
data[1] = image[(i * 4) + 4];
|
|
data[2] = image[(i * 4) + 5];
|
|
data[3] = image[(i * 4) + 6];
|
|
encode_4_bytes_gcr(data, group);
|
|
filepos += fwrite(group, 1, sizeof group, f);
|
|
checksum ^= (data[0] ^ data[1] ^ data[2] ^ data[3]);
|
|
}
|
|
data[0] = image[0xff];
|
|
data[1] = data[0] ^ checksum;
|
|
data[2] = 0;
|
|
data[3] = 0;
|
|
encode_4_bytes_gcr(data, group);
|
|
filepos += fwrite(group, 1, sizeof group, f);
|
|
|
|
for (int i = gap_bytes; i > 0; --i) {
|
|
filepos += fwrite(gap, 1, 1, f);
|
|
}
|
|
|
|
image += 0x0100;
|
|
} /* for each sector */
|
|
|
|
size_t tail_gap = track_bytes - filepos + track_begin;
|
|
if (tail_gap > 0) {
|
|
for (size_t i = tail_gap; i > 0; --i) {
|
|
filepos += fwrite(gap, 1, 1, f);
|
|
}
|
|
|
|
is_uniform = false;
|
|
}
|
|
|
|
for (int i = (track_size - track_bytes); i > 0; --i) {
|
|
filepos += fwrite(sync, 1, 1, f);
|
|
}
|
|
} /* for each track */
|
|
|
|
fclose(f);
|
|
|
|
if (!is_uniform) {
|
|
printf("\nWARNING: \"%s\" is not UniFormAt'ed\n", imagepath);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Generates a unique filename, either based on the proposed name, or using track and sector. */
|
|
static void
|
|
generate_unique_filename(image_type type, unsigned char *image, unsigned char *name, int track, int sector, int start, char marker)
|
|
{
|
|
int i, t, s, o;
|
|
if(name[0] == 0xa0 || name[0] == 0) {
|
|
/* no name provided, create one */
|
|
name[0] = a2p('t');
|
|
pputnum2(name+1, track);
|
|
name[3] = a2p('s');
|
|
pputnum2(name+4, sector);
|
|
name[6] = a2p('$');
|
|
pputhex(name+7, start);
|
|
for(int pos = 11; pos < FILENAMEMAXSIZE; pos++) {
|
|
name[pos] = 0xa0;
|
|
}
|
|
}
|
|
int marker_pos = pstrlen(name);
|
|
if(marker_pos > FILENAMEMAXSIZE-1) {
|
|
marker_pos = FILENAMEMAXSIZE-1;
|
|
}
|
|
if(marker) {
|
|
name[marker_pos] = marker;
|
|
} else {
|
|
marker_pos++;
|
|
}
|
|
int appendix = 1;
|
|
int appendix_len = 2;
|
|
int namelen = pstrlen(name);
|
|
while(find_existing_file(type, image, name, &i, &t, &s, &o)) {
|
|
marker_pos = namelen + appendix_len;
|
|
if(marker_pos > FILENAMEMAXSIZE-1) {
|
|
marker_pos = FILENAMEMAXSIZE-1;
|
|
if(!marker) {
|
|
marker_pos++;
|
|
}
|
|
}
|
|
if(marker) {
|
|
name[marker_pos] = marker;
|
|
}
|
|
int appendix_pos = marker_pos - appendix_len;
|
|
name[appendix_pos] = '.';
|
|
pputnum(name + appendix_pos + 1, appendix);
|
|
appendix++;
|
|
if(appendix == 10) {
|
|
appendix_len++;
|
|
}
|
|
if(appendix == 100) {
|
|
appendix_len++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Count number of blocks in file, assumes valid t/s chain */
|
|
static int
|
|
count_blocks(image_type type, unsigned char *image, unsigned int track, int sector)
|
|
{
|
|
int num = 0;
|
|
while(track != 0) {
|
|
int b = linear_sector(type, track, sector);
|
|
if(b < 0) {
|
|
break;
|
|
}
|
|
int block_offset = b * BLOCKSIZE;
|
|
track = image[block_offset + TRACKLINKOFFSET];
|
|
sector = image[block_offset + SECTORLINKOFFSET];
|
|
num++;
|
|
};
|
|
return num;
|
|
}
|
|
|
|
/* Overwrites all blocks that are marked as potentially allocated with the given value */
|
|
static void
|
|
mark_sector_chain(image_type type, unsigned char *image, char* atab, unsigned int track, int sector, unsigned int last_track, int last_sector, int mark)
|
|
{
|
|
if(track <= 0 || track > image_num_tracks(type) || sector < 0 || sector > num_sectors(type, track)) {
|
|
return;
|
|
}
|
|
while(1) {
|
|
int b = linear_sector(type, track, sector);
|
|
atab[b] = mark;
|
|
if(track == last_track && sector == last_sector) {
|
|
break;
|
|
}
|
|
int block_offset = b * BLOCKSIZE;
|
|
track = image[block_offset + TRACKLINKOFFSET];
|
|
sector = image[block_offset + SECTORLINKOFFSET];
|
|
};
|
|
}
|
|
|
|
/* Validates sector chain starting at track/sector, returns how and in which t/s the chain ends */
|
|
static int
|
|
validate_sector_chain(image_type type, unsigned char* image, char* atab, unsigned int track, int sector, unsigned int *last_track, int *last_sector)
|
|
{
|
|
*last_track = track;
|
|
*last_sector = sector;
|
|
if(track == 0 || track > image_num_tracks(type)) {
|
|
return FIRST_BROKEN;
|
|
}
|
|
if(sector >= num_sectors(type, track)) {
|
|
return FIRST_BROKEN;
|
|
}
|
|
if(atab[linear_sector(type, track, sector)] != UNALLOCATED) {
|
|
return FIRST_BROKEN;
|
|
}
|
|
while (1) {
|
|
atab[linear_sector(type, track, sector)] = POTENTIALLYALLOCATED;
|
|
int block_offset = linear_sector(type, track, sector) * BLOCKSIZE;
|
|
unsigned int next_track = image[block_offset + TRACKLINKOFFSET];
|
|
int next_sector = image[block_offset + SECTORLINKOFFSET];
|
|
if (next_track == 0) {
|
|
return VALID; /* end of valid chain */
|
|
}
|
|
if (next_track > image_num_tracks(type)) {
|
|
return ILLEGAL_TRACK;
|
|
}
|
|
if (next_sector >= num_sectors(type, next_track)) {
|
|
return ILLEGAL_SECTOR;
|
|
}
|
|
switch(atab[linear_sector(type, next_track, next_sector)]) {
|
|
case POTENTIALLYALLOCATED:
|
|
return LOOP;
|
|
case ALLOCATED:
|
|
return COLLISION;
|
|
case FILESTART:
|
|
return CHAINED;
|
|
case FILESTART_TRUNCATED:
|
|
return CHAINED_TRUNCATED;
|
|
}
|
|
track = next_track;
|
|
sector = next_sector;
|
|
*last_track = track;
|
|
*last_sector = sector;
|
|
}
|
|
}
|
|
|
|
static void
|
|
init_atab(image_type type, unsigned char* image, char* atab)
|
|
{
|
|
int dt = dirtrack(type);
|
|
int ds = (type == IMAGE_D81) ? 3 : 1;
|
|
int offset = 0;
|
|
char *blockmap = calloc(image_num_blocks(type), sizeof(char));
|
|
if(blockmap == NULL) {
|
|
fprintf(stderr, "ERROR: Memory allocation error\n");
|
|
exit(-1);
|
|
}
|
|
|
|
do {
|
|
int db = linear_sector(type, dt, ds);
|
|
atab[db] = ALLOCATED;
|
|
int filetype = image[db * BLOCKSIZE + offset + FILETYPEOFFSET] & 0xf;
|
|
if(filetype != FILETYPEDEL) {
|
|
int track = image[db * BLOCKSIZE + offset + FILETRACKOFFSET];
|
|
int sector = image[db * BLOCKSIZE + offset + FILESECTOROFFSET];
|
|
unsigned int last_track;
|
|
int last_sector;
|
|
int error = validate_sector_chain(type, image, atab, track, sector, &last_track, &last_sector);
|
|
if(error != VALID) {
|
|
printf("WARNING: file ");
|
|
print_filename(stdout, &image[db * BLOCKSIZE + offset + FILENAMEOFFSET]);
|
|
printf(" seems corrupt (%s)\n", error_name[error]);
|
|
}
|
|
mark_sector_chain(type, image, atab, track, sector, last_track, last_sector, ALLOCATED);
|
|
}
|
|
} while(next_dir_entry(type, image, &dt, &ds, &offset, blockmap));
|
|
free(blockmap);
|
|
}
|
|
|
|
/* Write atab into BAM */
|
|
static void
|
|
write_atab(image_type type, unsigned char* image, char* atab)
|
|
{
|
|
for(unsigned int t = 1; t <= image_num_tracks(type); t++) {
|
|
for(int s = 0; s < num_sectors(type, t); s++) {
|
|
int b = linear_sector(type, t, s);
|
|
int free = (atab[b] == UNALLOCATED);
|
|
if(!free) {
|
|
/* keep allocated BAM entries, only add new ones */
|
|
mark_sector(type, image, t, s, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Try to undelete a file given the directory entry, returns true if successful */
|
|
static bool
|
|
undelete_file(image_type type, unsigned char* image, int dt, int ds, int offset, char* atab, int level)
|
|
{
|
|
unsigned int last_track;
|
|
int last_sector;
|
|
char marker = 0;
|
|
int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE;
|
|
int track = image[dirblock + offset + FILETRACKOFFSET];
|
|
int sector = image[dirblock + offset + FILESECTOROFFSET];
|
|
int error = validate_sector_chain(type, image, atab, track, sector, &last_track, &last_sector);
|
|
if(error == VALID || ((level == RESTORE_DIR_ONLY || level >= RESTORE_INVALID_FILES) && error != FIRST_BROKEN)) {
|
|
unsigned char name[17];
|
|
if(error != VALID) {
|
|
if(level != RESTORE_DIR_ONLY) {
|
|
/* terminate chain */
|
|
int block_offset = linear_sector(type, last_track, last_sector) * BLOCKSIZE;
|
|
image[block_offset + TRACKLINKOFFSET] = 0;
|
|
marker = '<';
|
|
}
|
|
}
|
|
/* restore dir entry */
|
|
memcpy(&name, &image[dirblock + offset + FILENAMEOFFSET], 16);
|
|
name[16] = 0;
|
|
int b = linear_sector(type, track, sector);
|
|
int address = image[b * BLOCKSIZE + 2] + 256 * image[b * BLOCKSIZE + 3];
|
|
generate_unique_filename(type, image, name, track, sector, address, marker);
|
|
memcpy(&image[dirblock + offset + FILENAMEOFFSET], &name, 16);
|
|
image[dirblock + offset + FILETYPEOFFSET] = 0x82; /* original file type is lost, use closed PRG instead */
|
|
mark_sector_chain(type, image, atab, track, sector, last_track, last_sector, ALLOCATED);
|
|
if(level != RESTORE_DIR_ONLY) {
|
|
int size = count_blocks(type, image, track, sector);
|
|
image[dirblock + offset + FILEBLOCKSHIOFFSET] = size / 256;
|
|
image[dirblock + offset + FILEBLOCKSLOOFFSET] = size % 256;
|
|
}
|
|
return true;
|
|
}
|
|
if(error != FIRST_BROKEN) {
|
|
mark_sector_chain(type, image, atab, track, sector, last_track, last_sector, UNALLOCATED);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* search for scratched directory entries and restore them */
|
|
static int
|
|
undelete(image_type type, unsigned char* image, char* atab, int level)
|
|
{
|
|
int dt = dirtrack(type);
|
|
int ds = (type == IMAGE_D81) ? 3 : 1;
|
|
int nsectors = num_sectors(type, dt);
|
|
int offset = 0;
|
|
int num_undeleted = 0;
|
|
int final_dt, final_ds; /* last linked directory sector and track */
|
|
char *blockmap = calloc(image_num_blocks(type), sizeof(char));
|
|
if(blockmap == NULL) {
|
|
fprintf(stderr, "ERROR: Memory allocation error\n");
|
|
exit(-1);
|
|
}
|
|
|
|
bool* searched = calloc(nsectors, sizeof(bool));
|
|
if(searched == NULL) {
|
|
fprintf(stderr, "ERROR: error allocating memory");
|
|
exit(-1);
|
|
}
|
|
|
|
/* go through directory sector chain */
|
|
do {
|
|
if(dt == dirtrack(type)) {
|
|
searched[ds] = true;
|
|
}
|
|
final_dt = dt;
|
|
final_ds = ds;
|
|
int db = linear_sector(type, dt, ds);
|
|
mark_sector(type, image, dt, ds, 0); /* needs to be updated in BAM, so that dir allocation works correctly later */
|
|
atab[db] = ALLOCATED;
|
|
int dirblock = db * BLOCKSIZE;
|
|
int filetype = image[dirblock + offset + FILETYPEOFFSET];
|
|
if(filetype == FILETYPEDEL && image[dirblock + offset + FILENAMEOFFSET] != 0 && undelete_file(type, image, dt, ds, offset, atab, level)) { /* filename starting with 0 means that likely there was no file */
|
|
num_undeleted++;
|
|
}
|
|
} while(next_dir_entry(type, image, &dt, &ds, &offset, blockmap));
|
|
free(blockmap);
|
|
|
|
/* search for unlinked dir sectors */
|
|
dt = dirtrack(type);
|
|
for(ds = (type == IMAGE_D81) ? 3 : 1; ds < nsectors; ds++) {
|
|
if(!searched[ds]) {
|
|
int found = 1;
|
|
for(offset = 0; offset < DIRENTRIESPERBLOCK*DIRENTRYSIZE; offset += DIRENTRYSIZE) {
|
|
if(!undelete_file(type, image, dt, ds, offset, atab, level)) {
|
|
found = 0;
|
|
break;
|
|
}
|
|
}
|
|
if(found) {
|
|
num_undeleted += 8;
|
|
if(!quiet) {
|
|
printf("Relinking directory sector %d\n", ds);
|
|
}
|
|
/* link found dir sector */
|
|
int block_offset = linear_sector(type, final_dt, final_ds) * BLOCKSIZE;
|
|
image[block_offset + TRACKLINKOFFSET] = dt;
|
|
image[block_offset + SECTORLINKOFFSET] = ds;
|
|
/* terminate found dir sector */
|
|
int db = linear_sector(type, dt, ds);
|
|
block_offset = db * BLOCKSIZE;
|
|
image[block_offset + TRACKLINKOFFSET] = 0;
|
|
final_dt = dt;
|
|
final_ds = ds;
|
|
mark_sector(type, image, dt, ds, 0); /* needs to be updated in BAM, so that dir allocation works correctly later */
|
|
atab[db] = ALLOCATED;
|
|
}
|
|
}
|
|
}
|
|
free(searched);
|
|
write_atab(type, image, atab);
|
|
return num_undeleted;
|
|
}
|
|
|
|
/* add new DIR entries for wild chains */
|
|
static void
|
|
add_wild_to_dir(image_type type, unsigned char* image, char* atab, imagefile files[])
|
|
{
|
|
/* create a DIR entry for each FILESTART */
|
|
for(unsigned int t = 1; t <= image_num_tracks(type); t++) {
|
|
for(int s = 0; s < num_sectors(type, t); s++) {
|
|
int b = linear_sector(type, t, s);
|
|
if(atab[b] == FILESTART || atab[b] == FILESTART_TRUNCATED) {
|
|
char marker = atab[b] == FILESTART ? 0 : '<';
|
|
unsigned char name[17];
|
|
name[0] = 0xa0;
|
|
int dir_index, dir_sector, dir_offset;
|
|
atab[b] = ALLOCATED;
|
|
new_dir_slot(type, image, (type == IMAGE_D81 ? 1 : 3), 0, &dir_index, &dir_sector, &dir_offset, files); /* TODO: handle full directory more gracefully */
|
|
int db = linear_sector(type, dirtrack(type), dir_sector);
|
|
atab[db] = ALLOCATED; /* make sure that potentially new dir block is marked as used */
|
|
int offset = db * BLOCKSIZE + dir_offset;
|
|
image[offset + FILETYPEOFFSET] = 0x82; /* closed PRG */
|
|
image[offset + FILETRACKOFFSET] = t;
|
|
image[offset + FILESECTOROFFSET] = s;
|
|
image[offset + FILENAMEOFFSET] = 0xa0; /* no proposed filename */
|
|
int address = image[b * BLOCKSIZE + 2] + 256 * image[b * BLOCKSIZE + 3];
|
|
generate_unique_filename(type, image, name, t, s, address, marker);
|
|
memcpy(&image[offset + FILENAMEOFFSET], name, 16);
|
|
int size = count_blocks(type, image, t, s);
|
|
image[offset + FILEBLOCKSHIOFFSET] = size / 256;
|
|
image[offset + FILEBLOCKSLOOFFSET] = size % 256;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* search for wild valid chains of unallocated sectors */
|
|
static int
|
|
undelete_wild(image_type type, unsigned char* image, char* atab, int level, imagefile files[])
|
|
{
|
|
int num_undeleted = 0;
|
|
int max_bam_sector = (type == IMAGE_D81) ? 2 : 0;
|
|
unsigned int dt = dirtrack(type);
|
|
/* search for well terminated sector chains */
|
|
for(unsigned int t = 1; t <= image_num_tracks(type); t++) {
|
|
for(int s = 0; s < num_sectors(type, t); s++) {
|
|
int b = linear_sector(type, t, s);
|
|
if(atab[b] == UNALLOCATED && (t != dt || s > max_bam_sector)) { /* ignore bam sectors */
|
|
unsigned int last_track;
|
|
int last_sector;
|
|
int error = validate_sector_chain(type, image, atab, t, s, &last_track, &last_sector);
|
|
int last_block = linear_sector(type, last_track, last_sector);
|
|
if(error == CHAINED || error == CHAINED_TRUNCATED) {
|
|
mark_sector_chain(type, image, atab, t, s, last_track, last_sector, ALLOCATED);
|
|
atab[b] = (error == CHAINED) ? FILESTART : CHAINED_TRUNCATED;
|
|
int chained_track = image[last_block * BLOCKSIZE + TRACKLINKOFFSET];
|
|
int chained_sector = image[last_block * BLOCKSIZE + SECTORLINKOFFSET];
|
|
int chained_block = linear_sector(type, chained_track, chained_sector);
|
|
atab[chained_block] = ALLOCATED; /* overwrite FILESTART */
|
|
} else if(error == VALID) {
|
|
if(last_track == t && last_sector == s) {
|
|
if(level == RESTORE_INVALID_SINGLES) {
|
|
/* single block files should have all 0 after file end */
|
|
bool valid = false;
|
|
int last_byte = image[last_block * BLOCKSIZE + SECTORLINKOFFSET];
|
|
if(image[last_block * BLOCKSIZE + TRACKLINKOFFSET] == 0 && last_byte >= 2) {
|
|
valid = true;
|
|
for(int i = last_byte + 1; i <= 255; i++) {
|
|
if(image[last_block * BLOCKSIZE + i] != 0) {
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(valid) {
|
|
atab[b] = FILESTART;
|
|
num_undeleted++;
|
|
} else {
|
|
atab[b] = UNALLOCATED;
|
|
}
|
|
atab[b] = valid ? FILESTART : UNALLOCATED;
|
|
} else {
|
|
atab[b] = UNALLOCATED;
|
|
}
|
|
} else {
|
|
mark_sector_chain(type, image, atab, t, s, last_track, last_sector, ALLOCATED);
|
|
atab[b] = FILESTART;
|
|
image[last_block * BLOCKSIZE + TRACKLINKOFFSET] = 0;
|
|
num_undeleted++;
|
|
}
|
|
} else {
|
|
if(error != FIRST_BROKEN) {
|
|
mark_sector_chain(type, image, atab, t, s, last_track, last_sector, UNALLOCATED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(num_undeleted) {
|
|
write_atab(type, image, atab);
|
|
add_wild_to_dir(type, image, atab, files);
|
|
}
|
|
return num_undeleted;
|
|
}
|
|
|
|
/* search for wild invalid chains of unallocated sectors and fix them */
|
|
static int
|
|
undelete_fix_wild(image_type type, unsigned char* image, char* atab, imagefile files[])
|
|
{
|
|
int num_undeleted = 0;
|
|
int max_bam_sector = (type == IMAGE_D81) ? 2 : 0;
|
|
unsigned int dt = dirtrack(type);
|
|
/* search for broken sector chains */
|
|
for(unsigned int t = 1; t <= image_num_tracks(type); t++) {
|
|
for(int s = 0; s < num_sectors(type, t); s++) {
|
|
int b = linear_sector(type, t, s);
|
|
if(atab[b] == UNALLOCATED && (t != dt || s > max_bam_sector)) { /* ignore bam sectors */
|
|
unsigned int last_track;
|
|
int last_sector;
|
|
int error = validate_sector_chain(type, image, atab, t, s, &last_track, &last_sector);
|
|
int last_block = linear_sector(type, last_track, last_sector);
|
|
if(error == CHAINED || error == CHAINED_TRUNCATED) {
|
|
mark_sector_chain(type, image, atab, t, s, last_track, last_sector, ALLOCATED);
|
|
atab[b] = (error == CHAINED) ? FILESTART : FILESTART_TRUNCATED;
|
|
int chained_track = image[last_block * BLOCKSIZE + TRACKLINKOFFSET];
|
|
int chained_sector = image[last_block * BLOCKSIZE + SECTORLINKOFFSET];
|
|
int chained_block = linear_sector(type, chained_track, chained_sector);
|
|
atab[chained_block] = ALLOCATED; /* overwrite FILESTART */
|
|
} else if(t != last_track || s != last_sector) {
|
|
mark_sector_chain(type, image, atab, t, s, last_track, last_sector, ALLOCATED);
|
|
atab[b] = FILESTART_TRUNCATED;
|
|
image[last_block * BLOCKSIZE + TRACKLINKOFFSET] = 0;
|
|
image[last_block * BLOCKSIZE + SECTORLINKOFFSET] = 255;
|
|
num_undeleted++;
|
|
} else {
|
|
if(error != FIRST_BROKEN) {
|
|
mark_sector_chain(type, image, atab, t, s, last_track, last_sector, UNALLOCATED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(num_undeleted) {
|
|
write_atab(type, image, atab);
|
|
add_wild_to_dir(type, image, atab, files);
|
|
}
|
|
return num_undeleted;
|
|
}
|
|
|
|
/* Tries to restore any deleted or formatted files */
|
|
static void
|
|
restore(image_type type, unsigned char* image, int level, imagefile files[])
|
|
{
|
|
int num_undeleted = 0;
|
|
/* create block allocation table */
|
|
char *atab = (char *)calloc(image_num_blocks(type), sizeof(char));
|
|
if (atab == NULL) {
|
|
fprintf(stderr, "ERROR: error allocating memory");
|
|
exit(-1);
|
|
}
|
|
init_atab(type, image, atab);
|
|
if(level == RESTORE_DIR_ONLY) {
|
|
num_undeleted += undelete(type, image, atab, RESTORE_DIR_ONLY);
|
|
} else {
|
|
num_undeleted += undelete(type, image, atab, RESTORE_VALID_FILES);
|
|
if(level >= RESTORE_VALID_CHAINS) {
|
|
num_undeleted += undelete_wild(type, image, atab, RESTORE_VALID_CHAINS, files);
|
|
}
|
|
if(level >= RESTORE_INVALID_FILES) {
|
|
num_undeleted += undelete(type, image, atab, RESTORE_INVALID_FILES);
|
|
}
|
|
if(level >= RESTORE_INVALID_CHAINS) {
|
|
num_undeleted += undelete_fix_wild(type, image, atab, files);
|
|
}
|
|
if(level >= RESTORE_INVALID_SINGLES) {
|
|
num_undeleted += undelete_wild(type, image, atab, RESTORE_INVALID_SINGLES, files);
|
|
}
|
|
}
|
|
free(atab);
|
|
if(num_undeleted) {
|
|
modified = 1;
|
|
}
|
|
if(!quiet) {
|
|
printf("%d files undeleted", num_undeleted);
|
|
if(num_undeleted) {
|
|
printf(", '<' at filename end marks truncated files");
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
/* Prints a command line to create dir art like the given image */
|
|
static void
|
|
convert_to_commandline(image_type type, unsigned char* image)
|
|
{
|
|
char *blockmap = calloc(image_num_blocks(type), sizeof(char));
|
|
if(blockmap == NULL) {
|
|
fprintf(stderr, "ERROR: Memory allocation error\n");
|
|
exit(-1);
|
|
}
|
|
printf("\nCommandline to create directory art: -m -n \"");
|
|
unsigned int bam = linear_sector(type, dirtrack(type), 0) * BLOCKSIZE;
|
|
print_filename_with_escapes(image + bam + get_header_offset(type), FILENAMEMAXSIZE);
|
|
printf("\" -i \"");
|
|
print_filename_with_escapes(image + bam + get_id_offset(type), 5);
|
|
printf("\" ");
|
|
|
|
int ds = (type == IMAGE_D81) ? 3 : 1;
|
|
int dt = dirtrack(type);
|
|
int offset = 0;
|
|
do {
|
|
int dirblock = linear_sector(type, dt, ds) * BLOCKSIZE + offset;
|
|
int filetype = image[dirblock + FILETYPEOFFSET];
|
|
if (filetype) {
|
|
if(filetype != 0x82) {
|
|
printf("-T %d ", filetype);
|
|
}
|
|
int size = image[dirblock + FILEBLOCKSLOOFFSET] + 256 * image[dirblock + FILEBLOCKSHIOFFSET];
|
|
if(size != 0) {
|
|
printf("-B %d ", size);
|
|
}
|
|
unsigned char *filename = (unsigned char *) image + dirblock + FILENAMEOFFSET;
|
|
printf("-N -f \"");
|
|
print_filename_with_escapes(filename, FILENAMEMAXSIZE);
|
|
printf("\" -L ");
|
|
}
|
|
} while (next_dir_entry(type, image, &dt, &ds, &offset, blockmap));
|
|
free(blockmap);
|
|
printf("\n\n");
|
|
}
|
|
|
|
/* Performs strict CBM DOS validation on the image */
|
|
static void
|
|
validate(image_type type, unsigned char* image)
|
|
{
|
|
/* create block allocation table */
|
|
char *atab = (char *)calloc(image_num_blocks(type), sizeof(int));
|
|
if (atab == NULL) {
|
|
fprintf(stderr, "ERROR: error allocating memory");
|
|
exit(-1);
|
|
}
|
|
/* check format specifier */
|
|
int format = image[linear_sector(type, dirtrack(type), 0) * BLOCKSIZE + 2];
|
|
if (format != 0x41) {
|
|
fprintf(stderr, "ERROR: validation failed, format specifier in directory (0x%02x) does not specify 1541 (0x41)\n", format);
|
|
exit(-1);
|
|
}
|
|
/* check each directory entry and set block allocation table */
|
|
atab[linear_sector(type, dirtrack(type), 0)] = ALLOCATED;
|
|
unsigned int dt = dirtrack(type);
|
|
int dirsector = 1;
|
|
unsigned int start_track = 1;
|
|
while (start_track != 0) {
|
|
atab[linear_sector(type, dt, dirsector)] = ALLOCATED;
|
|
int dirblock = linear_sector(type, dt, dirsector) * BLOCKSIZE;
|
|
for (int direntry = 0; direntry < DIRENTRIESPERBLOCK; direntry++) {
|
|
int entryOffset = direntry * DIRENTRYSIZE;
|
|
int filetype = image[dirblock + entryOffset + FILETYPEOFFSET] & 0xf;
|
|
if (filetype > 4) {
|
|
fprintf(stderr, "ERROR: validation failed, illegal file type (0x%02x) in directory\n", filetype);
|
|
exit(-1);
|
|
}
|
|
if (filetype != 0) { /* skip deleted entries */
|
|
start_track = image[dirblock + entryOffset + FILETRACKOFFSET];
|
|
int start_sector = image[dirblock + entryOffset + FILESECTOROFFSET];
|
|
if (start_track == 0 || start_track > image_num_tracks(type)) {
|
|
fprintf(stderr, "ERROR: validation failed, illegal track reference (%u) in directory\n", start_track);
|
|
exit(-1);
|
|
}
|
|
if (start_sector >= num_sectors(type, start_track)) {
|
|
fprintf(stderr, "ERROR: validation failed, illegal sector reference (track %u, sector %d) in directory\n", start_track, start_sector);
|
|
exit(-1);
|
|
}
|
|
if (atab[linear_sector(type, start_track, start_sector)] == ALLOCATED) {
|
|
fprintf(stderr, "ERROR: validation failed, file starts in the middle of another file (track %u, sector %d)\n", start_track, start_sector);
|
|
exit(-1);
|
|
}
|
|
if (atab[linear_sector(type, start_track, start_sector)] != FILESTART) { /* loop files are allowed */
|
|
unsigned int error_track;
|
|
int error_sector;
|
|
int result = validate_sector_chain(type, image, atab, start_track, start_sector, &error_track, &error_sector);
|
|
switch(result) {
|
|
case ILLEGAL_TRACK:
|
|
fprintf(stderr, "ERROR: validation failed, illegal track reference in file sector chain at track %d, sector %d\n", error_track, error_sector);
|
|
exit(-1);
|
|
case ILLEGAL_SECTOR:
|
|
fprintf(stderr, "ERROR: validation failed, illegal sector reference in file sector chain at track %d, sector %d\n", error_track, error_sector);
|
|
exit(-1);
|
|
case LOOP:
|
|
fprintf(stderr, "ERROR: validation failed, loop in file sector chain at track %d, sector %d\n", error_track, error_sector);
|
|
exit(-1);
|
|
case COLLISION:
|
|
case CHAINED:
|
|
fprintf(stderr, "ERROR: validation failed, collision with existing file in file sector chain at track %d, sector %d\n", error_track, error_sector);
|
|
exit(-1);
|
|
}
|
|
atab[linear_sector(type, start_track, start_sector)] = FILESTART;
|
|
}
|
|
}
|
|
}
|
|
dt = image[dirblock + TRACKLINKOFFSET];
|
|
dirsector = image[dirblock + SECTORLINKOFFSET];
|
|
if (dt == 0) {
|
|
break;
|
|
}
|
|
}
|
|
/* check BAM for consistency with block allocation table */
|
|
for (unsigned int t = 1; t <= image_num_tracks(type); t++) {
|
|
unsigned char* bitmap = image + get_bam_offset(type, t);
|
|
int num_free = 0;
|
|
for (int s = 0; s < num_sectors(type, t); s++) {
|
|
int atab_used = (atab[linear_sector(type, t, s)] != UNALLOCATED);
|
|
int bam_used = ((bitmap[s >> 3] & (1 << (s & 7))) == 0);
|
|
num_free += (1 - bam_used);
|
|
if (bam_used != atab_used) {
|
|
fprintf(stderr, "ERROR: validation failed, BAM (%s) is not consistent with files (%s) for track %u sector %d\n", bam_used ? "used" : "free", atab_used ? "used" : "free", t, s);
|
|
exit(-1);
|
|
}
|
|
}
|
|
if (*(bitmap - 1) != num_free) {
|
|
fprintf(stderr, "ERROR: validation failed, BAM number of free blocks (%d) is not consistent with bitmap (%#02x%#02x%#02x) for track %u\n", *(bitmap - 1), *bitmap, *(bitmap + 1), *(bitmap + 2), t);
|
|
exit(-1);
|
|
}
|
|
}
|
|
free(atab);
|
|
if(!quiet) {
|
|
fprintf(stderr, "CBM DOS validation passed\n");
|
|
}
|
|
}
|
|
|
|
int
|
|
main(int argc, char* argv[])
|
|
{
|
|
imagefile files[MAXNUMFILES_D81];
|
|
memset(files, 0, sizeof files);
|
|
|
|
image_type type = IMAGE_D64;
|
|
char* imagepath = NULL;
|
|
char* filename_g64 = NULL;
|
|
unsigned char* header = (unsigned char*)"cc1541";
|
|
unsigned char* id = (unsigned char*)"00 2a";
|
|
unsigned char* bam_message = NULL;
|
|
int dirtracksplit = 1;
|
|
int usedirtrack = 0;
|
|
unsigned int shadowdirtrack = 0;
|
|
|
|
int default_first_sector_new_track = 0;
|
|
int first_sector_new_track = 0;
|
|
int defaultSectorInterleave = 10;
|
|
int sectorInterleave = 0;
|
|
int dir_sector_interleave = 3;
|
|
int numdirblocks = 2;
|
|
int nrSectorsShown = -1;
|
|
unsigned char* filename = NULL;
|
|
int set_header = 0;
|
|
int nooverwrite = 0;
|
|
int dovalidate = 0;
|
|
int restore_level = -1;
|
|
int ignore_collision = 0;
|
|
int filetype = 0x82; /* default is closed PRG */
|
|
bool filetype_set = false;
|
|
bool print_art_commandline = false;
|
|
|
|
/* flags to detect illegal settings for Transwarp or D81 */
|
|
int transwarp_set = 0;
|
|
int sector_interleave_set = 0;
|
|
int default_sector_interleave_set = 0;
|
|
int file_start_sector_set = 0;
|
|
int new_track_start_sector_set = 0;
|
|
|
|
int retval = 0;
|
|
|
|
int i, j;
|
|
|
|
if (argc == 1 || strcmp(argv[argc-1], "-h") == 0) {
|
|
usage();
|
|
}
|
|
for (j = 1; j < argc - 1; j++) {
|
|
if (strcmp(argv[j], "-n") == 0) {
|
|
if (argc < j + 2) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -n\n");
|
|
return -1;
|
|
}
|
|
header = (unsigned char*)argv[++j];
|
|
set_header = 1;
|
|
modified = 1;
|
|
} else if (strcmp(argv[j], "-i") == 0) {
|
|
if (argc < j + 2) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -i\n");
|
|
return -1;
|
|
}
|
|
id = (unsigned char*)argv[++j];
|
|
set_header = 1;
|
|
modified = 1;
|
|
} else if (strcmp(argv[j], "-H") == 0) {
|
|
if (argc < j + 2) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -H\n");
|
|
return -1;
|
|
}
|
|
bam_message = (unsigned char*)argv[++j];
|
|
set_header = 1;
|
|
modified = 1;
|
|
} else if (strcmp(argv[j], "-M") == 0) {
|
|
if ((argc < j + 2) || !sscanf(argv[++j], "%d", &max_hash_length)) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -M\n");
|
|
return -1;
|
|
}
|
|
if ((max_hash_length < 1) || (max_hash_length > FILENAMEMAXSIZE)) {
|
|
fprintf(stderr, "ERROR: Hash computation maximum filename length %d specified\n", max_hash_length);
|
|
return -1;
|
|
}
|
|
} else if (strcmp(argv[j], "-m") == 0) {
|
|
ignore_collision = 1;
|
|
} else if (strcmp(argv[j], "-F") == 0) {
|
|
if ((argc < j + 2) || !sscanf(argv[++j], "%d", &first_sector_new_track)) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -F\n");
|
|
return -1;
|
|
}
|
|
new_track_start_sector_set = 1;
|
|
} else if (strcmp(argv[j], "-S") == 0) {
|
|
if ((argc < j + 2) || !sscanf(argv[++j], "%d", &defaultSectorInterleave)) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -S\n");
|
|
return -1;
|
|
}
|
|
if(defaultSectorInterleave < 1 || defaultSectorInterleave > 21) {
|
|
fprintf(stderr, "ERROR: Illegal value for -S\n");
|
|
return -1;
|
|
}
|
|
default_sector_interleave_set = 1;
|
|
} else if (strcmp(argv[j], "-s") == 0) {
|
|
if ((argc < j + 2) || !sscanf(argv[++j], "%d", §orInterleave)) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -s\n");
|
|
return -1;
|
|
}
|
|
if(sectorInterleave < 1 || sectorInterleave > 21) {
|
|
fprintf(stderr, "ERROR: Illegal value for -s\n");
|
|
return -1;
|
|
}
|
|
sector_interleave_set = 1;
|
|
} else if (strcmp(argv[j], "-f") == 0) {
|
|
if (argc < j + 2) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -f\n");
|
|
return -1;
|
|
}
|
|
filename = (unsigned char*)argv[++j];
|
|
} else if (strcmp(argv[j], "-e") == 0) {
|
|
files[num_files].mode |= MODE_SAVETOEMPTYTRACKS;
|
|
} else if (strcmp(argv[j], "-E") == 0) {
|
|
files[num_files].mode |= MODE_FITONSINGLETRACK;
|
|
} else if (strcmp(argv[j], "-r") == 0) {
|
|
if ((argc < j + 2) || !sscanf(argv[++j], "%d", &i)) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -r\n");
|
|
return -1;
|
|
}
|
|
if ((i < 1) || (((i << MODE_MIN_TRACK_SHIFT) & MODE_MIN_TRACK_MASK) != (i << MODE_MIN_TRACK_SHIFT))) {
|
|
fprintf(stderr, "ERROR: Invalid minimum track %d specified\n", i);
|
|
return -1;
|
|
}
|
|
files[num_files].mode = (files[num_files].mode & ~MODE_MIN_TRACK_MASK) | (i << MODE_MIN_TRACK_SHIFT);
|
|
} else if (strcmp(argv[j], "-b") == 0) {
|
|
if ((argc < j + 2) || !sscanf(argv[++j], "%d", &i)) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -b\n");
|
|
return -1;
|
|
}
|
|
if ((i < 0) || (i >= num_sectors(type, 1))) {
|
|
fprintf(stderr, "ERROR: Invalid beginning sector %d specified\n", i);
|
|
return -1;
|
|
}
|
|
files[num_files].mode = (files[num_files].mode & ~MODE_BEGINNING_SECTOR_MASK) | (i + 1);
|
|
file_start_sector_set = 1;
|
|
} else if (strcmp(argv[j], "-c") == 0) {
|
|
files[num_files].mode |= MODE_SAVECLUSTEROPTIMIZED;
|
|
} else if (strcmp(argv[j], "-o") == 0) {
|
|
nooverwrite = 1;
|
|
} else if (strcmp(argv[j], "-V") == 0) {
|
|
dovalidate = 1;
|
|
} else if (strcmp(argv[j], "-T") == 0) {
|
|
if (argc < j + 2) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -T\n");
|
|
return -1;
|
|
}
|
|
if (strcmp(argv[j + 1], "DEL") == 0) {
|
|
filetype = (filetype & 0xf0) | FILETYPEDEL;
|
|
} else if (strcmp(argv[j + 1], "SEQ") == 0) {
|
|
filetype = (filetype & 0xf0) | FILETYPESEQ;
|
|
} else if (strcmp(argv[j + 1], "PRG") == 0) {
|
|
filetype = (filetype & 0xf0) | FILETYPEPRG;
|
|
} else if (strcmp(argv[j + 1], "USR") == 0) {
|
|
filetype = (filetype & 0xf0) | FILETYPEUSR;
|
|
} else if (strcmp(argv[j + 1], "REL") == 0) {
|
|
filetype = (filetype & 0xf0) | FILETYPEREL;
|
|
} else {
|
|
char* dummy;
|
|
filetype = strtol(argv[j + 1], &dummy, 10);
|
|
if(dummy == argv[j + 1] || *dummy != 0 || filetype < 0 || filetype > 255) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -T\n");
|
|
return -1;
|
|
}
|
|
}
|
|
filetype_set = true;
|
|
j++;
|
|
} else if (strcmp(argv[j], "-O") == 0) {
|
|
filetype &= 0x7f;
|
|
} else if (strcmp(argv[j], "-P") == 0) {
|
|
filetype |= 0x40;
|
|
} else if (strcmp(argv[j], "-N") == 0) {
|
|
files[num_files].force_new = 1;
|
|
} else if (strcmp(argv[j], "-K") == 0) {
|
|
if (argc < j + 2) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -K\n");
|
|
return -1;
|
|
}
|
|
evalhexescape((unsigned char *) argv[++j], files[num_files].key, TRANSWARPKEYSIZE, 0);
|
|
files[num_files].have_key = true;
|
|
} else if ((strcmp(argv[j], "-w") == 0)
|
|
|| (strcmp(argv[j], "-W") == 0)) {
|
|
if (argc < j + 2) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for %s\n", argv[j]);
|
|
return -1;
|
|
}
|
|
files[num_files].alocalname = (unsigned char*)argv[j + 1];
|
|
if (filename == NULL) {
|
|
ascii2petscii(basename(files[num_files].alocalname), files[num_files].pfilename, FILENAMEMAXSIZE); /* do not eval escapes when converting the filename, as the local filename could contain the escape char */
|
|
} else {
|
|
evalhexescape(filename, files[num_files].pfilename, FILENAMEMAXSIZE, FILENAMEEMPTYCHAR);
|
|
}
|
|
files[num_files].sectorInterleave = sectorInterleave ? sectorInterleave : defaultSectorInterleave;
|
|
files[num_files].first_sector_new_track = first_sector_new_track;
|
|
files[num_files].nrSectorsShown = nrSectorsShown;
|
|
files[num_files].filetype = filetype;
|
|
files[num_files].direntryindex = -1;
|
|
|
|
if (strcmp(argv[j], "-W") == 0) {
|
|
if(nrSectorsShown != -1) {
|
|
fprintf(stderr, "ERROR: -B cannot be used for Transwarp files\n");
|
|
return -1;
|
|
}
|
|
transwarp_set = true;
|
|
if(!filetype_set && files[num_files].have_key) {
|
|
files[num_files].filetype = (filetype & 0xf0) | FILETYPEUSR | FILETYPETRANSWARPMASK;
|
|
} else {
|
|
files[num_files].filetype = filetype | FILETYPETRANSWARPMASK;
|
|
}
|
|
files[num_files].sectorInterleave = 1;
|
|
}
|
|
|
|
first_sector_new_track = default_first_sector_new_track;
|
|
filename = NULL;
|
|
sectorInterleave = 0;
|
|
nrSectorsShown = -1;
|
|
filetype = 0x82;
|
|
filetype_set = false;
|
|
num_files++;
|
|
modified = 1;
|
|
j++;
|
|
} else if (strcmp(argv[j], "-l") == 0) {
|
|
if (argc < j + 2) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -l\n");
|
|
return -1;
|
|
}
|
|
files[num_files].alocalname = (unsigned char*)argv[j + 1];
|
|
evalhexescape(files[num_files].alocalname, files[num_files].plocalname, FILENAMEMAXSIZE, FILENAMEEMPTYCHAR);
|
|
if (filename == NULL) {
|
|
fprintf(stderr, "ERROR: Loop files require a filename set with -f\n");
|
|
return -1;
|
|
}
|
|
evalhexescape(filename, files[num_files].pfilename, FILENAMEMAXSIZE, FILENAMEEMPTYCHAR);
|
|
if(memcmp(files[num_files].pfilename, files[num_files].plocalname, FILENAMEMAXSIZE) == 0 && !files[num_files].force_new) {
|
|
fprintf(stderr, "ERROR: Loop file cannot have the same name as the file they refer to, unless with -N\n");
|
|
return -1;
|
|
}
|
|
files[num_files].mode |= MODE_LOOPFILE;
|
|
files[num_files].sectorInterleave = 0;
|
|
files[num_files].first_sector_new_track = first_sector_new_track;
|
|
first_sector_new_track = default_first_sector_new_track;
|
|
files[num_files].nrSectorsShown = nrSectorsShown;
|
|
files[num_files].filetype = filetype;
|
|
files[num_files].direntryindex = -1;
|
|
filename = NULL;
|
|
sectorInterleave = 0;
|
|
nrSectorsShown = -1;
|
|
filetype = 0x82;
|
|
filetype_set = false;
|
|
num_files++;
|
|
modified = 1;
|
|
j++;
|
|
} else if (strcmp(argv[j], "-L") == 0) {
|
|
if (filename == NULL) {
|
|
fprintf(stderr, "ERROR: Writing no file using -L requires disk filename set with -f\n");
|
|
return -1;
|
|
}
|
|
evalhexescape(filename, files[num_files].pfilename, FILENAMEMAXSIZE, FILENAMEEMPTYCHAR);
|
|
files[num_files].nrSectorsShown = nrSectorsShown;
|
|
files[num_files].filetype = filetype;
|
|
files[num_files].direntryindex = -1;
|
|
files[num_files].mode |= MODE_NOFILE;
|
|
|
|
first_sector_new_track = default_first_sector_new_track;
|
|
filename = NULL;
|
|
sectorInterleave = 0;
|
|
nrSectorsShown = -1;
|
|
filetype = 0x82;
|
|
filetype_set = false;
|
|
num_files++;
|
|
modified = 1;
|
|
} else if (strcmp(argv[j], "-x") == 0) {
|
|
dirtracksplit = 0;
|
|
} else if (strcmp(argv[j], "-t") == 0) {
|
|
usedirtrack = 1;
|
|
} else if (strcmp(argv[j], "-d") == 0) {
|
|
if ((argc < j + 2) || !sscanf(argv[++j], "%u", &shadowdirtrack)) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -d\n");
|
|
return -1;
|
|
}
|
|
modified = 1;
|
|
} else if (strcmp(argv[j], "-u") == 0) {
|
|
if ((argc < j + 2) || !sscanf(argv[++j], "%d", &numdirblocks)) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -u\n");
|
|
return -1;
|
|
}
|
|
} else if (strcmp(argv[j], "-B") == 0) {
|
|
if ((argc < j + 2) || !sscanf(argv[++j], "%d", &nrSectorsShown)) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -B\n");
|
|
return -1;
|
|
}
|
|
if (nrSectorsShown < 0 || nrSectorsShown > 65535) {
|
|
fprintf(stderr, "ERROR: Argument must be between 0 and 65535 for -B\n");
|
|
return -1;
|
|
}
|
|
} else if (strcmp(argv[j], "-4") == 0) {
|
|
type = IMAGE_D64_EXTENDED_SPEED_DOS;
|
|
modified = 1;
|
|
} else if (strcmp(argv[j], "-R") == 0) {
|
|
if ((argc < j + 2) || !sscanf(argv[++j], "%d", &restore_level)) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -R\n");
|
|
return -1;
|
|
}
|
|
if(restore_level < 0 || restore_level > 5) {
|
|
fprintf(stderr, "ERROR: Argument must be between 0 and 5 for -R\n");
|
|
return -1;
|
|
}
|
|
} else if (strcmp(argv[j], "-5") == 0) {
|
|
type = IMAGE_D64_EXTENDED_DOLPHIN_DOS;
|
|
modified = 1;
|
|
} else if (strcmp(argv[j], "-a") == 0) {
|
|
print_art_commandline = true;
|
|
} else if(strcmp(argv[j], "-g") == 0) {
|
|
if (argc < j + 2) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -g\n");
|
|
return -1;
|
|
}
|
|
filename_g64 = argv[++j];
|
|
} else if (strcmp(argv[j], "-U") == 0) {
|
|
if ((argc < j + 2) || !sscanf(argv[++j], "%d", &unicode)) {
|
|
fprintf(stderr, "ERROR: Error parsing argument for -U\n");
|
|
return -1;
|
|
}
|
|
if(unicode < 0 || unicode > 2) {
|
|
fprintf(stderr, "ERROR: Argument must be between 0 and 2 for -U\n");
|
|
return -1;
|
|
}
|
|
} else if (strcmp(argv[j], "-q") == 0) {
|
|
quiet = 1;
|
|
} else if (strcmp(argv[j], "-v") == 0) {
|
|
verbose = 1;
|
|
} else if (strcmp(argv[j], "-h") == 0) {
|
|
usage();
|
|
} else {
|
|
fprintf(stderr, "ERROR: Error parsing command line at \"%s\"\n", argv[j]);
|
|
printf("Use -h for help.\n");
|
|
return -1;
|
|
}
|
|
}
|
|
if (j >= argc) {
|
|
fprintf(stderr, "ERROR: No image file provided, or misparsed last option\n");
|
|
return -1;
|
|
}
|
|
imagepath = argv[argc-1];
|
|
|
|
if (strlen(imagepath) >= 4) {
|
|
if (strcmp(imagepath + strlen(imagepath) - 4, ".d71") == 0) {
|
|
if ((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) {
|
|
fprintf(stderr, "ERROR: Extended .d71 images are not supported\n");
|
|
return -1;
|
|
}
|
|
type = IMAGE_D71;
|
|
} else if (strcmp(imagepath + strlen(imagepath) - 4, ".d81") == 0) {
|
|
if ((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) {
|
|
fprintf(stderr, "ERROR: Extended .d81 images are not supported\n");
|
|
return -1;
|
|
}
|
|
type = IMAGE_D81;
|
|
dir_sector_interleave = 1;
|
|
}
|
|
}
|
|
|
|
if(bam_message != NULL && type != IMAGE_D64 && type != IMAGE_D64_EXTENDED_SPEED_DOS) {
|
|
fprintf(stderr, "ERROR: Bam message only supported for D64 and SPEED DOS images\n");
|
|
return -1;
|
|
}
|
|
|
|
if(shadowdirtrack > image_num_tracks(type) || (int)shadowdirtrack == dirtrack(type) || (type == IMAGE_D71 && (int)shadowdirtrack == dirtrack(type) + D64NUMTRACKS)) {
|
|
fprintf(stderr, "ERROR: Invalid shadow directory track\n");
|
|
return -1;
|
|
}
|
|
|
|
if (type != IMAGE_D64) {
|
|
if (transwarp_set
|
|
&& (type != IMAGE_D64_EXTENDED_SPEED_DOS)
|
|
&& (type != IMAGE_D64_EXTENDED_DOLPHIN_DOS)) {
|
|
fprintf(stderr, "ERROR: Transwarp encoding is not supported for non-D64 images\n");
|
|
return -1;
|
|
}
|
|
|
|
if (filename_g64 != NULL) {
|
|
fprintf(stderr, "ERROR: G64 output is only supported for non-extended D64 images\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Check for unsupported settings for D81 */
|
|
if (type == IMAGE_D81) {
|
|
if (default_sector_interleave_set) {
|
|
fprintf(stderr, "ERROR: -S is not supported for D81 images\n");
|
|
return -1;
|
|
}
|
|
if (sector_interleave_set) {
|
|
fprintf(stderr, "ERROR: -s is not supported for D81 images\n");
|
|
return -1;
|
|
}
|
|
if (new_track_start_sector_set) {
|
|
fprintf(stderr, "ERROR: -F is not supported for D81 images\n");
|
|
return -1;
|
|
}
|
|
if (file_start_sector_set) {
|
|
fprintf(stderr, "ERROR: -b is not supported for D81 images\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Change locale from C to default to allow unicode printouts */
|
|
if(unicode != 0) {
|
|
setlocale(LC_ALL, "");
|
|
}
|
|
|
|
/* quiet has precedence over verbose */
|
|
if(quiet) {
|
|
verbose = 0;
|
|
}
|
|
|
|
/* open image */
|
|
unsigned int imagesize = image_size(type);
|
|
unsigned char* image = (unsigned char*)calloc(imagesize, sizeof(unsigned char));
|
|
if (image == NULL) {
|
|
fprintf(stderr, "ERROR: Memory allocation error\n");
|
|
return -1;
|
|
}
|
|
FILE* f = fopen(imagepath, "rb");
|
|
if (f == NULL) {
|
|
modified = 1;
|
|
if (!quiet) {
|
|
printf("Adding %d files to new image %s\n", num_files, basename((unsigned char*)imagepath));
|
|
}
|
|
initialize_directory(type, image, header, id, bam_message, shadowdirtrack);
|
|
} else {
|
|
if (!quiet) {
|
|
printf("Adding %d files to existing image %s\n", num_files, basename((unsigned char*)imagepath));
|
|
}
|
|
size_t read_size = fread(image, 1, imagesize, f);
|
|
fclose(f);
|
|
if (read_size != imagesize) {
|
|
if (((type == IMAGE_D64_EXTENDED_SPEED_DOS) || (type == IMAGE_D64_EXTENDED_DOLPHIN_DOS)) && (read_size == D64SIZE)) {
|
|
/* Clear extra tracks */
|
|
memset(image + image_size(IMAGE_D64), 0, image_size(type) - image_size(IMAGE_D64));
|
|
|
|
/* Mark all extra sectors unused */
|
|
for (unsigned int t = D64NUMTRACKS + 1; t <= image_num_tracks(type); t++) {
|
|
for (int s = 0; s < num_sectors(type, t); s++) {
|
|
mark_sector(type, image, t, s, 1 /* free */);
|
|
}
|
|
}
|
|
} else {
|
|
fprintf(stderr, "ERROR: Wrong filesize: expected to read %u bytes, but read %u bytes\n", imagesize, (unsigned int) read_size);
|
|
return -1;
|
|
}
|
|
}
|
|
if (dovalidate) {
|
|
validate(type, image);
|
|
}
|
|
if (restore_level >= 0) {
|
|
restore(type, image, restore_level, files);
|
|
}
|
|
if (set_header) {
|
|
update_directory(type, image, header, id, bam_message, shadowdirtrack);
|
|
}
|
|
}
|
|
|
|
/* Print command line before adding anything to the image */
|
|
if(print_art_commandline) {
|
|
convert_to_commandline(type, image);
|
|
}
|
|
|
|
/* Create directory entries */
|
|
create_dir_entries(type, image, files, num_files, dir_sector_interleave, shadowdirtrack, nooverwrite);
|
|
|
|
/* Write files and mark sectors in BAM */
|
|
write_files(type, image, files, num_files, usedirtrack, dirtracksplit, shadowdirtrack, numdirblocks, dir_sector_interleave);
|
|
|
|
/* Print allocation info */
|
|
if (verbose) {
|
|
print_file_allocation(type, image, files, num_files);
|
|
}
|
|
int blocks_free = check_bam(type, image);
|
|
|
|
/* Print directory */
|
|
if (!quiet) {
|
|
print_directory(type, image, blocks_free);
|
|
}
|
|
|
|
/* Show directory issues if present */
|
|
if(dir_error != DIR_OK) {
|
|
fprintf(stdout, "WARNING: %s\n", dir_error_string[dir_error]);
|
|
}
|
|
|
|
/* Save image */
|
|
if(modified) {
|
|
f = fopen(imagepath, "wb");
|
|
if (f == NULL || fwrite(image, imagesize, 1, f) != 1) {
|
|
fprintf(stderr, "ERROR: Failed to write %s\n", imagepath);
|
|
retval = -1;
|
|
}
|
|
if (f != NULL) {
|
|
fclose(f);
|
|
}
|
|
}
|
|
|
|
/* Save optional g64 image */
|
|
if (filename_g64 != NULL) {
|
|
/* retval might be set to -1 already. Thus we need to take its
|
|
previous state and OR it with the following return value. */
|
|
retval |= generate_uniformat_g64(image, filename_g64);
|
|
}
|
|
|
|
if (!ignore_collision && check_hashes(type, image)) {
|
|
fprintf(stderr, "\nERROR: Filename hash collision detected, image is not compatible with Krill's loader. Use -m to ignore this error.\n");
|
|
retval = -1;
|
|
}
|
|
|
|
free(image);
|
|
|
|
return retval;
|
|
}
|