mirror of
https://github.com/holub/mame
synced 2025-10-09 09:44:40 +03:00
6420 lines
187 KiB
C++
6420 lines
187 KiB
C++
// license:BSD-3-Clause
|
|
// copyright-holders:Raphael Nabet
|
|
/****************************************************************************
|
|
|
|
mac.c
|
|
|
|
Handlers for Classic MacOS images (MFS and HFS formats).
|
|
|
|
Raphael Nabet, 2003
|
|
|
|
TODO:
|
|
* add support for HFS write
|
|
|
|
*****************************************************************************
|
|
|
|
terminology:
|
|
disk block: 512-byte logical block. With sectors of 512 bytes, one logical
|
|
block is equivalent to one sector; when the sector size is not 512
|
|
bytes, sectors are split or grouped to make 512-byte disk blocks.
|
|
allocation block: The File Manager always allocates logical disk blocks to
|
|
a file in groups called allocation blocks; an allocation block is
|
|
simply a group of consecutive logical blocks. The size of a volume's
|
|
allocation blocks depends on the capacity of the volume; there can be
|
|
at most 4094 (MFS) or 65535 (HFS) allocation blocks on a volume.
|
|
MFS (Macintosh File System): File system used by the early Macintosh. This
|
|
File system does not support folders (you may create folders on a MFS
|
|
disk, but such folders are not implemented on File System level but in
|
|
the Desktop file, and they are just a hint of how programs should list
|
|
files, i.e. you can't have two files with the same name on a volume
|
|
even if they are in two different folders), and it is not adequate for
|
|
large volumes.
|
|
HFS (Hierarchical File System): File system introduced with the HD20
|
|
harddisk, the Macintosh Plus ROMs, and system 3.2 (IIRC). Contrary to
|
|
MFS, it supports hierarchical folders. Also, it is suitable for larger
|
|
volumes.
|
|
HFS+ (HFS Plus): New file system introduced with MacOS 8.1. It has a lot
|
|
in common with HFS, but it supports more allocation blocks (up to 4
|
|
billions IIRC), and many extra features, including longer file names
|
|
(up to 255 UTF-16 Unicode chars).
|
|
tag data: with the GCR encoding, each disk block is associated with a 12
|
|
(3.5" floppies) or 20 (HD20) byte tag record. This tag record contains
|
|
information on the block allocation status (whether it is allocated
|
|
in a file or free, which file is it belongs to, what offset the block
|
|
has in the file). This enables to recover all files whose data blocks
|
|
are still on the disk surface even if the disk catalog has been trashed
|
|
completely (though most file properties, like the name, type and
|
|
logical EOF, are not saved in the tag record and cannot be recovered).
|
|
|
|
Organization of an MFS volume:
|
|
|
|
Logical Contents Allocation block
|
|
block
|
|
|
|
0 - 1: System startup information
|
|
2 - m: Master directory block (MDB)
|
|
+ allocation block link pointers
|
|
m+1 - n: Directory file
|
|
n+1 - p-2: Other files and free space 0 - ((p-2)-(n+1))/k
|
|
p-1: Alternate MDB
|
|
p: Not used
|
|
usually, k = 2, m = 3, n = 16, p = 799 (SSDD 3.5" floppy)
|
|
with DSDD 3.5" floppy, I assume that p = 1599, but I don't know the other
|
|
values
|
|
|
|
|
|
Master Directory Block:
|
|
|
|
Offset Length Description
|
|
------ ------ -----------
|
|
0 2 Volume Signature
|
|
2 4 Creation Date
|
|
6 4 Last Modification Date
|
|
10 2 Volume Attributes
|
|
12 2 Number of Files In Root Directory
|
|
14 2 First Block of Volume Bitmap
|
|
...
|
|
|
|
Links:
|
|
http://developer.apple.com/documentation/mac/Files/Files-99.html
|
|
http://developer.apple.com/documentation/mac/Files/Files-100.html
|
|
http://developer.apple.com/documentation/mac/Files/Files-101.html
|
|
http://developer.apple.com/documentation/mac/Files/Files-102.html
|
|
http://developer.apple.com/documentation/mac/Files/Files-103.html
|
|
http://developer.apple.com/documentation/mac/Files/Files-104.html
|
|
http://developer.apple.com/documentation/mac/Files/Files-105.html
|
|
http://developer.apple.com/documentation/mac/Files/Files-106.html
|
|
http://developer.apple.com/documentation/mac/Devices/Devices-121.html#MARKER-2-169
|
|
http://developer.apple.com/documentation/mac/MoreToolbox/MoreToolbox-99.html
|
|
|
|
*****************************************************************************/
|
|
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <limits.h>
|
|
#include <stddef.h>
|
|
|
|
#include "formats/imageutl.h"
|
|
#include "imgtool.h"
|
|
#include "macutil.h"
|
|
#include "iflopimg.h"
|
|
#include "formats/ap_dsk35.h"
|
|
|
|
/* if 1, check consistency of B-Tree (most of the checks will eventually be
|
|
suppressed when the image is opened as read-only and only enabled when
|
|
the image is opened as read/write) */
|
|
#define BTREE_CHECKS 1
|
|
/* if 1, check consistency of tag data when we are at risk of corrupting the
|
|
disk (file write and allocation) */
|
|
#define TAG_CHECKS 1
|
|
/* if 1, check consistency of tag data when reading files (not recommended
|
|
IMHO) */
|
|
#define TAG_EXTRA_CHECKS 0
|
|
|
|
#if 0
|
|
#pragma mark MISCELLANEOUS UTILITIES
|
|
#endif
|
|
|
|
struct UINT16BE
|
|
{
|
|
UINT8 bytes[2];
|
|
};
|
|
|
|
struct UINT24BE
|
|
{
|
|
UINT8 bytes[3];
|
|
};
|
|
|
|
struct UINT32BE
|
|
{
|
|
UINT8 bytes[4];
|
|
};
|
|
|
|
static inline UINT16 get_UINT16BE(UINT16BE word)
|
|
{
|
|
return (word.bytes[0] << 8) | word.bytes[1];
|
|
}
|
|
|
|
static inline void set_UINT16BE(UINT16BE *word, UINT16 data)
|
|
{
|
|
word->bytes[0] = (data >> 8) & 0xff;
|
|
word->bytes[1] = data & 0xff;
|
|
}
|
|
|
|
#if 0
|
|
static inline UINT32 get_UINT24BE(UINT24BE word)
|
|
{
|
|
return (word.bytes[0] << 16) | (word.bytes[1] << 8) | word.bytes[2];
|
|
}
|
|
|
|
static inline void set_UINT24BE(UINT24BE *word, UINT32 data)
|
|
{
|
|
word->bytes[0] = (data >> 16) & 0xff;
|
|
word->bytes[1] = (data >> 8) & 0xff;
|
|
word->bytes[2] = data & 0xff;
|
|
}
|
|
#endif
|
|
|
|
static inline UINT32 get_UINT32BE(UINT32BE word)
|
|
{
|
|
return (word.bytes[0] << 24) | (word.bytes[1] << 16) | (word.bytes[2] << 8) | word.bytes[3];
|
|
}
|
|
|
|
static inline void set_UINT32BE(UINT32BE *word, UINT32 data)
|
|
{
|
|
word->bytes[0] = (data >> 24) & 0xff;
|
|
word->bytes[1] = (data >> 16) & 0xff;
|
|
word->bytes[2] = (data >> 8) & 0xff;
|
|
word->bytes[3] = data & 0xff;
|
|
}
|
|
|
|
/*
|
|
Macintosh string: first byte is length
|
|
*/
|
|
typedef UINT8 mac_str27[28];
|
|
typedef UINT8 mac_str31[32];
|
|
typedef UINT8 mac_str63[64];
|
|
typedef UINT8 mac_str255[256];
|
|
|
|
/*
|
|
Macintosh type/creator code: 4 char value
|
|
*/
|
|
typedef UINT32BE mac_type;
|
|
|
|
/*
|
|
point record, with the y and x coordinates
|
|
*/
|
|
struct mac_point
|
|
{
|
|
UINT16BE v; /* actually signed */
|
|
UINT16BE h; /* actually signed */
|
|
};
|
|
|
|
/*
|
|
rect record, with the corner coordinates
|
|
*/
|
|
struct mac_rect
|
|
{
|
|
UINT16BE top; /* actually signed */
|
|
UINT16BE left; /* actually signed */
|
|
UINT16BE bottom;/* actually signed */
|
|
UINT16BE right; /* actually signed */
|
|
};
|
|
|
|
/*
|
|
FInfo (Finder file info) record
|
|
*/
|
|
struct mac_FInfo
|
|
{
|
|
mac_type type; /* file type */
|
|
mac_type creator; /* file creator */
|
|
UINT16BE flags; /* Finder flags */
|
|
mac_point location; /* file's location in window */
|
|
/* If set to {0, 0}, and the inited flag is
|
|
clear, the Finder will place the item
|
|
automatically */
|
|
UINT16BE fldr; /* MFS: window that contains file */
|
|
/* -3: trash */
|
|
/* -2: desktop */
|
|
/* -1: new folder template?????? */
|
|
/* 0: disk window ("root") */
|
|
/* > 0: specific folder, index of FOBJ resource??? */
|
|
/* The FOBJ resource contains some folder info;
|
|
the name of the resource is the folder name. */
|
|
/* HFS & HFS+:
|
|
System 7: The window in which the file???s icon appears
|
|
System 8: reserved (set to 0) */
|
|
};
|
|
|
|
/*
|
|
FXInfo (Finder extended file info) record -- not found in MFS
|
|
*/
|
|
struct mac_FXInfo
|
|
{
|
|
UINT16BE iconID; /* System 7: An ID number for the file???s icon; the
|
|
numbers that identify icons are assigned by the
|
|
Finder */
|
|
/* System 8: Reserved (set to 0) */
|
|
UINT16BE reserved[3]; /* Reserved (set to 0) */
|
|
UINT8 script; /* System 7: if high-bit is set, the script code
|
|
for displaying the file name; ignored otherwise */
|
|
/* System 8: Extended flags MSB(?) */
|
|
UINT8 XFlags; /* Extended flags */
|
|
UINT16BE comment; /* System 7: Comment ID if high-bit is clear */
|
|
/* System 8: Reserved (set to 0) */
|
|
UINT32BE putAway; /* Put away folder ID (i.e. if the user moves the
|
|
file onto the desktop, the directory ID of the
|
|
folder from which the user moves the file is
|
|
saved here) */
|
|
};
|
|
|
|
/*
|
|
DInfo (Finder folder info) record -- not found in MFS
|
|
*/
|
|
struct mac_DInfo
|
|
{
|
|
mac_rect rect; /* Folder's window bounds */
|
|
UINT16BE flags; /* Finder flags, e.g. kIsInvisible, kNameLocked, etc */
|
|
mac_point location; /* Location of the folder in parent window */
|
|
/* If set to {0, 0}, and the initied flag is
|
|
clear, the Finder will place the item
|
|
automatically */
|
|
UINT16BE view; /* System 7: The manner in which folders are
|
|
displayed */
|
|
/* System 8: reserved (set to 0) */
|
|
};
|
|
|
|
/*
|
|
DXInfo (Finder extended folder info) record -- not found in MFS
|
|
*/
|
|
struct mac_DXInfo
|
|
{
|
|
mac_point scroll; /* Scroll position */
|
|
UINT32BE openChain; /* System 7: chain of directory IDs for open folders */
|
|
/* System 8: reserved (set to 0) */
|
|
UINT8 script; /* System 7: if high-bit is set, the script code
|
|
for displaying the folder name; ignored otherwise */
|
|
/* System 8: Extended flags MSB(?) */
|
|
UINT8 XFlags; /* Extended flags */
|
|
UINT16BE comment; /* System 7: Comment ID if high-bit is clear */
|
|
/* System 8: Reserved (set to 0) */
|
|
UINT32BE putAway; /* Put away folder ID (i.e. if the user moves the
|
|
folder onto the desktop, the directory ID of
|
|
the folder from which the user moves it is
|
|
saved here) */
|
|
};
|
|
|
|
/*
|
|
defines for FInfo & DInfo flags fields
|
|
*/
|
|
enum
|
|
{
|
|
fif_isOnDesk = 0x0001, /* System 6: set if item is located on desktop (files and folders) */
|
|
/* System 7: Unused (set to 0) */
|
|
fif_color = 0x000E, /* color coding (files and folders) */
|
|
fif_colorReserved = 0x0010, /* System 6: reserved??? */
|
|
/* System 7: Unused (set to 0) */
|
|
fif_requiresSwitchLaunch= 0x0020, /* System 6: ??? */
|
|
/* System 7: Unused (set to 0) */
|
|
fif_isShared = 0x0040, /* Applications files: if set, the */
|
|
/* application can be executed by */
|
|
/* multiple users simultaneously. */
|
|
/* Otherwise, set to 0. */
|
|
fif_hasNoINITs = 0x0080, /* Extensions/Control Panels: if set(?), */
|
|
/* this file contains no INIT */
|
|
/* resource */
|
|
/* Otherwise, set to 0. */
|
|
fif_hasBeenInited = 0x0100, /* System 6: The Finder has recorded information from
|
|
the file???s bundle resource into the desktop
|
|
database and given the file or folder a
|
|
position on the desktop. */
|
|
/* System 7? 8?: Clear if the file contains desktop database */
|
|
/* resources ('BNDL', 'FREF', 'open', 'kind'...) */
|
|
/* that have not been added yet. Set only by the Finder */
|
|
/* Reserved for folders - make sure this bit is cleared for folders */
|
|
|
|
/* bit 0x0200 was at a point (AOCE for system 7.x?) the letter bit for
|
|
AOCE, but was reserved before and it is reserved again in recent MacOS
|
|
releases. */
|
|
|
|
fif_hasCustomIcon = 0x0400, /* (files and folders) */
|
|
fif_isStationery = 0x0800, /* System 7: (files only) */
|
|
fif_nameLocked = 0x1000, /* (files and folders) */
|
|
fif_hasBundle = 0x2000, /* Files only */
|
|
fif_isInvisible = 0x4000, /* (files and folders) */
|
|
fif_isAlias = 0x8000 /* System 7: (files only) */
|
|
};
|
|
|
|
/*
|
|
mac_to_c_strncpy()
|
|
|
|
Encode a macintosh string as a C string. The NUL character is escaped,
|
|
using the "%00" sequence. To avoid any ambiguity, '%' is escaped with
|
|
'%25'.
|
|
|
|
dst (O): C string
|
|
n (I): length of buffer pointed to by dst
|
|
src (I): macintosh string (first byte is length)
|
|
*/
|
|
static void mac_to_c_strncpy(char *dst, int n, UINT8 *src)
|
|
{
|
|
size_t len = src[0];
|
|
int i, j;
|
|
|
|
i = j = 0;
|
|
while ((i < len) && (j < n-1))
|
|
{
|
|
switch (src[i+1])
|
|
{
|
|
case '\0':
|
|
if (j >= n-3)
|
|
goto exit;
|
|
dst[j] = '%';
|
|
dst[j+1] = '0';
|
|
dst[j+2] = '0';
|
|
j += 3;
|
|
break;
|
|
|
|
case '%':
|
|
if (j >= n-3)
|
|
goto exit;
|
|
dst[j] = '%';
|
|
dst[j+1] = '2';
|
|
dst[j+2] = '5';
|
|
j += 3;
|
|
break;
|
|
|
|
default:
|
|
dst[j] = src[i+1];
|
|
j++;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
exit:
|
|
if (n > 0)
|
|
dst[j] = '\0';
|
|
}
|
|
|
|
/*
|
|
c_to_mac_strncpy()
|
|
|
|
Decode a C string to a macintosh string. The NUL character is escaped,
|
|
using the "%00" sequence. To avoid any ambiguity, '%' is escaped with
|
|
'%25'.
|
|
|
|
dst (O): macintosh string (first byte is length)
|
|
src (I): C string
|
|
n (I): length of string pointed to by src
|
|
*/
|
|
static void c_to_mac_strncpy(mac_str255 dst, const char *src, int n)
|
|
{
|
|
size_t len;
|
|
int i;
|
|
char buf[3];
|
|
|
|
|
|
len = 0;
|
|
i = 0;
|
|
while ((i < n) && (len < 255))
|
|
{
|
|
switch (src[i])
|
|
{
|
|
case '%':
|
|
if (i >= n-2)
|
|
goto exit;
|
|
buf[0] = src[i+1];
|
|
buf[1] = src[i+2];
|
|
buf[2] = '\0';
|
|
dst[len+1] = strtoul(buf, NULL, 16);
|
|
i += 3;
|
|
break;
|
|
|
|
default:
|
|
dst[len+1] = src[i];
|
|
i++;
|
|
break;
|
|
}
|
|
len++;
|
|
}
|
|
|
|
exit:
|
|
dst[0] = len;
|
|
}
|
|
|
|
/*
|
|
mac_strcmp()
|
|
|
|
Compare two macintosh strings
|
|
|
|
s1 (I): the string to compare
|
|
s2 (I): the comparison string
|
|
|
|
Return a zero if s1 and s2 are equal, a negative value if s1 is less than
|
|
s2, and a positive value if s1 is greater than s2.
|
|
*/
|
|
#ifdef UNUSED_FUNCTION
|
|
static int mac_strcmp(const UINT8 *s1, const UINT8 *s2)
|
|
{
|
|
size_t common_len;
|
|
|
|
common_len = (s1[0] <= s2[0]) ? s1[0] : s2[0];
|
|
|
|
return memcmp(s1+1, s2+1, common_len) || ((int)s1[0] - s2[0]);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
mac_stricmp()
|
|
|
|
Compare two macintosh strings in a manner compatible with the macintosh HFS
|
|
file system.
|
|
|
|
This functions emulates the way HFS (and MFS???) sorts string: this is
|
|
equivalent to the RelString macintosh toolbox call with the caseSensitive
|
|
parameter as false and the diacSensitive parameter as true.
|
|
|
|
s1 (I): the string to compare
|
|
s2 (I): the comparison string
|
|
|
|
Return a zero if s1 and s2 are equal, a negative value if s1 is less than
|
|
s2, and a positive value if s1 is greater than s2.
|
|
|
|
Known issues:
|
|
Using this function makes sense with the MacRoman encoding, as it means the
|
|
computer will regard "DeskTop File", "Desktop File", "Desktop file", etc,
|
|
as the same file. (UNIX users will probably regard this as an heresy, but
|
|
you must consider that, unlike UNIX, the Macintosh was not designed for
|
|
droids, but error-prone human beings that may forget about case.)
|
|
|
|
(Also, letters with diatrical signs follow the corresponding letters
|
|
without diacritical signs in the HFS catalog file. This does not matter,
|
|
though, since the Finder will not display files in the HFS catalog order,
|
|
but it will instead sort files according to whatever order is currently
|
|
selected in the View menu.)
|
|
|
|
However, with other text encodings, the behavior will be completely absurd.
|
|
For instance, with the Greek encoding, it will think that the degree symbol
|
|
is the same letter (with different case) as the upper-case Psi, so that if
|
|
a program asks for a file called "90??" on a greek HFS volume, and there is
|
|
a file called "90??" on this volume, file "90??" will be opened.
|
|
Results will probably be even weirder with 2-byte scripts like Japanese or
|
|
Chinese. Of course, we are not going to fix this issue, since doing so
|
|
would break the compatibility with the original Macintosh OS.
|
|
*/
|
|
|
|
static int mac_stricmp(const UINT8 *s1, const UINT8 *s2)
|
|
{
|
|
static const unsigned char mac_char_sort_table[256] =
|
|
{
|
|
/* \x00 \x01 \x02 \x03 \x04 \x05 \x06 \x07 */
|
|
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
|
/* \x08 \x09 \x0a \x0b \x0c \x0d \x0e \x0f */
|
|
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
|
/* \x10 \x11 \x12 \x13 \x14 \x15 \x16 \x17 */
|
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
|
/* \x18 \x19 \x1a \x1b \x1c \x1d \x1e \x1f */
|
|
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
|
/* \x20 \x21 \x22 \x23 \x24 \x25 \x26 \x27 */
|
|
0x20, 0x21, 0x22, 0x27, 0x28, 0x29, 0x2a, 0x2b,
|
|
/* \x28 \x29 \x2a \x2b \x2c \x2d \x2e \x2f */
|
|
0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
|
|
/* \x30 \x31 \x32 \x33 \x34 \x35 \x36 \x37 */
|
|
0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d,
|
|
/* \x38 \x39 \x3a \x3b \x3c \x3d \x3e \x3f */
|
|
0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
|
|
/* \x40 \x41 \x42 \x43 \x44 \x45 \x46 \x47 */
|
|
0x46, 0x47, 0x51, 0x52, 0x54, 0x55, 0x5a, 0x5b,
|
|
/* \x48 \x49 \x4a \x4b \x4c \x4d \x4e \x4f */
|
|
0x5c, 0x5d, 0x62, 0x63, 0x64, 0x65, 0x66, 0x68,
|
|
/* \x50 \x51 \x52 \x53 \x54 \x55 \x56 \x57 */
|
|
0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x7c, 0x7d,
|
|
/* \x58 \x59 \x5a \x5b \x5c \x5d \x5e \x5f */
|
|
0x7e, 0x7f, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86,
|
|
/* \x60 \x61 \x62 \x63 \x64 \x65 \x66 \x67 */
|
|
0x4d, 0x47, 0x51, 0x52, 0x54, 0x55, 0x5a, 0x5b,
|
|
/* \x68 \x69 \x6a \x6b \x6c \x6d \x6e \x6f */
|
|
0x5c, 0x5d, 0x62, 0x63, 0x64, 0x65, 0x66, 0x68,
|
|
/* \x70 \x71 \x72 \x73 \x74 \x75 \x76 \x77 */
|
|
0x71, 0x72, 0x73, 0x74, 0x76, 0x77, 0x7c, 0x7d,
|
|
/* \x78 \x79 \x7a \x7b \x7c \x7d \x7e \x7f */
|
|
0x7e, 0x7f, 0x81, 0x87, 0x88, 0x89, 0x8a, 0x8b,
|
|
/* \x80 \x81 \x82 \x83 \x84 \x85 \x86 \x87 */
|
|
0x49, 0x4b, 0x53, 0x56, 0x67, 0x69, 0x78, 0x4e,
|
|
/* \x88 \x89 \x8a \x8b \x8c \x8d \x8e \x8f */
|
|
0x48, 0x4f, 0x49, 0x4a, 0x4b, 0x53, 0x56, 0x57,
|
|
/* \x90 \x91 \x92 \x93 \x94 \x95 \x96 \x97 */
|
|
0x58, 0x59, 0x5e, 0x5f, 0x60, 0x61, 0x67, 0x6d,
|
|
/* \x98 \x99 \x9a \x9b \x9c \x9d \x9e \x9f */
|
|
0x6e, 0x6f, 0x69, 0x6a, 0x79, 0x7a, 0x7b, 0x78,
|
|
/* \xa0 \xa1 \xa2 \xa3 \xa4 \xa5 \xa6 \xa7 */
|
|
0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x75,
|
|
/* \xa8 \xa9 \xaa \xab \xac \xad \xae \xaf */
|
|
0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x4c, 0x6b,
|
|
/* \xb0 \xb1 \xb2 \xb3 \xb4 \xb5 \xb6 \xb7 */
|
|
0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0,
|
|
/* \xb8 \xb9 \xba \xbb \xbc \xbd \xbe \xbf */
|
|
0xa1, 0xa2, 0xa3, 0x50, 0x70, 0xa4, 0x4c, 0x6b,
|
|
/* \xc0 \xc1 \xc2 \xc3 \xc4 \xc5 \xc6 \xc7 */
|
|
0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0x25,
|
|
/* \xc8 \xc9 \xca \xcb \xcc \xcd \xce \xcf */
|
|
0x26, 0xac, 0x20, 0x48, 0x4a, 0x6a, 0x6c, 0x6c,
|
|
/* \xd0 \xd1 \xd2 \xd3 \xd4 \xd5 \xd6 \xd7 */
|
|
0xad, 0xae, 0x23, 0x24, 0x2c, 0x2d, 0xaf, 0xb0,
|
|
/* \xd8 \xd9 \xda \xdb \xdc \xdd \xde \xdf */
|
|
0x80, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
|
|
/* \xe0 \xe1 \xe2 \xe3 \xe4 \xe5 \xe6 \xe7 */
|
|
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
|
|
/* \xe8 \xe9 \xea \xeb \xec \xed \xee \xef */
|
|
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
|
|
/* \xf0 \xf1 \xf2 \xf3 \xf4 \xf5 \xf6 \xf7 */
|
|
0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
|
|
/* \xf8 \xf9 \xfa \xfb \xfc \xfd \xfe \xff */
|
|
0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7
|
|
};
|
|
|
|
size_t common_len;
|
|
int i;
|
|
int c1, c2;
|
|
|
|
common_len = (s1[0] <= s2[0]) ? s1[0] : s2[0];
|
|
|
|
for (i=0; i<common_len; i++)
|
|
{
|
|
c1 = mac_char_sort_table[s1[i+1]];
|
|
c2 = mac_char_sort_table[s2[i+1]];
|
|
if (c1 != c2)
|
|
return c1 - c2;
|
|
}
|
|
|
|
return ((int)s1[0] - s2[0]);
|
|
}
|
|
|
|
/*
|
|
mac_strcpy()
|
|
|
|
Copy a macintosh string
|
|
|
|
dst (O): dest macintosh string
|
|
src (I): source macintosh string (first byte is length)
|
|
|
|
Return a zero if s1 and s2 are equal, a negative value if s1 is less than
|
|
s2, and a positive value if s1 is greater than s2.
|
|
*/
|
|
static inline void mac_strcpy(UINT8 *dest, const UINT8 *src)
|
|
{
|
|
memcpy(dest, src, src[0]+1);
|
|
}
|
|
|
|
#ifdef UNUSED_FUNCTION
|
|
/*
|
|
mac_strncpy()
|
|
|
|
Copy a macintosh string
|
|
|
|
dst (O): dest macintosh string
|
|
n (I): max string length for dest (range 0-255, buffer length + 1)
|
|
src (I): source macintosh string (first byte is length)
|
|
*/
|
|
static void mac_strncpy(UINT8 *dest, int n, const UINT8 *src)
|
|
{
|
|
size_t len;
|
|
|
|
len = src[0];
|
|
if (len > n)
|
|
len = n;
|
|
dest[0] = len;
|
|
memcpy(dest+1, src+1, len);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
disk image reference
|
|
*/
|
|
struct mac_l1_imgref
|
|
{
|
|
imgtool::image *image;
|
|
UINT32 heads;
|
|
};
|
|
|
|
|
|
|
|
static imgtoolerr_t mac_find_block(mac_l1_imgref *image, int block,
|
|
UINT32 *track, UINT32 *head, UINT32 *sector)
|
|
{
|
|
*track = 0;
|
|
while(block >= (apple35_sectors_per_track(imgtool_floppy(*image->image), *track) * image->heads))
|
|
{
|
|
block -= (apple35_sectors_per_track(imgtool_floppy(*image->image), (*track)++) * image->heads);
|
|
if (*track >= 80)
|
|
return IMGTOOLERR_SEEKERROR;
|
|
}
|
|
|
|
*head = block / apple35_sectors_per_track(imgtool_floppy(*image->image), *track);
|
|
*sector = block % apple35_sectors_per_track(imgtool_floppy(*image->image), *track);
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
image_read_block()
|
|
|
|
Read one 512-byte block of data from a macintosh disk image
|
|
|
|
image (I/O): level-1 image reference
|
|
block (I): address of block to read
|
|
dest (O): buffer where block data should be stored
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t image_read_block(mac_l1_imgref *image, UINT32 block, void *dest)
|
|
{
|
|
imgtoolerr_t err;
|
|
floperr_t ferr;
|
|
UINT32 track, head, sector;
|
|
|
|
err = mac_find_block(image, block, &track, &head, §or);
|
|
if (err)
|
|
return err;
|
|
|
|
ferr = floppy_read_sector(imgtool_floppy(*image->image), head, track, sector, 0, dest, 512);
|
|
if (ferr)
|
|
return imgtool_floppy_error(ferr);
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
image_write_block()
|
|
|
|
Read one 512-byte block of data from a macintosh disk image
|
|
|
|
image (I/O): level-1 image reference
|
|
block (I): address of block to write
|
|
src (I): buffer with the block data
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t image_write_block(mac_l1_imgref *image, UINT32 block, const void *src)
|
|
{
|
|
imgtoolerr_t err;
|
|
floperr_t ferr;
|
|
UINT32 track, head, sector;
|
|
|
|
err = mac_find_block(image, block, &track, &head, §or);
|
|
if (err)
|
|
return err;
|
|
|
|
ferr = floppy_write_sector(imgtool_floppy(*image->image), head, track, sector, 0, src, 512, 0); /* TODO: pass ddam argument from imgtool */
|
|
if (ferr)
|
|
return imgtool_floppy_error(ferr);
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
image_get_tag_len()
|
|
|
|
Get length of tag data (12 for GCR floppies, 20 for HD20, 0 otherwise)
|
|
|
|
image (I/O): level-1 image reference
|
|
|
|
Return tag length
|
|
*/
|
|
static inline int image_get_tag_len(mac_l1_imgref *image)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
image_read_tag()
|
|
|
|
Read a 12- or 20-byte tag record associated with a disk block
|
|
|
|
image (I/O): level-1 image reference
|
|
block (I): address of block to read
|
|
dest (O): buffer where tag data should be stored
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t image_read_tag(mac_l1_imgref *image, UINT32 block, void *dest)
|
|
{
|
|
return IMGTOOLERR_UNEXPECTED;
|
|
}
|
|
|
|
/*
|
|
image_write_tag()
|
|
|
|
Write a 12- or 20-byte tag record associated with a disk block
|
|
|
|
image (I/O): level-1 image reference
|
|
block (I): address of block to write
|
|
src (I): buffer with the tag data
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t image_write_tag(mac_l1_imgref *image, UINT32 block, const void *src)
|
|
{
|
|
return IMGTOOLERR_UNEXPECTED;
|
|
}
|
|
|
|
#if 0
|
|
#pragma mark -
|
|
#pragma mark MFS/HFS WRAPPERS
|
|
#endif
|
|
|
|
enum mac_format
|
|
{
|
|
L2I_MFS,
|
|
L2I_HFS
|
|
};
|
|
|
|
enum mac_forkID { data_fork = 0x00, rsrc_fork = 0xff };
|
|
|
|
/*
|
|
MFS image ref
|
|
*/
|
|
struct mfs_l2_imgref
|
|
{
|
|
UINT16 dir_num_files;
|
|
UINT16 dir_start;
|
|
UINT16 dir_blk_len;
|
|
|
|
UINT16 ABStart;
|
|
|
|
mac_str27 volname;
|
|
|
|
unsigned char ABlink_dirty[13]; /* dirty flag for each disk block in the ABlink array */
|
|
UINT8 ABlink[6141];
|
|
};
|
|
|
|
/*
|
|
HFS extent descriptor
|
|
*/
|
|
struct hfs_extent
|
|
{
|
|
UINT16BE stABN; /* first allocation block */
|
|
UINT16BE numABlks; /* number of allocation blocks */
|
|
};
|
|
|
|
/*
|
|
HFS likes to group extents by 3 (it is 8 with HFS+), so we create a
|
|
specific type.
|
|
*/
|
|
typedef hfs_extent hfs_extent_3[3];
|
|
|
|
/*
|
|
MFS open file ref
|
|
*/
|
|
struct mfs_fileref
|
|
{
|
|
UINT16 stBlk; /* first allocation block of file */
|
|
};
|
|
|
|
/*
|
|
HFS open file ref
|
|
*/
|
|
struct hfs_fileref
|
|
{
|
|
hfs_extent_3 extents; /* first 3 file extents */
|
|
|
|
UINT32 parID; /* CNID of parent directory (undefined for extent & catalog files) */
|
|
mac_str31 filename; /* file name (undefined for extent & catalog files) */
|
|
};
|
|
|
|
struct mac_l2_imgref;
|
|
|
|
/*
|
|
MFS/HFS open file ref
|
|
*/
|
|
struct mac_fileref
|
|
{
|
|
struct mac_l2_imgref *l2_img; /* image pointer */
|
|
|
|
UINT32 fileID; /* file ID (a.k.a. CNID in HFS/HFS+) */
|
|
|
|
mac_forkID forkType; /* 0x00 for data, 0xff for resource */
|
|
|
|
UINT32 eof; /* logical end-of-file */
|
|
UINT32 pLen; /* physical end-of-file */
|
|
|
|
UINT32 crPs; /* current position in file */
|
|
|
|
UINT8 reload_buf;
|
|
UINT8 block_buffer[512]; /* buffer with current file block */
|
|
|
|
union
|
|
{
|
|
mfs_fileref mfs;
|
|
hfs_fileref hfs;
|
|
};
|
|
};
|
|
|
|
/*
|
|
open BT ref
|
|
*/
|
|
struct mac_BTref
|
|
{
|
|
struct mac_fileref fileref; /* open B-tree file ref */
|
|
|
|
UINT16 nodeSize; /* size of a node, in bytes */
|
|
UINT32 rootNode; /* node number of root node */
|
|
UINT32 firstLeafNode; /* node number of first leaf node */
|
|
UINT32 attributes; /* persistent attributes about the tree */
|
|
UINT16 treeDepth; /* maximum height (usually leaf nodes) */
|
|
UINT16 maxKeyLength; /* maximum key length */
|
|
|
|
/* function to compare keys during tree searches */
|
|
int (*key_compare_func)(const void *key1, const void *key2);
|
|
|
|
void *node_buf; /* current node buffer */
|
|
};
|
|
|
|
/*
|
|
Constants for BTHeaderRec attributes field
|
|
*/
|
|
enum
|
|
{
|
|
btha_badCloseMask = 0x00000001, /* reserved */
|
|
btha_bigKeysMask = 0x00000002, /* key length field is 16 bits */
|
|
btha_variableIndexKeysMask = 0x00000004 /* keys in index nodes are variable length */
|
|
};
|
|
|
|
/*
|
|
HFS image ref
|
|
*/
|
|
struct hfs_l2_imgref
|
|
{
|
|
UINT16 VBM_start;
|
|
|
|
UINT16 ABStart;
|
|
|
|
mac_str27 volname;
|
|
|
|
mac_BTref extents_BT;
|
|
mac_BTref cat_BT;
|
|
|
|
UINT8 VBM[8192];
|
|
};
|
|
|
|
/*
|
|
MFS/HFS image ref
|
|
*/
|
|
struct mac_l2_imgref
|
|
{
|
|
mac_l1_imgref l1_img;
|
|
|
|
UINT16 numABs;
|
|
UINT16 blocksperAB;
|
|
|
|
UINT16 freeABs;
|
|
|
|
UINT32 nxtCNID; /* nxtFNum in MFS, nxtCNID in HFS */
|
|
|
|
mac_format format;
|
|
union
|
|
{
|
|
mfs_l2_imgref mfs;
|
|
hfs_l2_imgref hfs;
|
|
} u;
|
|
};
|
|
|
|
/*
|
|
MFS Master Directory Block
|
|
*/
|
|
struct mfs_mdb
|
|
{
|
|
UINT8 sigWord[2]; /* volume signature - always $D2D7 */
|
|
UINT32BE crDate; /* date and time of volume creation */
|
|
UINT32BE lsMod/*lsBkUp???*/;/* date and time of last modification (backup???) */
|
|
UINT16BE atrb; /* volume attributes (0x0000) */
|
|
/* bit 15 is set if volume is locked by software */
|
|
UINT16BE nmFls; /* number of files in directory */
|
|
UINT16BE dirSt; /* first block of directory */
|
|
|
|
UINT16BE blLn; /* length of directory in blocks (0x000C) */
|
|
UINT16BE nmAlBlks; /* number of allocation blocks in volume (0x0187) */
|
|
UINT32BE alBlkSiz; /* size (in bytes) of allocation blocks (0x00000400) */
|
|
UINT32BE clpSiz; /* default clump size - number of bytes to allocate
|
|
when a file grows (0x00002000) */
|
|
UINT16BE alBlSt; /* first allocation block in volume (0x0010) */
|
|
|
|
UINT32BE nxtFNum; /* next unused file number */
|
|
UINT16BE freeABs; /* number of unused allocation blocks */
|
|
|
|
mac_str27 VN; /* volume name */
|
|
|
|
UINT8 ABlink[512-64];/* Link array for file ABs. Array of nmAlBlks
|
|
12-bit-long entries, indexed by AB address. If an
|
|
AB belongs to no file, the entry is 0; if an AB is
|
|
the last in any file, the entry is 1; if an AB
|
|
belongs to a file and is not the last one, the
|
|
entry is the AB address of the next file AB plus 1.
|
|
Note that the array extends on as many consecutive
|
|
disk blocks as needed (usually the MDB block plus
|
|
the next one). Incidentally, this array is not
|
|
saved in the secondary MDB: presumably, the idea
|
|
was that the disk utility could rely on the tag
|
|
data to rebuild the link array if it should ever
|
|
be corrupted. */
|
|
};
|
|
|
|
/*
|
|
HFS Master Directory Block
|
|
*/
|
|
struct hfs_mdb
|
|
{
|
|
/* First fields are similar to MFS, though several fields have a different meaning */
|
|
UINT8 sigWord[2]; /* volume signature - always $D2D7 */
|
|
UINT32BE crDate; /* date and time of volume creation */
|
|
UINT32BE lsMod; /* date and time of last modification */
|
|
UINT16BE atrb; /* volume attributes (0x0000) */
|
|
/* bit 15 is set if volume is locked by software */
|
|
UINT16BE nmFls; /* number of files in root folder */
|
|
UINT16BE VBMSt; /* first block of volume bitmap */
|
|
UINT16BE allocPtr; /* start of next allocation search */
|
|
|
|
UINT16BE nmAlBlks; /* number of allocation blocks in volume */
|
|
UINT32BE alBlkSiz; /* size (in bytes) of allocation blocks */
|
|
UINT32BE clpSiz; /* default clump size - number of bytes to allocate
|
|
when a file grows */
|
|
UINT16BE alBlSt; /* first allocation block in volume (0x0010) */
|
|
UINT32BE nxtCNID; /* next unused catalog node ID */
|
|
UINT16BE freeABs; /* number of unused allocation blocks */
|
|
mac_str27 VN; /* volume name */
|
|
|
|
/* next fields are HFS-specific */
|
|
|
|
UINT32BE volBkUp; /* date and time of last backup */
|
|
UINT16BE vSeqNum; /* volume backup sequence number */
|
|
UINT32BE wrCnt; /* volume write count */
|
|
UINT32BE xtClpSiz; /* clump size for extents overflow file */
|
|
UINT32BE ctClpSiz; /* clump size for catalog file */
|
|
UINT16BE nmRtDirs; /* number of directories in root folder */
|
|
UINT32BE filCnt; /* number of files in volume */
|
|
UINT32BE dirCnt; /* number of directories in volume */
|
|
UINT8 fndrInfo[32]; /* information used by the Finder */
|
|
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
UINT16BE VCSize; /* size (in blocks) of volume cache */
|
|
UINT16BE VBMCSize; /* size (in blocks) of volume bitmap cache */
|
|
UINT16BE ctlCSize; /* size (in blocks) of common volume cache */
|
|
};
|
|
struct
|
|
{
|
|
UINT16BE embedSigWord; /* embedded volume signature */
|
|
hfs_extent embedExtent; /* embedded volume location and size */
|
|
} v2;
|
|
} u;
|
|
|
|
UINT32BE xtFlSize; /* size (in bytes) of extents overflow file */
|
|
hfs_extent_3 xtExtRec; /* extent record for extents overflow file */
|
|
UINT32BE ctFlSize; /* size (in bytes) of catalog file */
|
|
hfs_extent_3 ctExtRec; /* extent record for catalog file */
|
|
};
|
|
|
|
/* to save a little stack space, we use the same buffer for MDB and next blocks */
|
|
union img_open_buf
|
|
{
|
|
struct mfs_mdb mfs_mdb;
|
|
struct hfs_mdb hfs_mdb;
|
|
UINT8 raw[512];
|
|
};
|
|
|
|
/*
|
|
Information extracted from catalog/directory
|
|
*/
|
|
struct mac_dirent
|
|
{
|
|
UINT16 dataRecType; /* type of data record */
|
|
|
|
mac_FInfo flFinderInfo; /* information used by the Finder */
|
|
mac_FXInfo flXFinderInfo; /* information used by the Finder */
|
|
|
|
UINT8 flags; /* bit 0=1 if file locked */
|
|
|
|
UINT32 fileID; /* file ID in directory/catalog */
|
|
|
|
UINT32 dataLogicalSize; /* logical EOF of data fork */
|
|
UINT32 dataPhysicalSize; /* physical EOF of data fork */
|
|
UINT32 rsrcLogicalSize; /* logical EOF of resource fork */
|
|
UINT32 rsrcPhysicalSize; /* physical EOF of resource fork */
|
|
|
|
UINT32 createDate; /* date and time of creation */
|
|
UINT32 modifyDate; /* date and time of last modification */
|
|
};
|
|
|
|
/*
|
|
Tag record for GCR floppies (12 bytes)
|
|
|
|
And, no, I don't know the format of the 20-byte tag record of the HD20
|
|
*/
|
|
struct floppy_tag_record
|
|
{
|
|
UINT32BE fileID; /* a.k.a. CNID */
|
|
/* a value of 1 seems to be the default for non-AB blocks, but this is not consistent */
|
|
UINT8 ftype; /* bit 1 = 1 if resource fork */
|
|
/* bit 0 = 1 if block is allocated to user file (i.e. it is not
|
|
in HFS extent & catalog, and not in non-AB blocks such
|
|
as MDB and MFS directory)??? */
|
|
/* bit 7 seems to be used, but I don't know what it means */
|
|
/* a value of $FF seems to be the default for non-AB blocks, but this is not consistent */
|
|
UINT8 fattr; /* bit 0 = 1 if locked(?) */
|
|
/* a value of $FF seems to be the default for non-AB blocks, but this is not consistent */
|
|
UINT16BE fblock; /* relative file block number (enough for any volume up to 32 MBytes in size) */
|
|
UINT32BE wrCnt; /* MFS: date and time of last write */
|
|
/* HFS: seems related to the wrCnt field in the mdb, i.e.
|
|
each time a volume is written to, the current value of
|
|
wrCnt is written in the tag field, then it is incremented */
|
|
/* (DV17 says "disk block number", but it cannot be true) */
|
|
};
|
|
|
|
#ifdef UNUSED_FUNCTION
|
|
static void hfs_image_close(struct mac_l2_imgref *l2_img);
|
|
#endif
|
|
static imgtoolerr_t mfs_file_get_nth_block_address(struct mac_fileref *fileref, UINT32 block_num, UINT32 *block_address);
|
|
static imgtoolerr_t hfs_file_get_nth_block_address(struct mac_fileref *fileref, UINT32 block_num, UINT32 *block_address);
|
|
static imgtoolerr_t mfs_lookup_path(struct mac_l2_imgref *l2_img, const char *fpath, mac_str255 filename, mac_dirent *cat_info, int create_it);
|
|
static imgtoolerr_t hfs_lookup_path(struct mac_l2_imgref *l2_img, const char *fpath, UINT32 *parID, mac_str255 filename, mac_dirent *cat_info);
|
|
static imgtoolerr_t mfs_file_open(struct mac_l2_imgref *l2_img, const mac_str255 filename, mac_forkID fork, struct mac_fileref *fileref);
|
|
static imgtoolerr_t hfs_file_open(struct mac_l2_imgref *l2_img, UINT32 parID, const mac_str255 filename, mac_forkID fork, struct mac_fileref *fileref);
|
|
static imgtoolerr_t mfs_file_setABeof(struct mac_fileref *fileref, UINT32 newABeof);
|
|
static imgtoolerr_t mfs_dir_update(struct mac_fileref *fileref);
|
|
|
|
static struct mac_l2_imgref *get_imgref(imgtool::image &img)
|
|
{
|
|
return (struct mac_l2_imgref *) imgtool_floppy_extrabytes(img);
|
|
}
|
|
|
|
|
|
#ifdef UNUSED_FUNCTION
|
|
/*
|
|
mac_image_close
|
|
|
|
Close a macintosh image.
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
*/
|
|
static void mac_image_close(struct mac_l2_imgref *l2_img)
|
|
{
|
|
switch (l2_img->format)
|
|
{
|
|
case L2I_MFS:
|
|
break;
|
|
|
|
case L2I_HFS:
|
|
hfs_image_close(l2_img);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
mac_lookup_path
|
|
|
|
Resolve a file path, and translate it to a parID + filename pair that enables
|
|
to do an efficient file search on a HFS volume (and an inefficient one on
|
|
MFS, but it is not an issue as MFS volumes typically have a few dozens
|
|
files, vs. possibly thousands with HFS volumes).
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
fpath (I): file path (C string)
|
|
parID (O): set to the CNID of the parent directory if the volume is in HFS
|
|
format (reserved for MFS volumes)
|
|
filename (O): set to the actual name of the file, with capitalization matching
|
|
the one on the volume rather than the one in the fpath parameter (Mac
|
|
string)
|
|
cat_info (O): catalog info for this file extracted from the catalog file
|
|
(may be NULL)
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mac_lookup_path(struct mac_l2_imgref *l2_img, const char *fpath, UINT32 *parID, mac_str255 filename, mac_dirent *cat_info, int create_it)
|
|
{
|
|
imgtoolerr_t err = IMGTOOLERR_UNEXPECTED;
|
|
|
|
switch (l2_img->format)
|
|
{
|
|
case L2I_MFS:
|
|
*parID = 0;
|
|
err = mfs_lookup_path(l2_img, fpath, filename, cat_info, create_it);
|
|
break;
|
|
|
|
case L2I_HFS:
|
|
err = hfs_lookup_path(l2_img, fpath, parID, filename, cat_info);
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
mac_file_open
|
|
|
|
Open a file located on a macintosh image
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
parID (I): CNID of the parent directory if the volume is in HFS format
|
|
(reserved for MFS volumes)
|
|
filename (I): name of the file (Mac string)
|
|
mac_forkID (I): tells which fork should be opened
|
|
fileref (O): mac file reference to open
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mac_file_open(struct mac_l2_imgref *l2_img, UINT32 parID, const mac_str255 filename, mac_forkID fork, struct mac_fileref *fileref)
|
|
{
|
|
switch (l2_img->format)
|
|
{
|
|
case L2I_MFS:
|
|
return mfs_file_open(l2_img, filename, fork, fileref);
|
|
|
|
case L2I_HFS:
|
|
return hfs_file_open(l2_img, parID, filename, fork, fileref);
|
|
}
|
|
|
|
return IMGTOOLERR_UNEXPECTED;
|
|
}
|
|
|
|
/*
|
|
mac_file_read
|
|
|
|
Read data from an open mac file, starting at current position in file
|
|
|
|
fileref (I/O): mac file reference
|
|
len (I): number of bytes to read
|
|
dest (O): destination buffer
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mac_file_read(struct mac_fileref *fileref, UINT32 len, void *dest)
|
|
{
|
|
UINT32 block = 0;
|
|
floppy_tag_record tag;
|
|
int run_len;
|
|
imgtoolerr_t err = IMGTOOLERR_SUCCESS;
|
|
|
|
if ((fileref->crPs + len) > fileref->eof)
|
|
/* EOF */
|
|
return IMGTOOLERR_UNEXPECTED;
|
|
|
|
while (len > 0)
|
|
{
|
|
if (fileref->reload_buf)
|
|
{
|
|
switch (fileref->l2_img->format)
|
|
{
|
|
case L2I_MFS:
|
|
err = mfs_file_get_nth_block_address(fileref, fileref->crPs/512, &block);
|
|
break;
|
|
|
|
case L2I_HFS:
|
|
err = hfs_file_get_nth_block_address(fileref, fileref->crPs/512, &block);
|
|
break;
|
|
}
|
|
if (err)
|
|
return err;
|
|
err = image_read_block(&fileref->l2_img->l1_img, block, fileref->block_buffer);
|
|
if (err)
|
|
return err;
|
|
fileref->reload_buf = FALSE;
|
|
|
|
if (TAG_EXTRA_CHECKS)
|
|
{
|
|
/* optional check */
|
|
if (image_get_tag_len(&fileref->l2_img->l1_img) == 12)
|
|
{
|
|
err = image_read_tag(&fileref->l2_img->l1_img, block, &tag);
|
|
if (err)
|
|
return err;
|
|
|
|
if ((get_UINT32BE(tag.fileID) != fileref->fileID)
|
|
|| (((tag.ftype & 2) != 0) != (fileref->forkType == rsrc_fork))
|
|
|| (get_UINT16BE(tag.fblock) != ((fileref->crPs/512) & 0xffff)))
|
|
{
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
run_len = 512 - (fileref->crPs % 512);
|
|
if (run_len > len)
|
|
run_len = len;
|
|
|
|
memcpy(dest, fileref->block_buffer+(fileref->crPs % 512), run_len);
|
|
len -= run_len;
|
|
dest = (UINT8 *)dest + run_len;
|
|
fileref->crPs += run_len;
|
|
if ((fileref->crPs % 512) == 0)
|
|
fileref->reload_buf = TRUE;
|
|
}
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
mac_file_write
|
|
|
|
Write data to an open mac file, starting at current position in file
|
|
|
|
fileref (I/O): mac file reference
|
|
len (I): number of bytes to read
|
|
dest (O): destination buffer
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mac_file_write(struct mac_fileref *fileref, UINT32 len, const void *src)
|
|
{
|
|
UINT32 block = 0;
|
|
floppy_tag_record tag;
|
|
int run_len;
|
|
imgtoolerr_t err = IMGTOOLERR_SUCCESS;
|
|
|
|
if ((fileref->crPs + len) > fileref->eof)
|
|
/* EOF */
|
|
return IMGTOOLERR_UNEXPECTED;
|
|
|
|
while (len > 0)
|
|
{
|
|
switch (fileref->l2_img->format)
|
|
{
|
|
case L2I_MFS:
|
|
err = mfs_file_get_nth_block_address(fileref, fileref->crPs/512, &block);
|
|
break;
|
|
|
|
case L2I_HFS:
|
|
err = hfs_file_get_nth_block_address(fileref, fileref->crPs/512, &block);
|
|
break;
|
|
}
|
|
if (err)
|
|
return err;
|
|
|
|
if (TAG_CHECKS)
|
|
{
|
|
/* optional check */
|
|
if (image_get_tag_len(&fileref->l2_img->l1_img) == 12)
|
|
{
|
|
err = image_read_tag(&fileref->l2_img->l1_img, block, &tag);
|
|
if (err)
|
|
return err;
|
|
|
|
if ((get_UINT32BE(tag.fileID) != fileref->fileID)
|
|
|| (((tag.ftype & 2) != 0) != (fileref->forkType == rsrc_fork))
|
|
|| (get_UINT16BE(tag.fblock) != ((fileref->crPs/512) & 0xffff)))
|
|
{
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fileref->reload_buf)
|
|
{
|
|
err = image_read_block(&fileref->l2_img->l1_img, block, fileref->block_buffer);
|
|
if (err)
|
|
return err;
|
|
fileref->reload_buf = FALSE;
|
|
}
|
|
run_len = 512 - (fileref->crPs % 512);
|
|
if (run_len > len)
|
|
run_len = len;
|
|
|
|
memcpy(fileref->block_buffer+(fileref->crPs % 512), src, run_len);
|
|
err = image_write_block(&fileref->l2_img->l1_img, block, fileref->block_buffer);
|
|
if (err)
|
|
return err;
|
|
/* update tag data */
|
|
if (image_get_tag_len(&fileref->l2_img->l1_img) == 12)
|
|
{
|
|
if (!TAG_CHECKS)
|
|
{
|
|
err = image_read_tag(&fileref->l2_img->l1_img, block, &tag);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
switch (fileref->l2_img->format)
|
|
{
|
|
case L2I_MFS:
|
|
set_UINT32BE(&tag.wrCnt, mac_time_now());
|
|
break;
|
|
|
|
case L2I_HFS:
|
|
/*set_UINT32BE(&tag.wrCnt, ++fileref->l2_img.u.hfs.wrCnt);*/ /* ***TODO*** */
|
|
break;
|
|
}
|
|
|
|
err = image_write_tag(&fileref->l2_img->l1_img, block, &tag);
|
|
if (err)
|
|
return err;
|
|
}
|
|
len -= run_len;
|
|
src = (const UINT8 *)src + run_len;
|
|
fileref->crPs += run_len;
|
|
if ((fileref->crPs % 512) == 0)
|
|
fileref->reload_buf = TRUE;
|
|
}
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
#ifdef UNUSED_FUNCTION
|
|
/*
|
|
mac_file_tell
|
|
|
|
Get current position in an open mac file
|
|
|
|
fileref (I/O): mac file reference
|
|
filePos (O): current position in file
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mac_file_tell(struct mac_fileref *fileref, UINT32 *filePos)
|
|
{
|
|
*filePos = fileref->crPs;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
mac_file_seek
|
|
|
|
Set current position in an open mac file
|
|
|
|
fileref (I/O): mac file reference
|
|
filePos (I): new position in file
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mac_file_seek(struct mac_fileref *fileref, UINT32 filePos)
|
|
{
|
|
if ((fileref->crPs / 512) != (filePos / 512))
|
|
fileref->reload_buf = TRUE;
|
|
|
|
fileref->crPs = filePos;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
mac_file_seteof
|
|
|
|
Set the position of the EOF in an open mac file
|
|
|
|
fileref (I/O): mac file reference
|
|
newEof (I): new position of EOF in file
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mac_file_seteof(struct mac_fileref *fileref, UINT32 newEof)
|
|
{
|
|
UINT32 newABEof;
|
|
imgtoolerr_t err = IMGTOOLERR_SUCCESS;
|
|
|
|
newABEof = (newEof + fileref->l2_img->blocksperAB * 512 - 1) / (fileref->l2_img->blocksperAB * 512);
|
|
|
|
#if 0
|
|
if (fileref->pLen % (fileref->l2_img->blocksperAB * 512))
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
#endif
|
|
|
|
if (newEof < fileref->eof)
|
|
fileref->eof = newEof;
|
|
|
|
switch (fileref->l2_img->format)
|
|
{
|
|
case L2I_MFS:
|
|
err = mfs_file_setABeof(fileref, newABEof);
|
|
break;
|
|
|
|
case L2I_HFS:
|
|
err = IMGTOOLERR_UNIMPLEMENTED;
|
|
break;
|
|
}
|
|
if (err)
|
|
return err;
|
|
|
|
fileref->eof = newEof;
|
|
|
|
err = mfs_dir_update(fileref);
|
|
if (err)
|
|
return err;
|
|
|
|
/* update current pos if beyond new EOF */
|
|
#if 0
|
|
if (fileref->crPs > newEof)
|
|
{
|
|
if ((fileref->crPs / 512) != (newEof / 512))
|
|
fileref->reload_buf = TRUE;
|
|
|
|
fileref->crPs = newEof;
|
|
}
|
|
#endif
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
#if 0
|
|
#pragma mark -
|
|
#pragma mark MFS IMPLEMENTATION
|
|
#endif
|
|
|
|
/*
|
|
directory entry for use in the directory file
|
|
|
|
Note the structure is variable length. It is always word-aligned, and
|
|
cannot cross block boundaries.
|
|
|
|
Note that the directory does not seem to be sorted: the order in which
|
|
files appear does not match file names, and it does not always match file
|
|
IDs.
|
|
*/
|
|
struct mfs_dir_entry
|
|
{
|
|
UINT8 flags; /* bit 7=1 if entry used, bit 0=1 if file locked */
|
|
/* 0x00 means end of block: if we are not done
|
|
with reading the directory, the remnants will
|
|
be read from next block */
|
|
UINT8 flVersNum; /* version number (usually 0x00, but I don't
|
|
have the IM volume that describes it) */
|
|
mac_FInfo flFinderInfo; /* information used by the Finder */
|
|
|
|
UINT32BE fileID; /* file ID */
|
|
|
|
UINT16BE dataStartBlock; /* first allocation block of data fork */
|
|
UINT32BE dataLogicalSize; /* logical EOF of data fork */
|
|
UINT32BE dataPhysicalSize; /* physical EOF of data fork */
|
|
UINT16BE rsrcStartBlock; /* first allocation block of resource fork */
|
|
UINT32BE rsrcLogicalSize; /* logical EOF of resource fork */
|
|
UINT32BE rsrcPhysicalSize; /* physical EOF of resource fork */
|
|
|
|
UINT32BE createDate; /* date and time of creation */
|
|
UINT32BE modifyDate; /* date and time of last modification */
|
|
|
|
UINT8 name[1]; /* first char is length of file name */
|
|
/* next chars are file name - 255 chars at most */
|
|
/* IIRC, Finder 7 only supports 31 chars,
|
|
wheareas earlier versions support 63 chars */
|
|
};
|
|
|
|
/*
|
|
FOBJ desktop resource: describes a folder, or the location of the volume
|
|
icon.
|
|
|
|
In typical Apple manner, this resource is not documented. However, I have
|
|
managed to reverse engineer some parts of it.
|
|
*/
|
|
struct mfs_FOBJ
|
|
{
|
|
UINT8 unknown0[2]; /* $00: $0004 for disk, $0008 for folder??? */
|
|
mac_point location; /* $02: location in parent window */
|
|
UINT8 unknown1[4]; /* $06: ??? */
|
|
UINT8 view; /* $0A: manner in which folders are displayed??? */
|
|
UINT8 unknown2; /* $0B: ??? */
|
|
UINT16BE par_fldr; /* $0C: parent folder ID */
|
|
UINT8 unknown3[10]; /* $0E: ??? */
|
|
UINT16BE unknown4; /* $18: ??? */
|
|
UINT32BE createDate; /* $1A: date and time of creation */
|
|
UINT32BE modifyDate; /* $1E: date and time of last modification */
|
|
UINT16BE unknown5; /* $22: put-away folder ID?????? */
|
|
UINT8 unknown6[8]; /* $24: ??? */
|
|
mac_rect bounds; /* $2C: window bounds */
|
|
mac_point scroll; /* $34: current scroll offset??? */
|
|
union
|
|
{ /* I think there are two versions of the structure */
|
|
struct
|
|
{
|
|
UINT16BE item_count; /* number of items (folders and files) in
|
|
this folder */
|
|
UINT32BE item_descs[1]; /* this variable-length array has
|
|
item_count entries - meaning of entry is unknown */
|
|
} v1;
|
|
struct
|
|
{
|
|
UINT16BE zerofill; /* always 0? */
|
|
UINT16BE unknown0; /* always 0??? */
|
|
UINT16BE item_count; /* number of items (folders and files) in
|
|
this folder */
|
|
UINT8 unknown1[20]; /* ??? */
|
|
UINT8 name[1]; /* variable-length macintosh string */
|
|
} v2;
|
|
} u;
|
|
};
|
|
|
|
/*
|
|
MFS open dir ref
|
|
*/
|
|
struct mfs_dirref
|
|
{
|
|
struct mac_l2_imgref *l2_img; /* image pointer */
|
|
UINT16 index; /* current file index in the disk directory */
|
|
UINT16 cur_block; /* current block offset in directory file */
|
|
UINT16 cur_offset; /* current byte offset in current block of directory file */
|
|
UINT8 block_buffer[512]; /* buffer with current directory block */
|
|
};
|
|
|
|
|
|
|
|
static imgtoolerr_t mfs_image_create(imgtool::image &image, imgtool::stream::ptr &&dummy, util::option_resolution *opts)
|
|
{
|
|
imgtoolerr_t err;
|
|
UINT8 buffer[512];
|
|
UINT32 heads, tracks, sector_bytes, i;
|
|
UINT32 total_disk_blocks, total_allocation_blocks, allocation_block_size;
|
|
UINT32 free_allocation_blocks;
|
|
|
|
heads = opts->lookup_int('H');
|
|
tracks = opts->lookup_int('T');
|
|
sector_bytes = opts->lookup_int('L');
|
|
|
|
get_imgref(image)->l1_img.image = ℑ
|
|
get_imgref(image)->l1_img.heads = heads;
|
|
|
|
if (sector_bytes != 512)
|
|
return IMGTOOLERR_UNEXPECTED;
|
|
|
|
/* computation */
|
|
allocation_block_size = 1024;
|
|
total_disk_blocks = 0;
|
|
for (i = 0; i < tracks; i++)
|
|
total_disk_blocks += heads * apple35_sectors_per_track(imgtool_floppy(image), i) * sector_bytes / 512;
|
|
total_allocation_blocks = total_disk_blocks / (allocation_block_size / 512);
|
|
free_allocation_blocks = total_allocation_blocks - 3;
|
|
|
|
/* write master directory block */
|
|
memset(buffer, 0, sizeof(buffer));
|
|
place_integer_be(buffer, 0, 2, 0xd2d7); /* signature */
|
|
place_integer_be(buffer, 2, 4, mac_time_now()); /* creation date */
|
|
place_integer_be(buffer, 6, 4, mac_time_now()); /* last modified date */
|
|
place_integer_be(buffer, 10, 2, 0); /* volume attributes */
|
|
place_integer_be(buffer, 12, 2, 0); /* number of files in directory */
|
|
place_integer_be(buffer, 14, 2, 4); /* first block of directory */
|
|
place_integer_be(buffer, 16, 2, 12); /* length of directory in blocks */
|
|
place_integer_be(buffer, 18, 2, total_allocation_blocks); /* allocation blocks on volume count */
|
|
place_integer_be(buffer, 20, 4, allocation_block_size); /* allocation block size */
|
|
place_integer_be(buffer, 24, 4, 8192); /* default clumping size */
|
|
place_integer_be(buffer, 28, 2, 16); /* first allocation block on volume */
|
|
place_integer_be(buffer, 30, 4, 2); /* next unused catalog node */
|
|
place_integer_be(buffer, 34, 2, free_allocation_blocks); /* free allocation block count */
|
|
pascal_from_c_string(&buffer[36], 28, "Untitled"); /* volume title */
|
|
|
|
err = image_write_block(&get_imgref(image)->l1_img, 2, buffer);
|
|
if (err)
|
|
return err;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
mfs_image_open
|
|
|
|
Open a MFS image. Image must already be open on level 1. This function
|
|
should not be called directly: call mac_image_open() instead.
|
|
|
|
l2_img (I/O): level-2 image reference to open (l1_img and format fields
|
|
must be initialized)
|
|
img_open_buf (I): buffer with the MDB block
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mfs_image_open(imgtool::image &image, imgtool::stream::ptr &&dummy)
|
|
{
|
|
imgtoolerr_t err;
|
|
struct mac_l2_imgref *l2_img;
|
|
img_open_buf buf_local;
|
|
img_open_buf *buf;
|
|
|
|
l2_img = get_imgref(image);
|
|
l2_img->l1_img.image = ℑ
|
|
l2_img->l1_img.heads = 1;
|
|
l2_img->format = L2I_MFS;
|
|
|
|
/* read MDB */
|
|
err = image_read_block(&l2_img->l1_img, 2, &buf_local.raw);
|
|
if (err)
|
|
return err;
|
|
buf = &buf_local;
|
|
|
|
/* check signature word */
|
|
if ((buf->mfs_mdb.sigWord[0] != 0xd2) || (buf->mfs_mdb.sigWord[1] != 0xd7)
|
|
|| (buf->mfs_mdb.VN[0] > 27))
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
l2_img->u.mfs.dir_num_files = get_UINT16BE(buf->mfs_mdb.nmFls);
|
|
l2_img->u.mfs.dir_start = get_UINT16BE(buf->mfs_mdb.dirSt);
|
|
l2_img->u.mfs.dir_blk_len = get_UINT16BE(buf->mfs_mdb.blLn);
|
|
|
|
l2_img->numABs = get_UINT16BE(buf->mfs_mdb.nmAlBlks);
|
|
if ((get_UINT32BE(buf->mfs_mdb.alBlkSiz) % 512) || (get_UINT32BE(buf->mfs_mdb.alBlkSiz) == 0))
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
l2_img->blocksperAB = get_UINT32BE(buf->mfs_mdb.alBlkSiz) / 512;
|
|
l2_img->u.mfs.ABStart = get_UINT16BE(buf->mfs_mdb.alBlSt);
|
|
|
|
l2_img->nxtCNID = get_UINT32BE(buf->mfs_mdb.nxtFNum);
|
|
|
|
l2_img->freeABs = get_UINT16BE(buf->mfs_mdb.freeABs);
|
|
|
|
mac_strcpy(l2_img->u.mfs.volname, buf->mfs_mdb.VN);
|
|
|
|
if (l2_img->numABs > 4094)
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
/* extract link array */
|
|
{
|
|
int byte_len = l2_img->numABs + ((l2_img->numABs + 1) >> 1);
|
|
int cur_byte;
|
|
int cur_block;
|
|
int block_len = sizeof(buf->mfs_mdb.ABlink);
|
|
|
|
/* clear dirty flags */
|
|
for (cur_block=0; cur_block<13; cur_block++)
|
|
l2_img->u.mfs.ABlink_dirty[cur_block] = 0;
|
|
|
|
/* append the chunk after MDB to link array */
|
|
cur_byte = 0;
|
|
if (block_len > (byte_len - cur_byte))
|
|
block_len = byte_len - cur_byte;
|
|
memcpy(l2_img->u.mfs.ABlink+cur_byte, buf->mfs_mdb.ABlink, block_len);
|
|
cur_byte += block_len;
|
|
cur_block = 2;
|
|
while (cur_byte < byte_len)
|
|
{
|
|
/* read next block */
|
|
cur_block++;
|
|
err = image_read_block(&l2_img->l1_img, cur_block, buf->raw);
|
|
if (err)
|
|
return err;
|
|
block_len = 512;
|
|
|
|
/* append this block to link array */
|
|
if (block_len > (byte_len - cur_byte))
|
|
block_len = byte_len - cur_byte;
|
|
memcpy(l2_img->u.mfs.ABlink+cur_byte, buf->raw, block_len);
|
|
cur_byte += block_len;
|
|
}
|
|
/* check that link array and directory don't overlap */
|
|
if (cur_block >= l2_img->u.mfs.dir_start)
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
}
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
mfs_update_mdb
|
|
|
|
Update MDB on disk
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mfs_update_mdb(struct mac_l2_imgref *l2_img)
|
|
{
|
|
imgtoolerr_t err;
|
|
union
|
|
{
|
|
struct mfs_mdb mfs_mdb;
|
|
UINT8 raw[512];
|
|
} buf;
|
|
|
|
assert(l2_img->format == L2I_MFS);
|
|
|
|
/* read MDB */
|
|
err = image_read_block(&l2_img->l1_img, 2, &buf.mfs_mdb);
|
|
if (err)
|
|
return err;
|
|
|
|
set_UINT16BE(&buf.mfs_mdb.nmFls, l2_img->u.mfs.dir_num_files);
|
|
#if 0 /* these fields are never changed */
|
|
set_UINT16BE(&buf.mfs_mdb.dirSt, l2_img->u.mfs.dir_start);
|
|
set_UINT16BE(&buf.mfs_mdb.blLn, l2_img->u.mfs.dir_blk_len);
|
|
|
|
set_UINT16BE(&buf.mfs_mdb.nmAlBlks, l2_img->numABs);
|
|
set_UINT32BE(&buf.mfs_mdb.alBlkSiz, l2_img->blocksperAB*512);
|
|
set_UINT16BE(&buf.mfs_mdb.alBlSt, l2_img->u.mfs.ABStart);
|
|
#endif
|
|
|
|
set_UINT32BE(&buf.mfs_mdb.nxtFNum, l2_img->nxtCNID);
|
|
|
|
set_UINT16BE(&buf.mfs_mdb.freeABs, l2_img->freeABs);
|
|
|
|
#if 0 /* these fields are never changed */
|
|
mac_strcpy(buf.mfs_mdb.VN, l2_img->u.mfs.volname);
|
|
#endif
|
|
|
|
/* save link array */
|
|
{
|
|
int byte_len = l2_img->numABs + ((l2_img->numABs + 1) >> 1);
|
|
int cur_byte = 0;
|
|
int cur_block = 2;
|
|
int block_len = sizeof(buf.mfs_mdb.ABlink);
|
|
|
|
/* update the chunk of link array after the MDB */
|
|
if (block_len > (byte_len - cur_byte))
|
|
block_len = byte_len - cur_byte;
|
|
memcpy(buf.mfs_mdb.ABlink, l2_img->u.mfs.ABlink+cur_byte, block_len);
|
|
cur_byte += block_len;
|
|
|
|
if (block_len < sizeof(buf.mfs_mdb.ABlink))
|
|
memset(buf.mfs_mdb.ABlink+block_len, 0, sizeof(buf.mfs_mdb.ABlink)-block_len);
|
|
|
|
l2_img->u.mfs.ABlink_dirty[0] = 0;
|
|
|
|
/* write back modified MDB+link */
|
|
err = image_write_block(&l2_img->l1_img, 2, &buf.mfs_mdb);
|
|
if (err)
|
|
return err;
|
|
|
|
while (cur_byte < byte_len)
|
|
{
|
|
/* advance to next block */
|
|
cur_block++;
|
|
block_len = 512;
|
|
|
|
/* extract the current chunk of link array */
|
|
if (block_len > (byte_len - cur_byte))
|
|
block_len = byte_len - cur_byte;
|
|
|
|
if (l2_img->u.mfs.ABlink_dirty[cur_block-2])
|
|
{
|
|
memcpy(buf.raw, l2_img->u.mfs.ABlink+cur_byte, block_len);
|
|
if (block_len < 512)
|
|
memset(buf.raw+block_len, 0, 512-block_len);
|
|
/* write back link array */
|
|
err = image_write_block(&l2_img->l1_img, cur_block, buf.raw);
|
|
if (err)
|
|
return err;
|
|
l2_img->u.mfs.ABlink_dirty[cur_block-2] = 0;
|
|
}
|
|
|
|
cur_byte += block_len;
|
|
}
|
|
}
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
mfs_dir_open
|
|
|
|
Open the directory file
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
dirref (O): open directory file reference
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mfs_dir_open(struct mac_l2_imgref *l2_img, const char *path, mfs_dirref *dirref)
|
|
{
|
|
imgtoolerr_t err;
|
|
|
|
assert(l2_img->format == L2I_MFS);
|
|
|
|
if (path[0])
|
|
return IMGTOOLERR_PATHNOTFOUND;
|
|
|
|
dirref->l2_img = l2_img;
|
|
dirref->index = 0;
|
|
|
|
dirref->cur_block = 0;
|
|
dirref->cur_offset = 0;
|
|
err = image_read_block(&l2_img->l1_img, l2_img->u.mfs.dir_start + dirref->cur_block, dirref->block_buffer);
|
|
if (err)
|
|
return err;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
mfs_dir_read
|
|
|
|
Read one entry of directory file
|
|
|
|
dirref (I/O): open directory file reference
|
|
dir_entry (O): set to point to the entry read: set to NULL if EOF or error
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mfs_dir_read(mfs_dirref *dirref, mfs_dir_entry **dir_entry)
|
|
{
|
|
mfs_dir_entry *cur_dir_entry;
|
|
size_t cur_dir_entry_len;
|
|
imgtoolerr_t err;
|
|
|
|
|
|
if (dir_entry)
|
|
*dir_entry = NULL;
|
|
|
|
if (dirref->index == dirref->l2_img->u.mfs.dir_num_files)
|
|
/* EOF */
|
|
return IMGTOOLERR_SUCCESS;
|
|
|
|
/* get cat entry pointer */
|
|
cur_dir_entry = (mfs_dir_entry *) (dirref->block_buffer + dirref->cur_offset);
|
|
while ((dirref->cur_offset == 512) || ! (cur_dir_entry->flags & 0x80))
|
|
{
|
|
dirref->cur_block++;
|
|
dirref->cur_offset = 0;
|
|
if (dirref->cur_block > dirref->l2_img->u.mfs.dir_blk_len)
|
|
/* aargh! */
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
err = image_read_block(&dirref->l2_img->l1_img, dirref->l2_img->u.mfs.dir_start + dirref->cur_block, dirref->block_buffer);
|
|
if (err)
|
|
return err;
|
|
cur_dir_entry = (mfs_dir_entry *) (dirref->block_buffer + dirref->cur_offset);
|
|
}
|
|
|
|
cur_dir_entry_len = offsetof(mfs_dir_entry, name) + cur_dir_entry->name[0] + 1;
|
|
|
|
if ((dirref->cur_offset + cur_dir_entry_len) > 512)
|
|
/* aargh! */
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
/* entry looks valid: set pointer to entry */
|
|
if (dir_entry)
|
|
*dir_entry = cur_dir_entry;
|
|
|
|
/* update offset in block */
|
|
dirref->cur_offset += cur_dir_entry_len;
|
|
/* align to word boundary */
|
|
dirref->cur_offset = (dirref->cur_offset + 1) & ~1;
|
|
|
|
/* update file count */
|
|
dirref->index++;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
mfs_dir_insert
|
|
|
|
Add an entry in the directory file
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
dirref (I/O): open directory file reference
|
|
filename (I): name of the file for which an entry is created (Mac string)
|
|
dir_entry (O): set to point to the created entry: set to NULL if EOF or
|
|
error
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mfs_dir_insert(struct mac_l2_imgref *l2_img, mfs_dirref *dirref, const UINT8 *new_fname, mfs_dir_entry **dir_entry)
|
|
{
|
|
size_t new_dir_entry_len;
|
|
mfs_dir_entry *cur_dir_entry;
|
|
size_t cur_dir_entry_len;
|
|
UINT32 cur_date;
|
|
imgtoolerr_t err;
|
|
|
|
dirref->l2_img = l2_img;
|
|
dirref->index = 0;
|
|
|
|
new_dir_entry_len = offsetof(mfs_dir_entry, name) + new_fname[0] + 1;
|
|
|
|
for (dirref->cur_block = 0; dirref->cur_block < dirref->l2_img->u.mfs.dir_blk_len; dirref->cur_block++)
|
|
{
|
|
/* read current block */
|
|
err = image_read_block(&dirref->l2_img->l1_img, dirref->l2_img->u.mfs.dir_start + dirref->cur_block, dirref->block_buffer);
|
|
if (err)
|
|
return err;
|
|
|
|
/* get free chunk in this block */
|
|
dirref->cur_offset = 0;
|
|
cur_dir_entry = (mfs_dir_entry *) (dirref->block_buffer + dirref->cur_offset);
|
|
while ((dirref->cur_offset < 512) && (cur_dir_entry->flags & 0x80))
|
|
{ /* skip cur_dir_entry */
|
|
cur_dir_entry_len = offsetof(mfs_dir_entry, name) + cur_dir_entry->name[0] + 1;
|
|
/* update offset in block */
|
|
dirref->cur_offset += cur_dir_entry_len;
|
|
/* align to word boundary */
|
|
dirref->cur_offset = (dirref->cur_offset + 1) & ~1;
|
|
/* update entry pointer */
|
|
cur_dir_entry = (mfs_dir_entry *) (dirref->block_buffer + dirref->cur_offset);
|
|
/* update file index (useless, but can't harm) */
|
|
dirref->index++;
|
|
}
|
|
|
|
if (new_dir_entry_len <= (/*512*/510 - dirref->cur_offset))
|
|
{
|
|
/*memcpy(cur_dir_entry, new_dir_entry, new_dir_entry_len);*/
|
|
cur_dir_entry->flags = 0x80;
|
|
cur_dir_entry->flVersNum = 0x00;
|
|
memset(&cur_dir_entry->flFinderInfo, 0, sizeof(cur_dir_entry->flFinderInfo));
|
|
set_UINT32BE(&cur_dir_entry->fileID, dirref->l2_img->nxtCNID++);
|
|
set_UINT16BE(&cur_dir_entry->dataStartBlock, 1);
|
|
set_UINT32BE(&cur_dir_entry->dataLogicalSize, 0);
|
|
set_UINT32BE(&cur_dir_entry->dataPhysicalSize, 0);
|
|
set_UINT16BE(&cur_dir_entry->rsrcStartBlock, 1);
|
|
set_UINT32BE(&cur_dir_entry->rsrcLogicalSize, 0);
|
|
set_UINT32BE(&cur_dir_entry->rsrcPhysicalSize, 0);
|
|
cur_date = mac_time_now();
|
|
set_UINT32BE(&cur_dir_entry->createDate, cur_date);
|
|
set_UINT32BE(&cur_dir_entry->modifyDate, cur_date);
|
|
mac_strcpy(cur_dir_entry->name, new_fname);
|
|
|
|
/* update offset in block */
|
|
dirref->cur_offset += new_dir_entry_len;
|
|
/* align to word boundary */
|
|
dirref->cur_offset = (dirref->cur_offset + 1) & ~1;
|
|
if (dirref->cur_offset < 512)
|
|
/* mark remaining space as free record */
|
|
dirref->block_buffer[dirref->cur_offset] = 0;
|
|
/* write back directory */
|
|
err = image_write_block(&dirref->l2_img->l1_img, dirref->l2_img->u.mfs.dir_start + dirref->cur_block, dirref->block_buffer);
|
|
if (err)
|
|
return err;
|
|
/* update file count */
|
|
dirref->l2_img->u.mfs.dir_num_files++;
|
|
|
|
/* update MDB (nxtCNID & dir_num_files fields) */
|
|
err = mfs_update_mdb(dirref->l2_img);
|
|
if (err)
|
|
return err;
|
|
|
|
if (dir_entry)
|
|
*dir_entry = cur_dir_entry;
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return IMGTOOLERR_NOSPACE;
|
|
}
|
|
|
|
/*
|
|
mfs_dir_update
|
|
|
|
Update one entry of directory file
|
|
|
|
fileref (I/O): open file reference
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mfs_dir_update(struct mac_fileref *fileref)
|
|
{
|
|
UINT16 cur_block;
|
|
UINT16 cur_offset;
|
|
UINT8 block_buffer[512];
|
|
mfs_dir_entry *cur_dir_entry;
|
|
size_t cur_dir_entry_len;
|
|
imgtoolerr_t err;
|
|
|
|
for (cur_block = 0; cur_block < fileref->l2_img->u.mfs.dir_blk_len; cur_block++)
|
|
{
|
|
/* read current block */
|
|
err = image_read_block(&fileref->l2_img->l1_img, fileref->l2_img->u.mfs.dir_start + cur_block, block_buffer);
|
|
if (err)
|
|
return err;
|
|
|
|
/* get free chunk in this block */
|
|
cur_offset = 0;
|
|
cur_dir_entry = (mfs_dir_entry *) (block_buffer + cur_offset);
|
|
while ((cur_offset < 512) && (cur_dir_entry->flags & 0x80))
|
|
{
|
|
if (get_UINT32BE(cur_dir_entry->fileID) == fileref->fileID)
|
|
{ /* found it: update directory entry */
|
|
switch (fileref->forkType)
|
|
{
|
|
case data_fork:
|
|
set_UINT16BE(&cur_dir_entry->dataStartBlock, fileref->mfs.stBlk);
|
|
set_UINT32BE(&cur_dir_entry->dataLogicalSize, fileref->eof);
|
|
set_UINT32BE(&cur_dir_entry->dataPhysicalSize, fileref->pLen);
|
|
break;
|
|
|
|
case rsrc_fork:
|
|
set_UINT16BE(&cur_dir_entry->rsrcStartBlock, fileref->mfs.stBlk);
|
|
set_UINT32BE(&cur_dir_entry->rsrcLogicalSize, fileref->eof);
|
|
set_UINT32BE(&cur_dir_entry->rsrcPhysicalSize, fileref->pLen);
|
|
break;
|
|
}
|
|
/* write back directory */
|
|
err = image_write_block(&fileref->l2_img->l1_img, fileref->l2_img->u.mfs.dir_start + cur_block, block_buffer);
|
|
if (err)
|
|
return err;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
/* skip cur_dir_entry */
|
|
cur_dir_entry_len = offsetof(mfs_dir_entry, name) + cur_dir_entry->name[0] + 1;
|
|
/* update offset in block */
|
|
cur_offset += cur_dir_entry_len;
|
|
/* align to word boundary */
|
|
cur_offset = (cur_offset + 1) & ~1;
|
|
/* update entry pointer */
|
|
cur_dir_entry = (mfs_dir_entry *) (block_buffer + cur_offset);
|
|
}
|
|
}
|
|
|
|
return IMGTOOLERR_UNEXPECTED;
|
|
}
|
|
|
|
|
|
/*
|
|
mfs_find_dir_entry
|
|
|
|
Find a file in an MFS directory
|
|
|
|
dirref (I/O): open directory file reference
|
|
filename (I): file name (Mac string)
|
|
dir_entry (O): set to point to the entry read: set to NULL if EOF or error
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mfs_find_dir_entry(mfs_dirref *dirref, const mac_str255 filename, mfs_dir_entry **dir_entry)
|
|
{
|
|
mfs_dir_entry *cur_dir_entry;
|
|
imgtoolerr_t err;
|
|
|
|
if (dir_entry)
|
|
*dir_entry = NULL;
|
|
|
|
/* scan dir for file */
|
|
while (1)
|
|
{
|
|
err = mfs_dir_read(dirref, &cur_dir_entry);
|
|
if (err)
|
|
return err;
|
|
if (!cur_dir_entry)
|
|
/* EOF */
|
|
break;
|
|
if ((! mac_stricmp(filename, cur_dir_entry->name)) && (cur_dir_entry->flVersNum == 0))
|
|
{ /* file found */
|
|
|
|
if (dir_entry)
|
|
*dir_entry = cur_dir_entry;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return IMGTOOLERR_FILENOTFOUND;
|
|
}
|
|
|
|
/*
|
|
mfs_lookup_path
|
|
|
|
Resolve a file path for MFS volumes. This function should not be called
|
|
directly: call mac_lookup_path instead.
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
fpath (I): file path (C string)
|
|
filename (O): set to the actual name of the file, with capitalization matching
|
|
the one on the volume rather than the one in the fpath parameter (Mac
|
|
string)
|
|
cat_info (I/O): on output, catalog info for this file extracted from the
|
|
catalog file (may be NULL)
|
|
If create_it is TRUE, created info will first be set according to the
|
|
data from cat_info
|
|
create_it (I): TRUE if entry should be created if not found
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mfs_lookup_path(struct mac_l2_imgref *l2_img, const char *fpath, mac_str255 filename, mac_dirent *cat_info, int create_it)
|
|
{
|
|
mfs_dirref dirref;
|
|
mfs_dir_entry *dir_entry;
|
|
imgtoolerr_t err;
|
|
|
|
/* rapid check */
|
|
if (strchr(fpath, ':'))
|
|
return IMGTOOLERR_BADFILENAME;
|
|
|
|
/* extract file name */
|
|
c_to_mac_strncpy(filename, fpath, strlen(fpath));
|
|
|
|
/* open dir */
|
|
mfs_dir_open(l2_img, "", &dirref);
|
|
|
|
/* find file */
|
|
err = mfs_find_dir_entry(&dirref, filename, &dir_entry);
|
|
if ((err == IMGTOOLERR_FILENOTFOUND) && create_it)
|
|
err = mfs_dir_insert(l2_img, &dirref, filename, &dir_entry);
|
|
if (err)
|
|
return err;
|
|
|
|
mac_strcpy(filename, dir_entry->name);
|
|
|
|
if (create_it && cat_info)
|
|
{
|
|
dir_entry->flFinderInfo = cat_info->flFinderInfo;
|
|
dir_entry->flags = (dir_entry->flags & 0x80) | (cat_info->flags & 0x7f);
|
|
set_UINT32BE(&dir_entry->createDate, cat_info->createDate);
|
|
set_UINT32BE(&dir_entry->modifyDate, cat_info->modifyDate);
|
|
|
|
/* write current directory block */
|
|
err = image_write_block(&l2_img->l1_img, l2_img->u.mfs.dir_start + dirref.cur_block, dirref.block_buffer);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
if (cat_info)
|
|
{
|
|
cat_info->flFinderInfo = dir_entry->flFinderInfo;
|
|
memset(&cat_info->flXFinderInfo, 0, sizeof(cat_info->flXFinderInfo));
|
|
cat_info->flags = dir_entry->flags;
|
|
cat_info->fileID = get_UINT32BE(dir_entry->fileID);
|
|
cat_info->dataLogicalSize = get_UINT32BE(dir_entry->dataLogicalSize);
|
|
cat_info->dataPhysicalSize = get_UINT32BE(dir_entry->dataPhysicalSize);
|
|
cat_info->rsrcLogicalSize = get_UINT32BE(dir_entry->rsrcLogicalSize);
|
|
cat_info->rsrcPhysicalSize = get_UINT32BE(dir_entry->rsrcPhysicalSize);
|
|
cat_info->createDate = get_UINT32BE(dir_entry->createDate);
|
|
cat_info->modifyDate = get_UINT32BE(dir_entry->modifyDate);
|
|
cat_info->dataRecType = 0x200; /* hcrt_File */
|
|
}
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
mfs_file_open_internal
|
|
|
|
Open a file fork, given its directory entry. This function should not be
|
|
called directly: call mfs_file_open instead.
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
dir_entry (I): directory entry for the file to open
|
|
mac_forkID (I): tells which fork should be opened
|
|
fileref (O): mac file reference to open
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mfs_file_open_internal(struct mac_l2_imgref *l2_img, const mfs_dir_entry *dir_entry, mac_forkID fork, struct mac_fileref *fileref)
|
|
{
|
|
assert(l2_img->format == L2I_MFS);
|
|
|
|
fileref->l2_img = l2_img;
|
|
|
|
fileref->fileID = get_UINT32BE(dir_entry->fileID);
|
|
fileref->forkType = fork;
|
|
|
|
switch (fork)
|
|
{
|
|
case data_fork:
|
|
fileref->mfs.stBlk = get_UINT16BE(dir_entry->dataStartBlock);
|
|
fileref->eof = get_UINT32BE(dir_entry->dataLogicalSize);
|
|
fileref->pLen = get_UINT32BE(dir_entry->dataPhysicalSize);
|
|
break;
|
|
|
|
case rsrc_fork:
|
|
fileref->mfs.stBlk = get_UINT16BE(dir_entry->rsrcStartBlock);
|
|
fileref->eof = get_UINT32BE(dir_entry->rsrcLogicalSize);
|
|
fileref->pLen = get_UINT32BE(dir_entry->rsrcPhysicalSize);
|
|
break;
|
|
}
|
|
|
|
fileref->crPs = 0;
|
|
|
|
fileref->reload_buf = TRUE;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
mfs_file_open
|
|
|
|
Open a file located on a MFS volume. This function should not be called
|
|
directly: call mac_file_open instead.
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
filename (I): name of the file (Mac string)
|
|
mac_forkID (I): tells which fork should be opened
|
|
fileref (O): mac file reference to open
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mfs_file_open(struct mac_l2_imgref *l2_img, const mac_str255 filename, mac_forkID fork, struct mac_fileref *fileref)
|
|
{
|
|
mfs_dirref dirref;
|
|
mfs_dir_entry *dir_entry;
|
|
imgtoolerr_t err;
|
|
|
|
/* open dir */
|
|
mfs_dir_open(l2_img, "", &dirref);
|
|
|
|
/* find file */
|
|
err = mfs_find_dir_entry(&dirref, filename, &dir_entry);
|
|
if (err)
|
|
return err;
|
|
|
|
/* open it */
|
|
return mfs_file_open_internal(l2_img, dir_entry, fork, fileref);
|
|
}
|
|
|
|
/*
|
|
mfs_get_ABlink
|
|
|
|
Read one entry of the Allocation Bitmap link array, on an MFS volume.
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
AB_address (I): index in the array, which is an AB address
|
|
|
|
Returns the 12-bit value read in array.
|
|
*/
|
|
static UINT16 mfs_get_ABlink(struct mac_l2_imgref *l2_img, UINT16 AB_address)
|
|
{
|
|
UINT16 reply;
|
|
int base;
|
|
|
|
assert(l2_img->format == L2I_MFS);
|
|
|
|
base = (AB_address >> 1) * 3;
|
|
|
|
if (! (AB_address & 1))
|
|
reply = (l2_img->u.mfs.ABlink[base] << 4) | ((l2_img->u.mfs.ABlink[base+1] >> 4) & 0x0f);
|
|
else
|
|
reply = ((l2_img->u.mfs.ABlink[base+1] << 8) & 0xf00) | l2_img->u.mfs.ABlink[base+2];
|
|
|
|
return reply;
|
|
}
|
|
|
|
/*
|
|
mfs_set_ABlink
|
|
|
|
Set one entry of the Allocation Bitmap link array, on an MFS volume.
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
AB_address (I): index in the array, which is an AB address
|
|
data (I): 12-bit value to write in array
|
|
*/
|
|
static void mfs_set_ABlink(struct mac_l2_imgref *l2_img, UINT16 AB_address, UINT16 data)
|
|
{
|
|
int base;
|
|
|
|
assert(l2_img->format == L2I_MFS);
|
|
|
|
base = (AB_address >> 1) * 3;
|
|
|
|
if (! (AB_address & 1))
|
|
{
|
|
l2_img->u.mfs.ABlink[base] = (data >> 4) & 0xff;
|
|
l2_img->u.mfs.ABlink[base+1] = (l2_img->u.mfs.ABlink[base+1] & 0x0f) | ((data << 4) & 0xf0);
|
|
|
|
l2_img->u.mfs.ABlink_dirty[(base+64)/512] = 1;
|
|
l2_img->u.mfs.ABlink_dirty[(base+1+64)/512] = 1;
|
|
}
|
|
else
|
|
{
|
|
l2_img->u.mfs.ABlink[base+1] = (l2_img->u.mfs.ABlink[base+1] & 0xf0) | ((data >> 8) & 0x0f);
|
|
l2_img->u.mfs.ABlink[base+2] = data & 0xff;
|
|
|
|
l2_img->u.mfs.ABlink_dirty[(base+1+64)/512] = 1;
|
|
l2_img->u.mfs.ABlink_dirty[(base+2+64)/512] = 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
mfs_file_get_nth_block_address
|
|
|
|
Get the disk block address of a given block in an open file on a MFS image.
|
|
Called by macintosh file code.
|
|
|
|
fileref (I/O): open mac file reference
|
|
block_num (I): file block index
|
|
block_address (O): disk block address for the file block
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mfs_file_get_nth_block_address(struct mac_fileref *fileref, UINT32 block_num, UINT32 *block_address)
|
|
{
|
|
UINT32 AB_num;
|
|
UINT32 i;
|
|
UINT16 AB_address;
|
|
|
|
assert(fileref->l2_img->format == L2I_MFS);
|
|
|
|
AB_num = block_num / fileref->l2_img->blocksperAB;
|
|
|
|
AB_address = fileref->mfs.stBlk;
|
|
if ((AB_address == 0) || (AB_address >= fileref->l2_img->numABs+2))
|
|
/* 0 -> ??? */
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
if (AB_address == 1)
|
|
/* EOF */
|
|
return IMGTOOLERR_UNEXPECTED;
|
|
AB_address -= 2;
|
|
for (i=0; i<AB_num; i++)
|
|
{
|
|
AB_address = mfs_get_ABlink(fileref->l2_img, AB_address);
|
|
if ((AB_address == 0) || (AB_address >= fileref->l2_img->numABs+2))
|
|
/* 0 -> empty block: there is no way an empty block could make it
|
|
into the link chain!!! */
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
if (AB_address == 1)
|
|
/* EOF */
|
|
return IMGTOOLERR_UNEXPECTED;
|
|
AB_address -= 2;
|
|
}
|
|
|
|
*block_address = fileref->l2_img->u.mfs.ABStart + AB_address * fileref->l2_img->blocksperAB
|
|
+ block_num % fileref->l2_img->blocksperAB;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
mfs_file_allocABs
|
|
|
|
Allocate a chunk of ABs
|
|
|
|
fileref (I/O): open mac file reference
|
|
lastAB (I): AB address on disk of last file AB (only if
|
|
fileref->mfs.stBlk != 1)
|
|
allocABs (I): number of ABs to allocate in addition to the current file
|
|
allocation
|
|
fblock (I): first file block to allocate (used for tag data)
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mfs_file_allocABs(struct mac_fileref *fileref, UINT16 lastAB, UINT32 allocABs, UINT32 fblock)
|
|
{
|
|
int numABs = fileref->l2_img->numABs;
|
|
int free_ABs;
|
|
int i, j;
|
|
floppy_tag_record tag;
|
|
int extentBaseAB, extentABlen;
|
|
int firstBestExtentBaseAB = 0, firstBestExtentABlen;
|
|
int secondBestExtentBaseAB = 0, secondBestExtentABlen;
|
|
imgtoolerr_t err;
|
|
|
|
/* return if done */
|
|
if (! allocABs)
|
|
return IMGTOOLERR_SUCCESS;
|
|
|
|
/* compute free space */
|
|
free_ABs = 0;
|
|
for (i=0; i<numABs; i++)
|
|
{
|
|
if (mfs_get_ABlink(fileref->l2_img, i) == 0)
|
|
{
|
|
if (TAG_CHECKS)
|
|
{
|
|
/* optional check */
|
|
if (image_get_tag_len(&fileref->l2_img->l1_img) == 12)
|
|
{
|
|
for (j=0; j<fileref->l2_img->blocksperAB; j++)
|
|
{
|
|
err = image_read_tag(&fileref->l2_img->l1_img, fileref->l2_img->u.mfs.ABStart + i * fileref->l2_img->blocksperAB + j, &tag);
|
|
if (err)
|
|
return err;
|
|
|
|
if (get_UINT32BE(tag.fileID) != 0)
|
|
{
|
|
/*return IMGTOOLERR_CORRUPTIMAGE;*/
|
|
goto corrupt_free_block;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
free_ABs++;
|
|
}
|
|
corrupt_free_block:
|
|
;
|
|
}
|
|
|
|
/* check we have enough free space */
|
|
if (free_ABs < allocABs)
|
|
return IMGTOOLERR_NOSPACE;
|
|
|
|
if (fileref->mfs.stBlk != 1)
|
|
{ /* try to extend last file extent */
|
|
/* append free ABs after last AB */
|
|
for (i=lastAB+1; (mfs_get_ABlink(fileref->l2_img, i) == 0) && (allocABs > 0) && (i < numABs); i++)
|
|
{
|
|
if (TAG_CHECKS)
|
|
{
|
|
/* optional check */
|
|
if (image_get_tag_len(&fileref->l2_img->l1_img) == 12)
|
|
{
|
|
for (j=0; j<fileref->l2_img->blocksperAB; j++)
|
|
{
|
|
err = image_read_tag(&fileref->l2_img->l1_img, fileref->l2_img->u.mfs.ABStart + i * fileref->l2_img->blocksperAB + j, &tag);
|
|
if (err)
|
|
return err;
|
|
|
|
if (get_UINT32BE(tag.fileID) != 0)
|
|
{
|
|
/*return IMGTOOLERR_CORRUPTIMAGE;*/
|
|
goto corrupt_free_block2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mfs_set_ABlink(fileref->l2_img, lastAB, i+2);
|
|
lastAB = i;
|
|
allocABs--;
|
|
free_ABs--;
|
|
}
|
|
corrupt_free_block2:
|
|
/* return if done */
|
|
if (! allocABs)
|
|
{
|
|
mfs_set_ABlink(fileref->l2_img, lastAB, 1);
|
|
fileref->l2_img->freeABs = free_ABs;
|
|
return IMGTOOLERR_SUCCESS; /* done */
|
|
}
|
|
}
|
|
|
|
while (allocABs)
|
|
{
|
|
/* find smallest data block at least nb_alloc_physrecs in length, and largest data block less than nb_alloc_physrecs in length */
|
|
firstBestExtentABlen = INT_MAX;
|
|
secondBestExtentABlen = 0;
|
|
for (i=0; i<numABs; i++)
|
|
{
|
|
if (mfs_get_ABlink(fileref->l2_img, i) == 0)
|
|
{ /* found one free block */
|
|
/* compute its length */
|
|
extentBaseAB = i;
|
|
extentABlen = 0;
|
|
while ((i<numABs) && (mfs_get_ABlink(fileref->l2_img, i) == 0))
|
|
{
|
|
if (TAG_CHECKS)
|
|
{
|
|
/* optional check */
|
|
if (image_get_tag_len(&fileref->l2_img->l1_img) == 12)
|
|
{
|
|
for (j=0; j<fileref->l2_img->blocksperAB; j++)
|
|
{
|
|
err = image_read_tag(&fileref->l2_img->l1_img, fileref->l2_img->u.mfs.ABStart + i * fileref->l2_img->blocksperAB + j, &tag);
|
|
if (err)
|
|
return err;
|
|
|
|
if (get_UINT32BE(tag.fileID) != 0)
|
|
{
|
|
/*return IMGTOOLERR_CORRUPTIMAGE;*/
|
|
goto corrupt_free_block3;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extentABlen++;
|
|
i++;
|
|
}
|
|
corrupt_free_block3:
|
|
/* compare to previous best and second-best blocks */
|
|
if ((extentABlen < firstBestExtentABlen) && (extentABlen >= allocABs))
|
|
{
|
|
firstBestExtentBaseAB = extentBaseAB;
|
|
firstBestExtentABlen = extentABlen;
|
|
if (extentABlen == allocABs)
|
|
/* no need to search further */
|
|
break;
|
|
}
|
|
else if ((extentABlen > secondBestExtentABlen) && (extentABlen < allocABs))
|
|
{
|
|
secondBestExtentBaseAB = extentBaseAB;
|
|
secondBestExtentABlen = extentABlen;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (firstBestExtentABlen != INT_MAX)
|
|
{ /* found one contiguous block which can hold it all */
|
|
extentABlen = allocABs;
|
|
for (i=0; i<allocABs; i++)
|
|
{
|
|
if (fileref->mfs.stBlk != 1)
|
|
mfs_set_ABlink(fileref->l2_img, lastAB, firstBestExtentBaseAB+i+2);
|
|
else
|
|
fileref->mfs.stBlk = firstBestExtentBaseAB+i+2;
|
|
lastAB = firstBestExtentBaseAB+i;
|
|
free_ABs--;
|
|
/* set tag to allocated */
|
|
if (image_get_tag_len(&fileref->l2_img->l1_img) == 12)
|
|
{
|
|
set_UINT32BE(&tag.fileID, fileref->fileID);
|
|
tag.ftype = 1;
|
|
if ((fileref->forkType) == rsrc_fork)
|
|
tag.ftype |= 2;
|
|
tag.fattr = /*fattr*/ 0; /* ***TODO*** */
|
|
for (j=0; j<fileref->l2_img->blocksperAB; j++)
|
|
{
|
|
set_UINT16BE(&tag.fblock, fblock & 0xffff);
|
|
set_UINT32BE(&tag.wrCnt, mac_time_now());
|
|
err = image_write_tag(&fileref->l2_img->l1_img, fileref->l2_img->u.mfs.ABStart + lastAB * fileref->l2_img->blocksperAB + j, &tag);
|
|
if (err)
|
|
{
|
|
mfs_set_ABlink(fileref->l2_img, lastAB, 1);
|
|
fileref->l2_img->freeABs = free_ABs;
|
|
return err;
|
|
}
|
|
fblock++;
|
|
}
|
|
}
|
|
}
|
|
allocABs = 0;
|
|
mfs_set_ABlink(fileref->l2_img, lastAB, 1);
|
|
fileref->l2_img->freeABs = free_ABs;
|
|
/*return IMGTOOLERR_SUCCESS;*/ /* done */
|
|
}
|
|
else if (secondBestExtentABlen != 0)
|
|
{ /* jeez, we need to fragment it. We use the largest smaller block to limit fragmentation. */
|
|
for (i=0; i<secondBestExtentABlen; i++)
|
|
{
|
|
if (fileref->mfs.stBlk != 1)
|
|
mfs_set_ABlink(fileref->l2_img, lastAB, secondBestExtentBaseAB+i+2);
|
|
else
|
|
fileref->mfs.stBlk = secondBestExtentBaseAB+i+2;
|
|
lastAB = secondBestExtentBaseAB+i;
|
|
free_ABs--;
|
|
/* set tag to allocated */
|
|
if (image_get_tag_len(&fileref->l2_img->l1_img) == 12)
|
|
{
|
|
set_UINT32BE(&tag.fileID, fileref->fileID);
|
|
tag.ftype = 1;
|
|
if ((fileref->forkType) == rsrc_fork)
|
|
tag.ftype |= 2;
|
|
tag.fattr = /*fattr*/ 0; /* ***TODO*** */
|
|
for (j=0; j<fileref->l2_img->blocksperAB; j++)
|
|
{
|
|
set_UINT16BE(&tag.fblock, fblock & 0xffff);
|
|
set_UINT32BE(&tag.wrCnt, mac_time_now());
|
|
err = image_write_tag(&fileref->l2_img->l1_img, fileref->l2_img->u.mfs.ABStart + lastAB * fileref->l2_img->blocksperAB + j, &tag);
|
|
if (err)
|
|
{
|
|
mfs_set_ABlink(fileref->l2_img, lastAB, 1);
|
|
fileref->l2_img->freeABs = free_ABs;
|
|
return err;
|
|
}
|
|
fblock++;
|
|
}
|
|
}
|
|
}
|
|
allocABs -= secondBestExtentABlen;
|
|
}
|
|
else
|
|
{
|
|
mfs_set_ABlink(fileref->l2_img, lastAB, 1);
|
|
return IMGTOOLERR_NOSPACE; /* This should never happen, as we pre-check that there is enough free space */
|
|
}
|
|
}
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
mfs_file_setABeof
|
|
|
|
Set physical file EOF in ABs
|
|
|
|
fileref (I/O): open mac file reference
|
|
newABeof (I): desired number of allocated ABs for this file
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t mfs_file_setABeof(struct mac_fileref *fileref, UINT32 newABeof)
|
|
{
|
|
UINT16 AB_address = 0;
|
|
UINT16 AB_link;
|
|
int i, j;
|
|
floppy_tag_record tag;
|
|
int MDB_dirty = 0;
|
|
imgtoolerr_t err = IMGTOOLERR_SUCCESS;
|
|
|
|
|
|
assert(fileref->l2_img->format == L2I_MFS);
|
|
|
|
/* run through link chain until we reach the old or the new EOF */
|
|
AB_link = fileref->mfs.stBlk;
|
|
if ((AB_link == 0) || (AB_link >= fileref->l2_img->numABs+2))
|
|
/* 0 -> ??? */
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
for (i=0; (i<newABeof) && (AB_link!=1); i++)
|
|
{
|
|
AB_address = AB_link - 2;
|
|
AB_link = mfs_get_ABlink(fileref->l2_img, AB_address);
|
|
if ((AB_link == 0) || (AB_link >= fileref->l2_img->numABs+2))
|
|
/* 0 -> empty block: there is no way an empty block could make it
|
|
into the link chain!!! */
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
if (TAG_CHECKS)
|
|
{
|
|
/* optional check */
|
|
if (image_get_tag_len(&fileref->l2_img->l1_img) == 12)
|
|
{
|
|
for (j=0; j<fileref->l2_img->blocksperAB; j++)
|
|
{
|
|
err = image_read_tag(&fileref->l2_img->l1_img, fileref->l2_img->u.mfs.ABStart + AB_address * fileref->l2_img->blocksperAB + j, &tag);
|
|
if (err)
|
|
return err;
|
|
|
|
if ((get_UINT32BE(tag.fileID) != fileref->fileID)
|
|
|| (((tag.ftype & 2) != 0) != (fileref->forkType == rsrc_fork))
|
|
|| (get_UINT16BE(tag.fblock) != ((i * fileref->l2_img->blocksperAB + j) & 0xffff)))
|
|
{
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i == newABeof)
|
|
{ /* new EOF is shorter than old one */
|
|
/* mark new eof */
|
|
if (i==0)
|
|
fileref->mfs.stBlk = 1;
|
|
else
|
|
{
|
|
mfs_set_ABlink(fileref->l2_img, AB_address, 1);
|
|
MDB_dirty = 1;
|
|
}
|
|
|
|
/* free all remaining blocks */
|
|
while (AB_link != 1)
|
|
{
|
|
AB_address = AB_link - 2;
|
|
AB_link = mfs_get_ABlink(fileref->l2_img, AB_address);
|
|
if ((AB_link == 0) || (AB_link >= fileref->l2_img->numABs+2))
|
|
{ /* 0 -> empty block: there is no way an empty block could make
|
|
it into the link chain!!! */
|
|
if (MDB_dirty)
|
|
{ /* update MDB (freeABs field) and ABLink array */
|
|
err = mfs_update_mdb(fileref->l2_img);
|
|
if (err)
|
|
return err;
|
|
}
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
}
|
|
|
|
if (TAG_CHECKS)
|
|
{
|
|
/* optional check */
|
|
if (image_get_tag_len(&fileref->l2_img->l1_img) == 12)
|
|
{
|
|
for (j=0; j<fileref->l2_img->blocksperAB; j++)
|
|
{
|
|
err = image_read_tag(&fileref->l2_img->l1_img, fileref->l2_img->u.mfs.ABStart + AB_address * fileref->l2_img->blocksperAB + j, &tag);
|
|
if (err)
|
|
return err;
|
|
|
|
if ((get_UINT32BE(tag.fileID) != fileref->fileID)
|
|
|| (((tag.ftype & 2) != 0) != (fileref->forkType == rsrc_fork))
|
|
|| (get_UINT16BE(tag.fblock) != ((i * fileref->l2_img->blocksperAB + j) & 0xffff)))
|
|
{
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mfs_set_ABlink(fileref->l2_img, AB_address, 0);
|
|
fileref->l2_img->freeABs++;
|
|
MDB_dirty = 1;
|
|
/* set tag to free */
|
|
if (image_get_tag_len(&fileref->l2_img->l1_img) == 12)
|
|
{
|
|
memset(&tag, 0, sizeof(tag));
|
|
for (j=0; j<fileref->l2_img->blocksperAB; j++)
|
|
{
|
|
err = image_write_tag(&fileref->l2_img->l1_img, fileref->l2_img->u.mfs.ABStart + AB_address * fileref->l2_img->blocksperAB + j, &tag);
|
|
if (err)
|
|
return err;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
else
|
|
{ /* new EOF is larger than old one */
|
|
err = mfs_file_allocABs(fileref, AB_address, newABeof - i, i * fileref->l2_img->blocksperAB);
|
|
if (err)
|
|
return err;
|
|
MDB_dirty = 1;
|
|
}
|
|
|
|
if (MDB_dirty)
|
|
{ /* update MDB (freeABs field) and ABLink array */
|
|
err = mfs_update_mdb(fileref->l2_img);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
fileref->pLen = newABeof * (fileref->l2_img->blocksperAB * 512);
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
#ifdef UNUSED_FUNCTION
|
|
/*
|
|
mfs_hashString
|
|
|
|
Hash a string: under MFS, this provides the resource ID of the comment
|
|
resource associated with the file whose name is provided (FCMT resource
|
|
type).
|
|
|
|
Ripped from Apple technote TB06 (converted from 68k ASM to C)
|
|
|
|
string (I): string to hash
|
|
|
|
Returns hash value
|
|
*/
|
|
static int mfs_hashString(const mac_str255 string)
|
|
{
|
|
int reply;
|
|
int len;
|
|
int i;
|
|
|
|
len = string[0];
|
|
|
|
reply = 0;
|
|
for (i=0; i<len; i++)
|
|
{
|
|
reply ^= string[i+1];
|
|
if (reply & 1)
|
|
reply = ((reply >> 1) & 0x7fff) | ~0x7fff;
|
|
else
|
|
reply = ((reply >> 1) & 0x7fff);
|
|
if (! (reply & 0x8000))
|
|
reply = - reply;
|
|
}
|
|
|
|
return reply;
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
#pragma mark -
|
|
#pragma mark HFS IMPLEMENTATION
|
|
#endif
|
|
|
|
/*
|
|
HFS extents B-tree key
|
|
*/
|
|
struct hfs_extentKey
|
|
{
|
|
UINT8 keyLength; /* length of key, excluding this field */
|
|
UINT8 forkType; /* 0 = data fork, FF = resource fork */
|
|
UINT32BE fileID; /* file ID */
|
|
UINT16BE startBlock; /* first file allocation block number in this extent */
|
|
};
|
|
enum
|
|
{
|
|
keyLength_hfs_extentKey = sizeof(hfs_extentKey) - sizeof(UINT8)
|
|
};
|
|
|
|
/*
|
|
HFS catalog B-tree key
|
|
*/
|
|
struct hfs_catKey
|
|
{
|
|
UINT8 keyLen; /* key length */
|
|
UINT8 resrv1; /* reserved */
|
|
UINT32BE parID; /* parent directory ID */
|
|
mac_str31 cName; /* catalog node name */
|
|
/* note that in index nodes, it is a mac_str31, but
|
|
in leaf keys it's a variable-length string */
|
|
};
|
|
|
|
/*
|
|
HFS catalog data record for a folder - 70 bytes
|
|
*/
|
|
struct hfs_catFolderData
|
|
{
|
|
UINT16BE recordType; /* record type */
|
|
UINT16BE flags; /* folder flags */
|
|
UINT16BE valence; /* folder valence */
|
|
UINT32BE folderID; /* folder ID */
|
|
UINT32BE createDate; /* date and time of creation */
|
|
UINT32BE modifyDate; /* date and time of last modification */
|
|
UINT32BE backupDate; /* date and time of last backup */
|
|
mac_DInfo userInfo; /* Finder information */
|
|
mac_DXInfo finderInfo; /* additional Finder information */
|
|
UINT32BE reserved[4]; /* reserved - set to zero */
|
|
};
|
|
|
|
/*
|
|
HFS catalog data record for a file - 102 bytes
|
|
*/
|
|
struct hfs_catFileData
|
|
{
|
|
UINT16BE recordType; /* record type */
|
|
UINT8 flags; /* file flags */
|
|
UINT8 fileType; /* file type (reserved, always 0?) */
|
|
mac_FInfo userInfo; /* Finder information */
|
|
UINT32BE fileID; /* file ID */
|
|
UINT16BE dataStartBlock; /* not used - set to zero */
|
|
UINT32BE dataLogicalSize; /* logical EOF of data fork */
|
|
UINT32BE dataPhysicalSize; /* physical EOF of data fork */
|
|
UINT16BE rsrcStartBlock; /* not used - set to zero */
|
|
UINT32BE rsrcLogicalSize; /* logical EOF of resource fork */
|
|
UINT32BE rsrcPhysicalSize; /* physical EOF of resource fork */
|
|
UINT32BE createDate; /* date and time of creation */
|
|
UINT32BE modifyDate; /* date and time of last modification */
|
|
UINT32BE backupDate; /* date and time of last backup */
|
|
mac_FXInfo finderInfo; /* additional Finder information */
|
|
UINT16BE clumpSize; /* file clump size (not used) */
|
|
hfs_extent_3 dataExtents; /* first data fork extent record */
|
|
hfs_extent_3 rsrcExtents; /* first resource fork extent record */
|
|
UINT32BE reserved; /* reserved - set to zero */
|
|
};
|
|
|
|
/*
|
|
HFS catalog data record for a thread - 46 bytes
|
|
|
|
The key for a thread record features the CNID of the item and an empty
|
|
name, instead of the CNID of the parent and the item name.
|
|
*/
|
|
struct hfs_catThreadData
|
|
{
|
|
UINT16BE recordType; /* record type */
|
|
UINT32BE reserved[2]; /* reserved - set to zero */
|
|
UINT32BE parID; /* parent ID for this catalog node */
|
|
mac_str31 nodeName; /* name of this catalog node */
|
|
};
|
|
|
|
/*
|
|
union for all types at once
|
|
*/
|
|
union hfs_catData
|
|
{
|
|
UINT16BE dataType;
|
|
hfs_catFolderData folder;
|
|
hfs_catFileData file;
|
|
hfs_catThreadData thread;
|
|
};
|
|
|
|
/*
|
|
HFS catalog record types
|
|
*/
|
|
enum
|
|
{
|
|
hcrt_Folder = 0x0100, /* Folder record */
|
|
hcrt_File = 0x0200, /* File record */
|
|
hcrt_FolderThread = 0x0300, /* Folder thread record */
|
|
hcrt_FileThread = 0x0400 /* File thread record */
|
|
};
|
|
|
|
/*
|
|
Catalog file record flags
|
|
|
|
This is similar to the MFS catalog flag field, but the "thread exists" flag
|
|
(0x02) is specific to HFS/HFS+, whereas the "Record in use" flag (0x80) is
|
|
only used by MFS.
|
|
*/
|
|
enum
|
|
{
|
|
cfrf_fileLocked = 0x01, /* file is locked and cannot be written to */
|
|
cfrf_threadExists = 0x02 /* a file thread record exists for this file */
|
|
};
|
|
|
|
/*
|
|
BT functions used by HFS functions
|
|
*/
|
|
struct BT_leaf_rec_enumerator
|
|
{
|
|
mac_BTref *BTref;
|
|
UINT32 cur_node;
|
|
int cur_rec;
|
|
};
|
|
|
|
static imgtoolerr_t BT_open(mac_BTref *BTref, int (*key_compare_func)(const void *key1, const void *key2), int is_extent);
|
|
static void BT_close(mac_BTref *BTref);
|
|
static imgtoolerr_t BT_search_leaf_rec(mac_BTref *BTref, const void *search_key,
|
|
UINT32 *node_ID, int *record_ID,
|
|
void **record_ptr, int *record_len,
|
|
int search_exact_match, int *match_found);
|
|
static imgtoolerr_t BT_get_keyed_record_data(mac_BTref *BTref, void *rec_ptr, int rec_len, void **data_ptr, int *data_len);
|
|
static imgtoolerr_t BT_leaf_rec_enumerator_open(mac_BTref *BTref, BT_leaf_rec_enumerator *enumerator);
|
|
static imgtoolerr_t BT_leaf_rec_enumerator_read(BT_leaf_rec_enumerator *enumerator, void **record_ptr, int *rec_len);
|
|
|
|
struct hfs_cat_enumerator
|
|
{
|
|
struct mac_l2_imgref *l2_img;
|
|
BT_leaf_rec_enumerator BT_enumerator;
|
|
UINT32 parID;
|
|
};
|
|
|
|
/*
|
|
hfs_open_extents_file
|
|
|
|
Open the file extents B-tree file
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
mdb (I): copy of the MDB block
|
|
fileref (O): mac open file reference
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t hfs_open_extents_file(struct mac_l2_imgref *l2_img, const struct hfs_mdb *mdb, struct mac_fileref *fileref)
|
|
{
|
|
assert(l2_img->format == L2I_HFS);
|
|
|
|
fileref->l2_img = l2_img;
|
|
|
|
fileref->fileID = 3;
|
|
fileref->forkType = (mac_forkID)0x00;
|
|
|
|
fileref->eof = fileref->pLen = get_UINT32BE(mdb->xtFlSize);
|
|
memcpy(fileref->hfs.extents, mdb->xtExtRec, sizeof(hfs_extent_3));
|
|
|
|
fileref->crPs = 0;
|
|
|
|
fileref->reload_buf = TRUE;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
hfs_open_cat_file
|
|
|
|
Open the disk catalog B-tree file
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
mdb (I): copy of the MDB block
|
|
fileref (O): mac open file reference
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t hfs_open_cat_file(struct mac_l2_imgref *l2_img, const struct hfs_mdb *mdb, struct mac_fileref *fileref)
|
|
{
|
|
assert(l2_img->format == L2I_HFS);
|
|
|
|
fileref->l2_img = l2_img;
|
|
|
|
fileref->fileID = 4;
|
|
fileref->forkType = (mac_forkID)0x00;
|
|
|
|
fileref->eof = fileref->pLen = get_UINT32BE(mdb->ctFlSize);
|
|
memcpy(fileref->hfs.extents, mdb->ctExtRec, sizeof(hfs_extent_3));
|
|
|
|
fileref->crPs = 0;
|
|
|
|
fileref->reload_buf = TRUE;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
hfs_extentKey_compare
|
|
|
|
key compare function for file extents B-tree
|
|
|
|
p1 (I): pointer to first key
|
|
p2 (I): pointer to second key
|
|
|
|
Return a zero the two keys are equal, a negative value if the key pointed
|
|
to by p1 is less than the key pointed to by p2, and a positive value if the
|
|
key pointed to by p1 is greater than the key pointed to by p2.
|
|
*/
|
|
static int hfs_extentKey_compare(const void *p1, const void *p2)
|
|
{
|
|
const hfs_extentKey *key1 = (const hfs_extentKey*)p1;
|
|
const hfs_extentKey *key2 = (const hfs_extentKey*)p2;
|
|
|
|
/* let's keep it simple for now */
|
|
return memcmp(key1, key2, sizeof(hfs_extentKey));
|
|
}
|
|
|
|
/*
|
|
hfs_catKey_compare
|
|
|
|
key compare function for disk catalog B-tree
|
|
|
|
p1 (I): pointer to first key
|
|
p2 (I): pointer to second key
|
|
|
|
Return a zero the two keys are equal, a negative value if the key pointed
|
|
to by p1 is less than the key pointed to by p2, and a positive value if the
|
|
key pointed to by p1 is greater than the key pointed to by p2.
|
|
*/
|
|
static int hfs_catKey_compare(const void *p1, const void *p2)
|
|
{
|
|
const hfs_catKey *key1 = (const hfs_catKey *)p1;
|
|
const hfs_catKey *key2 = (const hfs_catKey *)p2;
|
|
|
|
if (get_UINT32BE(key1->parID) != get_UINT32BE(key2->parID))
|
|
return (get_UINT32BE(key1->parID) < get_UINT32BE(key2->parID)) ? -1 : +1;
|
|
|
|
return mac_stricmp(key1->cName, key2->cName);
|
|
}
|
|
|
|
/*
|
|
hfs_image_open
|
|
|
|
Open a HFS image. Image must already be open on level 1.
|
|
|
|
l2_img (I/O): level-2 image reference to open (l1_img and format fields
|
|
must be initialized)
|
|
img_open_buf (I): buffer with the MDB block
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t hfs_image_open(imgtool::image &image, imgtool::stream::ptr &&stream)
|
|
{
|
|
imgtoolerr_t err;
|
|
struct mac_l2_imgref *l2_img;
|
|
img_open_buf buf_local;
|
|
img_open_buf *buf;
|
|
|
|
l2_img = get_imgref(image);
|
|
l2_img->l1_img.image = ℑ
|
|
l2_img->l1_img.heads = 2;
|
|
l2_img->format = L2I_HFS;
|
|
|
|
/* read MDB */
|
|
err = image_read_block(&l2_img->l1_img, 2, &buf_local.raw);
|
|
if (err)
|
|
return err;
|
|
buf = &buf_local;
|
|
|
|
/* check signature word */
|
|
if ((buf->hfs_mdb.sigWord[0] != 0x42) || (buf->hfs_mdb.sigWord[1] != 0x44)
|
|
|| (buf->hfs_mdb.VN[0] > 27))
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
l2_img->u.hfs.VBM_start = get_UINT16BE(buf->hfs_mdb.VBMSt);
|
|
|
|
l2_img->numABs = get_UINT16BE(buf->hfs_mdb.nmAlBlks);
|
|
if (get_UINT32BE(buf->hfs_mdb.alBlkSiz) % 512)
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
l2_img->blocksperAB = get_UINT32BE(buf->hfs_mdb.alBlkSiz) / 512;
|
|
l2_img->u.hfs.ABStart = get_UINT16BE(buf->hfs_mdb.alBlSt);
|
|
|
|
l2_img->nxtCNID = get_UINT32BE(buf->hfs_mdb.nxtCNID);
|
|
|
|
l2_img->freeABs = get_UINT16BE(buf->hfs_mdb.freeABs);
|
|
|
|
mac_strcpy(l2_img->u.hfs.volname, buf->hfs_mdb.VN);
|
|
|
|
/* open extents and catalog BT */
|
|
err = hfs_open_extents_file(l2_img, &buf->hfs_mdb, &l2_img->u.hfs.extents_BT.fileref);
|
|
if (err)
|
|
return err;
|
|
err = BT_open(&l2_img->u.hfs.extents_BT, hfs_extentKey_compare, TRUE);
|
|
if (err)
|
|
return err;
|
|
if ((l2_img->u.hfs.extents_BT.attributes & btha_bigKeysMask)
|
|
/*|| (l2_img->u.hfs.extents_BT.attributes & kBTVariableIndexKeysMask)*/
|
|
|| (l2_img->u.hfs.extents_BT.maxKeyLength != 7))
|
|
{ /* This is not supported by the HFS format */
|
|
/* Variable Index keys are not supported either, but hopefully it will
|
|
not break this imgtool module if it set (though it would probably break
|
|
a real macintosh) */
|
|
BT_close(&l2_img->u.hfs.extents_BT);
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
}
|
|
err = hfs_open_cat_file(l2_img, &buf->hfs_mdb, &l2_img->u.hfs.cat_BT.fileref);
|
|
if (err)
|
|
{
|
|
BT_close(&l2_img->u.hfs.extents_BT);
|
|
return err;
|
|
}
|
|
err = BT_open(&l2_img->u.hfs.cat_BT, hfs_catKey_compare, FALSE);
|
|
if (err)
|
|
{
|
|
return err;
|
|
}
|
|
if ((l2_img->u.hfs.cat_BT.attributes & btha_bigKeysMask)
|
|
/*|| (l2_img->u.hfs.cat_BT.attributes & kBTVariableIndexKeysMask)*/
|
|
|| (l2_img->u.hfs.cat_BT.maxKeyLength != 37))
|
|
{ /* This is not supported by the HFS format */
|
|
/* Variable Index keys are not supported either, but hopefully it will
|
|
not break this imgtool module if it set (though it would probably break
|
|
a real macintosh) */
|
|
BT_close(&l2_img->u.hfs.extents_BT);
|
|
BT_close(&l2_img->u.hfs.cat_BT);
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
}
|
|
|
|
/* extract volume bitmap */
|
|
{
|
|
int byte_len = (l2_img->numABs + 7) / 8;
|
|
int cur_byte = 0;
|
|
int cur_block = l2_img->u.hfs.VBM_start;
|
|
|
|
while (cur_byte < byte_len)
|
|
{
|
|
/* read next block */
|
|
err = image_read_block(&l2_img->l1_img, cur_block, buf->raw);
|
|
if (err)
|
|
return err;
|
|
cur_block++;
|
|
|
|
/* append this block to VBM */
|
|
memcpy(l2_img->u.hfs.VBM+cur_byte, buf->raw, 512);
|
|
cur_byte += 512;
|
|
}
|
|
}
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
#ifdef UNUSED_FUNCTION
|
|
/*
|
|
hfs_image_close
|
|
|
|
Close a HFS image.
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
*/
|
|
static void hfs_image_close(struct mac_l2_imgref *l2_img)
|
|
{
|
|
assert(l2_img->format == L2I_HFS);
|
|
|
|
BT_close(&l2_img->u.hfs.extents_BT);
|
|
BT_close(&l2_img->u.hfs.cat_BT);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
hfs_get_cat_record_data
|
|
|
|
extract data from a catalog B-tree leaf record
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
rec_raw (I): pointer to record key and data, as returned by
|
|
BT_node_get_keyed_record
|
|
rec_len (I): total length of record, as returned by
|
|
BT_node_get_keyed_record
|
|
rec_key (O): set to point to record key
|
|
rec_data (O): set to point to record data
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t hfs_get_cat_record_data(struct mac_l2_imgref *l2_img, void *rec_raw, int rec_len, hfs_catKey **rec_key, hfs_catData **rec_data)
|
|
{
|
|
hfs_catKey *lrec_key;
|
|
void *rec_data_raw;
|
|
hfs_catData *lrec_data;
|
|
int rec_data_len;
|
|
int min_data_size;
|
|
imgtoolerr_t err;
|
|
|
|
|
|
assert(l2_img->format == L2I_HFS);
|
|
|
|
lrec_key = (hfs_catKey*)rec_raw;
|
|
/* check that key is long enough to hold it all */
|
|
if ((lrec_key->keyLen+1) < (offsetof(hfs_catKey, cName) + lrec_key->cName[0] + 1))
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
/* get pointer to record data */
|
|
err = BT_get_keyed_record_data(&l2_img->u.hfs.cat_BT, rec_raw, rec_len, &rec_data_raw, &rec_data_len);
|
|
if (err)
|
|
return err;
|
|
lrec_data = (hfs_catData*)rec_data_raw;
|
|
|
|
/* extract record type */
|
|
if (rec_data_len < 2)
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
/* check that the record is large enough for its type */
|
|
switch (get_UINT16BE(lrec_data->dataType))
|
|
{
|
|
case hcrt_Folder:
|
|
min_data_size = sizeof(hfs_catFolderData);
|
|
break;
|
|
|
|
case hcrt_File:
|
|
min_data_size = sizeof(hfs_catFileData);
|
|
break;
|
|
|
|
case hcrt_FolderThread:
|
|
case hcrt_FileThread:
|
|
min_data_size = sizeof(hfs_catThreadData);
|
|
break;
|
|
|
|
default:
|
|
/* records of unknown type can be safely ignored */
|
|
min_data_size = 0;
|
|
break;
|
|
}
|
|
|
|
if (rec_data_len < min_data_size)
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
if (rec_key)
|
|
*rec_key = lrec_key;
|
|
if (rec_data)
|
|
*rec_data = lrec_data;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
hfs_cat_open
|
|
|
|
Open an enumerator on the disk catalog
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
enumerator (O): open catalog enumerator reference
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t hfs_cat_open(struct mac_l2_imgref *l2_img, const char *path, hfs_cat_enumerator *enumerator)
|
|
{
|
|
imgtoolerr_t err;
|
|
UINT32 parID;
|
|
mac_str255 filename;
|
|
mac_dirent cat_info;
|
|
|
|
assert(l2_img->format == L2I_HFS);
|
|
|
|
/* resolve path and fetch file info from directory/catalog */
|
|
err = mac_lookup_path(l2_img, path, &parID, filename, &cat_info, FALSE);
|
|
if (err)
|
|
return err;
|
|
if (cat_info.dataRecType != hcrt_Folder)
|
|
return IMGTOOLERR_FILENOTFOUND;
|
|
|
|
enumerator->l2_img = l2_img;
|
|
enumerator->parID = parID;
|
|
|
|
return BT_leaf_rec_enumerator_open(&l2_img->u.hfs.cat_BT, &enumerator->BT_enumerator);
|
|
}
|
|
|
|
/*
|
|
hfs_cat_read
|
|
|
|
Enumerate the disk catalog
|
|
|
|
enumerator (I/O): open catalog enumerator reference
|
|
rec_key (O): set to point to record key
|
|
rec_data (O): set to point to record data
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t hfs_cat_read(hfs_cat_enumerator *enumerator, hfs_catKey **rec_key, hfs_catData **rec_data)
|
|
{
|
|
void *rec;
|
|
int rec_len = 0;
|
|
imgtoolerr_t err;
|
|
|
|
|
|
*rec_key = NULL;
|
|
*rec_data = NULL;
|
|
|
|
/* read next record */
|
|
err = BT_leaf_rec_enumerator_read(&enumerator->BT_enumerator, &rec, &rec_len);
|
|
if (err)
|
|
return err;
|
|
|
|
/* check EOList condition */
|
|
if (rec == NULL)
|
|
return IMGTOOLERR_SUCCESS;
|
|
|
|
/* extract record data */
|
|
err = hfs_get_cat_record_data(enumerator->l2_img, rec, rec_len, rec_key, rec_data);
|
|
if (err)
|
|
return err;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
hfs_cat_search
|
|
|
|
Search the catalog for a given file
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
parID (I): CNID of file parent directory
|
|
cName (I): file name
|
|
rec_key (O): set to point to record key
|
|
rec_data (O): set to point to record data
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t hfs_cat_search(struct mac_l2_imgref *l2_img, UINT32 parID, const mac_str31 cName, hfs_catKey **rec_key, hfs_catData **rec_data)
|
|
{
|
|
hfs_catKey search_key;
|
|
void *rec;
|
|
int rec_len;
|
|
imgtoolerr_t err;
|
|
|
|
assert(l2_img->format == L2I_HFS);
|
|
|
|
if (cName[0] > 31)
|
|
return IMGTOOLERR_UNEXPECTED;
|
|
|
|
/* generate search key */
|
|
search_key.keyLen = search_key.resrv1 = 0; /* these fields do not matter
|
|
to the compare function, so we
|
|
don't fill them */
|
|
set_UINT32BE(&search_key.parID, parID);
|
|
mac_strcpy(search_key.cName, cName);
|
|
|
|
/* search key */
|
|
err = BT_search_leaf_rec(&l2_img->u.hfs.cat_BT, &search_key, NULL, NULL, &rec, &rec_len, TRUE, NULL);
|
|
if (err)
|
|
return err;
|
|
|
|
/* extract record data */
|
|
err = hfs_get_cat_record_data(l2_img, rec, rec_len, rec_key, rec_data);
|
|
if (err)
|
|
return err;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
hfs_lookup_path
|
|
|
|
Resolve a file path
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
fpath (I): file path (C string)
|
|
parID (O): set to the CNID of the file parent directory
|
|
filename (O): set to the actual name of the file, with capitalization matching
|
|
the one on the volume rather than the one in the fpath parameter (Mac
|
|
string)
|
|
cat_info (O): catalog info for this file extracted from the catalog file
|
|
(may be NULL)
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t hfs_lookup_path(struct mac_l2_imgref *l2_img, const char *fpath, UINT32 *parID, mac_str255 filename, mac_dirent *cat_info)
|
|
{
|
|
const char *element_start;
|
|
int element_len;
|
|
mac_str255 mac_element_name;
|
|
//int level;
|
|
imgtoolerr_t err;
|
|
hfs_catKey *catrec_key = NULL;
|
|
hfs_catData *catrec_data = NULL;
|
|
UINT16 dataRecType = hcrt_Folder;
|
|
|
|
/* iterate each path element */
|
|
element_start = fpath;
|
|
//level = 0;
|
|
*parID = 2; /* root parID is 2 */
|
|
|
|
while(*element_start)
|
|
{
|
|
/* find next path element */
|
|
element_len = strlen(element_start);
|
|
/* decode path element name */
|
|
c_to_mac_strncpy(mac_element_name, element_start, element_len);
|
|
|
|
err = hfs_cat_search(l2_img, *parID, mac_element_name, &catrec_key, &catrec_data);
|
|
if (err)
|
|
return err;
|
|
|
|
dataRecType = get_UINT16BE(catrec_data->dataType);
|
|
|
|
/* regular folder/file name */
|
|
if (dataRecType == hcrt_Folder)
|
|
*parID = get_UINT32BE(catrec_data->folder.folderID);
|
|
else if (element_start[element_len + 1])
|
|
return IMGTOOLERR_BADFILENAME;
|
|
|
|
/* iterate */
|
|
element_start += element_len + 1;
|
|
}
|
|
|
|
if (catrec_key && (dataRecType == hcrt_File))
|
|
{
|
|
/* save ref */
|
|
*parID = get_UINT32BE(catrec_key->parID);
|
|
mac_strcpy(filename, catrec_key->cName);
|
|
}
|
|
|
|
if (cat_info)
|
|
{
|
|
if (catrec_data && (dataRecType == hcrt_File))
|
|
{
|
|
cat_info->flFinderInfo = catrec_data->file.userInfo;
|
|
cat_info->flXFinderInfo = catrec_data->file.finderInfo;
|
|
cat_info->flags = catrec_data->file.flags;
|
|
cat_info->fileID = get_UINT32BE(catrec_data->file.fileID);
|
|
cat_info->dataLogicalSize = get_UINT32BE(catrec_data->file.dataLogicalSize);
|
|
cat_info->dataPhysicalSize = get_UINT32BE(catrec_data->file.dataPhysicalSize);
|
|
cat_info->rsrcLogicalSize = get_UINT32BE(catrec_data->file.rsrcLogicalSize);
|
|
cat_info->rsrcPhysicalSize = get_UINT32BE(catrec_data->file.rsrcPhysicalSize);
|
|
cat_info->createDate = get_UINT32BE(catrec_data->file.createDate);
|
|
cat_info->modifyDate = get_UINT32BE(catrec_data->file.modifyDate);
|
|
}
|
|
else
|
|
{
|
|
memset(cat_info, 0, sizeof(*cat_info));
|
|
}
|
|
cat_info->dataRecType = dataRecType;
|
|
}
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
hfs_file_open_internal
|
|
|
|
Open a file fork, given its catalog entry. This function should not be
|
|
called directly: call hfs_file_open instead.
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
file_rec (I): catalog entry for the file to open
|
|
mac_forkID (I): tells which fork should be opened
|
|
fileref (O): mac file reference to open
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t hfs_file_open_internal(struct mac_l2_imgref *l2_img, const hfs_catFileData *file_rec, mac_forkID fork, struct mac_fileref *fileref)
|
|
{
|
|
assert(l2_img->format == L2I_HFS);
|
|
|
|
fileref->l2_img = l2_img;
|
|
|
|
fileref->fileID = get_UINT32BE(file_rec->fileID);
|
|
fileref->forkType = fork;
|
|
|
|
switch (fork)
|
|
{
|
|
case data_fork:
|
|
fileref->eof = get_UINT32BE(file_rec->dataLogicalSize);
|
|
fileref->pLen = get_UINT32BE(file_rec->dataPhysicalSize);
|
|
memcpy(fileref->hfs.extents, file_rec->dataExtents, sizeof(hfs_extent_3));
|
|
break;
|
|
|
|
case rsrc_fork:
|
|
fileref->eof = get_UINT32BE(file_rec->rsrcLogicalSize);
|
|
fileref->pLen = get_UINT32BE(file_rec->rsrcPhysicalSize);
|
|
memcpy(fileref->hfs.extents, file_rec->rsrcExtents, sizeof(hfs_extent_3));
|
|
break;
|
|
}
|
|
|
|
fileref->crPs = 0;
|
|
|
|
fileref->reload_buf = TRUE;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
hfs_file_open
|
|
|
|
Open a file located on a HFS volume. This function should not be called
|
|
directly: call mac_file_open instead.
|
|
|
|
l2_img (I/O): level-2 image reference
|
|
parID (I): CNID of file parent directory
|
|
filename (I): name of the file (Mac string)
|
|
mac_forkID (I): tells which fork should be opened
|
|
fileref (O): mac file reference to open
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t hfs_file_open(struct mac_l2_imgref *l2_img, UINT32 parID, const mac_str255 filename, mac_forkID fork, struct mac_fileref *fileref)
|
|
{
|
|
hfs_catKey *catrec_key;
|
|
hfs_catData *catrec_data;
|
|
UINT16 dataRecType;
|
|
imgtoolerr_t err;
|
|
|
|
/* lookup file in catalog */
|
|
err = hfs_cat_search(l2_img, parID, filename, &catrec_key, &catrec_data);
|
|
if (err)
|
|
return err;
|
|
|
|
dataRecType = get_UINT16BE(catrec_data->dataType);
|
|
|
|
/* file expected */
|
|
if (dataRecType != hcrt_File)
|
|
return IMGTOOLERR_BADFILENAME;
|
|
|
|
fileref->hfs.parID = get_UINT32BE(catrec_key->parID);
|
|
mac_strcpy(fileref->hfs.filename, catrec_key->cName);
|
|
|
|
/* open it */
|
|
return hfs_file_open_internal(l2_img, &catrec_data->file, fork, fileref);
|
|
}
|
|
|
|
/*
|
|
hfs_file_get_nth_block_address
|
|
|
|
Get the disk block address of a given block in an open file on a MFS image.
|
|
Called by macintosh file code.
|
|
|
|
fileref (I/O): open mac file reference
|
|
block_num (I): file block index
|
|
block_address (O): disk block address for the file block
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t hfs_file_get_nth_block_address(struct mac_fileref *fileref, UINT32 block_num, UINT32 *block_address)
|
|
{
|
|
UINT32 AB_num;
|
|
UINT32 cur_AB;
|
|
UINT32 i;
|
|
void *cur_extents_raw;
|
|
hfs_extent *cur_extents;
|
|
int cur_extents_len;
|
|
void *extents_BT_rec;
|
|
int extents_BT_rec_len;
|
|
imgtoolerr_t err;
|
|
UINT16 AB_address;
|
|
|
|
assert(fileref->l2_img->format == L2I_HFS);
|
|
|
|
AB_num = block_num / fileref->l2_img->blocksperAB;
|
|
cur_AB = 0;
|
|
cur_extents = fileref->hfs.extents;
|
|
|
|
/* first look in catalog tree extents */
|
|
for (i=0; i<3; i++)
|
|
{
|
|
if (AB_num < cur_AB+get_UINT16BE(cur_extents[i].numABlks))
|
|
break;
|
|
cur_AB += get_UINT16BE(cur_extents[i].numABlks);
|
|
}
|
|
if (i == 3)
|
|
{
|
|
/* extent not found: read extents record from extents BT */
|
|
hfs_extentKey search_key;
|
|
hfs_extentKey *found_key;
|
|
|
|
search_key.keyLength = keyLength_hfs_extentKey;
|
|
search_key.forkType = fileref->forkType;
|
|
set_UINT32BE(&search_key.fileID, fileref->fileID);
|
|
set_UINT16BE(&search_key.startBlock, AB_num);
|
|
|
|
/* search for the record with the largest key lower than or equal to
|
|
search_key. The keys are constructed in such a way that, if a record
|
|
includes AB_num, it is that one. */
|
|
err = BT_search_leaf_rec(&fileref->l2_img->u.hfs.extents_BT, &search_key,
|
|
NULL, NULL, &extents_BT_rec, &extents_BT_rec_len,
|
|
FALSE, NULL);
|
|
if (err)
|
|
return err;
|
|
|
|
if (extents_BT_rec == NULL)
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
found_key = (hfs_extentKey*)extents_BT_rec;
|
|
/* check that this record concerns the correct file */
|
|
if ((found_key->forkType != fileref->forkType)
|
|
|| (get_UINT32BE(found_key->fileID) != fileref->fileID))
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
/* extract start AB */
|
|
cur_AB = get_UINT16BE(found_key->startBlock);
|
|
/* get extents pointer */
|
|
err = BT_get_keyed_record_data(&fileref->l2_img->u.hfs.extents_BT, extents_BT_rec, extents_BT_rec_len, &cur_extents_raw, &cur_extents_len);
|
|
if (err)
|
|
return err;
|
|
if (cur_extents_len < 3*sizeof(hfs_extent))
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
cur_extents = (hfs_extent*)cur_extents_raw;
|
|
|
|
/* pick correct extent in record */
|
|
for (i=0; i<3; i++)
|
|
{
|
|
if (AB_num < cur_AB+get_UINT16BE(cur_extents[i].numABlks))
|
|
break;
|
|
cur_AB += get_UINT16BE(cur_extents[i].numABlks);
|
|
}
|
|
if (i == 3)
|
|
/* extent not found */
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
}
|
|
|
|
AB_address = get_UINT16BE(cur_extents[i].stABN) + (AB_num-cur_AB);
|
|
|
|
if (AB_address >= fileref->l2_img->numABs)
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
*block_address = fileref->l2_img->u.hfs.ABStart + AB_address * fileref->l2_img->blocksperAB
|
|
+ block_num % fileref->l2_img->blocksperAB;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
#if 0
|
|
#pragma mark -
|
|
#pragma mark B-TREE IMPLEMENTATION
|
|
#endif
|
|
|
|
/*
|
|
B-tree (Balanced search tree) files are used by the HFS and HFS+ file
|
|
systems: the Extents and Catalog files are both B-Tree.
|
|
|
|
Note that these B-trees are B+-trees: data is only on the leaf level, and
|
|
nodes located on the same level are also linked sequentially, which allows
|
|
fast sequenctial access to the catalog file.
|
|
|
|
These files are normal files, except for the fact that they are not
|
|
referenced from the catalog but the MDB. They are allocated in fixed-size
|
|
records of 512 bytes (HFS). (HFS+ supports any power of two from 512
|
|
through 32768, and uses a default of 1024 for Extents, and 4096 for both
|
|
Catalog and Attributes.)
|
|
|
|
Nodes can contain any number of records. The nodes can be of any of four
|
|
types: header node (unique node with b-tree information, pointer to root
|
|
node and start of the node allocation bitmap), map nodes (which are created
|
|
when the node allocation bitmap outgrows the header node), index nodes
|
|
(root and branch node that enable to efficiently search the leaf nodes for
|
|
a specific key value), and leaf nodes (which hold the actual user data
|
|
records with keys and data). The first node is always a header node.
|
|
Other nodes can be of any of the 3 other type, or they can be free.
|
|
*/
|
|
|
|
/*
|
|
BTNodeHeader
|
|
|
|
Header of a node record
|
|
*/
|
|
struct BTNodeHeader
|
|
{
|
|
UINT32BE fLink; /* (index of) next node at this level */
|
|
UINT32BE bLink; /* (index of) previous node at this level */
|
|
UINT8 kind; /* kind of node (leaf, index, header, map) */
|
|
UINT8 height; /* zero for header, map; 1 for leaf, 2 through
|
|
treeDepth for index (child is one LESS than
|
|
parent, whatever IM says) */
|
|
UINT16BE numRecords; /* number of records in this node */
|
|
UINT16BE reserved; /* reserved; set to zero */
|
|
};
|
|
|
|
/*
|
|
Constants for BTNodeHeader kind field
|
|
*/
|
|
enum
|
|
{
|
|
btnk_leafNode = 0xff, /* leaf nodes hold the actual user data records
|
|
with keys and data */
|
|
btnk_indexNode = 0, /* root and branch node that enable to efficiently
|
|
search the leaf nodes for a specific key value */
|
|
btnk_headerNode = 1, /* unique node with b-tree information, pointer to
|
|
root node and start of the node allocation
|
|
bitmap */
|
|
btnk_mapNode = 2 /* map nodes are created when the node allocation
|
|
bitmap outgrows the header node */
|
|
};
|
|
|
|
/*
|
|
BTHeaderRecord: first record of a B-tree header node (second record is
|
|
unused, and third is node allocation bitmap).
|
|
*/
|
|
struct BTHeaderRecord
|
|
{
|
|
UINT16BE treeDepth; /* maximum height (usually leaf nodes) */
|
|
UINT32BE rootNode; /* node number of root node */
|
|
UINT32BE leafRecords; /* number of leaf records in all leaf nodes */
|
|
UINT32BE firstLeafNode; /* node number of first leaf node */
|
|
UINT32BE lastLeafNode; /* node number of last leaf node */
|
|
UINT16BE nodeSize; /* size of a node, in bytes */
|
|
UINT16BE maxKeyLength; /* maximum length of data (index + leaf) record keys;
|
|
length of all index record keys if
|
|
btha_variableIndexKeysMask attribute flag is not set */
|
|
UINT32BE totalNodes; /* total number of nodes in tree */
|
|
UINT32BE freeNodes; /* number of unused (free) nodes in tree */
|
|
|
|
UINT16BE reserved1; /* unused */
|
|
UINT32BE clumpSize; /* used in some HFS implementations? (reserved in
|
|
early HFS implementations, and in HFS Plus) */
|
|
UINT8 btreeType; /* reserved - set to 0 */
|
|
UINT8 reserved2; /* reserved */
|
|
UINT32BE attributes; /* persistent attributes about the tree */
|
|
UINT32BE reserved3[16]; /* reserved */
|
|
};
|
|
|
|
static imgtoolerr_t BT_check(mac_BTref *BTref, int is_extent);
|
|
|
|
/*
|
|
BT_open
|
|
|
|
Open a file as a B-tree. The file must be already open as a macintosh
|
|
file.
|
|
|
|
BTref (I/O): B-tree file handle to open (BTref->fileref must have been
|
|
open previously)
|
|
key_compare_func (I): function that compares two keys
|
|
is_extent (I): TRUE if we are opening the extent B-tree (we want to do
|
|
extra checks in this case because the extent B-Tree may include extent
|
|
records for the extent B-tree itself, and if an extent record for the
|
|
extent B-tree is located in an extent that has not been defined by
|
|
previous extent records, then we can never retreive this extent record)
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t BT_open(mac_BTref *BTref, int (*key_compare_func)(const void *key1, const void *key2), int is_extent)
|
|
{
|
|
imgtoolerr_t err;
|
|
BTNodeHeader node_header;
|
|
BTHeaderRecord header_rec;
|
|
|
|
/* seek to node 0 */
|
|
err = mac_file_seek(&BTref->fileref, 0);
|
|
if (err)
|
|
return err;
|
|
|
|
/* read node header */
|
|
err = mac_file_read(&BTref->fileref, sizeof(node_header), &node_header);
|
|
if (err)
|
|
return err;
|
|
|
|
if ((node_header.kind != btnk_headerNode) || (get_UINT16BE(node_header.numRecords) < 3)
|
|
|| (node_header.height != 0))
|
|
return IMGTOOLERR_CORRUPTIMAGE; /* right??? */
|
|
|
|
/* CHEESY HACK: we assume that the header record immediately follows the
|
|
node header. This is because we need to know the node length to know where
|
|
the record pointers are located, but we need to read the header record to
|
|
know the node length. */
|
|
err = mac_file_read(&BTref->fileref, sizeof(header_rec), &header_rec);
|
|
if (err)
|
|
return err;
|
|
|
|
BTref->nodeSize = get_UINT16BE(header_rec.nodeSize);
|
|
BTref->rootNode = get_UINT32BE(header_rec.rootNode);
|
|
BTref->firstLeafNode = get_UINT32BE(header_rec.firstLeafNode);
|
|
BTref->attributes = get_UINT32BE(header_rec.attributes);
|
|
BTref->treeDepth = get_UINT16BE(header_rec.treeDepth);
|
|
BTref->maxKeyLength = get_UINT16BE(header_rec.maxKeyLength);
|
|
|
|
BTref->key_compare_func = key_compare_func;
|
|
|
|
BTref->node_buf = malloc(BTref->nodeSize);
|
|
if (!BTref->node_buf)
|
|
return IMGTOOLERR_OUTOFMEMORY;
|
|
|
|
if (BTREE_CHECKS)
|
|
{
|
|
/* optional: check integrity of B-tree */
|
|
err = BT_check(BTref, is_extent);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
BT_close
|
|
|
|
Close a B-tree
|
|
|
|
BTref (I/O): open B-tree file handle
|
|
*/
|
|
static void BT_close(mac_BTref *BTref)
|
|
{
|
|
free(BTref->node_buf);
|
|
}
|
|
|
|
/*
|
|
BT_read_node
|
|
|
|
Read a node from a B-tree
|
|
|
|
BTref (I/O): open B-tree file handle
|
|
node_ID (I): index of the node to read
|
|
expected_kind (I): kind of the node to read
|
|
expected_depth (I): depth of the node to read
|
|
dest (O): destination buffer
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t BT_read_node(mac_BTref *BTref, UINT32 node_ID, int expected_kind, int expected_depth, void *dest)
|
|
{
|
|
imgtoolerr_t err;
|
|
|
|
/* seek to node */
|
|
err = mac_file_seek(&BTref->fileref, node_ID*BTref->nodeSize);
|
|
if (err)
|
|
return err;
|
|
|
|
/* read it */
|
|
err = mac_file_read(&BTref->fileref, BTref->nodeSize, dest);
|
|
if (err)
|
|
return err;
|
|
|
|
/* check node kind and depth */
|
|
if ((((BTNodeHeader *) dest)->kind != expected_kind)
|
|
|| (((BTNodeHeader *) dest)->height != expected_depth))
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
BT_node_get_record
|
|
|
|
Extract a raw record from a B-tree node
|
|
|
|
BTref (I/O): open B-tree file handle
|
|
node_buf (I): buffer with the node the record should be extracted from
|
|
recnum (I): index of record to read
|
|
rec_ptr (O): set to point to start of record (key + data)
|
|
rec_len (O): set to total length of record (key + data)
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t BT_node_get_record(mac_BTref *BTref, void *node_buf, unsigned recnum, void **rec_ptr, int *rec_len)
|
|
{
|
|
UINT16 node_numRecords = get_UINT16BE(((BTNodeHeader *) node_buf)->numRecords);
|
|
UINT16 offset;
|
|
UINT16 next_offset;
|
|
|
|
if (recnum >= node_numRecords)
|
|
return IMGTOOLERR_UNEXPECTED;
|
|
|
|
int recnum_s = (int)recnum;
|
|
offset = get_UINT16BE(((UINT16BE *)((UINT8 *) node_buf + BTref->nodeSize))[-recnum_s - 1]);
|
|
next_offset = get_UINT16BE(((UINT16BE *)((UINT8 *) node_buf + BTref->nodeSize))[-recnum_s - 2]);
|
|
|
|
if ((offset < sizeof(BTNodeHeader)) || (offset > BTref->nodeSize-2*node_numRecords)
|
|
|| (next_offset < sizeof(BTNodeHeader)) || (next_offset > BTref->nodeSize-2*node_numRecords)
|
|
|| (offset & 1) || (next_offset & 1)
|
|
|| (offset > next_offset))
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
*rec_ptr = (UINT8 *)node_buf + offset;
|
|
*rec_len = next_offset - offset;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
BT_node_get_keyed_record
|
|
|
|
Extract a keyed record from a B-tree node. Equivalent to
|
|
BT_node_get_record, only we do extra checks.
|
|
|
|
BTref (I/O): open B-tree file handle
|
|
node_buf (I): buffer with the node the record should be extracted from
|
|
node_is_index (I): TRUE if node is index node
|
|
recnum (I): index of record to read
|
|
rec_ptr (O): set to point to start of record (key + data)
|
|
rec_len (O): set to total length of record (key + data)
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t BT_node_get_keyed_record(mac_BTref *BTref, void *node_buf, int node_is_index, unsigned recnum, void **rec_ptr, int *rec_len)
|
|
{
|
|
imgtoolerr_t err;
|
|
void *lrec_ptr;
|
|
int lrec_len;
|
|
int key_len;
|
|
|
|
/* extract record */
|
|
err = BT_node_get_record(BTref, node_buf, recnum, &lrec_ptr, &lrec_len);
|
|
if (err)
|
|
return err;
|
|
|
|
/* read key len */
|
|
key_len = (BTref->attributes & btha_bigKeysMask)
|
|
? get_UINT16BE(* (UINT16BE *)lrec_ptr)
|
|
: (* (UINT8 *)lrec_ptr);
|
|
|
|
/* check that key fits in record */
|
|
if ((key_len + ((BTref->attributes & btha_bigKeysMask) ? 2 : 1)) > lrec_len)
|
|
/* hurk! */
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
if (key_len > BTref->maxKeyLength)
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
if (node_is_index && (! (BTref->attributes & btha_variableIndexKeysMask)) && (key_len != BTref->maxKeyLength))
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
if (rec_ptr)
|
|
*rec_ptr = lrec_ptr;
|
|
if (rec_len)
|
|
*rec_len = lrec_len;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
BT_get_keyed_record_data
|
|
|
|
extract data from a keyed record
|
|
|
|
BTref (I/O): open B-tree file handle
|
|
rec_ptr (I): point to start of record (key + data)
|
|
rec_len (I): total length of record (key + data)
|
|
data_ptr (O): set to point to record data
|
|
data_len (O): set to length of record data
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t BT_get_keyed_record_data(mac_BTref *BTref, void *rec_ptr, int rec_len, void **data_ptr, int *data_len)
|
|
{
|
|
int lkey_len;
|
|
int data_offset;
|
|
|
|
/* read key len */
|
|
lkey_len = (BTref->attributes & btha_bigKeysMask)
|
|
? get_UINT16BE(* (UINT16BE *)rec_ptr)
|
|
: (* (UINT8 *)rec_ptr);
|
|
|
|
/* compute offset to data record */
|
|
data_offset = lkey_len + ((BTref->attributes & btha_bigKeysMask) ? 2 : 1);
|
|
if (data_offset > rec_len)
|
|
/* hurk! */
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
/* fix alignment */
|
|
if (data_offset & 1)
|
|
data_offset++;
|
|
|
|
if (data_ptr)
|
|
*data_ptr = (UINT8 *)rec_ptr + data_offset;
|
|
if (data_len)
|
|
*data_len = (rec_len > data_offset) ? rec_len-data_offset : 0;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
BT_check
|
|
|
|
Check integrity of a complete B-tree
|
|
|
|
BTref (I/O): open B-tree file handle
|
|
is_extent (I): TRUE if we are opening the extent B-tree (we want to do
|
|
extra checks in this case because the extent B-Tree may include extent
|
|
records for the extent B-tree itself, and if an extent record for the
|
|
extent B-tree is located in an extent that has not been defined by
|
|
previous extent records, then we can never retreive this extent record)
|
|
|
|
Return imgtool error code
|
|
*/
|
|
struct data_nodes_t
|
|
{
|
|
void *buf;
|
|
UINT32 node_num;
|
|
UINT32 cur_rec;
|
|
UINT32 num_recs;
|
|
};
|
|
static imgtoolerr_t BT_check(mac_BTref *BTref, int is_extent)
|
|
{
|
|
UINT16 node_numRecords;
|
|
BTHeaderRecord *header_rec;
|
|
UINT8 *bitmap;
|
|
|
|
data_nodes_t *data_nodes;
|
|
int i, j;
|
|
UINT32 cur_node, prev_node;
|
|
void *rec1, *rec2;
|
|
int rec1_len, rec2_len;
|
|
void *rec1_data;
|
|
int rec1_data_len;
|
|
UINT32 totalNodes, lastLeafNode;
|
|
UINT32 freeNodes;
|
|
int compare_result;
|
|
UINT32 map_count, map_len;
|
|
UINT32 run_len;
|
|
UINT32 run_bit_len;
|
|
UINT32 actualFreeNodes;
|
|
imgtoolerr_t err;
|
|
UINT32 maxExtentAB = 0, maxExtentNode = 0, extentEOL = 0; /* if is_extent is TRUE */
|
|
|
|
if (is_extent)
|
|
{
|
|
switch (BTref->fileref.l2_img->format)
|
|
{
|
|
case L2I_MFS:
|
|
/* MFS does not feature any extents B-tree! */
|
|
return IMGTOOLERR_UNEXPECTED;
|
|
|
|
case L2I_HFS:
|
|
maxExtentAB = 0;
|
|
for (j=0; j<3; j++)
|
|
maxExtentAB += get_UINT16BE(BTref->fileref.hfs.extents[j].numABlks);
|
|
maxExtentNode = (UINT64)maxExtentAB * 512 * BTref->fileref.l2_img->blocksperAB
|
|
/ BTref->nodeSize;
|
|
extentEOL = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* read header node */
|
|
if ((! is_extent) || (0 < maxExtentNode))
|
|
err = BT_read_node(BTref, 0, btnk_headerNode, 0, BTref->node_buf);
|
|
else
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
if (err)
|
|
return err;
|
|
|
|
/* check we have enough records */
|
|
node_numRecords = get_UINT16BE(((BTNodeHeader *) BTref->node_buf)->numRecords);
|
|
if (node_numRecords < 3)
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
/* get header record */
|
|
err = BT_node_get_record(BTref, BTref->node_buf, 0, &rec1, &rec1_len);
|
|
if (err)
|
|
return err;
|
|
header_rec = (BTHeaderRecord *)rec1;
|
|
|
|
/* check length of header record */
|
|
if (rec1_len < sizeof(BTHeaderRecord))
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
totalNodes = get_UINT32BE(header_rec->totalNodes);
|
|
if (totalNodes == 0)
|
|
/* we need at least one header node */
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
lastLeafNode = get_UINT32BE(header_rec->lastLeafNode);
|
|
freeNodes = get_UINT32BE(header_rec->freeNodes);
|
|
|
|
/* check file length */
|
|
if ((BTref->nodeSize * totalNodes) > BTref->fileref.pLen)
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
/* initialize for the function postlog ("bail:" tag) */
|
|
err = IMGTOOLERR_SUCCESS;
|
|
bitmap = NULL;
|
|
data_nodes = NULL;
|
|
|
|
/* alloc buffer for reconstructed bitmap */
|
|
map_len = (totalNodes + 7) / 8;
|
|
bitmap = (UINT8*)malloc(map_len);
|
|
if (! bitmap)
|
|
return IMGTOOLERR_OUTOFMEMORY;
|
|
memset(bitmap, 0, map_len);
|
|
|
|
/* check B-tree data nodes (i.e. index and leaf nodes) */
|
|
if (BTref->treeDepth == 0)
|
|
{
|
|
/* B-tree is empty */
|
|
if (BTref->rootNode || BTref->firstLeafNode || lastLeafNode)
|
|
{
|
|
err = IMGTOOLERR_OUTOFMEMORY;
|
|
goto bail;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* alloc array of buffers for catalog data nodes */
|
|
data_nodes = (data_nodes_t *)malloc(sizeof(data_nodes_t) * BTref->treeDepth);
|
|
if (! data_nodes)
|
|
{
|
|
err = IMGTOOLERR_OUTOFMEMORY;
|
|
goto bail;
|
|
}
|
|
for (i=0; i<BTref->treeDepth; i++)
|
|
data_nodes[i].buf = NULL; /* required for function postlog to work should next loop fail */
|
|
for (i=0; i<BTref->treeDepth; i++)
|
|
{
|
|
data_nodes[i].buf = malloc(BTref->nodeSize);
|
|
if (!data_nodes[i].buf)
|
|
{
|
|
err = IMGTOOLERR_OUTOFMEMORY;
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
/* read first data nodes */
|
|
cur_node = BTref->rootNode;
|
|
for (i=BTref->treeDepth-1; i>=0; i--)
|
|
{
|
|
/* check node index */
|
|
if (cur_node >= totalNodes)
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
/* check that node has not been used for another purpose */
|
|
/* this check is unecessary because the current consistency checks
|
|
that forward and back linking match and that node height is correct
|
|
are enough to detect such errors */
|
|
#if 0
|
|
if (bitmap[cur_node >> 3] & (0x80 >> (cur_node & 7)))
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
#endif
|
|
/* add node in bitmap */
|
|
bitmap[cur_node >> 3] |= (0x80 >> (cur_node & 7));
|
|
/* read node */
|
|
if ((! is_extent) || (cur_node < maxExtentNode))
|
|
err = BT_read_node(BTref, cur_node, i ? btnk_indexNode : btnk_leafNode, i+1, data_nodes[i].buf);
|
|
else
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
if (err)
|
|
goto bail;
|
|
/* check that it is the first node at this level */
|
|
if (get_UINT32BE(((BTNodeHeader *) data_nodes[i].buf)->bLink))
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
/* fill other fields */
|
|
data_nodes[i].node_num = cur_node;
|
|
data_nodes[i].cur_rec = 0;
|
|
data_nodes[i].num_recs = get_UINT16BE(((BTNodeHeader *) data_nodes[i].buf)->numRecords);
|
|
/* check that there is at least one record */
|
|
if (data_nodes[i].num_recs == 0)
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
|
|
/* iterate to next level if applicable */
|
|
if (i != 0)
|
|
{
|
|
/* extract first record */
|
|
err = BT_node_get_keyed_record(BTref, data_nodes[i].buf, TRUE, 0, &rec1, &rec1_len);
|
|
if (err)
|
|
goto bail;
|
|
|
|
/* extract record data ptr */
|
|
err = BT_get_keyed_record_data(BTref, rec1, rec1_len, &rec1_data, &rec1_data_len);
|
|
if (err)
|
|
goto bail;
|
|
if (rec1_data_len < sizeof(UINT32BE))
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
|
|
/* iterate to next level */
|
|
cur_node = get_UINT32BE(* (UINT32BE *)rec1_data);
|
|
}
|
|
}
|
|
|
|
/* check that a) the root node has no successor, and b) that we have really
|
|
read the first leaf node */
|
|
if (get_UINT32BE(((BTNodeHeader *) data_nodes[BTref->treeDepth-1].buf)->fLink)
|
|
|| (cur_node != BTref->firstLeafNode))
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
|
|
/* check that keys are ordered correctly */
|
|
while (1)
|
|
{
|
|
/* iterate through parent nodes */
|
|
i = 0;
|
|
while ((i<BTref->treeDepth) && ((data_nodes[i].cur_rec == 0) || (data_nodes[i].cur_rec == data_nodes[i].num_recs)))
|
|
{
|
|
/* read next node if necessary */
|
|
if (data_nodes[i].cur_rec == data_nodes[i].num_recs)
|
|
{
|
|
/* get link to next node */
|
|
cur_node = get_UINT32BE(((BTNodeHeader *) data_nodes[i].buf)->fLink);
|
|
if (cur_node == 0)
|
|
{
|
|
if (i == 0)
|
|
/* normal End of List */
|
|
goto end_of_list;
|
|
else
|
|
{
|
|
/* error */
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
}
|
|
/* add node in bitmap */
|
|
bitmap[cur_node >> 3] |= (0x80 >> (cur_node & 7));
|
|
/* read node */
|
|
if ((! is_extent) || (cur_node < maxExtentNode))
|
|
err = BT_read_node(BTref, cur_node, i ? btnk_indexNode : btnk_leafNode, i+1, data_nodes[i].buf);
|
|
else
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
if (err)
|
|
goto bail;
|
|
/* check that backward linking match forward linking */
|
|
if (get_UINT32BE(((BTNodeHeader *) data_nodes[i].buf)->bLink) != data_nodes[i].node_num)
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
/* fill other fields */
|
|
data_nodes[i].node_num = cur_node;
|
|
data_nodes[i].cur_rec = 0;
|
|
data_nodes[i].num_recs = get_UINT16BE(((BTNodeHeader *) data_nodes[i].buf)->numRecords);
|
|
/* check that there is at least one record */
|
|
if (data_nodes[i].num_recs == 0)
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
/* next test is not necessary because we have checked that
|
|
the root node has no successor */
|
|
#if 0
|
|
if (i < BTref->treeDepth-1)
|
|
{
|
|
#endif
|
|
data_nodes[i+1].cur_rec++;
|
|
#if 0
|
|
}
|
|
else
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
#endif
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (is_extent && !extentEOL)
|
|
{
|
|
/* extract current leaf record and update maxExtentAB and
|
|
maxExtentNode */
|
|
hfs_extentKey *extentKey;
|
|
hfs_extent *extentData;
|
|
|
|
/* extract current leaf record */
|
|
err = BT_node_get_keyed_record(BTref, data_nodes[0].buf, FALSE, data_nodes[0].cur_rec, &rec1, &rec1_len);
|
|
if (err)
|
|
goto bail;
|
|
|
|
extentKey = (hfs_extentKey*)rec1;
|
|
if ((extentKey->keyLength < 7) || (extentKey->forkType != 0) || (get_UINT32BE(extentKey->fileID) != 3)
|
|
|| (get_UINT16BE(extentKey->startBlock) != maxExtentAB))
|
|
/* the key is corrupt or does not concern the extent
|
|
B-tree: set the extentEOL flag so that we stop looking for
|
|
further extent records for the extent B-tree */
|
|
extentEOL = TRUE;
|
|
else
|
|
{ /* this key concerns the extent B-tree: update maxExtentAB
|
|
and maxExtentNode */
|
|
/* extract record data ptr */
|
|
err = BT_get_keyed_record_data(BTref, rec1, rec1_len, &rec1_data, &rec1_data_len);
|
|
if (err)
|
|
goto bail;
|
|
if (rec1_data_len < sizeof(hfs_extent)*3)
|
|
/* the record is corrupt: set the extentEOL flag so
|
|
that we stop looking for further extent records for the
|
|
extent B-tree */
|
|
extentEOL = TRUE;
|
|
else
|
|
{
|
|
extentData = (hfs_extent*)rec1_data;
|
|
|
|
for (j=0; j<3; j++)
|
|
maxExtentAB += get_UINT16BE(extentData[j].numABlks);
|
|
maxExtentNode = (UINT64)maxExtentAB * 512 * BTref->fileref.l2_img->blocksperAB
|
|
/ BTref->nodeSize;
|
|
}
|
|
}
|
|
if (extentEOL)
|
|
{
|
|
/* check that the extent B-Tree has been defined entirely */
|
|
if (maxExtentNode < totalNodes)
|
|
{ /* no good */
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (i<BTref->treeDepth)
|
|
{
|
|
/* extract current record */
|
|
err = BT_node_get_keyed_record(BTref, data_nodes[i].buf, i > 0, data_nodes[i].cur_rec, &rec1, &rec1_len);
|
|
if (err)
|
|
goto bail;
|
|
|
|
/* extract previous record */
|
|
err = BT_node_get_keyed_record(BTref, data_nodes[i].buf, i > 0, data_nodes[i].cur_rec-1, &rec2, &rec2_len);
|
|
if (err)
|
|
goto bail;
|
|
|
|
/* check that it is sorted correctly */
|
|
compare_result = (*BTref->key_compare_func)(rec1, rec2);
|
|
if (compare_result <= 0)
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
|
|
i--;
|
|
}
|
|
else
|
|
{
|
|
i--;
|
|
if (i>0)
|
|
{ /* extract first record of root if it is an index node */
|
|
err = BT_node_get_keyed_record(BTref, data_nodes[i].buf, TRUE, data_nodes[i].cur_rec, &rec1, &rec1_len);
|
|
if (err)
|
|
goto bail;
|
|
}
|
|
i--;
|
|
}
|
|
|
|
while (i>=0)
|
|
{
|
|
/* extract first record of current level */
|
|
err = BT_node_get_keyed_record(BTref, data_nodes[i].buf, i > 0, data_nodes[i].cur_rec, &rec2, &rec2_len);
|
|
if (err)
|
|
goto bail;
|
|
|
|
/* compare key with key of current record of upper level */
|
|
compare_result = (*BTref->key_compare_func)(rec1, rec2);
|
|
if (compare_result != 0)
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
|
|
/* extract record data ptr */
|
|
err = BT_get_keyed_record_data(BTref, rec1, rec1_len, &rec1_data, &rec1_data_len);
|
|
if (err)
|
|
goto bail;
|
|
if (rec1_data_len < sizeof(UINT32BE))
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
cur_node = get_UINT32BE(* (UINT32BE *)rec1_data);
|
|
|
|
/* compare node index with data of current record of upper
|
|
level */
|
|
if (cur_node != data_nodes[i].node_num)
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
|
|
/* iterate to next level */
|
|
rec1 = rec2;
|
|
rec1_len = rec2_len;
|
|
i--;
|
|
}
|
|
|
|
/* next leaf record */
|
|
data_nodes[0].cur_rec++;
|
|
}
|
|
|
|
end_of_list:
|
|
/* check that we are at the end of list for each index level */
|
|
for (i=1; i<BTref->treeDepth; i++)
|
|
{
|
|
if ((data_nodes[i].cur_rec != (data_nodes[i].num_recs-1))
|
|
|| get_UINT32BE(((BTNodeHeader *) data_nodes[i].buf)->fLink))
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
}
|
|
/* check that the last leaf node is what it is expected to be */
|
|
if (data_nodes[0].node_num != lastLeafNode)
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
/* check map node chain */
|
|
cur_node = 0; /* node 0 is the header node... */
|
|
bitmap[0] |= 0x80;
|
|
/* check back linking */
|
|
if (get_UINT32BE(((BTNodeHeader *) BTref->node_buf)->bLink))
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
/* get pointer to next node */
|
|
cur_node = get_UINT32BE(((BTNodeHeader *) BTref->node_buf)->fLink);
|
|
while (cur_node != 0)
|
|
{
|
|
/* save node address */
|
|
prev_node = cur_node;
|
|
/* check that node has not been used for another purpose */
|
|
/* this check is unecessary because the current consistency checks that
|
|
forward and back linking match and that node height is correct are
|
|
enough to detect such errors */
|
|
#if 0
|
|
if (bitmap[cur_node >> 3] & (0x80 >> (cur_node & 7)))
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
#endif
|
|
/* add node in bitmap */
|
|
bitmap[cur_node >> 3] |= (0x80 >> (cur_node & 7));
|
|
/* read map node */
|
|
if ((! is_extent) || (cur_node < maxExtentNode))
|
|
err = BT_read_node(BTref, cur_node, btnk_mapNode, 0, BTref->node_buf);
|
|
else
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
if (err)
|
|
goto bail;
|
|
/* check back linking */
|
|
if (get_UINT32BE(((BTNodeHeader *) BTref->node_buf)->bLink) != prev_node)
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
/* get pointer to next node */
|
|
cur_node = get_UINT32BE(((BTNodeHeader *) BTref->node_buf)->fLink);
|
|
}
|
|
|
|
/* re-read header node */
|
|
err = BT_read_node(BTref, 0, btnk_headerNode, 0, BTref->node_buf);
|
|
if (err)
|
|
goto bail;
|
|
|
|
/* get header bitmap record */
|
|
err = BT_node_get_record(BTref, BTref->node_buf, 2, &rec1, &rec1_len);
|
|
if (err)
|
|
goto bail;
|
|
|
|
/* check bitmap, iterating map nodes */
|
|
map_count = 0;
|
|
actualFreeNodes = 0;
|
|
while (map_count < map_len)
|
|
{
|
|
/* compute compare len */
|
|
run_len = rec1_len;
|
|
if (run_len > (map_len-map_count))
|
|
run_len = map_len-map_count;
|
|
/* check that all used nodes are marked as such in the B-tree bitmap */
|
|
for (i=0; i<run_len; i++)
|
|
if (bitmap[map_count+i] & ~((UINT8 *)rec1)[i])
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
/* count free nodes */
|
|
run_bit_len = rec1_len*8;
|
|
if (run_bit_len > (totalNodes-map_count*8))
|
|
run_bit_len = totalNodes-map_count*8;
|
|
for (i=0; i<run_bit_len; i++)
|
|
if (! (((UINT8 *)rec1)[i>>3] & (0x80 >> (i & 7))))
|
|
actualFreeNodes++;
|
|
map_count += run_len;
|
|
/* read next map node if required */
|
|
if (map_count < map_len)
|
|
{
|
|
/* get pointer to next node */
|
|
cur_node = get_UINT32BE(((BTNodeHeader *) BTref->node_buf)->fLink);
|
|
if (cur_node == 0)
|
|
{
|
|
err = IMGTOOLERR_CORRUPTIMAGE;
|
|
goto bail;
|
|
}
|
|
/* read map node */
|
|
err = BT_read_node(BTref, cur_node, btnk_mapNode, 0, BTref->node_buf);
|
|
if (err)
|
|
goto bail;
|
|
/* get map record */
|
|
err = BT_node_get_record(BTref, BTref->node_buf, 0, &rec1, &rec1_len);
|
|
if (err)
|
|
goto bail;
|
|
header_rec = (BTHeaderRecord *)rec1;
|
|
}
|
|
}
|
|
|
|
/* check free node count */
|
|
if (freeNodes != actualFreeNodes)
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
bail:
|
|
/* free buffers */
|
|
if (data_nodes)
|
|
{
|
|
for (i=0; i<BTref->treeDepth; i++)
|
|
if (data_nodes[i].buf)
|
|
free(data_nodes[i].buf);
|
|
free(data_nodes);
|
|
}
|
|
if (bitmap)
|
|
free(bitmap);
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
BT_search_leaf_rec
|
|
|
|
Search for a given key in a B-Tree. If exact match found, returns
|
|
corresponding leaf record. Otherwise, may return the greatest record less
|
|
than the requested key (of course, this will fail if the key is lower than
|
|
all keys in the B-Tree).
|
|
|
|
BTref (I/O): open B-tree file handle
|
|
search_key (I): key to search the B-Tree for
|
|
node_ID (O): set to the node ID of the node the record is located in (may
|
|
be NULL)
|
|
record_ID (O): set to the index of the record in the node (may be NULL)
|
|
record_ptr (O): set to point to record in node buffer (may be NULL)
|
|
record_len (O): set to total record len (may be NULL)
|
|
search_exact_match (I): if TRUE, the function will search for a record
|
|
equal to search_key; if FALSE, the function will search for the
|
|
greatest record less than or equal to search_key
|
|
match_found (O): set to TRUE if an exact match for search_key has been
|
|
found (only makes sense if search_exact_match is FALSE) (may be NULL)
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t BT_search_leaf_rec(mac_BTref *BTref, const void *search_key,
|
|
UINT32 *node_ID, int *record_ID,
|
|
void **record_ptr, int *record_len,
|
|
int search_exact_match, int *match_found)
|
|
{
|
|
imgtoolerr_t err;
|
|
int i;
|
|
UINT32 cur_node;
|
|
void *cur_rec;
|
|
int cur_rec_len;
|
|
void *last_rec;
|
|
int last_rec_len = 0;
|
|
void *rec_data;
|
|
int rec_data_len;
|
|
int depth;
|
|
UINT16 node_numRecords;
|
|
int compare_result = 0;
|
|
|
|
/* start with root node */
|
|
if ((BTref->rootNode == 0) || (BTref->treeDepth == 0))
|
|
/* tree is empty */
|
|
return ((BTref->rootNode == 0) == (BTref->treeDepth == 0))
|
|
? IMGTOOLERR_FILENOTFOUND
|
|
: IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
cur_node = BTref->rootNode;
|
|
depth = BTref->treeDepth;
|
|
|
|
while (1)
|
|
{
|
|
/* read current node */
|
|
err = BT_read_node(BTref, cur_node, (depth > 1) ? btnk_indexNode : btnk_leafNode, depth, BTref->node_buf);
|
|
if (err)
|
|
return err;
|
|
|
|
/* search for key */
|
|
node_numRecords = get_UINT16BE(((BTNodeHeader *) BTref->node_buf)->numRecords);
|
|
last_rec = cur_rec = NULL;
|
|
for (i=0; i<node_numRecords; i++)
|
|
{
|
|
err = BT_node_get_keyed_record(BTref, BTref->node_buf, depth > 1, i, &cur_rec, &cur_rec_len);
|
|
if (err)
|
|
return err;
|
|
|
|
compare_result = (*BTref->key_compare_func)(cur_rec, search_key);
|
|
if (compare_result > 0)
|
|
break;
|
|
last_rec = cur_rec;
|
|
last_rec_len = cur_rec_len;
|
|
if (compare_result == 0)
|
|
break;
|
|
}
|
|
|
|
if (! last_rec)
|
|
{ /* all keys are greater than the search key: the search key is
|
|
nowhere in the tree */
|
|
if (search_exact_match)
|
|
return IMGTOOLERR_FILENOTFOUND;
|
|
|
|
if (match_found)
|
|
*match_found = FALSE;
|
|
|
|
if (node_ID)
|
|
*node_ID = 0;
|
|
|
|
if (record_ID)
|
|
*record_ID = -1;
|
|
|
|
if (record_ptr)
|
|
*record_ptr = NULL;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
if (((BTNodeHeader *) BTref->node_buf)->kind == btnk_leafNode)
|
|
/* leaf node -> end of search */
|
|
break;
|
|
|
|
/* extract record data ptr */
|
|
err = BT_get_keyed_record_data(BTref, last_rec, last_rec_len, &rec_data, &rec_data_len);
|
|
if (err)
|
|
return err;
|
|
if (rec_data_len < sizeof(UINT32BE))
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
|
|
/* iterate to next level */
|
|
cur_node = get_UINT32BE(* (UINT32BE *)rec_data);
|
|
depth--;
|
|
}
|
|
|
|
if (compare_result != 0)
|
|
/* key not found */
|
|
if (search_exact_match)
|
|
return IMGTOOLERR_FILENOTFOUND;
|
|
|
|
if (match_found)
|
|
*match_found = (compare_result == 0);
|
|
|
|
if (node_ID)
|
|
*node_ID = cur_node;
|
|
|
|
if (record_ID)
|
|
*record_ID = i;
|
|
|
|
if (record_ptr)
|
|
*record_ptr = last_rec;
|
|
|
|
if (record_len)
|
|
*record_len = last_rec_len;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
BT_leaf_rec_enumerator_open
|
|
|
|
Open enumerator for leaf records of a B-Tree
|
|
|
|
BTref (I/O): open B-tree file handle
|
|
enumerator (O): B-Tree enumerator to open
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t BT_leaf_rec_enumerator_open(mac_BTref *BTref, BT_leaf_rec_enumerator *enumerator)
|
|
{
|
|
enumerator->BTref = BTref;
|
|
enumerator->cur_node = BTref->firstLeafNode;
|
|
enumerator->cur_rec = 0;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
BT_leaf_rec_enumerator_read
|
|
|
|
Read next leaf record of a B-Tree
|
|
|
|
enumerator (I/O): open B-Tree enumerator
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t BT_leaf_rec_enumerator_read(BT_leaf_rec_enumerator *enumerator, void **record_ptr, int *rec_len)
|
|
{
|
|
UINT16 node_numRecords;
|
|
imgtoolerr_t err;
|
|
|
|
|
|
*record_ptr = NULL;
|
|
|
|
/* check EOList condition */
|
|
if (enumerator->cur_node == 0)
|
|
return IMGTOOLERR_SUCCESS;
|
|
|
|
/* read current node */
|
|
err = BT_read_node(enumerator->BTref, enumerator->cur_node, btnk_leafNode, 1, enumerator->BTref->node_buf);
|
|
if (err)
|
|
return err;
|
|
node_numRecords = get_UINT16BE(((BTNodeHeader *) enumerator->BTref->node_buf)->numRecords);
|
|
|
|
/* skip nodes until we find a record */
|
|
while ((enumerator->cur_rec >= node_numRecords) && (enumerator->cur_node != 0))
|
|
{
|
|
enumerator->cur_node = get_UINT32BE(((BTNodeHeader *) enumerator->BTref->node_buf)->fLink);
|
|
enumerator->cur_rec = 0;
|
|
|
|
/* read node */
|
|
err = BT_read_node(enumerator->BTref, enumerator->cur_node, btnk_leafNode, 1, enumerator->BTref->node_buf);
|
|
if (err)
|
|
return err;
|
|
node_numRecords = get_UINT16BE(((BTNodeHeader *) enumerator->BTref->node_buf)->numRecords);
|
|
}
|
|
|
|
/* check EOList condition */
|
|
if (enumerator->cur_node == 0)
|
|
return IMGTOOLERR_SUCCESS;
|
|
|
|
/* get current record */
|
|
err = BT_node_get_keyed_record(enumerator->BTref, enumerator->BTref->node_buf, FALSE, enumerator->cur_rec, record_ptr, rec_len);
|
|
if (err)
|
|
return err;
|
|
|
|
/* iterate to next record */
|
|
enumerator->cur_rec++;
|
|
if (enumerator->cur_rec >= node_numRecords)
|
|
{ /* iterate to next node if last record (not required, but will improve
|
|
performance on next iteration) */
|
|
enumerator->cur_node = get_UINT32BE(((BTNodeHeader *) enumerator->BTref->node_buf)->fLink);
|
|
enumerator->cur_rec = 0;
|
|
}
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
B-Tree extend EOF algorithm:
|
|
* see if the bitmap will need to be extended
|
|
* extend EOF by min 1 (if bitmap is large engough) or 2 (if bitmap needs
|
|
to be extended) and max ClumpSiz (see extClpSiz and ctClpSiz in MDB)
|
|
***If we are extending the extent B-Tree, we need to defer the possible
|
|
creation of an additional extent record, or we might enter an endless
|
|
recursion loop***
|
|
|
|
Empty node alloc algorithm:
|
|
|
|
* see if there is any free node in B-tree bitmap
|
|
* optionally, try to compact the B-tree if file is full
|
|
* if file is still full, extend EOF and try again
|
|
* mark new block as used and return its index
|
|
|
|
|
|
Empty node delete algorithm:
|
|
|
|
* remove node from link list
|
|
* mark node as free in the B-tree bitmap
|
|
* optionally, if more than N% of the B-tree is free, compact the B-tree and
|
|
free some disk space
|
|
* Count nodes on this level; if there is only one left, delete parent index
|
|
node and mark the relaining node as root; if it was the last leaf node,
|
|
update header node with an empty B-tree; in either case, decrement tree
|
|
depth
|
|
|
|
|
|
Record shifting algorithm:
|
|
|
|
For a given node and its first non-empty successor node:
|
|
|
|
* compute how much free room there is in the node
|
|
* see if the first record of the first non-empty successor can fit
|
|
* if so, move it (i.e. delete the first record of the later node, and add a
|
|
copy of it to the end of the former)
|
|
|
|
|
|
Node merging algorithm
|
|
|
|
* Consider node and its predecessor. If there is room, shift all records
|
|
from later to former, then delete empty later node, and delete later
|
|
record from parent index node.
|
|
|
|
|
|
Node splitting algorithm (non-first)
|
|
|
|
* Consider node and its predecessor. Create new middle node and split
|
|
records in 3 even sets. Update record for last node and insert record
|
|
for middle node in parent index node.
|
|
|
|
|
|
Node splitting algorithm (first node)
|
|
|
|
* Create new successor node, and split records in 2 1/3 and 2/3 sets.
|
|
Insert record for later node in parent index node.
|
|
|
|
|
|
Record delete algorithm:
|
|
|
|
* remove record from node
|
|
* if record was first in node, test if node is now empty
|
|
* if node is not empty, substitute key of deleted record with key of
|
|
new head record in index tree
|
|
* if node is empty, delete key of deleted record in index tree, then
|
|
delete empty node
|
|
* optionally, look the predecessor node. Merge the two nodes if possible.
|
|
|
|
|
|
Record insert algorithm:
|
|
|
|
* if there room, just insert new record in node; if new record is in first
|
|
position, update record in parent index node
|
|
* else consider predecessor: see if we can make enough room by shifting
|
|
records. If so, do shift records, insert new record, update record in
|
|
parent index node
|
|
* else split the nodes and insert record
|
|
*/
|
|
/*
|
|
Possible additions:
|
|
|
|
Node compaction algorithm:
|
|
|
|
This algorithm can be executed with a specific start point and max number
|
|
of nodes, or with all nodes on a level.
|
|
|
|
* see how many nodes we can save by shifting records left
|
|
* if we will save at least one node, do shift as many records as possible
|
|
(try to leave free space split homogeneously???)
|
|
*/
|
|
|
|
/*static void*/
|
|
|
|
#if 0
|
|
#pragma mark -
|
|
#pragma mark RESOURCE IMPLEMENTATION
|
|
#endif
|
|
|
|
/*
|
|
Resource manager
|
|
|
|
The resource manager stores arbitrary chunks of data (resource) identified
|
|
by a type/id pair. The resource type is a 4-char code, which generally
|
|
implies the format of the data (e.g. 'PICT' is for a quickdraw picture,
|
|
'STR ' for a macintosh string, 'CODE' for 68k machine code, etc). The
|
|
resource id is a signed 16-bit number that uniquely identifies each
|
|
resource of a given type. Note that, with most resource types, resources
|
|
with id < 128 are system resources that are available to all applications,
|
|
whereas resources with id >= 128 are application resources visible only to
|
|
the application that defines them.
|
|
|
|
Each resource can optionally have a resource name, which is a macintosh
|
|
string of 255 chars at most.
|
|
|
|
Limits:
|
|
16MBytes of data
|
|
64kbytes of type+reference lists
|
|
64kbytes of resource names
|
|
|
|
The Macintosh toolbox can open several resource files simulteanously to
|
|
overcome these restrictions.
|
|
|
|
Resources are used virtually everywhere in the Macintosh Toolbox, so it is
|
|
no surprise that file comments and MFS folders are stored in resource files.
|
|
*/
|
|
|
|
/*
|
|
Resource header
|
|
*/
|
|
struct rsrc_header
|
|
{
|
|
UINT32BE data_offs; /* Offset from beginning of resource fork to resource data */
|
|
UINT32BE map_offs; /* Offset from beginning of resource fork to resource map */
|
|
UINT32BE data_len; /* Length of resource data */
|
|
UINT32BE map_len; /* Length of resource map */
|
|
};
|
|
|
|
/*
|
|
Resource data: each data entry is preceded by its len (UINT32BE)
|
|
Offset to specific data fields are gotten from the resource map
|
|
*/
|
|
|
|
/*
|
|
Resource map:
|
|
*/
|
|
struct rsrc_map_header
|
|
{
|
|
rsrc_header reserved0; /* Reserved for copy of resource header */
|
|
UINT32BE reserved1; /* Reserved for handle to next resource map */
|
|
UINT16BE reserved2; /* Reserved for file reference number */
|
|
|
|
UINT16BE attr; /* Resource fork attributes */
|
|
UINT16BE typelist_offs; /* Offset from beginning of map to resource type list */
|
|
UINT16BE namelist_offs; /* Offset from beginning of map to resource name list */
|
|
UINT16BE type_count; /* Number of types in the map minus 1 */
|
|
/* This is actually part of the type list, which matters for offsets */
|
|
};
|
|
|
|
/*
|
|
Resource type list entry
|
|
*/
|
|
struct rsrc_type_entry
|
|
{
|
|
UINT32BE type; /* Resource type */
|
|
UINT16BE ref_count; /* Number of resources of this type in map minus 1 */
|
|
UINT16BE ref_offs; /* Offset from beginning of resource type list to reference list for this type */
|
|
};
|
|
|
|
/*
|
|
Resource reference list entry
|
|
*/
|
|
struct rsrc_ref_entry
|
|
{
|
|
UINT16BE id; /* Resource ID */
|
|
UINT16BE name_offs; /* Offset from beginning of resource name list to resource name */
|
|
/* (-1 if none) */
|
|
UINT8 attr; /* Resource attributes */
|
|
UINT24BE data_offs; /* Offset from beginning of resource data to data for this resource */
|
|
UINT32BE reserved; /* Reserved for handle to resource */
|
|
};
|
|
|
|
/*
|
|
Resource name list entry: this is just a standard macintosh string
|
|
*/
|
|
|
|
struct mac_resfileref
|
|
{
|
|
mac_fileref fileref; /* open resource fork ref (you may open resources
|
|
files in data fork, too, if you ever need to,
|
|
but Classic MacOS never does such a thing
|
|
(MacOS X often does so, though)) */
|
|
UINT32 data_offs; /* Offset from beginning of resource file to resource data */
|
|
UINT32 map_offs; /* Offset from beginning of resource file to resource data */
|
|
|
|
UINT16 typelist_offs; /* Offset from beginning of map to resource type list */
|
|
UINT16 namelist_offs; /* Offset from beginning of map to resource name list */
|
|
UINT16 type_count; /* Number of types in the map minus 1 */
|
|
/* This is actually part of the type list, which matters for offsets */
|
|
};
|
|
|
|
#ifdef UNUSED_FUNCTION
|
|
/*
|
|
resfile_open
|
|
|
|
Open a file as a resource file. The file must be already open as a
|
|
macintosh file.
|
|
|
|
resfileref (I/O): resource file handle to open (resfileref->fileref must
|
|
have been opened previously)
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t resfile_open(mac_resfileref *resfileref)
|
|
{
|
|
imgtoolerr_t err;
|
|
rsrc_header header;
|
|
rsrc_map_header map_header;
|
|
|
|
/* seek to resource header */
|
|
err = mac_file_seek(&resfileref->fileref, 0);
|
|
if (err)
|
|
return err;
|
|
|
|
err = mac_file_read(&resfileref->fileref, sizeof(header), &header);
|
|
if (err)
|
|
return err;
|
|
|
|
resfileref->data_offs = get_UINT32BE(header.data_offs);
|
|
resfileref->map_offs = get_UINT32BE(header.map_offs);
|
|
|
|
/* seek to resource map header */
|
|
err = mac_file_seek(&resfileref->fileref, resfileref->map_offs);
|
|
if (err)
|
|
return err;
|
|
|
|
err = mac_file_read(&resfileref->fileref, sizeof(map_header), &map_header);
|
|
if (err)
|
|
return err;
|
|
|
|
resfileref->typelist_offs = get_UINT16BE(map_header.typelist_offs);
|
|
resfileref->namelist_offs = get_UINT16BE(map_header.namelist_offs);
|
|
resfileref->type_count = get_UINT16BE(map_header.type_count);
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
resfile_get_entry
|
|
|
|
Get the resource entry in the resource map associated with a given type/id
|
|
pair.
|
|
|
|
resfileref (I/O): open resource file handle
|
|
type (I): type of the resource
|
|
id (I): id of the resource
|
|
entry (O): resource entry that has been read
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t resfile_get_entry(mac_resfileref *resfileref, UINT32 type, UINT16 id, rsrc_ref_entry *entry)
|
|
{
|
|
imgtoolerr_t err;
|
|
rsrc_type_entry type_entry;
|
|
UINT16 ref_count;
|
|
int i;
|
|
|
|
/* seek to resource type list in resource map */
|
|
err = mac_file_seek(&resfileref->fileref, resfileref->map_offs+resfileref->typelist_offs+2);
|
|
if (err)
|
|
return err;
|
|
|
|
if (resfileref->type_count == 0xffff)
|
|
/* type list is empty */
|
|
return IMGTOOLERR_FILENOTFOUND;
|
|
|
|
for (i=0; i<=resfileref->type_count; i++)
|
|
{
|
|
err = mac_file_read(&resfileref->fileref, sizeof(type_entry), &type_entry);
|
|
if (err)
|
|
return err;
|
|
if (type == get_UINT32BE(type_entry.type))
|
|
break;
|
|
}
|
|
if (i > resfileref->type_count)
|
|
/* type not found in list */
|
|
return IMGTOOLERR_FILENOTFOUND;
|
|
|
|
ref_count = get_UINT16BE(type_entry.ref_count);
|
|
|
|
/* seek to resource ref list for this type in resource map */
|
|
err = mac_file_seek(&resfileref->fileref, resfileref->map_offs+resfileref->typelist_offs+get_UINT16BE(type_entry.ref_offs));
|
|
if (err)
|
|
return err;
|
|
|
|
if (ref_count == 0xffff)
|
|
/* ref list is empty */
|
|
return IMGTOOLERR_FILENOTFOUND;
|
|
|
|
for (i=0; i<=ref_count; i++)
|
|
{
|
|
err = mac_file_read(&resfileref->fileref, sizeof(*entry), entry);
|
|
if (err)
|
|
return err;
|
|
if (id == get_UINT16BE(entry->id))
|
|
break;
|
|
}
|
|
if (i > ref_count)
|
|
/* id not found in list */
|
|
return IMGTOOLERR_FILENOTFOUND;
|
|
|
|
/* type+id have been found... */
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
resfile_get_resname
|
|
|
|
Get the name of a resource.
|
|
|
|
resfileref (I/O): open resource file handle
|
|
entry (I): resource entry in the resource map (returned by
|
|
resfile_get_entry)
|
|
string (O): resource name
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t resfile_get_resname(mac_resfileref *resfileref, const rsrc_ref_entry *entry, mac_str255 string)
|
|
{
|
|
imgtoolerr_t err;
|
|
UINT16 name_offs;
|
|
UINT8 len;
|
|
|
|
name_offs = get_UINT16BE(entry->name_offs);
|
|
|
|
if (name_offs == 0xffff)
|
|
/* ref list is empty */
|
|
return IMGTOOLERR_UNEXPECTED;
|
|
|
|
/* seek to resource name in name list in resource map */
|
|
err = mac_file_seek(&resfileref->fileref, resfileref->map_offs+name_offs);
|
|
if (err)
|
|
return err;
|
|
|
|
/* get string length */
|
|
err = mac_file_read(&resfileref->fileref, 1, &len);
|
|
if (err)
|
|
return err;
|
|
|
|
string[0] = len;
|
|
|
|
/* get string data */
|
|
err = mac_file_read(&resfileref->fileref, len, string+1);
|
|
if (err)
|
|
return err;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
resfile_get_reslen
|
|
|
|
Get the data length for a given resource.
|
|
|
|
resfileref (I/O): open resource file handle
|
|
entry (I): resource entry in the resource map (returned by
|
|
resfile_get_entry)
|
|
len (O): resource length
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t resfile_get_reslen(mac_resfileref *resfileref, const rsrc_ref_entry *entry, UINT32 *len)
|
|
{
|
|
imgtoolerr_t err;
|
|
UINT32 data_offs;
|
|
UINT32BE llen;
|
|
|
|
data_offs = get_UINT24BE(entry->data_offs);
|
|
|
|
/* seek to resource data in resource data section */
|
|
err = mac_file_seek(&resfileref->fileref, resfileref->data_offs+data_offs);
|
|
if (err)
|
|
return err;
|
|
|
|
/* get data length */
|
|
err = mac_file_read(&resfileref->fileref, sizeof(llen), &llen);
|
|
if (err)
|
|
return err;
|
|
|
|
*len = get_UINT32BE(llen);
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
resfile_get_resdata
|
|
|
|
Get the data for a given resource.
|
|
|
|
resfileref (I/O): open resource file handle
|
|
entry (I): resource entry in the resource map (returned by
|
|
resfile_get_entry)
|
|
offset (I): offset the data should be read from, usually 0
|
|
len (I): length of the data to read, usually the value returned by
|
|
resfile_get_reslen
|
|
dest (O): resource data
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t resfile_get_resdata(mac_resfileref *resfileref, const rsrc_ref_entry *entry, UINT32 offset, UINT32 len, void *dest)
|
|
{
|
|
imgtoolerr_t err;
|
|
UINT32 data_offs;
|
|
UINT32BE llen;
|
|
|
|
data_offs = get_UINT24BE(entry->data_offs);
|
|
|
|
/* seek to resource data in resource data section */
|
|
err = mac_file_seek(&resfileref->fileref, resfileref->data_offs+data_offs);
|
|
if (err)
|
|
return err;
|
|
|
|
/* get data length */
|
|
err = mac_file_read(&resfileref->fileref, sizeof(llen), &llen);
|
|
if (err)
|
|
return err;
|
|
|
|
/* check that we do not ask to read more data than avalaible */
|
|
if ((offset + len) > get_UINT32BE(llen))
|
|
return IMGTOOLERR_UNEXPECTED;
|
|
|
|
if (offset)
|
|
{ /* seek to resource data offset in resource data section */
|
|
err = mac_file_seek(&resfileref->fileref, resfileref->data_offs+data_offs+4+offset);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
/* get data */
|
|
err = mac_file_read(&resfileref->fileref, len, dest);
|
|
if (err)
|
|
return err;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
#pragma mark -
|
|
#pragma mark DESKTOP FILE IMPLEMENTATION
|
|
#endif
|
|
/*
|
|
All macintosh volumes have information stored in the desktop file or the
|
|
desktop database.
|
|
|
|
Such information include file comments, copy of BNDL and FREF resources
|
|
that describes supported file types for each application on the volume,
|
|
copy of icons for each file type registered by each application on the
|
|
volume, etc. On MFS volumes, the list of folders is stored in the desktop
|
|
file as well.
|
|
|
|
|
|
There have been two implementations of the desktop metadata:
|
|
|
|
* The original desktop file. The database is stored in the resource fork
|
|
of a (usually invisible) file called "Desktop" (case may change
|
|
according to system versions), located at the root of the volume. The
|
|
desktop file is used by System 6 and earlier for all volumes (unless
|
|
Appleshare 2 is installed and the volume is shared IIRC), and by System
|
|
7 and later for volumes smaller than 2MBytes (so that floppy disks
|
|
remain fully compatible with earlier versions of system). The desktop
|
|
file is incompletely documented by Apple technote TB06.
|
|
|
|
* The desktop database. The database is stored in the resource fork is
|
|
stored in the data fork of two (usually invisible) files called
|
|
"Desktop DF" and "Desktop DF". The desktop database is used for
|
|
volumes shared by Appleshare 2, and for most volumes under System 7 and
|
|
later. The format of these file is not documented AFAIK.
|
|
|
|
|
|
The reasons for the introduction of the desktop database were:
|
|
* the macintosh resource manager cannot share resource files, which was
|
|
a problem for Appleshare
|
|
* the macintosh resource manager is pretty limited (+/-16MByte of data and
|
|
2727 resources at most), which was a problem for large hard disks with
|
|
many programs/comments
|
|
*/
|
|
|
|
#ifdef UNUSED_FUNCTION
|
|
/*
|
|
get_comment
|
|
|
|
Get a comment from the Desktop file
|
|
|
|
l2_img (I): macintosh image the data should be read from
|
|
id (I): comment id (from mfs_hashString(), or HFS FXInfo/DXInfo records)
|
|
comment (O): comment that has been read
|
|
|
|
Return imgtool error code
|
|
*/
|
|
static imgtoolerr_t get_comment(struct mac_l2_imgref *l2_img, UINT16 id, mac_str255 comment)
|
|
{
|
|
static const UINT8 desktop_fname[] = {'\7','D','e','s','k','t','o','p'};
|
|
#define restype_FCMT (('F' << 24) | ('C' << 16) | ('M' << 8) | 'T')
|
|
mac_resfileref resfileref;
|
|
rsrc_ref_entry resentry;
|
|
UINT32 reslen;
|
|
imgtoolerr_t err;
|
|
|
|
/* open rsrc fork of file Desktop in root directory */
|
|
err = mac_file_open(l2_img, 2, desktop_fname, rsrc_fork, &resfileref.fileref);
|
|
if (err)
|
|
return err;
|
|
|
|
/* open resource structures */
|
|
err = resfile_open(&resfileref);
|
|
if (err)
|
|
return err;
|
|
|
|
/* look for resource FCMT #id */
|
|
err = resfile_get_entry(&resfileref, restype_FCMT, id, &resentry);
|
|
if (err)
|
|
return err;
|
|
|
|
/* extract comment len */
|
|
err = resfile_get_reslen(&resfileref, &resentry, &reslen);
|
|
if (err)
|
|
return err;
|
|
|
|
/* check comment len */
|
|
if (reslen > 256)
|
|
/* hurk */
|
|
/*return IMGTOOLERR_CORRUPTIMAGE;*/
|
|
/* people willing to extend the MFM comment field (you know, the kind
|
|
of masochists that try to support 20-year-old OSes) might append extra
|
|
fields, so we just truncate the resource */
|
|
reslen = 256;
|
|
|
|
/* extract comment data */
|
|
err = resfile_get_resdata(&resfileref, &resentry, 0, reslen, comment);
|
|
if (err)
|
|
return err;
|
|
|
|
/* phew, we are done! */
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
#pragma mark -
|
|
#pragma mark IMGTOOL MODULE IMPLEMENTATION
|
|
#endif
|
|
|
|
#ifdef UNUSED_FUNCTION
|
|
static void mac_image_exit(imgtool::image *img);
|
|
#endif
|
|
static void mac_image_info(imgtool::image &img, char *string, size_t len);
|
|
static imgtoolerr_t mac_image_beginenum(imgtool::directory *enumeration, const char *path);
|
|
static imgtoolerr_t mac_image_nextenum(imgtool::directory *enumeration, imgtool_dirent *ent);
|
|
static imgtoolerr_t mac_image_freespace(imgtool::partition *partition, UINT64 *size);
|
|
static imgtoolerr_t mac_image_readfile(imgtool::partition *partition, const char *filename, const char *fork, imgtool::stream &destf);
|
|
static imgtoolerr_t mac_image_writefile(imgtool::partition *partition, const char *filename, const char *fork, imgtool::stream &sourcef, util::option_resolution *writeoptions);
|
|
|
|
#ifdef UNUSED_FUNCTION
|
|
/*
|
|
close a mfs/hfs image
|
|
*/
|
|
static void mac_image_exit(imgtool::image *img)
|
|
{
|
|
struct mac_l2_imgref *image = get_imgref(img);
|
|
|
|
mac_image_close(image);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
get basic information on a mfs/hfs image
|
|
|
|
Currently returns the volume name
|
|
*/
|
|
static void mac_image_info(imgtool::image &img, char *string, size_t len)
|
|
{
|
|
struct mac_l2_imgref *image = get_imgref(img);
|
|
|
|
switch (image->format)
|
|
{
|
|
case L2I_MFS:
|
|
mac_to_c_strncpy(string, len, image->u.mfs.volname);
|
|
break;
|
|
|
|
case L2I_HFS:
|
|
mac_to_c_strncpy(string, len, image->u.hfs.volname);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
MFS/HFS catalog iterator, used when imgtool reads the catalog
|
|
*/
|
|
struct mac_iterator
|
|
{
|
|
mac_format format;
|
|
struct mac_l2_imgref *l2_img;
|
|
union
|
|
{
|
|
struct
|
|
{
|
|
mfs_dirref dirref; /* open directory reference */
|
|
} mfs;
|
|
struct
|
|
{
|
|
hfs_cat_enumerator catref; /* catalog file enumerator */
|
|
} hfs;
|
|
} u;
|
|
};
|
|
|
|
/*
|
|
Open the disk catalog for enumeration
|
|
*/
|
|
static imgtoolerr_t mac_image_beginenum(imgtool::directory *enumeration, const char *path)
|
|
{
|
|
struct mac_l2_imgref *image = get_imgref(enumeration->image());
|
|
mac_iterator *iter = (mac_iterator *) enumeration->extra_bytes();
|
|
imgtoolerr_t err = IMGTOOLERR_UNEXPECTED;
|
|
|
|
iter->format = image->format;
|
|
iter->l2_img = image;
|
|
|
|
switch (iter->format)
|
|
{
|
|
case L2I_MFS:
|
|
err = mfs_dir_open(image, path, &iter->u.mfs.dirref);
|
|
break;
|
|
|
|
case L2I_HFS:
|
|
err = hfs_cat_open(image, path, &iter->u.hfs.catref);
|
|
break;
|
|
}
|
|
|
|
if (err)
|
|
return err;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
Enumerate disk catalog next entry (MFS)
|
|
*/
|
|
static imgtoolerr_t mfs_image_nextenum(mac_iterator *iter, imgtool_dirent *ent)
|
|
{
|
|
mfs_dir_entry *cur_dir_entry;
|
|
imgtoolerr_t err;
|
|
|
|
|
|
assert(iter->format == L2I_MFS);
|
|
|
|
ent->corrupt = 0;
|
|
ent->eof = 0;
|
|
|
|
err = mfs_dir_read(&iter->u.mfs.dirref, &cur_dir_entry);
|
|
if (err)
|
|
{
|
|
/* error */
|
|
ent->corrupt = 1;
|
|
return err;
|
|
}
|
|
else if (!cur_dir_entry)
|
|
{
|
|
/* EOF */
|
|
ent->eof = 1;
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/* copy info */
|
|
mac_to_c_strncpy(ent->filename, ARRAY_LENGTH(ent->filename), cur_dir_entry->name);
|
|
ent->filesize = get_UINT32BE(cur_dir_entry->dataPhysicalSize)
|
|
+ get_UINT32BE(cur_dir_entry->rsrcPhysicalSize);
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
#if 0
|
|
/*
|
|
Concatenate path elements in the reverse order
|
|
|
|
dest (O): destination buffer
|
|
dest_cur_pos (I/O): current position in destination buffer (buffer is
|
|
filled from end to start)
|
|
dest_max_len (I): length of destination buffer (use length minus one if you
|
|
want to preserve a trailing NUL character)
|
|
src (I): source C string
|
|
*/
|
|
static void concat_fname(char *dest, int *dest_cur_pos, int dest_max_len, const char *src)
|
|
{
|
|
static const char ellipsis[] = { '.', '.', '.' };
|
|
int src_len = strlen(src); /* number of chars from src to insert */
|
|
|
|
if (src_len <= *dest_cur_pos)
|
|
{
|
|
*dest_cur_pos -= src_len;
|
|
memcpy(dest + *dest_cur_pos, src, src_len);
|
|
}
|
|
else
|
|
{
|
|
memcpy(dest, src + src_len - *dest_cur_pos, *dest_cur_pos);
|
|
*dest_cur_pos = 0;
|
|
memcpy(dest, ellipsis, (sizeof(ellipsis) <= dest_max_len)
|
|
? sizeof(ellipsis)
|
|
: dest_max_len);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
Enumerate disk catalog next entry (HFS)
|
|
*/
|
|
static imgtoolerr_t hfs_image_nextenum(mac_iterator *iter, imgtool_dirent *ent)
|
|
{
|
|
hfs_catKey *catrec_key;
|
|
hfs_catData *catrec_data;
|
|
UINT16 dataRecType;
|
|
imgtoolerr_t err;
|
|
/* currently, the mac->C conversion transcodes one mac char with at most 3
|
|
C chars */
|
|
int cur_name_head;
|
|
|
|
assert(iter->format == L2I_HFS);
|
|
|
|
ent->corrupt = 0;
|
|
ent->eof = 0;
|
|
|
|
do
|
|
{
|
|
err = hfs_cat_read(&iter->u.hfs.catref, &catrec_key, &catrec_data);
|
|
if (err)
|
|
{
|
|
/* error */
|
|
ent->corrupt = 1;
|
|
return err;
|
|
}
|
|
else if (!catrec_key)
|
|
{
|
|
/* EOF */
|
|
ent->eof = 1;
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
dataRecType = get_UINT16BE(catrec_data->dataType);
|
|
} while (((dataRecType != hcrt_Folder) && (dataRecType != hcrt_File))
|
|
|| (get_UINT32BE(catrec_key->parID) != iter->u.hfs.catref.parID));
|
|
|
|
/* copy info */
|
|
switch (get_UINT16BE(catrec_data->dataType))
|
|
{
|
|
case hcrt_Folder:
|
|
ent->directory = 1;
|
|
ent->filesize = 0;
|
|
break;
|
|
|
|
case hcrt_File:
|
|
ent->directory = 0;
|
|
ent->filesize = get_UINT32BE(catrec_data->file.dataPhysicalSize)
|
|
+ get_UINT32BE(catrec_data->file.rsrcPhysicalSize);
|
|
break;
|
|
}
|
|
|
|
/* initialize file path buffer */
|
|
cur_name_head = ARRAY_LENGTH(ent->filename);
|
|
if (cur_name_head > 0)
|
|
{
|
|
cur_name_head--;
|
|
ent->filename[cur_name_head] = '\0';
|
|
}
|
|
|
|
/* insert folder/file name in buffer */
|
|
mac_to_c_strncpy(ent->filename, ARRAY_LENGTH(ent->filename), catrec_key->cName);
|
|
// concat_fname(ent->filename, &cur_name_head, ARRAY_LENGTH(ent->filename) - 1, buf);
|
|
|
|
#if 0
|
|
/* extract parent directory ID */
|
|
parID = get_UINT32BE(catrec_key->parID);
|
|
|
|
/* looping while (parID != 1) will display the volume name; looping while
|
|
(parID != 2) won't */
|
|
while (parID != /*1*/2)
|
|
{
|
|
/* search catalog for folder thread */
|
|
err = hfs_cat_search(iter->l2_img, parID, mac_empty_str, &catrec_key, &catrec_data);
|
|
if (err)
|
|
{
|
|
/* error */
|
|
concat_fname(ent->filename, &cur_name_head, ARRAY_LENGTH(ent->filename) - 1, ":");
|
|
concat_fname(ent->filename, &cur_name_head, ARRAY_LENGTH(ent->filename) - 1, "???");
|
|
|
|
memmove(ent->filename, ent->filename+cur_name_head, ARRAY_LENGTH(ent->filename) - cur_name_head);
|
|
ent->corrupt = 1;
|
|
return err;
|
|
}
|
|
|
|
dataRecType = get_UINT16BE(catrec_data->dataType);
|
|
|
|
if (dataRecType != hcrt_FolderThread)
|
|
{
|
|
/* error */
|
|
concat_fname(ent->filename, &cur_name_head, ARRAY_LENGTH(ent->filename)-1, ":");
|
|
concat_fname(ent->filename, &cur_name_head, ARRAY_LENGTH(ent->filename)-1, "???");
|
|
|
|
memmove(ent->filename, ent->filename+cur_name_head, ARRAY_LENGTH(ent->filename)-cur_name_head);
|
|
ent->corrupt = 1;
|
|
return IMGTOOLERR_CORRUPTIMAGE;
|
|
}
|
|
|
|
/* got folder thread record: insert the folder name at the start of
|
|
file path, then iterate */
|
|
mac_to_c_strncpy(buf, sizeof(buf), catrec_data->thread.nodeName);
|
|
concat_fname(ent->filename, &cur_name_head, ARRAY_LENGTH(ent->filename) - 1, ":");
|
|
concat_fname(ent->filename, &cur_name_head, ARRAY_LENGTH(ent->filename) - 1, buf);
|
|
|
|
/* extract parent directory ID */
|
|
parID = get_UINT32BE(catrec_data->thread.parID);
|
|
}
|
|
memmove(ent->filename, ent->filename+cur_name_head, ARRAY_LENGTH(ent->filename) -cur_name_head);
|
|
#endif
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
Enumerate disk catalog next entry
|
|
*/
|
|
static imgtoolerr_t mac_image_nextenum(imgtool::directory *enumeration, imgtool_dirent *ent)
|
|
{
|
|
imgtoolerr_t err;
|
|
mac_iterator *iter = (mac_iterator *) enumeration->extra_bytes();
|
|
|
|
switch (iter->format)
|
|
{
|
|
case L2I_MFS:
|
|
err = mfs_image_nextenum(iter, ent);
|
|
break;
|
|
|
|
case L2I_HFS:
|
|
err = hfs_image_nextenum(iter, ent);
|
|
break;
|
|
|
|
default:
|
|
assert(1);
|
|
err = IMGTOOLERR_UNEXPECTED;
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
Compute free space on disk image in bytes
|
|
*/
|
|
static imgtoolerr_t mac_image_freespace(imgtool::partition *partition, UINT64 *size)
|
|
{
|
|
imgtool::image &image(partition->image());
|
|
*size = ((UINT64) get_imgref(image)->freeABs) * 512;
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
#ifdef UNUSED_FUNCTION
|
|
static imgtoolerr_t mac_get_comment(struct mac_l2_imgref *image, mac_str255 filename, const mac_dirent *cat_info, mac_str255 comment)
|
|
{
|
|
imgtoolerr_t err = IMGTOOLERR_SUCCESS;
|
|
UINT16 commentID;
|
|
|
|
comment[0] = '\0';
|
|
|
|
/* get comment from Desktop file */
|
|
switch (image->format)
|
|
{
|
|
case L2I_MFS:
|
|
commentID = mfs_hashString(filename);
|
|
err = get_comment(image, commentID, comment);
|
|
break;
|
|
|
|
case L2I_HFS:
|
|
/* This is the way to get Finder comments in system <= 7. Attached
|
|
comments use another method, and Finder 8 uses yet another one. */
|
|
commentID = get_UINT16BE(cat_info->flXFinderInfo.comment);
|
|
if (commentID)
|
|
err = get_comment(image, commentID, comment);
|
|
break;
|
|
}
|
|
return err;
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
Extract a file from a disk image.
|
|
*/
|
|
static imgtoolerr_t mac_image_readfile(imgtool::partition *partition, const char *fpath, const char *fork, imgtool::stream &destf)
|
|
{
|
|
imgtoolerr_t err;
|
|
imgtool::image &img(partition->image());
|
|
struct mac_l2_imgref *image = get_imgref(img);
|
|
UINT32 parID;
|
|
mac_str255 filename;
|
|
mac_dirent cat_info;
|
|
mac_fileref fileref;
|
|
UINT8 buf[512];
|
|
UINT32 i, run_len, data_len;
|
|
mac_fork_t fork_num;
|
|
|
|
err = mac_identify_fork(fork, &fork_num);
|
|
if (err)
|
|
return err;
|
|
|
|
/* resolve path and fetch file info from directory/catalog */
|
|
err = mac_lookup_path(image, fpath, &parID, filename, &cat_info, FALSE);
|
|
if (err)
|
|
return err;
|
|
if (cat_info.dataRecType != hcrt_File)
|
|
return IMGTOOLERR_FILENOTFOUND;
|
|
|
|
/* open file */
|
|
err = mac_file_open(image, parID, filename, fork_num ? rsrc_fork : data_fork, &fileref);
|
|
if (err)
|
|
return err;
|
|
|
|
data_len = fork_num ? cat_info.rsrcLogicalSize : cat_info.dataLogicalSize;
|
|
|
|
/* extract DF */
|
|
i = 0;
|
|
while(i < data_len)
|
|
{
|
|
run_len = std::min(size_t(data_len - i), sizeof(buf));
|
|
|
|
err = mac_file_read(&fileref, run_len, buf);
|
|
if (err)
|
|
return err;
|
|
if (destf.write(buf, run_len) != run_len)
|
|
return IMGTOOLERR_WRITEERROR;
|
|
i += run_len;
|
|
}
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
Add a file to a disk image.
|
|
*/
|
|
static imgtoolerr_t mac_image_writefile(imgtool::partition *partition, const char *fpath, const char *fork, imgtool::stream &sourcef, util::option_resolution *writeoptions)
|
|
{
|
|
imgtool::image &img(partition->image());
|
|
struct mac_l2_imgref *image = get_imgref(img);
|
|
UINT32 parID;
|
|
mac_str255 filename;
|
|
mac_dirent cat_info;
|
|
mac_fileref fileref;
|
|
UINT32 fork_len;
|
|
/*UINT16 commentID;*/
|
|
/*mac_str255 comment;*/
|
|
UINT8 buf[512];
|
|
UINT32 i, run_len;
|
|
imgtoolerr_t err;
|
|
mac_fork_t fork_num;
|
|
|
|
(void) writeoptions;
|
|
|
|
if (image->format == L2I_HFS)
|
|
return IMGTOOLERR_UNIMPLEMENTED;
|
|
|
|
err = mac_identify_fork(fork, &fork_num);
|
|
if (err)
|
|
return err;
|
|
|
|
#if 0
|
|
if (header.version_old != 0)
|
|
return IMGTOOLERR_UNIMPLEMENTED;
|
|
#endif
|
|
/*mac_strcpy(filename, header.filename);*/
|
|
memset(&cat_info, 0, sizeof(cat_info));
|
|
set_UINT32BE(&cat_info.flFinderInfo.type, 0x3F3F3F3F);
|
|
set_UINT32BE(&cat_info.flFinderInfo.creator, 0x3F3F3F3F);
|
|
fork_len = sourcef.size();
|
|
/*comment[0] = get_UINT16BE(header.comment_len);*/ /* comment length */
|
|
/* Next two fields are set to 0 with MFS volumes. IIRC, 0 normally
|
|
means system script: I don't think MFS stores the file name script code
|
|
anywhere on disk, so it should be a reasonable approximation. */
|
|
|
|
/* create file */
|
|
/* clear inited flag and file location in window */
|
|
set_UINT16BE(&cat_info.flFinderInfo.flags, get_UINT16BE(cat_info.flFinderInfo.flags) & ~fif_hasBeenInited);
|
|
set_UINT16BE(&cat_info.flFinderInfo.location.v, 0);
|
|
set_UINT16BE(&cat_info.flFinderInfo.location.h, 0);
|
|
|
|
/* resolve path and create file */
|
|
err = mac_lookup_path(image, fpath, &parID, filename, &cat_info, TRUE);
|
|
if (err)
|
|
return err;
|
|
|
|
/* open file fork */
|
|
err = mac_file_open(image, parID, filename, (fork_num ? rsrc_fork : data_fork), &fileref);
|
|
if (err)
|
|
return err;
|
|
|
|
err = mac_file_seteof(&fileref, fork_len);
|
|
if (err)
|
|
return err;
|
|
|
|
/* extract fork */
|
|
for (i=0; i<fork_len;)
|
|
{
|
|
run_len = fork_len - i;
|
|
if (run_len > 512)
|
|
run_len = 512;
|
|
if (sourcef.read(buf, run_len) != run_len)
|
|
return IMGTOOLERR_READERROR;
|
|
err = mac_file_write(&fileref, run_len, buf);
|
|
if (err)
|
|
return err;
|
|
i += run_len;
|
|
}
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
static imgtoolerr_t mac_image_listforks(imgtool::partition *partition, const char *path, imgtool_forkent *ents, size_t len)
|
|
{
|
|
imgtoolerr_t err;
|
|
UINT32 parID;
|
|
mac_str255 filename;
|
|
mac_dirent cat_info;
|
|
int fork_num = 0;
|
|
imgtool::image &img(partition->image());
|
|
struct mac_l2_imgref *image = get_imgref(img);
|
|
|
|
/* resolve path and fetch file info from directory/catalog */
|
|
err = mac_lookup_path(image, path, &parID, filename, &cat_info, FALSE);
|
|
if (err)
|
|
return err;
|
|
if (cat_info.dataRecType != hcrt_File)
|
|
return IMGTOOLERR_FILENOTFOUND;
|
|
|
|
/* specify data fork */
|
|
ents[fork_num].type = FORK_DATA;
|
|
ents[fork_num].forkname[0] = '\0';
|
|
ents[fork_num].size = cat_info.dataLogicalSize;
|
|
fork_num++;
|
|
|
|
if (cat_info.rsrcLogicalSize > 0)
|
|
{
|
|
/* specify the resource fork */
|
|
ents[fork_num].type = FORK_RESOURCE;
|
|
strcpy(ents[fork_num].forkname, "RESOURCE_FORK");
|
|
ents[fork_num].size = cat_info.rsrcLogicalSize;
|
|
fork_num++;
|
|
}
|
|
|
|
ents[fork_num].type = FORK_END;
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
static imgtoolerr_t mac_image_getattrs(imgtool::partition *partition, const char *path, const UINT32 *attrs, imgtool_attribute *values)
|
|
{
|
|
imgtoolerr_t err;
|
|
imgtool::image &img(partition->image());
|
|
UINT32 parID;
|
|
mac_str255 filename;
|
|
mac_dirent cat_info;
|
|
struct mac_l2_imgref *image = get_imgref(img);
|
|
int i;
|
|
|
|
/* resolve path and fetch file info from directory/catalog */
|
|
err = mac_lookup_path(image, path, &parID, filename, &cat_info, FALSE);
|
|
if (err)
|
|
return err;
|
|
if (cat_info.dataRecType != hcrt_File)
|
|
return IMGTOOLERR_FILENOTFOUND;
|
|
|
|
for (i = 0; attrs[i]; i++)
|
|
{
|
|
switch(attrs[i])
|
|
{
|
|
case IMGTOOLATTR_INT_MAC_TYPE:
|
|
values[i].i = get_UINT32BE(cat_info.flFinderInfo.type);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_CREATOR:
|
|
values[i].i = get_UINT32BE(cat_info.flFinderInfo.creator);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_FINDERFLAGS:
|
|
values[i].i = get_UINT16BE(cat_info.flFinderInfo.flags);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_COORDX:
|
|
values[i].i = get_UINT16BE(cat_info.flFinderInfo.location.h);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_COORDY:
|
|
values[i].i = get_UINT16BE(cat_info.flFinderInfo.location.v);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_FINDERFOLDER:
|
|
values[i].i = get_UINT16BE(cat_info.flFinderInfo.fldr);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_ICONID:
|
|
values[i].i = get_UINT16BE(cat_info.flXFinderInfo.iconID);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_SCRIPTCODE:
|
|
values[i].i = cat_info.flXFinderInfo.script;
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_EXTENDEDFLAGS:
|
|
values[i].i = cat_info.flXFinderInfo.XFlags;
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_COMMENTID:
|
|
values[i].i = get_UINT16BE(cat_info.flXFinderInfo.comment);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_PUTAWAYDIRECTORY:
|
|
values[i].i = get_UINT32BE(cat_info.flXFinderInfo.putAway);
|
|
break;
|
|
|
|
case IMGTOOLATTR_TIME_CREATED:
|
|
values[i].t = mac_crack_time(cat_info.createDate);
|
|
break;
|
|
case IMGTOOLATTR_TIME_LASTMODIFIED:
|
|
values[i].t = mac_crack_time(cat_info.modifyDate);
|
|
break;
|
|
}
|
|
}
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
static imgtoolerr_t mac_image_setattrs(imgtool::partition *partition, const char *path, const UINT32 *attrs, const imgtool_attribute *values)
|
|
{
|
|
imgtoolerr_t err;
|
|
UINT32 parID;
|
|
mac_str255 filename;
|
|
mac_dirent cat_info;
|
|
imgtool::image &img(partition->image());
|
|
struct mac_l2_imgref *image = get_imgref(img);
|
|
int i;
|
|
|
|
/* resolve path and fetch file info from directory/catalog */
|
|
err = mac_lookup_path(image, path, &parID, filename, &cat_info, FALSE);
|
|
if (err)
|
|
return err;
|
|
if (cat_info.dataRecType != hcrt_File)
|
|
return IMGTOOLERR_FILENOTFOUND;
|
|
|
|
for (i = 0; attrs[i]; i++)
|
|
{
|
|
switch(attrs[i])
|
|
{
|
|
case IMGTOOLATTR_INT_MAC_TYPE:
|
|
set_UINT32BE(&cat_info.flFinderInfo.type, values[i].i);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_CREATOR:
|
|
set_UINT32BE(&cat_info.flFinderInfo.creator, values[i].i);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_FINDERFLAGS:
|
|
set_UINT16BE(&cat_info.flFinderInfo.flags, values[i].i);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_COORDX:
|
|
set_UINT16BE(&cat_info.flFinderInfo.location.h, values[i].i);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_COORDY:
|
|
set_UINT16BE(&cat_info.flFinderInfo.location.v, values[i].i);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_FINDERFOLDER:
|
|
set_UINT16BE(&cat_info.flFinderInfo.fldr, values[i].i);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_ICONID:
|
|
set_UINT16BE(&cat_info.flXFinderInfo.iconID, values[i].i);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_SCRIPTCODE:
|
|
cat_info.flXFinderInfo.script = values[i].i;
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_EXTENDEDFLAGS:
|
|
cat_info.flXFinderInfo.XFlags = values[i].i;
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_COMMENTID:
|
|
set_UINT16BE(&cat_info.flXFinderInfo.comment, values[i].i);
|
|
break;
|
|
case IMGTOOLATTR_INT_MAC_PUTAWAYDIRECTORY:
|
|
set_UINT32BE(&cat_info.flXFinderInfo.putAway, values[i].i);
|
|
break;
|
|
|
|
case IMGTOOLATTR_TIME_CREATED:
|
|
cat_info.createDate = mac_setup_time(values[i].t);
|
|
break;
|
|
case IMGTOOLATTR_TIME_LASTMODIFIED:
|
|
cat_info.modifyDate = mac_setup_time(values[i].t);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* resolve path and fetch file info from directory/catalog */
|
|
err = mac_lookup_path(image, path, &parID, filename, &cat_info, TRUE);
|
|
if (err)
|
|
return err;
|
|
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
/*************************************
|
|
*
|
|
* Our very own resource manager
|
|
*
|
|
*************************************/
|
|
|
|
static const void *mac_walk_resources(const void *resource_fork, size_t resource_fork_length,
|
|
UINT32 resource_type,
|
|
int (*discriminator)(const void *resource, UINT16 id, UINT32 length, void *param_),
|
|
void *param, UINT16 *resource_id, UINT32 *resource_length)
|
|
{
|
|
UINT32 resource_data_offset, resource_data_length;
|
|
UINT32 resource_map_offset, resource_map_length;
|
|
UINT32 resource_typelist_count, resource_typelist_offset;
|
|
UINT32 resource_entry_offset, resource_entry_count;
|
|
UINT32 i, this_resource_type;
|
|
UINT32 this_resource_data, this_resource_length;
|
|
UINT16 this_resource_id;
|
|
const void *this_resource_ptr;
|
|
|
|
if (resource_fork_length < 16)
|
|
return NULL;
|
|
|
|
/* read resource header; its ok if anything past this point fails */
|
|
resource_data_offset = pick_integer_be(resource_fork, 0, 4);
|
|
resource_map_offset = pick_integer_be(resource_fork, 4, 4);
|
|
resource_data_length = pick_integer_be(resource_fork, 8, 4);
|
|
resource_map_length = pick_integer_be(resource_fork, 12, 4);
|
|
if ((resource_data_offset + resource_data_length) > resource_fork_length)
|
|
return NULL;
|
|
if ((resource_map_offset + resource_map_length) > resource_fork_length)
|
|
return NULL;
|
|
if (resource_map_length < 30)
|
|
return NULL;
|
|
|
|
/* read resource map and locate the resource type list */
|
|
resource_typelist_offset = pick_integer_be(resource_fork,
|
|
resource_map_offset + 24, 2) + resource_map_offset;
|
|
if ((resource_typelist_offset + 2) > resource_fork_length)
|
|
return NULL;
|
|
resource_typelist_count = pick_integer_be(resource_fork,
|
|
resource_typelist_offset, 2) + 1;
|
|
if ((resource_typelist_offset + resource_typelist_count * 16 + 2) > resource_fork_length)
|
|
return NULL;
|
|
|
|
/* scan the resource type list and locate the entries for this type */
|
|
resource_entry_count = 0;
|
|
resource_entry_offset = 0;
|
|
for (i = 0; i < resource_typelist_count; i++)
|
|
{
|
|
this_resource_type = pick_integer_be(resource_fork, resource_typelist_offset
|
|
+ (i * 8) + 2 + 0, 4);
|
|
if (this_resource_type == resource_type)
|
|
{
|
|
resource_entry_count = pick_integer_be(resource_fork, resource_typelist_offset
|
|
+ (i * 8) + 2 + 4, 2) + 1;
|
|
resource_entry_offset = pick_integer_be(resource_fork, resource_typelist_offset
|
|
+ (i * 8) + 2 + 6, 2) + resource_typelist_offset;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* scan the resource entries, and find the correct resource */
|
|
for (i = 0; i < resource_entry_count; i++)
|
|
{
|
|
this_resource_id = pick_integer_be(resource_fork, resource_entry_offset
|
|
+ (i * 12) + 0, 2);
|
|
this_resource_data = pick_integer_be(resource_fork, resource_entry_offset
|
|
+ (i * 12) + 5, 3) + resource_data_offset;
|
|
if ((this_resource_data + 4) > resource_fork_length)
|
|
return NULL;
|
|
|
|
/* gauge the length */
|
|
this_resource_length = pick_integer_be(resource_fork, this_resource_data, 4);
|
|
this_resource_data += 4;
|
|
if ((this_resource_data + this_resource_length) > resource_fork_length)
|
|
return NULL;
|
|
|
|
this_resource_ptr = ((const UINT8 *) resource_fork) + this_resource_data;
|
|
if (discriminator(this_resource_ptr, this_resource_id,
|
|
this_resource_length, param))
|
|
{
|
|
if (resource_length)
|
|
*resource_length = this_resource_length;
|
|
if (resource_id)
|
|
*resource_id = this_resource_id;
|
|
return this_resource_ptr;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
static int get_resource_discriminator(const void *resource, UINT16 id, UINT32 length, void *param)
|
|
{
|
|
const UINT16 *id_ptr = (const UINT16 *) param;
|
|
return id == *id_ptr;
|
|
}
|
|
|
|
|
|
|
|
static const void *mac_get_resource(const void *resource_fork, size_t resource_fork_length,
|
|
UINT32 resource_type, UINT16 resource_id, UINT32 *resource_length)
|
|
{
|
|
return mac_walk_resources(resource_fork, resource_fork_length,
|
|
resource_type, get_resource_discriminator, &resource_id, NULL, resource_length);
|
|
}
|
|
|
|
|
|
|
|
/*************************************
|
|
*
|
|
* Custom icons
|
|
*
|
|
*************************************/
|
|
|
|
static int bundle_discriminator(const void *resource, UINT16 id, UINT32 length, void *param)
|
|
{
|
|
UINT32 this_creator_code = pick_integer_be(resource, 0, 4);
|
|
UINT32 desired_creator_code = *((UINT32 *) param);
|
|
return this_creator_code == desired_creator_code;
|
|
}
|
|
|
|
|
|
|
|
static int get_pixel(const UINT8 *src, int width, int height, int bpp,
|
|
int x, int y)
|
|
{
|
|
UINT8 byte, mask;
|
|
int bit_position;
|
|
|
|
byte = src[(y * width + x) * bpp / 8];
|
|
bit_position = 8 - ((x % (8 / bpp)) + 1) * bpp;
|
|
mask = (1 << bpp) - 1;
|
|
return (byte >> bit_position) & mask;
|
|
}
|
|
|
|
|
|
|
|
static int load_icon(UINT32 *dest, const void *resource_fork, UINT64 resource_fork_length,
|
|
UINT32 resource_type, UINT16 resource_id, int width, int height, int bpp,
|
|
const UINT32 *palette, int has_mask)
|
|
{
|
|
int success = FALSE;
|
|
int y, x, color, is_masked;
|
|
UINT32 pixel;
|
|
const UINT8 *src;
|
|
UINT32 resource_length;
|
|
UINT32 frame_length;
|
|
UINT32 total_length;
|
|
|
|
frame_length = width * height * bpp / 8;
|
|
total_length = frame_length * (has_mask ? 2 : 1);
|
|
|
|
/* attempt to fetch resource */
|
|
src = (const UINT8*)mac_get_resource(resource_fork, resource_fork_length, resource_type,
|
|
resource_id, &resource_length);
|
|
if (src && (resource_length == total_length))
|
|
{
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
for (x = 0; x < width; x ++)
|
|
{
|
|
/* first check mask bit */
|
|
if (has_mask)
|
|
is_masked = get_pixel(src + frame_length, width, height, bpp, x, y);
|
|
else
|
|
is_masked = dest[y * width + x] >= 0x80000000;
|
|
|
|
if (is_masked)
|
|
{
|
|
/* mask is ok; check the actual icon */
|
|
color = get_pixel(src, width, height, bpp, x, y);
|
|
pixel = palette[color] | 0xFF000000;
|
|
}
|
|
else
|
|
{
|
|
/* masked out; nothing */
|
|
pixel = 0;
|
|
}
|
|
|
|
dest[y * width + x] = pixel;
|
|
}
|
|
}
|
|
success = TRUE;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
|
|
|
|
static imgtoolerr_t mac_image_geticoninfo(imgtool::partition *partition, const char *path, imgtool_iconinfo *iconinfo)
|
|
{
|
|
static const UINT32 mac_palette_1bpp[2] = { 0xFFFFFF, 0x000000 };
|
|
|
|
static const UINT32 mac_palette_4bpp[16] =
|
|
{
|
|
0xFFFFFF, 0xFCF305, 0xFF6402, 0xDD0806, 0xF20884, 0x4600A5,
|
|
0x0000D4, 0x02ABEA, 0x1FB714, 0x006411, 0x562C05, 0x90713A,
|
|
0xC0C0C0, 0x808080, 0x404040, 0x000000
|
|
};
|
|
|
|
static const UINT32 mac_palette_8bpp[256] =
|
|
{
|
|
0xFFFFFF, 0xFFFFCC, 0xFFFF99, 0xFFFF66, 0xFFFF33, 0xFFFF00,
|
|
0xFFCCFF, 0xFFCCCC, 0xFFCC99, 0xFFCC66, 0xFFCC33, 0xFFCC00,
|
|
0xFF99FF, 0xFF99CC, 0xFF9999, 0xFF9966, 0xFF9933, 0xFF9900,
|
|
0xFF66FF, 0xFF66CC, 0xFF6699, 0xFF6666, 0xFF6633, 0xFF6600,
|
|
0xFF33FF, 0xFF33CC, 0xFF3399, 0xFF3366, 0xFF3333, 0xFF3300,
|
|
0xFF00FF, 0xFF00CC, 0xFF0099, 0xFF0066, 0xFF0033, 0xFF0000,
|
|
0xCCFFFF, 0xCCFFCC, 0xCCFF99, 0xCCFF66, 0xCCFF33, 0xCCFF00,
|
|
0xCCCCFF, 0xCCCCCC, 0xCCCC99, 0xCCCC66, 0xCCCC33, 0xCCCC00,
|
|
0xCC99FF, 0xCC99CC, 0xCC9999, 0xCC9966, 0xCC9933, 0xCC9900,
|
|
0xCC66FF, 0xCC66CC, 0xCC6699, 0xCC6666, 0xCC6633, 0xCC6600,
|
|
0xCC33FF, 0xCC33CC, 0xCC3399, 0xCC3366, 0xCC3333, 0xCC3300,
|
|
0xCC00FF, 0xCC00CC, 0xCC0099, 0xCC0066, 0xCC0033, 0xCC0000,
|
|
0x99FFFF, 0x99FFCC, 0x99FF99, 0x99FF66, 0x99FF33, 0x99FF00,
|
|
0x99CCFF, 0x99CCCC, 0x99CC99, 0x99CC66, 0x99CC33, 0x99CC00,
|
|
0x9999FF, 0x9999CC, 0x999999, 0x999966, 0x999933, 0x999900,
|
|
0x9966FF, 0x9966CC, 0x996699, 0x996666, 0x996633, 0x996600,
|
|
0x9933FF, 0x9933CC, 0x993399, 0x993366, 0x993333, 0x993300,
|
|
0x9900FF, 0x9900CC, 0x990099, 0x990066, 0x990033, 0x990000,
|
|
0x66FFFF, 0x66FFCC, 0x66FF99, 0x66FF66, 0x66FF33, 0x66FF00,
|
|
0x66CCFF, 0x66CCCC, 0x66CC99, 0x66CC66, 0x66CC33, 0x66CC00,
|
|
0x6699FF, 0x6699CC, 0x669999, 0x669966, 0x669933, 0x669900,
|
|
0x6666FF, 0x6666CC, 0x666699, 0x666666, 0x666633, 0x666600,
|
|
0x6633FF, 0x6633CC, 0x663399, 0x663366, 0x663333, 0x663300,
|
|
0x6600FF, 0x6600CC, 0x660099, 0x660066, 0x660033, 0x660000,
|
|
0x33FFFF, 0x33FFCC, 0x33FF99, 0x33FF66, 0x33FF33, 0x33FF00,
|
|
0x33CCFF, 0x33CCCC, 0x33CC99, 0x33CC66, 0x33CC33, 0x33CC00,
|
|
0x3399FF, 0x3399CC, 0x339999, 0x339966, 0x339933, 0x339900,
|
|
0x3366FF, 0x3366CC, 0x336699, 0x336666, 0x336633, 0x336600,
|
|
0x3333FF, 0x3333CC, 0x333399, 0x333366, 0x333333, 0x333300,
|
|
0x3300FF, 0x3300CC, 0x330099, 0x330066, 0x330033, 0x330000,
|
|
0x00FFFF, 0x00FFCC, 0x00FF99, 0x00FF66, 0x00FF33, 0x00FF00,
|
|
0x00CCFF, 0x00CCCC, 0x00CC99, 0x00CC66, 0x00CC33, 0x00CC00,
|
|
0x0099FF, 0x0099CC, 0x009999, 0x009966, 0x009933, 0x009900,
|
|
0x0066FF, 0x0066CC, 0x006699, 0x006666, 0x006633, 0x006600,
|
|
0x0033FF, 0x0033CC, 0x003399, 0x003366, 0x003333, 0x003300,
|
|
0x0000FF, 0x0000CC, 0x000099, 0x000066, 0x000033, 0xEE0000,
|
|
0xDD0000, 0xBB0000, 0xAA0000, 0x880000, 0x770000, 0x550000,
|
|
0x440000, 0x220000, 0x110000, 0x00EE00, 0x00DD00, 0x00BB00,
|
|
0x00AA00, 0x008800, 0x007700, 0x005500, 0x004400, 0x002200,
|
|
0x001100, 0x0000EE, 0x0000DD, 0x0000BB, 0x0000AA, 0x000088,
|
|
0x000077, 0x000055, 0x000044, 0x000022, 0x000011, 0xEEEEEE,
|
|
0xDDDDDD, 0xBBBBBB, 0xAAAAAA, 0x888888, 0x777777, 0x555555,
|
|
0x444444, 0x222222, 0x111111, 0x000000
|
|
};
|
|
|
|
static const UINT32 attrs[4] =
|
|
{
|
|
IMGTOOLATTR_INT_MAC_TYPE,
|
|
IMGTOOLATTR_INT_MAC_CREATOR,
|
|
IMGTOOLATTR_INT_MAC_FINDERFLAGS
|
|
};
|
|
|
|
imgtoolerr_t err;
|
|
imgtool_attribute attr_values[3];
|
|
UINT32 type_code, creator_code, finder_flags;
|
|
imgtool::stream *stream = NULL;
|
|
const void *resource_fork;
|
|
UINT64 resource_fork_length;
|
|
const void *bundle;
|
|
UINT32 bundle_length, pos, fref_pos, icn_pos, i;
|
|
UINT16 local_id = 0, resource_id;
|
|
UINT32 fref_bundleentry_length, icn_bundleentry_length;
|
|
const void *fref;
|
|
UINT32 resource_length;
|
|
|
|
assert((ARRAY_LENGTH(attrs) - 1)
|
|
== ARRAY_LENGTH(attr_values));
|
|
|
|
/* first retrieve type and creator code */
|
|
err = mac_image_getattrs(partition, path, attrs, attr_values);
|
|
if (err)
|
|
goto done;
|
|
type_code = (UINT32) attr_values[0].i;
|
|
creator_code = (UINT32) attr_values[1].i;
|
|
finder_flags = (UINT32) attr_values[2].i;
|
|
|
|
/* check the bundle bit; if clear (and the type is not 'APPL'), use the
|
|
* desktop file */
|
|
if (!(finder_flags & 0x2000) && (type_code != /* APPL */ 0x4150504C))
|
|
path = "Desktop\0";
|
|
|
|
stream = imgtool::stream::open_mem(NULL, 0);
|
|
if (!stream)
|
|
{
|
|
err = IMGTOOLERR_SUCCESS;
|
|
goto done;
|
|
}
|
|
|
|
/* read in the resource fork */
|
|
err = mac_image_readfile(partition, path, "RESOURCE_FORK", *stream);
|
|
if (err)
|
|
goto done;
|
|
resource_fork = stream->getptr();
|
|
resource_fork_length = stream->size();
|
|
|
|
/* attempt to look up the bundle */
|
|
bundle = mac_walk_resources(resource_fork, resource_fork_length, /* BNDL */ 0x424E444C,
|
|
bundle_discriminator, &creator_code, NULL, &bundle_length);
|
|
if (!bundle)
|
|
goto done;
|
|
|
|
/* find the FREF and the icon family */
|
|
pos = 8;
|
|
fref_pos = icn_pos = 0;
|
|
fref_bundleentry_length = icn_bundleentry_length = 0;
|
|
while((pos + 10) <= bundle_length)
|
|
{
|
|
UINT32 this_bundleentry_type = pick_integer_be(bundle, pos + 0, 4);
|
|
UINT32 this_bundleentry_length = pick_integer_be(bundle, pos + 4, 2) + 1;
|
|
|
|
if (this_bundleentry_type == /* FREF */ 0x46524546)
|
|
{
|
|
fref_pos = pos;
|
|
fref_bundleentry_length = this_bundleentry_length;
|
|
}
|
|
if (this_bundleentry_type == /* ICN# */ 0x49434E23)
|
|
{
|
|
icn_pos = pos;
|
|
icn_bundleentry_length = this_bundleentry_length;
|
|
}
|
|
pos += 6 + this_bundleentry_length * 4;
|
|
}
|
|
if (!fref_pos || !icn_pos)
|
|
goto done;
|
|
|
|
/* look up the FREF */
|
|
for (i = 0; i < fref_bundleentry_length; i++)
|
|
{
|
|
local_id = pick_integer_be(bundle, fref_pos + (i * 4) + 6, 2);
|
|
resource_id = pick_integer_be(bundle, fref_pos + (i * 4) + 8, 2);
|
|
|
|
fref = mac_get_resource(resource_fork, resource_fork_length,
|
|
/* FREF */ 0x46524546, resource_id, &resource_length);
|
|
if (fref && (resource_length >= 7))
|
|
{
|
|
if (pick_integer_be(fref, 0, 4) == type_code)
|
|
break;
|
|
}
|
|
}
|
|
if (i >= fref_bundleentry_length)
|
|
goto done;
|
|
|
|
/* now look up the icon family */
|
|
resource_id = 0;
|
|
for (i = 0; i < icn_bundleentry_length; i++)
|
|
{
|
|
if (pick_integer_be(bundle, icn_pos + (i * 4) + 6, 2) == local_id)
|
|
{
|
|
resource_id = pick_integer_be(bundle, icn_pos + (i * 4) + 8, 2);
|
|
break;
|
|
}
|
|
}
|
|
if (i >= icn_bundleentry_length)
|
|
goto done;
|
|
|
|
/* fetch 32x32 icons (ICN#, icl4, icl8) */
|
|
if (load_icon((UINT32 *) iconinfo->icon32x32, resource_fork, resource_fork_length,
|
|
/* ICN# */ 0x49434E23, resource_id, 32, 32, 1, mac_palette_1bpp, TRUE))
|
|
{
|
|
iconinfo->icon32x32_specified = 1;
|
|
|
|
load_icon((UINT32 *) iconinfo->icon32x32, resource_fork, resource_fork_length,
|
|
/* icl4 */ 0x69636C34, resource_id, 32, 32, 4, mac_palette_4bpp, FALSE);
|
|
load_icon((UINT32 *) iconinfo->icon32x32, resource_fork, resource_fork_length,
|
|
/* icl8 */ 0x69636C38, resource_id, 32, 32, 8, mac_palette_8bpp, FALSE);
|
|
}
|
|
|
|
/* fetch 16x16 icons (ics#, ics4, ics8) */
|
|
if (load_icon((UINT32 *) iconinfo->icon16x16, resource_fork, resource_fork_length,
|
|
/* ics# */ 0x69637323, resource_id, 16, 16, 1, mac_palette_1bpp, TRUE))
|
|
{
|
|
iconinfo->icon16x16_specified = 1;
|
|
|
|
load_icon((UINT32 *) iconinfo->icon32x32, resource_fork, resource_fork_length,
|
|
/* ics4 */ 0x69637334, resource_id, 32, 32, 4, mac_palette_4bpp, FALSE);
|
|
load_icon((UINT32 *) iconinfo->icon32x32, resource_fork, resource_fork_length,
|
|
/* ics8 */ 0x69637338, resource_id, 32, 32, 8, mac_palette_8bpp, FALSE);
|
|
}
|
|
|
|
done:
|
|
if (stream)
|
|
delete stream;
|
|
return err;
|
|
}
|
|
|
|
|
|
|
|
/*************************************
|
|
*
|
|
* File transfer suggestions
|
|
*
|
|
*************************************/
|
|
|
|
static imgtoolerr_t mac_image_suggesttransfer(imgtool::partition *partition, const char *path, imgtool_transfer_suggestion *suggestions, size_t suggestions_length)
|
|
{
|
|
imgtoolerr_t err;
|
|
UINT32 parID;
|
|
mac_str255 filename;
|
|
mac_dirent cat_info;
|
|
imgtool::image &img(partition->image());
|
|
struct mac_l2_imgref *image = get_imgref(img);
|
|
mac_filecategory_t file_category = MAC_FILECATEGORY_DATA;
|
|
|
|
if (path)
|
|
{
|
|
/* resolve path and fetch file info from directory/catalog */
|
|
err = mac_lookup_path(image, path, &parID, filename, &cat_info, FALSE);
|
|
if (err)
|
|
return err;
|
|
if (cat_info.dataRecType != hcrt_File)
|
|
return IMGTOOLERR_FILENOTFOUND;
|
|
|
|
file_category = (cat_info.rsrcLogicalSize > 0) ? MAC_FILECATEGORY_FORKED : MAC_FILECATEGORY_DATA;
|
|
}
|
|
|
|
mac_suggest_transfer(file_category, suggestions, suggestions_length);
|
|
return IMGTOOLERR_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
/*************************************
|
|
*
|
|
* Module population
|
|
*
|
|
*************************************/
|
|
|
|
static void generic_mac_get_info(const imgtool_class *imgclass, UINT32 state, union imgtoolinfo *info)
|
|
{
|
|
switch(state)
|
|
{
|
|
/* --- the following bits of info are returned as 64-bit signed integers --- */
|
|
case IMGTOOLINFO_INT_OPEN_IS_STRICT: info->i = 1; break;
|
|
case IMGTOOLINFO_INT_IMAGE_EXTRA_BYTES: info->i = sizeof(struct mac_l2_imgref); break;
|
|
case IMGTOOLINFO_INT_DIRECTORY_EXTRA_BYTES: info->i = sizeof(struct mac_iterator); break;
|
|
case IMGTOOLINFO_INT_PATH_SEPARATOR: info->i = ':'; break;
|
|
|
|
/* --- the following bits of info are returned as NULL-terminated strings --- */
|
|
case IMGTOOLINFO_STR_FILE: strcpy(info->s = imgtool_temp_str(), __FILE__); break;
|
|
case IMGTOOLINFO_STR_EOLN: strcpy(info->s = imgtool_temp_str(), "\r"); break;
|
|
|
|
/* --- the following bits of info are returned as pointers to data or functions --- */
|
|
case IMGTOOLINFO_PTR_MAKE_CLASS: info->make_class = imgtool_floppy_make_class; break;
|
|
case IMGTOOLINFO_PTR_CLOSE: /* info->close = mac_image_exit */; break;
|
|
case IMGTOOLINFO_PTR_INFO: info->info = mac_image_info; break;
|
|
case IMGTOOLINFO_PTR_BEGIN_ENUM: info->begin_enum = mac_image_beginenum; break;
|
|
case IMGTOOLINFO_PTR_NEXT_ENUM: info->next_enum = mac_image_nextenum; break;
|
|
case IMGTOOLINFO_PTR_FREE_SPACE: info->free_space = mac_image_freespace; break;
|
|
case IMGTOOLINFO_PTR_READ_FILE: info->read_file = mac_image_readfile; break;
|
|
case IMGTOOLINFO_PTR_LIST_FORKS: info->list_forks = mac_image_listforks; break;
|
|
case IMGTOOLINFO_PTR_GET_ATTRS: info->get_attrs = mac_image_getattrs; break;
|
|
case IMGTOOLINFO_PTR_SET_ATTRS: info->set_attrs = mac_image_setattrs; break;
|
|
case IMGTOOLINFO_PTR_GET_ICON_INFO: info->get_iconinfo = mac_image_geticoninfo; break;
|
|
case IMGTOOLINFO_PTR_SUGGEST_TRANSFER: info->suggest_transfer = mac_image_suggesttransfer; break;
|
|
case IMGTOOLINFO_PTR_FLOPPY_FORMAT: info->p = (void *) floppyoptions_apple35_mac; break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void mac_mfs_get_info(const imgtool_class *imgclass, UINT32 state, union imgtoolinfo *info)
|
|
{
|
|
switch(state)
|
|
{
|
|
/* --- the following bits of info are returned as NULL-terminated strings --- */
|
|
case IMGTOOLINFO_STR_NAME: strcpy(info->s = imgtool_temp_str(), "mac_mfs"); break;
|
|
case IMGTOOLINFO_STR_DESCRIPTION: strcpy(info->s = imgtool_temp_str(), "Mac MFS Floppy"); break;
|
|
|
|
/* --- the following bits of info are returned as pointers to data or functions --- */
|
|
case IMGTOOLINFO_PTR_FLOPPY_CREATE: info->create = mfs_image_create; break;
|
|
case IMGTOOLINFO_PTR_FLOPPY_OPEN: info->open = mfs_image_open; break;
|
|
case IMGTOOLINFO_PTR_WRITE_FILE: info->write_file = mac_image_writefile; break;
|
|
|
|
default: generic_mac_get_info(imgclass, state, info); break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void mac_hfs_get_info(const imgtool_class *imgclass, UINT32 state, union imgtoolinfo *info)
|
|
{
|
|
switch(state)
|
|
{
|
|
/* --- the following bits of info are returned as NULL-terminated strings --- */
|
|
case IMGTOOLINFO_STR_NAME: strcpy(info->s = imgtool_temp_str(), "mac_hfs"); break;
|
|
case IMGTOOLINFO_STR_DESCRIPTION: strcpy(info->s = imgtool_temp_str(), "Mac HFS Floppy"); break;
|
|
|
|
/* --- the following bits of info are returned as pointers to data or functions --- */
|
|
case IMGTOOLINFO_PTR_FLOPPY_OPEN: info->open = hfs_image_open; break;
|
|
|
|
default: generic_mac_get_info(imgclass, state, info); break;
|
|
}
|
|
}
|