mirror of
https://github.com/marketideas/qasm.git
synced 2025-04-12 19:37:02 +00:00
building ciderpress libraries with cmake
This commit is contained in:
parent
e74f463a1e
commit
9c66992c23
1
app_config.h
Symbolic link
1
app_config.h
Symbolic link
@ -0,0 +1 @@
|
||||
config.h
|
@ -862,15 +862,8 @@ static NuError Nu_OpenTempFile(UNICHAR* fileNameUNI, FILE** pFp)
|
||||
#else
|
||||
char* result;
|
||||
|
||||
#if 1
|
||||
DBUG(("+++ Using mktemp\n"));
|
||||
result = mktemp(fileNameUNI);
|
||||
#else
|
||||
char fbuff[256];
|
||||
sprintf(fbuff,"%s%s",fileNameUNI,"XXXXXX");
|
||||
result=mkstemp();
|
||||
#endif
|
||||
|
||||
if (result == NULL) {
|
||||
Nu_ReportError(NU_BLOB, kNuErrNone, "mktemp failed on '%s'",
|
||||
fileNameUNI);
|
||||
|
29
ciderpress/nufxlib/COPYING-LIB
Normal file
29
ciderpress/nufxlib/COPYING-LIB
Normal file
@ -0,0 +1,29 @@
|
||||
Copyright (C) 2007, Andy McFadden.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of project
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
183
ciderpress/nufxlib/INSTALL
Normal file
183
ciderpress/nufxlib/INSTALL
Normal file
@ -0,0 +1,183 @@
|
||||
Basic Installation
|
||||
==================
|
||||
|
||||
These are generic installation instructions.
|
||||
|
||||
The `configure' shell script attempts to guess correct values for
|
||||
various system-dependent variables used during compilation. It uses
|
||||
those values to create a `Makefile' in each directory of the package.
|
||||
It may also create one or more `.h' files containing system-dependent
|
||||
definitions. Finally, it creates a shell script `config.status' that
|
||||
you can run in the future to recreate the current configuration, a file
|
||||
`config.cache' that saves the results of its tests to speed up
|
||||
reconfiguring, and a file `config.log' containing compiler output
|
||||
(useful mainly for debugging `configure').
|
||||
|
||||
If you need to do unusual things to compile the package, please try
|
||||
to figure out how `configure' could check whether to do them, and mail
|
||||
diffs or instructions to the address given in the `README' so they can
|
||||
be considered for the next release. If at some point `config.cache'
|
||||
contains results you don't want to keep, you may remove or edit it.
|
||||
|
||||
The file `configure.in' is used to create `configure' by a program
|
||||
called `autoconf'. You only need `configure.in' if you want to change
|
||||
it or regenerate `configure' using a newer version of `autoconf'.
|
||||
|
||||
The simplest way to compile this package is:
|
||||
|
||||
1. `cd' to the directory containing the package's source code and type
|
||||
`./configure' to configure the package for your system. If you're
|
||||
using `csh' on an old version of System V, you might need to type
|
||||
`sh ./configure' instead to prevent `csh' from trying to execute
|
||||
`configure' itself.
|
||||
|
||||
Running `configure' takes awhile. While running, it prints some
|
||||
messages telling which features it is checking for.
|
||||
|
||||
2. Type `make' to compile the package.
|
||||
|
||||
3. Optionally, type `make check' to run any self-tests that come with
|
||||
the package.
|
||||
|
||||
4. Type `make install' to install the programs and any data files and
|
||||
documentation.
|
||||
|
||||
5. You can remove the program binaries and object files from the
|
||||
source code directory by typing `make clean'. To also remove the
|
||||
files that `configure' created (so you can compile the package for
|
||||
a different kind of computer), type `make distclean'. There is
|
||||
also a `make maintainer-clean' target, but that is intended mainly
|
||||
for the package's developers. If you use it, you may have to get
|
||||
all sorts of other programs in order to regenerate files that came
|
||||
with the distribution.
|
||||
|
||||
Compilers and Options
|
||||
=====================
|
||||
|
||||
Some systems require unusual options for compilation or linking that
|
||||
the `configure' script does not know about. You can give `configure'
|
||||
initial values for variables by setting them in the environment. Using
|
||||
a Bourne-compatible shell, you can do that on the command line like
|
||||
this:
|
||||
CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
|
||||
|
||||
Or on systems that have the `env' program, you can do it like this:
|
||||
env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure
|
||||
|
||||
Compiling For Multiple Architectures
|
||||
====================================
|
||||
|
||||
You can compile the package for more than one kind of computer at the
|
||||
same time, by placing the object files for each architecture in their
|
||||
own directory. To do this, you must use a version of `make' that
|
||||
supports the `VPATH' variable, such as GNU `make'. `cd' to the
|
||||
directory where you want the object files and executables to go and run
|
||||
the `configure' script. `configure' automatically checks for the
|
||||
source code in the directory that `configure' is in and in `..'.
|
||||
|
||||
If you have to use a `make' that does not supports the `VPATH'
|
||||
variable, you have to compile the package for one architecture at a time
|
||||
in the source code directory. After you have installed the package for
|
||||
one architecture, use `make distclean' before reconfiguring for another
|
||||
architecture.
|
||||
|
||||
Installation Names
|
||||
==================
|
||||
|
||||
By default, `make install' will install the package's files in
|
||||
`/usr/local/bin', `/usr/local/man', etc. You can specify an
|
||||
installation prefix other than `/usr/local' by giving `configure' the
|
||||
option `--prefix=PATH'.
|
||||
|
||||
You can specify separate installation prefixes for
|
||||
architecture-specific files and architecture-independent files. If you
|
||||
give `configure' the option `--exec-prefix=PATH', the package will use
|
||||
PATH as the prefix for installing programs and libraries.
|
||||
Documentation and other data files will still use the regular prefix.
|
||||
|
||||
In addition, if you use an unusual directory layout you can give
|
||||
options like `--bindir=PATH' to specify different values for particular
|
||||
kinds of files. Run `configure --help' for a list of the directories
|
||||
you can set and what kinds of files go in them.
|
||||
|
||||
If the package supports it, you can cause programs to be installed
|
||||
with an extra prefix or suffix on their names by giving `configure' the
|
||||
option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
|
||||
|
||||
Optional Features
|
||||
=================
|
||||
|
||||
Some packages pay attention to `--enable-FEATURE' options to
|
||||
`configure', where FEATURE indicates an optional part of the package.
|
||||
They may also pay attention to `--with-PACKAGE' options, where PACKAGE
|
||||
is something like `gnu-as' or `x' (for the X Window System). The
|
||||
`README' should mention any `--enable-' and `--with-' options that the
|
||||
package recognizes.
|
||||
|
||||
For packages that use the X Window System, `configure' can usually
|
||||
find the X include and library files automatically, but if it doesn't,
|
||||
you can use the `configure' options `--x-includes=DIR' and
|
||||
`--x-libraries=DIR' to specify their locations.
|
||||
|
||||
Specifying the System Type
|
||||
==========================
|
||||
|
||||
There may be some features `configure' can not figure out
|
||||
automatically, but needs to determine by the type of host the package
|
||||
will run on. Usually `configure' can figure that out, but if it prints
|
||||
a message saying it can not guess the host type, give it the
|
||||
`--host=TYPE' option. TYPE can either be a short name for the system
|
||||
type, such as `sun4', or a canonical name with three fields:
|
||||
CPU-COMPANY-SYSTEM
|
||||
|
||||
See the file `config.sub' for the possible values of each field. If
|
||||
`config.sub' isn't included in this package, then this package doesn't
|
||||
need to know the host type.
|
||||
|
||||
If you are building compiler tools for cross-compiling, you can also
|
||||
use the `--target=TYPE' option to select the type of system they will
|
||||
produce code for and the `--build=TYPE' option to select the type of
|
||||
system on which you are compiling the package.
|
||||
|
||||
Sharing Defaults
|
||||
================
|
||||
|
||||
If you want to set default values for `configure' scripts to share,
|
||||
you can create a site shell script called `config.site' that gives
|
||||
default values for variables like `CC', `cache_file', and `prefix'.
|
||||
`configure' looks for `PREFIX/share/config.site' if it exists, then
|
||||
`PREFIX/etc/config.site' if it exists. Or, you can set the
|
||||
`CONFIG_SITE' environment variable to the location of the site script.
|
||||
A warning: not all `configure' scripts look for a site script.
|
||||
|
||||
Operation Controls
|
||||
==================
|
||||
|
||||
`configure' recognizes the following options to control how it
|
||||
operates.
|
||||
|
||||
`--cache-file=FILE'
|
||||
Use and save the results of the tests in FILE instead of
|
||||
`./config.cache'. Set FILE to `/dev/null' to disable caching, for
|
||||
debugging `configure'.
|
||||
|
||||
`--help'
|
||||
Print a summary of the options to `configure', and exit.
|
||||
|
||||
`--quiet'
|
||||
`--silent'
|
||||
`-q'
|
||||
Do not print messages saying which checks are being made. To
|
||||
suppress all normal output, redirect it to `/dev/null' (any error
|
||||
messages will still be shown).
|
||||
|
||||
`--srcdir=DIR'
|
||||
Look for the package's source code in directory DIR. Usually
|
||||
`configure' can determine that directory automatically.
|
||||
|
||||
`--version'
|
||||
Print the version of Autoconf used to generate the `configure'
|
||||
script, and exit.
|
||||
|
||||
`configure' also accepts some other, not widely useful, options.
|
||||
|
@ -1,15 +1,141 @@
|
||||
.SILENT:
|
||||
export CC=gcc
|
||||
export CXX=g++
|
||||
#
|
||||
# NuFX archive manipulation library
|
||||
# Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved.
|
||||
# This is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the BSD License, see the file COPYING-LIB.
|
||||
#
|
||||
# Makefile for nufxlib (should work with non-GNU "make").
|
||||
#
|
||||
# You can use:
|
||||
# make (builds library and sample applications)
|
||||
# make shared (builds shared library if you're using GNU ld or similar)
|
||||
#
|
||||
# The shared library support currently leaves much to be desired.
|
||||
#
|
||||
# If you build with -DDEBUG_MSGS, nulib2 will be able to use the hidden
|
||||
# 'g' command, which generates a verbose archive dump for debugging.
|
||||
#
|
||||
|
||||
.PHONY: clean
|
||||
# NufxLib install location.
|
||||
prefix = /usr/local
|
||||
exec_prefix = ${prefix}
|
||||
includedir = ${prefix}/include
|
||||
libdir = ${exec_prefix}/lib
|
||||
srcdir = .
|
||||
|
||||
all: nufx
|
||||
SHELL = /bin/sh
|
||||
INSTALL = /usr/bin/install -c
|
||||
INSTALL_PROGRAM = ${INSTALL}
|
||||
INSTALL_DATA = ${INSTALL} -m 644
|
||||
CC = gcc
|
||||
AR = ar rcv
|
||||
#OPT = -g -O2 -DNDEBUG
|
||||
OPT = -g -O2
|
||||
#OPT = -g -O2 -DDEBUG_MSGS
|
||||
#OPT = -g -O2 -DDEBUG_VERBOSE
|
||||
GCC_FLAGS = -Wall -Wwrite-strings -Wstrict-prototypes -Wpointer-arith -Wshadow
|
||||
CFLAGS = $(OPT) $(GCC_FLAGS) -I. -DHAVE_CONFIG_H -DOPTFLAGSTR="\"$(OPT)\""
|
||||
|
||||
nufx:
|
||||
-mkdir -p ./build
|
||||
cd ./build && cmake ..
|
||||
cd ./build && $(MAKE)
|
||||
SRCS = Archive.c ArchiveIO.c Bzip2.c Charset.c Compress.c Crc16.c \
|
||||
Debug.c Deferred.c Deflate.c Entry.c Expand.c FileIO.c Funnel.c \
|
||||
Lzc.c Lzw.c MiscStuff.c MiscUtils.c Record.c SourceSink.c \
|
||||
Squeeze.c Thread.c Value.c Version.c
|
||||
OBJS = Archive.o ArchiveIO.o Bzip2.o Charset.o Compress.o Crc16.o \
|
||||
Debug.o Deferred.o Deflate.o Entry.o Expand.o FileIO.o Funnel.o \
|
||||
Lzc.o Lzw.o MiscStuff.o MiscUtils.o Record.o SourceSink.o \
|
||||
Squeeze.o Thread.o Value.o Version.o
|
||||
|
||||
STATIC_PRODUCT = libnufx.a
|
||||
SHARED_PRODUCT = libnufx.so
|
||||
PRODUCT = $(STATIC_PRODUCT)
|
||||
|
||||
|
||||
#
|
||||
# Build stuff
|
||||
#
|
||||
|
||||
all: $(PRODUCT) samples
|
||||
@true
|
||||
|
||||
install: $(STATIC_PRODUCT)
|
||||
$(srcdir)/mkinstalldirs $(libdir)
|
||||
$(INSTALL_DATA) $(STATIC_PRODUCT) $(libdir)
|
||||
$(srcdir)/mkinstalldirs $(includedir)
|
||||
$(INSTALL_DATA) NufxLib.h $(includedir)
|
||||
|
||||
install-shared: $(SHARED_PRODUCT)
|
||||
$(srcdir)/mkinstalldirs $(libdir)
|
||||
$(INSTALL_DATA) $(SHARED_PRODUCT) $(libdir)
|
||||
$(srcdir)/mkinstalldirs $(includedir)
|
||||
$(INSTALL_DATA) NufxLib.h $(includedir)
|
||||
|
||||
samples::
|
||||
@echo "Building samples..."
|
||||
@(cd samples; set +e; unset CFLAGS OBJS; set -e; \
|
||||
LIB_PRODUCT="../$(PRODUCT)" $(MAKE))
|
||||
|
||||
shared::
|
||||
PRODUCT="$(SHARED_PRODUCT)" $(MAKE) -e
|
||||
|
||||
$(STATIC_PRODUCT): $(OBJS)
|
||||
-rm -f $(STATIC_PRODUCT) $(SHARED_PRODUCT)
|
||||
$(AR) $@ $(OBJS)
|
||||
ranlib $@
|
||||
|
||||
# BUG: we need -fPIC, maybe -D_REENTRANT when compiling for this.
|
||||
# BUG: for Linux we may want -Wl,-soname,libnufx.so.1 on the link line.
|
||||
$(SHARED_PRODUCT): $(OBJS)
|
||||
-rm -f $(STATIC_PRODUCT) $(SHARED_PRODUCT)
|
||||
$(CC) -shared -o $@ $(OBJS) -lz
|
||||
|
||||
clean:
|
||||
-rm -rf ./build
|
||||
(cd samples; make clean)
|
||||
-rm -f *.o core
|
||||
-rm -f $(SHARED_PRODUCT) $(STATIC_PRODUCT)
|
||||
|
||||
# build tags; assumes fancy GNU tag generation
|
||||
tags::
|
||||
@ctags -R --totals *
|
||||
@#ctags *.[ch]
|
||||
|
||||
distclean: clean
|
||||
(cd samples; make distclean)
|
||||
-rm -f Makefile Makefile.bak
|
||||
-rm -f config.log config.cache config.status config.h
|
||||
-rm -f tags
|
||||
|
||||
# Make a tarfile with a backup of the essential files. We include "Makefile"
|
||||
# so that we can do a "make distclean" during packaging.
|
||||
baktar:
|
||||
@tar cvf nufxlib.tar *.txt COPYING-LIB INSTALL configure *.in Makefile \
|
||||
Makefile.msc Makefile.dll install-sh config.guess config.sub \
|
||||
mkinstalldirs *.[ch] samples/*.txt samples/Makefile* samples/*.[ch]
|
||||
@gzip -9 nufxlib.tar
|
||||
@mv -i nufxlib.tar.gz /home/fadden/BAK/
|
||||
|
||||
# dependency info
|
||||
COMMON_HDRS = NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||
Archive.o: Archive.c $(COMMON_HDRS)
|
||||
ArchiveIO.o: ArchiveIO.c $(COMMON_HDRS)
|
||||
Bzip2.o: Bzip2.c $(COMMON_HDRS)
|
||||
Charset.o: Charset.c $(COMMON_HDRS)
|
||||
Compress.o: Compress.c $(COMMON_HDRS)
|
||||
Crc16.o: Crc16.c $(COMMON_HDRS)
|
||||
Debug.o: Debug.c $(COMMON_HDRS)
|
||||
Deferred.o: Deferred.c $(COMMON_HDRS)
|
||||
Deflate.o: Deflate.c $(COMMON_HDRS)
|
||||
Entry.o: Entry.c $(COMMON_HDRS)
|
||||
Expand.o: Expand.c $(COMMON_HDRS)
|
||||
FileIO.o: FileIO.c $(COMMON_HDRS)
|
||||
Funnel.o: Funnel.c $(COMMON_HDRS)
|
||||
Lzc.o: Lzc.c $(COMMON_HDRS)
|
||||
Lzw.o: Lzw.c $(COMMON_HDRS)
|
||||
MiscStuff.o: MiscStuff.c $(COMMON_HDRS)
|
||||
MiscUtils.o: MiscUtils.c $(COMMON_HDRS)
|
||||
Record.o: Record.c $(COMMON_HDRS)
|
||||
SourceSink.o: SourceSink.c $(COMMON_HDRS)
|
||||
Squeeze.o: Squeeze.c $(COMMON_HDRS)
|
||||
Thread.o: Thread.c $(COMMON_HDRS)
|
||||
Value.o: Value.c $(COMMON_HDRS)
|
||||
Version.o: Version.c $(COMMON_HDRS) Makefile
|
||||
|
||||
|
141
ciderpress/nufxlib/Makefile.in
Normal file
141
ciderpress/nufxlib/Makefile.in
Normal file
@ -0,0 +1,141 @@
|
||||
#
|
||||
# NuFX archive manipulation library
|
||||
# Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved.
|
||||
# This is free software; you can redistribute it and/or modify it under the
|
||||
# terms of the BSD License, see the file COPYING-LIB.
|
||||
#
|
||||
# Makefile for nufxlib (should work with non-GNU "make").
|
||||
#
|
||||
# You can use:
|
||||
# make (builds library and sample applications)
|
||||
# make shared (builds shared library if you're using GNU ld or similar)
|
||||
#
|
||||
# The shared library support currently leaves much to be desired.
|
||||
#
|
||||
# If you build with -DDEBUG_MSGS, nulib2 will be able to use the hidden
|
||||
# 'g' command, which generates a verbose archive dump for debugging.
|
||||
#
|
||||
|
||||
# NufxLib install location.
|
||||
prefix = @prefix@
|
||||
exec_prefix = @exec_prefix@
|
||||
includedir = @includedir@
|
||||
libdir = @libdir@
|
||||
srcdir = @srcdir@
|
||||
|
||||
SHELL = @SHELL@
|
||||
INSTALL = @INSTALL@
|
||||
INSTALL_PROGRAM = @INSTALL_PROGRAM@
|
||||
INSTALL_DATA = @INSTALL_DATA@
|
||||
CC = @CC@
|
||||
AR = ar rcv
|
||||
#OPT = @CFLAGS@ -DNDEBUG
|
||||
OPT = @CFLAGS@
|
||||
#OPT = @CFLAGS@ -DDEBUG_MSGS
|
||||
#OPT = @CFLAGS@ -DDEBUG_VERBOSE
|
||||
GCC_FLAGS = -Wall -Wwrite-strings -Wstrict-prototypes -Wpointer-arith -Wshadow
|
||||
CFLAGS = @BUILD_FLAGS@ -I. @DEFS@ -DOPTFLAGSTR="\"$(OPT)\""
|
||||
|
||||
SRCS = Archive.c ArchiveIO.c Bzip2.c Charset.c Compress.c Crc16.c \
|
||||
Debug.c Deferred.c Deflate.c Entry.c Expand.c FileIO.c Funnel.c \
|
||||
Lzc.c Lzw.c MiscStuff.c MiscUtils.c Record.c SourceSink.c \
|
||||
Squeeze.c Thread.c Value.c Version.c
|
||||
OBJS = Archive.o ArchiveIO.o Bzip2.o Charset.o Compress.o Crc16.o \
|
||||
Debug.o Deferred.o Deflate.o Entry.o Expand.o FileIO.o Funnel.o \
|
||||
Lzc.o Lzw.o MiscStuff.o MiscUtils.o Record.o SourceSink.o \
|
||||
Squeeze.o Thread.o Value.o Version.o
|
||||
|
||||
STATIC_PRODUCT = libnufx.a
|
||||
SHARED_PRODUCT = libnufx.so
|
||||
PRODUCT = $(STATIC_PRODUCT)
|
||||
|
||||
|
||||
#
|
||||
# Build stuff
|
||||
#
|
||||
|
||||
all: $(PRODUCT) samples
|
||||
@true
|
||||
|
||||
install: $(STATIC_PRODUCT)
|
||||
$(srcdir)/mkinstalldirs $(libdir)
|
||||
$(INSTALL_DATA) $(STATIC_PRODUCT) $(libdir)
|
||||
$(srcdir)/mkinstalldirs $(includedir)
|
||||
$(INSTALL_DATA) NufxLib.h $(includedir)
|
||||
|
||||
install-shared: $(SHARED_PRODUCT)
|
||||
$(srcdir)/mkinstalldirs $(libdir)
|
||||
$(INSTALL_DATA) $(SHARED_PRODUCT) $(libdir)
|
||||
$(srcdir)/mkinstalldirs $(includedir)
|
||||
$(INSTALL_DATA) NufxLib.h $(includedir)
|
||||
|
||||
samples::
|
||||
@echo "Building samples..."
|
||||
@(cd samples; set +e; unset CFLAGS OBJS; set -e; \
|
||||
@SET_MAKE@ LIB_PRODUCT="../$(PRODUCT)" $(MAKE))
|
||||
|
||||
shared::
|
||||
PRODUCT="$(SHARED_PRODUCT)" $(MAKE) -e
|
||||
|
||||
$(STATIC_PRODUCT): $(OBJS)
|
||||
-rm -f $(STATIC_PRODUCT) $(SHARED_PRODUCT)
|
||||
$(AR) $@ $(OBJS)
|
||||
@RANLIB@ $@
|
||||
|
||||
# BUG: we need -fPIC, maybe -D_REENTRANT when compiling for this.
|
||||
# BUG: for Linux we may want -Wl,-soname,libnufx.so.1 on the link line.
|
||||
$(SHARED_PRODUCT): $(OBJS)
|
||||
-rm -f $(STATIC_PRODUCT) $(SHARED_PRODUCT)
|
||||
$(CC) @SHARE_FLAGS@ -o $@ $(OBJS) @LIBS@
|
||||
|
||||
clean:
|
||||
(cd samples; make clean)
|
||||
-rm -f *.o core
|
||||
-rm -f $(SHARED_PRODUCT) $(STATIC_PRODUCT)
|
||||
|
||||
# build tags; assumes fancy GNU tag generation
|
||||
tags::
|
||||
@ctags -R --totals *
|
||||
@#ctags *.[ch]
|
||||
|
||||
distclean: clean
|
||||
(cd samples; make distclean)
|
||||
-rm -f Makefile Makefile.bak
|
||||
-rm -f config.log config.cache config.status config.h
|
||||
-rm -f tags
|
||||
|
||||
# Make a tarfile with a backup of the essential files. We include "Makefile"
|
||||
# so that we can do a "make distclean" during packaging.
|
||||
baktar:
|
||||
@tar cvf nufxlib.tar *.txt COPYING-LIB INSTALL configure *.in Makefile \
|
||||
Makefile.msc Makefile.dll install-sh config.guess config.sub \
|
||||
mkinstalldirs *.[ch] samples/*.txt samples/Makefile* samples/*.[ch]
|
||||
@gzip -9 nufxlib.tar
|
||||
@mv -i nufxlib.tar.gz /home/fadden/BAK/
|
||||
|
||||
# dependency info
|
||||
COMMON_HDRS = NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||
Archive.o: Archive.c $(COMMON_HDRS)
|
||||
ArchiveIO.o: ArchiveIO.c $(COMMON_HDRS)
|
||||
Bzip2.o: Bzip2.c $(COMMON_HDRS)
|
||||
Charset.o: Charset.c $(COMMON_HDRS)
|
||||
Compress.o: Compress.c $(COMMON_HDRS)
|
||||
Crc16.o: Crc16.c $(COMMON_HDRS)
|
||||
Debug.o: Debug.c $(COMMON_HDRS)
|
||||
Deferred.o: Deferred.c $(COMMON_HDRS)
|
||||
Deflate.o: Deflate.c $(COMMON_HDRS)
|
||||
Entry.o: Entry.c $(COMMON_HDRS)
|
||||
Expand.o: Expand.c $(COMMON_HDRS)
|
||||
FileIO.o: FileIO.c $(COMMON_HDRS)
|
||||
Funnel.o: Funnel.c $(COMMON_HDRS)
|
||||
Lzc.o: Lzc.c $(COMMON_HDRS)
|
||||
Lzw.o: Lzw.c $(COMMON_HDRS)
|
||||
MiscStuff.o: MiscStuff.c $(COMMON_HDRS)
|
||||
MiscUtils.o: MiscUtils.c $(COMMON_HDRS)
|
||||
Record.o: Record.c $(COMMON_HDRS)
|
||||
SourceSink.o: SourceSink.c $(COMMON_HDRS)
|
||||
Squeeze.o: Squeeze.c $(COMMON_HDRS)
|
||||
Thread.o: Thread.c $(COMMON_HDRS)
|
||||
Value.o: Value.c $(COMMON_HDRS)
|
||||
Version.o: Version.c $(COMMON_HDRS) Makefile
|
||||
|
160
ciderpress/nufxlib/Makefile.msc
Normal file
160
ciderpress/nufxlib/Makefile.msc
Normal file
@ -0,0 +1,160 @@
|
||||
# Makefile for NufxLib using Microsoft Visual C++. This builds the library
|
||||
# as a static lib and as a DLL, and builds all samples. The test-basic
|
||||
# sample is built twice, once with the static lib, and once with the DLL.
|
||||
#
|
||||
# Tested with VS 2013 Pro. From the "VS2013 x86 Native Tools Command
|
||||
# Prompt", run "nmake -f makefile.msc".
|
||||
#
|
||||
# If you're including zlib support, place copies of zlib.h, zconf.h,
|
||||
# and the zlib library in this directory.
|
||||
#
|
||||
# Adapted from zlib's Makefile.msc.
|
||||
#
|
||||
|
||||
TOP = .
|
||||
|
||||
STATICLIB = nufxlib2.lib
|
||||
SHAREDLIB = nufxlib2.dll
|
||||
IMPLIB = nufxdll.lib
|
||||
|
||||
CC = cl
|
||||
LD = link
|
||||
AR = lib
|
||||
|
||||
# C compiler flags
|
||||
# -Fd: rename PDB file from "VCx0.pdb" (where 'x' is the version number);
|
||||
# allows DLL debug info to be separate from app debug info
|
||||
# -Ox: full optimization
|
||||
# -Oy-: disable frame pointer omission (for easier debugging)
|
||||
# -MD: create a multithreaded DLL using MSVCRT.lib; alternatively,
|
||||
# use -MDd to create a debug executable with MSVCRTD.lib
|
||||
# -nologo: suppress display of copyright banner
|
||||
# -W3: set warning level to 3 (all production-level warnings)
|
||||
# -Zi: generate a PDB file with full debugging info
|
||||
#
|
||||
# The OPTFLAGSTR define is used by Version.c to show how the library was
|
||||
# built. Defining NUFXLIB_EXPORTS enables the __declspec(dllexport)
|
||||
# macros that are required for creating the DLL.
|
||||
OPTFLAGS = -Ox -Oy-
|
||||
CFLAGS = -nologo -MD -W3 $(OPTFLAGS) -Zi -Fd"nufxlib"
|
||||
|
||||
LIB_CFLAGS = -DOPTFLAGSTR="\"$(OPTFLAGS)\"" #-DNUFXLIB_EXPORTS
|
||||
|
||||
# Warning suppression flags
|
||||
WFLAGS = -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE
|
||||
|
||||
# Linker flags
|
||||
# -debug: creates debugging info for EXE or DLL in PDB file
|
||||
# -incremental:no: disable incremental linking, making the resulting library
|
||||
# a tad smaller
|
||||
# -nologo: suppress display of copyright banner
|
||||
# -opt:ref: eliminates unreferenced functions and data (default for non-debug
|
||||
# builds, but we've enabled debug info)
|
||||
LDFLAGS = -nologo -debug -incremental:no -opt:ref
|
||||
|
||||
# Library creator flags
|
||||
ARFLAGS = -nologo
|
||||
|
||||
|
||||
ZLIB=1
|
||||
!ifdef ZLIB
|
||||
# enable deflate support; requires zlib
|
||||
CFLAGS = $(CFLAGS) -DENABLE_DEFLATE
|
||||
LDFLAGS = $(LDFLAGS) zlib.lib
|
||||
!endif
|
||||
|
||||
|
||||
# object files
|
||||
OBJS = Archive.obj ArchiveIO.obj Bzip2.obj Charset.obj Compress.obj \
|
||||
Crc16.obj Debug.obj Deferred.obj Deflate.obj Entry.obj Expand.obj \
|
||||
FileIO.obj Funnel.obj Lzc.obj Lzw.obj MiscStuff.obj MiscUtils.obj \
|
||||
Record.obj SourceSink.obj Squeeze.obj Thread.obj Value.obj Version.obj
|
||||
|
||||
|
||||
# build targets -- static library, dynamic library, and test programs
|
||||
all: $(STATICLIB) $(SHAREDLIB) $(IMPLIB) \
|
||||
exerciser.exe imgconv.exe launder.exe test-basic.exe test-basic-d.exe \
|
||||
test-extract.exe test-names.exe test-simple.exe test-twirl.exe
|
||||
|
||||
clean:
|
||||
-del *.obj *.pdb *.exp
|
||||
-del $(STATICLIB) $(SHAREDLIB) $(IMPLIB)
|
||||
|
||||
$(STATICLIB): $(OBJS)
|
||||
$(AR) $(ARFLAGS) -out:$@ $(OBJS)
|
||||
|
||||
$(IMPLIB): $(SHAREDLIB)
|
||||
|
||||
$(SHAREDLIB): $(OBJS)
|
||||
$(LD) $(LDFLAGS) -dll -def:nufxlib.def -implib:$(IMPLIB) -out:$@ \
|
||||
$(OBJS)
|
||||
|
||||
exerciser.exe: Exerciser.obj $(STATICLIB)
|
||||
$(LD) $(LDFLAGS) -out:$@ Exerciser.obj $(STATICLIB)
|
||||
|
||||
imgconv.exe: ImgConv.obj $(STATICLIB)
|
||||
$(LD) $(LDFLAGS) -out:$@ ImgConv.obj $(STATICLIB)
|
||||
|
||||
launder.exe: Launder.obj $(STATICLIB)
|
||||
$(LD) $(LDFLAGS) -out:$@ Launder.obj $(STATICLIB)
|
||||
|
||||
test-basic.exe: TestBasic.obj $(STATICLIB)
|
||||
$(LD) $(LDFLAGS) -out:$@ TestBasic.obj $(STATICLIB)
|
||||
|
||||
test-basic-d.exe: TestBasic.obj $(IMPLIB)
|
||||
$(LD) $(LDFLAGS) -out:$@ TestBasic.obj $(IMPLIB)
|
||||
|
||||
test-extract.exe: TestExtract.obj $(STATICLIB)
|
||||
$(LD) $(LDFLAGS) -out:$@ TestExtract.obj $(STATICLIB)
|
||||
|
||||
test-names.exe: TestNames.obj $(STATICLIB)
|
||||
$(LD) $(LDFLAGS) -out:$@ TestNames.obj $(STATICLIB)
|
||||
|
||||
test-simple.exe: TestSimple.obj $(STATICLIB)
|
||||
$(LD) $(LDFLAGS) -out:$@ TestSimple.obj $(STATICLIB)
|
||||
|
||||
test-twirl.exe: TestTwirl.obj $(STATICLIB)
|
||||
$(LD) $(LDFLAGS) -out:$@ TestTwirl.obj $(STATICLIB)
|
||||
|
||||
# generic rules
|
||||
{$(TOP)}.c.obj:
|
||||
$(CC) -c $(WFLAGS) $(CFLAGS) $(LIB_CFLAGS) $<
|
||||
|
||||
{$(TOP)/samples}.c.obj:
|
||||
$(CC) -c -I$(TOP) $(WFLAGS) $(CFLAGS) $<
|
||||
|
||||
# dependency info
|
||||
COMMON_HDRS = NufxLibPriv.h NufxLib.h MiscStuff.h SysDefs.h
|
||||
Archive.obj: Archive.c $(COMMON_HDRS)
|
||||
ArchiveIO.obj: ArchiveIO.c $(COMMON_HDRS)
|
||||
Bzip2.obj: Bzip2.c $(COMMON_HDRS)
|
||||
Charset.obj: Charset.c $(COMMON_HDRS)
|
||||
Compress.obj: Compress.c $(COMMON_HDRS)
|
||||
Crc16.obj: Crc16.c $(COMMON_HDRS)
|
||||
Debug.obj: Debug.c $(COMMON_HDRS)
|
||||
Deferred.obj: Deferred.c $(COMMON_HDRS)
|
||||
Deflate.obj: Deflate.c $(COMMON_HDRS)
|
||||
Entry.obj: Entry.c $(COMMON_HDRS)
|
||||
Expand.obj: Expand.c $(COMMON_HDRS)
|
||||
FileIO.obj: FileIO.c $(COMMON_HDRS)
|
||||
Funnel.obj: Funnel.c $(COMMON_HDRS)
|
||||
Lzc.obj: Lzc.c $(COMMON_HDRS)
|
||||
Lzw.obj: Lzw.c $(COMMON_HDRS)
|
||||
MiscStuff.obj: MiscStuff.c $(COMMON_HDRS)
|
||||
MiscUtils.obj: MiscUtils.c $(COMMON_HDRS)
|
||||
Record.obj: Record.c $(COMMON_HDRS)
|
||||
SourceSink.obj: SourceSink.c $(COMMON_HDRS)
|
||||
Squeeze.obj: Squeeze.c $(COMMON_HDRS)
|
||||
Thread.obj: Thread.c $(COMMON_HDRS)
|
||||
Value.obj: Value.c $(COMMON_HDRS)
|
||||
Version.obj: Version.c $(COMMON_HDRS)
|
||||
|
||||
Exerciser.obj: samples/Exerciser.c $(COMMON_HDRS)
|
||||
ImgConv.obj: samples/ImgConv.c $(COMMON_HDRS)
|
||||
Launder.obj: samples/Launder.c $(COMMON_HDRS)
|
||||
TestBasic.obj: samples/TestBasic.c $(COMMON_HDRS)
|
||||
TestExtract.obj: samples/TestExtract.c $(COMMON_HDRS)
|
||||
TestNames.obj: samples/TestNames.c $(COMMON_HDRS)
|
||||
TestSimple.obj: samples/TestSimple.c $(COMMON_HDRS)
|
||||
TestTwirl.obj: samples/TestTwirl.c $(COMMON_HDRS)
|
||||
|
339
ciderpress/nufxlib/NOTES.md
Normal file
339
ciderpress/nufxlib/NOTES.md
Normal file
@ -0,0 +1,339 @@
|
||||
NufxLib NOTES
|
||||
=============
|
||||
Last revised: 2015/01/04
|
||||
|
||||
|
||||
The interface is documented in "nufxlibapi.html", available from the
|
||||
http://www.nulib.com/ web site. This discusses some of the internal
|
||||
design that may be of interest.
|
||||
|
||||
Some familiarity with the NuFX file format is assumed.
|
||||
|
||||
- - -
|
||||
|
||||
### Read-Write Data Structures ###
|
||||
|
||||
For both read-only and read-write files (but not streaming read-only files),
|
||||
the archive is represented internally as a linked list of Records, each
|
||||
of which has an array of Threads attached. No attempt is made to
|
||||
optimize searches by filename, so use of the "replace existing entry when
|
||||
filenames match" option should be restricted to situations where it is
|
||||
necessary. Otherwise, O(N^2) behavior can result.
|
||||
|
||||
Modifications, such as deletions, changes to filename threads, and
|
||||
additions of new records, are queued up in a separate list until a NuFlush
|
||||
call is issued. The list works much the same way as the temporary file:
|
||||
when the operation completes, the "new" list becomes the "original" list.
|
||||
If the operation is aborted, the "new" list is scrubbed, and the "original"
|
||||
list remains unmodified.
|
||||
|
||||
Just as it is inefficient to write data to the temp file when it's not
|
||||
necessary to do so, it is inefficient to allocate a complete copy of the
|
||||
records from the original list if none are changed. As a result, there are
|
||||
actually two "new" lists, one with a copy of the original record list, and
|
||||
one with new additions. The "copy" list starts out uninitialized, and
|
||||
remains that way until one of the entries from the original list is
|
||||
modified. When that happens, the entire original list is duplicated, and
|
||||
the changes are made directly to members of the "copy" list. (This is
|
||||
important for really large archives, like a by-file archive with the
|
||||
entire contents of a hard drive, where the record index could be several
|
||||
megabytes in size.)
|
||||
|
||||
It would be more *memory* efficient to simply maintain a list of what
|
||||
has changed. However, we can't disturb the "original" list in any way or
|
||||
we lose the ability to roll back quickly if the operation is aborted.
|
||||
Consequently, we need to create a new list of records that reflects
|
||||
the state of the new archive, so that when we rename the temp file over
|
||||
the original, we can simply "rename" the new record list over the original.
|
||||
Since we're going to need the new list eventually, we might as well create
|
||||
it as soon as it is needed, and deal with memory allocation failures up
|
||||
front rather than during the update process. (Some items, such as the
|
||||
record's file offset in the archive, have to be updated even for records
|
||||
that aren't themselves changing... which means we potentially need to
|
||||
modify all existing record structures, so we need a complete copy of the
|
||||
record list regardless of how little or how much has changed.)
|
||||
|
||||
This also ties into the "modify original archive file directly if possible"
|
||||
option, which avoids the need for creating and renaming a temp file. If
|
||||
the only changes are updates to pre-sized records (e.g. renaming a file
|
||||
inside the archive, or updating a comment), or adding new records onto the
|
||||
end, there is little risk and possibly a huge efficiency gain in just
|
||||
modifying the archive in place. If none of the operations caused the
|
||||
"copy" list to be initialized, then clearly there's no need to write to a
|
||||
temp file. (It's not actually this simple, because updates to pre-sized
|
||||
threads are annotated in the "copy" list.)
|
||||
|
||||
One of the goals was to be able to execute a sequence of operations like:
|
||||
|
||||
open original archive
|
||||
read original archive
|
||||
modify archive
|
||||
flush (success)
|
||||
modify archive
|
||||
flush (failure, rollback)
|
||||
modify archive
|
||||
flush (success)
|
||||
close archive
|
||||
|
||||
The archive is opened at the start and held open across many operations.
|
||||
There is never a need to re-read the entire archive. We could avoid the
|
||||
need to allocate two complete Record lists by requiring that the archive be
|
||||
re-scanned after changes are aborted; if we did that, we could just modify
|
||||
the original record list in place, and let the changes become "permanent"
|
||||
after a successful write. In many ways, though, its cleaner to have two
|
||||
lists.
|
||||
|
||||
Archives with several thousand entries should be sufficiently rare, and
|
||||
virtual memory should be sufficiently plentiful, that this won't be a
|
||||
problem for anyone. Scanning repeatedly through a 15MB archive stored on a
|
||||
CD-ROM is likely to be very annoying though, so the design makes every
|
||||
attempt to avoid repeated scans of the archive. And in any event, this
|
||||
only applies to archive updates. The memory requirements for simple file
|
||||
extraction are minimal.
|
||||
|
||||
In summary:
|
||||
|
||||
- "orig" list has original set of records, and is not disturbed until
|
||||
the changes are committed.
|
||||
- "copy" list is created on first add/update/delete operation, and
|
||||
initially contains a complete copy of "orig".
|
||||
- "new" list contains all new additions to the archive, including
|
||||
new additions that replace existing entries (the existing entry
|
||||
is deleted from "copy" and then added to "new").
|
||||
|
||||
|
||||
Each Record in the list has a "thread modification" list attached to it.
|
||||
Any changes to the record header or additions to the thread mod list are
|
||||
made in the "copy" set; the "original" set remains untouched. The thread
|
||||
mod list can have the following items in it:
|
||||
|
||||
- delete thread (NuThreadIdx)
|
||||
- add thread (type, otherSize, format, +contents)
|
||||
- update pre-sized thread (NuThreadIdx, +contents)
|
||||
|
||||
Contents are specified with a NuDataSource, which allows the application
|
||||
to indicate that the data is already compressed. This is useful for
|
||||
copying parts of records between archives without having to expand and
|
||||
recompress the data.
|
||||
|
||||
Some interactions and concepts that are important to understand:
|
||||
|
||||
When a file is added, the file type information will be placed in the
|
||||
"new" Record immediately (subject to some restrictions: adding a data
|
||||
fork always causes the type info to be updated, adding a rsrc fork only
|
||||
updates the type info if a data fork is not already present).
|
||||
|
||||
Deleting a record results in the Record being removed from the "copy"
|
||||
list immediately. Future modify operations on that NuRecordIdx will
|
||||
fail. Future read operations will work just fine until the next
|
||||
NuFlush is issued, because read operations use the "original" list.
|
||||
|
||||
Deleting all threads from a record results in the record being
|
||||
deleted, but not until the NuFlush call is issued. It is possible to
|
||||
delete all the existing threads and then add new ones.
|
||||
|
||||
It is *not* allowed to delete a modified thread, modify a deleted thread,
|
||||
or delete a record that has been modified. This limitation was added to
|
||||
keep the system simple. Note this does not mean you can't delete a data
|
||||
fork and add a data fork; doing so results in operations on two threads
|
||||
with different NuThreadIdx values. What you can't do is update the
|
||||
filename thread and then delete it, or vice-versa. (If anyone can think
|
||||
of a reason why you'd want to rename a file and then delete it with the
|
||||
same NuFlush call, I'll figure out a way to support it.)
|
||||
|
||||
Updating a filename thread is intercepted, and causes the Record's
|
||||
filename cache to be updated as well. Adding a filename thread for
|
||||
records where the filename is stored in the record itself cause the
|
||||
"in-record" filename to be zeroed. Adding a filename thread to a
|
||||
record that already has one isn't allowed; nufxlib restricts you to
|
||||
a single filename thread per record.
|
||||
|
||||
Some actions on an archive are allowed but strongly discouraged. For
|
||||
example, deleting a filename thread but leaving the data threads behind
|
||||
is a valid thing to do, but leaves most archivers in a state of mild
|
||||
confusion. Deleting the data threads but leaving the filename thread is
|
||||
similarly perplexing.
|
||||
|
||||
You can't call "update thread" on a thread that doesn't yet exist,
|
||||
even if an "add thread" call has been made. You can, however, call
|
||||
"add thread" on a newly created Record.
|
||||
|
||||
When a new record is created because of a "create record" call, a filename
|
||||
thread is created automatically. It is not necessary to explicitly add the
|
||||
filename.
|
||||
|
||||
Failures encountered while committing changes to a record cause all
|
||||
operations on that record to be rolled back. If, during a NuFlush, a
|
||||
file add fails, the user is given the option of aborting the entire
|
||||
operation or skipping the file in question (and perhaps retrying or other
|
||||
options as well). Aborting the flush causes a complete rollback. If only
|
||||
the thread mod operation is canceled, then all thread mods for that record
|
||||
are ignored. The temp file (or archive file) will have its file pointer
|
||||
reset to the original start of the record, and if the record already
|
||||
existed in the original archive, the full original record will be copied
|
||||
over. This may seem drastic, but it helps ensure that you don't end up
|
||||
with a record in a partially created state.
|
||||
|
||||
If a failure occurs during an "update in place", it isn't possible to
|
||||
roll back all changes. If the failure was due to a bug in NufxLib, it
|
||||
is possible that the archive could be unrecoverably damaged. NufxLib
|
||||
tries to identify such situations, and will leave the archive open in
|
||||
read-only mode after rolling back any new file additions.
|
||||
|
||||
- - -
|
||||
|
||||
### Updating Filenames ###
|
||||
|
||||
Updating filenames is a small nightmare, because the filename can be
|
||||
either in the record header or in a filename thread. It's possible,
|
||||
but illogical, to have a single record with a filename in the record
|
||||
header and two or more filenames in threads.
|
||||
|
||||
NufxLib will not automatically "fix" broken records, but it will prevent
|
||||
applications from creating situations that should not exist.
|
||||
|
||||
- When reading an archive, NufxLib will use the filename from the
|
||||
first filename thread found. If no filename threads are found, the
|
||||
filename from the record header will be used.
|
||||
|
||||
- If you add a filename thread to a record that has a filename in the
|
||||
record header, the header name will be removed.
|
||||
|
||||
- If you update a filename thread in a record that has a filename in
|
||||
the record header, the header name will be left untouched.
|
||||
|
||||
- Adding a filename thread is only allowed if no filename thread exists,
|
||||
or all existing filename threads have been deleted.
|
||||
|
||||
|
||||
- - -
|
||||
|
||||
### Unicode Filenames ###
|
||||
|
||||
Modern operating systems support filenames with a broader range of
|
||||
characters than the Apple II did. This presents problems and opportunities.
|
||||
|
||||
#### Background ####
|
||||
|
||||
The Apple IIgs and old Macintoshes use the Mac OS Roman ("MOR") character
|
||||
set. This defines a set of characters outside the ASCII range, i.e.
|
||||
byte values with the high bit set. In addition to the usual collection
|
||||
of vowels with accents and umlauts, MOR has some less-common characters,
|
||||
including the Apple logo.
|
||||
|
||||
On Windows, the high-ASCII values are generally interpreted according
|
||||
to Windows Code Page 1252 ("CP-1252"), which defines a similar set
|
||||
of vowels with accents and miscellaneous symbols. MOR and CP-1252
|
||||
have some overlap, but you can't really translate one into the other.
|
||||
The standards-approved equivalent of CP-1252 is ISO-8859-1, though
|
||||
according to [wikipedia](http://en.wikipedia.org/wiki/Windows-1252)
|
||||
there was some confusion between the two.
|
||||
|
||||
Modern operating systems support the Unicode Universal Character Set.
|
||||
This system allows for a very large number of characters (over a million),
|
||||
and includes definitions for all of the symbols in MOR and CP-1252.
|
||||
Each character is assigned a "code point", which is a numeric value between
|
||||
zero and 0x10FFFF. Most of the characters used in modern languages can
|
||||
be found in the Basic Multilingual Plane (BMP), which uses code points
|
||||
between zero and 0xFFFF (requiring only 16 bits).
|
||||
|
||||
There are different ways of encoding code points. Consider, for example,
|
||||
Unicode LATIN SMALL LETTER A WITH ACUTE:
|
||||
|
||||
MOR: 0x87
|
||||
CP-1252: 0xE1
|
||||
Unicode: U+00E1
|
||||
UTF-16: 0x00E1
|
||||
UTF-8: 0xC3 0xA1
|
||||
|
||||
Or the humble TRADE MARK SIGN:
|
||||
|
||||
MOR: 0xAA
|
||||
CP-1252: 0x99
|
||||
Unicode: U+2122
|
||||
UTF-16: 0x2122
|
||||
UTF-8: 0xE2 0x84 0xA2
|
||||
|
||||
Modern Linux and Mac OS X use UTF-8 encoding in filenames. Because it's a
|
||||
byte-oriented encoding, and 7-bit ASCII values are trivially represented
|
||||
as 7-bit ASCII values, all of the existing system and library calls work
|
||||
as they did before (i.e. if they took a `char*`, they still do).
|
||||
|
||||
Windows uses UTF-16, which requires at least 16 bits per code point.
|
||||
Filenames are now "wide" strings, based on `wchar_t*`. Windows includes
|
||||
an elaborate system of defines based around the `TCHAR` type, which can
|
||||
be either `char` or `wchar_t` depending on whether a program is compiled
|
||||
with `_MBCS` (Multi-Byte Character System) or `_UNICODE`. A set of
|
||||
preprocessor definitions is provided that will map I/O function names,
|
||||
so you can call `_tfopen(TCHAR* ...)`, and the compiler will turn it into
|
||||
either `fopen(char* ...)` or `_wfopen(wchar_t* ...)`. MBCS is deprecated
|
||||
in favor of Unicode, so any new code should be strictly UTF-16 based.
|
||||
|
||||
This means that, for code to work on both Linux and Windows, it has to
|
||||
work with incompatible filename string types and different I/O functions.
|
||||
|
||||
#### Opening Archive Files ####
|
||||
|
||||
On Linux and Mac OS X, NuLib2 can open any file named on the command line.
|
||||
On Windows, it's a bit trickier.
|
||||
|
||||
The problem is that NuLib2 provides a `main()` function that is passed a
|
||||
vector of "narrow" strings. The filenames provided on the command line
|
||||
will be converted from wide to narrow, so unless the filename is entirely
|
||||
composed of ASCII or CP-1252 characters, some information will be lost
|
||||
and it will be impossible to open the file.
|
||||
|
||||
NuLib2 must instead provide a `wmain()` function that takes wide strings.
|
||||
The strings must be stored and passed around as wide throughout the
|
||||
program, and passed into NufxLib this way (because NufxLib issues the
|
||||
actual _wopen call). This means that NufxLib API must take narrow strings
|
||||
when built for Linux, and wide strings when built for Windows.
|
||||
|
||||
#### Adding/Extracting Mac OS Roman Files ####
|
||||
|
||||
GS/ShrinkIt was designed to handle GS/OS files from HFS volumes, so NuFX
|
||||
archive filenames use the MOR character set. To preserve the encoding
|
||||
we could simply extract the values as-is and let them appear as whatever
|
||||
values happen to line up in CP-1252, which is what pre-3.0 NuLib2 did.
|
||||
It's much nicer to translate from MOR to Unicode when extracting, and
|
||||
convert back from Unicode to MOR when adding files to an archive.
|
||||
|
||||
The key consideration is that the character set associated with a
|
||||
filename must be tracked. The code can't simply extract a filename from
|
||||
the archive and pass it to a 'creat()` call. Character set conversions
|
||||
must take place at appropriate times.
|
||||
|
||||
With Windows it's a bit harder to confuse MOR and Unicode names, because
|
||||
one uses 8-bit characters and the other uses UTF-16, but the compiler
|
||||
doesn't catch everything.
|
||||
|
||||
#### Current State ####
|
||||
|
||||
NufxLib defines the UNICHAR type, which has a role very like TCHAR:
|
||||
it can be `char*` or `wchar_t*`, and can be accompanied by a set of
|
||||
preprocessor mappings that switch between I/O functions. The UNICHAR
|
||||
type will be determined based on a define provided from the compiler
|
||||
command line (perhaps `-DUSE_UTF16_FILENAMES`).
|
||||
|
||||
The current version of NufxLib (v3.0.0) takes the first step, defining
|
||||
all filename strings as either UNICHAR or MOR, and converting between them
|
||||
as necessary. This, plus a few minor tweaks to NuLib2, was enough to
|
||||
get Unicode filename support working on Linux and Mac OS X.
|
||||
|
||||
None of the work needed to make Windows work properly has been done.
|
||||
The string conversion functions are no-ops for Win32. As a result,
|
||||
NuLib2 for Windows treats filenames the same way in 3.x as it did in 2.x.
|
||||
|
||||
There are some situations where things can go awry even with UNICHAR,
|
||||
most notably printf-style arguments. These are checked by gcc, but
|
||||
not by Visual Studio unless you run the static analyzer. A simple
|
||||
`printf("filename=%s\n", filename)` would be correct for narrow strings
|
||||
but wrong for wide strings. It will likely be necessary to define a
|
||||
filename format string (similar to `PRI64d` for 64-bit values) and switch
|
||||
between "%s" and "%ls".
|
||||
|
||||
This is a fair bit of work and requires some amount of uglification to
|
||||
NuLib2 and NufxLib. Since Windows users can use CiderPress, and the
|
||||
vast majority of NuFX archives use ASCII-only ProDOS file names, it's
|
||||
not clear that the effort would be worthwhile.
|
||||
|
119
ciderpress/nufxlib/README.txt
Normal file
119
ciderpress/nufxlib/README.txt
Normal file
@ -0,0 +1,119 @@
|
||||
NufxLib README, updated 2014/12/23
|
||||
http://www.nulib.com/
|
||||
|
||||
See "COPYING-LIB" for distribution restrictions.
|
||||
|
||||
|
||||
UNIX
|
||||
====
|
||||
|
||||
Run the "configure" script. Read through "INSTALL" if you haven't used
|
||||
one of these before, especially if you want to use a specific compiler
|
||||
or a particular set of compiler flags.
|
||||
|
||||
You can disable specific compression methods with "--disable-METHOD"
|
||||
(run "sh ./configure --help" to see the possible options). By default,
|
||||
all methods are enabled except bzip2.
|
||||
|
||||
Run "make depend" if you have makedepend, and then type "make". This will
|
||||
build the library and all of the programs in the "samples" directory.
|
||||
There are some useful programs in "samples", described in a README.txt
|
||||
file there. In particular, you should run samples/test-basic to verify
|
||||
that things are more or less working.
|
||||
|
||||
If you want to install the library and header file into standard system
|
||||
locations (usually /usr/local), run "make install". To learn how to
|
||||
specify different locations, read the INSTALL document.
|
||||
|
||||
There are some flags in "OPT" you may want to use. The "autoconf" default
|
||||
for @CFLAGS@ is "-g -O2".
|
||||
|
||||
-DNDEBUG
|
||||
Disable assert() calls and extra tests. This will speed things up,
|
||||
but errors won't get caught until later on, making the root cause
|
||||
harder to locate.
|
||||
|
||||
-DDEBUG_MSGS
|
||||
Enable debug messages. This increases the size of the executable,
|
||||
but shouldn't affect performance. When errors occur, more output is
|
||||
produced. The "debug dump" feature is enabled by this flag.
|
||||
|
||||
-DDEBUG_VERBOSE
|
||||
(Implicitly sets DEBUG_MSGS.) Spray lots of debugging output.
|
||||
|
||||
If you want to do benchmarks, use "-O2 -DNDEBUG". The recommended
|
||||
configuration is "-g -O2 -DDEBUG_MSGS", so that verbose debug output is
|
||||
available when errors occur.
|
||||
|
||||
|
||||
BeOS
|
||||
====
|
||||
|
||||
This works just like the UNIX version, but certain defaults have been
|
||||
changed. Running configure without arguments under BeOS is equivalent to:
|
||||
|
||||
./configure --prefix=/boot --includedir='${prefix}/develop/headers'
|
||||
--libdir='${exec_prefix}/home/config/lib' --mandir='/tmp'
|
||||
--bindir='${exec_prefix}/home/config/bin'
|
||||
|
||||
If you're using BeOS/PPC, it will also do:
|
||||
|
||||
CC=cc CFLAGS='-proc 603 -opt full'
|
||||
|
||||
|
||||
Mac OS X
|
||||
========
|
||||
|
||||
This works just like the UNIX version, but includes support for resource
|
||||
forks and Finder file/aux types.
|
||||
|
||||
Tested with Xcode v5.1.1 and Mac OS 10.8.5.
|
||||
|
||||
|
||||
Win32
|
||||
=====
|
||||
|
||||
If you're using an environment that supports "configure" scripts, such as
|
||||
DJGPP, follow the UNIX instructions.
|
||||
|
||||
NufxLib has been tested with Microsoft Visual C++ 12 (Visual Studio 2013).
|
||||
To build NufxLib, run the "Visual Studio 2013 x86 Native Tools Command
|
||||
Prompt" shortcut to get a shell. Change to the nufxlib directory, then:
|
||||
|
||||
nmake -f makefile.msc
|
||||
|
||||
When the build finishes, run "test-basic.exe" to confirm things are working.
|
||||
|
||||
If you want to have zlib support enabled, you will need to have zlib.h,
|
||||
zconf.h, and zlib.lib copied into the directory. See "makefile.msc" for
|
||||
more details.
|
||||
|
||||
The makefile builds NufxLib as a static library and as a DLL.
|
||||
|
||||
|
||||
Other Notes
|
||||
===========
|
||||
|
||||
If you want to use the library in a multithreaded application, you should
|
||||
define "USE_REENTRANT_CALLS" to tell it to use reentrant versions of
|
||||
certain library calls. This defines _REENTRANT, which causes Solaris to
|
||||
add the appropriate goodies. (Seems to me you'd always want this on, but
|
||||
for some reason Solaris makes you take an extra step, so I'm not going to
|
||||
define it by default.)
|
||||
|
||||
Originally, NufxLib / NuLib2 were intended to be usable natively on the
|
||||
Apple IIgs, so some of the design decisions were influenced by the need
|
||||
to minimize memory usage (e.g. being able to get a directory listing
|
||||
without holding the entire directory in memory) and interact with GS/OS
|
||||
(forked files have a single filename, files have type/auxtype). The IIgs
|
||||
port was never started.
|
||||
|
||||
|
||||
Legalese
|
||||
========
|
||||
|
||||
NufxLib, a NuFX archive manipulation library.
|
||||
Copyright (C) 2000-2014 by Andy McFadden, All Rights Reserved.
|
||||
|
||||
See COPYING for license.
|
||||
|
@ -72,40 +72,6 @@
|
||||
# define vsnprintf _vsnprintf
|
||||
# endif
|
||||
|
||||
#else
|
||||
# ifndef HAVE_CONFIG_H
|
||||
# define HAVE_FCNTL_H
|
||||
# define HAVE_MALLOC_H
|
||||
# define HAVE_STDLIB_H
|
||||
# define HAVE_SYS_STAT_H
|
||||
# undef HAVE_SYS_TIME_H
|
||||
# define HAVE_SYS_TYPES_H
|
||||
# define HAVE_UNISTD_H
|
||||
# define HAVE_UTIME_H
|
||||
# undef HAVE_SYS_UTIME_H
|
||||
# define HAVE_FDOPEN
|
||||
# undef HAVE_FTRUNCATE
|
||||
# define HAVE_MEMMOVE
|
||||
# undef HAVE_MKSTEMP
|
||||
# define HAVE_MKTIME
|
||||
# define HAVE_SNPRINTF
|
||||
# undef HAVE_STRCASECMP
|
||||
# undef HAVE_STRNCASECMP
|
||||
# define HAVE_STRERROR
|
||||
# define HAVE_STRTOUL
|
||||
# define HAVE_VSNPRINTF
|
||||
# define SNPRINTF_DECLARED
|
||||
# define VSNPRINTF_DECLARED
|
||||
# define SPRINTF_RETURNS_INT
|
||||
# define inline /*Visual C++6.0 can't inline ".c" files*/
|
||||
# define ENABLE_SQ
|
||||
# define ENABLE_LZW
|
||||
# define ENABLE_LZC
|
||||
/*# define ENABLE_DEFLATE*/
|
||||
/*# define ENABLE_BZIP2*/
|
||||
# endif
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_MALLOC_H
|
||||
|
1466
ciderpress/nufxlib/config.guess
vendored
Normal file
1466
ciderpress/nufxlib/config.guess
vendored
Normal file
File diff suppressed because it is too large
Load Diff
134
ciderpress/nufxlib/config.h.in
Normal file
134
ciderpress/nufxlib/config.h.in
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* NuFX archive manipulation library
|
||||
* Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved.
|
||||
* This is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the BSD License, see the file COPYING-LIB.
|
||||
*/
|
||||
/* config.h.in. */
|
||||
|
||||
/* Define to empty if the keyword does not work. */
|
||||
#undef const
|
||||
|
||||
/* Define to empty if the keyword does not work. */
|
||||
#undef inline
|
||||
|
||||
/* Define to `int' if <sys/types.h> doesn't define. */
|
||||
#undef mode_t
|
||||
|
||||
/* Define to `long' if <sys/types.h> doesn't define. */
|
||||
#undef off_t
|
||||
|
||||
/* Define to `unsigned' if <sys/types.h> doesn't define. */
|
||||
#undef size_t
|
||||
|
||||
/* Define if you have the ANSI C header files. */
|
||||
#undef STDC_HEADERS
|
||||
|
||||
/* Define if your <sys/time.h> declares struct tm. */
|
||||
#undef TM_IN_SYS_TIME
|
||||
|
||||
/* Define to `int' if <sys/types.h> doesn't define. */
|
||||
#undef mode_t
|
||||
|
||||
/* Define to `long' if <sys/types.h> doesn't define. */
|
||||
#undef off_t
|
||||
|
||||
/* Define to `unsigned' if <sys/types.h> doesn't define. */
|
||||
#undef size_t
|
||||
|
||||
/* Define if you have the fdopen function. */
|
||||
#undef HAVE_FDOPEN
|
||||
|
||||
/* Define if you have the ftruncate function. */
|
||||
#undef HAVE_FTRUNCATE
|
||||
|
||||
/* Define if you have the localtime_r function. */
|
||||
#undef HAVE_LOCALTIME_R
|
||||
|
||||
/* Define if you have the memmove function. */
|
||||
#undef HAVE_MEMMOVE
|
||||
|
||||
/* Define if you have the mkdir function. */
|
||||
#undef HAVE_MKDIR
|
||||
|
||||
/* Define if you have the mkstemp function. */
|
||||
#undef HAVE_MKSTEMP
|
||||
|
||||
/* Define if you have the mktime function. */
|
||||
#undef HAVE_MKTIME
|
||||
|
||||
/* Define if you have the snprintf function. */
|
||||
#undef HAVE_SNPRINTF
|
||||
|
||||
/* Define if you have the strcasecmp function. */
|
||||
#undef HAVE_STRCASECMP
|
||||
|
||||
/* Define if you have the strncasecmp function. */
|
||||
#undef HAVE_STRNCASECMP
|
||||
|
||||
/* Define if you have the strerror function. */
|
||||
#undef HAVE_STRERROR
|
||||
|
||||
/* Define if you have the strtoul function. */
|
||||
#undef HAVE_STRTOUL
|
||||
|
||||
/* Define if you have the timelocal function. */
|
||||
#undef HAVE_TIMELOCAL
|
||||
|
||||
/* Define if you have the vsnprintf function. */
|
||||
#undef HAVE_VSNPRINTF
|
||||
|
||||
/* Define if you have the <fcntl.h> header file. */
|
||||
#undef HAVE_FCNTL_H
|
||||
|
||||
/* Define if you have the <malloc.h> header file. */
|
||||
#undef HAVE_MALLOC_H
|
||||
|
||||
/* Define if you have the <stdlib.h> header file. */
|
||||
#undef HAVE_STDLIB_H
|
||||
|
||||
/* Define if you have the <sys/time.h> header file. */
|
||||
#undef HAVE_SYS_STAT_H
|
||||
|
||||
/* Define if you have the <sys/time.h> header file. */
|
||||
#undef HAVE_SYS_TIME_H
|
||||
|
||||
/* Define if you have the <sys/types.h> header file. */
|
||||
#undef HAVE_SYS_TYPES_H
|
||||
|
||||
/* Define if you have the <sys/utime.h> header file. */
|
||||
#undef HAVE_SYS_UTIME_H
|
||||
|
||||
/* Define if you have the <unistd.h> header file. */
|
||||
#undef HAVE_UNISTD_H
|
||||
|
||||
/* Define if you have the <utime.h> header file. */
|
||||
#undef HAVE_UTIME_H
|
||||
|
||||
/* Define if sprintf returns an int. */
|
||||
#undef SPRINTF_RETURNS_INT
|
||||
|
||||
/* Define if SNPRINTF is declared in stdio.h. */
|
||||
#undef SNPRINTF_DECLARED
|
||||
|
||||
/* Define if VSNPRINTF is declared in stdio.h. */
|
||||
#undef VSNPRINTF_DECLARED
|
||||
|
||||
/* Define to include SQ (Huffman+RLE) compression. */
|
||||
#undef ENABLE_SQ
|
||||
|
||||
/* Define to include LZW (ShrinkIt LZW/1 and LZW/2) compression. */
|
||||
#undef ENABLE_LZW
|
||||
|
||||
/* Define to include LZC (12-bit and 16-bit UNIX "compress") compression. */
|
||||
#undef ENABLE_LZC
|
||||
|
||||
/* Define to include deflate (zlib) compression (also need -l in Makefile). */
|
||||
#undef ENABLE_DEFLATE
|
||||
|
||||
/* Define to include bzip2 (libbz2) compression (also need -l in Makefile). */
|
||||
#undef ENABLE_BZIP2
|
||||
|
||||
/* Define if we want to use the dmalloc library (also need -l in Makefile). */
|
||||
#undef USE_DMALLOC
|
||||
|
1579
ciderpress/nufxlib/config.sub
vendored
Normal file
1579
ciderpress/nufxlib/config.sub
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5503
ciderpress/nufxlib/configure
vendored
Executable file
5503
ciderpress/nufxlib/configure
vendored
Executable file
File diff suppressed because it is too large
Load Diff
218
ciderpress/nufxlib/configure.in
Normal file
218
ciderpress/nufxlib/configure.in
Normal file
@ -0,0 +1,218 @@
|
||||
dnl NuFX archive manipulation library
|
||||
dnl Copyright (C) 2000-2007 by Andy McFadden, All Rights Reserved.
|
||||
dnl This is free software; you can redistribute it and/or modify it under the
|
||||
dnl terms of the BSD License, see the file COPYING-LIB.
|
||||
dnl
|
||||
dnl Process this file with autoconf to produce a configure script.
|
||||
|
||||
AC_INIT(NufxLibPriv.h)
|
||||
AC_CONFIG_HEADER(config.h)
|
||||
|
||||
dnl Checks for programs.
|
||||
AC_CANONICAL_HOST
|
||||
dnl AC_PROG_AWK
|
||||
AC_PROG_CC
|
||||
AC_PROG_INSTALL
|
||||
AC_PROG_MAKE_SET
|
||||
AC_PROG_RANLIB
|
||||
|
||||
dnl Checks for header files.
|
||||
AC_CHECK_HEADERS(fcntl.h malloc.h stdlib.h sys/stat.h sys/time.h sys/types.h \
|
||||
sys/utime.h unistd.h utime.h)
|
||||
|
||||
LIBS=""
|
||||
|
||||
dnl Checks for typedefs, structures, and compiler characteristics.
|
||||
AC_C_CONST
|
||||
AC_C_INLINE
|
||||
AC_TYPE_MODE_T
|
||||
AC_TYPE_OFF_T
|
||||
AC_TYPE_SIZE_T
|
||||
AC_STRUCT_TM
|
||||
|
||||
dnl Checks for library functions.
|
||||
AC_CHECK_FUNCS(fdopen ftruncate memmove mkdir mkstemp mktime timelocal \
|
||||
localtime_r snprintf strcasecmp strncasecmp strtoul strerror vsnprintf)
|
||||
|
||||
dnl Kent says: snprintf doesn't always have a declaration
|
||||
AC_MSG_CHECKING(if snprintf is declared)
|
||||
AC_CACHE_VAL(nufxlib_cv_snprintf_in_header, [
|
||||
AC_EGREP_HEADER(snprintf, stdio.h,
|
||||
[nufxlib_cv_snprintf_in_header=yes],
|
||||
[nufxlib_cv_snprintf_in_header=no])
|
||||
])
|
||||
if test $nufxlib_cv_snprintf_in_header = "yes"; then
|
||||
AC_DEFINE(SNPRINTF_DECLARED)
|
||||
fi
|
||||
AC_MSG_RESULT($nufxlib_cv_snprintf_in_header)
|
||||
|
||||
dnl Kent says: vsnprintf doesn't always have a declaration
|
||||
AC_MSG_CHECKING(if vsnprintf is declared)
|
||||
AC_CACHE_VAL(nufxlib_cv_vsnprintf_in_header, [
|
||||
AC_EGREP_HEADER(vsnprintf, stdio.h,
|
||||
[nufxlib_cv_vsnprintf_in_header=yes],
|
||||
[nufxlib_cv_vsnprintf_in_header=no])
|
||||
])
|
||||
if test $nufxlib_cv_vsnprintf_in_header = "yes"; then
|
||||
AC_DEFINE(VSNPRINTF_DECLARED)
|
||||
fi
|
||||
AC_MSG_RESULT($nufxlib_cv_vsnprintf_in_header)
|
||||
|
||||
dnl if we're using gcc, include gcc-specific warning flags
|
||||
if test -z "$GCC"; then
|
||||
BUILD_FLAGS='$(OPT)'
|
||||
else
|
||||
BUILD_FLAGS='$(OPT) $(GCC_FLAGS)'
|
||||
fi
|
||||
|
||||
dnl ---------------------------------------------------------------------------
|
||||
dnl Some host-specific stuff. Variables you can test (set by the
|
||||
dnl AC_CANONICAL_HOST call earlier) look like this:
|
||||
dnl
|
||||
dnl $host = i686-pc-linux-gnu
|
||||
dnl $host_cpu = i686
|
||||
dnl $host_vendor = pc
|
||||
dnl $host_os = linux-gnu
|
||||
|
||||
dnl Figure out what the build and link flags should be; different for BeOS.
|
||||
dnl (Should really use the auto-shared-lib stuff, but that adds a whole
|
||||
dnl bunch of stuff.)
|
||||
SHARE_FLAGS='-shared'
|
||||
if test "$host_cpu" = "powerpc" -a "$host_os" = "beos"; then
|
||||
dnl BeOS/PPC, with Metrowerks compiler
|
||||
CC=cc
|
||||
GCC=
|
||||
CFLAGS='-proc 603 -opt full'
|
||||
SHARE_FLAGS='-shared -nostdlib'
|
||||
echo "forcing CC to \"$CC\" and CFLAGS to \"$CFLAGS\""
|
||||
elif test "$host_os" = "beos"; then
|
||||
dnl BeOS/x86
|
||||
SHARE_FLAGS='-nostartfiles -Xlinker -soname="$@"'
|
||||
fi
|
||||
|
||||
AC_SUBST(BUILD_FLAGS)
|
||||
AC_SUBST(SHARE_FLAGS)
|
||||
|
||||
dnl BeOS doesn't like /usr/local/include, and gets feisty about it. If libdir
|
||||
dnl and includedir are set to defaults, replace them with BeOS values. This
|
||||
dnl might be going a little too far...
|
||||
if test "$host_os" = "beos"; then
|
||||
if test "$prefix" = "NONE" -a \
|
||||
"$includedir" = '${prefix}/include' -a \
|
||||
"$libdir" = '${exec_prefix}/lib' -a \
|
||||
"$bindir" = '${exec_prefix}/bin' -a \
|
||||
"$mandir" = '${prefix}/man'
|
||||
then
|
||||
echo replacing install locations with BeOS values
|
||||
prefix=/boot
|
||||
includedir='${prefix}/develop/headers'
|
||||
libdir='${exec_prefix}/home/config/lib'
|
||||
bindir='${exec_prefix}/home/config/bin'
|
||||
mandir='/tmp'
|
||||
AC_SUBST(prefix)
|
||||
AC_SUBST(includedir)
|
||||
AC_SUBST(libdir)
|
||||
AC_SUBST(bindir)
|
||||
AC_SUBST(mandir)
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
dnl Test to see if sprintf does something reasonable. I'm going to assume
|
||||
dnl that vsprintf and (if available) vsnprintf return the same thing.
|
||||
AC_MSG_CHECKING(if sprintf returns int)
|
||||
AC_CACHE_VAL(nufxlib_cv_sprintf_returns_int, [
|
||||
AC_TRY_RUN([
|
||||
#include <stdio.h>
|
||||
int main(void)
|
||||
{
|
||||
int count;
|
||||
char buf[8];
|
||||
count = sprintf(buf, "123"); /* should return three */
|
||||
exit(count != 3);
|
||||
}
|
||||
],
|
||||
[nufxlib_cv_sprintf_returns_int=yes], [nufxlib_cv_sprintf_returns_int=no],
|
||||
[nufxlib_cv_sprintf_returns_int=no])
|
||||
])
|
||||
|
||||
if test $nufxlib_cv_sprintf_returns_int = "yes"; then
|
||||
AC_DEFINE(SPRINTF_RETURNS_INT)
|
||||
fi
|
||||
AC_MSG_RESULT($nufxlib_cv_sprintf_returns_int)
|
||||
|
||||
dnl
|
||||
dnl Allow selective disabling of compression algorithms. By default,
|
||||
dnl all are enabled. We do a little extra work for libz and libbz2
|
||||
dnl because they're not built in.
|
||||
dnl
|
||||
dnl If we're creating a shared library, we need to explicitly link
|
||||
dnl against libz and/or libbz2 when those features are enabled.
|
||||
dnl
|
||||
|
||||
AC_ARG_ENABLE(sq,
|
||||
[ --disable-sq disable SQ compression],
|
||||
[ ], [ enable_sq=yes ])
|
||||
if test $enable_sq = "yes"; then
|
||||
AC_DEFINE(ENABLE_SQ)
|
||||
fi
|
||||
|
||||
AC_ARG_ENABLE(lzw,
|
||||
[ --disable-lzw disable LZW/1 and LZW/2 compression],
|
||||
[ ], [ enable_lzw=yes ])
|
||||
if test $enable_lzw = "yes"; then
|
||||
AC_DEFINE(ENABLE_LZW)
|
||||
fi
|
||||
|
||||
AC_ARG_ENABLE(lzc,
|
||||
[ --disable-lzc disable 12- and 16-bit LZC compression],
|
||||
[ ], [ enable_lzc=yes ])
|
||||
if test $enable_lzc = "yes"; then
|
||||
AC_DEFINE(ENABLE_LZC)
|
||||
fi
|
||||
|
||||
AC_ARG_ENABLE(deflate,
|
||||
[ --disable-deflate disable zlib deflate compression],
|
||||
[ ], [ enable_deflate=yes ])
|
||||
if test $enable_deflate = "yes"; then
|
||||
dnl Check for zlib. Make sure it comes with zlib.h.
|
||||
got_zlibh=false
|
||||
AC_CHECK_LIB(z, deflate, got_libz=true, got_libz=false)
|
||||
if $got_libz; then
|
||||
AC_CHECK_HEADER(zlib.h, got_zlibh=true LIBS="$LIBS -lz")
|
||||
fi
|
||||
if $got_zlibh; then
|
||||
echo " (found libz and zlib.h, enabling deflate)"
|
||||
AC_DEFINE(ENABLE_DEFLATE)
|
||||
else
|
||||
echo " (couldn't find libz and zlib.h, not enabling deflate)"
|
||||
fi
|
||||
fi
|
||||
|
||||
AC_ARG_ENABLE(bzip2,
|
||||
[ --enable-bzip2 enable libbz2 bzip2 compression],
|
||||
[ ], [ enable_bzip2=no ])
|
||||
if test $enable_bzip2 = "yes"; then
|
||||
dnl Check for libbz2. Make sure it comes with bzlib.h.
|
||||
dnl AC_CHECK_LIB(bz2, BZ2_bzCompress,
|
||||
dnl AC_CHECK_HEADER(bzlib.h, AC_DEFINE(ENABLE_BZIP2) LIBS="$LIBS -lbz2"))
|
||||
got_bzlibh=false
|
||||
AC_CHECK_LIB(bz2, BZ2_bzCompress, got_libbz=true, got_libbz=false)
|
||||
if $got_libbz; then
|
||||
AC_CHECK_HEADER(bzlib.h, got_bzlibh=true LIBS="$LIBS -lbz2")
|
||||
fi
|
||||
if $got_bzlibh; then
|
||||
echo " (found libbz2 and bzlib.h, enabling bzip2)"
|
||||
AC_DEFINE(ENABLE_BZIP2)
|
||||
else
|
||||
echo " (couldn't find libbz2 and bzlib.h, not enabling bzip2)"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
AC_ARG_ENABLE(dmalloc, [ --enable-dmalloc do dmalloc stuff],
|
||||
[ echo "--- enabling dmalloc";
|
||||
LIBS="$LIBS -L/usr/local/lib -ldmalloc"; AC_DEFINE(USE_DMALLOC) ])
|
||||
|
||||
AC_OUTPUT(Makefile samples/Makefile)
|
69
ciderpress/nufxlib/nufxlib.def
Normal file
69
ciderpress/nufxlib/nufxlib.def
Normal file
@ -0,0 +1,69 @@
|
||||
; NufxLib library exported symbols
|
||||
;
|
||||
; This is redundant with the __declspec(dllexport) declarations enabled
|
||||
; with the NUFXLIB_EXPORTS symbol. If we're just building a DLL, there's
|
||||
; no need for this file. However, the objects we're building are used for
|
||||
; both the static library and the dynamic library, and we don't want to
|
||||
; have exports in the static library. If we do, the linker will create
|
||||
; .lib and .exp files for every executable we link against it. This is
|
||||
; mostly harmless, but a tad messy. I don't expect the interface to change,
|
||||
; so there's not much of a maintenance burden here.
|
||||
;
|
||||
EXPORTS
|
||||
NuAbort
|
||||
NuAddFile
|
||||
NuAddRecord
|
||||
NuAddThread
|
||||
NuClose
|
||||
NuContents
|
||||
NuConvertMORToUNI
|
||||
NuConvertUNIToMOR
|
||||
NuCreateDataSinkForBuffer
|
||||
NuCreateDataSinkForFP
|
||||
NuCreateDataSinkForFile
|
||||
NuCreateDataSourceForBuffer
|
||||
NuCreateDataSourceForFP
|
||||
NuCreateDataSourceForFile
|
||||
NuDataSinkGetOutCount
|
||||
NuDataSourceSetRawCrc
|
||||
NuDebugDumpArchive
|
||||
NuDelete
|
||||
NuDeleteRecord
|
||||
NuDeleteThread
|
||||
NuExtract
|
||||
NuExtractRecord
|
||||
NuExtractThread
|
||||
NuFlush
|
||||
NuFreeDataSink
|
||||
NuFreeDataSource
|
||||
NuGetAttr
|
||||
NuGetExtraData
|
||||
NuGetMasterHeader
|
||||
NuGetRecord
|
||||
NuGetRecordIdxByName
|
||||
NuGetRecordIdxByPosition
|
||||
NuGetValue
|
||||
NuGetVersion
|
||||
NuIsPresizedThreadID
|
||||
NuOpenRO
|
||||
NuOpenRW
|
||||
NuRecordCopyAttr
|
||||
NuRecordCopyThreads
|
||||
NuRecordGetNumThreads
|
||||
NuRename
|
||||
NuSetErrorHandler
|
||||
NuSetErrorMessageHandler
|
||||
NuSetExtraData
|
||||
NuSetGlobalErrorMessageHandler
|
||||
NuSetOutputPathnameFilter
|
||||
NuSetProgressUpdater
|
||||
NuSetRecordAttr
|
||||
NuSetSelectionFilter
|
||||
NuSetValue
|
||||
NuStrError
|
||||
NuStreamOpenRO
|
||||
NuTest
|
||||
NuTestFeature
|
||||
NuTestRecord
|
||||
NuThreadGetByIdx
|
||||
NuUpdatePresizedThread
|
118
ciderpress/nufxlib/nufxlib.vcxproj
Normal file
118
ciderpress/nufxlib/nufxlib.vcxproj
Normal file
@ -0,0 +1,118 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{C48AE53B-3DCB-43B1-9207-B7C5B6BB78AF}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>nufxlib</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<UseOfMfc>Dynamic</UseOfMfc>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;NUFXLIB_EXPORTS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;NUFXLIB_EXPORTS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="MiscStuff.h" />
|
||||
<ClInclude Include="NufxLib.h" />
|
||||
<ClInclude Include="NufxLibPriv.h" />
|
||||
<ClInclude Include="SysDefs.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Archive.c" />
|
||||
<ClCompile Include="ArchiveIO.c" />
|
||||
<ClCompile Include="Bzip2.c" />
|
||||
<ClCompile Include="Charset.c" />
|
||||
<ClCompile Include="Compress.c" />
|
||||
<ClCompile Include="Crc16.c" />
|
||||
<ClCompile Include="Debug.c" />
|
||||
<ClCompile Include="Deferred.c" />
|
||||
<ClCompile Include="Deflate.c" />
|
||||
<ClCompile Include="Entry.c" />
|
||||
<ClCompile Include="Expand.c" />
|
||||
<ClCompile Include="FileIO.c" />
|
||||
<ClCompile Include="Funnel.c" />
|
||||
<ClCompile Include="Lzc.c" />
|
||||
<ClCompile Include="Lzw.c" />
|
||||
<ClCompile Include="MiscStuff.c" />
|
||||
<ClCompile Include="MiscUtils.c" />
|
||||
<ClCompile Include="Record.c" />
|
||||
<ClCompile Include="SourceSink.c" />
|
||||
<ClCompile Include="Squeeze.c" />
|
||||
<ClCompile Include="Thread.c" />
|
||||
<ClCompile Include="Value.c" />
|
||||
<ClCompile Include="Version.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\zlib\zlib.vcxproj">
|
||||
<Project>{b66109f4-217b-43c0-86aa-eb55657e5ac0}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
102
ciderpress/nufxlib/nufxlib.vcxproj.filters
Normal file
102
ciderpress/nufxlib/nufxlib.vcxproj.filters
Normal file
@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="MiscStuff.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="NufxLib.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="NufxLibPriv.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SysDefs.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Archive.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ArchiveIO.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Bzip2.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Compress.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Crc16.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Debug.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Deferred.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Deflate.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Entry.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Expand.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FileIO.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Funnel.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Lzc.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Lzw.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MiscStuff.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MiscUtils.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Record.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SourceSink.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Squeeze.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Thread.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Value.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Version.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Charset.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
615
diskimg/ASPI.cpp
Normal file
615
diskimg/ASPI.cpp
Normal file
@ -0,0 +1,615 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* ASPI I/O functions.
|
||||
*
|
||||
* Some notes on ASPI stuff:
|
||||
* - The Nero ASPI provides an interface for IDE hard drives. It also
|
||||
* throws in a couple of mystery devices on a host adapter at the end.
|
||||
* It has "unknown" device type and doesn't respond to SCSI device
|
||||
* inquiries, so it's easy to ignore.
|
||||
* - The Win98 generic ASPI only finds CD-ROM drives on the IDE bus.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#if defined(_WIN32) && defined (WANT_ASPI)
|
||||
|
||||
#include "DiskImgPriv.h"
|
||||
#include "SCSIDefs.h"
|
||||
#include "CP_wnaspi32.h"
|
||||
#include "ASPI.h"
|
||||
|
||||
|
||||
/*
|
||||
* Initialize ASPI.
|
||||
*/
|
||||
DIError ASPI::Init(void)
|
||||
{
|
||||
DWORD aspiStatus;
|
||||
static const char* kASPIDllName = "wnaspi32.dll";
|
||||
|
||||
/*
|
||||
* Try to load the DLL.
|
||||
*/
|
||||
fhASPI = ::LoadLibrary(kASPIDllName);
|
||||
if (fhASPI == NULL) {
|
||||
DWORD lastErr = ::GetLastError();
|
||||
if (lastErr == ERROR_MOD_NOT_FOUND) {
|
||||
LOGI("ASPI DLL '%s' not found", kASPIDllName);
|
||||
} else {
|
||||
LOGI("ASPI LoadLibrary(%s) failed (err=%ld)",
|
||||
kASPIDllName, GetLastError());
|
||||
}
|
||||
return kDIErrGeneric;
|
||||
}
|
||||
|
||||
GetASPI32SupportInfo = (DWORD(*)(void))::GetProcAddress(fhASPI, "GetASPI32SupportInfo");
|
||||
SendASPI32Command = (DWORD(*)(LPSRB))::GetProcAddress(fhASPI, "SendASPI32Command");
|
||||
GetASPI32DLLVersion = (DWORD(*)(void))::GetProcAddress(fhASPI, "GetASPI32DLLVersion");
|
||||
if (GetASPI32SupportInfo == NULL || SendASPI32Command == NULL) {
|
||||
LOGI("ASPI functions not found in dll");
|
||||
::FreeLibrary(fhASPI);
|
||||
fhASPI = NULL;
|
||||
return kDIErrGeneric;
|
||||
}
|
||||
|
||||
if (GetASPI32DLLVersion != NULL) {
|
||||
fASPIVersion = GetASPI32DLLVersion();
|
||||
LOGI(" ASPI version is %d.%d.%d.%d",
|
||||
fASPIVersion & 0x0ff,
|
||||
(fASPIVersion >> 8) & 0xff,
|
||||
(fASPIVersion >> 16) & 0xff,
|
||||
(fASPIVersion >> 24) & 0xff);
|
||||
} else {
|
||||
LOGI("ASPI WARNING: couldn't find GetASPI32DLLVersion interface");
|
||||
}
|
||||
|
||||
/*
|
||||
* Successfully loaded the library. Start it up and see if it works.
|
||||
*/
|
||||
aspiStatus = GetASPI32SupportInfo();
|
||||
if (HIBYTE(LOWORD(aspiStatus)) != SS_COMP) {
|
||||
LOGI("ASPI loaded but not working (status=%d)",
|
||||
HIBYTE(LOWORD(aspiStatus)));
|
||||
::FreeLibrary(fhASPI);
|
||||
fhASPI = NULL;
|
||||
return kDIErrASPIFailure;
|
||||
}
|
||||
|
||||
fHostAdapterCount = LOBYTE(LOWORD(aspiStatus));
|
||||
LOGI("ASPI loaded successfully, hostAdapterCount=%d",
|
||||
fHostAdapterCount);
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Destructor. Unload the ASPI DLL.
|
||||
*/
|
||||
ASPI::~ASPI(void)
|
||||
{
|
||||
if (fhASPI != NULL) {
|
||||
LOGI("Unloading ASPI DLL");
|
||||
::FreeLibrary(fhASPI);
|
||||
fhASPI = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Issue an ASPI host adapter inquiry request for the specified adapter.
|
||||
*
|
||||
* Pass in a pointer to a struct that receives the result.
|
||||
*/
|
||||
DIError ASPI::HostAdapterInquiry(unsigned char adapter, AdapterInfo* pAdapterInfo)
|
||||
{
|
||||
SRB_HAInquiry req;
|
||||
DWORD result;
|
||||
|
||||
assert(adapter >= 0 && adapter < kMaxAdapters);
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.SRB_Cmd = SC_HA_INQUIRY;
|
||||
req.SRB_HaId = adapter;
|
||||
|
||||
result = SendASPI32Command(&req);
|
||||
if (result != SS_COMP) {
|
||||
LOGI("ASPI(SC_HA_INQUIRY on %d) failed with result=0x%lx",
|
||||
adapter, result);
|
||||
return kDIErrASPIFailure;
|
||||
}
|
||||
|
||||
pAdapterInfo->adapterScsiID = req.HA_SCSI_ID;
|
||||
memcpy(pAdapterInfo->managerID, req.HA_ManagerId,
|
||||
sizeof(pAdapterInfo->managerID)-1);
|
||||
pAdapterInfo->managerID[sizeof(pAdapterInfo->managerID)-1] = '\0';
|
||||
memcpy(pAdapterInfo->identifier, req.HA_Identifier,
|
||||
sizeof(pAdapterInfo->identifier)-1);
|
||||
pAdapterInfo->identifier[sizeof(pAdapterInfo->identifier)-1] = '\0';
|
||||
pAdapterInfo->maxTargets = req.HA_Unique[3];
|
||||
pAdapterInfo->bufferAlignment =
|
||||
(unsigned short) req.HA_Unique[1] << 8 | req.HA_Unique[0];
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Issue an ASPI query on device type.
|
||||
*/
|
||||
DIError ASPI::GetDeviceType(unsigned char adapter, unsigned char target,
|
||||
unsigned char lun, unsigned char* pType)
|
||||
{
|
||||
SRB_GDEVBlock req;
|
||||
DWORD result;
|
||||
|
||||
assert(adapter >= 0 && adapter < kMaxAdapters);
|
||||
assert(target >= 0 && target < kMaxTargets);
|
||||
assert(lun >= 0 && lun < kMaxLuns);
|
||||
assert(pType != NULL);
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.SRB_Cmd = SC_GET_DEV_TYPE;
|
||||
req.SRB_HaId = adapter;
|
||||
req.SRB_Target = target;
|
||||
req.SRB_Lun = lun;
|
||||
|
||||
result = SendASPI32Command(&req);
|
||||
if (result != SS_COMP)
|
||||
return kDIErrASPIFailure;
|
||||
|
||||
*pType = req.SRB_DeviceType;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a printable string for the given device type.
|
||||
*/
|
||||
const char* ASPI::DeviceTypeToString(unsigned char deviceType)
|
||||
{
|
||||
switch (deviceType) {
|
||||
case kScsiDevTypeDASD: return "Disk device";
|
||||
case kScsiDevTypeSEQD: return "Tape device";
|
||||
case kScsiDevTypePRNT: return "Printer";
|
||||
case kScsiDevTypePROC: return "Processor";
|
||||
case kScsiDevTypeWORM: return "Write-once read-multiple";
|
||||
case kScsiDevTypeCDROM: return "CD-ROM device";
|
||||
case kScsiDevTypeSCAN: return "Scanner device";
|
||||
case kScsiDevTypeOPTI: return "Optical memory device";
|
||||
case kScsiDevTypeJUKE: return "Medium changer device";
|
||||
case kScsiDevTypeCOMM: return "Communications device";
|
||||
case kScsiDevTypeUNKNOWN: return "Unknown or no device type";
|
||||
default: return "Invalid type";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Issue a SCSI device inquiry and return the interesting parts.
|
||||
*/
|
||||
DIError ASPI::DeviceInquiry(unsigned char adapter, unsigned char target,
|
||||
unsigned char lun, Inquiry* pInquiry)
|
||||
{
|
||||
DIError dierr;
|
||||
SRB_ExecSCSICmd srb;
|
||||
CDB6Inquiry* pCDB;
|
||||
unsigned char buf[96]; // enough to hold everything of interest, and more
|
||||
CDB_InquiryData* pInqData = (CDB_InquiryData*) buf;
|
||||
|
||||
assert(sizeof(CDB6Inquiry) == 6);
|
||||
|
||||
memset(&srb, 0, sizeof(srb));
|
||||
srb.SRB_Cmd = SC_EXEC_SCSI_CMD;
|
||||
srb.SRB_HaId = adapter;
|
||||
srb.SRB_Target = target;
|
||||
srb.SRB_Lun = lun;
|
||||
srb.SRB_Flags = SRB_DIR_IN;
|
||||
srb.SRB_BufLen = sizeof(buf);
|
||||
srb.SRB_BufPointer = buf;
|
||||
srb.SRB_SenseLen = SENSE_LEN;
|
||||
srb.SRB_CDBLen = sizeof(*pCDB);
|
||||
|
||||
pCDB = (CDB6Inquiry*) srb.CDBByte;
|
||||
pCDB->operationCode = kScsiOpInquiry;
|
||||
pCDB->allocationLength = sizeof(buf);
|
||||
|
||||
// Don't set pCDB->logicalUnitNumber. It's only there for SCSI-1
|
||||
// devices. SCSI-2 uses an IDENTIFY command; I gather ASPI is doing
|
||||
// this for us.
|
||||
|
||||
dierr = ExecSCSICommand(&srb);
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
|
||||
memcpy(pInquiry->vendorID, pInqData->vendorId,
|
||||
sizeof(pInquiry->vendorID)-1);
|
||||
pInquiry->vendorID[sizeof(pInquiry->vendorID)-1] = '\0';
|
||||
memcpy(pInquiry->productID, pInqData->productId,
|
||||
sizeof(pInquiry->productID)-1);
|
||||
pInquiry->productID[sizeof(pInquiry->productID)-1] = '\0';
|
||||
pInquiry->productRevision[0] = pInqData->productRevisionLevel[0];
|
||||
pInquiry->productRevision[1] = pInqData->productRevisionLevel[1];
|
||||
pInquiry->productRevision[2] = pInqData->productRevisionLevel[2];
|
||||
pInquiry->productRevision[3] = pInqData->productRevisionLevel[3];
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Get the capacity of a SCSI block device.
|
||||
*/
|
||||
DIError ASPI::GetDeviceCapacity(unsigned char adapter, unsigned char target,
|
||||
unsigned char lun, unsigned long* pLastBlock, unsigned long* pBlockSize)
|
||||
{
|
||||
DIError dierr;
|
||||
SRB_ExecSCSICmd srb;
|
||||
CDB10* pCDB;
|
||||
CDB_ReadCapacityData dataBuf;
|
||||
|
||||
assert(sizeof(dataBuf) == 8); // READ CAPACITY returns two longs
|
||||
assert(sizeof(CDB10) == 10);
|
||||
|
||||
memset(&srb, 0, sizeof(srb));
|
||||
srb.SRB_Cmd = SC_EXEC_SCSI_CMD;
|
||||
srb.SRB_HaId = adapter;
|
||||
srb.SRB_Target = target;
|
||||
srb.SRB_Lun = lun;
|
||||
srb.SRB_Flags = SRB_DIR_IN;
|
||||
srb.SRB_BufLen = sizeof(dataBuf);
|
||||
srb.SRB_BufPointer = (unsigned char*)&dataBuf;
|
||||
srb.SRB_SenseLen = SENSE_LEN;
|
||||
srb.SRB_CDBLen = sizeof(*pCDB);
|
||||
|
||||
pCDB = (CDB10*) srb.CDBByte;
|
||||
pCDB->operationCode = kScsiOpReadCapacity;
|
||||
// rest of CDB is zero
|
||||
|
||||
dierr = ExecSCSICommand(&srb);
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
|
||||
*pLastBlock =
|
||||
(unsigned long) dataBuf.logicalBlockAddr0 << 24 |
|
||||
(unsigned long) dataBuf.logicalBlockAddr1 << 16 |
|
||||
(unsigned long) dataBuf.logicalBlockAddr2 << 8 |
|
||||
(unsigned long) dataBuf.logicalBlockAddr3;
|
||||
*pBlockSize =
|
||||
(unsigned long) dataBuf.bytesPerBlock0 << 24 |
|
||||
(unsigned long) dataBuf.bytesPerBlock1 << 16 |
|
||||
(unsigned long) dataBuf.bytesPerBlock2 << 8 |
|
||||
(unsigned long) dataBuf.bytesPerBlock3;
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test to see if a device is ready.
|
||||
*
|
||||
* Returns "true" if the device is ready, "false" if not.
|
||||
*/
|
||||
DIError ASPI::TestUnitReady(unsigned char adapter, unsigned char target,
|
||||
unsigned char lun, bool* pReady)
|
||||
{
|
||||
DIError dierr;
|
||||
SRB_ExecSCSICmd srb;
|
||||
CDB6* pCDB;
|
||||
|
||||
assert(sizeof(CDB6) == 6);
|
||||
|
||||
memset(&srb, 0, sizeof(srb));
|
||||
srb.SRB_Cmd = SC_EXEC_SCSI_CMD;
|
||||
srb.SRB_HaId = adapter;
|
||||
srb.SRB_Target = target;
|
||||
srb.SRB_Lun = lun;
|
||||
srb.SRB_Flags = 0; //SRB_DIR_IN;
|
||||
srb.SRB_BufLen = 0;
|
||||
srb.SRB_BufPointer = NULL;
|
||||
srb.SRB_SenseLen = SENSE_LEN;
|
||||
srb.SRB_CDBLen = sizeof(*pCDB);
|
||||
|
||||
pCDB = (CDB6*) srb.CDBByte;
|
||||
pCDB->operationCode = kScsiOpTestUnitReady;
|
||||
// rest of CDB is zero
|
||||
|
||||
dierr = ExecSCSICommand(&srb);
|
||||
if (dierr != kDIErrNone) {
|
||||
const CDB_SenseData* pSense = (const CDB_SenseData*) srb.SenseArea;
|
||||
|
||||
if (srb.SRB_TargStat == kScsiStatCheckCondition &&
|
||||
pSense->senseKey == kScsiSenseNotReady)
|
||||
{
|
||||
// expect pSense->additionalSenseCode to be
|
||||
// kScsiAdSenseNoMediaInDevice; no need to check it really.
|
||||
LOGI(" ASPI TestUnitReady: drive %d:%d:%d is NOT ready",
|
||||
adapter, target, lun);
|
||||
} else {
|
||||
LOGI(" ASPI TestUnitReady failed, status=0x%02x sense=0x%02x ASC=0x%02x",
|
||||
srb.SRB_TargStat, pSense->senseKey,
|
||||
pSense->additionalSenseCode);
|
||||
}
|
||||
*pReady = false;
|
||||
} else {
|
||||
const CDB_SenseData* pSense = (const CDB_SenseData*) srb.SenseArea;
|
||||
LOGI(" ASPI TestUnitReady: drive %d:%d:%d is ready",
|
||||
adapter, target, lun);
|
||||
//LOGI(" status=0x%02x sense=0x%02x ASC=0x%02x",
|
||||
// srb.SRB_TargStat, pSense->senseKey, pSense->additionalSenseCode);
|
||||
*pReady = true;
|
||||
}
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read one or more blocks from the device.
|
||||
*
|
||||
* The block size is going to be whatever the device's native size is
|
||||
* (possibly modified by extents, but we'll ignore that). For a CD-ROM
|
||||
* this means 2048-byte blocks.
|
||||
*/
|
||||
DIError ASPI::ReadBlocks(unsigned char adapter, unsigned char target,
|
||||
unsigned char lun, long startBlock, short numBlocks, long blockSize,
|
||||
void* buf)
|
||||
{
|
||||
SRB_ExecSCSICmd srb;
|
||||
CDB10* pCDB;
|
||||
|
||||
//LOGI(" ASPI ReadBlocks start=%ld num=%d (size=%d)",
|
||||
// startBlock, numBlocks, blockSize);
|
||||
|
||||
assert(sizeof(CDB10) == 10);
|
||||
assert(startBlock >= 0);
|
||||
assert(numBlocks > 0);
|
||||
assert(buf != NULL);
|
||||
|
||||
memset(&srb, 0, sizeof(srb));
|
||||
srb.SRB_Cmd = SC_EXEC_SCSI_CMD;
|
||||
srb.SRB_HaId = adapter;
|
||||
srb.SRB_Target = target;
|
||||
srb.SRB_Lun = lun;
|
||||
srb.SRB_Flags = SRB_DIR_IN;
|
||||
srb.SRB_BufLen = numBlocks * blockSize;
|
||||
srb.SRB_BufPointer = (unsigned char*)buf;
|
||||
srb.SRB_SenseLen = SENSE_LEN;
|
||||
srb.SRB_CDBLen = sizeof(*pCDB);
|
||||
|
||||
pCDB = (CDB10*) srb.CDBByte;
|
||||
pCDB->operationCode = kScsiOpRead;
|
||||
pCDB->logicalBlockAddr0 = (unsigned char) (startBlock >> 24); // MSB
|
||||
pCDB->logicalBlockAddr1 = (unsigned char) (startBlock >> 16);
|
||||
pCDB->logicalBlockAddr2 = (unsigned char) (startBlock >> 8);
|
||||
pCDB->logicalBlockAddr3 = (unsigned char) startBlock; // LSB
|
||||
pCDB->transferLength0 = (unsigned char) (numBlocks >> 8); // MSB
|
||||
pCDB->transferLength1 = (unsigned char) numBlocks; // LSB
|
||||
|
||||
return ExecSCSICommand(&srb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write one or more blocks to the device.
|
||||
*/
|
||||
DIError ASPI::WriteBlocks(unsigned char adapter, unsigned char target,
|
||||
unsigned char lun, long startBlock, short numBlocks, long blockSize,
|
||||
const void* buf)
|
||||
{
|
||||
SRB_ExecSCSICmd srb;
|
||||
CDB10* pCDB;
|
||||
|
||||
LOGI(" ASPI WriteBlocks start=%ld num=%d (size=%d)",
|
||||
startBlock, numBlocks, blockSize);
|
||||
|
||||
assert(sizeof(CDB10) == 10);
|
||||
assert(startBlock >= 0);
|
||||
assert(numBlocks > 0);
|
||||
assert(buf != NULL);
|
||||
|
||||
memset(&srb, 0, sizeof(srb));
|
||||
srb.SRB_Cmd = SC_EXEC_SCSI_CMD;
|
||||
srb.SRB_HaId = adapter;
|
||||
srb.SRB_Target = target;
|
||||
srb.SRB_Lun = lun;
|
||||
srb.SRB_Flags = SRB_DIR_IN;
|
||||
srb.SRB_BufLen = numBlocks * blockSize;
|
||||
srb.SRB_BufPointer = (unsigned char*)buf;
|
||||
srb.SRB_SenseLen = SENSE_LEN;
|
||||
srb.SRB_CDBLen = sizeof(*pCDB);
|
||||
|
||||
pCDB = (CDB10*) srb.CDBByte;
|
||||
pCDB->operationCode = kScsiOpWrite;
|
||||
pCDB->logicalBlockAddr0 = (unsigned char) (startBlock >> 24); // MSB
|
||||
pCDB->logicalBlockAddr1 = (unsigned char) (startBlock >> 16);
|
||||
pCDB->logicalBlockAddr2 = (unsigned char) (startBlock >> 8);
|
||||
pCDB->logicalBlockAddr3 = (unsigned char) startBlock; // LSB
|
||||
pCDB->transferLength0 = (unsigned char) (numBlocks >> 8); // MSB
|
||||
pCDB->transferLength1 = (unsigned char) numBlocks; // LSB
|
||||
|
||||
return ExecSCSICommand(&srb);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Execute a SCSI command.
|
||||
*
|
||||
* Returns an error if ASPI reports an error or the SCSI status isn't
|
||||
* kScsiStatGood.
|
||||
*
|
||||
* The Nero ASPI layer typically returns immediately, and hands back an
|
||||
* SS_ERR when something fails. Win98 ASPI does the SS_PENDING thang.
|
||||
*/
|
||||
DIError ASPI::ExecSCSICommand(SRB_ExecSCSICmd* pSRB)
|
||||
{
|
||||
HANDLE completionEvent = NULL;
|
||||
DWORD eventStatus;
|
||||
DWORD aspiStatus;
|
||||
|
||||
assert(pSRB->SRB_Cmd == SC_EXEC_SCSI_CMD);
|
||||
assert(pSRB->SRB_Flags == SRB_DIR_IN ||
|
||||
pSRB->SRB_Flags == SRB_DIR_OUT ||
|
||||
pSRB->SRB_Flags == 0);
|
||||
|
||||
/*
|
||||
* Set up event-waiting stuff, as described in the Adaptec ASPI docs.
|
||||
*/
|
||||
pSRB->SRB_Flags |= SRB_EVENT_NOTIFY;
|
||||
|
||||
completionEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
if (completionEvent == NULL) {
|
||||
LOGI("Failed creating a completion event?");
|
||||
return kDIErrGeneric;
|
||||
}
|
||||
|
||||
pSRB->SRB_PostProc = completionEvent;
|
||||
|
||||
/*
|
||||
* Send the request.
|
||||
*/
|
||||
(void)SendASPI32Command((LPSRB) pSRB);
|
||||
aspiStatus = pSRB->SRB_Status;
|
||||
if (aspiStatus == SS_PENDING) {
|
||||
//LOGI(" (waiting for completion)");
|
||||
eventStatus = ::WaitForSingleObject(completionEvent, kTimeout * 1000);
|
||||
|
||||
::CloseHandle(completionEvent);
|
||||
|
||||
if (eventStatus == WAIT_TIMEOUT) {
|
||||
LOGI(" ASPI exec timed out!");
|
||||
return kDIErrSCSIFailure;
|
||||
} else if (eventStatus != WAIT_OBJECT_0) {
|
||||
LOGI(" ASPI exec returned weird wait state %ld", eventStatus);
|
||||
return kDIErrGeneric;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the final status.
|
||||
*/
|
||||
aspiStatus = pSRB->SRB_Status;
|
||||
|
||||
if (aspiStatus == SS_COMP) {
|
||||
/* success! */
|
||||
} else if (aspiStatus == SS_ERR) {
|
||||
const CDB_SenseData* pSense = (const CDB_SenseData*) pSRB->SenseArea;
|
||||
|
||||
LOGI(" ASPI SCSI command 0x%02x failed: scsiStatus=0x%02x"
|
||||
" senseKey=0x%02x ASC=0x%02x\n",
|
||||
pSRB->CDBByte[0], pSRB->SRB_TargStat,
|
||||
pSense->senseKey, pSense->additionalSenseCode);
|
||||
return kDIErrSCSIFailure;
|
||||
} else {
|
||||
// SS_ABORTED, SS_ABORT_FAIL, SS_NO_DEVICE, ...
|
||||
LOGI(" ASPI failed on command 0x%02x: aspiStatus=%d scsiStatus=%d",
|
||||
pSRB->CDBByte[0], aspiStatus, pSRB->SRB_TargStat);
|
||||
return kDIErrASPIFailure;
|
||||
}
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Return an array of accessible devices we found.
|
||||
*
|
||||
* Only return the devices matching device types in "deviceMask".
|
||||
*/
|
||||
DIError ASPI::GetAccessibleDevices(int deviceMask, ASPIDevice** ppDeviceArray,
|
||||
int* pNumDevices)
|
||||
{
|
||||
DIError dierr;
|
||||
ASPIDevice* deviceArray = NULL;
|
||||
int idx = 0;
|
||||
|
||||
assert(deviceMask != 0);
|
||||
assert((deviceMask & ~(kDevMaskCDROM | kDevMaskHardDrive)) == 0);
|
||||
assert(ppDeviceArray != NULL);
|
||||
assert(pNumDevices != NULL);
|
||||
|
||||
deviceArray = new ASPIDevice[kMaxAccessibleDrives];
|
||||
if (deviceArray == NULL)
|
||||
return kDIErrMalloc;
|
||||
|
||||
LOGI("ASPI scanning %d host adapters", fHostAdapterCount);
|
||||
|
||||
for (int ha = 0; ha < fHostAdapterCount; ha++) {
|
||||
AdapterInfo adi;
|
||||
|
||||
dierr = HostAdapterInquiry(ha, &adi);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" ASPI inquiry on %d failed", ha);
|
||||
continue;
|
||||
}
|
||||
|
||||
LOGI(" ASPI host adapter %d (SCSI ID=%d)", ha, adi.adapterScsiID);
|
||||
LOGI(" identifier='%s' managerID='%s'",
|
||||
adi.identifier, adi.managerID);
|
||||
LOGI(" maxTargets=%d bufferAlignment=%d",
|
||||
adi.maxTargets, adi.bufferAlignment);
|
||||
|
||||
int maxTargets = adi.maxTargets;
|
||||
if (!maxTargets) {
|
||||
/* Win98 ASPI reports zero here for ATAPI */
|
||||
maxTargets = 8;
|
||||
}
|
||||
if (maxTargets > kMaxTargets)
|
||||
maxTargets = kMaxTargets;
|
||||
for (int targ = 0; targ < maxTargets; targ++) {
|
||||
for (int lun = 0; lun < kMaxLuns; lun++) {
|
||||
Inquiry inq;
|
||||
unsigned char deviceType;
|
||||
char addrString[48];
|
||||
bool deviceReady;
|
||||
|
||||
dierr = GetDeviceType(ha, targ, lun, &deviceType);
|
||||
if (dierr != kDIErrNone)
|
||||
continue;
|
||||
|
||||
sprintf(addrString, "%d:%d:%d", ha, targ, lun);
|
||||
|
||||
dierr = DeviceInquiry(ha, targ, lun, &inq);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" ASPI DeviceInquiry for '%s' (type=%d) failed",
|
||||
addrString, deviceType);
|
||||
continue;
|
||||
}
|
||||
|
||||
LOGI(" Device %s is %s '%s' '%s'",
|
||||
addrString, DeviceTypeToString(deviceType),
|
||||
inq.vendorID, inq.productID);
|
||||
|
||||
if ((deviceMask & kDevMaskCDROM) != 0 &&
|
||||
deviceType == kScsiDevTypeCDROM)
|
||||
{
|
||||
/* found CD-ROM */
|
||||
} else if ((deviceMask & kDevMaskHardDrive) != 0 &&
|
||||
deviceType == kScsiDevTypeDASD)
|
||||
{
|
||||
/* found hard drive */
|
||||
} else
|
||||
continue;
|
||||
|
||||
if (idx >= kMaxAccessibleDrives) {
|
||||
LOGI("GLITCH: ran out of places to stuff CD-ROM drives");
|
||||
assert(false);
|
||||
goto done;
|
||||
}
|
||||
|
||||
dierr = TestUnitReady(ha, targ, lun, &deviceReady);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" ASPI TestUnitReady for '%s' failed", addrString);
|
||||
continue;
|
||||
}
|
||||
|
||||
deviceArray[idx].Init(ha, targ, lun, inq.vendorID,
|
||||
inq.productID, deviceType, deviceReady);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
*ppDeviceArray = deviceArray;
|
||||
*pNumDevices = idx;
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
#endif /*_WIN32*/
|
202
diskimg/ASPI.h
Normal file
202
diskimg/ASPI.h
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* ASPI (Advanced SCSI Programming Interface) definitions.
|
||||
*
|
||||
* This may be included directly by an application. It must not be necessary
|
||||
* to include the lower-level headers, e.g. wnaspi32.h.
|
||||
*
|
||||
* TODO: this was only necessary for older versions of Windows, e.g. Win98,
|
||||
* as a way to access SCSI drives. It's no longer needed.
|
||||
*/
|
||||
#ifndef __ASPI__
|
||||
#define __ASPI__
|
||||
|
||||
#if !defined(_WIN32) || !defined(WANT_ASPI)
|
||||
/*
|
||||
* Placeholder definition to keep Linux build happy.
|
||||
*/
|
||||
namespace DiskImgLib {
|
||||
class DISKIMG_API ASPI {
|
||||
public:
|
||||
ASPI(void) {}
|
||||
virtual ~ASPI(void) {}
|
||||
};
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
|
||||
|
||||
#ifndef __WNASPI32_H__
|
||||
struct SRB_ExecSCSICmd; // fwd
|
||||
#endif
|
||||
|
||||
namespace DiskImgLib {
|
||||
|
||||
/*
|
||||
* Descriptor for one SCSI device.
|
||||
*/
|
||||
class DISKIMG_API ASPIDevice {
|
||||
public:
|
||||
ASPIDevice(void) : fVendorID(NULL), fProductID(NULL),
|
||||
fAdapter(0xff), fTarget(0xff), fLun(0xff), fDeviceReady(false)
|
||||
{}
|
||||
virtual ~ASPIDevice(void) {
|
||||
delete[] fVendorID;
|
||||
delete[] fProductID;
|
||||
}
|
||||
|
||||
void Init(unsigned char adapter, unsigned char target, unsigned char lun,
|
||||
const unsigned char* vendor, unsigned const char* product,
|
||||
int deviceType, bool ready)
|
||||
{
|
||||
fAdapter = adapter;
|
||||
fTarget = target;
|
||||
fLun = lun;
|
||||
assert(fVendorID == NULL);
|
||||
fVendorID = new char[strlen((const char*)vendor)+1];
|
||||
strcpy(fVendorID, (const char*)vendor);
|
||||
assert(fProductID == NULL);
|
||||
fProductID = new char[strlen((const char*)product)+1];
|
||||
strcpy(fProductID, (const char*)product);
|
||||
fDeviceReady = ready;
|
||||
fDeviceType = deviceType;
|
||||
}
|
||||
|
||||
enum {
|
||||
kTypeDASD = 0, // kScsiDevTypeDASD
|
||||
kTypeCDROM = 5, // kScsiDevTypeCDROM
|
||||
};
|
||||
|
||||
unsigned char GetAdapter(void) const { return fAdapter; }
|
||||
unsigned char GetTarget(void) const { return fTarget; }
|
||||
unsigned char GetLun(void) const { return fLun; }
|
||||
const char* GetVendorID(void) const { return fVendorID; }
|
||||
const char* GetProductID(void) const { return fProductID; }
|
||||
bool GetDeviceReady(void) const { return fDeviceReady; }
|
||||
int GetDeviceType(void) const { return fDeviceType; }
|
||||
|
||||
private:
|
||||
// strings from SCSI inquiry, padded with spaces at end
|
||||
char* fVendorID;
|
||||
char* fProductID;
|
||||
|
||||
unsigned char fAdapter; // physical or logical host adapter (0-15)
|
||||
unsigned char fTarget; // SCSI ID on adapter (0-15)
|
||||
unsigned char fLun; // logical unit (0-7)
|
||||
|
||||
int fDeviceType; // e.g. kScsiDevTypeCDROM
|
||||
bool fDeviceReady;
|
||||
};
|
||||
|
||||
/*
|
||||
* There should be only one instance of this in the library (part of the
|
||||
* DiskImgLib Globals). It wouldn't actually break anything to have more than
|
||||
* one, but there's no need for it.
|
||||
*/
|
||||
class DISKIMG_API ASPI {
|
||||
public:
|
||||
ASPI(void) :
|
||||
fhASPI(NULL),
|
||||
GetASPI32SupportInfo(NULL),
|
||||
SendASPI32Command(NULL),
|
||||
GetASPI32DLLVersion(NULL),
|
||||
fASPIVersion(0),
|
||||
fHostAdapterCount(-1)
|
||||
{}
|
||||
virtual ~ASPI(void);
|
||||
|
||||
// load ASPI DLL if it exists
|
||||
DIError Init(void);
|
||||
|
||||
// return the version returned by the loaded DLL
|
||||
DWORD GetVersion(void) const {
|
||||
assert(fhASPI != NULL);
|
||||
return fASPIVersion;
|
||||
}
|
||||
|
||||
// Return an *array* of ASPIDevice structures for drives in system;
|
||||
// the caller is expected to delete[] it when done.
|
||||
enum { kDevMaskCDROM = 0x01, kDevMaskHardDrive = 0x02 };
|
||||
DIError GetAccessibleDevices(int deviceMask, ASPIDevice** ppDeviceArray,
|
||||
int* pNumDevices);
|
||||
|
||||
// Get the type of the device (DTYPE_*, 0x00-0x1f) using ASPI query
|
||||
DIError GetDeviceType(unsigned char adapter, unsigned char target,
|
||||
unsigned char lun, unsigned char* pType);
|
||||
// convert DTYPE_* to a string
|
||||
const char* DeviceTypeToString(unsigned char deviceType);
|
||||
|
||||
// Get the capacity, expressed as the highest-available LBA and the device
|
||||
// block size.
|
||||
DIError GetDeviceCapacity(unsigned char adapter, unsigned char target,
|
||||
unsigned char lun, unsigned long* pLastBlock, unsigned long* pBlockSize);
|
||||
|
||||
// Read blocks from the device.
|
||||
DIError ReadBlocks(unsigned char adapter, unsigned char target,
|
||||
unsigned char lun, long startBlock, short numBlocks, long blockSize,
|
||||
void* buf);
|
||||
|
||||
// Write blocks to the device.
|
||||
DIError WriteBlocks(unsigned char adapter, unsigned char target,
|
||||
unsigned char lun, long startBlock, short numBlocks, long blockSize,
|
||||
const void* buf);
|
||||
|
||||
private:
|
||||
/*
|
||||
* The interesting bits that come out of an SC_HA_INQUIRY request.
|
||||
*/
|
||||
typedef struct AdapterInfo {
|
||||
unsigned char adapterScsiID; // SCSI ID of the adapter itself
|
||||
unsigned char managerID[16+1]; // string describing manager
|
||||
unsigned char identifier[16+1]; // string describing host adapter
|
||||
unsigned char maxTargets; // max #of targets on this adapter
|
||||
unsigned short bufferAlignment; // buffer alignment requirement
|
||||
} AdapterInfo;
|
||||
/*
|
||||
* The interesting bits from a SCSI device INQUIRY command.
|
||||
*/
|
||||
typedef struct Inquiry {
|
||||
unsigned char vendorID[8+1]; // vendor ID string
|
||||
unsigned char productID[16+1]; // product ID string
|
||||
unsigned char productRevision[4]; // product revision bytes
|
||||
} Inquiry;
|
||||
|
||||
// Issue an ASPI adapter inquiry request.
|
||||
DIError HostAdapterInquiry(unsigned char adapter,
|
||||
AdapterInfo* pAdapterInfo);
|
||||
// Issue a SCSI device inquiry request.
|
||||
DIError DeviceInquiry(unsigned char adapter, unsigned char target,
|
||||
unsigned char lun, Inquiry* pInquiry);
|
||||
// Issue a SCSI test unit ready request.
|
||||
DIError TestUnitReady(unsigned char adapter, unsigned char target,
|
||||
unsigned char lun, bool* pReady);
|
||||
// execute a SCSI command
|
||||
DIError ExecSCSICommand(SRB_ExecSCSICmd* pSRB);
|
||||
|
||||
|
||||
enum {
|
||||
kMaxAdapters = 16,
|
||||
kMaxTargets = 16,
|
||||
kMaxLuns = 8,
|
||||
kTimeout = 30, // timeout, in seconds
|
||||
kMaxAccessibleDrives = 16,
|
||||
};
|
||||
|
||||
HMODULE fhASPI;
|
||||
DWORD (*GetASPI32SupportInfo)(void);
|
||||
DWORD (*SendASPI32Command)(void* lpsrb);
|
||||
DWORD (*GetASPI32DLLVersion)(void);
|
||||
DWORD fASPIVersion;
|
||||
int fHostAdapterCount;
|
||||
};
|
||||
|
||||
}; // namespace DiskImgLib
|
||||
|
||||
#endif /*__ASPI__*/
|
||||
|
||||
#endif /*_WIN32*/
|
591
diskimg/CFFA.cpp
Normal file
591
diskimg/CFFA.cpp
Normal file
@ -0,0 +1,591 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* The "CFFA" DiskFS is a container class for multiple ProDOS and HFS volumes.
|
||||
*
|
||||
* The CFFA card doesn't have any RAM, so the author used a fixed partitioning
|
||||
* scheme. You get 4 or 8 volumes -- depending on which firmware you jumper
|
||||
* in -- at 32MB each. CF cards usually hold less than you would expect, so
|
||||
* a 64MB card would have one 32MB volume and one less-than-32MB volume.
|
||||
*
|
||||
* With Dave's GS/OS driver, you get an extra drive or two at the end, at up
|
||||
* to 1GB each. The driver only works in 4-volume mode.
|
||||
*
|
||||
* There is no magic CFFA block at the front, so it looks like a plain
|
||||
* ProDOS or HFS volume. If the size is less than 32MB -- meaning there's
|
||||
* only one volume -- we don't need to take an interest in the file,
|
||||
* because the regular filesystem goodies will handle it just fine. If it's
|
||||
* more than 32MB, we need to create a structure in which multiple volumes
|
||||
* reside.
|
||||
*
|
||||
* The trick is finding all the volumes. The first four are easy. The
|
||||
* fifth one is either another 32MB volume (if you're in 8-volume mode)
|
||||
* or a volume whose size is somewhere between the amount of space left
|
||||
* and 1GB. Not an issue until we get to CF cards > 128MB. We have to
|
||||
* rely on the CFFA card making volumes as large as it can.
|
||||
*
|
||||
* I think it's reasonable to require that the first volume be either ProDOS
|
||||
* or HFS. That way we don't go digging through large non-CFFA files when
|
||||
* auto-probing.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
/*
|
||||
* Figure out if this is a CFFA volume, and if so, whether it was formatted
|
||||
* in 4-partition or 8-partition mode.
|
||||
*
|
||||
* The "imageOrder" parameter has no use here, because (in the current
|
||||
* version) embedded parent volumes are implicitly ProDOS-ordered.
|
||||
*
|
||||
* "*pFormatFound" should be either a CFFA format or "unknown" on entry.
|
||||
* If it's not "unknown", we will look for the specified format first.
|
||||
* Otherwise, we look for 4-partition then 8-partition. The first one
|
||||
* we find successfully is returned.
|
||||
*
|
||||
* Ideally we'd have some way to express ambiguity here, so that we could
|
||||
* force the "disk format verification" dialog to come up. No such
|
||||
* mechanism exists, and for now it doesn't seem worthwhile to add one.
|
||||
*/
|
||||
/*static*/ DIError DiskFSCFFA::TestImage(DiskImg* pImg,
|
||||
DiskImg::SectorOrder imageOrder, DiskImg::FSFormat* pFormatFound)
|
||||
{
|
||||
DIError dierr;
|
||||
long totalBlocks = pImg->GetNumBlocks();
|
||||
long startBlock, maxBlocks, totalBlocksLeft;
|
||||
long fsNumBlocks;
|
||||
DiskFS* pNewFS = NULL;
|
||||
DiskImg* pNewImg = NULL;
|
||||
//bool fiveIs32MB;
|
||||
|
||||
assert(totalBlocks > kEarlyVolExpectedSize);
|
||||
|
||||
// could be "generic" from an earlier override
|
||||
if (*pFormatFound != DiskImg::kFormatCFFA4 &&
|
||||
*pFormatFound != DiskImg::kFormatCFFA8)
|
||||
{
|
||||
*pFormatFound = DiskImg::kFormatUnknown;
|
||||
}
|
||||
|
||||
LOGI("----- BEGIN CFFA SCAN (fmt=%d) -----", *pFormatFound);
|
||||
|
||||
startBlock = 0;
|
||||
totalBlocksLeft = totalBlocks;
|
||||
|
||||
/*
|
||||
* Look for a 32MB ProDOS or HFS volume in the first slot. If we
|
||||
* don't find this, it's probably not CFFA. It's possible they just
|
||||
* didn't format the first one, but that seems unlikely, and it's not
|
||||
* unreasonable to insist that they format the first partition.
|
||||
*/
|
||||
maxBlocks = totalBlocksLeft;
|
||||
if (maxBlocks > kEarlyVolExpectedSize)
|
||||
maxBlocks = kEarlyVolExpectedSize;
|
||||
|
||||
dierr = OpenSubVolume(pImg, startBlock, maxBlocks, true,
|
||||
&pNewImg, &pNewFS);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" CFFA failed opening sub-volume #1");
|
||||
goto bail;
|
||||
}
|
||||
fsNumBlocks = pNewFS->GetFSNumBlocks();
|
||||
delete pNewFS;
|
||||
delete pNewImg;
|
||||
if (fsNumBlocks != kEarlyVolExpectedSize &&
|
||||
fsNumBlocks != kEarlyVolExpectedSize-1)
|
||||
{
|
||||
LOGI(" CFFA found fsNumBlocks=%ld in slot #1", fsNumBlocks);
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
goto bail;
|
||||
}
|
||||
LOGI(" CFFA found good volume in slot #1");
|
||||
|
||||
startBlock += maxBlocks;
|
||||
totalBlocksLeft -= maxBlocks;
|
||||
assert(totalBlocksLeft > 0);
|
||||
|
||||
/*
|
||||
* Look for a ProDOS or HFS volume <= 32MB in the second slot. If
|
||||
* we don't find something here, and this is a 64MB card, then there's
|
||||
* no advantage to using CFFA (in fact, the single-volume handling may
|
||||
* be more convenient). If there's at least another 32MB, we continue
|
||||
* looking.
|
||||
*/
|
||||
maxBlocks = totalBlocksLeft;
|
||||
if (maxBlocks > kEarlyVolExpectedSize)
|
||||
maxBlocks = kEarlyVolExpectedSize;
|
||||
|
||||
dierr = OpenSubVolume(pImg, startBlock, maxBlocks, true,
|
||||
&pNewImg, &pNewFS);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" CFFA failed opening sub-volume #2");
|
||||
if (maxBlocks < kEarlyVolExpectedSize)
|
||||
goto bail;
|
||||
// otherwise, assume they just didn't format #2, and keep going
|
||||
} else {
|
||||
fsNumBlocks = pNewFS->GetFSNumBlocks();
|
||||
delete pNewFS;
|
||||
delete pNewImg;
|
||||
#if 0
|
||||
if (fsNumBlocks != kEarlyVolExpectedSize &&
|
||||
fsNumBlocks != kEarlyVolExpectedSize-1)
|
||||
{
|
||||
LOGI(" CFFA found fsNumBlocks=%ld in slot #2", fsNumBlocks);
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
goto bail;
|
||||
}
|
||||
#endif
|
||||
LOGI(" CFFA found good volume in slot #2");
|
||||
}
|
||||
|
||||
startBlock += maxBlocks;
|
||||
totalBlocksLeft -= maxBlocks;
|
||||
if (totalBlocksLeft == 0) {
|
||||
*pFormatFound = DiskImg::kFormatCFFA4;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Skip #3 and #4.
|
||||
*/
|
||||
LOGI(" CFFA skipping over slot #3");
|
||||
maxBlocks = kEarlyVolExpectedSize*2;
|
||||
if (maxBlocks > totalBlocksLeft)
|
||||
maxBlocks = totalBlocksLeft;
|
||||
startBlock += maxBlocks;
|
||||
totalBlocksLeft -= maxBlocks;
|
||||
if (totalBlocksLeft == 0) {
|
||||
// no more partitions to find; we're done
|
||||
*pFormatFound = DiskImg::kFormatCFFA4;
|
||||
goto bail;
|
||||
}
|
||||
LOGI(" CFFA skipping over slot #4");
|
||||
|
||||
/*
|
||||
* Partition #5. We know where it starts, but not how large it is.
|
||||
* Could be 32MB, could be 1GB, could be anything between.
|
||||
*
|
||||
* CF cards come in power-of-two sizes -- 128MB, 256MB, etc. -- but
|
||||
* we don't want to make assumptions here. It's possible we're
|
||||
* looking at an odd-sized image file that some clever person is
|
||||
* expecting to access with CiderPress.
|
||||
*/
|
||||
maxBlocks = totalBlocksLeft;
|
||||
if (maxBlocks > kOneGB)
|
||||
maxBlocks = kOneGB;
|
||||
if (maxBlocks <= kEarlyVolExpectedSize) {
|
||||
/*
|
||||
* Only enough room for one <= 32MB volume. Not expected for a
|
||||
* real CFFA card, unless they come in 160MB sizes.
|
||||
*
|
||||
* Treat it like 4-partition; it'll look like somebody slapped a
|
||||
* 32MB volume into the first 1GB area.
|
||||
*/
|
||||
LOGI(" CFFA assuming odd-sized slot #5");
|
||||
*pFormatFound = DiskImg::kFormatCFFA4;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/*
|
||||
* We could be looking at a 32MB ProDOS partition, 32MB HFS partition,
|
||||
* or an up to 1GB HFS partition. We have to specify the size in
|
||||
* the OpenSubVolume request, which means trying it both ways and
|
||||
* finding a match from GetFSNumBlocks(). Complicating matters is
|
||||
* truncation (ProDOS max 65535, not 65536) and round-off (not sure
|
||||
* how HFS deals with left-over blocks).
|
||||
*
|
||||
* Start with <= 1GB.
|
||||
*/
|
||||
dierr = OpenSubVolume(pImg, startBlock, maxBlocks, true,
|
||||
&pNewImg, &pNewFS);
|
||||
if (dierr != kDIErrNone) {
|
||||
if (dierr == kDIErrCancelled)
|
||||
goto bail;
|
||||
LOGI(" CFFA failed opening large sub-volume #5");
|
||||
// if we can't get #5, don't bother looking for #6
|
||||
// (we could try anyway, but it's easier to just skip it)
|
||||
dierr = kDIErrNone;
|
||||
*pFormatFound = DiskImg::kFormatCFFA4;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
fsNumBlocks = pNewFS->GetFSNumBlocks();
|
||||
delete pNewFS;
|
||||
delete pNewImg;
|
||||
if (fsNumBlocks < 2 || fsNumBlocks > maxBlocks) {
|
||||
LOGI(" CFFA WARNING: FSNumBlocks #5 reported blocks=%ld",
|
||||
fsNumBlocks);
|
||||
}
|
||||
if (fsNumBlocks == kEarlyVolExpectedSize-1 ||
|
||||
fsNumBlocks == kEarlyVolExpectedSize)
|
||||
{
|
||||
LOGI(" CFFA #5 is a 32MB volume");
|
||||
// tells us nothing -- could still be 4 or 8, so we keep going
|
||||
maxBlocks = kEarlyVolExpectedSize;
|
||||
} else if (fsNumBlocks > kEarlyVolExpectedSize) {
|
||||
// must be a GS/OS 1GB area
|
||||
LOGI(" CFFA #5 is larger than 32MB");
|
||||
*pFormatFound = DiskImg::kFormatCFFA4;
|
||||
goto bail;
|
||||
} else {
|
||||
LOGI(" CFFA #5 was unexpectedly small (%ld blocks)", fsNumBlocks);
|
||||
// just stop now
|
||||
*pFormatFound = DiskImg::kFormatCFFA4;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
startBlock += maxBlocks;
|
||||
totalBlocksLeft -= maxBlocks;
|
||||
|
||||
if (!totalBlocksLeft) {
|
||||
LOGI(" CFFA got 5 volumes");
|
||||
*pFormatFound = DiskImg::kFormatCFFA4;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Various possibilities for slots 5 and up:
|
||||
* A. Card in 4-partition mode. 5th partition isn't formatted. Don't
|
||||
* bother looking for 6th. [already handled]
|
||||
* B. Card in 4-partition mode. 5th partition is >32MB HFS. 6th
|
||||
* partition will be at +1GB. [already handled]
|
||||
* C. Card in 4-partition mode. 5th partition is 32MB ProDOS. 6th
|
||||
* partition will be at +1GB.
|
||||
* D. Card in 8-partition mode. 5th partition is 32MB HFS. 6th
|
||||
* partition will be at +32MB.
|
||||
* E. Card in 8-partition mode. 5th partition is 32MB ProDOS. 6th
|
||||
* partition will be at +32MB.
|
||||
*
|
||||
* I'm ignoring D on the off chance somebody could create a 32MB HFS
|
||||
* partition in the 1GB space. D and E are handled alike.
|
||||
*
|
||||
* The difference between C and D/E can *usually* be determined by
|
||||
* opening up a 6th partition in the two expected locations.
|
||||
*/
|
||||
LOGI(" CFFA probing 6th slot for 4-vs-8");
|
||||
/*
|
||||
* Look in two different places. If we find something at the
|
||||
* +32MB mark, assume it's in "8 mode". If we find something at
|
||||
* the +1GB mark, assume it's in "4 + GS/OS mode". If both exist
|
||||
* we have a problem.
|
||||
*/
|
||||
bool foundSmall, foundGig;
|
||||
|
||||
foundSmall = false;
|
||||
maxBlocks = totalBlocksLeft;
|
||||
if (maxBlocks > kEarlyVolExpectedSize)
|
||||
maxBlocks = kEarlyVolExpectedSize;
|
||||
dierr = OpenSubVolume(pImg, startBlock + kEarlyVolExpectedSize,
|
||||
maxBlocks, true, &pNewImg, &pNewFS);
|
||||
if (dierr != kDIErrNone) {
|
||||
if (dierr == kDIErrCancelled)
|
||||
goto bail;
|
||||
LOGI(" CFFA no vol #6 found at +32MB");
|
||||
} else {
|
||||
foundSmall = true;
|
||||
delete pNewFS;
|
||||
delete pNewImg;
|
||||
}
|
||||
|
||||
foundGig = false;
|
||||
// no need to look if we don't have at least 1GB left!
|
||||
if (totalBlocksLeft >= kOneGB) {
|
||||
maxBlocks = totalBlocksLeft;
|
||||
if (maxBlocks > kOneGB)
|
||||
maxBlocks = kOneGB;
|
||||
dierr = OpenSubVolume(pImg, startBlock + kOneGB,
|
||||
maxBlocks, true, &pNewImg, &pNewFS);
|
||||
if (dierr != kDIErrNone) {
|
||||
if (dierr == kDIErrCancelled)
|
||||
goto bail;
|
||||
LOGI(" CFFA no vol #6 found at +1GB");
|
||||
} else {
|
||||
foundGig = true;
|
||||
delete pNewFS;
|
||||
delete pNewImg;
|
||||
}
|
||||
}
|
||||
|
||||
dierr = kDIErrNone;
|
||||
|
||||
if (!foundSmall && !foundGig) {
|
||||
LOGI(" CFFA no valid filesystem found in 6th position");
|
||||
*pFormatFound = DiskImg::kFormatCFFA4;
|
||||
// don't bother looking for 7 and 8
|
||||
} else if (foundSmall && foundGig) {
|
||||
LOGI(" CFFA WARNING: found valid volumes at +32MB *and* +1GB");
|
||||
// default to 4-partition mode
|
||||
if (*pFormatFound == DiskImg::kFormatUnknown)
|
||||
*pFormatFound = DiskImg::kFormatCFFA4;
|
||||
} else if (foundGig) {
|
||||
LOGI(" CFFA found 6th volume at +1GB, assuming 4-mode w/GSOS");
|
||||
if (fsNumBlocks < 2 || fsNumBlocks > kOneGB) {
|
||||
LOGI(" CFFA WARNING: FSNumBlocks #6 reported as %ld",
|
||||
fsNumBlocks);
|
||||
}
|
||||
*pFormatFound = DiskImg::kFormatCFFA4;
|
||||
} else if (foundSmall) {
|
||||
LOGI(" CFFA found 6th volume at +32MB, assuming 8-mode");
|
||||
if (fsNumBlocks < 2 || fsNumBlocks > kEarlyVolExpectedSize) {
|
||||
LOGI(" CFFA WARNING: FSNumBlocks #6 reported as %ld",
|
||||
fsNumBlocks);
|
||||
}
|
||||
*pFormatFound = DiskImg::kFormatCFFA8;
|
||||
} else {
|
||||
assert(false); // how'd we get here??
|
||||
}
|
||||
|
||||
// done!
|
||||
|
||||
bail:
|
||||
LOGI("----- END CFFA SCAN (err=%d format=%d) -----",
|
||||
dierr, *pFormatFound);
|
||||
if (dierr == kDIErrNone) {
|
||||
assert(*pFormatFound != DiskImg::kFormatUnknown);
|
||||
} else {
|
||||
*pFormatFound = DiskImg::kFormatUnknown;
|
||||
}
|
||||
return dierr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Open up a sub-volume.
|
||||
*
|
||||
* If "scanOnly" is set, the full DiskFS initialization isn't performed.
|
||||
* We just do enough to get the volume size info.
|
||||
*/
|
||||
/*static*/ DIError DiskFSCFFA::OpenSubVolume(DiskImg* pImg, long startBlock,
|
||||
long numBlocks, bool scanOnly, DiskImg** ppNewImg, DiskFS** ppNewFS)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
DiskFS* pNewFS = NULL;
|
||||
DiskImg* pNewImg = NULL;
|
||||
|
||||
pNewImg = new DiskImg;
|
||||
if (pNewImg == NULL) {
|
||||
dierr = kDIErrMalloc;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
dierr = pNewImg->OpenImage(pImg, startBlock, numBlocks);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" CFFASub: OpenImage(%ld,%ld) failed (err=%d)",
|
||||
startBlock, numBlocks, dierr);
|
||||
goto bail;
|
||||
}
|
||||
//LOGI(" +++ CFFASub: new image has ro=%d (parent=%d)",
|
||||
// pNewImg->GetReadOnly(), pImg->GetReadOnly());
|
||||
|
||||
dierr = pNewImg->AnalyzeImage();
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" CFFASub: analysis failed (err=%d)", dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
|
||||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
|
||||
{
|
||||
LOGI(" CFFASub: unable to identify filesystem at %ld",
|
||||
startBlock);
|
||||
dierr = kDIErrUnsupportedFSFmt;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* open a DiskFS for the sub-image */
|
||||
LOGI(" CFFASub (%ld,%ld) analyze succeeded!", startBlock, numBlocks);
|
||||
pNewFS = pNewImg->OpenAppropriateDiskFS();
|
||||
if (pNewFS == NULL) {
|
||||
LOGI(" CFFASub: OpenAppropriateDiskFS failed");
|
||||
dierr = kDIErrUnsupportedFSFmt;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* we encapsulate arbitrary stuff, so encourage child to scan */
|
||||
pNewFS->SetScanForSubVolumes(kScanSubEnabled);
|
||||
|
||||
/*
|
||||
* Load the files from the sub-image. When doing our initial tests,
|
||||
* or when loading data for the volume copier, we don't want to dig
|
||||
* into our sub-volumes, just figure out what they are and where.
|
||||
*/
|
||||
InitMode initMode;
|
||||
if (scanOnly)
|
||||
initMode = kInitHeaderOnly;
|
||||
else
|
||||
initMode = kInitFull;
|
||||
dierr = pNewFS->Initialize(pNewImg, initMode);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" CFFASub: error %d reading list of files from disk", dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
bail:
|
||||
if (dierr != kDIErrNone) {
|
||||
delete pNewFS;
|
||||
delete pNewImg;
|
||||
} else {
|
||||
*ppNewImg = pNewImg;
|
||||
*ppNewFS = pNewFS;
|
||||
}
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check to see if this is a CFFA volume.
|
||||
*/
|
||||
/*static*/ DIError DiskFSCFFA::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
||||
DiskImg::FSFormat* pFormat, FSLeniency leniency)
|
||||
{
|
||||
if (pImg->GetNumBlocks() < kMinInterestingBlocks)
|
||||
return kDIErrFilesystemNotFound;
|
||||
if (pImg->GetIsEmbedded()) // don't look for CFFA inside CFFA!
|
||||
return kDIErrFilesystemNotFound;
|
||||
|
||||
/* assume ProDOS -- shouldn't matter, since it's embedded */
|
||||
if (TestImage(pImg, DiskImg::kSectorOrderProDOS, pFormat) == kDIErrNone) {
|
||||
assert(*pFormat == DiskImg::kFormatCFFA4 ||
|
||||
*pFormat == DiskImg::kFormatCFFA8);
|
||||
*pOrder = DiskImg::kSectorOrderProDOS;
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
// make sure we didn't tamper with it
|
||||
assert(*pFormat == DiskImg::kFormatUnknown);
|
||||
|
||||
LOGI(" FS didn't find valid CFFA");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Prep the CFFA "container" for use.
|
||||
*/
|
||||
DIError DiskFSCFFA::Initialize(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
|
||||
LOGI("CFFA initializing (scanForSub=%d)", fScanForSubVolumes);
|
||||
|
||||
/* seems pointless *not* to, but we just do what we're told */
|
||||
if (fScanForSubVolumes != kScanSubDisabled) {
|
||||
dierr = FindSubVolumes();
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/* blank out the volume usage map */
|
||||
SetVolumeUsageMap();
|
||||
|
||||
return dierr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Find the various sub-volumes and open them.
|
||||
*
|
||||
* We don't handle the volume specially unless it's at least 32MB, which
|
||||
* means there are at least 2 partitions.
|
||||
*/
|
||||
DIError DiskFSCFFA::FindSubVolumes(void)
|
||||
{
|
||||
DIError dierr;
|
||||
long startBlock, blocksLeft;
|
||||
|
||||
startBlock = 0;
|
||||
blocksLeft = fpImg->GetNumBlocks();
|
||||
|
||||
if (fpImg->GetFSFormat() == DiskImg::kFormatCFFA4) {
|
||||
LOGI(" CFFA opening 4+2 volumes");
|
||||
dierr = AddVolumeSeries(0, 4, kEarlyVolExpectedSize, /*ref*/startBlock,
|
||||
/*ref*/blocksLeft);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
LOGI(" CFFA after first 4, startBlock=%ld blocksLeft=%ld",
|
||||
startBlock, blocksLeft);
|
||||
if (blocksLeft > 0) {
|
||||
dierr = AddVolumeSeries(4, 2, kOneGB, /*ref*/startBlock,
|
||||
/*ref*/blocksLeft);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
}
|
||||
} else if (fpImg->GetFSFormat() == DiskImg::kFormatCFFA8) {
|
||||
LOGI(" CFFA opening 8 volumes");
|
||||
dierr = AddVolumeSeries(0, 8, kEarlyVolExpectedSize, /*ref*/startBlock,
|
||||
/*ref*/blocksLeft);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
} else {
|
||||
assert(false);
|
||||
return kDIErrInternal;
|
||||
}
|
||||
|
||||
if (blocksLeft != 0) {
|
||||
LOGI(" CFFA ignoring leftover %ld blocks", blocksLeft);
|
||||
}
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a series of equal-sized volumes.
|
||||
*
|
||||
* Updates "startBlock" and "totalBlocksLeft".
|
||||
*/
|
||||
DIError DiskFSCFFA::AddVolumeSeries(int start, int count, long blocksPerVolume,
|
||||
long& startBlock, long& totalBlocksLeft)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
DiskFS* pNewFS = NULL;
|
||||
DiskImg* pNewImg = NULL;
|
||||
long maxBlocks, fsNumBlocks;
|
||||
bool scanOnly = false;
|
||||
|
||||
/* used by volume copier, to avoid deep scan */
|
||||
if (GetScanForSubVolumes() == kScanSubContainerOnly)
|
||||
scanOnly = true;
|
||||
|
||||
for (int i = start; i < start+count; i++) {
|
||||
maxBlocks = blocksPerVolume;
|
||||
if (maxBlocks > totalBlocksLeft)
|
||||
maxBlocks = totalBlocksLeft;
|
||||
|
||||
dierr = OpenSubVolume(fpImg, startBlock, maxBlocks, scanOnly,
|
||||
&pNewImg, &pNewFS);
|
||||
if (dierr != kDIErrNone) {
|
||||
if (dierr == kDIErrCancelled)
|
||||
goto bail;
|
||||
LOGI(" CFFA failed opening sub-volume %d (not formatted?)", i);
|
||||
/* create a fake one to represent the partition */
|
||||
dierr = CreatePlaceholder(startBlock, maxBlocks, NULL, NULL,
|
||||
&pNewImg, &pNewFS);
|
||||
if (dierr == kDIErrNone) {
|
||||
AddSubVolumeToList(pNewImg, pNewFS);
|
||||
} else {
|
||||
LOGI(" CFFA unable to create placeholder (%ld, %ld) (err=%d)",
|
||||
startBlock, maxBlocks, dierr);
|
||||
goto bail;
|
||||
}
|
||||
} else {
|
||||
fsNumBlocks = pNewFS->GetFSNumBlocks();
|
||||
if (fsNumBlocks < 2 || fsNumBlocks > blocksPerVolume) {
|
||||
LOGI(" CFFA WARNING: FSNumBlocks #%d reported as %ld",
|
||||
i, fsNumBlocks);
|
||||
}
|
||||
AddSubVolumeToList(pNewImg, pNewFS);
|
||||
}
|
||||
|
||||
startBlock += maxBlocks;
|
||||
totalBlocksLeft -= maxBlocks;
|
||||
if (!totalBlocksLeft)
|
||||
break; // all done
|
||||
}
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
75
diskimg/CMakeLists.txt
Normal file
75
diskimg/CMakeLists.txt
Normal file
@ -0,0 +1,75 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
set(CMAKE_BUILD_TYPE DEBUG)
|
||||
|
||||
set(BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
set(PROJECT_NAME diskimg)
|
||||
set(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
project(${PROJECT_NAME})
|
||||
|
||||
|
||||
set(ALL_DEFINES " " )
|
||||
set(DEBUG_OPT "-D_DEBUG -DDEBUG -O0 -g3 " )
|
||||
set(RELEASE_OPT "-O3 " )
|
||||
|
||||
set(CMAKE_C_FLAGS "-Wall ${ALL_DEFINES} ")
|
||||
set(CMAKE_CXX_FLAGS "-Wall ${ALL_DEFINES} ")
|
||||
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${DEBUG_OPT} ")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${RELEASE_OPT}")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${DEBUG_OPT} ")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${RELEASE_OPT}")
|
||||
|
||||
set(FIND_LIBRARY_USE_LIB64_PATHS TRUE)
|
||||
|
||||
#add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/libhfs)
|
||||
|
||||
set (SOURCE
|
||||
ASPI.cpp
|
||||
DiskFS.cpp
|
||||
FAT.cpp
|
||||
Gutenberg.cpp
|
||||
Nibble35.cpp
|
||||
ProDOS.cpp
|
||||
UNIDOS.cpp
|
||||
CFFA.cpp
|
||||
DiskImg.cpp
|
||||
FDI.cpp
|
||||
HFS.cpp
|
||||
Nibble.cpp
|
||||
RDOS.cpp
|
||||
VolumeUsage.cpp
|
||||
Container.cpp
|
||||
DIUtil.cpp
|
||||
FocusDrive.cpp
|
||||
ImageWrapper.cpp
|
||||
OuterWrapper.cpp
|
||||
SPTI.cpp
|
||||
Win32BlockIO.cpp
|
||||
CPM.cpp
|
||||
DOS33.cpp
|
||||
GenericFD.cpp
|
||||
MacPart.cpp
|
||||
OzDOS.cpp
|
||||
StdAfx.cpp
|
||||
DDD.cpp
|
||||
DOSImage.cpp
|
||||
Global.cpp
|
||||
MicroDrive.cpp
|
||||
Pascal.cpp
|
||||
TwoImg.cpp
|
||||
)
|
||||
|
||||
include_directories(BEFORE
|
||||
${PROJECT_ROOT}
|
||||
)
|
||||
|
||||
add_library( ${PROJECT_NAME} SHARED ${SOURCE})
|
||||
add_library( ${PROJECT_NAME}_static STATIC ${SOURCE})
|
||||
|
||||
target_link_libraries (
|
||||
${PROJECT_NAME}
|
||||
)
|
||||
|
||||
|
||||
|
763
diskimg/CPM.cpp
Normal file
763
diskimg/CPM.cpp
Normal file
@ -0,0 +1,763 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Apple II CP/M disk format.
|
||||
*
|
||||
* Limitations:
|
||||
* - Read-only.
|
||||
* - Does not do much with user numbers.
|
||||
* - Rumor has it that "sparse" files are possible. Not handled.
|
||||
* - I'm currently treating the directory as fixed-length. This may
|
||||
* not be correct.
|
||||
* - Not handling special entries (volume label, date stamps,
|
||||
* password control).
|
||||
*
|
||||
* As I have no practical experience with CP/M, this is the weakest of the
|
||||
* filesystem implementations.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* DiskFSCPM
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
const int kBlkSize = 512; // really ought to be 1024
|
||||
const int kVolDirBlock = 24; // track 3 sector 0
|
||||
const int kVolDirCount = 4; // 4 prodos blocks
|
||||
const int kNoDataByte = 0xe5;
|
||||
const int kMaxUserNumber = 31; // 0-15 on some systems, 0-31 on others
|
||||
const int kMaxSpecialUserNumber = 0x21; // 0x20 and 0x21 have special meanings
|
||||
const int kMaxExtent = 31; // extent counter, 0-31
|
||||
|
||||
/*
|
||||
* See if this looks like a CP/M volume.
|
||||
*
|
||||
* We test a few fields in the volume directory for validity.
|
||||
*/
|
||||
static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t dirBuf[kBlkSize * kVolDirCount];
|
||||
uint8_t* dptr;
|
||||
int i;
|
||||
|
||||
assert(sizeof(dirBuf) == DiskFSCPM::kFullDirSize);
|
||||
|
||||
for (i = 0; i < kVolDirCount; i++) {
|
||||
dierr = pImg->ReadBlockSwapped(kVolDirBlock + i, dirBuf + kBlkSize*i,
|
||||
imageOrder, DiskImg::kSectorOrderCPM);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
}
|
||||
|
||||
dptr = dirBuf;
|
||||
for (i = 0; i < DiskFSCPM::kFullDirSize/DiskFSCPM::kDirectoryEntryLen; i++)
|
||||
{
|
||||
if (*dptr != kNoDataByte) {
|
||||
/*
|
||||
* Usually userNumber is 0, but sometimes not. It's expected to
|
||||
* be < 0x20 for a normal file, may be 0x21 or 0x22 for special
|
||||
* entries (volume label, date stamps).
|
||||
*/
|
||||
if (*dptr > kMaxSpecialUserNumber) {
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
break;
|
||||
}
|
||||
|
||||
/* extent counter, 0-31 */
|
||||
if (dptr[12] > kMaxExtent) {
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
break;
|
||||
}
|
||||
|
||||
/* check for a valid filename here; high bit may be set on some bytes */
|
||||
uint8_t firstLet = *(dptr+1) & 0x7f;
|
||||
if (firstLet < 0x20) {
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
break;
|
||||
}
|
||||
}
|
||||
dptr += DiskFSCPM::kDirectoryEntryLen;
|
||||
}
|
||||
if (dierr == kDIErrNone) {
|
||||
LOGI(" CPM found clean directory, imageOrder=%d", imageOrder);
|
||||
}
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test to see if the image is a CP/M disk.
|
||||
*
|
||||
* On the Apple II, these were always on 5.25" disks. However, it's possible
|
||||
* to create hard drive volumes up to 8MB.
|
||||
*/
|
||||
/*static*/ DIError DiskFSCPM::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
||||
DiskImg::FSFormat* pFormat, FSLeniency leniency)
|
||||
{
|
||||
/* CP/M disks use 1K blocks, so ignore anything with odd count */
|
||||
if (pImg->GetNumBlocks() == 0 ||
|
||||
(pImg->GetNumBlocks() & 0x01) != 0)
|
||||
{
|
||||
LOGI(" CPM rejecting image with numBlocks=%ld",
|
||||
pImg->GetNumBlocks());
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
|
||||
|
||||
DiskImg::GetSectorOrderArray(ordering, *pOrder);
|
||||
|
||||
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
|
||||
if (ordering[i] == DiskImg::kSectorOrderUnknown)
|
||||
continue;
|
||||
if (TestImage(pImg, ordering[i]) == kDIErrNone) {
|
||||
*pOrder = ordering[i];
|
||||
*pFormat = DiskImg::kFormatCPM;
|
||||
return kDIErrNone;
|
||||
}
|
||||
}
|
||||
|
||||
LOGI(" CPM didn't find valid FS");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get things rolling.
|
||||
*
|
||||
* Since we're assured that this is a valid disk, errors encountered from here
|
||||
* on out must be handled somehow, possibly by claiming that the disk is
|
||||
* completely full and has no files on it.
|
||||
*/
|
||||
DIError DiskFSCPM::Initialize(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
|
||||
dierr = ReadCatalog();
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
fVolumeUsage.Create(fpImg->GetNumBlocks());
|
||||
dierr = ScanFileUsage();
|
||||
if (dierr != kDIErrNone) {
|
||||
/* this might not be fatal; just means that *some* files are bad */
|
||||
dierr = kDIErrNone;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
fDiskIsGood = CheckDiskIsGood();
|
||||
|
||||
fVolumeUsage.Dump();
|
||||
|
||||
//A2File* pFile;
|
||||
//pFile = GetNextFile(NULL);
|
||||
//while (pFile != NULL) {
|
||||
// pFile->Dump();
|
||||
// pFile = GetNextFile(pFile);
|
||||
//}
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the entire CP/M catalog (all 2K of it) into memory, and parse
|
||||
* out the individual files.
|
||||
*
|
||||
* A single file can have more than one directory entry. We only want
|
||||
* to create an A2File object for the first one.
|
||||
*/
|
||||
DIError DiskFSCPM::ReadCatalog(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t dirBuf[kFullDirSize];
|
||||
uint8_t* dptr;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < kVolDirCount; i++) {
|
||||
dierr = fpImg->ReadBlock(kVolDirBlock + i, dirBuf + kBlkSize*i);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
}
|
||||
|
||||
dptr = dirBuf;
|
||||
for (i = 0; i < kNumDirEntries; i++) {
|
||||
fDirEntry[i].userNumber = dptr[0x00];
|
||||
/* copy the filename, stripping the high bits off */
|
||||
for (int j = 0; j < kDirFileNameLen; j++)
|
||||
fDirEntry[i].fileName[j] = dptr[0x01 + j] & 0x7f;
|
||||
fDirEntry[i].fileName[kDirFileNameLen] = '\0';
|
||||
fDirEntry[i].extent = dptr[0x0c] + dptr[0x0e] * kExtentsInLowByte;
|
||||
fDirEntry[i].S1 = dptr[0x0d];
|
||||
fDirEntry[i].records = dptr[0x0f];
|
||||
memcpy(fDirEntry[i].blocks, &dptr[0x10], kDirEntryBlockCount);
|
||||
fDirEntry[i].readOnly = (dptr[0x09] & 0x80) != 0;
|
||||
fDirEntry[i].system = (dptr[0x0a] & 0x80) != 0;
|
||||
fDirEntry[i].badBlockList = false; // set if block list is bad
|
||||
|
||||
dptr += kDirectoryEntryLen;
|
||||
}
|
||||
|
||||
/* create an entry for the first extent of each file */
|
||||
for (i = 0; i < kNumDirEntries; i++) {
|
||||
A2FileCPM* pFile;
|
||||
|
||||
if (fDirEntry[i].userNumber == kNoDataByte || fDirEntry[i].extent != 0)
|
||||
continue;
|
||||
if (fDirEntry[i].userNumber > kMaxUserNumber) {
|
||||
/* skip over volume label, date stamps, etc */
|
||||
LOGI("Skipping entry with userNumber=0x%02x",
|
||||
fDirEntry[i].userNumber);
|
||||
}
|
||||
|
||||
pFile = new A2FileCPM(this, fDirEntry);
|
||||
FormatName(pFile->fFileName, (char*)fDirEntry[i].fileName);
|
||||
pFile->fReadOnly = fDirEntry[i].readOnly;
|
||||
pFile->fDirIdx = i;
|
||||
|
||||
pFile->fLength = 0;
|
||||
dierr = ComputeLength(pFile);
|
||||
if (dierr != kDIErrNone) {
|
||||
pFile->SetQuality(A2File::kQualityDamaged);
|
||||
dierr = kDIErrNone;
|
||||
}
|
||||
AddFileToList(pFile);
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate the list of blocks.
|
||||
*/
|
||||
int maxCpmBlock;
|
||||
maxCpmBlock = (fpImg->GetNumBlocks() - kVolDirBlock) / 2;
|
||||
for (i = 0; i < kNumDirEntries; i++) {
|
||||
if (fDirEntry[i].userNumber == kNoDataByte)
|
||||
continue;
|
||||
for (int j = 0; j < kDirEntryBlockCount; j++) {
|
||||
if (fDirEntry[i].blocks[j] >= maxCpmBlock) {
|
||||
LOGI(" CPM invalid block %d in file '%s'",
|
||||
fDirEntry[i].blocks[j], fDirEntry[i].fileName);
|
||||
//pFile->SetQuality(A2File::kQualityDamaged);
|
||||
fDirEntry[i].badBlockList = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reformat from 11 chars with spaces into clean xxxxx.yyy format.
|
||||
*/
|
||||
void DiskFSCPM::FormatName(char* dstBuf, const char* srcBuf)
|
||||
{
|
||||
char workBuf[kDirFileNameLen+1];
|
||||
char* cp;
|
||||
|
||||
assert(strlen(srcBuf) < sizeof(workBuf));
|
||||
strcpy(workBuf, srcBuf);
|
||||
|
||||
cp = workBuf;
|
||||
while (*cp != '\0') {
|
||||
//*cp &= 0x7f; // [no longer necessary]
|
||||
if (*cp == ' ')
|
||||
*cp = '\0';
|
||||
if (*cp == ':') // don't think this is allowed, but check
|
||||
*cp = 'X'; // for it anyway
|
||||
cp++;
|
||||
}
|
||||
|
||||
strcpy(dstBuf, workBuf);
|
||||
dstBuf[8] = '\0'; // in case filename part is full 8 chars
|
||||
strcat(dstBuf, ".");
|
||||
strcat(dstBuf, workBuf+8);
|
||||
|
||||
assert(strlen(dstBuf) <= A2FileCPM::kMaxFileName);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compute the length of a file. Sets "pFile->fLength".
|
||||
*
|
||||
* This requires walking through the list of extents and looking for the
|
||||
* last one. We use the "records" field of the last extent to determine
|
||||
* the file length.
|
||||
*
|
||||
* (Should probably just get the block list and then walk that, rather than
|
||||
* having directory parse code in two places.)
|
||||
*/
|
||||
DIError DiskFSCPM::ComputeLength(A2FileCPM* pFile)
|
||||
{
|
||||
int i;
|
||||
int best, maxExtent;
|
||||
|
||||
best = maxExtent = -1;
|
||||
|
||||
for (i = 0; i < DiskFSCPM::kNumDirEntries; i++) {
|
||||
if (fDirEntry[i].userNumber == kNoDataByte)
|
||||
continue;
|
||||
|
||||
if (strcmp((const char*)fDirEntry[i].fileName,
|
||||
(const char*)fDirEntry[pFile->fDirIdx].fileName) == 0 &&
|
||||
fDirEntry[i].userNumber == fDirEntry[pFile->fDirIdx].userNumber)
|
||||
{
|
||||
/* this entry is part of the file */
|
||||
if (fDirEntry[i].extent > maxExtent) {
|
||||
best = i;
|
||||
maxExtent = fDirEntry[i].extent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (maxExtent < 0 || best < 0) {
|
||||
LOGI(" CPM couldn't find existing file '%s'!", pFile->fFileName);
|
||||
assert(false);
|
||||
return kDIErrInternal;
|
||||
}
|
||||
|
||||
pFile->fLength = kDirEntryBlockCount * 1024 * maxExtent +
|
||||
fDirEntry[best].records * 128;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Scan file usage into the volume usage map.
|
||||
*
|
||||
* Tracks 0, 1, and 2 are always used by the boot loader. The volume directory
|
||||
* is on the first half of track 3 (blocks 0 and 1).
|
||||
*/
|
||||
DIError DiskFSCPM::ScanFileUsage(void)
|
||||
{
|
||||
int cpmBlock;
|
||||
int i, j;
|
||||
|
||||
for (i = 0; i < kVolDirBlock; i++)
|
||||
SetBlockUsage(i, VolumeUsage::kChunkPurposeSystem);
|
||||
for (i = kVolDirBlock; i < kVolDirBlock + kVolDirCount; i++)
|
||||
SetBlockUsage(i, VolumeUsage::kChunkPurposeVolumeDir);
|
||||
|
||||
for (i = 0; i < kNumDirEntries; i++) {
|
||||
if (fDirEntry[i].userNumber == kNoDataByte)
|
||||
continue;
|
||||
if (fDirEntry[i].badBlockList)
|
||||
continue;
|
||||
|
||||
for (j = 0; j < kDirEntryBlockCount; j++) {
|
||||
cpmBlock = fDirEntry[i].blocks[j];
|
||||
if (cpmBlock == 0)
|
||||
break;
|
||||
SetBlockUsage(CPMToProDOSBlock(cpmBlock),
|
||||
VolumeUsage::kChunkPurposeUserData);
|
||||
SetBlockUsage(CPMToProDOSBlock(cpmBlock)+1,
|
||||
VolumeUsage::kChunkPurposeUserData);
|
||||
}
|
||||
}
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update an entry in the usage map.
|
||||
*
|
||||
* "block" is a 512-byte block, so you will have to call here twice for every
|
||||
* 1K CP/M block.
|
||||
*/
|
||||
void DiskFSCPM::SetBlockUsage(long block, VolumeUsage::ChunkPurpose purpose)
|
||||
{
|
||||
VolumeUsage::ChunkState cstate;
|
||||
|
||||
if (fVolumeUsage.GetChunkState(block, &cstate) != kDIErrNone) {
|
||||
LOGI(" CPM ERROR: unable to set state on block %ld", block);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cstate.isUsed) {
|
||||
cstate.purpose = VolumeUsage::kChunkPurposeConflict;
|
||||
LOGI(" CPM conflicting uses for block=%ld", block);
|
||||
} else {
|
||||
cstate.isUsed = true;
|
||||
cstate.isMarkedUsed = true; // no volume bitmap
|
||||
cstate.purpose = purpose;
|
||||
}
|
||||
fVolumeUsage.SetChunkState(block, &cstate);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Scan for damaged files and conflicting file allocation entries.
|
||||
*
|
||||
* Appends some entries to the DiskImg notes, so this should only be run
|
||||
* once per DiskFS.
|
||||
*
|
||||
* Returns "true" if disk appears to be perfect, "false" otherwise.
|
||||
*/
|
||||
bool DiskFSCPM::CheckDiskIsGood(void)
|
||||
{
|
||||
//DIError dierr;
|
||||
bool result = true;
|
||||
|
||||
//if (fEarlyDamage)
|
||||
// result = false;
|
||||
|
||||
/*
|
||||
* TO DO: look for multiple files occupying the same blocks.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Scan for "damaged" or "suspicious" files diagnosed earlier.
|
||||
*/
|
||||
bool damaged, suspicious;
|
||||
ScanForDamagedFiles(&damaged, &suspicious);
|
||||
|
||||
if (damaged) {
|
||||
fpImg->AddNote(DiskImg::kNoteWarning,
|
||||
"One or more files are damaged.");
|
||||
result = false;
|
||||
} else if (suspicious) {
|
||||
fpImg->AddNote(DiskImg::kNoteWarning,
|
||||
"One or more files look suspicious.");
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* A2FileCPM
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Not a whole lot to do, since there's no fancy index blocks.
|
||||
*
|
||||
* Calling GetBlockList twice is probably not the best way to go through life.
|
||||
* This needs an overhaul.
|
||||
*/
|
||||
DIError A2FileCPM::Open(A2FileDescr** ppOpenFile, bool readOnly,
|
||||
bool rsrcFork /*=false*/)
|
||||
{
|
||||
DIError dierr;
|
||||
A2FDCPM* pOpenFile = NULL;
|
||||
|
||||
if (fpOpenFile != NULL)
|
||||
return kDIErrAlreadyOpen;
|
||||
if (rsrcFork)
|
||||
return kDIErrForkNotFound;
|
||||
|
||||
assert(readOnly);
|
||||
|
||||
pOpenFile = new A2FDCPM(this);
|
||||
|
||||
dierr = GetBlockList(&pOpenFile->fBlockCount, NULL);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
pOpenFile->fBlockList = new uint8_t[pOpenFile->fBlockCount+1];
|
||||
pOpenFile->fBlockList[pOpenFile->fBlockCount] = 0xff;
|
||||
|
||||
dierr = GetBlockList(&pOpenFile->fBlockCount, pOpenFile->fBlockList);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
assert(pOpenFile->fBlockList[pOpenFile->fBlockCount] == 0xff);
|
||||
|
||||
pOpenFile->fOffset = 0;
|
||||
//fOpen = true;
|
||||
|
||||
fpOpenFile = pOpenFile;
|
||||
*ppOpenFile = pOpenFile;
|
||||
pOpenFile = NULL;
|
||||
|
||||
bail:
|
||||
delete pOpenFile;
|
||||
return dierr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Get the complete block list for a file. This will involve reading
|
||||
* one or more directory entries.
|
||||
*
|
||||
* Call this once with "blockBuf" equal to "NULL" to get the block count,
|
||||
* then call a second time after allocating blockBuf.
|
||||
*/
|
||||
DIError A2FileCPM::GetBlockList(long* pBlockCount, uint8_t* blockBuf) const
|
||||
{
|
||||
di_off_t length = fLength;
|
||||
int blockCount = 0;
|
||||
int i, j;
|
||||
|
||||
/*
|
||||
* Run through the entries, pulling blocks out until we account for the
|
||||
* entire length of the file.
|
||||
*
|
||||
* [Should probably pay more attention to extent numbers, making sure
|
||||
* that they make sense. Not vital until we allow writes.]
|
||||
*/
|
||||
for (i = 0; i < DiskFSCPM::kNumDirEntries; i++) {
|
||||
if (length <= 0)
|
||||
break;
|
||||
if (fpDirEntry[i].userNumber == kNoDataByte)
|
||||
continue;
|
||||
|
||||
if (strcmp((const char*)fpDirEntry[i].fileName,
|
||||
(const char*)fpDirEntry[fDirIdx].fileName) == 0 &&
|
||||
fpDirEntry[i].userNumber == fpDirEntry[fDirIdx].userNumber)
|
||||
{
|
||||
/* this entry is part of the file */
|
||||
for (j = 0; j < DiskFSCPM::kDirEntryBlockCount; j++) {
|
||||
if (fpDirEntry[i].blocks[j] == 0) {
|
||||
LOGI(" CPM found sparse block %d/%d", i, j);
|
||||
}
|
||||
blockCount++;
|
||||
|
||||
if (blockBuf != NULL) {
|
||||
long listOffset = j +
|
||||
fpDirEntry[i].extent * DiskFSCPM::kDirEntryBlockCount;
|
||||
blockBuf[listOffset] = fpDirEntry[i].blocks[j];
|
||||
}
|
||||
|
||||
length -= 1024;
|
||||
if (length <= 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (length > 0) {
|
||||
LOGI(" CPM WARNING: can't account for %ld bytes!", (long) length);
|
||||
//assert(false);
|
||||
}
|
||||
|
||||
//LOGI(" Returning blockCount=%d for '%s'", blockCount,
|
||||
// fpDirEntry[fDirIdx].fileName);
|
||||
if (pBlockCount != NULL) {
|
||||
assert(blockBuf == NULL || *pBlockCount == blockCount);
|
||||
*pBlockCount = blockCount;
|
||||
}
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dump the contents of the A2File structure.
|
||||
*/
|
||||
void A2FileCPM::Dump(void) const
|
||||
{
|
||||
LOGI("A2FileCPM '%s' length=%ld", fFileName, (long) fLength);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* A2FDCPM
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Read a chunk of data from the current offset.
|
||||
*/
|
||||
DIError A2FDCPM::Read(void* buf, size_t len, size_t* pActual)
|
||||
{
|
||||
LOGI(" CP/M reading %lu bytes from '%s' (offset=%ld)",
|
||||
(unsigned long) len, fpFile->GetPathName(), (long) fOffset);
|
||||
|
||||
A2FileCPM* pFile = (A2FileCPM*) fpFile;
|
||||
|
||||
/* don't allow them to read past the end of the file */
|
||||
if (fOffset + (long)len > pFile->fLength) {
|
||||
if (pActual == NULL)
|
||||
return kDIErrDataUnderrun;
|
||||
len = (size_t) (pFile->fLength - fOffset);
|
||||
}
|
||||
if (pActual != NULL)
|
||||
*pActual = len;
|
||||
long incrLen = len;
|
||||
|
||||
DIError dierr = kDIErrNone;
|
||||
const int kCPMBlockSize = kBlkSize*2;
|
||||
assert(kCPMBlockSize == 1024);
|
||||
uint8_t blkBuf[kCPMBlockSize];
|
||||
int blkIndex = (int) (fOffset / kCPMBlockSize);
|
||||
int bufOffset = (int) (fOffset % kCPMBlockSize); // (& 0x3ff)
|
||||
size_t thisCount;
|
||||
long prodosBlock;
|
||||
|
||||
if (len == 0)
|
||||
return kDIErrNone;
|
||||
assert(pFile->fLength != 0);
|
||||
|
||||
while (len) {
|
||||
if (blkIndex >= fBlockCount) {
|
||||
/* ran out of data */
|
||||
return kDIErrDataUnderrun;
|
||||
}
|
||||
|
||||
if (fBlockList[blkIndex] == 0) {
|
||||
/*
|
||||
* Sparse block.
|
||||
*/
|
||||
memset(blkBuf, kNoDataByte, sizeof(blkBuf));
|
||||
} else {
|
||||
/*
|
||||
* Read one CP/M block (two ProDOS blocks) and pull out the
|
||||
* set of data that the user wants.
|
||||
*
|
||||
* On some Microsoft Softcard disks, the first three tracks hold
|
||||
* file data rather than the system image.
|
||||
*/
|
||||
prodosBlock = DiskFSCPM::CPMToProDOSBlock(fBlockList[blkIndex]);
|
||||
if (prodosBlock >= 280)
|
||||
prodosBlock -= 280;
|
||||
|
||||
dierr = fpFile->GetDiskFS()->GetDiskImg()->ReadBlock(prodosBlock,
|
||||
blkBuf);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" CP/M error1 reading file '%s'", pFile->fFileName);
|
||||
return dierr;
|
||||
}
|
||||
dierr = fpFile->GetDiskFS()->GetDiskImg()->ReadBlock(prodosBlock+1,
|
||||
blkBuf + kBlkSize);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" CP/M error2 reading file '%s'", pFile->fFileName);
|
||||
return dierr;
|
||||
}
|
||||
}
|
||||
|
||||
thisCount = kCPMBlockSize - bufOffset;
|
||||
if (thisCount > len)
|
||||
thisCount = len;
|
||||
|
||||
memcpy(buf, blkBuf + bufOffset, thisCount);
|
||||
len -= thisCount;
|
||||
buf = (char*)buf + thisCount;
|
||||
|
||||
bufOffset = 0;
|
||||
blkIndex++;
|
||||
}
|
||||
|
||||
fOffset += incrLen;
|
||||
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write data at the current offset.
|
||||
*/
|
||||
DIError A2FDCPM::Write(const void* buf, size_t len, size_t* pActual)
|
||||
{
|
||||
return kDIErrNotSupported;
|
||||
}
|
||||
|
||||
/*
|
||||
* Seek to a new offset.
|
||||
*/
|
||||
DIError A2FDCPM::Seek(di_off_t offset, DIWhence whence)
|
||||
{
|
||||
di_off_t fileLength = ((A2FileCPM*) fpFile)->fLength;
|
||||
|
||||
switch (whence) {
|
||||
case kSeekSet:
|
||||
if (offset < 0 || offset > fileLength)
|
||||
return kDIErrInvalidArg;
|
||||
fOffset = offset;
|
||||
break;
|
||||
case kSeekEnd:
|
||||
if (offset > 0 || offset < -fileLength)
|
||||
return kDIErrInvalidArg;
|
||||
fOffset = fileLength + offset;
|
||||
break;
|
||||
case kSeekCur:
|
||||
if (offset < -fOffset ||
|
||||
offset >= (fileLength - fOffset))
|
||||
{
|
||||
return kDIErrInvalidArg;
|
||||
}
|
||||
fOffset += offset;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
return kDIErrInvalidArg;
|
||||
}
|
||||
|
||||
assert(fOffset >= 0 && fOffset <= fileLength);
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return current offset.
|
||||
*/
|
||||
di_off_t A2FDCPM::Tell(void)
|
||||
{
|
||||
return fOffset;
|
||||
}
|
||||
|
||||
/*
|
||||
* Release file state, such as it is.
|
||||
*/
|
||||
DIError A2FDCPM::Close(void)
|
||||
{
|
||||
fpFile->CloseDescr(this);
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the #of sectors/blocks in the file.
|
||||
*/
|
||||
long A2FDCPM::GetSectorCount(void) const
|
||||
{
|
||||
return fBlockCount * 4;
|
||||
}
|
||||
long A2FDCPM::GetBlockCount(void) const
|
||||
{
|
||||
return fBlockCount * 2;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the Nth track/sector in this file.
|
||||
*/
|
||||
DIError A2FDCPM::GetStorage(long sectorIdx, long* pTrack, long* pSector) const
|
||||
{
|
||||
long cpmIdx = sectorIdx / 4; // 4 256-byte sectors per 1K CP/M block
|
||||
if (cpmIdx >= fBlockCount)
|
||||
return kDIErrInvalidIndex; // CP/M files can have *no* storage
|
||||
|
||||
long cpmBlock = fBlockList[cpmIdx];
|
||||
long prodosBlock = DiskFSCPM::CPMToProDOSBlock(cpmBlock);
|
||||
if (sectorIdx & 0x02)
|
||||
prodosBlock++;
|
||||
|
||||
BlockToTrackSector(prodosBlock, (sectorIdx & 0x01) != 0, pTrack, pSector);
|
||||
return kDIErrNone;
|
||||
}
|
||||
/*
|
||||
* Return the Nth 512-byte block in this file. Since things aren't stored
|
||||
* in 512-byte blocks, we grab the appropriate 1K block and pick half.
|
||||
*/
|
||||
DIError A2FDCPM::GetStorage(long blockIdx, long* pBlock) const
|
||||
{
|
||||
long cpmIdx = blockIdx / 2; // 4 256-byte sectors per 1K CP/M block
|
||||
if (cpmIdx >= fBlockCount)
|
||||
return kDIErrInvalidIndex;
|
||||
|
||||
long cpmBlock = fBlockList[cpmIdx];
|
||||
long prodosBlock = DiskFSCPM::CPMToProDOSBlock(cpmBlock);
|
||||
if (blockIdx & 0x01)
|
||||
prodosBlock++;
|
||||
|
||||
*pBlock = prodosBlock;
|
||||
assert(*pBlock < fpFile->GetDiskFS()->GetDiskImg()->GetNumBlocks());
|
||||
return kDIErrNone;
|
||||
}
|
325
diskimg/CP_WNASPI32.H
Normal file
325
diskimg/CP_WNASPI32.H
Normal file
@ -0,0 +1,325 @@
|
||||
/******************************************************************************
|
||||
**
|
||||
** Module Name: wnaspi32.h
|
||||
**
|
||||
** Description: Header file for ASPI for Win32. This header includes
|
||||
** macro and type declarations, and can be included without
|
||||
** modification when using Borland C++ or Microsoft Visual
|
||||
** C++ with 32-bit compilation. If you are using a different
|
||||
** compiler then you MUST ensure that structures are packed
|
||||
** onto byte alignments, and that C++ name mangling is turned
|
||||
** off.
|
||||
**
|
||||
** Notes: This file created using 4 spaces per tab.
|
||||
**
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef DISKIMG_WNASPI32_H
|
||||
#define DISKIMG_WNASPI32_H
|
||||
|
||||
/*
|
||||
** Make sure structures are packed and undecorated.
|
||||
*/
|
||||
|
||||
#ifdef __BORLANDC__
|
||||
#pragma option -a1
|
||||
#endif //__BORLANDC__
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma pack(1)
|
||||
#endif //__MSC_VER
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif //__cplusplus
|
||||
|
||||
//*****************************************************************************
|
||||
// %%% SCSI MISCELLANEOUS EQUATES %%%
|
||||
//*****************************************************************************
|
||||
|
||||
#define SENSE_LEN 18 // Default sense buffer length (ATM: was 14)
|
||||
#define SRB_DIR_SCSI 0x00 // Direction determined by SCSI
|
||||
#define SRB_POSTING 0x01 // Enable ASPI posting
|
||||
#define SRB_ENABLE_RESIDUAL_COUNT 0x04 // Enable residual byte count reporting
|
||||
#define SRB_DIR_IN 0x08 // Transfer from SCSI target to host
|
||||
#define SRB_DIR_OUT 0x10 // Transfer from host to SCSI target
|
||||
#define SRB_EVENT_NOTIFY 0x40 // Enable ASPI event notification
|
||||
|
||||
#define RESIDUAL_COUNT_SUPPORTED 0x02 // Extended buffer flag
|
||||
#define MAX_SRB_TIMEOUT 108000lu // 30 hour maximum timeout in s
|
||||
#define DEFAULT_SRB_TIMEOUT 108000lu // Max timeout by default
|
||||
|
||||
|
||||
//*****************************************************************************
|
||||
// %%% ASPI Command Definitions %%%
|
||||
//*****************************************************************************
|
||||
|
||||
#define SC_HA_INQUIRY 0x00 // Host adapter inquiry
|
||||
#define SC_GET_DEV_TYPE 0x01 // Get device type
|
||||
#define SC_EXEC_SCSI_CMD 0x02 // Execute SCSI command
|
||||
#define SC_ABORT_SRB 0x03 // Abort an SRB
|
||||
#define SC_RESET_DEV 0x04 // SCSI bus device reset
|
||||
#define SC_SET_HA_PARMS 0x05 // Set HA parameters
|
||||
#define SC_GET_DISK_INFO 0x06 // Get Disk information
|
||||
#define SC_RESCAN_SCSI_BUS 0x07 // ReBuild SCSI device map
|
||||
#define SC_GETSET_TIMEOUTS 0x08 // Get/Set target timeouts
|
||||
|
||||
//*****************************************************************************
|
||||
// %%% SRB Status %%%
|
||||
//*****************************************************************************
|
||||
|
||||
#define SS_PENDING 0x00 // SRB being processed
|
||||
#define SS_COMP 0x01 // SRB completed without error
|
||||
#define SS_ABORTED 0x02 // SRB aborted
|
||||
#define SS_ABORT_FAIL 0x03 // Unable to abort SRB
|
||||
#define SS_ERR 0x04 // SRB completed with error
|
||||
|
||||
#define SS_INVALID_CMD 0x80 // Invalid ASPI command
|
||||
#define SS_INVALID_HA 0x81 // Invalid host adapter number
|
||||
#define SS_NO_DEVICE 0x82 // SCSI device not installed
|
||||
|
||||
#define SS_INVALID_SRB 0xE0 // Invalid parameter set in SRB
|
||||
#define SS_OLD_MANAGER 0xE1 // ASPI manager doesn't support Windows
|
||||
#define SS_BUFFER_ALIGN 0xE1 // Buffer not aligned (replaces OLD_MANAGER in Win32)
|
||||
#define SS_ILLEGAL_MODE 0xE2 // Unsupported Windows mode
|
||||
#define SS_NO_ASPI 0xE3 // No ASPI managers resident
|
||||
#define SS_FAILED_INIT 0xE4 // ASPI for windows failed init
|
||||
#define SS_ASPI_IS_BUSY 0xE5 // No resources available to execute cmd
|
||||
#define SS_BUFFER_TO_BIG 0xE6 // Buffer size to big to handle!
|
||||
#define SS_MISMATCHED_COMPONENTS 0xE7 // The DLLs/EXEs of ASPI don't version check
|
||||
#define SS_NO_ADAPTERS 0xE8 // No host adapters to manage
|
||||
#define SS_INSUFFICIENT_RESOURCES 0xE9 // Couldn't allocate resources needed to init
|
||||
#define SS_ASPI_IS_SHUTDOWN 0xEA // Call came to ASPI after PROCESS_DETACH
|
||||
#define SS_BAD_INSTALL 0xEB // The DLL or other components are installed wrong
|
||||
|
||||
//*****************************************************************************
|
||||
// %%% Host Adapter Status %%%
|
||||
//*****************************************************************************
|
||||
|
||||
#define HASTAT_OK 0x00 // Host adapter did not detect an error
|
||||
#define HASTAT_SEL_TO 0x11 // Selection Timeout
|
||||
#define HASTAT_DO_DU 0x12 // Data overrun data underrun
|
||||
#define HASTAT_BUS_FREE 0x13 // Unexpected bus free
|
||||
#define HASTAT_PHASE_ERR 0x14 // Target bus phase sequence failure
|
||||
#define HASTAT_TIMEOUT 0x09 // Timed out while SRB was waiting to be processed.
|
||||
#define HASTAT_COMMAND_TIMEOUT 0x0B // Adapter timed out processing SRB.
|
||||
#define HASTAT_MESSAGE_REJECT 0x0D // While processing SRB, the adapter received a MESSAGE
|
||||
#define HASTAT_BUS_RESET 0x0E // A bus reset was detected.
|
||||
#define HASTAT_PARITY_ERROR 0x0F // A parity error was detected.
|
||||
#define HASTAT_REQUEST_SENSE_FAILED 0x10 // The adapter failed in issuing
|
||||
|
||||
//*****************************************************************************
|
||||
// %%% SRB - HOST ADAPTER INQUIRY - SC_HA_INQUIRY (0) %%%
|
||||
//*****************************************************************************
|
||||
|
||||
typedef struct // Offset
|
||||
{ // HX/DEC
|
||||
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_HA_INQUIRY
|
||||
BYTE SRB_Status; // 01/001 ASPI command status byte
|
||||
BYTE SRB_HaId; // 02/002 ASPI host adapter number
|
||||
BYTE SRB_Flags; // 03/003 ASPI request flags
|
||||
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved, MUST = 0
|
||||
BYTE HA_Count; // 08/008 Number of host adapters present
|
||||
BYTE HA_SCSI_ID; // 09/009 SCSI ID of host adapter
|
||||
BYTE HA_ManagerId[16]; // 0A/010 String describing the manager
|
||||
BYTE HA_Identifier[16]; // 1A/026 String describing the host adapter
|
||||
BYTE HA_Unique[16]; // 2A/042 Host Adapter Unique parameters
|
||||
WORD HA_Rsvd1; // 3A/058 Reserved, MUST = 0
|
||||
}
|
||||
SRB_HAInquiry, *PSRB_HAInquiry, FAR *LPSRB_HAInquiry;
|
||||
|
||||
//*****************************************************************************
|
||||
// %%% SRB - GET DEVICE TYPE - SC_GET_DEV_TYPE (1) %%%
|
||||
//*****************************************************************************
|
||||
|
||||
typedef struct // Offset
|
||||
{ // HX/DEC
|
||||
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_GET_DEV_TYPE
|
||||
BYTE SRB_Status; // 01/001 ASPI command status byte
|
||||
BYTE SRB_HaId; // 02/002 ASPI host adapter number
|
||||
BYTE SRB_Flags; // 03/003 Reserved, MUST = 0
|
||||
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved, MUST = 0
|
||||
BYTE SRB_Target; // 08/008 Target's SCSI ID
|
||||
BYTE SRB_Lun; // 09/009 Target's LUN number
|
||||
BYTE SRB_DeviceType; // 0A/010 Target's peripheral device type
|
||||
BYTE SRB_Rsvd1; // 0B/011 Reserved, MUST = 0
|
||||
}
|
||||
SRB_GDEVBlock, *PSRB_GDEVBlock, FAR *LPSRB_GDEVBlock;
|
||||
|
||||
//*****************************************************************************
|
||||
// %%% SRB - EXECUTE SCSI COMMAND - SC_EXEC_SCSI_CMD (2) %%%
|
||||
//*****************************************************************************
|
||||
|
||||
typedef struct // Offset
|
||||
{ // HX/DEC
|
||||
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_EXEC_SCSI_CMD
|
||||
BYTE SRB_Status; // 01/001 ASPI command status byte
|
||||
BYTE SRB_HaId; // 02/002 ASPI host adapter number
|
||||
BYTE SRB_Flags; // 03/003 ASPI request flags
|
||||
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved
|
||||
BYTE SRB_Target; // 08/008 Target's SCSI ID
|
||||
BYTE SRB_Lun; // 09/009 Target's LUN number
|
||||
WORD SRB_Rsvd1; // 0A/010 Reserved for Alignment
|
||||
DWORD SRB_BufLen; // 0C/012 Data Allocation Length
|
||||
BYTE FAR *SRB_BufPointer; // 10/016 Data Buffer Pointer
|
||||
BYTE SRB_SenseLen; // 14/020 Sense Allocation Length
|
||||
BYTE SRB_CDBLen; // 15/021 CDB Length
|
||||
BYTE SRB_HaStat; // 16/022 Host Adapter Status
|
||||
BYTE SRB_TargStat; // 17/023 Target Status
|
||||
VOID FAR *SRB_PostProc; // 18/024 Post routine
|
||||
BYTE SRB_Rsvd2[20]; // 1C/028 Reserved, MUST = 0
|
||||
BYTE CDBByte[16]; // 30/048 SCSI CDB
|
||||
BYTE SenseArea[SENSE_LEN+2]; // 50/064 Request Sense buffer
|
||||
}
|
||||
SRB_ExecSCSICmd, *PSRB_ExecSCSICmd, FAR *LPSRB_ExecSCSICmd;
|
||||
|
||||
//*****************************************************************************
|
||||
// %%% SRB - ABORT AN SRB - SC_ABORT_SRB (3) %%%
|
||||
//*****************************************************************************
|
||||
|
||||
typedef struct // Offset
|
||||
{ // HX/DEC
|
||||
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_ABORT_SRB
|
||||
BYTE SRB_Status; // 01/001 ASPI command status byte
|
||||
BYTE SRB_HaId; // 02/002 ASPI host adapter number
|
||||
BYTE SRB_Flags; // 03/003 Reserved
|
||||
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved
|
||||
VOID FAR *SRB_ToAbort; // 08/008 Pointer to SRB to abort
|
||||
}
|
||||
SRB_Abort, *PSRB_Abort, FAR *LPSRB_Abort;
|
||||
|
||||
//*****************************************************************************
|
||||
// %%% SRB - BUS DEVICE RESET - SC_RESET_DEV (4) %%%
|
||||
//*****************************************************************************
|
||||
|
||||
typedef struct // Offset
|
||||
{ // HX/DEC
|
||||
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_RESET_DEV
|
||||
BYTE SRB_Status; // 01/001 ASPI command status byte
|
||||
BYTE SRB_HaId; // 02/002 ASPI host adapter number
|
||||
BYTE SRB_Flags; // 03/003 ASPI request flags
|
||||
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved
|
||||
BYTE SRB_Target; // 08/008 Target's SCSI ID
|
||||
BYTE SRB_Lun; // 09/009 Target's LUN number
|
||||
BYTE SRB_Rsvd1[12]; // 0A/010 Reserved for Alignment
|
||||
BYTE SRB_HaStat; // 16/022 Host Adapter Status
|
||||
BYTE SRB_TargStat; // 17/023 Target Status
|
||||
VOID FAR *SRB_PostProc; // 18/024 Post routine
|
||||
BYTE SRB_Rsvd2[36]; // 1C/028 Reserved, MUST = 0
|
||||
}
|
||||
SRB_BusDeviceReset, *PSRB_BusDeviceReset, FAR *LPSRB_BusDeviceReset;
|
||||
|
||||
//*****************************************************************************
|
||||
// %%% SRB - GET DISK INFORMATION - SC_GET_DISK_INFO %%%
|
||||
//*****************************************************************************
|
||||
|
||||
typedef struct // Offset
|
||||
{ // HX/DEC
|
||||
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_GET_DISK_INFO
|
||||
BYTE SRB_Status; // 01/001 ASPI command status byte
|
||||
BYTE SRB_HaId; // 02/002 ASPI host adapter number
|
||||
BYTE SRB_Flags; // 03/003 Reserved, MUST = 0
|
||||
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved, MUST = 0
|
||||
BYTE SRB_Target; // 08/008 Target's SCSI ID
|
||||
BYTE SRB_Lun; // 09/009 Target's LUN number
|
||||
BYTE SRB_DriveFlags; // 0A/010 Driver flags
|
||||
BYTE SRB_Int13HDriveInfo; // 0B/011 Host Adapter Status
|
||||
BYTE SRB_Heads; // 0C/012 Preferred number of heads translation
|
||||
BYTE SRB_Sectors; // 0D/013 Preferred number of sectors translation
|
||||
BYTE SRB_Rsvd1[10]; // 0E/014 Reserved, MUST = 0
|
||||
}
|
||||
SRB_GetDiskInfo, *PSRB_GetDiskInfo, FAR *LPSRB_GetDiskInfo;
|
||||
|
||||
//*****************************************************************************
|
||||
// %%% SRB - RESCAN SCSI BUS(ES) ON SCSIPORT %%%
|
||||
//*****************************************************************************
|
||||
|
||||
typedef struct // Offset
|
||||
{ // HX/DEC
|
||||
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_RESCAN_SCSI_BUS
|
||||
BYTE SRB_Status; // 01/001 ASPI command status byte
|
||||
BYTE SRB_HaId; // 02/002 ASPI host adapter number
|
||||
BYTE SRB_Flags; // 03/003 Reserved, MUST = 0
|
||||
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved, MUST = 0
|
||||
}
|
||||
SRB_RescanPort, *PSRB_RescanPort, FAR *LPSRB_RescanPort;
|
||||
|
||||
//*****************************************************************************
|
||||
// %%% SRB - GET/SET TARGET TIMEOUTS %%%
|
||||
//*****************************************************************************
|
||||
|
||||
typedef struct // Offset
|
||||
{ // HX/DEC
|
||||
BYTE SRB_Cmd; // 00/000 ASPI command code = SC_GETSET_TIMEOUTS
|
||||
BYTE SRB_Status; // 01/001 ASPI command status byte
|
||||
BYTE SRB_HaId; // 02/002 ASPI host adapter number
|
||||
BYTE SRB_Flags; // 03/003 ASPI request flags
|
||||
DWORD SRB_Hdr_Rsvd; // 04/004 Reserved, MUST = 0
|
||||
BYTE SRB_Target; // 08/008 Target's SCSI ID
|
||||
BYTE SRB_Lun; // 09/009 Target's LUN number
|
||||
DWORD SRB_Timeout; // 0A/010 Timeout in half seconds
|
||||
}
|
||||
SRB_GetSetTimeouts, *PSRB_GetSetTimeouts, FAR *LPSRB_GetSetTimeouts;
|
||||
|
||||
//*****************************************************************************
|
||||
// %%% ASPIBUFF - Structure For Controllng I/O Buffers %%%
|
||||
//*****************************************************************************
|
||||
|
||||
typedef struct tag_ASPI32BUFF // Offset
|
||||
{ // HX/DEC
|
||||
PBYTE AB_BufPointer; // 00/000 Pointer to the ASPI allocated buffer
|
||||
DWORD AB_BufLen; // 04/004 Length in bytes of the buffer
|
||||
DWORD AB_ZeroFill; // 08/008 Flag set to 1 if buffer should be zeroed
|
||||
DWORD AB_Reserved; // 0C/012 Reserved
|
||||
}
|
||||
ASPI32BUFF, *PASPI32BUFF, FAR *LPASPI32BUFF;
|
||||
|
||||
//*****************************************************************************
|
||||
// %%% PROTOTYPES - User Callable ASPI for Win32 Functions %%%
|
||||
//*****************************************************************************
|
||||
|
||||
typedef void *LPSRB;
|
||||
|
||||
#if defined(__BORLANDC__)
|
||||
|
||||
DWORD _import GetASPI32SupportInfo( void );
|
||||
DWORD _import SendASPI32Command( LPSRB );
|
||||
BOOL _import GetASPI32Buffer( PASPI32BUFF );
|
||||
BOOL _import FreeASPI32Buffer( PASPI32BUFF );
|
||||
BOOL _import TranslateASPI32Address( PDWORD, PDWORD );
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
|
||||
__declspec(dllimport) DWORD GetASPI32SupportInfo( void );
|
||||
__declspec(dllimport) DWORD SendASPI32Command( LPSRB );
|
||||
__declspec(dllimport) BOOL GetASPI32Buffer( PASPI32BUFF );
|
||||
__declspec(dllimport) BOOL FreeASPI32Buffer( PASPI32BUFF );
|
||||
__declspec(dllimport) BOOL TranslateASPI32Address( PDWORD, PDWORD );
|
||||
|
||||
#else
|
||||
|
||||
extern DWORD GetASPI32SupportInfo( void );
|
||||
extern DWORD SendASPI32Command( LPSRB );
|
||||
extern BOOL GetASPI32Buffer( PASPI32BUFF );
|
||||
extern BOOL FreeASPI32Buffer( PASPI32BUFF );
|
||||
extern BOOL TranslateASPI32Address( PDWORD, PDWORD );
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Restore compiler default packing and close off the C declarations.
|
||||
*/
|
||||
|
||||
#ifdef __BORLANDC__
|
||||
#pragma option -a.
|
||||
#endif //__BORLANDC__
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma pack()
|
||||
#endif //_MSC_VER
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif //__cplusplus
|
||||
|
||||
#endif //DISKIMG_WNASPI32_H
|
184
diskimg/CP_ntddscsi.h
Normal file
184
diskimg/CP_ntddscsi.h
Normal file
@ -0,0 +1,184 @@
|
||||
/*
|
||||
* ntddscsi.h
|
||||
*
|
||||
* SCSI port IOCTL interface.
|
||||
*
|
||||
* This file is part of the w32api package.
|
||||
*
|
||||
* Contributors:
|
||||
* Created by Casper S. Hornstrup <chorns@users.sourceforge.net>
|
||||
*
|
||||
* THIS SOFTWARE IS NOT COPYRIGHTED
|
||||
*
|
||||
* This source code is offered for use in the public domain. You may
|
||||
* use, modify or distribute it freely.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful but
|
||||
* WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY
|
||||
* DISCLAIMED. This includes but is not limited to warranties of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DISKIMG_NTDDSCSI_H
|
||||
#define DISKIMG_NTDDSCSI_H
|
||||
|
||||
#if __GNUC__ >=3
|
||||
#pragma GCC system_header
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#pragma pack(push,4)
|
||||
|
||||
//#include "ntddk.h"
|
||||
|
||||
#ifndef ULONG_PTR
|
||||
# define ULONG_PTR DWORD
|
||||
#endif
|
||||
|
||||
|
||||
#define DD_SCSI_DEVICE_NAME "\\Device\\ScsiPort"
|
||||
#define DD_SCSI_DEVICE_NAME_U L"\\Device\\ScsiPort"
|
||||
|
||||
#define IOCTL_SCSI_BASE FILE_DEVICE_CONTROLLER
|
||||
|
||||
#define IOCTL_SCSI_GET_INQUIRY_DATA \
|
||||
CTL_CODE(IOCTL_SCSI_BASE, 0x0403, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_SCSI_GET_CAPABILITIES \
|
||||
CTL_CODE(IOCTL_SCSI_BASE, 0x0404, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_SCSI_GET_ADDRESS \
|
||||
CTL_CODE(IOCTL_SCSI_BASE, 0x0406, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_SCSI_MINIPORT \
|
||||
CTL_CODE(IOCTL_SCSI_BASE, 0x0402, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
|
||||
#define IOCTL_SCSI_PASS_THROUGH \
|
||||
CTL_CODE(IOCTL_SCSI_BASE, 0x0401, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
|
||||
#define IOCTL_SCSI_PASS_THROUGH_DIRECT \
|
||||
CTL_CODE(IOCTL_SCSI_BASE, 0x0405, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
|
||||
|
||||
#define IOCTL_SCSI_RESCAN_BUS \
|
||||
CTL_CODE(IOCTL_SCSI_BASE, 0x0407, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
|
||||
//DEFINE_GUID(ScsiRawInterfaceGuid, \
|
||||
// 0x53f56309L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
|
||||
|
||||
//DEFINE_GUID(WmiScsiAddressGuid, \
|
||||
// 0x53f5630fL, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b);
|
||||
|
||||
typedef struct _SCSI_PASS_THROUGH {
|
||||
USHORT Length;
|
||||
UCHAR ScsiStatus;
|
||||
UCHAR PathId;
|
||||
UCHAR TargetId;
|
||||
UCHAR Lun;
|
||||
UCHAR CdbLength;
|
||||
UCHAR SenseInfoLength;
|
||||
UCHAR DataIn;
|
||||
ULONG DataTransferLength;
|
||||
ULONG TimeOutValue;
|
||||
ULONG_PTR DataBufferOffset;
|
||||
ULONG SenseInfoOffset;
|
||||
UCHAR Cdb[16];
|
||||
} SCSI_PASS_THROUGH, *PSCSI_PASS_THROUGH;
|
||||
|
||||
typedef struct _SCSI_PASS_THROUGH_DIRECT {
|
||||
USHORT Length;
|
||||
UCHAR ScsiStatus;
|
||||
UCHAR PathId;
|
||||
UCHAR TargetId;
|
||||
UCHAR Lun;
|
||||
UCHAR CdbLength;
|
||||
UCHAR SenseInfoLength;
|
||||
UCHAR DataIn;
|
||||
ULONG DataTransferLength;
|
||||
ULONG TimeOutValue;
|
||||
PVOID DataBuffer;
|
||||
ULONG SenseInfoOffset;
|
||||
UCHAR Cdb[16];
|
||||
} SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT;
|
||||
|
||||
typedef struct _SRB_IO_CONTROL {
|
||||
ULONG HeaderLength;
|
||||
UCHAR Signature[8];
|
||||
ULONG Timeout;
|
||||
ULONG ControlCode;
|
||||
ULONG ReturnCode;
|
||||
ULONG Length;
|
||||
} SRB_IO_CONTROL, *PSRB_IO_CONTROL;
|
||||
|
||||
typedef struct _SCSI_ADDRESS {
|
||||
ULONG Length;
|
||||
UCHAR PortNumber;
|
||||
UCHAR PathId;
|
||||
UCHAR TargetId;
|
||||
UCHAR Lun;
|
||||
} SCSI_ADDRESS, *PSCSI_ADDRESS;
|
||||
|
||||
typedef struct _SCSI_BUS_DATA {
|
||||
UCHAR NumberOfLogicalUnits;
|
||||
UCHAR InitiatorBusId;
|
||||
ULONG InquiryDataOffset;
|
||||
}SCSI_BUS_DATA, *PSCSI_BUS_DATA;
|
||||
|
||||
typedef struct _SCSI_ADAPTER_BUS_INFO {
|
||||
UCHAR NumberOfBuses;
|
||||
SCSI_BUS_DATA BusData[1];
|
||||
} SCSI_ADAPTER_BUS_INFO, *PSCSI_ADAPTER_BUS_INFO;
|
||||
|
||||
typedef struct _IO_SCSI_CAPABILITIES {
|
||||
ULONG Length;
|
||||
ULONG MaximumTransferLength;
|
||||
ULONG MaximumPhysicalPages;
|
||||
ULONG SupportedAsynchronousEvents;
|
||||
ULONG AlignmentMask;
|
||||
BOOLEAN TaggedQueuing;
|
||||
BOOLEAN AdapterScansDown;
|
||||
BOOLEAN AdapterUsesPio;
|
||||
} IO_SCSI_CAPABILITIES, *PIO_SCSI_CAPABILITIES;
|
||||
|
||||
typedef struct _SCSI_INQUIRY_DATA {
|
||||
UCHAR PathId;
|
||||
UCHAR TargetId;
|
||||
UCHAR Lun;
|
||||
BOOLEAN DeviceClaimed;
|
||||
ULONG InquiryDataLength;
|
||||
ULONG NextInquiryDataOffset;
|
||||
UCHAR InquiryData[1];
|
||||
} SCSI_INQUIRY_DATA, *PSCSI_INQUIRY_DATA;
|
||||
|
||||
#define SCSI_IOCTL_DATA_OUT 0
|
||||
#define SCSI_IOCTL_DATA_IN 1
|
||||
#define SCSI_IOCTL_DATA_UNSPECIFIED 2
|
||||
|
||||
struct ADAPTER_OBJECT;
|
||||
typedef struct ADAPTER_OBJECT* PADAPTER_OBJECT;
|
||||
|
||||
typedef struct _DUMP_POINTERS {
|
||||
PADAPTER_OBJECT AdapterObject;
|
||||
PVOID MappedRegisterBase;
|
||||
PVOID DumpData;
|
||||
PVOID CommonBufferVa;
|
||||
LARGE_INTEGER CommonBufferPa;
|
||||
ULONG CommonBufferSize;
|
||||
BOOLEAN AllocateCommonBuffers;
|
||||
BOOLEAN UseDiskDump;
|
||||
UCHAR Spare1[2];
|
||||
PVOID DeviceObject;
|
||||
} DUMP_POINTERS, *PDUMP_POINTERS;
|
||||
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /*DISKIMG_NTDDSCSI_H*/
|
120
diskimg/Container.cpp
Normal file
120
diskimg/Container.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Base "container FS" support.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
|
||||
/*
|
||||
* Blank out the volume usage map, setting all entries to "embedded".
|
||||
*/
|
||||
void DiskFSContainer::SetVolumeUsageMap(void)
|
||||
{
|
||||
VolumeUsage::ChunkState cstate;
|
||||
long block;
|
||||
|
||||
fVolumeUsage.Create(fpImg->GetNumBlocks());
|
||||
|
||||
cstate.isUsed = true;
|
||||
cstate.isMarkedUsed = true;
|
||||
cstate.purpose = VolumeUsage::kChunkPurposeEmbedded;
|
||||
|
||||
for (block = fpImg->GetNumBlocks()-1; block >= 0; block--)
|
||||
fVolumeUsage.SetChunkState(block, &cstate);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Create a "placeholder" sub-volume. Useful for some of the tools when
|
||||
* dealing with unformatted (or unknown-formatted) partitions.
|
||||
*/
|
||||
DIError DiskFSContainer::CreatePlaceholder(long startBlock, long numBlocks,
|
||||
const char* partName, const char* partType,
|
||||
DiskImg** ppNewImg, DiskFS** ppNewFS)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
DiskFS* pNewFS = NULL;
|
||||
DiskImg* pNewImg = NULL;
|
||||
|
||||
LOGI(" %s/CrPl creating placeholder for %ld +%ld", GetDebugName(),
|
||||
startBlock, numBlocks);
|
||||
|
||||
if (startBlock > fpImg->GetNumBlocks()) {
|
||||
LOGI(" %s/CrPl start block out of range (%ld vs %ld)",
|
||||
GetDebugName(), startBlock, fpImg->GetNumBlocks());
|
||||
return kDIErrBadPartition;
|
||||
}
|
||||
|
||||
pNewImg = new DiskImg;
|
||||
if (pNewImg == NULL) {
|
||||
dierr = kDIErrMalloc;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (partName != NULL) {
|
||||
if (partType != NULL)
|
||||
pNewImg->AddNote(DiskImg::kNoteInfo,
|
||||
"Partition name='%s' type='%s'.", partName, partType);
|
||||
else
|
||||
pNewImg->AddNote(DiskImg::kNoteInfo,
|
||||
"Partition name='%s'.", partName);
|
||||
}
|
||||
|
||||
dierr = pNewImg->OpenImage(fpImg, startBlock, numBlocks);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" %s/CrPl: OpenImage(%ld,%ld) failed (err=%d)",
|
||||
GetDebugName(), startBlock, numBlocks, dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/*
|
||||
* If this slot isn't formatted at all, the call will return with
|
||||
* "unknown FS". If it's formatted enough to pass the initial test,
|
||||
* but fails during "Initialize", we'll have a non-unknown value
|
||||
* for the FS format. We need to stomp that.
|
||||
*/
|
||||
dierr = pNewImg->AnalyzeImage();
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" %s/CrPl: analysis failed (err=%d)", GetDebugName(), dierr);
|
||||
goto bail;
|
||||
}
|
||||
if (pNewImg->GetFSFormat() != DiskImg::kFormatUnknown) {
|
||||
dierr = pNewImg->OverrideFormat(pNewImg->GetPhysicalFormat(),
|
||||
DiskImg::kFormatUnknown, pNewImg->GetSectorOrder());
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" %s/CrPl: unable to override to unknown",
|
||||
GetDebugName());
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
|
||||
/* open a DiskFS for the sub-image, allowing "unknown" */
|
||||
pNewFS = pNewImg->OpenAppropriateDiskFS(true);
|
||||
if (pNewFS == NULL) {
|
||||
LOGI(" %s/CrPl: OpenAppropriateDiskFS failed", GetDebugName());
|
||||
dierr = kDIErrUnsupportedFSFmt;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* sets the DiskImg ptr (and very little else) */
|
||||
dierr = pNewFS->Initialize(pNewImg, kInitFull);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" %s/CrPl: init failed (err=%d)", GetDebugName(), dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
bail:
|
||||
if (dierr != kDIErrNone) {
|
||||
delete pNewFS;
|
||||
delete pNewImg;
|
||||
} else {
|
||||
*ppNewImg = pNewImg;
|
||||
*ppNewFS = pNewFS;
|
||||
}
|
||||
return dierr;
|
||||
}
|
629
diskimg/DDD.cpp
Normal file
629
diskimg/DDD.cpp
Normal file
@ -0,0 +1,629 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Pack and unpack DDD format.
|
||||
*/
|
||||
/*
|
||||
The trouble with unpacking DOS DDD 2.x files:
|
||||
|
||||
[ Most of this is no longer relevant, but the discussion is enlightening. ]
|
||||
|
||||
DDD writes its files as DOS 3.3 binary (type 'B') files, which have
|
||||
starting address and length embedded as the first 4 bytes. Unfortunately, it
|
||||
cannot write the length, because the largest possible 16-bit length value is
|
||||
only 64K. Instead, DDD sets it to zero. DDD v2.0 does store a copy of the
|
||||
length *in sectors* in the filename (e.g. "<397>"), but this doesn't really
|
||||
help much. When CiderPress goes to extract or view the file, it just sees a
|
||||
zero-length binary file.
|
||||
|
||||
CiderPress could make an exception and assume that any binary file with
|
||||
zero length and more than one sector allocated has a length equal to the
|
||||
number of sectors times 256. This could cause problems for other things,
|
||||
but it's probably pretty safe. However, we still don't have an accurate
|
||||
idea of where the end of the file is.
|
||||
|
||||
Knowing where the file ends is important because there is no identifying
|
||||
information or checksum in a DDD file. The only way to know that it's a
|
||||
DDD compressed disk is to try to unpack it and see if you end up at exactly
|
||||
140K at the same time that you run out of input. Without knowing where the
|
||||
file really ends this test is much less certain. (DDD 2.5 appears to have
|
||||
added some sort of checksum, which was appended to the DOS filename, but
|
||||
without knowing how it was calculated there's no way to verify it.)
|
||||
|
||||
The only safe way to make this work would be to skip the automatic format
|
||||
detection and tell CiderPress that the file is definitely DDD format.
|
||||
There's currently no easy way to do that without complicating the user
|
||||
interface. Filename extensions might be useful in making the decision,
|
||||
but they're rare under DOS 3.3, and I don't know if the "<397>" convention
|
||||
is common to all versions of DDD.
|
||||
|
||||
Complicating the matter is that, if a DOS DDD file (type 'B') is converted
|
||||
to ProDOS, the first 4 bytes will be stripped off. Without unpacking
|
||||
the file and knowing to within a byte where it ends, there's no way to
|
||||
automatically tell whether to start at byte 0 or byte 4. (DDD Pro files
|
||||
have four bytes of garbage at the very start, probably in an attempt to
|
||||
retain compatibility with the DOS version. Because it uses REL files the
|
||||
4 bytes of extra DOS stuff aren't added when the files are copied around,
|
||||
so this was a reasonably smart thing to do, but it complicates matters
|
||||
for CiderPress because a file extracted from DOS and a file extracted
|
||||
from ProDOS will come out differently because only the DOS version has the
|
||||
leading 4 bytes stripped. This could be avoided if the DOS file uses the
|
||||
'R' or 'S' file type, but we still lack an accurate file length.)
|
||||
|
||||
To unpack a file created by DOS DDD v2.x with CiderPress:
|
||||
- In an emulator, copy the file to a ProDOS disk, using something that
|
||||
guesses at the actual length when one isn't provided (Copy ][+ 9.0
|
||||
may work).
|
||||
- Reduce the length to within a byte or two of the actual end of file.
|
||||
This is nearly impossible, because DDD doesn't zero out the remaining
|
||||
data in the last sector.
|
||||
- Insert 4 bytes of garbage at the front of the file. My copy of DDD
|
||||
Pro 1.1 seems to like 03 c9 bf d0.
|
||||
Probably not worth the effort. Just unpack it with an emulator.
|
||||
|
||||
In general DDD is a rather poor choice, because the compression isn't
|
||||
very good and there's no checksum. ShrinkIt is a much better way to go.
|
||||
|
||||
NOTE: DOS DDD v2.0 seems to have a bug where it doesn't always write the last
|
||||
run correctly. On the DOS system master this caused the last half of the
|
||||
last sector (FID's T/S list) to have garbage written instead of zero bytes,
|
||||
which caused CP to label FID as damaged. DDD v2.1 and later doesn't
|
||||
appear to have this issue. Unfortunate that DDD 2.0 is what shipped on the
|
||||
SST disk.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
const int kNumSymbols = 256;
|
||||
const int kNumFavorites = 20;
|
||||
const int kRLEDelim = 0x97; // value MUST have high bit set
|
||||
const int kMaxExcessByteCount = WrapperDDD::kMaxDDDZeroCount + 1;
|
||||
//const int kTrackLen = 4096;
|
||||
//const int kNumTracks = 35;
|
||||
|
||||
/* I suspect this is random garbage, but it's appearing consistently */
|
||||
const unsigned long kDDDProSignature = 0xd0bfc903;
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* BitBuffer
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Class for getting and putting bits to and from a file.
|
||||
*/
|
||||
class WrapperDDD::BitBuffer {
|
||||
public:
|
||||
BitBuffer(void) : fpGFD(NULL), fBits(0), fBitCount(0), fIOFailure(false) {}
|
||||
~BitBuffer(void) {}
|
||||
|
||||
void SetFile(GenericFD* pGFD) { fpGFD = pGFD; }
|
||||
void PutBits(uint8_t bits, int numBits);
|
||||
uint8_t GetBits(int numBits);
|
||||
|
||||
bool IOFailure(void) const { return fIOFailure; }
|
||||
|
||||
static uint8_t Reverse(uint8_t val);
|
||||
|
||||
private:
|
||||
GenericFD* fpGFD;
|
||||
uint8_t fBits;
|
||||
int fBitCount;
|
||||
bool fIOFailure;
|
||||
};
|
||||
|
||||
/*
|
||||
* Add bits to the buffer.
|
||||
*
|
||||
* We roll the low bits out of "bits" and shift them to the left (in the
|
||||
* reverse order in which they were passed in). As soon as we get 8 bits
|
||||
* we flush.
|
||||
*/
|
||||
void WrapperDDD::BitBuffer::PutBits(uint8_t bits, int numBits)
|
||||
{
|
||||
assert(fBitCount >= 0 && fBitCount < 8);
|
||||
assert(numBits > 0 && numBits <= 8);
|
||||
assert(fpGFD != NULL);
|
||||
|
||||
DIError dierr;
|
||||
|
||||
while (numBits--) {
|
||||
fBits = (fBits << 1) | (bits & 0x01);
|
||||
fBitCount++;
|
||||
|
||||
if (fBitCount == 8) {
|
||||
dierr = fpGFD->Write(&fBits, 1);
|
||||
fIOFailure = (dierr != kDIErrNone);
|
||||
fBitCount = 0;
|
||||
}
|
||||
|
||||
bits >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Get bits from the buffer.
|
||||
*
|
||||
* These come out in the order in which they appear in the file, which
|
||||
* means that in some cases they will have to be reversed.
|
||||
*/
|
||||
uint8_t WrapperDDD::BitBuffer::GetBits(int numBits)
|
||||
{
|
||||
assert(fBitCount >= 0 && fBitCount < 8);
|
||||
assert(numBits > 0 && numBits <= 8);
|
||||
assert(fpGFD != NULL);
|
||||
|
||||
DIError dierr;
|
||||
uint8_t retVal;
|
||||
|
||||
if (fBitCount == 0) {
|
||||
/* have no bits */
|
||||
dierr = fpGFD->Read(&fBits, 1);
|
||||
fIOFailure = (dierr != kDIErrNone);
|
||||
fBitCount = 8;
|
||||
}
|
||||
|
||||
if (numBits <= fBitCount) {
|
||||
/* just serve up what we've already got */
|
||||
retVal = fBits >> (8 - numBits);
|
||||
fBits <<= numBits;
|
||||
fBitCount -= numBits;
|
||||
} else {
|
||||
/* some old, some new; load what we have right-aligned */
|
||||
retVal = fBits >> (8 - fBitCount);
|
||||
numBits -= fBitCount;
|
||||
|
||||
dierr = fpGFD->Read(&fBits, 1);
|
||||
fIOFailure = (dierr != kDIErrNone);
|
||||
fBitCount = 8;
|
||||
|
||||
/* make room for the rest (also zeroes out the low bits) */
|
||||
retVal <<= numBits;
|
||||
|
||||
/* add the high bits from the new byte */
|
||||
retVal |= fBits >> (8 - numBits);
|
||||
fBits <<= numBits;
|
||||
fBitCount -= numBits;
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility function to reverse the order of bits in a byte.
|
||||
*/
|
||||
/*static*/ uint8_t WrapperDDD::BitBuffer::Reverse(uint8_t val)
|
||||
{
|
||||
int i;
|
||||
uint8_t result = 0; // init is to make valgrind happy
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
result = (result << 1) + (val & 0x01);
|
||||
val >>= 1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* DDD compression functions
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* These are all odd, which when they're written in reverse order means
|
||||
* they all have their hi bits set.
|
||||
*/
|
||||
static const uint8_t kFavoriteBitEnc[kNumFavorites] = {
|
||||
0x03, 0x09, 0x1f, 0x0f, 0x07, 0x1b, 0x0b, 0x0d, 0x15, 0x37,
|
||||
0x3d, 0x25, 0x05, 0xb1, 0x11, 0x21, 0x01, 0x57, 0x5d, 0x1d
|
||||
};
|
||||
static const int kFavoriteBitEncLen[kNumFavorites] = {
|
||||
4, 4, 5, 5, 5, 5, 5, 5, 5, 6,
|
||||
6, 6, 6, 6, 6, 6, 6, 7, 7, 7
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Pack a disk image with DDD.
|
||||
*
|
||||
* Assumes pSrcGFD points to DOS-ordered sectors. (This is enforced when the
|
||||
* disk image is first being created.)
|
||||
*/
|
||||
/*static*/ DIError WrapperDDD::PackDisk(GenericFD* pSrcGFD, GenericFD* pWrapperGFD,
|
||||
short diskVolNum)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
BitBuffer bitBuffer;
|
||||
|
||||
assert(diskVolNum >= 0 && diskVolNum < 256);
|
||||
|
||||
/* write four zeroes to replace the DOS addr/len bytes */
|
||||
/* (actually, let's write the apparent DDD Pro v1.1 signature instead) */
|
||||
WriteLongLE(pWrapperGFD, kDDDProSignature);
|
||||
|
||||
bitBuffer.SetFile(pWrapperGFD);
|
||||
|
||||
bitBuffer.PutBits(0x00, 3);
|
||||
bitBuffer.PutBits((uint8_t)diskVolNum, 8);
|
||||
|
||||
/*
|
||||
* Process all tracks.
|
||||
*/
|
||||
for (int track = 0; track < kNumTracks; track++) {
|
||||
uint8_t trackBuf[kTrackLen];
|
||||
|
||||
dierr = pSrcGFD->Read(trackBuf, kTrackLen);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" DDD error during read (err=%d)", dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
PackTrack(trackBuf, &bitBuffer);
|
||||
}
|
||||
|
||||
/* write 8 bits of zeroes to flush remaining data out of buffer */
|
||||
bitBuffer.PutBits(0x00, 8);
|
||||
|
||||
/* write another zero byte because that's what DDD Pro v1.1 does */
|
||||
long zero;
|
||||
zero = 0;
|
||||
dierr = pWrapperGFD->Write(&zero, 1);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
assert(dierr == kDIErrNone);
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compress a track full of data.
|
||||
*/
|
||||
/*static*/ void WrapperDDD::PackTrack(const uint8_t* trackBuf, BitBuffer* pBitBuf)
|
||||
{
|
||||
uint16_t freqCounts[kNumSymbols];
|
||||
uint8_t favorites[kNumFavorites];
|
||||
int i, fav;
|
||||
|
||||
ComputeFreqCounts(trackBuf, freqCounts);
|
||||
ComputeFavorites(freqCounts, favorites);
|
||||
|
||||
/* write favorites */
|
||||
for (fav = 0; fav < kNumFavorites; fav++)
|
||||
pBitBuf->PutBits(favorites[fav], 8);
|
||||
|
||||
/*
|
||||
* Compress track data. Store runs as { 0x97 char count }, where
|
||||
* a count of zero means 256.
|
||||
*/
|
||||
const uint8_t* ucp = trackBuf;
|
||||
for (i = 0; i < kTrackLen; i++, ucp++) {
|
||||
if (i < (kTrackLen-3) &&
|
||||
*ucp == *(ucp+1) &&
|
||||
*ucp == *(ucp+2) &&
|
||||
*ucp == *(ucp+3))
|
||||
{
|
||||
int runLen = 4;
|
||||
i += 3;
|
||||
ucp += 3;
|
||||
|
||||
while (i < kTrackLen-1 && *ucp == *(ucp+1)) {
|
||||
runLen++;
|
||||
ucp++;
|
||||
i++;
|
||||
|
||||
if (runLen == 256) {
|
||||
runLen = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pBitBuf->PutBits(kRLEDelim, 8); // note kRLEDelim has hi bit set
|
||||
pBitBuf->PutBits(*ucp, 8);
|
||||
pBitBuf->PutBits(runLen, 8);
|
||||
|
||||
} else {
|
||||
/*
|
||||
* Not a run, see if it's one of our favorites.
|
||||
*/
|
||||
for (fav = 0; fav < kNumFavorites; fav++) {
|
||||
if (*ucp == favorites[fav])
|
||||
break;
|
||||
}
|
||||
if (fav == kNumFavorites) {
|
||||
/* just a plain byte */
|
||||
pBitBuf->PutBits(0x00, 1);
|
||||
pBitBuf->PutBits(*ucp, 8);
|
||||
} else {
|
||||
/* found a favorite; leading hi bit is implied */
|
||||
pBitBuf->PutBits(kFavoriteBitEnc[fav], kFavoriteBitEncLen[fav]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Compute the #of times each byte appears in trackBuf. Runs of four
|
||||
* bytes or longer are completely ignored.
|
||||
*
|
||||
* "trackBuf" holds kTrackLen bytes of data, and "freqCounts" holds
|
||||
* kNumSymbols (256) 16-bit values.
|
||||
*/
|
||||
/*static*/ void WrapperDDD::ComputeFreqCounts(const uint8_t* trackBuf,
|
||||
uint16_t* freqCounts)
|
||||
{
|
||||
const uint8_t* ucp;
|
||||
int i;
|
||||
|
||||
memset(freqCounts, 0, 256 * sizeof(uint16_t));
|
||||
|
||||
ucp = trackBuf;
|
||||
for (i = 0; i < kTrackLen; i++, ucp++) {
|
||||
if (i < (kTrackLen-3) &&
|
||||
*ucp == *(ucp+1) &&
|
||||
*ucp == *(ucp+2) &&
|
||||
*ucp == *(ucp+3))
|
||||
{
|
||||
int runLen = 4; // DEBUG only
|
||||
i += 3;
|
||||
ucp += 3;
|
||||
|
||||
while (i < kTrackLen-1 && *ucp == *(ucp+1)) {
|
||||
runLen++;
|
||||
ucp++;
|
||||
i++;
|
||||
|
||||
if (runLen == 256) {
|
||||
runLen = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//LOGI("Found run of %d of 0x%02x", runLen, *ucp);
|
||||
} else {
|
||||
/* not a run, just update stats */
|
||||
freqCounts[*ucp]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the 20 most frequently occurring symbols, in order.
|
||||
*
|
||||
* Modifies "freqCounts".
|
||||
*/
|
||||
/*static*/ void WrapperDDD::ComputeFavorites(uint16_t* freqCounts,
|
||||
uint8_t* favorites)
|
||||
{
|
||||
int i, fav;
|
||||
|
||||
for (fav = 0; fav < kNumFavorites; fav++) {
|
||||
uint16_t bestCount = 0;
|
||||
uint8_t bestSym = 0;
|
||||
|
||||
for (i = 0; i < kNumSymbols; i++) {
|
||||
if (freqCounts[i] >= bestCount) {
|
||||
bestSym = (uint8_t) i;
|
||||
bestCount = freqCounts[i];
|
||||
}
|
||||
}
|
||||
|
||||
favorites[fav] = bestSym;
|
||||
freqCounts[bestSym] = 0;
|
||||
}
|
||||
|
||||
//LOGI("FAVORITES: ");
|
||||
//for (fav = 0; fav < kNumFavorites; fav++)
|
||||
// LOGI("%02x", favorites[fav]);
|
||||
//LOGI("");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* DDD expansion functions
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* This is the reverse of the kFavoriteBitEnc table. The bits are
|
||||
* reversed and lack the high bit.
|
||||
*/
|
||||
static const uint8_t kFavoriteBitDec[kNumFavorites] = {
|
||||
0x04, 0x01, 0x0f, 0x0e, 0x0c, 0x0b, 0x0a, 0x06, 0x05, 0x1b,
|
||||
0x0f, 0x09, 0x08, 0x03, 0x02, 0x01, 0x00, 0x35, 0x1d, 0x1c
|
||||
};
|
||||
|
||||
/*
|
||||
* Entry point for unpacking a disk image compressed with DDD.
|
||||
*
|
||||
* The result is an unadorned DOS-ordered image.
|
||||
*/
|
||||
/*static*/ DIError WrapperDDD::UnpackDisk(GenericFD* pGFD, GenericFD* pNewGFD,
|
||||
short* pDiskVolNum)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
BitBuffer bitBuffer;
|
||||
uint8_t val;
|
||||
long lbuf;
|
||||
|
||||
assert(pGFD != NULL);
|
||||
assert(pNewGFD != NULL);
|
||||
|
||||
/* read four zeroes to skip the DOS addr/len bytes */
|
||||
assert(sizeof(lbuf) >= 4);
|
||||
dierr = pGFD->Read(&lbuf, 4);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
bitBuffer.SetFile(pGFD);
|
||||
|
||||
val = bitBuffer.GetBits(3);
|
||||
if (val != 0) {
|
||||
LOGI(" DDD bits not zero, this isn't a DDD II file (0x%02x)", val);
|
||||
dierr = kDIErrGeneric;
|
||||
goto bail;
|
||||
}
|
||||
val = bitBuffer.GetBits(8);
|
||||
*pDiskVolNum = bitBuffer.Reverse(val);
|
||||
LOGI(" DDD found disk volume num = %d", *pDiskVolNum);
|
||||
|
||||
int track;
|
||||
for (track = 0; track < kNumTracks; track++) {
|
||||
uint8_t trackBuf[kTrackLen];
|
||||
|
||||
if (!UnpackTrack(&bitBuffer, trackBuf)) {
|
||||
LOGI(" DDD failed unpacking track %d", track);
|
||||
dierr = kDIErrBadCompressedData;
|
||||
goto bail;
|
||||
}
|
||||
if (bitBuffer.IOFailure()) {
|
||||
LOGI(" DDD failure or EOF on input file");
|
||||
dierr = kDIErrBadCompressedData;
|
||||
goto bail;
|
||||
}
|
||||
dierr = pNewGFD->Write(trackBuf, kTrackLen);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/*
|
||||
* We should be within a byte or two of the end of the file. Try
|
||||
* to read more and expect it to fail.
|
||||
*
|
||||
* Unfortunately, if this was a DOS DDD file, we could be up to 256
|
||||
* bytes off (the 1 additional byte it adds plus the remaining 255
|
||||
* bytes in the sector). We have to choose between a tight auto-detect
|
||||
* and the ability to process DOS DDD files.
|
||||
*
|
||||
* Fortunately the need to hit track boundaries exactly and the quick test
|
||||
* for long runs of bytes provides some opportunity for correct
|
||||
* detection.
|
||||
*/
|
||||
size_t actual;
|
||||
char sctBuf[256 + 16];
|
||||
dierr = pGFD->Read(&sctBuf, sizeof(sctBuf), &actual);
|
||||
if (dierr == kDIErrNone) {
|
||||
if (actual > /*kMaxExcessByteCount*/ 256) {
|
||||
LOGW(" DDD looks like too much data in input file (%lu extra)",
|
||||
(unsigned long) actual);
|
||||
dierr = kDIErrBadCompressedData;
|
||||
goto bail;
|
||||
} else {
|
||||
LOGI(" DDD excess bytes (%lu) within normal parameters",
|
||||
(unsigned long) actual);
|
||||
}
|
||||
}
|
||||
|
||||
LOGI(" DDD looks like a DDD archive!");
|
||||
dierr = kDIErrNone;
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unpack a single track.
|
||||
*
|
||||
* Returns "true" if all went well, "false" if something failed.
|
||||
*/
|
||||
/*static*/ bool WrapperDDD::UnpackTrack(BitBuffer* pBitBuffer, uint8_t* trackBuf)
|
||||
{
|
||||
uint8_t favorites[kNumFavorites];
|
||||
uint8_t val;
|
||||
uint8_t* trackPtr;
|
||||
int fav;
|
||||
|
||||
/*
|
||||
* Start by pulling our favorites out, in reverse order.
|
||||
*/
|
||||
for (fav = 0; fav < kNumFavorites; fav++) {
|
||||
val = pBitBuffer->GetBits(8);
|
||||
val = pBitBuffer->Reverse(val);
|
||||
favorites[fav] = val;
|
||||
}
|
||||
|
||||
trackPtr = trackBuf;
|
||||
|
||||
/*
|
||||
* Keep pulling data out until the track is full.
|
||||
*/
|
||||
while (trackPtr < trackBuf + kTrackLen) {
|
||||
val = pBitBuffer->GetBits(1);
|
||||
if (!val) {
|
||||
/* simple byte */
|
||||
val = pBitBuffer->GetBits(8);
|
||||
val = pBitBuffer->Reverse(val);
|
||||
*trackPtr++ = val;
|
||||
} else {
|
||||
/* try for a prefix match */
|
||||
int extraBits;
|
||||
|
||||
val = pBitBuffer->GetBits(2);
|
||||
|
||||
for (extraBits = 0; extraBits < 4; extraBits++) {
|
||||
val = (val << 1) | pBitBuffer->GetBits(1);
|
||||
int start, end;
|
||||
|
||||
if (extraBits == 0) {
|
||||
start = 0;
|
||||
end = 2;
|
||||
} else if (extraBits == 1) {
|
||||
start = 2;
|
||||
end = 9;
|
||||
} else if (extraBits == 2) {
|
||||
start = 9;
|
||||
end = 17;
|
||||
} else {
|
||||
start = 17;
|
||||
end = 20;
|
||||
}
|
||||
|
||||
while (start < end) {
|
||||
if (val == kFavoriteBitDec[start]) {
|
||||
/* winner! */
|
||||
*trackPtr++ = favorites[start];
|
||||
break;
|
||||
}
|
||||
start++;
|
||||
}
|
||||
if (start != end)
|
||||
break; // we got it, break out of for loop
|
||||
}
|
||||
if (extraBits == 4) {
|
||||
/* we didn't get it, this must be RLE */
|
||||
uint8_t rleChar;
|
||||
int rleCount;
|
||||
|
||||
(void) pBitBuffer->GetBits(1); // get last bit of 0x97
|
||||
val = pBitBuffer->GetBits(8);
|
||||
rleChar = pBitBuffer->Reverse(val);
|
||||
val = pBitBuffer->GetBits(8);
|
||||
rleCount = pBitBuffer->Reverse(val);
|
||||
//LOGI(" DDD found run of %d of 0x%02x", rleCount, rleChar);
|
||||
|
||||
if (rleCount == 0)
|
||||
rleCount = 256;
|
||||
|
||||
/* make sure we won't overrun */
|
||||
if (trackPtr + rleCount > trackBuf + kTrackLen) {
|
||||
LOGI(" DDD overrun in RLE");
|
||||
return false;
|
||||
}
|
||||
while (rleCount--)
|
||||
*trackPtr++ = rleChar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
350
diskimg/DIUtil.cpp
Normal file
350
diskimg/DIUtil.cpp
Normal file
@ -0,0 +1,350 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* DiskImgLib global utility functions.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
#define kFilenameExtDelim '.' /* separates extension from filename */
|
||||
|
||||
/*
|
||||
* Get values from a memory buffer.
|
||||
*/
|
||||
uint16_t DiskImgLib::GetShortLE(const uint8_t* ptr)
|
||||
{
|
||||
return *ptr | (uint16_t) *(ptr+1) << 8;
|
||||
}
|
||||
|
||||
uint32_t DiskImgLib::GetLongLE(const uint8_t* ptr)
|
||||
{
|
||||
return *ptr |
|
||||
(uint32_t) *(ptr+1) << 8 |
|
||||
(uint32_t) *(ptr+2) << 16 |
|
||||
(uint32_t) *(ptr+3) << 24;
|
||||
}
|
||||
|
||||
uint16_t DiskImgLib::GetShortBE(const uint8_t* ptr)
|
||||
{
|
||||
return *(ptr+1) | (uint16_t) *ptr << 8;
|
||||
}
|
||||
|
||||
uint32_t DiskImgLib::GetLongBE(const uint8_t* ptr)
|
||||
{
|
||||
return *(ptr+3) |
|
||||
(uint32_t) *(ptr+2) << 8 |
|
||||
(uint32_t) *(ptr+1) << 16 |
|
||||
(uint32_t) *ptr << 24;
|
||||
}
|
||||
|
||||
uint32_t DiskImgLib::Get24BE(const uint8_t* ptr)
|
||||
{
|
||||
return *(ptr+2) |
|
||||
(uint32_t) *(ptr+1) << 8 |
|
||||
(uint32_t) *ptr << 16;
|
||||
}
|
||||
|
||||
void DiskImgLib::PutShortLE(uint8_t* ptr, uint16_t val)
|
||||
{
|
||||
*ptr++ = (uint8_t) val;
|
||||
*ptr = val >> 8;
|
||||
}
|
||||
|
||||
void DiskImgLib::PutLongLE(uint8_t* ptr, uint32_t val)
|
||||
{
|
||||
*ptr++ = (uint8_t) val;
|
||||
*ptr++ = (uint8_t) (val >> 8);
|
||||
*ptr++ = (uint8_t) (val >> 16);
|
||||
*ptr = (uint8_t) (val >> 24);
|
||||
}
|
||||
|
||||
void DiskImgLib::PutShortBE(uint8_t* ptr, uint16_t val)
|
||||
{
|
||||
*ptr++ = val >> 8;
|
||||
*ptr = (uint8_t) val;
|
||||
}
|
||||
|
||||
void DiskImgLib::PutLongBE(uint8_t* ptr, uint32_t val)
|
||||
{
|
||||
*ptr++ = (uint8_t) (val >> 24);
|
||||
*ptr++ = (uint8_t) (val >> 16);
|
||||
*ptr++ = (uint8_t) (val >> 8);
|
||||
*ptr = (uint8_t) val;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Read a two-byte little-endian value.
|
||||
*/
|
||||
DIError DiskImgLib::ReadShortLE(GenericFD* pGFD, uint16_t* pBuf)
|
||||
{
|
||||
DIError dierr;
|
||||
uint8_t val[2];
|
||||
|
||||
dierr = pGFD->Read(&val[0], 1);
|
||||
if (dierr == kDIErrNone)
|
||||
dierr = pGFD->Read(&val[1], 1);
|
||||
|
||||
*pBuf = val[0] | (short) val[1] << 8;
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read a four-byte little-endian value.
|
||||
*/
|
||||
DIError DiskImgLib::ReadLongLE(GenericFD* pGFD, uint32_t* pBuf)
|
||||
{
|
||||
DIError dierr;
|
||||
uint8_t val[4];
|
||||
|
||||
dierr = pGFD->Read(&val[0], 1);
|
||||
if (dierr == kDIErrNone)
|
||||
dierr = pGFD->Read(&val[1], 1);
|
||||
if (dierr == kDIErrNone)
|
||||
dierr = pGFD->Read(&val[2], 1);
|
||||
if (dierr == kDIErrNone)
|
||||
dierr = pGFD->Read(&val[3], 1);
|
||||
|
||||
*pBuf = val[0] | (uint32_t)val[1] << 8 |
|
||||
(uint32_t)val[2] << 16 | (uint32_t)val[3] << 24;
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a two-byte little-endian value.
|
||||
*/
|
||||
DIError DiskImgLib::WriteShortLE(FILE* fp, uint16_t val)
|
||||
{
|
||||
putc(val, fp);
|
||||
putc(val >> 8, fp);
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a four-byte little-endian value.
|
||||
*/
|
||||
DIError DiskImgLib::WriteLongLE(FILE* fp, uint32_t val)
|
||||
{
|
||||
putc(val, fp);
|
||||
putc(val >> 8, fp);
|
||||
putc(val >> 16, fp);
|
||||
putc(val >> 24, fp);
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a two-byte little-endian value.
|
||||
*/
|
||||
DIError DiskImgLib::WriteShortLE(GenericFD* pGFD, uint16_t val)
|
||||
{
|
||||
uint8_t buf;
|
||||
|
||||
buf = (uint8_t) val;
|
||||
pGFD->Write(&buf, 1);
|
||||
buf = val >> 8;
|
||||
return pGFD->Write(&buf, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a four-byte little-endian value.
|
||||
*/
|
||||
DIError DiskImgLib::WriteLongLE(GenericFD* pGFD, uint32_t val)
|
||||
{
|
||||
uint8_t buf;
|
||||
|
||||
buf = (uint8_t) val;
|
||||
pGFD->Write(&buf, 1);
|
||||
buf = (uint8_t) (val >> 8);
|
||||
pGFD->Write(&buf, 1);
|
||||
buf = (uint8_t) (val >> 16);
|
||||
pGFD->Write(&buf, 1);
|
||||
buf = (uint8_t) (val >> 24);
|
||||
return pGFD->Write(&buf, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a two-byte big-endian value.
|
||||
*/
|
||||
DIError DiskImgLib::WriteShortBE(GenericFD* pGFD, uint16_t val)
|
||||
{
|
||||
uint8_t buf;
|
||||
|
||||
buf = val >> 8;
|
||||
pGFD->Write(&buf, 1);
|
||||
buf = (uint8_t) val;
|
||||
return pGFD->Write(&buf, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write a four-byte big-endian value.
|
||||
*/
|
||||
DIError DiskImgLib::WriteLongBE(GenericFD* pGFD, uint32_t val)
|
||||
{
|
||||
uint8_t buf;
|
||||
|
||||
buf = (uint8_t) (val >> 24);
|
||||
pGFD->Write(&buf, 1);
|
||||
buf = (uint8_t) (val >> 16);
|
||||
pGFD->Write(&buf, 1);
|
||||
buf = (uint8_t) (val >> 8);
|
||||
pGFD->Write(&buf, 1);
|
||||
buf = (uint8_t) val;
|
||||
return pGFD->Write(&buf, 1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Find the filename component of a local pathname. Uses the fssep passed
|
||||
* in. If the fssep is '\0' (as is the case for DOS 3.3), then the entire
|
||||
* pathname is returned.
|
||||
*
|
||||
* Always returns a pointer to a string; never returns NULL.
|
||||
*/
|
||||
const char* DiskImgLib::FilenameOnly(const char* pathname, char fssep)
|
||||
{
|
||||
const char* retstr;
|
||||
const char* pSlash;
|
||||
char* tmpStr = NULL;
|
||||
|
||||
assert(pathname != NULL);
|
||||
if (fssep == '\0') {
|
||||
retstr = pathname;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
pSlash = strrchr(pathname, fssep);
|
||||
if (pSlash == NULL) {
|
||||
retstr = pathname; /* whole thing is the filename */
|
||||
goto bail;
|
||||
}
|
||||
|
||||
pSlash++;
|
||||
if (*pSlash == '\0') {
|
||||
if (strlen(pathname) < 2) {
|
||||
retstr = pathname; /* the pathname is just "/"? Whatever */
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* some bonehead put an fssep on the very end; back up before it */
|
||||
/* (not efficient, but this should be rare, and I'm feeling lazy) */
|
||||
tmpStr = strdup(pathname);
|
||||
tmpStr[strlen(pathname)-1] = '\0';
|
||||
pSlash = strrchr(tmpStr, fssep);
|
||||
|
||||
if (pSlash == NULL) {
|
||||
retstr = pathname; /* just a filename with a '/' after it */
|
||||
goto bail;
|
||||
}
|
||||
|
||||
pSlash++;
|
||||
if (*pSlash == '\0') {
|
||||
retstr = pathname; /* I give up! */
|
||||
goto bail;
|
||||
}
|
||||
|
||||
retstr = pathname + (pSlash - tmpStr);
|
||||
|
||||
} else {
|
||||
retstr = pSlash;
|
||||
}
|
||||
|
||||
bail:
|
||||
free(tmpStr);
|
||||
return retstr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the filename extension found in a full pathname.
|
||||
*
|
||||
* An extension is the stuff following the last '.' in the filename. If
|
||||
* there is nothing following the last '.', then there is no extension.
|
||||
*
|
||||
* Returns a pointer to the '.' preceding the extension, or NULL if no
|
||||
* extension was found.
|
||||
*
|
||||
* We guarantee that there is at least one character after the '.'.
|
||||
*/
|
||||
const char* DiskImgLib::FindExtension(const char* pathname, char fssep)
|
||||
{
|
||||
const char* pFilename;
|
||||
const char* pExt;
|
||||
|
||||
/*
|
||||
* We have to isolate the filename so that we don't get excited
|
||||
* about "/foo.bar/file".
|
||||
*/
|
||||
pFilename = FilenameOnly(pathname, fssep);
|
||||
assert(pFilename != NULL);
|
||||
pExt = strrchr(pFilename, kFilenameExtDelim);
|
||||
|
||||
/* also check for "/blah/foo.", which doesn't count */
|
||||
if (pExt != NULL && *(pExt+1) != '\0')
|
||||
return pExt;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Like strcpy(), but allocate with new[] instead.
|
||||
*
|
||||
* If "str" is NULL, or "new" fails, this returns NULL.
|
||||
*
|
||||
* TODO: should be "StrdupNew()"
|
||||
*/
|
||||
char* DiskImgLib::StrcpyNew(const char* str)
|
||||
{
|
||||
char* newStr;
|
||||
|
||||
if (str == NULL)
|
||||
return NULL;
|
||||
newStr = new char[strlen(str)+1];
|
||||
if (newStr != NULL)
|
||||
strcpy(newStr, str);
|
||||
return newStr;
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
/*
|
||||
* Convert the value from GetLastError() to its DIError counterpart.
|
||||
*/
|
||||
DIError DiskImgLib::LastErrorToDIError(void)
|
||||
{
|
||||
DWORD lastErr = ::GetLastError();
|
||||
|
||||
switch (lastErr) {
|
||||
case ERROR_FILE_NOT_FOUND: return kDIErrFileNotFound; // 2
|
||||
case ERROR_ACCESS_DENIED: return kDIErrAccessDenied; // 5
|
||||
case ERROR_WRITE_PROTECT: return kDIErrWriteProtected; // 19
|
||||
case ERROR_SECTOR_NOT_FOUND: return kDIErrGeneric; // 27
|
||||
case ERROR_SHARING_VIOLATION: return kDIErrSharingViolation; // 32
|
||||
case ERROR_HANDLE_EOF: return kDIErrEOF; // 38
|
||||
case ERROR_INVALID_PARAMETER: return kDIErrInvalidArg; // 87
|
||||
case ERROR_SEM_TIMEOUT: return kDIErrGenericIO; // 121
|
||||
// ERROR_SEM_TIMEOUT seen read bad blocks from floptical under Win2K
|
||||
|
||||
case ERROR_INVALID_HANDLE: // 6
|
||||
LOGI("HEY: got ERROR_INVALID_HANDLE!");
|
||||
return kDIErrInternal;
|
||||
case ERROR_NEGATIVE_SEEK: // 131
|
||||
LOGI("HEY: got ERROR_NEGATIVE_SEEK!");
|
||||
return kDIErrInternal;
|
||||
default:
|
||||
LOGI("LastErrorToDIError: not converting 0x%08lx (%ld)",
|
||||
lastErr, lastErr);
|
||||
return kDIErrGeneric;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns "true" if we're running on Win9x (Win95, Win98, WinME), "false"
|
||||
* if not (could be WinNT/2K/XP or even Win31 with Win32s).
|
||||
*/
|
||||
bool DiskImgLib::IsWin9x(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
3400
diskimg/DOS33.cpp
Normal file
3400
diskimg/DOS33.cpp
Normal file
File diff suppressed because it is too large
Load Diff
2904
diskimg/DOSImage.cpp
Normal file
2904
diskimg/DOSImage.cpp
Normal file
File diff suppressed because it is too large
Load Diff
537
diskimg/DiskFS.cpp
Normal file
537
diskimg/DiskFS.cpp
Normal file
@ -0,0 +1,537 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* DiskFS base class.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* A2File
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Set the quality level (a/k/a damage level) of a file.
|
||||
*
|
||||
* Refuse to "improve" the quality level of a file.
|
||||
*/
|
||||
void A2File::SetQuality(FileQuality quality)
|
||||
{
|
||||
if (quality == kQualityGood &&
|
||||
(fFileQuality == kQualitySuspicious || fFileQuality == kQualityDamaged))
|
||||
{
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
if (quality == kQualitySuspicious && fFileQuality == kQualityDamaged) {
|
||||
//assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
fFileQuality = quality;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset the quality level after making repairs.
|
||||
*/
|
||||
void A2File::ResetQuality(void)
|
||||
{
|
||||
fFileQuality = kQualityGood;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* DiskFS
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Set the DiskImg pointer. We add or subtract from the DiskImg's ref count
|
||||
* so that it can be sure there are no DiskFS objects left dangling when the
|
||||
* DiskImg is deleted.
|
||||
*/
|
||||
void DiskFS::SetDiskImg(DiskImg* pImg)
|
||||
{
|
||||
if (pImg == NULL && fpImg == NULL) {
|
||||
LOGI("SetDiskImg: no-op (both NULL)");
|
||||
return;
|
||||
} else if (fpImg == pImg) {
|
||||
LOGI("SetDiskImg: no-op (old == new)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (fpImg != NULL)
|
||||
fpImg->RemoveDiskFS(this);
|
||||
if (pImg != NULL)
|
||||
pImg->AddDiskFS(this);
|
||||
fpImg = pImg;
|
||||
}
|
||||
|
||||
/*
|
||||
* Flush changes to disk.
|
||||
*/
|
||||
DIError DiskFS::Flush(DiskImg::FlushMode mode)
|
||||
{
|
||||
SubVolume* pSubVol = GetNextSubVolume(NULL);
|
||||
DIError dierr;
|
||||
|
||||
while (pSubVol != NULL) {
|
||||
|
||||
// quick sanity check
|
||||
assert(pSubVol->GetDiskFS()->GetDiskImg() == pSubVol->GetDiskImg());
|
||||
|
||||
dierr = pSubVol->GetDiskFS()->Flush(mode); // recurse
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
|
||||
pSubVol = GetNextSubVolume(pSubVol);
|
||||
}
|
||||
|
||||
assert(fpImg != NULL);
|
||||
|
||||
return fpImg->FlushImage(mode);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the "read only" flag on our DiskImg and those of our sub-volumes.
|
||||
*/
|
||||
void DiskFS::SetAllReadOnly(bool val)
|
||||
{
|
||||
SubVolume* pSubVol = GetNextSubVolume(NULL);
|
||||
|
||||
/* put current volume in read-only mode */
|
||||
if (fpImg != NULL)
|
||||
fpImg->SetReadOnly(val);
|
||||
|
||||
/* handle our kids */
|
||||
while (pSubVol != NULL) {
|
||||
// quick sanity check
|
||||
assert(pSubVol->GetDiskFS()->GetDiskImg() == pSubVol->GetDiskImg());
|
||||
|
||||
//pSubVol->GetDiskImg()->SetReadOnly(val);
|
||||
pSubVol->GetDiskFS()->SetAllReadOnly(val); // recurse
|
||||
|
||||
pSubVol = GetNextSubVolume(pSubVol);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* The file list looks something like this:
|
||||
*
|
||||
* volume-dir
|
||||
* file1
|
||||
* file2
|
||||
* subdir1
|
||||
* subdir1:file1
|
||||
* subdir1:file2
|
||||
* subdir1:subsub1
|
||||
* subdir1:subsub1:file1
|
||||
* subdir1:subsub2
|
||||
* subdir1:subsub2:file1
|
||||
* subdir1:subsub2:file2
|
||||
* subdir1:file3
|
||||
* file3
|
||||
*
|
||||
* Everything contained within a subdir comes after the subdir entry and
|
||||
* before any entries from later subdirs at the same level.
|
||||
*
|
||||
* It's unclear whether a linear list or a hierarchical tree structure is
|
||||
* the most appropriate way to hold the data. The tree is easier to update,
|
||||
* but the linear list corresponds to the primary view in CiderPress, and
|
||||
* lists are simpler and easier to manage. For now I'm sticking with a list.
|
||||
*
|
||||
* The files MUST be in the order in which they came from the disk. This
|
||||
* doesn't matter most of the time, but for Pascal volumes it's essential
|
||||
* for ensuring that the Write command doesn't run over the next file.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Add a file to the end of our list.
|
||||
*/
|
||||
void DiskFS::AddFileToList(A2File* pFile)
|
||||
{
|
||||
assert(pFile->GetNext() == NULL);
|
||||
|
||||
if (fpA2Head == NULL) {
|
||||
assert(fpA2Tail == NULL);
|
||||
fpA2Head = fpA2Tail = pFile;
|
||||
} else {
|
||||
pFile->SetPrev(fpA2Tail);
|
||||
fpA2Tail->SetNext(pFile);
|
||||
fpA2Tail = pFile;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Insert a file into its appropriate place in the list, based on a file
|
||||
* hierarchy.
|
||||
*
|
||||
* Pass in the thing to be added ("pFile") and the previous entry ("pPrev").
|
||||
* An empty hierarchic filesystem will have an entry for the volume dir, so
|
||||
* we should never have an empty list or a NULL pPrev.
|
||||
*
|
||||
* The part where things go pear-shaped happens if "pPrev" is a subdirectory.
|
||||
* If so, we need to come after all of the subdir's entries, including any
|
||||
* entries for sub-subdirs. There's no graceful way to go about this in a
|
||||
* linear list.
|
||||
*
|
||||
* (We'd love to be able to find the *next* entry and then back up one,
|
||||
* but odds are that there isn't a "next" entry if we're busily creating
|
||||
* files.)
|
||||
*/
|
||||
void DiskFS::InsertFileInList(A2File* pFile, A2File* pPrev)
|
||||
{
|
||||
assert(pFile->GetNext() == NULL);
|
||||
|
||||
if (fpA2Head == NULL) {
|
||||
assert(pPrev == NULL);
|
||||
fpA2Head = fpA2Tail = pFile;
|
||||
return;
|
||||
} else if (pPrev == NULL) {
|
||||
// create two entries on DOS disk, delete first, add new file
|
||||
pFile->SetNext(fpA2Head);
|
||||
fpA2Head = pFile;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we're inserting after the parent (i.e. we're the very first thing
|
||||
* in a subdir) or after a plain file, just drop it in.
|
||||
*
|
||||
* If we're inserting after a subdir, go fish.
|
||||
*/
|
||||
if (pPrev->IsDirectory() && pFile->GetParent() != pPrev) {
|
||||
pPrev = SkipSubdir(pPrev);
|
||||
}
|
||||
|
||||
pFile->SetNext(pPrev->GetNext());
|
||||
pPrev->SetNext(pFile);
|
||||
}
|
||||
|
||||
/*
|
||||
* Skip over all entries in the subdir we're pointing to.
|
||||
*
|
||||
* The return value is the very last entry in the subdir.
|
||||
*/
|
||||
A2File* DiskFS::SkipSubdir(A2File* pSubdir)
|
||||
{
|
||||
if (pSubdir->GetNext() == NULL)
|
||||
return pSubdir; // end of list reached -- subdir is empty
|
||||
|
||||
A2File* pCur = pSubdir;
|
||||
A2File* pNext = NULL;
|
||||
|
||||
assert(pCur != NULL); // at least one time through the loop
|
||||
|
||||
while (pCur != NULL) {
|
||||
pNext = pCur->GetNext();
|
||||
if (pNext == NULL) // end of list reached
|
||||
return pCur;
|
||||
|
||||
if (pNext->GetParent() != pSubdir) // end of dir reached
|
||||
return pCur;
|
||||
if (pNext->IsDirectory())
|
||||
pCur = SkipSubdir(pNext); // get last entry in dir
|
||||
else
|
||||
pCur = pNext; // advance forward one
|
||||
}
|
||||
|
||||
/* should never get here */
|
||||
assert(false);
|
||||
return pNext;
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete a member from the list.
|
||||
*
|
||||
* We're currently singly-linked, making this rather expensive.
|
||||
*/
|
||||
void DiskFS::DeleteFileFromList(A2File* pFile)
|
||||
{
|
||||
if (fpA2Head == pFile) {
|
||||
/* delete the head of the list */
|
||||
fpA2Head = fpA2Head->GetNext();
|
||||
delete pFile;
|
||||
} else {
|
||||
A2File* pCur = fpA2Head;
|
||||
while (pCur != NULL) {
|
||||
if (pCur->GetNext() == pFile) {
|
||||
/* found it */
|
||||
A2File* pNextNext = pCur->GetNext()->GetNext();
|
||||
delete pCur->GetNext();
|
||||
pCur->SetNext(pNextNext);
|
||||
break;
|
||||
}
|
||||
pCur = pCur->GetNext();
|
||||
}
|
||||
|
||||
if (pCur == NULL) {
|
||||
LOGI("GLITCH: couldn't find element to delete!");
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Access the "next" pointer.
|
||||
*
|
||||
* Because we apparently can't declare an anonymous class as a friend
|
||||
* in MSVC++6.0, this can't be an inline function.
|
||||
*/
|
||||
A2File* DiskFS::GetNextFile(A2File* pFile) const
|
||||
{
|
||||
if (pFile == NULL)
|
||||
return fpA2Head;
|
||||
else
|
||||
return pFile->GetNext();
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the #of elements in the linear file list.
|
||||
*
|
||||
* Right now the only code that calls this is the disk info panel in
|
||||
* CiderPress, so we don't need it to be efficient.
|
||||
*/
|
||||
long DiskFS::GetFileCount(void) const
|
||||
{
|
||||
long count = 0;
|
||||
|
||||
A2File* pFile = fpA2Head;
|
||||
while (pFile != NULL) {
|
||||
count++;
|
||||
pFile = pFile->GetNext();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete all entries in the list.
|
||||
*/
|
||||
void DiskFS::DeleteFileList(void)
|
||||
{
|
||||
A2File* pFile;
|
||||
A2File* pNext;
|
||||
|
||||
pFile = fpA2Head;
|
||||
while (pFile != NULL) {
|
||||
pNext = pFile->GetNext();
|
||||
delete pFile;
|
||||
pFile = pNext;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Dump file list.
|
||||
*/
|
||||
void DiskFS::DumpFileList(void)
|
||||
{
|
||||
A2File* pFile;
|
||||
|
||||
LOGI("DiskFS file list contents:");
|
||||
|
||||
pFile = GetNextFile(NULL);
|
||||
while (pFile != NULL) {
|
||||
LOGI(" %s", pFile->GetPathName());
|
||||
pFile = GetNextFile(pFile);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Run through the list of files and find one that matches (case-insensitive).
|
||||
*
|
||||
* This does not attempt to open files in sub-volumes. We could, but it's
|
||||
* likely that the application has "decorated" the name in some fashion,
|
||||
* e.g. by prepending the sub-volume's volume name to the filename. May
|
||||
* be best to let the application dig for the sub-volume.
|
||||
*/
|
||||
A2File* DiskFS::GetFileByName(const char* fileName, StringCompareFunc func)
|
||||
{
|
||||
A2File* pFile;
|
||||
|
||||
if (func == NULL)
|
||||
func = ::strcasecmp;
|
||||
|
||||
pFile = GetNextFile(NULL);
|
||||
while (pFile != NULL) {
|
||||
if ((*func)(pFile->GetPathName(), fileName) == 0)
|
||||
return pFile;
|
||||
|
||||
pFile = GetNextFile(pFile);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Add a sub-volume to the end of our list.
|
||||
*
|
||||
* Copies some parameters from "this" into pDiskFS, such as whether to
|
||||
* scan for sub-volumes and the various DiskFS parameters.
|
||||
*
|
||||
* Note this happens AFTER the disk has been scanned.
|
||||
*/
|
||||
void DiskFS::AddSubVolumeToList(DiskImg* pDiskImg, DiskFS* pDiskFS)
|
||||
{
|
||||
SubVolume* pSubVol;
|
||||
|
||||
/*
|
||||
* Check the arguments.
|
||||
*/
|
||||
if (pDiskImg == NULL || pDiskFS == NULL) {
|
||||
LOGI(" DiskFS bogus sub volume ptrs %08lx %08lx",
|
||||
(long) pDiskImg, (long) pDiskFS);
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
if (pDiskImg == fpImg || pDiskFS == this) {
|
||||
LOGI(" DiskFS attempt to add self to sub-vol list");
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
if (pDiskFS->GetDiskImg() == NULL) {
|
||||
LOGI(" DiskFS lacks a DiskImg pointer");
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
pSubVol = fpSubVolumeHead;
|
||||
while (pSubVol != NULL) {
|
||||
if (pSubVol->GetDiskImg() == pDiskImg ||
|
||||
pSubVol->GetDiskFS() == pDiskFS)
|
||||
{
|
||||
LOGI(" DiskFS multiple adds on diskimg or diskfs");
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
pSubVol = pSubVol->GetNext();
|
||||
}
|
||||
|
||||
assert(pDiskFS->GetDiskImg() == pDiskImg);
|
||||
|
||||
/*
|
||||
* Looks good. Add it.
|
||||
*/
|
||||
pSubVol = new SubVolume;
|
||||
if (pSubVol == NULL)
|
||||
return;
|
||||
|
||||
pSubVol->Create(pDiskImg, pDiskFS);
|
||||
|
||||
if (fpSubVolumeHead == NULL) {
|
||||
assert(fpSubVolumeTail == NULL);
|
||||
fpSubVolumeHead = fpSubVolumeTail = pSubVol;
|
||||
} else {
|
||||
pSubVol->SetPrev(fpSubVolumeTail);
|
||||
fpSubVolumeTail->SetNext(pSubVol);
|
||||
fpSubVolumeTail = pSubVol;
|
||||
}
|
||||
|
||||
/* make sure inheritable stuff gets copied */
|
||||
CopyInheritables(pDiskFS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy parameters to a sub-volume.
|
||||
*/
|
||||
void DiskFS::CopyInheritables(DiskFS* pNewFS)
|
||||
{
|
||||
for (int i = 0; i < (int) NELEM(fParmTable); i++)
|
||||
pNewFS->fParmTable[i] = fParmTable[i];
|
||||
|
||||
pNewFS->fScanForSubVolumes = fScanForSubVolumes;
|
||||
|
||||
#if 0
|
||||
/* copy scan progress update stuff */
|
||||
pNewFS->fpScanProgressCallback = fpScanProgressCallback;
|
||||
pNewFS->fpScanProgressCookie = fpScanProgressCookie;
|
||||
pNewFS->fpScanCount = -1;
|
||||
strcpy(pNewFS->fpScanMsg, "HEY");
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Access the "next" pointer.
|
||||
*
|
||||
* Because we apparently can't declare an anonymous class as a friend
|
||||
* in MSVC++6.0, this can't be an inline function.
|
||||
*/
|
||||
DiskFS::SubVolume* DiskFS::GetNextSubVolume(const SubVolume* pSubVol) const
|
||||
{
|
||||
if (pSubVol == NULL)
|
||||
return fpSubVolumeHead;
|
||||
else
|
||||
return pSubVol->GetNext();
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete all entries in the list.
|
||||
*/
|
||||
void DiskFS::DeleteSubVolumeList(void)
|
||||
{
|
||||
SubVolume* pSubVol;
|
||||
SubVolume* pNext;
|
||||
|
||||
pSubVol = fpSubVolumeHead;
|
||||
while (pSubVol != NULL) {
|
||||
pNext = pSubVol->GetNext();
|
||||
delete pSubVol;
|
||||
pSubVol = pNext;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Get a parameter.
|
||||
*/
|
||||
long DiskFS::GetParameter(DiskFSParameter parm)
|
||||
{
|
||||
assert(parm > kParmUnknown && parm < kParmMax);
|
||||
return fParmTable[parm];
|
||||
}
|
||||
|
||||
/*
|
||||
* Set a parameter.
|
||||
*
|
||||
* The setting propagates to all sub-volumes.
|
||||
*/
|
||||
void DiskFS::SetParameter(DiskFSParameter parm, long val)
|
||||
{
|
||||
assert(parm > kParmUnknown && parm < kParmMax);
|
||||
fParmTable[parm] = val;
|
||||
|
||||
SubVolume* pSubVol = GetNextSubVolume(NULL);
|
||||
while (pSubVol != NULL) {
|
||||
pSubVol->GetDiskFS()->SetParameter(parm, val);
|
||||
pSubVol = GetNextSubVolume(pSubVol);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Scan for damaged or suspicious files.
|
||||
*/
|
||||
void DiskFS::ScanForDamagedFiles(bool* pDamaged, bool* pSuspicious)
|
||||
{
|
||||
A2File* pFile;
|
||||
|
||||
*pDamaged = *pSuspicious = false;
|
||||
|
||||
pFile = GetNextFile(NULL);
|
||||
while (pFile != NULL) {
|
||||
if (pFile->GetQuality() == A2File::kQualityDamaged)
|
||||
*pDamaged = true;
|
||||
if (pFile->GetQuality() != A2File::kQualityGood)
|
||||
*pSuspicious = true;
|
||||
pFile = GetNextFile(pFile);
|
||||
}
|
||||
}
|
3502
diskimg/DiskImg.cpp
Normal file
3502
diskimg/DiskImg.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1686
diskimg/DiskImg.h
Normal file
1686
diskimg/DiskImg.h
Normal file
File diff suppressed because it is too large
Load Diff
3320
diskimg/DiskImgDetail.h
Normal file
3320
diskimg/DiskImgDetail.h
Normal file
File diff suppressed because it is too large
Load Diff
348
diskimg/DiskImgPriv.h
Normal file
348
diskimg/DiskImgPriv.h
Normal file
@ -0,0 +1,348 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Declarations common within but private to the DiskImg library.
|
||||
*
|
||||
* External code should not include this.
|
||||
*/
|
||||
#ifndef DISKIMG_DISKIMGPRIV_H
|
||||
#define DISKIMG_DISKIMGPRIV_H
|
||||
|
||||
#include "DiskImgDetail.h"
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
// "GenericFD.h" included at end
|
||||
|
||||
using namespace DiskImgLib; // make life easy for all internal code
|
||||
|
||||
namespace DiskImgLib {
|
||||
|
||||
/*
|
||||
* Debug logging macros.
|
||||
*
|
||||
* The macro choice implies a severity level, but we don't currently
|
||||
* support that in the callback interface, so it's not used.
|
||||
*/
|
||||
#define DLOG_BASE(file, line, format, ...) \
|
||||
Global::PrintDebugMsg((file), (line), (format), ##__VA_ARGS__)
|
||||
|
||||
//#ifdef SHOW_LOGV
|
||||
# define LOGV(format, ...) DLOG_BASE(__FILE__, __LINE__, (format), ##__VA_ARGS__)
|
||||
//#else
|
||||
//# define LOGV(format, ...) ((void) 0)
|
||||
//#endif
|
||||
#define LOGD(format, ...) DLOG_BASE(__FILE__, __LINE__, (format), ##__VA_ARGS__)
|
||||
#define LOGI(format, ...) DLOG_BASE(__FILE__, __LINE__, (format), ##__VA_ARGS__)
|
||||
#define LOGW(format, ...) DLOG_BASE(__FILE__, __LINE__, (format), ##__VA_ARGS__)
|
||||
#define LOGE(format, ...) DLOG_BASE(__FILE__, __LINE__, (format), ##__VA_ARGS__)
|
||||
|
||||
/* put this in to break on interesting events when built debug */
|
||||
#if defined(_DEBUG)
|
||||
# define DebugBreak() { assert(false); }
|
||||
#else
|
||||
# define DebugBreak() ((void) 0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Standard goodies.
|
||||
*/
|
||||
#define NELEM(x) (sizeof(x) / sizeof(x[0]))
|
||||
|
||||
#define ErrnoOrGeneric() (errno != 0 ? (DIError) errno : kDIErrGeneric)
|
||||
|
||||
|
||||
/* filename manipulation functions */
|
||||
const char* FilenameOnly(const char* pathname, char fssep);
|
||||
const char* FindExtension(const char* pathname, char fssep);
|
||||
char* StrcpyNew(const char* str);
|
||||
|
||||
/* get/set integer values out of a memory buffer */
|
||||
uint16_t GetShortLE(const uint8_t* buf);
|
||||
uint32_t GetLongLE(const uint8_t* buf);
|
||||
uint16_t GetShortBE(const uint8_t* buf);
|
||||
uint32_t GetLongBE(const uint8_t* buf);
|
||||
uint32_t Get24BE(const uint8_t* ptr);
|
||||
void PutShortLE(uint8_t* ptr, uint16_t val);
|
||||
void PutLongLE(uint8_t* ptr, uint32_t val);
|
||||
void PutShortBE(uint8_t* ptr, uint16_t val);
|
||||
void PutLongBE(uint8_t* ptr, uint32_t val);
|
||||
|
||||
/* little-endian read/write, for file headers (mainly 2MG and DC42) */
|
||||
DIError ReadShortLE(GenericFD* pGFD, uint16_t* pBuf);
|
||||
DIError ReadLongLE(GenericFD* pGFD, uint32_t* pBuf);
|
||||
DIError WriteShortLE(FILE* fp, uint16_t val);
|
||||
DIError WriteLongLE(FILE* fp, uint32_t val);
|
||||
DIError WriteShortLE(GenericFD* pGFD, uint16_t val);
|
||||
DIError WriteLongLE(GenericFD* pGFD, uint32_t val);
|
||||
DIError WriteShortBE(GenericFD* pGFD, uint16_t val);
|
||||
DIError WriteLongBE(GenericFD* pGFD, uint32_t val);
|
||||
|
||||
#ifdef _WIN32
|
||||
/* Windows helpers */
|
||||
DIError LastErrorToDIError(void);
|
||||
bool IsWin9x(void);
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Provide access to a buffer of data as if it were a circular buffer.
|
||||
* Access is through the C array operator ([]).
|
||||
*
|
||||
* This DOES NOT own the array it is handed, and will not try to
|
||||
* free it.
|
||||
*/
|
||||
class CircularBufferAccess {
|
||||
public:
|
||||
CircularBufferAccess(uint8_t* buf, long len) :
|
||||
fBuf(buf), fLen(len)
|
||||
{ assert(fLen > 0); assert(fBuf != NULL); }
|
||||
CircularBufferAccess(const uint8_t* buf, long len) :
|
||||
fBuf(const_cast<uint8_t*>(buf)), fLen(len)
|
||||
{ assert(fLen > 0); assert(fBuf != NULL); }
|
||||
~CircularBufferAccess(void) {}
|
||||
|
||||
/*
|
||||
* Be circular. Assume that we won't stray far past the end, so
|
||||
* it's cheaper to subtract than mod.
|
||||
*/
|
||||
uint8_t& operator[](int idx) const {
|
||||
if (idx < 0) {
|
||||
assert(false);
|
||||
}
|
||||
while (idx >= fLen)
|
||||
idx -= fLen;
|
||||
return fBuf[idx];
|
||||
}
|
||||
|
||||
//uint8_t* GetPointer(int idx) const {
|
||||
// while (idx >= fLen)
|
||||
// idx -= fLen;
|
||||
// return &fBuf[idx];
|
||||
//}
|
||||
|
||||
int Normalize(int idx) const {
|
||||
while (idx >= fLen)
|
||||
idx -= fLen;
|
||||
return idx;
|
||||
}
|
||||
|
||||
long GetSize(void) const {
|
||||
return fLen;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t* fBuf;
|
||||
long fLen;
|
||||
};
|
||||
|
||||
/*
|
||||
* Manage an output buffer into which we write one bit at a time.
|
||||
*
|
||||
* Bits fill in from the MSB to the LSB. If we write 10 bits, the
|
||||
* output buffer will look like this:
|
||||
*
|
||||
* xxxxxxxx xx000000
|
||||
*
|
||||
* Call WriteBit() repeatedly. When done, call Finish() to write any pending
|
||||
* data and return the number of bits in the buffer.
|
||||
*/
|
||||
class BitOutputBuffer {
|
||||
public:
|
||||
/* pass in the output buffer and the output buffer's size */
|
||||
BitOutputBuffer(uint8_t* buf, int size) {
|
||||
fBufStart = fBuf = buf;
|
||||
fBufSize = size;
|
||||
fBitMask = 0x80;
|
||||
fByte = 0;
|
||||
fOverflow = false;
|
||||
}
|
||||
virtual ~BitOutputBuffer(void) {}
|
||||
|
||||
/* write a single bit */
|
||||
void WriteBit(int val) {
|
||||
if (fBuf - fBufStart >= fBufSize) {
|
||||
if (!fOverflow) {
|
||||
LOGI("Overran bit output buffer");
|
||||
DebugBreak();
|
||||
fOverflow = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (val)
|
||||
fByte |= fBitMask;
|
||||
fBitMask >>= 1;
|
||||
if (fBitMask == 0) {
|
||||
*fBuf++ = fByte;
|
||||
fBitMask = 0x80;
|
||||
fByte = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* flush pending bits; returns length in bits (or -1 on overrun) */
|
||||
int Finish(void) {
|
||||
int outputBits;
|
||||
|
||||
if (fOverflow)
|
||||
return -1;
|
||||
|
||||
outputBits = (fBuf - fBufStart) * 8;
|
||||
|
||||
if (fBitMask != 0x80) {
|
||||
*fBuf++ = fByte;
|
||||
|
||||
assert(fBitMask != 0);
|
||||
while (fBitMask != 0x80) {
|
||||
outputBits++;
|
||||
fBitMask <<= 1;
|
||||
}
|
||||
}
|
||||
return outputBits;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t* fBufStart;
|
||||
uint8_t* fBuf;
|
||||
int fBufSize;
|
||||
uint8_t fBitMask;
|
||||
uint8_t fByte;
|
||||
bool fOverflow;
|
||||
};
|
||||
|
||||
/*
|
||||
* Extract data from the buffer one bit or one byte at a time.
|
||||
*/
|
||||
class BitInputBuffer {
|
||||
public:
|
||||
BitInputBuffer(const uint8_t* buf, int bitCount) {
|
||||
fBufStart = fBuf = buf;
|
||||
fBitCount = bitCount;
|
||||
fCurrentBit = 0;
|
||||
fBitPosn = 7;
|
||||
fBitsConsumed = 0;
|
||||
}
|
||||
virtual ~BitInputBuffer(void) {}
|
||||
|
||||
/*
|
||||
* Get the next bit. Returns 0 or 1.
|
||||
*
|
||||
* If we wrapped around to the start of the buffer, and "pWrap" is
|
||||
* non-null, set "*pWrap". (This does *not* set it to "false" if we
|
||||
* don't wrap.)
|
||||
*/
|
||||
uint8_t GetBit(bool* pWrap) {
|
||||
uint8_t val;
|
||||
|
||||
//assert(fBitPosn == 7 - (fCurrentBit & 0x07));
|
||||
|
||||
if (fCurrentBit == fBitCount) {
|
||||
/* end reached, wrap to start */
|
||||
fCurrentBit = 0;
|
||||
fBitPosn = 7;
|
||||
fBuf = fBufStart;
|
||||
//fByte = *fBuf++;
|
||||
if (pWrap != NULL)
|
||||
*pWrap = true;
|
||||
}
|
||||
|
||||
val = (*fBuf >> fBitPosn) & 0x01;
|
||||
|
||||
fCurrentBit++;
|
||||
fBitPosn--;
|
||||
if (fBitPosn < 0) {
|
||||
fBitPosn = 7;
|
||||
fBuf++;
|
||||
}
|
||||
|
||||
fBitsConsumed++;
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the next 8 bits.
|
||||
*/
|
||||
uint8_t GetByte(bool* pWrap) {
|
||||
uint8_t val;
|
||||
int i;
|
||||
|
||||
if (true || fCurrentBit > fBitCount-8) {
|
||||
/* near end, use single-bit function iteratively */
|
||||
val = 0;
|
||||
for (i = 0; i < 8; i++)
|
||||
val = (val << 1) | GetBit(pWrap);
|
||||
} else {
|
||||
/* room to spare, grab it in one or two chunks */
|
||||
assert(false);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the start position.
|
||||
*/
|
||||
void SetStartPosition(int bitOffset) {
|
||||
assert(bitOffset >= 0 && bitOffset < fBitCount);
|
||||
fCurrentBit = bitOffset;
|
||||
fBitPosn = 7 - (bitOffset & 0x07); // mod 8, 0 to MSB
|
||||
fBuf = fBufStart + (bitOffset >> 3); // div 8
|
||||
}
|
||||
|
||||
/* used to ensure we consume exactly 100% of bits */
|
||||
void ResetBitsConsumed(void) { fBitsConsumed = 0; }
|
||||
int GetBitsConsumed(void) const { return fBitsConsumed; }
|
||||
|
||||
private:
|
||||
const uint8_t* fBufStart;
|
||||
const uint8_t* fBuf;
|
||||
int fBitCount; // #of bits in buffer
|
||||
int fCurrentBit; // where we are in buffer
|
||||
int fBitPosn; // which bit to access within byte
|
||||
//uint8_t fByte;
|
||||
|
||||
int fBitsConsumed; // sanity check - all bits used?
|
||||
};
|
||||
|
||||
/*
|
||||
* Linear bitmap. Suitable for use as a bad block map.
|
||||
*/
|
||||
class LinearBitmap {
|
||||
public:
|
||||
LinearBitmap(int numBits) {
|
||||
assert(numBits > 0);
|
||||
fBits = new uint8_t[(numBits + 7) / 8];
|
||||
memset(fBits, 0, (numBits + 7) / 8);
|
||||
fNumBits = numBits;
|
||||
}
|
||||
~LinearBitmap(void) {
|
||||
delete[] fBits;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set or get the status of bit N.
|
||||
*/
|
||||
bool IsSet(int bit) const {
|
||||
assert(bit >= 0 && bit < fNumBits);
|
||||
return ((fBits[bit >> 3] >> (bit & 0x07)) & 0x01) != 0;
|
||||
}
|
||||
void Set(int bit) {
|
||||
assert(bit >= 0 && bit < fNumBits);
|
||||
fBits[bit >> 3] |= 1 << (bit & 0x07);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t* fBits;
|
||||
int fNumBits;
|
||||
};
|
||||
|
||||
|
||||
} // namespace DiskImgLib
|
||||
|
||||
/*
|
||||
* Most of the code needs these.
|
||||
*/
|
||||
#include "GenericFD.h"
|
||||
|
||||
#endif /*DISKIMG_DISKIMGPRIV_H*/
|
508
diskimg/FAT.cpp
Normal file
508
diskimg/FAT.cpp
Normal file
@ -0,0 +1,508 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Implementation of the Windows FAT filesystem.
|
||||
*
|
||||
* Right now we just try to identify that a disk is in a PC format rather
|
||||
* than Apple II. The trick here is to figure out whether block 0 is a
|
||||
* Master Boot Record or merely a Boot Sector.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* DiskFSFAT
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
const int kBlkSize = 512;
|
||||
const long kBootBlock = 0;
|
||||
const uint16_t kSignature = 0xaa55; // MBR or boot sector
|
||||
const int kSignatureOffset = 0x1fe;
|
||||
const uint8_t kOpcodeMumble = 0x33; // seen on 2nd drive
|
||||
const uint8_t kOpcodeBranch = 0xeb;
|
||||
const uint8_t kOpcodeSetInt = 0xfa;
|
||||
|
||||
typedef struct PartitionTableEntry {
|
||||
uint8_t driveNum; // dl (0x80 or 0x00)
|
||||
uint8_t startHead; // dh
|
||||
uint8_t startSector; // cl (&0x3f=sector, +two hi bits cyl)
|
||||
uint8_t startCylinder; // ch (low 8 bits of 10-bit cylinder)
|
||||
uint8_t type; // partition type
|
||||
uint8_t endHead; // dh
|
||||
uint8_t endSector; // cl
|
||||
uint8_t endCylinder; // ch
|
||||
uint32_t startLBA; // in blocks
|
||||
uint32_t size; // in blocks
|
||||
} PartitionTableEntry;
|
||||
|
||||
/*
|
||||
* Definition of a Master Boot Record, which is block 0 of a physical volume.
|
||||
*/
|
||||
typedef struct DiskFSFAT::MasterBootRecord {
|
||||
/*
|
||||
* Begins immediately with code, usually 0xfa (set interrupt flag) or
|
||||
* 0xeb (relative branch).
|
||||
*/
|
||||
uint8_t firstByte;
|
||||
|
||||
/*
|
||||
* Partition table starts at 0x1be. Four entries, each 16 bytes.
|
||||
*/
|
||||
PartitionTableEntry parTab[4];
|
||||
} MasterBootRecord;
|
||||
|
||||
/*
|
||||
* Definition of a boot sector, which is block 0 of a logical volume.
|
||||
*/
|
||||
typedef struct DiskFSFAT::BootSector {
|
||||
/*
|
||||
* The first few bytes of the boot sector is called the BIOS Parameter
|
||||
* Block, or BPB.
|
||||
*/
|
||||
uint8_t jump[3]; // usually EB XX 90
|
||||
uint8_t oemName[8]; // e.g. "MSWIN4.1" or "MSDOS5.0"
|
||||
uint16_t bytesPerSector; // usually (always?) 512
|
||||
uint8_t sectPerCluster;
|
||||
uint16_t reservedSectors;
|
||||
uint8_t numFAT;
|
||||
uint16_t numRootDirEntries;
|
||||
uint16_t numSectors; // if set, ignore numSectorsHuge
|
||||
uint8_t mediaType;
|
||||
uint16_t numFATSectors;
|
||||
uint16_t sectorsPerTrack;
|
||||
uint16_t numHeads;
|
||||
uint32_t numHiddenSectors;
|
||||
uint32_t numSectorsHuge; // only if numSectors==0
|
||||
/*
|
||||
* This next part can start immediately after the above (at 0x24) for
|
||||
* FAT12/FAT16, or somewhat later (0x42) for FAT32. It doesn't seem
|
||||
* to exist for NTFS. Probably safest to assume it doesn't exist.
|
||||
*
|
||||
* The only way to be sure of what we're dealing with is to know the
|
||||
* partition type, but if this is our block 0 then we can't know what
|
||||
* that is.
|
||||
*/
|
||||
uint8_t driveNum;
|
||||
uint8_t reserved;
|
||||
uint8_t signature; // 0x29
|
||||
uint32_t volumeID;
|
||||
uint8_t volumeLabel[11]; // e.g. "FUBAR "
|
||||
uint8_t fileSysType[8]; // e.g. "FAT12 "
|
||||
|
||||
/*
|
||||
* Code follows. Signature 0xaa55 in the last two bytes.
|
||||
*/
|
||||
} BootSector;
|
||||
|
||||
// some values for MediaType
|
||||
enum MediaType {
|
||||
kMediaTypeLarge = 0xf0, // 1440KB or 2800KB 3.5" disk
|
||||
kMediaTypeHardDrive = 0xf8,
|
||||
kMediaTypeMedium = 0xf9, // 720KB 3.5" disk or 1.2MB 5.25" disk
|
||||
kMediaTypeSmall = 0xfd, // 360KB 5.25" disk
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Unpack the MBR.
|
||||
*
|
||||
* Returns "true" if this looks like an MBR, "false" otherwise.
|
||||
*/
|
||||
/*static*/ bool DiskFSFAT::UnpackMBR(const uint8_t* buf, MasterBootRecord* pOut)
|
||||
{
|
||||
const uint8_t* ptr;
|
||||
int i;
|
||||
|
||||
pOut->firstByte = buf[0x00];
|
||||
|
||||
ptr = &buf[0x1be];
|
||||
for (i = 0; i < 4; i++) {
|
||||
pOut->parTab[i].driveNum = ptr[0x00];
|
||||
pOut->parTab[i].startHead = ptr[0x01];
|
||||
pOut->parTab[i].startSector = ptr[0x02];
|
||||
pOut->parTab[i].startCylinder = ptr[0x03];
|
||||
pOut->parTab[i].type = ptr[0x04];
|
||||
pOut->parTab[i].endHead = ptr[0x05];
|
||||
pOut->parTab[i].endSector = ptr[0x06];
|
||||
pOut->parTab[i].endCylinder = ptr[0x07];
|
||||
pOut->parTab[i].startLBA = GetLongLE(&ptr[0x08]);
|
||||
pOut->parTab[i].size = GetLongLE(&ptr[0x0c]);
|
||||
|
||||
ptr += 16;
|
||||
}
|
||||
|
||||
if (pOut->firstByte != kOpcodeBranch &&
|
||||
pOut->firstByte != kOpcodeSetInt &&
|
||||
pOut->firstByte != kOpcodeMumble)
|
||||
return false;
|
||||
bool foundActive = false;
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (pOut->parTab[i].driveNum == 0x80)
|
||||
foundActive = true;
|
||||
else if (pOut->parTab[i].driveNum != 0x00)
|
||||
return false; // must be 0x00 or 0x80
|
||||
}
|
||||
// CFFA cards don't seem to set the "active" flag
|
||||
if (false && !foundActive)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unpack the boot sector.
|
||||
*
|
||||
* Returns "true" if this looks like a boot sector, "false" otherwise.
|
||||
*/
|
||||
/*static*/ bool DiskFSFAT::UnpackBootSector(const uint8_t* buf, BootSector* pOut)
|
||||
{
|
||||
memcpy(pOut->jump, &buf[0x00], sizeof(pOut->jump));
|
||||
memcpy(pOut->oemName, &buf[0x03], sizeof(pOut->oemName));
|
||||
pOut->bytesPerSector = GetShortLE(&buf[0x0b]);
|
||||
pOut->sectPerCluster = buf[0x0d];
|
||||
pOut->reservedSectors = GetShortLE(&buf[0x0e]);
|
||||
pOut->numFAT = buf[0x10];
|
||||
pOut->numRootDirEntries = GetShortLE(&buf[0x11]);
|
||||
pOut->numSectors = GetShortLE(&buf[0x13]);
|
||||
pOut->mediaType = buf[0x15];
|
||||
pOut->numFATSectors = GetShortLE(&buf[0x16]);
|
||||
pOut->sectorsPerTrack = GetShortLE(&buf[0x18]);
|
||||
pOut->numHeads = GetShortLE(&buf[0x1a]);
|
||||
pOut->numHiddenSectors = GetLongLE(&buf[0x1c]);
|
||||
pOut->numSectorsHuge = GetLongLE(&buf[0x20]);
|
||||
|
||||
if (pOut->jump[0] != kOpcodeBranch && pOut->jump[0] != kOpcodeSetInt)
|
||||
return false;
|
||||
if (pOut->bytesPerSector != 512)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* See if this looks like a FAT volume.
|
||||
*/
|
||||
/*static*/ DIError DiskFSFAT::TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t blkBuf[kBlkSize];
|
||||
MasterBootRecord mbr;
|
||||
BootSector bs;
|
||||
|
||||
dierr = pImg->ReadBlockSwapped(kBootBlock, blkBuf, imageOrder,
|
||||
DiskImg::kSectorOrderProDOS);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
/*
|
||||
* Both MBR and boot sectors have the same signature in block 0.
|
||||
*/
|
||||
if (GetShortLE(&blkBuf[kSignatureOffset]) != kSignature) {
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode it as an MBR and as a partition table. Figure out which
|
||||
* one makes sense. If neither make sense, fail.
|
||||
*/
|
||||
bool hasMBR, hasBS;
|
||||
hasMBR = UnpackMBR(blkBuf, &mbr);
|
||||
hasBS = UnpackBootSector(blkBuf, &bs);
|
||||
LOGI(" FAT hasMBR=%d hasBS=%d", hasMBR, hasBS);
|
||||
|
||||
if (!hasMBR && !hasBS) {
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
goto bail;
|
||||
}
|
||||
if (hasMBR) {
|
||||
LOGI(" FAT partition table found:");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
LOGI(" %d: type=0x%02x start LBA=%-9u size=%u",
|
||||
i, mbr.parTab[i].type,
|
||||
mbr.parTab[i].startLBA, mbr.parTab[i].size);
|
||||
}
|
||||
}
|
||||
if (hasBS) {
|
||||
LOGI(" FAT boot sector found:");
|
||||
LOGI(" OEMName is '%.8s'", bs.oemName);
|
||||
}
|
||||
|
||||
// looks good!
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test to see if the image is a FAT disk.
|
||||
*/
|
||||
/*static*/ DIError DiskFSFAT::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
||||
DiskImg::FSFormat* pFormat, FSLeniency leniency)
|
||||
{
|
||||
/* must be block format, should be at least 360K */
|
||||
if (!pImg->GetHasBlocks() || pImg->GetNumBlocks() < kExpectedMinBlocks)
|
||||
return kDIErrFilesystemNotFound;
|
||||
if (pImg->GetIsEmbedded()) // don't look for FAT inside CFFA!
|
||||
return kDIErrFilesystemNotFound;
|
||||
|
||||
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
|
||||
|
||||
DiskImg::GetSectorOrderArray(ordering, *pOrder);
|
||||
|
||||
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
|
||||
if (ordering[i] == DiskImg::kSectorOrderUnknown)
|
||||
continue;
|
||||
if (TestImage(pImg, ordering[i]) == kDIErrNone) {
|
||||
*pOrder = ordering[i];
|
||||
*pFormat = DiskImg::kFormatMSDOS;
|
||||
return kDIErrNone;
|
||||
}
|
||||
}
|
||||
|
||||
LOGI(" FAT didn't find valid FS");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get things rolling.
|
||||
*/
|
||||
DIError DiskFSFAT::Initialize(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
|
||||
strcpy(fVolumeName, "[MS-DOS]"); // max 11 chars
|
||||
strcpy(fVolumeID, "FATxx [MS-DOS]");
|
||||
|
||||
// take the easy way out
|
||||
fTotalBlocks = fpImg->GetNumBlocks();
|
||||
|
||||
CreateFakeFile();
|
||||
|
||||
SetVolumeUsageMap();
|
||||
|
||||
return dierr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Blank out the volume usage map.
|
||||
*/
|
||||
void DiskFSFAT::SetVolumeUsageMap(void)
|
||||
{
|
||||
VolumeUsage::ChunkState cstate;
|
||||
long block;
|
||||
|
||||
fVolumeUsage.Create(fpImg->GetNumBlocks());
|
||||
|
||||
cstate.isUsed = true;
|
||||
cstate.isMarkedUsed = true;
|
||||
cstate.purpose = VolumeUsage::kChunkPurposeUnknown;
|
||||
|
||||
for (block = fTotalBlocks-1; block >= 0; block--)
|
||||
fVolumeUsage.SetChunkState(block, &cstate);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Fill a buffer with some interesting stuff, and add it to the file list.
|
||||
*/
|
||||
void DiskFSFAT::CreateFakeFile(void)
|
||||
{
|
||||
A2FileFAT* pFile;
|
||||
char buf[768]; // currently running about 430
|
||||
static const char* kFormatMsg =
|
||||
"The FAT12/16/32 and NTFS filesystems are not supported. CiderPress knows\r\n"
|
||||
"how to recognize MS-DOS and Windows volumes so that it can identify\r\n"
|
||||
"PC data on removable media, but it does not know how to view or extract\r\n"
|
||||
"files from them.\r\n"
|
||||
"\r\n"
|
||||
"Some information about this FAT volume:\r\n"
|
||||
"\r\n"
|
||||
" Volume name : '%s'\r\n"
|
||||
" Volume size : %ld blocks (%.2fMB)\r\n"
|
||||
"\r\n"
|
||||
"(CiderPress limits itself to 8GB, so larger volume sizes may not be shown.)\r\n"
|
||||
;
|
||||
long capacity;
|
||||
|
||||
capacity = fTotalBlocks;
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
snprintf(buf, NELEM(buf)-1, kFormatMsg,
|
||||
fVolumeName,
|
||||
capacity,
|
||||
(double) capacity / 2048.0);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
pFile = new A2FileFAT(this);
|
||||
pFile->SetFakeFile(buf, strlen(buf));
|
||||
strcpy(pFile->fFileName, "(not supported)");
|
||||
|
||||
AddFileToList(pFile);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* A2FileFAT
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Dump the contents of the A2File structure.
|
||||
*/
|
||||
void A2FileFAT::Dump(void) const
|
||||
{
|
||||
LOGD("A2FileFAT '%s'", fFileName);
|
||||
}
|
||||
|
||||
/*
|
||||
* Not a whole lot to do.
|
||||
*/
|
||||
DIError A2FileFAT::Open(A2FileDescr** ppOpenFile, bool readOnly,
|
||||
bool rsrcFork /*=false*/)
|
||||
{
|
||||
A2FDFAT* pOpenFile = NULL;
|
||||
|
||||
if (fpOpenFile != NULL)
|
||||
return kDIErrAlreadyOpen;
|
||||
if (rsrcFork)
|
||||
return kDIErrForkNotFound;
|
||||
assert(readOnly == true);
|
||||
|
||||
pOpenFile = new A2FDFAT(this);
|
||||
|
||||
fpOpenFile = pOpenFile;
|
||||
*ppOpenFile = pOpenFile;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* A2FDFAT
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Read a chunk of data from the fake file.
|
||||
*/
|
||||
DIError A2FDFAT::Read(void* buf, size_t len, size_t* pActual)
|
||||
{
|
||||
LOGD(" FAT reading %lu bytes from '%s' (offset=%ld)",
|
||||
(unsigned long) len, fpFile->GetPathName(), (long) fOffset);
|
||||
|
||||
A2FileFAT* pFile = (A2FileFAT*) fpFile;
|
||||
|
||||
/* don't allow them to read past the end of the file */
|
||||
if (fOffset + (long)len > pFile->fLength) {
|
||||
if (pActual == NULL)
|
||||
return kDIErrDataUnderrun;
|
||||
len = (size_t) (pFile->fLength - fOffset);
|
||||
}
|
||||
if (pActual != NULL)
|
||||
*pActual = len;
|
||||
|
||||
memcpy(buf, pFile->GetFakeFileBuf(), len);
|
||||
|
||||
fOffset += len;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write data at the current offset.
|
||||
*/
|
||||
DIError A2FDFAT::Write(const void* buf, size_t len, size_t* pActual)
|
||||
{
|
||||
return kDIErrNotSupported;
|
||||
}
|
||||
|
||||
/*
|
||||
* Seek to a new offset.
|
||||
*/
|
||||
DIError A2FDFAT::Seek(di_off_t offset, DIWhence whence)
|
||||
{
|
||||
di_off_t fileLen = ((A2FileFAT*) fpFile)->fLength;
|
||||
|
||||
switch (whence) {
|
||||
case kSeekSet:
|
||||
if (offset < 0 || offset > fileLen)
|
||||
return kDIErrInvalidArg;
|
||||
fOffset = offset;
|
||||
break;
|
||||
case kSeekEnd:
|
||||
if (offset > 0 || offset < -fileLen)
|
||||
return kDIErrInvalidArg;
|
||||
fOffset = fileLen + offset;
|
||||
break;
|
||||
case kSeekCur:
|
||||
if (offset < -fOffset ||
|
||||
offset >= (fileLen - fOffset))
|
||||
{
|
||||
return kDIErrInvalidArg;
|
||||
}
|
||||
fOffset += offset;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
return kDIErrInvalidArg;
|
||||
}
|
||||
|
||||
assert(fOffset >= 0 && fOffset <= fileLen);
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return current offset.
|
||||
*/
|
||||
di_off_t A2FDFAT::Tell(void)
|
||||
{
|
||||
return fOffset;
|
||||
}
|
||||
|
||||
/*
|
||||
* Release file state, and tell our parent to destroy us.
|
||||
*/
|
||||
DIError A2FDFAT::Close(void)
|
||||
{
|
||||
fpFile->CloseDescr(this);
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the #of sectors/blocks in the file.
|
||||
*/
|
||||
long A2FDFAT::GetSectorCount(void) const
|
||||
{
|
||||
A2FileFAT* pFile = (A2FileFAT*) fpFile;
|
||||
return (long) ((pFile->fLength+255) / 256);
|
||||
}
|
||||
|
||||
long A2FDFAT::GetBlockCount(void) const
|
||||
{
|
||||
A2FileFAT* pFile = (A2FileFAT*) fpFile;
|
||||
return (long) ((pFile->fLength+511) / 512);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the Nth track/sector in this file.
|
||||
*/
|
||||
DIError A2FDFAT::GetStorage(long sectorIdx, long* pTrack, long* pSector) const
|
||||
{
|
||||
return kDIErrNotSupported;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the Nth 512-byte block in this file.
|
||||
*/
|
||||
DIError A2FDFAT::GetStorage(long blockIdx, long* pBlock) const
|
||||
{
|
||||
return kDIErrNotSupported;
|
||||
}
|
1522
diskimg/FDI.cpp
Normal file
1522
diskimg/FDI.cpp
Normal file
File diff suppressed because it is too large
Load Diff
353
diskimg/FocusDrive.cpp
Normal file
353
diskimg/FocusDrive.cpp
Normal file
@ -0,0 +1,353 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* This is a container for the Parsons Engineering FocusDrive.
|
||||
*
|
||||
* The format was reverse-engineered by Ranger Harke.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
|
||||
const int kBlkSize = 512;
|
||||
const int kPartMapBlock = 0; // partition map lives here
|
||||
const int kMaxPartitions = 30; // max allowed partitions on a drive
|
||||
const int kPartNameStart = 1; // partition names start here (2 blocks)
|
||||
const int kPartNameLen = 32; // max length of partition name
|
||||
|
||||
static const char* kSignature = "Parsons Engin.";
|
||||
const int kSignatureLen = 14;
|
||||
|
||||
/*
|
||||
* Format of partition map. It resides in the first 256 bytes of block 0.
|
||||
* All values are in little-endian order.
|
||||
*
|
||||
* We also make space here for the partition names, which live on blocks 1+2.
|
||||
*/
|
||||
typedef struct DiskFSFocusDrive::PartitionMap {
|
||||
uint8_t signature[kSignatureLen];
|
||||
uint8_t unknown1;
|
||||
uint8_t partCount; // could be ushort, combined w/unknown1
|
||||
uint8_t unknown2[16];
|
||||
struct Entry {
|
||||
uint32_t startBlock;
|
||||
uint32_t blockCount;
|
||||
uint32_t unknown1;
|
||||
uint32_t unknown2;
|
||||
uint8_t name[kPartNameLen+1];
|
||||
} entry[kMaxPartitions];
|
||||
} PartitionMap;
|
||||
|
||||
|
||||
/*
|
||||
* Figure out if this is a FocusDrive partition.
|
||||
*
|
||||
* The "imageOrder" parameter has no use here, because (in the current
|
||||
* version) embedded parent volumes are implicitly ProDOS-ordered.
|
||||
*/
|
||||
/*static*/ DIError DiskFSFocusDrive::TestImage(DiskImg* pImg,
|
||||
DiskImg::SectorOrder imageOrder)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t blkBuf[kBlkSize];
|
||||
int partCount;
|
||||
|
||||
/*
|
||||
* See if block 0 is a FocusDrive partition map.
|
||||
*/
|
||||
dierr = pImg->ReadBlockSwapped(kPartMapBlock, blkBuf, imageOrder,
|
||||
DiskImg::kSectorOrderProDOS);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
if (memcmp(blkBuf, kSignature, kSignatureLen) != 0) {
|
||||
LOGI(" FocusDrive partition signature not found in first part block");
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
partCount = blkBuf[0x0f];
|
||||
if (partCount == 0 || partCount > kMaxPartitions) {
|
||||
LOGI(" FocusDrive partition count looks bad (%d)", partCount);
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
// success!
|
||||
LOGI(" Looks like FocusDrive with %d partitions", partCount);
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unpack a partition map block into a partition map data structure.
|
||||
*/
|
||||
/*static*/ void DiskFSFocusDrive::UnpackPartitionMap(const uint8_t* buf,
|
||||
const uint8_t* nameBuf, PartitionMap* pMap)
|
||||
{
|
||||
const uint8_t* ptr;
|
||||
const uint8_t* namePtr;
|
||||
int i;
|
||||
|
||||
memcpy(pMap->signature, &buf[0x00], kSignatureLen);
|
||||
pMap->unknown1 = buf[0x0e];
|
||||
pMap->partCount = buf[0x0f];
|
||||
memcpy(pMap->unknown2, &buf[0x10], 16);
|
||||
|
||||
ptr = &buf[0x20];
|
||||
namePtr = &nameBuf[kPartNameLen]; // not sure what first 32 bytes are
|
||||
for (i = 0; i < kMaxPartitions; i++) {
|
||||
pMap->entry[i].startBlock = GetLongLE(ptr);
|
||||
pMap->entry[i].blockCount = GetLongLE(ptr+4);
|
||||
pMap->entry[i].unknown1 = GetLongLE(ptr+8);
|
||||
pMap->entry[i].unknown2 = GetLongLE(ptr+12);
|
||||
|
||||
memcpy(pMap->entry[i].name, namePtr, kPartNameLen);
|
||||
pMap->entry[i].name[kPartNameLen] = '\0';
|
||||
|
||||
ptr += 0x10;
|
||||
namePtr += kPartNameLen;
|
||||
}
|
||||
|
||||
assert(ptr == buf + kBlkSize);
|
||||
}
|
||||
|
||||
/*
|
||||
* Debug: dump the contents of the partition map.
|
||||
*/
|
||||
/*static*/ void DiskFSFocusDrive::DumpPartitionMap(const PartitionMap* pMap)
|
||||
{
|
||||
int i;
|
||||
|
||||
LOGI(" FocusDrive partition map (%d partitions):", pMap->partCount);
|
||||
for (i = 0; i < pMap->partCount; i++) {
|
||||
LOGI(" %2d: %8d %8d '%s'", i, pMap->entry[i].startBlock,
|
||||
pMap->entry[i].blockCount, pMap->entry[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Open up a sub-volume.
|
||||
*/
|
||||
DIError DiskFSFocusDrive::OpenSubVolume(long startBlock, long numBlocks,
|
||||
const char* name)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
DiskFS* pNewFS = NULL;
|
||||
DiskImg* pNewImg = NULL;
|
||||
//bool tweaked = false;
|
||||
|
||||
LOGI("Adding %ld +%ld", startBlock, numBlocks);
|
||||
|
||||
if (startBlock > fpImg->GetNumBlocks()) {
|
||||
LOGI("FocusDrive start block out of range (%ld vs %ld)",
|
||||
startBlock, fpImg->GetNumBlocks());
|
||||
return kDIErrBadPartition;
|
||||
}
|
||||
if (startBlock + numBlocks > fpImg->GetNumBlocks()) {
|
||||
LOGI("FocusDrive partition too large (%ld vs %ld avail)",
|
||||
numBlocks, fpImg->GetNumBlocks() - startBlock);
|
||||
fpImg->AddNote(DiskImg::kNoteInfo,
|
||||
"Reduced partition from %ld blocks to %ld.\n",
|
||||
numBlocks, fpImg->GetNumBlocks() - startBlock);
|
||||
numBlocks = fpImg->GetNumBlocks() - startBlock;
|
||||
//tweaked = true;
|
||||
}
|
||||
|
||||
pNewImg = new DiskImg;
|
||||
if (pNewImg == NULL) {
|
||||
dierr = kDIErrMalloc;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
dierr = pNewImg->OpenImage(fpImg, startBlock, numBlocks);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" FocusDriveSub: OpenImage(%ld,%ld) failed (err=%d)",
|
||||
startBlock, numBlocks, dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
//LOGI(" +++ CFFASub: new image has ro=%d (parent=%d)",
|
||||
// pNewImg->GetReadOnly(), pImg->GetReadOnly());
|
||||
|
||||
/* figure out what the format is */
|
||||
dierr = pNewImg->AnalyzeImage();
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" FocusDriveSub: analysis failed (err=%d)", dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* we allow unrecognized partitions */
|
||||
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
|
||||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
|
||||
{
|
||||
LOGI(" FocusDriveSub (%ld,%ld): unable to identify filesystem",
|
||||
startBlock, numBlocks);
|
||||
DiskFSUnknown* pUnknownFS = new DiskFSUnknown;
|
||||
if (pUnknownFS == NULL) {
|
||||
dierr = kDIErrInternal;
|
||||
goto bail;
|
||||
}
|
||||
//pUnknownFS->SetVolumeInfo((const char*)pMap->pmParType);
|
||||
pNewFS = pUnknownFS;
|
||||
} else {
|
||||
/* open a DiskFS for the sub-image */
|
||||
LOGI(" FocusDriveSub (%ld,%ld) analyze succeeded!", startBlock, numBlocks);
|
||||
pNewFS = pNewImg->OpenAppropriateDiskFS(true);
|
||||
if (pNewFS == NULL) {
|
||||
LOGI(" FocusDriveSub: OpenAppropriateDiskFS failed");
|
||||
dierr = kDIErrUnsupportedFSFmt;
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
pNewImg->AddNote(DiskImg::kNoteInfo, "Partition name='%s'.", name);
|
||||
|
||||
/* we encapsulate arbitrary stuff, so encourage child to scan */
|
||||
pNewFS->SetScanForSubVolumes(kScanSubEnabled);
|
||||
|
||||
/*
|
||||
* Load the files from the sub-image. When doing our initial tests,
|
||||
* or when loading data for the volume copier, we don't want to dig
|
||||
* into our sub-volumes, just figure out what they are and where.
|
||||
*
|
||||
* If "initialize" fails, the sub-volume won't get added to the list.
|
||||
* It's important that a failure at this stage doesn't cause the whole
|
||||
* thing to fall over.
|
||||
*/
|
||||
InitMode initMode;
|
||||
if (GetScanForSubVolumes() == kScanSubContainerOnly)
|
||||
initMode = kInitHeaderOnly;
|
||||
else
|
||||
initMode = kInitFull;
|
||||
dierr = pNewFS->Initialize(pNewImg, initMode);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGE(" FocusDriveSub: error %d reading list of files from disk", dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* add it to the list */
|
||||
AddSubVolumeToList(pNewImg, pNewFS);
|
||||
pNewImg = NULL;
|
||||
pNewFS = NULL;
|
||||
|
||||
bail:
|
||||
delete pNewFS;
|
||||
delete pNewImg;
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check to see if this is a FocusDrive volume.
|
||||
*/
|
||||
/*static*/ DIError DiskFSFocusDrive::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
||||
DiskImg::FSFormat* pFormat, FSLeniency leniency)
|
||||
{
|
||||
if (pImg->GetNumBlocks() < kMinInterestingBlocks)
|
||||
return kDIErrFilesystemNotFound;
|
||||
if (pImg->GetIsEmbedded()) // don't look for partitions inside
|
||||
return kDIErrFilesystemNotFound;
|
||||
|
||||
/* assume ProDOS -- shouldn't matter, since it's embedded */
|
||||
if (TestImage(pImg, DiskImg::kSectorOrderProDOS) == kDIErrNone) {
|
||||
*pFormat = DiskImg::kFormatFocusDrive;
|
||||
*pOrder = DiskImg::kSectorOrderProDOS;
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
LOGI(" FS didn't find valid FocusDrive");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Prep the FocusDrive "container" for use.
|
||||
*/
|
||||
DIError DiskFSFocusDrive::Initialize(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
|
||||
LOGI("FocusDrive initializing (scanForSub=%d)", fScanForSubVolumes);
|
||||
|
||||
/* seems pointless *not* to, but we just do what we're told */
|
||||
if (fScanForSubVolumes != kScanSubDisabled) {
|
||||
dierr = FindSubVolumes();
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/* blank out the volume usage map */
|
||||
SetVolumeUsageMap();
|
||||
|
||||
return dierr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Find the various sub-volumes and open them.
|
||||
*/
|
||||
DIError DiskFSFocusDrive::FindSubVolumes(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t buf[kBlkSize];
|
||||
uint8_t nameBuf[kBlkSize*2];
|
||||
PartitionMap map;
|
||||
int i;
|
||||
|
||||
dierr = fpImg->ReadBlock(kPartMapBlock, buf);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
dierr = fpImg->ReadBlock(kPartNameStart, nameBuf);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
dierr = fpImg->ReadBlock(kPartNameStart+1, nameBuf+kBlkSize);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
UnpackPartitionMap(buf, nameBuf, &map);
|
||||
DumpPartitionMap(&map);
|
||||
|
||||
for (i = 0; i < map.partCount; i++) {
|
||||
dierr = OpenVol(i, map.entry[i].startBlock, map.entry[i].blockCount,
|
||||
(const char*)map.entry[i].name);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
}
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Open the volume. If it fails, open a placeholder instead. (If *that*
|
||||
* fails, return with an error.)
|
||||
*/
|
||||
DIError DiskFSFocusDrive::OpenVol(int idx, long startBlock, long numBlocks,
|
||||
const char* name)
|
||||
{
|
||||
DIError dierr;
|
||||
|
||||
dierr = OpenSubVolume(startBlock, numBlocks, name);
|
||||
if (dierr != kDIErrNone) {
|
||||
if (dierr == kDIErrCancelled)
|
||||
goto bail;
|
||||
DiskFS* pNewFS = NULL;
|
||||
DiskImg* pNewImg = NULL;
|
||||
|
||||
LOGI(" FocusDrive failed opening sub-volume %d", idx);
|
||||
dierr = CreatePlaceholder(startBlock, numBlocks, name, NULL,
|
||||
&pNewImg, &pNewFS);
|
||||
if (dierr == kDIErrNone) {
|
||||
AddSubVolumeToList(pNewImg, pNewFS);
|
||||
} else {
|
||||
LOGI(" FocusDrive unable to create placeholder (err=%d)",
|
||||
dierr);
|
||||
// fall out with error
|
||||
}
|
||||
}
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
866
diskimg/GenericFD.cpp
Normal file
866
diskimg/GenericFD.cpp
Normal file
@ -0,0 +1,866 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Generic file descriptor class.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* GenericFD utility functions
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copy "length" bytes from "pSrc" to "pDst". Both GenericFDs should be
|
||||
* seeked to their initial positions.
|
||||
*
|
||||
* If "pCRC" is non-NULL, this computes a CRC32 as it goes, using the zlib
|
||||
* library function.
|
||||
*/
|
||||
/*static*/ DIError GenericFD::CopyFile(GenericFD* pDst, GenericFD* pSrc,
|
||||
di_off_t length, uint32_t* pCRC)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
const int kCopyBufSize = 32768;
|
||||
uint8_t* copyBuf = NULL;
|
||||
int copySize;
|
||||
|
||||
LOGD("+++ CopyFile: %ld bytes", (long) length);
|
||||
|
||||
if (pDst == NULL || pSrc == NULL || length < 0)
|
||||
return kDIErrInvalidArg;
|
||||
if (length == 0)
|
||||
return kDIErrNone;
|
||||
|
||||
copyBuf = new uint8_t[kCopyBufSize];
|
||||
if (copyBuf == NULL)
|
||||
return kDIErrMalloc;
|
||||
|
||||
if (pCRC != NULL)
|
||||
*pCRC = crc32(0L, Z_NULL, 0);
|
||||
|
||||
while (length != 0) {
|
||||
copySize = kCopyBufSize;
|
||||
if (copySize > length)
|
||||
copySize = (int) length;
|
||||
|
||||
dierr = pSrc->Read(copyBuf, copySize);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
if (pCRC != NULL)
|
||||
*pCRC = crc32(*pCRC, copyBuf, copySize);
|
||||
|
||||
dierr = pDst->Write(copyBuf, copySize);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
length -= copySize;
|
||||
}
|
||||
|
||||
bail:
|
||||
delete[] copyBuf;
|
||||
return dierr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* GFDFile
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* The stdio functions (fopen/fread/fwrite/fseek/ftell) are buffered and,
|
||||
* therefore, faster for small operations. Unfortunately we need 64-bit
|
||||
* file offsets, and it doesn't look like the Windows stdio stuff will
|
||||
* support it cleanly (e.g. even the _FPOSOFF macro returns a "long").
|
||||
*
|
||||
* Recent versions of Linux have "fseeko", which is like fseek but takes
|
||||
* an off_t, so we can continue to use the FILE* functions there. Under
|
||||
* Windows "lseek" takes a long, so we have to use their specific 64-bit
|
||||
* variant.
|
||||
*
|
||||
* TODO: Visual Studio 2005 added _fseeki64. We should be able to merge
|
||||
* the bulk of the implementation now.
|
||||
*/
|
||||
#ifdef HAVE_FSEEKO
|
||||
|
||||
DIError GFDFile::Open(const char* filename, bool readOnly)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
|
||||
if (fFp != NULL)
|
||||
return kDIErrAlreadyOpen;
|
||||
if (filename == NULL)
|
||||
return kDIErrInvalidArg;
|
||||
if (filename[0] == '\0')
|
||||
return kDIErrInvalidArg;
|
||||
|
||||
delete[] fPathName;
|
||||
fPathName = new char[strlen(filename) +1];
|
||||
strcpy(fPathName, filename);
|
||||
|
||||
fFp = fopen(filename, readOnly ? "rb" : "r+b");
|
||||
if (fFp == NULL) {
|
||||
if (errno == EACCES)
|
||||
dierr = kDIErrAccessDenied;
|
||||
else
|
||||
dierr = ErrnoOrGeneric();
|
||||
LOGI(" GDFile Open failed opening '%s', ro=%d (err=%d)",
|
||||
filename, readOnly, dierr);
|
||||
return dierr;
|
||||
}
|
||||
fReadOnly = readOnly;
|
||||
return dierr;
|
||||
}
|
||||
|
||||
DIError GFDFile::Read(void* buf, size_t length, size_t* pActual)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
size_t actual;
|
||||
|
||||
if (fFp == NULL)
|
||||
return kDIErrNotReady;
|
||||
actual = ::fread(buf, 1, length, fFp);
|
||||
if (actual == 0) {
|
||||
if (feof(fFp))
|
||||
return kDIErrEOF;
|
||||
if (ferror(fFp)) {
|
||||
dierr = ErrnoOrGeneric();
|
||||
return dierr;
|
||||
}
|
||||
LOGI("MYSTERY FREAD RESULT");
|
||||
return kDIErrInternal;
|
||||
}
|
||||
|
||||
if (pActual == NULL) {
|
||||
if (actual != length) {
|
||||
dierr = ErrnoOrGeneric();
|
||||
LOGW(" GDFile Read failed on %lu bytes (actual=%lu, err=%d)",
|
||||
(unsigned long) length, (unsigned long) actual, dierr);
|
||||
return dierr;
|
||||
}
|
||||
} else {
|
||||
*pActual = actual;
|
||||
}
|
||||
return dierr;
|
||||
}
|
||||
|
||||
DIError GFDFile::Write(const void* buf, size_t length, size_t* pActual)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
|
||||
if (fFp == NULL)
|
||||
return kDIErrNotReady;
|
||||
if (fReadOnly)
|
||||
return kDIErrAccessDenied;
|
||||
assert(pActual == NULL); // not handling this yet
|
||||
if (::fwrite(buf, length, 1, fFp) != 1) {
|
||||
dierr = ErrnoOrGeneric();
|
||||
LOGW(" GDFile Write failed on %lu bytes (err=%d)",
|
||||
(unsigned long) length, dierr);
|
||||
return dierr;
|
||||
}
|
||||
return dierr;
|
||||
}
|
||||
|
||||
DIError GFDFile::Seek(di_off_t offset, DIWhence whence)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
//static const long kOneGB = 1024*1024*1024;
|
||||
//static const long kAlmostTwoGB = kOneGB + (kOneGB -1);
|
||||
|
||||
if (fFp == NULL)
|
||||
return kDIErrNotReady;
|
||||
//assert(offset <= kAlmostTwoGB);
|
||||
//if (::fseek(fFp, (long) offset, whence) != 0) {
|
||||
if (::fseeko(fFp, offset, whence) != 0) {
|
||||
dierr = ErrnoOrGeneric();
|
||||
LOGI(" GDFile Seek failed (err=%d)", dierr);
|
||||
return dierr;
|
||||
}
|
||||
return dierr;
|
||||
}
|
||||
|
||||
di_off_t GFDFile::Tell(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
di_off_t result;
|
||||
|
||||
if (fFp == NULL)
|
||||
return kDIErrNotReady;
|
||||
//result = ::ftell(fFp);
|
||||
result = ::ftello(fFp);
|
||||
if (result == -1) {
|
||||
dierr = ErrnoOrGeneric();
|
||||
LOGI(" GDFile Tell failed (err=%d)", dierr);
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
DIError GFDFile::Truncate(void)
|
||||
{
|
||||
#if defined(HAVE_FTRUNCATE)
|
||||
int cc;
|
||||
cc = ::ftruncate(fileno(fFp), (long) Tell());
|
||||
if (cc != 0)
|
||||
return kDIErrWriteFailed;
|
||||
#elif defined(HAVE_CHSIZE)
|
||||
assert(false); // not tested
|
||||
int cc;
|
||||
cc = ::chsize(fFd, (long) Tell());
|
||||
if (cc != 0)
|
||||
return kDIErrWriteFailed;
|
||||
#else
|
||||
# error "missing truncate"
|
||||
#endif
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
DIError GFDFile::Close(void)
|
||||
{
|
||||
if (fFp == NULL)
|
||||
return kDIErrNotReady;
|
||||
|
||||
LOGI(" GFDFile closing '%s'", fPathName);
|
||||
fclose(fFp);
|
||||
fFp = NULL;
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
#else /*HAVE_FSEEKO*/
|
||||
|
||||
DIError GFDFile::Open(const char* filename, bool readOnly)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
|
||||
if (fFd >= 0)
|
||||
return kDIErrAlreadyOpen;
|
||||
if (filename == NULL)
|
||||
return kDIErrInvalidArg;
|
||||
if (filename[0] == '\0')
|
||||
return kDIErrInvalidArg;
|
||||
|
||||
delete[] fPathName;
|
||||
fPathName = new char[strlen(filename) +1];
|
||||
strcpy(fPathName, filename);
|
||||
|
||||
fFd = open(filename, readOnly ? O_RDONLY|O_BINARY : O_RDWR|O_BINARY, 0);
|
||||
if (fFd < 0) {
|
||||
if (errno == EACCES) {
|
||||
dierr = kDIErrAccessDenied;
|
||||
} else if (errno == EINVAL) {
|
||||
// Happens on Win32 if a Unicode filename conversion failed,
|
||||
// because non-converting chars turn into '?', which is illegal.
|
||||
dierr = kDIErrInvalidArg;
|
||||
} else {
|
||||
dierr = ErrnoOrGeneric();
|
||||
}
|
||||
LOGW(" GDFile Open failed opening '%s', ro=%d (err=%d)",
|
||||
filename, readOnly, dierr);
|
||||
return dierr;
|
||||
}
|
||||
fReadOnly = readOnly;
|
||||
return dierr;
|
||||
}
|
||||
|
||||
DIError GFDFile::Read(void* buf, size_t length, size_t* pActual)
|
||||
{
|
||||
DIError dierr;
|
||||
ssize_t actual;
|
||||
|
||||
if (fFd < 0)
|
||||
return kDIErrNotReady;
|
||||
actual = ::read(fFd, buf, length);
|
||||
if (actual == 0)
|
||||
return kDIErrEOF;
|
||||
if (actual < 0) {
|
||||
dierr = ErrnoOrGeneric();
|
||||
LOGW(" GDFile Read failed on %d bytes (actual=%d, err=%d)",
|
||||
length, actual, dierr);
|
||||
return dierr;
|
||||
}
|
||||
|
||||
if (pActual == NULL) {
|
||||
if (actual != (ssize_t) length) {
|
||||
LOGI(" GDFile Read partial (wanted=%d actual=%d)",
|
||||
length, actual);
|
||||
return kDIErrReadFailed;
|
||||
}
|
||||
} else {
|
||||
*pActual = actual;
|
||||
}
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
DIError GFDFile::Write(const void* buf, size_t length, size_t* pActual)
|
||||
{
|
||||
DIError dierr;
|
||||
ssize_t actual;
|
||||
|
||||
if (fFd < 0)
|
||||
return kDIErrNotReady;
|
||||
if (fReadOnly)
|
||||
return kDIErrAccessDenied;
|
||||
assert(pActual == NULL); // not handling partial writes yet
|
||||
actual = ::write(fFd, buf, length);
|
||||
if (actual != (ssize_t) length) {
|
||||
dierr = ErrnoOrGeneric();
|
||||
LOGI(" GDFile Write failed on %d bytes (actual=%d err=%d)",
|
||||
length, actual, dierr);
|
||||
return dierr;
|
||||
}
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
DIError GFDFile::Seek(di_off_t offset, DIWhence whence)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
if (fFd < 0)
|
||||
return kDIErrNotReady;
|
||||
|
||||
#ifdef WIN32
|
||||
__int64 newPosn;
|
||||
const __int64 kFailure = (__int64) -1;
|
||||
newPosn = ::_lseeki64(fFd, (__int64) offset, whence);
|
||||
#else
|
||||
di_off_t newPosn;
|
||||
const di_off_t kFailure = (di_off_t) -1;
|
||||
newPosn = lseek(fFd, offset, whence);
|
||||
#endif
|
||||
|
||||
if (newPosn == kFailure) {
|
||||
assert((uint32_t) offset != 0xccccccccUL); // uninitialized data!
|
||||
dierr = ErrnoOrGeneric();
|
||||
LOGI(" GDFile Seek %ld-%lu failed (err=%d)",
|
||||
(long) (offset >> 32), (uint32_t) offset, dierr);
|
||||
}
|
||||
return dierr;
|
||||
}
|
||||
|
||||
di_off_t GFDFile::Tell(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
di_off_t result;
|
||||
|
||||
if (fFd < 0)
|
||||
return kDIErrNotReady;
|
||||
|
||||
#ifdef WIN32
|
||||
result = ::_lseeki64(fFd, 0, SEEK_CUR);
|
||||
#else
|
||||
result = lseek(fFd, 0, SEEK_CUR);
|
||||
#endif
|
||||
|
||||
if (result == -1) {
|
||||
dierr = ErrnoOrGeneric();
|
||||
LOGI(" GDFile Tell failed (err=%d)", dierr);
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
DIError GFDFile::Truncate(void)
|
||||
{
|
||||
#if defined(HAVE_FTRUNCATE)
|
||||
int cc;
|
||||
cc = ::ftruncate(fFd, (long) Tell());
|
||||
if (cc != 0)
|
||||
return kDIErrWriteFailed;
|
||||
#elif defined(HAVE_CHSIZE)
|
||||
int cc;
|
||||
cc = ::chsize(fFd, (long) Tell());
|
||||
if (cc != 0)
|
||||
return kDIErrWriteFailed;
|
||||
#else
|
||||
# error "missing truncate"
|
||||
#endif
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
DIError GFDFile::Close(void)
|
||||
{
|
||||
if (fFd < 0)
|
||||
return kDIErrNotReady;
|
||||
|
||||
LOGI(" GFDFile closing '%s'", fPathName);
|
||||
::close(fFd);
|
||||
fFd = -1;
|
||||
return kDIErrNone;
|
||||
}
|
||||
#endif /*HAVE_FSEEKO else*/
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* GFDBuffer
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
DIError GFDBuffer::Open(void* buffer, di_off_t length, bool doDelete,
|
||||
bool doExpand, bool readOnly)
|
||||
{
|
||||
if (fBuffer != NULL)
|
||||
return kDIErrAlreadyOpen;
|
||||
if (length <= 0)
|
||||
return kDIErrInvalidArg;
|
||||
if (length > kMaxReasonableSize) {
|
||||
// be reasonable
|
||||
LOGI(" GFDBuffer refusing to allocate buffer size(long)=%ld bytes",
|
||||
(long) length);
|
||||
return kDIErrInvalidArg;
|
||||
}
|
||||
|
||||
/* if buffer is NULL, allocate it ourselves */
|
||||
if (buffer == NULL) {
|
||||
fBuffer = (void*) new uint8_t[(int) length];
|
||||
if (fBuffer == NULL)
|
||||
return kDIErrMalloc;
|
||||
} else
|
||||
fBuffer = buffer;
|
||||
|
||||
fLength = (long) length;
|
||||
fAllocLength = (long) length;
|
||||
fDoDelete = doDelete;
|
||||
fDoExpand = doExpand;
|
||||
fReadOnly = readOnly;
|
||||
|
||||
fCurrentOffset = 0;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
DIError GFDBuffer::Read(void* buf, size_t length, size_t* pActual)
|
||||
{
|
||||
if (fBuffer == NULL)
|
||||
return kDIErrNotReady;
|
||||
if (length == 0)
|
||||
return kDIErrInvalidArg;
|
||||
|
||||
if (fCurrentOffset + (long)length > fLength) {
|
||||
if (pActual == NULL) {
|
||||
LOGW(" GFDBuffer underrrun off=%ld len=%lu flen=%ld",
|
||||
(long) fCurrentOffset, (unsigned long) length, (long) fLength);
|
||||
return kDIErrDataUnderrun;
|
||||
} else {
|
||||
/* set *pActual and adjust "length" */
|
||||
assert(fLength >= fCurrentOffset);
|
||||
length = (size_t) (fLength - fCurrentOffset);
|
||||
*pActual = length;
|
||||
|
||||
if (length == 0)
|
||||
return kDIErrEOF;
|
||||
}
|
||||
}
|
||||
if (pActual != NULL)
|
||||
*pActual = length;
|
||||
|
||||
memcpy(buf, (const char*)fBuffer + fCurrentOffset, length);
|
||||
fCurrentOffset += length;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
DIError GFDBuffer::Write(const void* buf, size_t length, size_t* pActual)
|
||||
{
|
||||
if (fBuffer == NULL)
|
||||
return kDIErrNotReady;
|
||||
assert(pActual == NULL); // not handling this yet
|
||||
if (fCurrentOffset + (long)length > fLength) {
|
||||
if (!fDoExpand) {
|
||||
LOGI(" GFDBuffer overrun off=%ld len=%lu flen=%ld",
|
||||
(long) fCurrentOffset, (unsigned long) length, (long) fLength);
|
||||
return kDIErrDataOverrun;
|
||||
}
|
||||
|
||||
/*
|
||||
* Expand the buffer as needed.
|
||||
*
|
||||
* We delete the old buffer unless "doDelete" is not set, in
|
||||
* which case we just drop the pointer. Anything we allocate
|
||||
* here can and will be deleted; "doDelete" only applies to the
|
||||
* pointer initially passed in.
|
||||
*/
|
||||
if (fCurrentOffset + (long)length <= fAllocLength) {
|
||||
/* fits inside allocated space, so just extend length */
|
||||
fLength = (long) fCurrentOffset + (long)length;
|
||||
} else {
|
||||
/* does not fit, realloc buffer */
|
||||
fAllocLength = (long) fCurrentOffset + (long)length + 8*1024;
|
||||
LOGI("Reallocating buffer (new size = %ld)", fAllocLength);
|
||||
assert(fAllocLength < kMaxReasonableSize);
|
||||
char* newBuf = new char[(int) fAllocLength];
|
||||
if (newBuf == NULL)
|
||||
return kDIErrMalloc;
|
||||
|
||||
memcpy(newBuf, fBuffer, fLength);
|
||||
|
||||
if (fDoDelete)
|
||||
delete[] (char*)fBuffer;
|
||||
else
|
||||
fDoDelete = true; // future deletions are okay
|
||||
|
||||
fBuffer = newBuf;
|
||||
fLength = (long) fCurrentOffset + (long)length;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy((char*)fBuffer + fCurrentOffset, buf, length);
|
||||
fCurrentOffset += length;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
DIError GFDBuffer::Seek(di_off_t offset, DIWhence whence)
|
||||
{
|
||||
if (fBuffer == NULL)
|
||||
return kDIErrNotReady;
|
||||
|
||||
switch (whence) {
|
||||
case kSeekSet:
|
||||
if (offset < 0 || offset >= fLength)
|
||||
return kDIErrInvalidArg;
|
||||
fCurrentOffset = offset;
|
||||
break;
|
||||
case kSeekEnd:
|
||||
if (offset > 0 || offset < -fLength)
|
||||
return kDIErrInvalidArg;
|
||||
fCurrentOffset = fLength + offset;
|
||||
break;
|
||||
case kSeekCur:
|
||||
if (offset < -fCurrentOffset ||
|
||||
offset >= (fLength - fCurrentOffset))
|
||||
{
|
||||
return kDIErrInvalidArg;
|
||||
}
|
||||
fCurrentOffset += offset;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
return kDIErrInvalidArg;
|
||||
}
|
||||
|
||||
assert(fCurrentOffset >= 0 && fCurrentOffset <= fLength);
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
di_off_t GFDBuffer::Tell(void)
|
||||
{
|
||||
if (fBuffer == NULL)
|
||||
return (di_off_t) -1;
|
||||
return fCurrentOffset;
|
||||
}
|
||||
|
||||
DIError GFDBuffer::Close(void)
|
||||
{
|
||||
if (fBuffer == NULL)
|
||||
return kDIErrNone;
|
||||
|
||||
if (fDoDelete) {
|
||||
LOGI(" GFDBuffer closing and deleting");
|
||||
delete[] (char*) fBuffer;
|
||||
} else {
|
||||
LOGI(" GFDBuffer closing");
|
||||
}
|
||||
fBuffer = NULL;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
/*
|
||||
* ===========================================================================
|
||||
* GFDWinVolume
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* This class is intended for use with logical volumes under Win32. Such
|
||||
* devices must be accessed on 512-byte boundaries, which means no arbitrary
|
||||
* seeks or reads. The device driver doesn't seem too adept at figuring
|
||||
* out how large the device is, either, so we need to work that out for
|
||||
* ourselves. (The standard approach appears to involve examining the
|
||||
* partition map for the logical or physical volume, but we don't have a
|
||||
* partition map to look at.)
|
||||
*/
|
||||
|
||||
/*
|
||||
* Prepare a logical volume device for reading or writing. "deviceName"
|
||||
* must have the form "N:\" for a logical volume or "80:\" for a physical
|
||||
* volume.
|
||||
*/
|
||||
DIError GFDWinVolume::Open(const char* deviceName, bool readOnly)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
HANDLE handle = NULL;
|
||||
//uint32_t kTwoGBBlocks;
|
||||
|
||||
if (fVolAccess.Ready())
|
||||
return kDIErrAlreadyOpen;
|
||||
if (deviceName == NULL)
|
||||
return kDIErrInvalidArg;
|
||||
if (deviceName[0] == '\0')
|
||||
return kDIErrInvalidArg;
|
||||
|
||||
delete[] fPathName;
|
||||
fPathName = new char[strlen(deviceName) +1];
|
||||
strcpy(fPathName, deviceName);
|
||||
|
||||
// Create a UNICODE representation of the device name. We may want
|
||||
// to make the argument UNICODE instead, but most of diskimg is 8-bit
|
||||
// character oriented.
|
||||
size_t srcLen = strlen(deviceName) + 1;
|
||||
WCHAR* wdeviceName = new WCHAR[srcLen];
|
||||
size_t convertedChars;
|
||||
mbstowcs_s(&convertedChars, wdeviceName, srcLen, deviceName, _TRUNCATE);
|
||||
dierr = fVolAccess.Open(wdeviceName, readOnly);
|
||||
delete[] wdeviceName;
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
fBlockSize = fVolAccess.GetBlockSize(); // must be power of 2
|
||||
assert(fBlockSize > 0);
|
||||
//kTwoGBBlocks = kTwoGB / fBlockSize;
|
||||
|
||||
long totalBlocks;
|
||||
totalBlocks = fVolAccess.GetTotalBlocks();
|
||||
fVolumeEOF = (di_off_t)totalBlocks * fBlockSize;
|
||||
|
||||
assert(fVolumeEOF > 0);
|
||||
|
||||
fReadOnly = readOnly;
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
DIError GFDWinVolume::Read(void* buf, size_t length, size_t* pActual)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t* blkBuf = NULL;
|
||||
|
||||
//LOGI(" GFDWinVolume: reading %ld bytes from offset %ld", length,
|
||||
// fCurrentOffset);
|
||||
|
||||
if (!fVolAccess.Ready())
|
||||
return kDIErrNotReady;
|
||||
|
||||
// don't allow reading past the end of file
|
||||
if (fCurrentOffset + (long) length > fVolumeEOF) {
|
||||
if (pActual == NULL)
|
||||
return kDIErrDataUnderrun;
|
||||
length = (size_t) (fVolumeEOF - fCurrentOffset);
|
||||
}
|
||||
if (pActual != NULL)
|
||||
*pActual = length;
|
||||
if (length == 0)
|
||||
return kDIErrNone;
|
||||
|
||||
long advanceLen = length;
|
||||
|
||||
blkBuf = new uint8_t[fBlockSize]; // get this off the heap??
|
||||
long blockIndex = (long) (fCurrentOffset / fBlockSize);
|
||||
int bufOffset = (int) (fCurrentOffset % fBlockSize); // req power of 2
|
||||
assert(blockIndex >= 0);
|
||||
|
||||
/*
|
||||
* When possible, do multi-block reads directly into "buf". The first
|
||||
* and last block may require special handling.
|
||||
*/
|
||||
while (length) {
|
||||
assert(length > 0);
|
||||
|
||||
if (bufOffset != 0 || length < (size_t) fBlockSize) {
|
||||
assert(bufOffset >= 0 && bufOffset < fBlockSize);
|
||||
|
||||
size_t thisCount;
|
||||
|
||||
dierr = fVolAccess.ReadBlocks(blockIndex, 1, blkBuf);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
thisCount = fBlockSize - bufOffset;
|
||||
if (thisCount > length)
|
||||
thisCount = length;
|
||||
|
||||
//LOGI(" Copying %d bytes from block %d",
|
||||
// thisCount, blockIndex);
|
||||
|
||||
memcpy(buf, blkBuf + bufOffset, thisCount);
|
||||
length -= thisCount;
|
||||
buf = (char*) buf + thisCount;
|
||||
|
||||
bufOffset = 0;
|
||||
blockIndex++;
|
||||
} else {
|
||||
assert(bufOffset == 0);
|
||||
|
||||
long blockCount = length / fBlockSize;
|
||||
assert(blockCount < 32768);
|
||||
|
||||
dierr = fVolAccess.ReadBlocks(blockIndex, (short) blockCount, buf);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
length -= blockCount * fBlockSize;
|
||||
buf = (char*) buf + blockCount * fBlockSize;
|
||||
|
||||
blockIndex += blockCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fCurrentOffset += advanceLen;
|
||||
|
||||
bail:
|
||||
delete[] blkBuf;
|
||||
return dierr;
|
||||
}
|
||||
|
||||
DIError GFDWinVolume::Write(const void* buf, size_t length, size_t* pActual)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t* blkBuf = NULL;
|
||||
|
||||
//LOGI(" GFDWinVolume: writing %ld bytes at offset %ld", length,
|
||||
// fCurrentOffset);
|
||||
|
||||
if (!fVolAccess.Ready())
|
||||
return kDIErrNotReady;
|
||||
if (fReadOnly)
|
||||
return kDIErrAccessDenied;
|
||||
|
||||
// don't allow writing past the end of the volume
|
||||
if (fCurrentOffset + (long) length > fVolumeEOF) {
|
||||
if (pActual == NULL)
|
||||
return kDIErrDataOverrun;
|
||||
length = (size_t) (fVolumeEOF - fCurrentOffset);
|
||||
}
|
||||
if (pActual != NULL)
|
||||
*pActual = length;
|
||||
if (length == 0)
|
||||
return kDIErrNone;
|
||||
|
||||
long advanceLen = length;
|
||||
|
||||
blkBuf = new uint8_t[fBlockSize]; // get this out of the heap??
|
||||
long blockIndex = (long) (fCurrentOffset / fBlockSize);
|
||||
int bufOffset = (int) (fCurrentOffset % fBlockSize); // req power of 2
|
||||
assert(blockIndex >= 0);
|
||||
|
||||
/*
|
||||
* When possible, do multi-block writes directly from "buf". The first
|
||||
* and last block may require special handling.
|
||||
*/
|
||||
while (length) {
|
||||
assert(length > 0);
|
||||
|
||||
if (bufOffset != 0 || length < (size_t) fBlockSize) {
|
||||
assert(bufOffset >= 0 && bufOffset < fBlockSize);
|
||||
|
||||
size_t thisCount;
|
||||
|
||||
dierr = fVolAccess.ReadBlocks(blockIndex, 1, blkBuf);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
thisCount = fBlockSize - bufOffset;
|
||||
if (thisCount > length)
|
||||
thisCount = length;
|
||||
|
||||
//LOGI(" Copying %d bytes into block %d (off=%d)",
|
||||
// thisCount, blockIndex, bufOffset);
|
||||
|
||||
memcpy(blkBuf + bufOffset, buf, thisCount);
|
||||
length -= thisCount;
|
||||
buf = (char*) buf + thisCount;
|
||||
|
||||
dierr = fVolAccess.WriteBlocks(blockIndex, 1, blkBuf);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
bufOffset = 0;
|
||||
blockIndex++;
|
||||
} else {
|
||||
assert(bufOffset == 0);
|
||||
|
||||
long blockCount = length / fBlockSize;
|
||||
assert(blockCount < 32768);
|
||||
|
||||
dierr = fVolAccess.WriteBlocks(blockIndex, (short) blockCount, buf);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
length -= blockCount * fBlockSize;
|
||||
buf = (char*) buf + blockCount * fBlockSize;
|
||||
|
||||
blockIndex += blockCount;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fCurrentOffset += advanceLen;
|
||||
|
||||
bail:
|
||||
delete[] blkBuf;
|
||||
return dierr;
|
||||
}
|
||||
|
||||
DIError GFDWinVolume::Seek(di_off_t offset, DIWhence whence)
|
||||
{
|
||||
if (!fVolAccess.Ready())
|
||||
return kDIErrNotReady;
|
||||
|
||||
switch (whence) {
|
||||
case kSeekSet:
|
||||
if (offset < 0 || offset >= fVolumeEOF)
|
||||
return kDIErrInvalidArg;
|
||||
fCurrentOffset = offset;
|
||||
break;
|
||||
case kSeekEnd:
|
||||
if (offset > 0 || offset < -fVolumeEOF)
|
||||
return kDIErrInvalidArg;
|
||||
fCurrentOffset = fVolumeEOF + offset;
|
||||
break;
|
||||
case kSeekCur:
|
||||
if (offset < -fCurrentOffset ||
|
||||
offset >= (fVolumeEOF - fCurrentOffset))
|
||||
{
|
||||
return kDIErrInvalidArg;
|
||||
}
|
||||
fCurrentOffset += offset;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
return kDIErrInvalidArg;
|
||||
}
|
||||
|
||||
assert(fCurrentOffset >= 0 && fCurrentOffset <= fVolumeEOF);
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
di_off_t GFDWinVolume::Tell(void)
|
||||
{
|
||||
if (!fVolAccess.Ready())
|
||||
return (di_off_t) -1;
|
||||
return fCurrentOffset;
|
||||
}
|
||||
|
||||
DIError GFDWinVolume::Close(void)
|
||||
{
|
||||
if (!fVolAccess.Ready())
|
||||
return kDIErrNotReady;
|
||||
|
||||
LOGI(" GFDWinVolume closing");
|
||||
fVolAccess.Close();
|
||||
return kDIErrNone;
|
||||
}
|
||||
#endif /*_WIN32*/
|
328
diskimg/GenericFD.h
Normal file
328
diskimg/GenericFD.h
Normal file
@ -0,0 +1,328 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Declarations for GenericFD class and sub-classes.
|
||||
*/
|
||||
#ifndef DISKIMG_GENERICFD_H
|
||||
#define DISKIMG_GENERICFD_H
|
||||
|
||||
#include "Win32BlockIO.h"
|
||||
|
||||
namespace DiskImgLib {
|
||||
|
||||
#if 0
|
||||
/*
|
||||
* Embedded file descriptor class, representing an open file on a disk image.
|
||||
*
|
||||
* Useful for opening disk images that are stored as files inside of other
|
||||
* disk images. For stuff like UNIDOS images, which don't have a file
|
||||
* associated with them, we can either open them as raw blocks, or create
|
||||
* a "fake" file to access them. The latter is more general, and will work
|
||||
* for sub-volumes of sub-volumes.
|
||||
*/
|
||||
class DISKIMG_API EmbeddedFD {
|
||||
public:
|
||||
EmbeddedFD(void) {
|
||||
fpDiskFS = NULL;
|
||||
fpA2File = NULL;
|
||||
}
|
||||
virtual ~EmbeddedFD(void) {}
|
||||
|
||||
typedef enum Fork { kForkData = 0, kForkRsrc = 1 } Fork;
|
||||
// bit-flag values for Open call's "access" parameter
|
||||
enum {
|
||||
kAccessNone = 0, // somewhat useless
|
||||
kAccessRead = 0x01, // O_RDONLY
|
||||
kAccessWrite = 0x02, // O_WRONLY
|
||||
kAccessCreate = 0x04, // O_CREAT
|
||||
kAccessMustNotExist = 0x08, // O_EXCL, pointless w/o O_CREAT
|
||||
|
||||
kAccessReadWrite = (kAccessRead | kAccessWrite),
|
||||
};
|
||||
|
||||
/*
|
||||
* Standard set of calls.
|
||||
*/
|
||||
DIError Open(DiskFS* pDiskFS, const char* filename, Fork fork = kForkData,
|
||||
int access = kAccessRead, int fileCreatePerms = 0);
|
||||
DIError OpenBlocks(DiskFS* pDiskFS, long blockStart, long blockCount,
|
||||
int access = kAccessRead);
|
||||
DIError Read(void* buf, size_t length);
|
||||
DIError Write(const void* buf, size_t length);
|
||||
DIError Seek(di_off_t offset, DIWhence whence);
|
||||
DIError Close(void);
|
||||
|
||||
private:
|
||||
// prevent bitwise copying behavior
|
||||
EmbeddedFD& operator=(const EmbeddedFD&);
|
||||
EmbeddedFD(const EmbeddedFD&);
|
||||
|
||||
DiskFS* fpDiskFS;
|
||||
A2File* fpA2File;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Generic file source base class. Allows us to treat files on disk, memory
|
||||
* buffers, and files embedded inside disk images equally.
|
||||
*
|
||||
* The file represented by the class is available in its entirety; skipping
|
||||
* past "wrapper headers" is expected to be done by the caller.
|
||||
*
|
||||
* The Read and Write calls take an optional parameter that allows the caller
|
||||
* to see how much data was actually read or written. If the parameter is
|
||||
* not specified (or specified as NULL), then failure to return the exact
|
||||
* amount of data requested results an error.
|
||||
*
|
||||
* This is not meant to be the end-all of file wrapper classes; in
|
||||
* particular, it does not support file creation.
|
||||
*
|
||||
* Some libraries, such as NufxLib, require an actual filename to operate
|
||||
* (bad architecture?). The GetPathName call will return the original
|
||||
* filename if one exists, or NULL if there isn't one. (At which point the
|
||||
* caller has the option of creating a temp file, copying the data into
|
||||
* it, and cranking up NufxLib or zlib on that.)
|
||||
*
|
||||
* NOTE to self: see fsopen() to control sharing.
|
||||
*
|
||||
* NOTE: the Seek() implementations currently do not consistently allow or
|
||||
* disallow seeking past the current EOF of a file. When writing a file this
|
||||
* can be very useful, so someday we should implement it for all classes.
|
||||
*/
|
||||
class GenericFD {
|
||||
public:
|
||||
GenericFD(void) : fReadOnly(true) {}
|
||||
virtual ~GenericFD(void) {} /* = 0 */
|
||||
|
||||
// All sub-classes must provide these, plus a type-specific Open call.
|
||||
virtual DIError Read(void* buf, size_t length,
|
||||
size_t* pActual = NULL) = 0;
|
||||
virtual DIError Write(const void* buf, size_t length,
|
||||
size_t* pActual = NULL) = 0;
|
||||
virtual DIError Seek(di_off_t offset, DIWhence whence) = 0;
|
||||
virtual di_off_t Tell(void) = 0;
|
||||
virtual DIError Truncate(void) = 0;
|
||||
virtual DIError Close(void) = 0;
|
||||
virtual const char* GetPathName(void) const = 0;
|
||||
|
||||
// Flush-data call, only needed for physical devices
|
||||
virtual DIError Flush(void) { return kDIErrNone; }
|
||||
|
||||
// Utility functions.
|
||||
virtual DIError Rewind(void) { return Seek(0, kSeekSet); }
|
||||
|
||||
virtual bool GetReadOnly(void) const { return fReadOnly; }
|
||||
|
||||
/*
|
||||
typedef enum {
|
||||
kGFDTypeUnknown = 0,
|
||||
kGFDTypeFile,
|
||||
kGFDTypeBuffer,
|
||||
kGFDTypeWinVolume,
|
||||
kGFDTypeGFD
|
||||
} GFDType;
|
||||
virtual GFDType GetGFDType(void) const = 0;
|
||||
*/
|
||||
|
||||
/*
|
||||
* Utility function to copy data from one GFD to another. Both GFDs must
|
||||
* be seeked to their initial positions. "length" bytes will be copied.
|
||||
*/
|
||||
static DIError CopyFile(GenericFD* pDst, GenericFD* pSrc, di_off_t length,
|
||||
uint32_t* pCRC = NULL);
|
||||
|
||||
protected:
|
||||
GenericFD& operator=(const GenericFD&);
|
||||
GenericFD(const GenericFD&);
|
||||
|
||||
bool fReadOnly; // set when file is opened
|
||||
};
|
||||
|
||||
class GFDFile : public GenericFD {
|
||||
public:
|
||||
#ifdef HAVE_FSEEKO
|
||||
GFDFile(void) : fPathName(NULL), fFp(NULL) {}
|
||||
#else
|
||||
GFDFile(void) : fPathName(NULL), fFd(-1) {}
|
||||
#endif
|
||||
virtual ~GFDFile(void) { Close(); delete[] fPathName; }
|
||||
|
||||
virtual DIError Open(const char* filename, bool readOnly);
|
||||
virtual DIError Read(void* buf, size_t length,
|
||||
size_t* pActual = NULL);
|
||||
virtual DIError Write(const void* buf, size_t length,
|
||||
size_t* pActual = NULL);
|
||||
virtual DIError Seek(di_off_t offset, DIWhence whence);
|
||||
virtual di_off_t Tell(void);
|
||||
virtual DIError Truncate(void);
|
||||
virtual DIError Close(void);
|
||||
virtual const char* GetPathName(void) const { return fPathName; }
|
||||
|
||||
private:
|
||||
char* fPathName;
|
||||
|
||||
#ifdef HAVE_FSEEKO
|
||||
FILE* fFp;
|
||||
#else
|
||||
int fFd;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
class GFDWinVolume : public GenericFD {
|
||||
public:
|
||||
GFDWinVolume(void) :
|
||||
fPathName(NULL),
|
||||
fCurrentOffset(0),
|
||||
fVolumeEOF(-1),
|
||||
fBlockSize(0)
|
||||
{}
|
||||
virtual ~GFDWinVolume(void) { delete[] fPathName; }
|
||||
|
||||
virtual DIError Open(const char* deviceName, bool readOnly);
|
||||
virtual DIError Read(void* buf, size_t length,
|
||||
size_t* pActual = NULL);
|
||||
virtual DIError Write(const void* buf, size_t length,
|
||||
size_t* pActual = NULL);
|
||||
virtual DIError Seek(di_off_t offset, DIWhence whence);
|
||||
virtual di_off_t Tell(void);
|
||||
virtual DIError Truncate(void) { return kDIErrNotSupported; }
|
||||
virtual DIError Close(void);
|
||||
virtual const char* GetPathName(void) const { return fPathName; }
|
||||
|
||||
virtual DIError Flush(void) { return fVolAccess.FlushCache(false); }
|
||||
|
||||
private:
|
||||
char* fPathName; // for display only
|
||||
Win32VolumeAccess fVolAccess;
|
||||
di_off_t fCurrentOffset;
|
||||
di_off_t fVolumeEOF;
|
||||
int fBlockSize; // usually 512
|
||||
};
|
||||
#endif
|
||||
|
||||
class GFDBuffer : public GenericFD {
|
||||
public:
|
||||
GFDBuffer(void) :
|
||||
fBuffer(NULL),
|
||||
fLength(0),
|
||||
fAllocLength(0),
|
||||
fDoDelete(false),
|
||||
fDoExpand(false),
|
||||
fCurrentOffset(0)
|
||||
{}
|
||||
virtual ~GFDBuffer(void) { Close(); }
|
||||
|
||||
// If "doDelete" is set, the buffer will be freed with delete[] when
|
||||
// Close is called. This should ONLY be used for storage allocated
|
||||
// by the DiskImg library, as under Windows it can cause problems
|
||||
// because DLLs can have their own heap.
|
||||
//
|
||||
// "doExpand" will cause writing past the end of the buffer to
|
||||
// reallocate the buffer. Again, for internally-allocated storage
|
||||
// only. We expect the initial size to be close to accurate, so we
|
||||
// don't aggressively expand the buffer.
|
||||
virtual DIError Open(void* buffer, di_off_t length, bool doDelete,
|
||||
bool doExpand, bool readOnly);
|
||||
virtual DIError Read(void* buf, size_t length,
|
||||
size_t* pActual = NULL);
|
||||
virtual DIError Write(const void* buf, size_t length,
|
||||
size_t* pActual = NULL);
|
||||
virtual DIError Seek(di_off_t offset, DIWhence whence);
|
||||
virtual di_off_t Tell(void);
|
||||
virtual DIError Truncate(void) {
|
||||
fLength = (long) Tell();
|
||||
return kDIErrNone;
|
||||
}
|
||||
virtual DIError Close(void);
|
||||
virtual const char* GetPathName(void) const { return NULL; }
|
||||
|
||||
// Back door; try not to use this.
|
||||
void* GetBuffer(void) const { return fBuffer; }
|
||||
|
||||
private:
|
||||
enum { kMaxReasonableSize = 256 * 1024 * 1024 };
|
||||
void* fBuffer;
|
||||
long fLength; // these sit in memory, so there's no
|
||||
long fAllocLength; // value in using di_off_t here
|
||||
bool fDoDelete;
|
||||
bool fDoExpand;
|
||||
di_off_t fCurrentOffset; // actually limited to (long)
|
||||
};
|
||||
|
||||
#if 0
|
||||
class GFDEmbedded : public GenericFD {
|
||||
public:
|
||||
GFDEmbedded(void) : fEFD(NULL) {}
|
||||
virtual ~GFDEmbedded(void) { Close(); }
|
||||
|
||||
virtual DIError Open(EmbeddedFD* pEFD, bool readOnly);
|
||||
virtual DIError Read(void* buf, size_t length,
|
||||
size_t* pActual = NULL);
|
||||
virtual DIError Write(const void* buf, size_t length,
|
||||
size_t* pActual = NULL);
|
||||
virtual DIError Seek(di_off_t offset, DIWhence whence);
|
||||
virtual di_off_t Tell(void);
|
||||
virtual DIError Close(void);
|
||||
virtual const char* GetPathName(void) const { return NULL; }
|
||||
|
||||
private:
|
||||
EmbeddedFD* fEFD;
|
||||
};
|
||||
#endif
|
||||
|
||||
/* pass all requests straight through to another GFD (with offset bias) */
|
||||
class GFDGFD : public GenericFD {
|
||||
public:
|
||||
GFDGFD(void) : fpGFD(NULL), fOffset(0) {}
|
||||
virtual ~GFDGFD(void) { Close(); }
|
||||
|
||||
virtual DIError Open(GenericFD* pGFD, di_off_t offset, bool readOnly) {
|
||||
if (pGFD == NULL)
|
||||
return kDIErrInvalidArg;
|
||||
if (!readOnly && pGFD->GetReadOnly())
|
||||
return kDIErrAccessDenied; // can't convert to read-write
|
||||
fpGFD = pGFD;
|
||||
fOffset = offset;
|
||||
fReadOnly = readOnly;
|
||||
Seek(0, kSeekSet);
|
||||
return kDIErrNone;
|
||||
}
|
||||
virtual DIError Read(void* buf, size_t length,
|
||||
size_t* pActual = NULL)
|
||||
{
|
||||
return fpGFD->Read(buf, length, pActual);
|
||||
}
|
||||
virtual DIError Write(const void* buf, size_t length,
|
||||
size_t* pActual = NULL)
|
||||
{
|
||||
return fpGFD->Write(buf, length, pActual);
|
||||
}
|
||||
virtual DIError Seek(di_off_t offset, DIWhence whence) {
|
||||
return fpGFD->Seek(offset + fOffset, whence);
|
||||
}
|
||||
virtual di_off_t Tell(void) {
|
||||
return fpGFD->Tell() -fOffset;
|
||||
}
|
||||
virtual DIError Truncate(void) {
|
||||
return fpGFD->Truncate();
|
||||
}
|
||||
virtual DIError Close(void) {
|
||||
/* do NOT close underlying descriptor */
|
||||
fpGFD = NULL;
|
||||
return kDIErrNone;
|
||||
}
|
||||
virtual const char* GetPathName(void) const { return fpGFD->GetPathName(); }
|
||||
|
||||
private:
|
||||
GenericFD* fpGFD;
|
||||
di_off_t fOffset;
|
||||
};
|
||||
|
||||
}; // namespace DiskImgLib
|
||||
|
||||
#endif /*__GENERIC_FD__*/
|
191
diskimg/Global.cpp
Normal file
191
diskimg/Global.cpp
Normal file
@ -0,0 +1,191 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Implementation of DiskImgLib globals.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
#include "ASPI.h"
|
||||
|
||||
/*static*/ bool Global::fAppInitCalled = false;
|
||||
|
||||
/*static*/ ASPI* Global::fpASPI = NULL;
|
||||
|
||||
/* global constant */
|
||||
const char* DiskImgLib::kASPIDev = "ASPI:";
|
||||
|
||||
|
||||
/*
|
||||
* Perform one-time DLL initialization.
|
||||
*/
|
||||
/*static*/ DIError Global::AppInit(void)
|
||||
{
|
||||
NuError nerr;
|
||||
int32_t major, minor, bug;
|
||||
|
||||
if (fAppInitCalled) {
|
||||
LOGW("DiskImg AppInit already called");
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
LOGI("Initializing DiskImg library v%d.%d.%d",
|
||||
kDiskImgVersionMajor, kDiskImgVersionMinor, kDiskImgVersionBug);
|
||||
|
||||
#ifdef _WIN32
|
||||
HMODULE hModule;
|
||||
WCHAR fileNameBuf[256];
|
||||
hModule = ::GetModuleHandle(L"DiskImg4.dll");
|
||||
if (hModule != NULL &&
|
||||
::GetModuleFileName(hModule, fileNameBuf,
|
||||
sizeof(fileNameBuf) / sizeof(WCHAR)) != 0)
|
||||
{
|
||||
// GetModuleHandle does not increase ref count, so no need to release
|
||||
LOGD("DiskImg DLL loaded from '%ls'", fileNameBuf);
|
||||
} else {
|
||||
LOGW("Unable to get DiskImg DLL filename");
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Make sure we're linked against a compatible version of NufxLib.
|
||||
*/
|
||||
nerr = NuGetVersion(&major, &minor, &bug, NULL, NULL);
|
||||
if (nerr != kNuErrNone) {
|
||||
LOGE("Unable to get version number from NufxLib.");
|
||||
return kDIErrNufxLibInitFailed;
|
||||
}
|
||||
|
||||
if (major != kNuVersionMajor || minor < kNuVersionMinor) {
|
||||
LOGE("Unexpected NufxLib version %d.%d.%d",
|
||||
major, minor, bug);
|
||||
return kDIErrNufxLibInitFailed;
|
||||
}
|
||||
|
||||
/*
|
||||
* Do one-time init over in the DiskImg class.
|
||||
*/
|
||||
DiskImg::CalcNibbleInvTables();
|
||||
|
||||
#if defined(HAVE_WINDOWS_CDROM) && defined(WANT_ASPI)
|
||||
if (kAlwaysTryASPI || IsWin9x()) {
|
||||
fpASPI = new ASPI;
|
||||
if (fpASPI->Init() != kDIErrNone) {
|
||||
delete fpASPI;
|
||||
fpASPI = NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
LOGD("DiskImg HasSPTI=%d HasASPI=%d", GetHasSPTI(), GetHasASPI());
|
||||
|
||||
fAppInitCalled = true;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform cleanup at application shutdown time.
|
||||
*/
|
||||
/*static*/ DIError Global::AppCleanup(void)
|
||||
{
|
||||
LOGI("DiskImgLib cleanup");
|
||||
delete fpASPI;
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Simple getters.
|
||||
*
|
||||
* SPTI is enabled if we're in Win2K *and* ASPI isn't loaded. If ASPI is
|
||||
* loaded, it can interfere with SPTI, so we want to stick with one or
|
||||
* the other.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
/*static*/ bool Global::GetHasSPTI(void) { return !IsWin9x() && fpASPI == NULL; }
|
||||
/*static*/ bool Global::GetHasASPI(void) { return fpASPI != NULL; }
|
||||
/*static*/ unsigned long Global::GetASPIVersion(void) {
|
||||
assert(fpASPI != NULL);
|
||||
#ifdef WANT_ASPI
|
||||
return fpASPI->GetVersion();
|
||||
#else
|
||||
return 123456789;
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
/*static*/ bool Global::GetHasSPTI(void) { return false; }
|
||||
/*static*/ bool Global::GetHasASPI(void) { return false; }
|
||||
/*static*/ unsigned long Global::GetASPIVersion(void) { assert(false); return 0; }
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Return current library versions.
|
||||
*/
|
||||
/*static*/ void Global::GetVersion(int32_t* pMajor, int32_t* pMinor,
|
||||
int32_t* pBug)
|
||||
{
|
||||
if (pMajor != NULL)
|
||||
*pMajor = kDiskImgVersionMajor;
|
||||
if (pMinor != NULL)
|
||||
*pMinor = kDiskImgVersionMinor;
|
||||
if (pBug != NULL)
|
||||
*pBug = kDiskImgVersionBug;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Pointer to debug message handler function.
|
||||
*/
|
||||
/*static*/ Global::DebugMsgHandler Global::gDebugMsgHandler = NULL;
|
||||
|
||||
/*
|
||||
* Change the debug message handler. The previous handler is returned.
|
||||
*/
|
||||
Global::DebugMsgHandler Global::SetDebugMsgHandler(DebugMsgHandler handler)
|
||||
{
|
||||
DebugMsgHandler oldHandler;
|
||||
|
||||
oldHandler = gDebugMsgHandler;
|
||||
gDebugMsgHandler = handler;
|
||||
return oldHandler;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a debug message to the debug message handler.
|
||||
*
|
||||
* Even if _DEBUG_MSGS is disabled we can still get here from the NuFX error
|
||||
* handler.
|
||||
*/
|
||||
/*static*/ void Global::PrintDebugMsg(const char* file, int line, const char* fmt, ...)
|
||||
{
|
||||
if (gDebugMsgHandler == NULL) {
|
||||
/*
|
||||
* This can happen if the app decides to bail with an exit()
|
||||
* call. I'm not sure what's zapping the pointer.
|
||||
*
|
||||
* We get here on "-install" or "-uninstall", which really
|
||||
* should be using a more Windows-friendly exit strategy.
|
||||
*/
|
||||
DebugBreak();
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[512];
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
#if defined(HAVE_VSNPRINTF)
|
||||
(void) vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
#elif defined(HAVE__VSNPRINTF)
|
||||
(void) _vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
#else
|
||||
# error "hosed"
|
||||
#endif
|
||||
va_end(args);
|
||||
|
||||
buf[sizeof(buf)-1] = '\0';
|
||||
|
||||
(*gDebugMsgHandler)(file, line, buf);
|
||||
}
|
690
diskimg/Gutenberg.cpp
Normal file
690
diskimg/Gutenberg.cpp
Normal file
@ -0,0 +1,690 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2009 by CiderPress authors. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Implementation of Gutenberg disk format (used by the Gutenberg and
|
||||
* Gutenberg Jr. word processors).
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* DiskFSGutenberg
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
The Gutenberg disk format embeds the file structure meta-data into the disk
|
||||
sectors themselves, rather than having a separate track/sector list. The
|
||||
first six bytes in every sector are:
|
||||
|
||||
+00 previous track in this file (+$80 indicates link not valid?)
|
||||
+01 previous sector
|
||||
+02 current track (+$80 indicates start of file)
|
||||
+03 current sector
|
||||
+04 next track (+$80 indicates end of file)
|
||||
+05 next sector
|
||||
|
||||
The files are circular -- the "next" and "previous" links will wrap around --
|
||||
so you have to test the high bit to see if you've reached an end.
|
||||
|
||||
The disk catalog works the same way, and is present as the first file on
|
||||
the disk (called "DIR"). (It's not quite the same -- the "previous" pointer
|
||||
in the first sector just points to the first sector.) The catalog begins
|
||||
at track 17 sector 7, and skips around the disk.
|
||||
|
||||
The boot area is not represented by a file, and does not include the
|
||||
embedded T/S links.
|
||||
|
||||
Each directory entry is 16 bytes, and lays out rather nicely in the sector
|
||||
editor:
|
||||
|
||||
00: 11 07 11 87 15 0e c7 c2 af cd c1 d3 d4 c5 d2 8d ......GB/MASTER.
|
||||
10: c4 c9 d2 a0 a0 a0 a0 a0 a0 a0 a0 a0 11 07 cc 8d DIR ..L.
|
||||
20: c3 cf d0 d9 a0 a0 a0 a0 a0 a0 a0 a0 10 40 d0 8d COPY .@P.
|
||||
30: c3 cf d0 d9 c1 cc cc a0 a0 a0 a0 a0 10 04 d0 8d COPYALL ..P.
|
||||
|
||||
This shows the six T/S bytes, followed by a nine-character volume name.
|
||||
The fact that each entry ends in 0x8d is likely a deliberate attempt to
|
||||
make the file readable as high-ASCII text, with one entry per line.
|
||||
|
||||
The regular directory entries start at 0x10. The file name is 12 bytes,
|
||||
followed by the track and sector of the start of the file. Note "DIR"
|
||||
starts at track 17 sector 7, as expected. The next entry, COPY, has 0x40 for
|
||||
its sector number, indicating that it has been deleted. (Some entries on
|
||||
Gutenberg Jr. disks use 0x40 for the track number instead?)
|
||||
|
||||
The next value is one of 'L', 'P', 'M', or ' ' (0xcc, 0xd0, 0xcd, 0xa0).
|
||||
Some files have text, some have fonts, some have executable code (a short
|
||||
header followed by 6502 instructions). There's no apparent link between
|
||||
the value and the type of data in the file.
|
||||
*/
|
||||
|
||||
const int kMaxSectors = 32;
|
||||
const int kMaxVolNameLen = 9;
|
||||
const int kSctSize = 256;
|
||||
const int kVTOCTrack = 17;
|
||||
const int kVTOCSector = 7;
|
||||
const int kCatalogEntryOffset = 0x10; // first entry in cat sect starts here
|
||||
const int kCatalogEntrySize = 16; // length in bytes of catalog entries
|
||||
const int kCatalogEntriesPerSect = 15; // #of entries per catalog sector
|
||||
const int kEntryDeleted = 0x40; // this is used to designate deleted files
|
||||
const int kEntryUnused = 0x00; // this is track# in never-used entries
|
||||
const int kMaxTSPairs = 0x7a; // 122 entries for 256-byte sectors
|
||||
const int kTSOffset = 0x0c; // first T/S entry in a T/S list
|
||||
|
||||
const int kMaxTSIterations = 32;
|
||||
|
||||
/*
|
||||
* Get a pointer to the Nth entry in a catalog sector.
|
||||
*/
|
||||
static inline uint8_t* GetCatalogEntryPtr(uint8_t* basePtr, int entryNum)
|
||||
{
|
||||
assert(entryNum >= 0 && entryNum < kCatalogEntriesPerSect);
|
||||
return basePtr + kCatalogEntryOffset + entryNum * kCatalogEntrySize;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Test this image for Gutenberg-ness.
|
||||
*
|
||||
*/
|
||||
static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder,
|
||||
int* pGoodCount)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t sctBuf[kSctSize];
|
||||
int catTrack = kVTOCTrack;
|
||||
int catSect = kVTOCSector;
|
||||
int foundGood = 0;
|
||||
int iterations = 0;
|
||||
|
||||
*pGoodCount = 0;
|
||||
|
||||
/*
|
||||
* Walk through the catalog track to try to figure out ordering.
|
||||
*/
|
||||
while (iterations < DiskFSGutenberg::kMaxCatalogSectors)
|
||||
{
|
||||
dierr = pImg->ReadTrackSectorSwapped(catTrack, catSect, sctBuf,
|
||||
imageOrder, DiskImg::kSectorOrderDOS);
|
||||
if (dierr != kDIErrNone) {
|
||||
dierr = kDIErrNone;
|
||||
break; /* allow it if earlier stuff was okay */
|
||||
}
|
||||
if (catTrack == (sctBuf[2] & 0x7f) && catSect == (sctBuf[3] & 0x7f)) {
|
||||
// current-sector values matched, check for the end-of-entry bits
|
||||
foundGood++;
|
||||
if (sctBuf[0x0f] == 0x8d && sctBuf[0x1f] == 0x8d &&
|
||||
sctBuf[0x2f] == 0x8d && sctBuf[0x3f] == 0x8d &&
|
||||
sctBuf[0x4f] == 0x8d && sctBuf[0x5f] == 0x8d &&
|
||||
sctBuf[0x6f] == 0x8d && sctBuf[0x7f] == 0x8d &&
|
||||
sctBuf[0x8f] == 0x8d && sctBuf[0x9f] == 0x8d)
|
||||
{
|
||||
foundGood++;
|
||||
}
|
||||
}
|
||||
catTrack = sctBuf[0x04];
|
||||
catSect = sctBuf[0x05];
|
||||
if ((catTrack & 0x80) != 0) {
|
||||
// full circle
|
||||
break;
|
||||
}
|
||||
iterations++; // watch for infinite loops
|
||||
}
|
||||
if (iterations >= DiskFSGutenberg::kMaxCatalogSectors) {
|
||||
/* possible cause: LF->CR conversion screws up link to sector $0a */
|
||||
dierr = kDIErrDirectoryLoop;
|
||||
LOGI(" Gutenberg directory links cause a loop (order=%d)", imageOrder);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
LOGI(" Gutenberg foundGood=%d order=%d", foundGood, imageOrder);
|
||||
*pGoodCount = foundGood;
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test to see if the image is a Gutenberg word processor data disk.
|
||||
*/
|
||||
/*static*/ DIError DiskFSGutenberg::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
||||
DiskImg::FSFormat* pFormat, FSLeniency leniency)
|
||||
{
|
||||
if (pImg->GetNumTracks() > kMaxInterestingTracks)
|
||||
return kDIErrFilesystemNotFound;
|
||||
|
||||
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
|
||||
|
||||
DiskImg::GetSectorOrderArray(ordering, *pOrder);
|
||||
|
||||
DiskImg::SectorOrder bestOrder = DiskImg::kSectorOrderUnknown;
|
||||
int bestCount = 0;
|
||||
|
||||
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
|
||||
int goodCount = 0;
|
||||
|
||||
if (ordering[i] == DiskImg::kSectorOrderUnknown)
|
||||
continue;
|
||||
if (TestImage(pImg, ordering[i], &goodCount) == kDIErrNone) {
|
||||
if (goodCount > bestCount) {
|
||||
bestCount = goodCount;
|
||||
bestOrder = ordering[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestCount >= 2 ||
|
||||
(leniency == kLeniencyVery && bestCount >= 1))
|
||||
{
|
||||
LOGI(" Gutenberg test: bestCount=%d for order=%d", bestCount, bestOrder);
|
||||
assert(bestOrder != DiskImg::kSectorOrderUnknown);
|
||||
*pOrder = bestOrder;
|
||||
*pFormat = DiskImg::kFormatGutenberg;
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
LOGI(" Gutenberg didn't find a valid filesystem.");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Get things rolling.
|
||||
*
|
||||
* Since we're assured that this is a valid disk, errors encountered from here
|
||||
* on out must be handled somehow, possibly by claiming that the disk is
|
||||
* completely full and has no files on it.
|
||||
*/
|
||||
DIError DiskFSGutenberg::Initialize(InitMode initMode)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
|
||||
fVolumeUsage.Create(fpImg->GetNumTracks(), fpImg->GetNumSectPerTrack());
|
||||
|
||||
/* read the contents of the catalog, creating our A2File list */
|
||||
dierr = ReadCatalog();
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
/* run through and get file lengths and data offsets */
|
||||
dierr = GetFileLengths();
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
sprintf(fDiskVolumeID, "Gutenberg: %s", fDiskVolumeName);
|
||||
|
||||
fDiskIsGood = CheckDiskIsGood();
|
||||
|
||||
fVolumeUsage.Dump();
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the amount of free space remaining.
|
||||
*/
|
||||
DIError DiskFSGutenberg::GetFreeSpaceCount(long* pTotalUnits, long* pFreeUnits,
|
||||
int* pUnitSize) const
|
||||
{
|
||||
*pTotalUnits = fpImg->GetNumTracks() * fpImg->GetNumSectPerTrack();
|
||||
*pFreeUnits = 0;
|
||||
*pUnitSize = kSectorSize;
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the disk's catalog.
|
||||
*/
|
||||
DIError DiskFSGutenberg::ReadCatalog(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t sctBuf[kSctSize];
|
||||
int catTrack, catSect;
|
||||
int iterations;
|
||||
|
||||
catTrack = kVTOCTrack;
|
||||
catSect = kVTOCSector;
|
||||
iterations = 0;
|
||||
|
||||
memset(fCatalogSectors, 0, sizeof(fCatalogSectors));
|
||||
|
||||
while (catTrack < 35 && catSect < 16 && iterations < kMaxCatalogSectors)
|
||||
{
|
||||
LOGD(" Gutenberg reading catalog sector T=%d S=%d", catTrack, catSect);
|
||||
dierr = fpImg->ReadTrackSector(catTrack, catSect, sctBuf);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
memcpy(fDiskVolumeName, &sctBuf[6], kMaxVolNameLen); // Copy out the volume name; it should be the same on all catalog sectors.
|
||||
fDiskVolumeName[kMaxVolNameLen] = 0x00;
|
||||
DiskFSGutenberg::LowerASCII((uint8_t*)fDiskVolumeName, kMaxVolNameLen);
|
||||
A2FileGutenberg::TrimTrailingSpaces(fDiskVolumeName);
|
||||
|
||||
dierr = ProcessCatalogSector(catTrack, catSect, sctBuf);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
fCatalogSectors[iterations].track = catTrack;
|
||||
fCatalogSectors[iterations].sector = catSect;
|
||||
|
||||
catTrack = sctBuf[0x04];
|
||||
catSect = sctBuf[0x05];
|
||||
|
||||
iterations++; // watch for infinite loops
|
||||
|
||||
}
|
||||
if (iterations >= kMaxCatalogSectors) {
|
||||
dierr = kDIErrDirectoryLoop;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process the list of files in one sector of the catalog.
|
||||
*
|
||||
* Pass in the track, sector, and the contents of that track and sector.
|
||||
* (We only use "catTrack" and "catSect" to fill out some fields.)
|
||||
*/
|
||||
DIError DiskFSGutenberg::ProcessCatalogSector(int catTrack, int catSect,
|
||||
const uint8_t* sctBuf)
|
||||
{
|
||||
A2FileGutenberg* pFile;
|
||||
const uint8_t* pEntry;
|
||||
int i;
|
||||
|
||||
pEntry = &sctBuf[kCatalogEntryOffset];
|
||||
|
||||
for (i = 0; i < kCatalogEntriesPerSect; i++) {
|
||||
if (pEntry[0x0c] != kEntryDeleted && pEntry[0x0d] != kEntryDeleted &&
|
||||
pEntry[0x00] != 0xa0 && pEntry[0x00] != 0x00)
|
||||
{
|
||||
pFile = new A2FileGutenberg(this);
|
||||
|
||||
pFile->SetQuality(A2File::kQualityGood);
|
||||
|
||||
pFile->fTrack = pEntry[0x0c];
|
||||
pFile->fSector = pEntry[0x0d];
|
||||
|
||||
memcpy(pFile->fFileName, &pEntry[0x00], A2FileGutenberg::kMaxFileName);
|
||||
pFile->fFileName[A2FileGutenberg::kMaxFileName] = '\0';
|
||||
pFile->FixFilename();
|
||||
|
||||
//pFile->fCatTS.track = catTrack;
|
||||
//pFile->fCatTS.sector = catSect;
|
||||
pFile->fCatEntryNum = i;
|
||||
|
||||
/* can't do these yet, so just set to defaults */
|
||||
pFile->fLength = 0;
|
||||
pFile->fSparseLength = 0;
|
||||
pFile->fDataOffset = 0;
|
||||
pFile->fLengthInSectors = 0;
|
||||
pFile->fLengthInSectors = 0;
|
||||
|
||||
AddFileToList(pFile);
|
||||
}
|
||||
//if (pEntry[0x00] == 0xa0)
|
||||
// break;
|
||||
pEntry += kCatalogEntrySize;
|
||||
}
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform consistency checks on the filesystem.
|
||||
*
|
||||
* Returns "true" if disk appears to be perfect, "false" otherwise.
|
||||
*/
|
||||
bool DiskFSGutenberg::CheckDiskIsGood(void)
|
||||
{
|
||||
bool result = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Run through our list of files, computing the lengths and marking file
|
||||
* usage in the VolumeUsage object.
|
||||
*/
|
||||
DIError DiskFSGutenberg::GetFileLengths(void)
|
||||
{
|
||||
A2FileGutenberg* pFile;
|
||||
uint8_t sctBuf[kSctSize];
|
||||
int tsCount = 0;
|
||||
uint16_t currentTrack, currentSector;
|
||||
|
||||
pFile = (A2FileGutenberg*) GetNextFile(NULL);
|
||||
while (pFile != NULL) {
|
||||
DIError dierr;
|
||||
tsCount = 0;
|
||||
currentTrack = pFile->fTrack;
|
||||
currentSector = pFile->fSector;
|
||||
|
||||
while (currentTrack < 0x80) {
|
||||
tsCount ++;
|
||||
dierr = fpImg->ReadTrackSector(currentTrack, currentSector, sctBuf);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI("Gutenberg failed loading track/sector for '%s'",
|
||||
pFile->GetPathName());
|
||||
goto bail;
|
||||
}
|
||||
currentTrack = sctBuf[0x04];
|
||||
currentSector = sctBuf[0x05];
|
||||
}
|
||||
pFile->fLengthInSectors = tsCount;
|
||||
pFile->fLength = tsCount * 250; // First six bytes of sector are t/s pointers
|
||||
|
||||
pFile = (A2FileGutenberg*) GetNextFile(pFile);
|
||||
}
|
||||
|
||||
bail:
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert high ASCII to low ASCII.
|
||||
*
|
||||
* Some people put inverse and flashing text into filenames, not to mention
|
||||
* control characters, so we have to cope with those too.
|
||||
*
|
||||
* We modify the first "len" bytes of "buf" in place.
|
||||
*/
|
||||
/*static*/ void DiskFSGutenberg::LowerASCII(uint8_t* buf, long len)
|
||||
{
|
||||
while (len--) {
|
||||
if (*buf & 0x80) {
|
||||
if (*buf >= 0xa0)
|
||||
*buf &= 0x7f;
|
||||
else
|
||||
*buf = (*buf & 0x7f) + 0x20;
|
||||
} else
|
||||
*buf = ((*buf & 0x3f) ^ 0x20) + 0x20;
|
||||
|
||||
buf++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* A2FileGutenberg
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Constructor.
|
||||
*/
|
||||
A2FileGutenberg::A2FileGutenberg(DiskFS* pDiskFS) : A2File(pDiskFS)
|
||||
{
|
||||
fTrack = -1;
|
||||
fSector = -1;
|
||||
fLengthInSectors = 0;
|
||||
fLocked = true;
|
||||
fFileName[0] = '\0';
|
||||
fFileType = kTypeText;
|
||||
|
||||
fCatTS.track = fCatTS.sector = 0;
|
||||
fCatEntryNum = -1;
|
||||
|
||||
fAuxType = 0;
|
||||
fDataOffset = 0;
|
||||
fLength = -1;
|
||||
fSparseLength = -1;
|
||||
|
||||
fpOpenFile = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Destructor. Make sure an "open" file gets "closed".
|
||||
*/
|
||||
A2FileGutenberg::~A2FileGutenberg(void)
|
||||
{
|
||||
delete fpOpenFile;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert the filetype enum to a ProDOS type.
|
||||
*
|
||||
*/
|
||||
uint32_t A2FileGutenberg::GetFileType(void) const
|
||||
{
|
||||
return 0x04; // TXT;
|
||||
}
|
||||
|
||||
/*
|
||||
* "Fix" a filename. Convert DOS-ASCII to normal ASCII, and strip
|
||||
* trailing spaces.
|
||||
*/
|
||||
void A2FileGutenberg::FixFilename(void)
|
||||
{
|
||||
DiskFSGutenberg::LowerASCII((uint8_t*)fFileName, kMaxFileName);
|
||||
TrimTrailingSpaces(fFileName);
|
||||
}
|
||||
|
||||
/*
|
||||
* Trim the spaces off the end of a filename.
|
||||
*
|
||||
* Assumes the filename has already been converted to low ASCII.
|
||||
*/
|
||||
/*static*/ void A2FileGutenberg::TrimTrailingSpaces(char* filename)
|
||||
{
|
||||
char* lastspc = filename + strlen(filename);
|
||||
|
||||
assert(*lastspc == '\0');
|
||||
|
||||
while (--lastspc) {
|
||||
if (*lastspc != ' ')
|
||||
break;
|
||||
}
|
||||
|
||||
*(lastspc+1) = '\0';
|
||||
}
|
||||
|
||||
/*
|
||||
* Encode a filename into high ASCII, padded out with spaces to
|
||||
* kMaxFileName chars. Lower case is converted to upper case. This
|
||||
* does not filter out control characters or other chunk.
|
||||
*
|
||||
* "buf" must be able to hold kMaxFileName+1 chars.
|
||||
*/
|
||||
/*static*/ void A2FileGutenberg::MakeDOSName(char* buf, const char* name)
|
||||
{
|
||||
for (int i = 0; i < kMaxFileName; i++) {
|
||||
if (*name == '\0')
|
||||
*buf++ = (char) 0xa0;
|
||||
else
|
||||
*buf++ = toupper(*name++) | 0x80;
|
||||
}
|
||||
*buf = '\0';
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Set up state for this file.
|
||||
*/
|
||||
DIError A2FileGutenberg::Open(A2FileDescr** ppOpenFile, bool readOnly,
|
||||
bool rsrcFork /*=false*/)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
A2FDGutenberg* pOpenFile = NULL;
|
||||
|
||||
if (!readOnly) {
|
||||
if (fpDiskFS->GetDiskImg()->GetReadOnly())
|
||||
return kDIErrAccessDenied;
|
||||
if (fpDiskFS->GetFSDamaged())
|
||||
return kDIErrBadDiskImage;
|
||||
}
|
||||
|
||||
if (fpOpenFile != NULL) {
|
||||
dierr = kDIErrAlreadyOpen;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (rsrcFork)
|
||||
return kDIErrForkNotFound;
|
||||
|
||||
pOpenFile = new A2FDGutenberg(this);
|
||||
|
||||
pOpenFile->fOffset = 0;
|
||||
pOpenFile->fOpenEOF = fLength;
|
||||
pOpenFile->fOpenSectorsUsed = fLengthInSectors;
|
||||
|
||||
fpOpenFile = pOpenFile; // add it to our single-member "open file set"
|
||||
*ppOpenFile = pOpenFile;
|
||||
pOpenFile = NULL;
|
||||
|
||||
bail:
|
||||
delete pOpenFile;
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dump the contents of an A2FileGutenberg.
|
||||
*/
|
||||
void A2FileGutenberg::Dump(void) const
|
||||
{
|
||||
LOGI("A2FileGutenberg '%s'", fFileName);
|
||||
LOGI(" TS T=%-2d S=%-2d", fTrack, fSector);
|
||||
LOGI(" Cat T=%-2d S=%-2d", fCatTS.track, fCatTS.sector);
|
||||
LOGI(" type=%d lck=%d slen=%d", fFileType, fLocked, fLengthInSectors);
|
||||
LOGI(" auxtype=0x%04x length=%ld",
|
||||
fAuxType, (long) fLength);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* A2FDGutenberg
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Read data from the current offset.
|
||||
*
|
||||
*/
|
||||
DIError A2FDGutenberg::Read(void* buf, size_t len, size_t* pActual)
|
||||
{
|
||||
LOGD(" Gutenberg reading %lu bytes from '%s' (offset=%ld)",
|
||||
(unsigned long) len, fpFile->GetPathName(), (long) fOffset);
|
||||
|
||||
A2FileGutenberg* pFile = (A2FileGutenberg*) fpFile;
|
||||
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t sctBuf[kSctSize];
|
||||
short currentTrack, currentSector;
|
||||
//di_off_t actualOffset = fOffset + pFile->fDataOffset; // adjust for embedded len
|
||||
int bufOffset = 6;
|
||||
size_t thisCount;
|
||||
|
||||
if (len == 0)
|
||||
return kDIErrNone;
|
||||
assert(fOpenEOF != 0);
|
||||
currentTrack = pFile->fTrack;
|
||||
currentSector = pFile->fSector;
|
||||
/* could be more clever in here and avoid double-buffering */
|
||||
while (len) {
|
||||
dierr = pFile->GetDiskFS()->GetDiskImg()->ReadTrackSector(
|
||||
currentTrack,
|
||||
currentSector,
|
||||
sctBuf);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" Gutenberg error reading file '%s'", pFile->GetPathName());
|
||||
return dierr;
|
||||
}
|
||||
thisCount = kSctSize - bufOffset;
|
||||
if (thisCount > len)
|
||||
thisCount = len;
|
||||
memcpy(buf, sctBuf + bufOffset, thisCount);
|
||||
len -= thisCount;
|
||||
buf = (char*)buf + thisCount;
|
||||
currentTrack = sctBuf[0x04];
|
||||
currentSector = sctBuf[0x05];
|
||||
}
|
||||
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Writing Gutenberg files isn't supported.
|
||||
*/
|
||||
DIError A2FDGutenberg::Write(const void* buf, size_t len, size_t* pActual)
|
||||
{
|
||||
return kDIErrNotSupported;
|
||||
}
|
||||
|
||||
/*
|
||||
* Seek to the specified offset.
|
||||
*/
|
||||
DIError A2FDGutenberg::Seek(di_off_t offset, DIWhence whence)
|
||||
{
|
||||
return kDIErrNotSupported;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return current offset.
|
||||
*/
|
||||
di_off_t A2FDGutenberg::Tell(void)
|
||||
{
|
||||
return kDIErrNotSupported;
|
||||
}
|
||||
|
||||
/*
|
||||
* Release file state.
|
||||
*
|
||||
* If the file was modified, we need to update the sector usage count in
|
||||
* the catalog track, and possibly a length word in the first sector of
|
||||
* the file (for A/I/B).
|
||||
*
|
||||
* Given the current "write all at once" implementation of Write, we could
|
||||
* have handled the length word back when initially writing the data, but
|
||||
* someday we may fix that and I don't want to have to rewrite this part.
|
||||
*
|
||||
* Most applications don't check the value of "Close", or call it from a
|
||||
* destructor, so we call CloseDescr whether we succeed or not.
|
||||
*/
|
||||
DIError A2FDGutenberg::Close(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
|
||||
fpFile->CloseDescr(this);
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the #of sectors/blocks in the file.
|
||||
*/
|
||||
long A2FDGutenberg::GetSectorCount(void) const
|
||||
{
|
||||
return fTSCount;
|
||||
}
|
||||
|
||||
long A2FDGutenberg::GetBlockCount(void) const
|
||||
{
|
||||
return (fTSCount+1)/2;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the Nth track/sector in this file.
|
||||
*
|
||||
* Returns (0,0) for a sparse sector.
|
||||
*/
|
||||
DIError A2FDGutenberg::GetStorage(long sectorIdx, long* pTrack, long* pSector) const
|
||||
{
|
||||
return kDIErrInvalidIndex;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unimplemented
|
||||
*/
|
||||
DIError A2FDGutenberg::GetStorage(long blockIdx, long* pBlock) const
|
||||
{
|
||||
return kDIErrInvalidIndex;
|
||||
}
|
2298
diskimg/HFS.cpp
Normal file
2298
diskimg/HFS.cpp
Normal file
File diff suppressed because it is too large
Load Diff
2476
diskimg/ImageWrapper.cpp
Normal file
2476
diskimg/ImageWrapper.cpp
Normal file
File diff suppressed because it is too large
Load Diff
470
diskimg/MacPart.cpp
Normal file
470
diskimg/MacPart.cpp
Normal file
@ -0,0 +1,470 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* The "MacPart" DiskFS is a container class for multiple ProDOS and HFS
|
||||
* volumes. It represents a partitioned disk device, such as a hard
|
||||
* drive or CD-ROM.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
|
||||
const int kBlkSize = 512;
|
||||
const int kDDRBlock = 0; // Driver Descriptor Record block
|
||||
const int kPartMapStart = 1; // start of partition map
|
||||
|
||||
|
||||
/*
|
||||
* Format of DDR (block 0).
|
||||
*/
|
||||
typedef struct DiskFSMacPart::DriverDescriptorRecord {
|
||||
uint16_t sbSig; // {device signature}
|
||||
uint16_t sbBlkSize; // {block size of the device}
|
||||
uint32_t sbBlkCount; // {number of blocks on the device}
|
||||
uint16_t sbDevType; // {reserved}
|
||||
uint16_t sbDevId; // {reserved}
|
||||
uint32_t sbData; // {reserved}
|
||||
uint16_t sbDrvrCount; // {number of driver descriptor entries}
|
||||
uint16_t hiddenPad; // implicit in specification
|
||||
uint32_t ddBlock; // {first driver's starting block}
|
||||
uint16_t ddSize; // {size of the driver, in 512-byte blocks}
|
||||
uint16_t ddType; // {operating system type (MacOS = 1)}
|
||||
uint16_t ddPad[242]; // {additional drivers, if any}
|
||||
} DriverDescriptorRecord;
|
||||
|
||||
/*
|
||||
* Format of partition map blocks. The partition map is an array of these.
|
||||
*/
|
||||
typedef struct DiskFSMacPart::PartitionMap {
|
||||
uint16_t pmSig; // {partition signature}
|
||||
uint16_t pmSigPad; // {reserved}
|
||||
uint32_t pmMapBlkCnt; // {number of blocks in partition map}
|
||||
uint32_t pmPyPartStart; // {first physical block of partition}
|
||||
uint32_t pmPartBlkCnt; // {number of blocks in partition}
|
||||
uint8_t pmPartName[32]; // {partition name}
|
||||
uint8_t pmParType[32]; // {partition type}
|
||||
uint32_t pmLgDataStart; // {first logical block of data area}
|
||||
uint32_t pmDataCnt; // {number of blocks in data area}
|
||||
uint32_t pmPartStatus; // {partition status information}
|
||||
uint32_t pmLgBootStart; // {first logical block of boot code}
|
||||
uint32_t pmBootSize; // {size of boot code, in bytes}
|
||||
uint32_t pmBootAddr; // {boot code load address}
|
||||
uint32_t pmBootAddr2; // {reserved}
|
||||
uint32_t pmBootEntry; // {boot code entry point}
|
||||
uint32_t pmBootEntry2; // {reserved}
|
||||
uint32_t pmBootCksum; // {boot code checksum}
|
||||
uint8_t pmProcessor[16]; // {processor type}
|
||||
uint16_t pmPad[188]; // {reserved}
|
||||
} PartitionMap;
|
||||
|
||||
|
||||
/*
|
||||
* Figure out if this is a Macintosh-style partition.
|
||||
*
|
||||
* The "imageOrder" parameter has no use here, because (in the current
|
||||
* version) embedded parent volumes are implicitly ProDOS-ordered.
|
||||
*
|
||||
* It would be difficult to guess the block order based on the partition
|
||||
* structure, because the partition map entries can appear in any order.
|
||||
*/
|
||||
/*static*/ DIError DiskFSMacPart::TestImage(DiskImg* pImg,
|
||||
DiskImg::SectorOrder imageOrder)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t blkBuf[kBlkSize];
|
||||
DriverDescriptorRecord ddr;
|
||||
long pmMapBlkCnt;
|
||||
|
||||
assert(sizeof(PartitionMap) == kBlkSize);
|
||||
assert(sizeof(DriverDescriptorRecord) == kBlkSize);
|
||||
|
||||
/* check the DDR block */
|
||||
dierr = pImg->ReadBlockSwapped(kDDRBlock, blkBuf, imageOrder,
|
||||
DiskImg::kSectorOrderProDOS);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
UnpackDDR(blkBuf, &ddr);
|
||||
|
||||
if (ddr.sbSig != kDDRSignature) {
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
goto bail;
|
||||
}
|
||||
if (ddr.sbBlkSize != kBlkSize || ddr.sbBlkCount == 0) {
|
||||
if (ddr.sbBlkSize == 0 && ddr.sbBlkCount == 0) {
|
||||
/*
|
||||
* This is invalid, but it's the way floptical images formatted
|
||||
* by the C.V.Tech format utilities look.
|
||||
*/
|
||||
LOGI(" MacPart NOTE: found zeroed-out DDR, continuing anyway");
|
||||
} else if (ddr.sbBlkSize == kBlkSize && ddr.sbBlkCount == 0) {
|
||||
/*
|
||||
* This showed up on a disc, so handle it too.
|
||||
*/
|
||||
LOGI(" MacPart NOTE: found partially-zeroed-out DDR, continuing");
|
||||
} else {
|
||||
LOGI(" MacPart found 'ER' signature but blkSize=%d blkCount=%d",
|
||||
ddr.sbBlkSize, ddr.sbBlkCount);
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
DumpDDR(&ddr);
|
||||
|
||||
/* make sure block 1 is a partition */
|
||||
dierr = pImg->ReadBlockSwapped(kPartMapStart, blkBuf, imageOrder,
|
||||
DiskImg::kSectorOrderProDOS);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
if (GetShortBE(&blkBuf[0x00]) != kPartitionSignature) {
|
||||
LOGI(" MacPart partition signature not found in first part block");
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
goto bail;
|
||||
}
|
||||
pmMapBlkCnt = GetLongBE(&blkBuf[0x04]);
|
||||
if (pmMapBlkCnt <= 0 || pmMapBlkCnt > 256) {
|
||||
LOGI(" MacPart unreasonable pmMapBlkCnt value %ld",
|
||||
pmMapBlkCnt);
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* could test the rest -- might fix "imageOrder", might not -- but
|
||||
the format is pretty unambiguous, and we don't care about the order */
|
||||
|
||||
// success!
|
||||
LOGI(" MacPart partition map block count = %ld", pmMapBlkCnt);
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unpack a DDR disk block into a DDR data structure.
|
||||
*/
|
||||
/*static*/ void DiskFSMacPart::UnpackDDR(const uint8_t* buf,
|
||||
DriverDescriptorRecord* pDDR)
|
||||
{
|
||||
pDDR->sbSig = GetShortBE(&buf[0x00]);
|
||||
pDDR->sbBlkSize = GetShortBE(&buf[0x02]);
|
||||
pDDR->sbBlkCount = GetLongBE(&buf[0x04]);
|
||||
pDDR->sbDevType = GetShortBE(&buf[0x08]);
|
||||
pDDR->sbDevId = GetShortBE(&buf[0x0a]);
|
||||
pDDR->sbData = GetLongBE(&buf[0x0c]);
|
||||
pDDR->sbDrvrCount = GetShortBE(&buf[0x10]);
|
||||
pDDR->hiddenPad = GetShortBE(&buf[0x12]);
|
||||
pDDR->ddBlock = GetLongBE(&buf[0x14]);
|
||||
pDDR->ddSize = GetShortBE(&buf[0x18]);
|
||||
pDDR->ddType = GetShortBE(&buf[0x1a]);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < (int) NELEM(pDDR->ddPad); i++) {
|
||||
pDDR->ddPad[i] = GetShortBE(&buf[0x1c] + i * sizeof(pDDR->ddPad[0]));
|
||||
}
|
||||
assert(0x1c + i * sizeof(pDDR->ddPad[0]) == (unsigned int) kBlkSize);
|
||||
}
|
||||
|
||||
/*
|
||||
* Debug: dump the contents of the DDR.
|
||||
*/
|
||||
/*static*/ void DiskFSMacPart::DumpDDR(const DriverDescriptorRecord* pDDR)
|
||||
{
|
||||
LOGI(" MacPart driver descriptor record");
|
||||
LOGI(" sbSig=0x%04x sbBlkSize=%d sbBlkCount=%d",
|
||||
pDDR->sbSig, pDDR->sbBlkSize, pDDR->sbBlkCount);
|
||||
LOGI(" sbDevType=%d sbDevId=%d sbData=%d sbDrvrCount=%d",
|
||||
pDDR->sbDevType, pDDR->sbDevId, pDDR->sbData, pDDR->sbDrvrCount);
|
||||
LOGI(" (pad=%d) ddBlock=%d ddSize=%d ddType=%d",
|
||||
pDDR->hiddenPad, pDDR->ddBlock, pDDR->ddSize, pDDR->ddType);
|
||||
}
|
||||
|
||||
/*
|
||||
* Unpack a partition map disk block into a partition map data structure.
|
||||
*/
|
||||
/*static*/ void DiskFSMacPart::UnpackPartitionMap(const uint8_t* buf,
|
||||
PartitionMap* pMap)
|
||||
{
|
||||
pMap->pmSig = GetShortBE(&buf[0x00]);
|
||||
pMap->pmSigPad = GetShortBE(&buf[0x02]);
|
||||
pMap->pmMapBlkCnt = GetLongBE(&buf[0x04]);
|
||||
pMap->pmPyPartStart = GetLongBE(&buf[0x08]);
|
||||
pMap->pmPartBlkCnt = GetLongBE(&buf[0x0c]);
|
||||
memcpy(pMap->pmPartName, &buf[0x10], sizeof(pMap->pmPartName));
|
||||
pMap->pmPartName[sizeof(pMap->pmPartName)-1] = '\0';
|
||||
memcpy(pMap->pmParType, &buf[0x30], sizeof(pMap->pmParType));
|
||||
pMap->pmParType[sizeof(pMap->pmParType)-1] = '\0';
|
||||
pMap->pmLgDataStart = GetLongBE(&buf[0x50]);
|
||||
pMap->pmDataCnt = GetLongBE(&buf[0x54]);
|
||||
pMap->pmPartStatus = GetLongBE(&buf[0x58]);
|
||||
pMap->pmLgBootStart = GetLongBE(&buf[0x5c]);
|
||||
pMap->pmBootSize = GetLongBE(&buf[0x60]);
|
||||
pMap->pmBootAddr = GetLongBE(&buf[0x64]);
|
||||
pMap->pmBootAddr2 = GetLongBE(&buf[0x68]);
|
||||
pMap->pmBootEntry = GetLongBE(&buf[0x6c]);
|
||||
pMap->pmBootEntry2 = GetLongBE(&buf[0x70]);
|
||||
pMap->pmBootCksum = GetLongBE(&buf[0x74]);
|
||||
memcpy((char*) pMap->pmProcessor, &buf[0x78], sizeof(pMap->pmProcessor));
|
||||
pMap->pmProcessor[sizeof(pMap->pmProcessor)-1] = '\0';
|
||||
|
||||
int i;
|
||||
for (i = 0; i < (int) NELEM(pMap->pmPad); i++) {
|
||||
pMap->pmPad[i] = GetShortBE(&buf[0x88] + i * sizeof(pMap->pmPad[0]));
|
||||
}
|
||||
assert(0x88 + i * sizeof(pMap->pmPad[0]) == (unsigned int) kBlkSize);
|
||||
}
|
||||
|
||||
/*
|
||||
* Debug: dump the contents of the partition map.
|
||||
*/
|
||||
/*static*/ void DiskFSMacPart::DumpPartitionMap(long block, const PartitionMap* pMap)
|
||||
{
|
||||
LOGI(" MacPart partition map: block=%ld", block);
|
||||
LOGI(" pmSig=0x%04x (pad=0x%04x) pmMapBlkCnt=%d",
|
||||
pMap->pmSig, pMap->pmSigPad, pMap->pmMapBlkCnt);
|
||||
LOGI(" pmPartName='%s' pmParType='%s'",
|
||||
pMap->pmPartName, pMap->pmParType);
|
||||
LOGI(" pmPyPartStart=%d pmPartBlkCnt=%d",
|
||||
pMap->pmPyPartStart, pMap->pmPartBlkCnt);
|
||||
LOGI(" pmLgDataStart=%d pmDataCnt=%d",
|
||||
pMap->pmLgDataStart, pMap->pmDataCnt);
|
||||
LOGI(" pmPartStatus=%d",
|
||||
pMap->pmPartStatus);
|
||||
LOGI(" pmLgBootStart=%d pmBootSize=%d",
|
||||
pMap->pmLgBootStart, pMap->pmBootSize);
|
||||
LOGI(" pmBootAddr=%d pmBootAddr2=%d pmBootEntry=%d pmBootEntry2=%d",
|
||||
pMap->pmBootAddr, pMap->pmBootAddr2,
|
||||
pMap->pmBootEntry, pMap->pmBootEntry2);
|
||||
LOGI(" pmBootCksum=%d pmProcessor='%s'",
|
||||
pMap->pmBootCksum, pMap->pmProcessor);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Open up a sub-volume.
|
||||
*/
|
||||
DIError DiskFSMacPart::OpenSubVolume(const PartitionMap* pMap)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
DiskFS* pNewFS = NULL;
|
||||
DiskImg* pNewImg = NULL;
|
||||
long startBlock, numBlocks;
|
||||
bool tweaked = false;
|
||||
|
||||
assert(pMap != NULL);
|
||||
startBlock = pMap->pmPyPartStart;
|
||||
numBlocks = pMap->pmPartBlkCnt;
|
||||
|
||||
LOGI("Adding '%s' (%s) %ld +%ld",
|
||||
pMap->pmPartName, pMap->pmParType, startBlock, numBlocks);
|
||||
|
||||
if (startBlock > fpImg->GetNumBlocks()) {
|
||||
LOGI("MacPart start block out of range (%ld vs %ld)",
|
||||
startBlock, fpImg->GetNumBlocks());
|
||||
return kDIErrBadPartition;
|
||||
}
|
||||
if (startBlock + numBlocks > fpImg->GetNumBlocks()) {
|
||||
LOGI("MacPart partition too large (%ld vs %ld avail)",
|
||||
numBlocks, fpImg->GetNumBlocks() - startBlock);
|
||||
fpImg->AddNote(DiskImg::kNoteInfo,
|
||||
"Reduced partition '%s' (%s) from %ld blocks to %ld.\n",
|
||||
pMap->pmPartName, pMap->pmParType, numBlocks,
|
||||
fpImg->GetNumBlocks() - startBlock);
|
||||
numBlocks = fpImg->GetNumBlocks() - startBlock;
|
||||
tweaked = true;
|
||||
}
|
||||
|
||||
pNewImg = new DiskImg;
|
||||
if (pNewImg == NULL) {
|
||||
dierr = kDIErrMalloc;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/*
|
||||
* If "tweaked" is true, we want to make the volume read-only, so that the
|
||||
* volume copier doesn't stomp on it (on the off chance we've got it
|
||||
* wrong). However, that won't stop the volume copier from stomping on
|
||||
* the entire thing, so we really need to change *all* members of the
|
||||
* diskimg tree to be read-only. This seems counter-productive though.
|
||||
*
|
||||
* So far the only actual occurrence of tweakedness was from the first
|
||||
* Apple "develop" CD-ROM, which had a bad Apple_Extra partition on the
|
||||
* end.
|
||||
*/
|
||||
(void) tweaked;
|
||||
|
||||
dierr = pNewImg->OpenImage(fpImg, startBlock, numBlocks);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" MacPartSub: OpenImage(%ld,%ld) failed (err=%d)",
|
||||
startBlock, numBlocks, dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
//LOGI(" +++ CFFASub: new image has ro=%d (parent=%d)",
|
||||
// pNewImg->GetReadOnly(), pImg->GetReadOnly());
|
||||
|
||||
/* the partition is typed; currently no way to give hints to analyzer */
|
||||
dierr = pNewImg->AnalyzeImage();
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" MacPartSub: analysis failed (err=%d)", dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* we allow unrecognized partitions */
|
||||
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
|
||||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
|
||||
{
|
||||
LOGI(" MacPartSub (%ld,%ld): unable to identify filesystem",
|
||||
startBlock, numBlocks);
|
||||
DiskFSUnknown* pUnknownFS = new DiskFSUnknown;
|
||||
if (pUnknownFS == NULL) {
|
||||
dierr = kDIErrInternal;
|
||||
goto bail;
|
||||
}
|
||||
pUnknownFS->SetVolumeInfo((const char*)pMap->pmParType);
|
||||
pNewFS = pUnknownFS;
|
||||
pNewImg->AddNote(DiskImg::kNoteInfo, "Partition name='%s' type='%s'.",
|
||||
pMap->pmPartName, pMap->pmParType);
|
||||
} else {
|
||||
/* open a DiskFS for the sub-image */
|
||||
LOGI(" MacPartSub (%ld,%ld) analyze succeeded!", startBlock, numBlocks);
|
||||
pNewFS = pNewImg->OpenAppropriateDiskFS(true);
|
||||
if (pNewFS == NULL) {
|
||||
LOGI(" MacPartSub: OpenAppropriateDiskFS failed");
|
||||
dierr = kDIErrUnsupportedFSFmt;
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
|
||||
/* we encapsulate arbitrary stuff, so encourage child to scan */
|
||||
pNewFS->SetScanForSubVolumes(kScanSubEnabled);
|
||||
|
||||
/*
|
||||
* Load the files from the sub-image. When doing our initial tests,
|
||||
* or when loading data for the volume copier, we don't want to dig
|
||||
* into our sub-volumes, just figure out what they are and where.
|
||||
*
|
||||
* If "initialize" fails, the sub-volume won't get added to the list.
|
||||
* It's important that a failure at this stage doesn't cause the whole
|
||||
* thing to fall over.
|
||||
*/
|
||||
InitMode initMode;
|
||||
if (GetScanForSubVolumes() == kScanSubContainerOnly)
|
||||
initMode = kInitHeaderOnly;
|
||||
else
|
||||
initMode = kInitFull;
|
||||
dierr = pNewFS->Initialize(pNewImg, initMode);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" MacPartSub: error %d reading list of files from disk", dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* add it to the list */
|
||||
AddSubVolumeToList(pNewImg, pNewFS);
|
||||
pNewImg = NULL;
|
||||
pNewFS = NULL;
|
||||
|
||||
bail:
|
||||
delete pNewFS;
|
||||
delete pNewImg;
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check to see if this is a MacPart volume.
|
||||
*/
|
||||
/*static*/ DIError DiskFSMacPart::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
||||
DiskImg::FSFormat* pFormat, FSLeniency leniency)
|
||||
{
|
||||
if (pImg->GetNumBlocks() < kMinInterestingBlocks)
|
||||
return kDIErrFilesystemNotFound;
|
||||
if (pImg->GetIsEmbedded()) // don't look for partitions inside
|
||||
return kDIErrFilesystemNotFound;
|
||||
|
||||
/* assume ProDOS -- shouldn't matter, since it's embedded */
|
||||
if (TestImage(pImg, DiskImg::kSectorOrderProDOS) == kDIErrNone) {
|
||||
*pFormat = DiskImg::kFormatMacPart;
|
||||
*pOrder = DiskImg::kSectorOrderProDOS;
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
LOGI(" FS didn't find valid MacPart");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prep the MacPart "container" for use.
|
||||
*/
|
||||
DIError DiskFSMacPart::Initialize(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
|
||||
LOGI("MacPart initializing (scanForSub=%d)", fScanForSubVolumes);
|
||||
|
||||
/* seems pointless *not* to, but we just do what we're told */
|
||||
if (fScanForSubVolumes != kScanSubDisabled) {
|
||||
dierr = FindSubVolumes();
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/* blank out the volume usage map */
|
||||
SetVolumeUsageMap();
|
||||
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the various sub-volumes and open them.
|
||||
*
|
||||
* Because the partitions are explicitly typed, we don't need to probe
|
||||
* their contents. But we do anyway.
|
||||
*/
|
||||
DIError DiskFSMacPart::FindSubVolumes(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t buf[kBlkSize];
|
||||
PartitionMap map;
|
||||
int i, numMapBlocks;
|
||||
|
||||
dierr = fpImg->ReadBlock(kPartMapStart, buf);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
UnpackPartitionMap(buf, &map);
|
||||
numMapBlocks = map.pmMapBlkCnt;
|
||||
|
||||
for (i = 0; i < numMapBlocks; i++) {
|
||||
if (i != 0) {
|
||||
dierr = fpImg->ReadBlock(kPartMapStart+i, buf);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
UnpackPartitionMap(buf, &map);
|
||||
}
|
||||
DumpPartitionMap(kPartMapStart+i, &map);
|
||||
|
||||
dierr = OpenSubVolume(&map);
|
||||
if (dierr != kDIErrNone) {
|
||||
if (dierr == kDIErrCancelled)
|
||||
goto bail;
|
||||
DiskFS* pNewFS = NULL;
|
||||
DiskImg* pNewImg = NULL;
|
||||
LOGI(" MacPart failed opening sub-volume %d", i);
|
||||
dierr = CreatePlaceholder(map.pmPyPartStart, map.pmPartBlkCnt,
|
||||
(const char*)map.pmPartName, (const char*)map.pmParType,
|
||||
&pNewImg, &pNewFS);
|
||||
if (dierr == kDIErrNone) {
|
||||
AddSubVolumeToList(pNewImg, pNewFS);
|
||||
} else {
|
||||
LOGI(" MacPart unable to create placeholder (err=%d)",
|
||||
dierr);
|
||||
break; // something's wrong -- bail out with error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
49
diskimg/Makefile
Normal file
49
diskimg/Makefile
Normal file
@ -0,0 +1,49 @@
|
||||
#
|
||||
# DiskImg makefile for Linux.
|
||||
#
|
||||
SHELL = /bin/sh
|
||||
CC = gcc
|
||||
CXX = g++
|
||||
AR = ar
|
||||
OPT = -g -D_DEBUG
|
||||
#-DEXCISE_GPL_CODE
|
||||
#OPT = -g -O2
|
||||
GCC_FLAGS = -Wall -Wwrite-strings -Wpointer-arith -Wshadow
|
||||
# -Wstrict-prototypes
|
||||
CXXFLAGS = $(OPT) $(GCC_FLAGS) -D_FILE_OFFSET_BITS=64
|
||||
|
||||
SRCS = ASPI.cpp CFFA.cpp Container.cpp CPM.cpp DDD.cpp DiskFS.cpp \
|
||||
DiskImg.cpp DIUtil.cpp DOS33.cpp DOSImage.cpp FAT.cpp FDI.cpp \
|
||||
FocusDrive.cpp \GenericFD.cpp Global.cpp Gutenberg.cpp HFS.cpp \
|
||||
ImageWrapper.cpp MacPart.cpp MicroDrive.cpp Nibble.cpp \
|
||||
Nibble35.cpp OuterWrapper.cpp OzDOS.cpp Pascal.cpp ProDOS.cpp \
|
||||
RDOS.cpp TwoImg.cpp UNIDOS.cpp VolumeUsage.cpp Win32BlockIO.cpp
|
||||
OBJS = ASPI.o CFFA.o Container.o CPM.o DDD.o DiskFS.o \
|
||||
DiskImg.o DIUtil.o DOS33.o DOSImage.o FDI.o \
|
||||
FocusDrive.o FAT.o GenericFD.o Global.o Gutenberg.o HFS.o \
|
||||
ImageWrapper.o MacPart.o MicroDrive.o Nibble.o \
|
||||
Nibble35.o OuterWrapper.o OzDOS.o Pascal.o ProDOS.o \
|
||||
RDOS.o TwoImg.o UNIDOS.o VolumeUsage.o Win32BlockIO.o
|
||||
|
||||
STATIC_PRODUCT = libdiskimg.a
|
||||
PRODUCT = $(STATIC_PRODUCT)
|
||||
|
||||
all: $(PRODUCT)
|
||||
@true
|
||||
|
||||
$(STATIC_PRODUCT): $(OBJS)
|
||||
-rm -f $(STATIC_PRODUCT)
|
||||
$(AR) rcv $@ $(OBJS)
|
||||
|
||||
clean:
|
||||
-rm -f *.o core
|
||||
-rm -f $(STATIC_PRODUCT)
|
||||
-rm -f Makefile.bak
|
||||
|
||||
tags::
|
||||
@ctags -R --totals *
|
||||
|
||||
depend:
|
||||
makedepend -- $(CFLAGS) -- $(SRCS)
|
||||
|
||||
# DO NOT DELETE THIS LINE -- make depend depends on it.
|
397
diskimg/MicroDrive.cpp
Normal file
397
diskimg/MicroDrive.cpp
Normal file
@ -0,0 +1,397 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* The "MicroDrive" DiskFS is a container class for multiple ProDOS and HFS
|
||||
* volumes. It represents a partitioned disk device, such as a hard
|
||||
* drive or CF card, that has been formatted for use with ///SHH Systeme's
|
||||
* MicroDrive card for the Apple II.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
|
||||
const int kBlkSize = 512;
|
||||
const int kPartMapBlock = 0; // partition map lives here
|
||||
const uint32_t kPartSizeMask = 0x00ffffff;
|
||||
|
||||
|
||||
/*
|
||||
* Format of partition map. It resides in the first 256 bytes of block 0.
|
||||
* All values are in little-endian order.
|
||||
*
|
||||
* The layout was discovered through reverse-engineering. Additional notes:
|
||||
*
|
||||
From Joachim Lange:
|
||||
|
||||
Below, this is the configuration block as it is used in all
|
||||
MicroDrive cards. Please verify that my ID shortcut can be
|
||||
found at offset 0, otherwise the partition info is not
|
||||
valid. Most of the other parms are not useful, some are
|
||||
historic and not useful anymore. As a second security
|
||||
measure, verify that the first partition starts at
|
||||
absolute block 256. This is also a fixed value used in all
|
||||
MicroDrive cards. Of course the partition size is not two
|
||||
bytes long but three (not four), the 4th byte is used for
|
||||
switching drives in a two-drive configuration. So, for
|
||||
completeness, when reading partition sizes, perform a
|
||||
partitionLength[..] & 0x00FFFFFF, or at least issue a
|
||||
warning that something may be wrong. The offset
|
||||
(partitionStart) could reach into the 4th byte.
|
||||
I have attached the config block in a zip file because
|
||||
the mailer would probably re-format the source text.
|
||||
*/
|
||||
const int kMaxNumParts = 8;
|
||||
typedef struct DiskFSMicroDrive::PartitionMap {
|
||||
uint16_t magic; // partition signature
|
||||
uint16_t cylinders; // #of cylinders
|
||||
uint16_t reserved1; // ??
|
||||
uint16_t heads; // #of heads/cylinder
|
||||
uint16_t sectors; // #of sectors/track
|
||||
uint16_t reserved2; // ??
|
||||
uint8_t numPart1; // #of partitions in first chunk
|
||||
uint8_t numPart2; // #of partitions in second chunk
|
||||
uint8_t reserved3[10]; // bytes 0x0e-0x17
|
||||
uint16_t romVersion; // IIgs ROM01 or ROM03
|
||||
uint8_t reserved4[6]; // bytes 0x1a-0x1f
|
||||
uint32_t partitionStart1[kMaxNumParts]; // bytes 0x20-0x3f
|
||||
uint32_t partitionLength1[kMaxNumParts]; // bytes 0x40-0x5f
|
||||
uint8_t reserved5[32]; // bytes 0x60-0x7f
|
||||
uint32_t partitionStart2[kMaxNumParts]; // bytes 0x80-0x9f
|
||||
uint32_t partitionLength2[kMaxNumParts]; // bytes 0xa0-0xbf
|
||||
|
||||
uint8_t padding[320];
|
||||
} PartitionMap;
|
||||
|
||||
|
||||
/*
|
||||
* Figure out if this is a MicroDrive partition.
|
||||
*
|
||||
* The "imageOrder" parameter has no use here, because (in the current
|
||||
* version) embedded parent volumes are implicitly ProDOS-ordered.
|
||||
*/
|
||||
/*static*/ DIError DiskFSMicroDrive::TestImage(DiskImg* pImg,
|
||||
DiskImg::SectorOrder imageOrder)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t blkBuf[kBlkSize];
|
||||
int partCount1, partCount2;
|
||||
|
||||
assert(sizeof(PartitionMap) == kBlkSize);
|
||||
|
||||
/*
|
||||
* See if block 0 is a MicroDrive partition map.
|
||||
*/
|
||||
dierr = pImg->ReadBlockSwapped(kPartMapBlock, blkBuf, imageOrder,
|
||||
DiskImg::kSectorOrderProDOS);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
if (GetShortLE(&blkBuf[0x00]) != kPartitionSignature) {
|
||||
LOGI(" MicroDrive partition signature not found in first part block");
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
goto bail;
|
||||
}
|
||||
/* might assert that partCount2 be zero unless partCount1 == 8? */
|
||||
partCount1 = blkBuf[0x0c];
|
||||
partCount2 = blkBuf[0x0d];
|
||||
if (partCount1 == 0 || partCount1 > kMaxNumParts ||
|
||||
partCount2 > kMaxNumParts)
|
||||
{
|
||||
LOGI(" MicroDrive unreasonable partCount values %d/%d",
|
||||
partCount1, partCount2);
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* consider testing other fields */
|
||||
|
||||
// success!
|
||||
LOGI(" MicroDrive partition map count = %d/%d", partCount1, partCount2);
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Unpack a partition map block into a partition map data structure.
|
||||
*/
|
||||
/*static*/ void DiskFSMicroDrive::UnpackPartitionMap(const uint8_t* buf,
|
||||
PartitionMap* pMap)
|
||||
{
|
||||
pMap->magic = GetShortLE(&buf[0x00]);
|
||||
pMap->cylinders = GetShortLE(&buf[0x02]);
|
||||
pMap->reserved1 = GetShortLE(&buf[0x04]);
|
||||
pMap->heads = GetShortLE(&buf[0x06]);
|
||||
pMap->sectors = GetShortLE(&buf[0x08]);
|
||||
pMap->reserved2 = GetShortLE(&buf[0x0a]);
|
||||
pMap->numPart1 = buf[0x0c];
|
||||
pMap->numPart2 = buf[0x0d];
|
||||
memcpy(pMap->reserved3, &buf[0x0e], sizeof(pMap->reserved3));
|
||||
pMap->romVersion = GetShortLE(&buf[0x18]);
|
||||
memcpy(pMap->reserved4, &buf[0x1a], sizeof(pMap->reserved4));
|
||||
|
||||
for (int i = 0; i < kMaxNumParts; i++) {
|
||||
pMap->partitionStart1[i] = GetLongLE(&buf[0x20] + i * 4);
|
||||
pMap->partitionLength1[i] = GetLongLE(&buf[0x40] + i * 4) & kPartSizeMask;
|
||||
pMap->partitionStart2[i] = GetLongLE(&buf[0x80] + i * 4);
|
||||
pMap->partitionLength2[i] = GetLongLE(&buf[0xa0] + i * 4) & kPartSizeMask;
|
||||
}
|
||||
memcpy(pMap->reserved5, &buf[0x60], sizeof(pMap->reserved5));
|
||||
memcpy(pMap->padding, &buf[0x80], sizeof(pMap->padding));
|
||||
}
|
||||
|
||||
/*
|
||||
* Debug: dump the contents of the partition map.
|
||||
*/
|
||||
/*static*/ void DiskFSMicroDrive::DumpPartitionMap(const PartitionMap* pMap)
|
||||
{
|
||||
LOGI(" MicroDrive partition map:");
|
||||
LOGI(" cyls=%d res1=%d heads=%d sects=%d",
|
||||
pMap->cylinders, pMap->reserved1, pMap->heads, pMap->sectors);
|
||||
LOGI(" res2=%d numPart1=%d numPart2=%d",
|
||||
pMap->reserved2, pMap->numPart1, pMap->numPart2);
|
||||
LOGI(" romVersion=ROM%02d", pMap->romVersion);
|
||||
|
||||
int i, parts;
|
||||
|
||||
parts = pMap->numPart1;
|
||||
assert(parts <= kMaxNumParts);
|
||||
for (i = 0; i < parts; i++) {
|
||||
LOGI(" %2d: startLBA=%8d length=%d",
|
||||
i, pMap->partitionStart1[i], pMap->partitionLength1[i]);
|
||||
}
|
||||
parts = pMap->numPart2;
|
||||
assert(parts <= kMaxNumParts);
|
||||
for (i = 0; i < parts; i++) {
|
||||
LOGI(" %2d: startLBA=%8d length=%d",
|
||||
i+8, pMap->partitionStart2[i], pMap->partitionLength2[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Open up a sub-volume.
|
||||
*/
|
||||
DIError DiskFSMicroDrive::OpenSubVolume(long startBlock, long numBlocks)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
DiskFS* pNewFS = NULL;
|
||||
DiskImg* pNewImg = NULL;
|
||||
//bool tweaked = false;
|
||||
|
||||
LOGI("Adding %ld +%ld", startBlock, numBlocks);
|
||||
|
||||
if (startBlock > fpImg->GetNumBlocks()) {
|
||||
LOGI("MicroDrive start block out of range (%ld vs %ld)",
|
||||
startBlock, fpImg->GetNumBlocks());
|
||||
return kDIErrBadPartition;
|
||||
}
|
||||
if (startBlock + numBlocks > fpImg->GetNumBlocks()) {
|
||||
LOGI("MicroDrive partition too large (%ld vs %ld avail)",
|
||||
numBlocks, fpImg->GetNumBlocks() - startBlock);
|
||||
fpImg->AddNote(DiskImg::kNoteInfo,
|
||||
"Reduced partition from %ld blocks to %ld.\n",
|
||||
numBlocks, fpImg->GetNumBlocks() - startBlock);
|
||||
numBlocks = fpImg->GetNumBlocks() - startBlock;
|
||||
//tweaked = true;
|
||||
}
|
||||
|
||||
pNewImg = new DiskImg;
|
||||
if (pNewImg == NULL) {
|
||||
dierr = kDIErrMalloc;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
dierr = pNewImg->OpenImage(fpImg, startBlock, numBlocks);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" MicroDriveSub: OpenImage(%ld,%ld) failed (err=%d)",
|
||||
startBlock, numBlocks, dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
//LOGI(" +++ CFFASub: new image has ro=%d (parent=%d)",
|
||||
// pNewImg->GetReadOnly(), pImg->GetReadOnly());
|
||||
|
||||
/* figure out what the format is */
|
||||
dierr = pNewImg->AnalyzeImage();
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" MicroDriveSub: analysis failed (err=%d)", dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* we allow unrecognized partitions */
|
||||
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
|
||||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
|
||||
{
|
||||
LOGI(" MicroDriveSub (%ld,%ld): unable to identify filesystem",
|
||||
startBlock, numBlocks);
|
||||
DiskFSUnknown* pUnknownFS = new DiskFSUnknown;
|
||||
if (pUnknownFS == NULL) {
|
||||
dierr = kDIErrInternal;
|
||||
goto bail;
|
||||
}
|
||||
//pUnknownFS->SetVolumeInfo((const char*)pMap->pmParType);
|
||||
pNewFS = pUnknownFS;
|
||||
//pNewImg->AddNote(DiskImg::kNoteInfo, "Partition name='%s' type='%s'.",
|
||||
// pMap->pmPartName, pMap->pmParType);
|
||||
} else {
|
||||
/* open a DiskFS for the sub-image */
|
||||
LOGI(" MicroDriveSub (%ld,%ld) analyze succeeded!", startBlock, numBlocks);
|
||||
pNewFS = pNewImg->OpenAppropriateDiskFS(true);
|
||||
if (pNewFS == NULL) {
|
||||
LOGI(" MicroDriveSub: OpenAppropriateDiskFS failed");
|
||||
dierr = kDIErrUnsupportedFSFmt;
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
|
||||
/* we encapsulate arbitrary stuff, so encourage child to scan */
|
||||
pNewFS->SetScanForSubVolumes(kScanSubEnabled);
|
||||
|
||||
/*
|
||||
* Load the files from the sub-image. When doing our initial tests,
|
||||
* or when loading data for the volume copier, we don't want to dig
|
||||
* into our sub-volumes, just figure out what they are and where.
|
||||
*
|
||||
* If "initialize" fails, the sub-volume won't get added to the list.
|
||||
* It's important that a failure at this stage doesn't cause the whole
|
||||
* thing to fall over.
|
||||
*/
|
||||
InitMode initMode;
|
||||
if (GetScanForSubVolumes() == kScanSubContainerOnly)
|
||||
initMode = kInitHeaderOnly;
|
||||
else
|
||||
initMode = kInitFull;
|
||||
dierr = pNewFS->Initialize(pNewImg, initMode);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" MicroDriveSub: error %d reading list of files from disk", dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* add it to the list */
|
||||
AddSubVolumeToList(pNewImg, pNewFS);
|
||||
pNewImg = NULL;
|
||||
pNewFS = NULL;
|
||||
|
||||
bail:
|
||||
delete pNewFS;
|
||||
delete pNewImg;
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check to see if this is a MicroDrive volume.
|
||||
*/
|
||||
/*static*/ DIError DiskFSMicroDrive::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
||||
DiskImg::FSFormat* pFormat, FSLeniency leniency)
|
||||
{
|
||||
if (pImg->GetNumBlocks() < kMinInterestingBlocks)
|
||||
return kDIErrFilesystemNotFound;
|
||||
if (pImg->GetIsEmbedded()) // don't look for partitions inside
|
||||
return kDIErrFilesystemNotFound;
|
||||
|
||||
/* assume ProDOS -- shouldn't matter, since it's embedded */
|
||||
if (TestImage(pImg, DiskImg::kSectorOrderProDOS) == kDIErrNone) {
|
||||
*pFormat = DiskImg::kFormatMicroDrive;
|
||||
*pOrder = DiskImg::kSectorOrderProDOS;
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
LOGI(" FS didn't find valid MicroDrive");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Prep the MicroDrive "container" for use.
|
||||
*/
|
||||
DIError DiskFSMicroDrive::Initialize(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
|
||||
LOGI("MicroDrive initializing (scanForSub=%d)", fScanForSubVolumes);
|
||||
|
||||
/* seems pointless *not* to, but we just do what we're told */
|
||||
if (fScanForSubVolumes != kScanSubDisabled) {
|
||||
dierr = FindSubVolumes();
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/* blank out the volume usage map */
|
||||
SetVolumeUsageMap();
|
||||
|
||||
return dierr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Find the various sub-volumes and open them.
|
||||
*/
|
||||
DIError DiskFSMicroDrive::FindSubVolumes(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t buf[kBlkSize];
|
||||
PartitionMap map;
|
||||
int i;
|
||||
|
||||
dierr = fpImg->ReadBlock(kPartMapBlock, buf);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
UnpackPartitionMap(buf, &map);
|
||||
DumpPartitionMap(&map);
|
||||
|
||||
/* first part of the table */
|
||||
for (i = 0; i < map.numPart1; i++) {
|
||||
dierr = OpenVol(i,
|
||||
map.partitionStart1[i], map.partitionLength1[i]);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* second part of the table */
|
||||
for (i = 0; i < map.numPart2; i++) {
|
||||
dierr = OpenVol(i + kMaxNumParts,
|
||||
map.partitionStart2[i], map.partitionLength2[i]);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
}
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Open the volume. If it fails, open a placeholder instead. (If *that*
|
||||
* fails, return with an error.)
|
||||
*/
|
||||
DIError DiskFSMicroDrive::OpenVol(int idx, long startBlock, long numBlocks)
|
||||
{
|
||||
DIError dierr;
|
||||
|
||||
dierr = OpenSubVolume(startBlock, numBlocks);
|
||||
if (dierr != kDIErrNone) {
|
||||
if (dierr == kDIErrCancelled)
|
||||
goto bail;
|
||||
DiskFS* pNewFS = NULL;
|
||||
DiskImg* pNewImg = NULL;
|
||||
|
||||
LOGW(" MicroDrive failed opening sub-volume %d", idx);
|
||||
dierr = CreatePlaceholder(startBlock, numBlocks, NULL, NULL,
|
||||
&pNewImg, &pNewFS);
|
||||
if (dierr == kDIErrNone) {
|
||||
AddSubVolumeToList(pNewImg, pNewFS);
|
||||
} else {
|
||||
LOGE(" MicroDrive unable to create placeholder (err=%d)",
|
||||
dierr);
|
||||
// fall out with error
|
||||
}
|
||||
}
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
1021
diskimg/Nibble.cpp
Normal file
1021
diskimg/Nibble.cpp
Normal file
File diff suppressed because it is too large
Load Diff
550
diskimg/Nibble35.cpp
Normal file
550
diskimg/Nibble35.cpp
Normal file
@ -0,0 +1,550 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* GCR nibble image support for 3.5" disks.
|
||||
*
|
||||
* Each track has between 8 and 12 512-byte sectors. The encoding is similar
|
||||
* to but different from that used on 5.25" disks.
|
||||
*
|
||||
* THOUGHT: this is currently designed for unpacking all blocks from a track.
|
||||
* We really ought to allow the user to view the track in nibble form, which
|
||||
* means reworking the interface to be more like the 5.25" nibble stuff. We
|
||||
* should present it as a block interface rather than track/sector; the code
|
||||
* here can convert the block # to track/sector, and just provide a raw
|
||||
* interface for the nibble track viewer.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
/*
|
||||
Physical sector layout:
|
||||
|
||||
+00 self-sync 0xff pattern (36 10-bit bytes, or 45 8-bit bytes)
|
||||
+36 addr prolog (0xd5 0xaa 0x96)
|
||||
+39 6&2enc track number (0-79 mod 63)
|
||||
+40 6&2enc sector number (0-N)
|
||||
+41 6&2enc side (0x00 or 0x20, ORed with 0x01 for tracks >= 64)
|
||||
+42 6&2enc format (0x22, 0x24, others?)
|
||||
+43 6&2enc checksum (track ^ sector ^ side ^ format)
|
||||
+44 addr epilog (0xde 0xaa)
|
||||
+46 self-sync 0xff (6 10-bit bytes)
|
||||
+52 data prolog (0xd5 0xaa 0xad)
|
||||
+55 6&2enc sector number (another copy)
|
||||
+56 6&2enc nibblized data (699 bytes)
|
||||
+755 checksum, 3 bytes 6&2 encoded as 4 bytes
|
||||
+759 data epilog (0xde 0xaa)
|
||||
+761 0xff (pad byte)
|
||||
|
||||
Some sources say it starts with 42 10-bit self-sync bytes instead of 36.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Basic disk geometry.
|
||||
*/
|
||||
const int kCylindersPerDisk = 80;
|
||||
const int kHeadsPerCylinder = 2;
|
||||
const int kMaxSectorsPerTrack = 12;
|
||||
const int kSectorSize35 = 524; // 512 data bytes + 12 tag bytes
|
||||
const int kTagBytesLen = 12;
|
||||
const int kDataChecksumLen = 3;
|
||||
const int kChunkSize35 = 175; // ceil(524 / 3)
|
||||
const int kOffsetToChecksum = 699;
|
||||
const int kNibblizedOutputLen = (kOffsetToChecksum + 4);
|
||||
const int kMaxDataReach = 48; // should only be 6 bytes */
|
||||
|
||||
enum {
|
||||
kAddrProlog0 = 0xd5,
|
||||
kAddrProlog1 = 0xaa,
|
||||
kAddrProlog2 = 0x96,
|
||||
kAddrEpilog0 = 0xde,
|
||||
kAddrEpilog1 = 0xaa,
|
||||
|
||||
kDataProlog0 = 0xd5,
|
||||
kDataProlog1 = 0xaa,
|
||||
kDataProlog2 = 0xad,
|
||||
kDataEpilog0 = 0xde,
|
||||
kDataEpilog1 = 0xaa,
|
||||
};
|
||||
|
||||
/*
|
||||
* There are 12 sectors per track for the first 16 cylinders, 11 sectors
|
||||
* per track for the next 16, and so on until we're down to 8 per track.
|
||||
*/
|
||||
/*static*/ int DiskImg::SectorsPerTrack35(int cylinder)
|
||||
{
|
||||
return kMaxSectorsPerTrack - (cylinder / 16);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert cylinder/head/sector to a block number on a 3.5" disk.
|
||||
*/
|
||||
/*static*/ int DiskImg::CylHeadSect35ToBlock(int cyl, int head, int sect)
|
||||
{
|
||||
int i, block;
|
||||
|
||||
assert(cyl >= 0 && cyl < kCylindersPerDisk);
|
||||
assert(head >= 0 && head < kHeadsPerCylinder);
|
||||
assert(sect >= 0 && sect < SectorsPerTrack35(cyl));
|
||||
|
||||
block = 0;
|
||||
for (i = 0; i < cyl; i++)
|
||||
block += SectorsPerTrack35(i) * kHeadsPerCylinder;
|
||||
if (head)
|
||||
block += SectorsPerTrack35(i);
|
||||
block += sect;
|
||||
|
||||
//LOGI("Nib35: c/h/s %d/%d/%d --> block %d", cyl, head, sect, block);
|
||||
assert(block >= 0 && block < 1600);
|
||||
return block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unpack a nibble track.
|
||||
*
|
||||
* "outputBuf" must be able to hold 512 * 12 sectors of decoded sector data.
|
||||
*/
|
||||
/*static*/ DIError DiskImg::UnpackNibbleTrack35(const uint8_t* nibbleBuf,
|
||||
long nibbleLen, uint8_t* outputBuf, int cyl, int head,
|
||||
LinearBitmap* pBadBlockMap)
|
||||
{
|
||||
CircularBufferAccess buffer(nibbleBuf, nibbleLen);
|
||||
bool foundSector[kMaxSectorsPerTrack];
|
||||
uint8_t sectorBuf[kSectorSize35];
|
||||
uint8_t readSum[kDataChecksumLen];
|
||||
uint8_t calcSum[kDataChecksumLen];
|
||||
int i;
|
||||
|
||||
memset(&foundSector, 0, sizeof(foundSector));
|
||||
|
||||
i = 0;
|
||||
while (i < nibbleLen) {
|
||||
int sector;
|
||||
|
||||
i = FindNextSector35(buffer, i, cyl, head, §or);
|
||||
if (i < 0)
|
||||
break;
|
||||
|
||||
assert(sector >= 0 && sector < SectorsPerTrack35(cyl));
|
||||
if (foundSector[sector]) {
|
||||
LOGI("Nib35: WARNING: found two copies of sect %d on cyl=%d head=%d",
|
||||
sector, cyl, head);
|
||||
} else {
|
||||
memset(sectorBuf, 0xa9, sizeof(sectorBuf));
|
||||
if (DecodeNibbleSector35(buffer, i, sectorBuf, readSum, calcSum))
|
||||
{
|
||||
/* successfully decoded sector, copy data & verify checksum */
|
||||
foundSector[sector] = true;
|
||||
memcpy(outputBuf + kBlockSize * sector,
|
||||
sectorBuf + kTagBytesLen, kBlockSize);
|
||||
|
||||
if (calcSum[0] != readSum[0] ||
|
||||
calcSum[1] != readSum[1] ||
|
||||
calcSum[2] != readSum[2])
|
||||
{
|
||||
LOGI("Nib35: checksum mismatch: 0x%06x vs. 0x%06x",
|
||||
calcSum[0] << 16 | calcSum[1] << 8 | calcSum[2],
|
||||
readSum[0] << 16 | readSum[1] << 8 | readSum[2]);
|
||||
LOGI("Nib35: marking cyl=%d head=%d sect=%d (block=%d)",
|
||||
cyl, head, sector,
|
||||
CylHeadSect35ToBlock(cyl, head, sector));
|
||||
pBadBlockMap->Set(CylHeadSect35ToBlock(cyl, head, sector));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check to see if we have all our parts. Anything missing sets
|
||||
* a flag in the "bad block" map.
|
||||
*/
|
||||
for (i = SectorsPerTrack35(cyl)-1; i >= 0; i--) {
|
||||
if (!foundSector[i]) {
|
||||
LOGI("Nib35: didn't find cyl=%d head=%d sect=%d (block=%d)",
|
||||
cyl, head, i, CylHeadSect35ToBlock(cyl, head, i));
|
||||
pBadBlockMap->Set(CylHeadSect35ToBlock(cyl, head, i));
|
||||
}
|
||||
|
||||
/*
|
||||
// DEBUG test
|
||||
if ((cyl == 0 || cyl == 12 || cyl == 79) &&
|
||||
(head == (cyl & 0x01)) &&
|
||||
(i == 1 || i == 7))
|
||||
{
|
||||
LOGI("DEBUG: setting bad %d/%d/%d (%d)",
|
||||
cyl, head, i, CylHeadSect35ToBlock(cyl, head, i));
|
||||
pBadBlockMap->Set(CylHeadSect35ToBlock(cyl, head, i));
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
return kDIErrNone; // maybe return an error if nothing found?
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the offset of the next sector, or -1 if we went off the end.
|
||||
*/
|
||||
/*static*/ int DiskImg::FindNextSector35(const CircularBufferAccess& buffer,
|
||||
int start, int cyl, int head, int* pSector)
|
||||
{
|
||||
int end = buffer.GetSize();
|
||||
int i;
|
||||
|
||||
for (i = start; i < end; i++) {
|
||||
bool foundAddr = false;
|
||||
|
||||
if (buffer[i] == kAddrProlog0 &&
|
||||
buffer[i+1] == kAddrProlog1 &&
|
||||
buffer[i+2] == kAddrProlog2)
|
||||
{
|
||||
foundAddr = true;
|
||||
}
|
||||
|
||||
if (foundAddr) {
|
||||
/* decode the address field */
|
||||
int trackNum, sectNum, side, format, checksum;
|
||||
|
||||
trackNum = kInvDiskBytes62[buffer[i+3]];
|
||||
sectNum = kInvDiskBytes62[buffer[i+4]];
|
||||
side = kInvDiskBytes62[buffer[i+5]];
|
||||
format = kInvDiskBytes62[buffer[i+6]];
|
||||
checksum = kInvDiskBytes62[buffer[i+7]];
|
||||
if (trackNum == kInvInvalidValue ||
|
||||
sectNum == kInvInvalidValue ||
|
||||
side == kInvInvalidValue ||
|
||||
format == kInvInvalidValue ||
|
||||
checksum == kInvInvalidValue)
|
||||
{
|
||||
LOGI("Nib35: garbled address header found");
|
||||
continue;
|
||||
}
|
||||
//LOGI(" Nib35: got addr: track=%2d sect=%2d side=%d format=%d sum=0x%02x",
|
||||
// trackNum, sectNum, side, format, checksum);
|
||||
if (side != ((head * 0x20) | (cyl >> 6))) {
|
||||
LOGI("Nib35: unexpected value for side: %d on cyl=%d head=%d",
|
||||
side, cyl, head);
|
||||
}
|
||||
if (sectNum >= SectorsPerTrack35(cyl)) {
|
||||
LOGI("Nib35: invalid value for sector: %d (cyl=%d)",
|
||||
sectNum, cyl);
|
||||
continue;
|
||||
}
|
||||
/* format seems to be 0x22 or 0x24 */
|
||||
if (checksum != (trackNum ^ sectNum ^ side ^ format)) {
|
||||
LOGI("Nib35: unexpected checksum: 0x%02x vs. 0x%02x",
|
||||
checksum, trackNum ^ sectNum ^ side ^ format);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* check the epilog bytes */
|
||||
if (buffer[i+8] != kAddrEpilog0 ||
|
||||
buffer[i+9] != kAddrEpilog1)
|
||||
{
|
||||
LOGI("Nib35: invalid address epilog");
|
||||
/* maybe we allow this anyway? */
|
||||
}
|
||||
|
||||
*pSector = sectNum;
|
||||
return i+10; // move past address field
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unpack a 524-byte sector from a 3.5" disk. Start with "start" pointed
|
||||
* in the general vicinity of the data prolog bytes.
|
||||
*
|
||||
* "sectorBuf" must hold at least kSectorSize35 bytes. It will be filled
|
||||
* with the decoded data.
|
||||
* "readChecksum" and "calcChecksum" must each hold at least kDataChecksumLen
|
||||
* bytes. The former holds the checksum read from the sector, the latter
|
||||
* holds the checksum computed from the data.
|
||||
*
|
||||
* The 4 to 3 conversion is pretty straightforward. The checksum is
|
||||
* a little crazy.
|
||||
*
|
||||
* Returns "true" if all goes well, "false" if there is a problem. Does
|
||||
* not return false on a checksum mismatch -- it's up to the caller to
|
||||
* verify the checksum if desired.
|
||||
*/
|
||||
/*static*/ bool DiskImg::DecodeNibbleSector35(const CircularBufferAccess& buffer,
|
||||
int start, uint8_t* sectorBuf, uint8_t* readChecksum,
|
||||
uint8_t* calcChecksum)
|
||||
{
|
||||
const int kMaxDataReach35 = 48; // fairly arbitrary
|
||||
uint8_t* sectorBufStart = sectorBuf;
|
||||
uint8_t part0[kChunkSize35], part1[kChunkSize35], part2[kChunkSize35];
|
||||
unsigned int chk0, chk1, chk2;
|
||||
uint8_t val, nib0, nib1, nib2, twos;
|
||||
int i, off;
|
||||
|
||||
/*
|
||||
* Find the start of the actual data. Adjust "start" to point at it.
|
||||
*/
|
||||
for (off = start; off < start + kMaxDataReach35; off++) {
|
||||
if (buffer[off] == kDataProlog0 &&
|
||||
buffer[off+1] == kDataProlog1 &&
|
||||
buffer[off+2] == kDataProlog2)
|
||||
{
|
||||
start = off + 4; // 3 prolog bytes + sector number
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (off == start + kMaxDataReach35) {
|
||||
LOGI("nib25: could not find start of data field");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Assemble 8-bit bytes from 6&2 encoded values.
|
||||
*/
|
||||
off = start;
|
||||
for (i = 0; i < kChunkSize35; i++) {
|
||||
twos = kInvDiskBytes62[buffer[off++]];
|
||||
nib0 = kInvDiskBytes62[buffer[off++]];
|
||||
nib1 = kInvDiskBytes62[buffer[off++]];
|
||||
if (i != kChunkSize35-1)
|
||||
nib2 = kInvDiskBytes62[buffer[off++]];
|
||||
else
|
||||
nib2 = 0;
|
||||
|
||||
if (twos == kInvInvalidValue ||
|
||||
nib0 == kInvInvalidValue ||
|
||||
nib1 == kInvInvalidValue ||
|
||||
nib2 == kInvInvalidValue)
|
||||
{
|
||||
// junk found
|
||||
LOGI("Nib25: found invalid disk byte in sector data at %d",
|
||||
off - start);
|
||||
LOGI(" (one of 0x%02x 0x%02x 0x%02x 0x%02x)",
|
||||
buffer[off-4], buffer[off-3], buffer[off-2], buffer[off-1]);
|
||||
return false;
|
||||
//if (twos == kInvInvalidValue)
|
||||
// twos = 0;
|
||||
//if (nib0 == kInvInvalidValue)
|
||||
// nib0 = 0;
|
||||
//if (nib1 == kInvInvalidValue)
|
||||
// nib1 = 0;
|
||||
//if (nib2 == kInvInvalidValue)
|
||||
// nib2 = 0;
|
||||
}
|
||||
|
||||
part0[i] = nib0 | ((twos << 2) & 0xc0);
|
||||
part1[i] = nib1 | ((twos << 4) & 0xc0);
|
||||
part2[i] = nib2 | ((twos << 6) & 0xc0);
|
||||
}
|
||||
assert(off == start + kOffsetToChecksum);
|
||||
|
||||
chk0 = chk1 = chk2 = 0;
|
||||
i = 0;
|
||||
while (true) {
|
||||
chk0 = (chk0 & 0xff) << 1;
|
||||
if (chk0 & 0x0100)
|
||||
chk0++;
|
||||
|
||||
val = part0[i] ^ chk0;
|
||||
chk2 += val;
|
||||
if (chk0 & 0x0100) {
|
||||
chk2++;
|
||||
chk0 &= 0xff;
|
||||
}
|
||||
*sectorBuf++ = val;
|
||||
|
||||
val = part1[i] ^ chk2;
|
||||
chk1 += val;
|
||||
if (chk2 > 0xff) {
|
||||
chk1++;
|
||||
chk2 &= 0xff;
|
||||
}
|
||||
*sectorBuf++ = val;
|
||||
|
||||
if (sectorBuf - sectorBufStart == 524)
|
||||
break;
|
||||
|
||||
val = part2[i] ^ chk1;
|
||||
chk0 += val;
|
||||
if (chk1 > 0xff) {
|
||||
chk0++;
|
||||
chk1 &= 0xff;
|
||||
}
|
||||
*sectorBuf++ = val;
|
||||
|
||||
i++;
|
||||
assert(i < kChunkSize35);
|
||||
//LOGI("i = %d, diff=%d", i, sectorBuf - sectorBufStart);
|
||||
}
|
||||
|
||||
calcChecksum[0] = chk0;
|
||||
calcChecksum[1] = chk1;
|
||||
calcChecksum[2] = chk2;
|
||||
|
||||
if (!UnpackChecksum35(buffer, off, readChecksum)) {
|
||||
LOGI("Nib35: failure reading checksum");
|
||||
readChecksum[0] = calcChecksum[0] ^ 0xff; // force a failure
|
||||
return false;
|
||||
}
|
||||
off += 4; // skip past checksum bytes
|
||||
|
||||
if (buffer[off] != kDataEpilog0 || buffer[off+1] != kDataEpilog1) {
|
||||
LOGI("nib25: WARNING: data epilog not found");
|
||||
// allow it, if the checksum matches
|
||||
}
|
||||
|
||||
//#define TEST_ENC_35
|
||||
#ifdef TEST_ENC_35
|
||||
{
|
||||
uint8_t nibBuf[kNibblizedOutputLen];
|
||||
memset(nibBuf, 0xcc, sizeof(nibBuf));
|
||||
|
||||
/* encode what we just decoded */
|
||||
EncodeNibbleSector35(sectorBufStart, nibBuf);
|
||||
/* compare it to the original */
|
||||
for (i = 0; i < kNibblizedOutputLen; i++) {
|
||||
if (buffer[start + i] != nibBuf[i]) {
|
||||
/*
|
||||
* The very last "twos" entry may have undefined bits when
|
||||
* written by a real drive. Peel it apart and ignore the
|
||||
* two flaky bits.
|
||||
*/
|
||||
if (i == 696) {
|
||||
uint8_t val1, val2;
|
||||
val1 = kInvDiskBytes62[buffer[start + i]];
|
||||
val2 = kInvDiskBytes62[nibBuf[i]];
|
||||
if ((val1 & 0xfc) != (val2 & 0xfc)) {
|
||||
LOGI("Nib35 DEBUG: output differs at byte %d"
|
||||
" (0x%02x vs 0x%02x / 0x%02x vs 0x%02x)",
|
||||
i, buffer[start+i], nibBuf[i], val1, val2);
|
||||
}
|
||||
} else {
|
||||
// note: checksum is 699-702
|
||||
LOGI("Nib35 DEBUG: output differs at byte %d (0x%02x vs 0x%02x)",
|
||||
i, buffer[start+i], nibBuf[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /*TEST_ENC_35*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unpack the 6&2 encoded 3-byte checksum at the end of a sector.
|
||||
*
|
||||
* "offset" should point to the first byte of the checksum.
|
||||
*
|
||||
* Returns "true" if all goes well, "false" otherwise.
|
||||
*/
|
||||
/*static*/ bool DiskImg::UnpackChecksum35(const CircularBufferAccess& buffer,
|
||||
int offset, uint8_t* checksumBuf)
|
||||
{
|
||||
uint8_t nib0, nib1, nib2, twos;
|
||||
|
||||
twos = kInvDiskBytes62[buffer[offset++]];
|
||||
nib2 = kInvDiskBytes62[buffer[offset++]];
|
||||
nib1 = kInvDiskBytes62[buffer[offset++]];
|
||||
nib0 = kInvDiskBytes62[buffer[offset++]];
|
||||
|
||||
if (twos == kInvInvalidValue ||
|
||||
nib0 == kInvInvalidValue ||
|
||||
nib1 == kInvInvalidValue ||
|
||||
nib2 == kInvInvalidValue)
|
||||
{
|
||||
LOGI("nib25: found invalid disk byte in checksum");
|
||||
return false;
|
||||
}
|
||||
|
||||
checksumBuf[0] = nib0 | ((twos << 6) & 0xc0);
|
||||
checksumBuf[1] = nib1 | ((twos << 4) & 0xc0);
|
||||
checksumBuf[2] = nib2 | ((twos << 2) & 0xc0);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Encode 524 bytes of sector data into 699 bytes of 6&2 nibblized data
|
||||
* plus a 4-byte checksum.
|
||||
*
|
||||
* "outBuf" must be able to hold kNibblizedOutputLen bytes.
|
||||
*/
|
||||
/*static*/ void DiskImg::EncodeNibbleSector35(const uint8_t* sectorData,
|
||||
uint8_t* outBuf)
|
||||
{
|
||||
const uint8_t* sectorDataStart = sectorData;
|
||||
uint8_t* outBufStart = outBuf;
|
||||
uint8_t part0[kChunkSize35], part1[kChunkSize35], part2[kChunkSize35];
|
||||
unsigned int chk0, chk1, chk2;
|
||||
uint8_t val, twos;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Compute checksum and split the input into 3 pieces.
|
||||
*/
|
||||
i = 0;
|
||||
chk0 = chk1 = chk2 = 0;
|
||||
while (true) {
|
||||
chk0 = (chk0 & 0xff) << 1;
|
||||
if (chk0 & 0x0100)
|
||||
chk0++;
|
||||
|
||||
val = *sectorData++;
|
||||
chk2 += val;
|
||||
if (chk0 & 0x0100) {
|
||||
chk2++;
|
||||
chk0 &= 0xff;
|
||||
}
|
||||
part0[i] = (val ^ chk0) & 0xff;
|
||||
|
||||
val = *sectorData++;
|
||||
chk1 += val;
|
||||
if (chk2 > 0xff) {
|
||||
chk1++;
|
||||
chk2 &= 0xff;
|
||||
}
|
||||
part1[i] = (val ^ chk2) & 0xff;
|
||||
|
||||
if (sectorData - sectorDataStart == 524)
|
||||
break;
|
||||
|
||||
val = *sectorData++;
|
||||
chk0 += val;
|
||||
if (chk1 > 0xff) {
|
||||
chk0++;
|
||||
chk1 &= 0xff;
|
||||
}
|
||||
part2[i] = (val ^ chk1) & 0xff;
|
||||
i++;
|
||||
}
|
||||
part2[kChunkSize35-1] = 0; // gets merged into the "twos"
|
||||
|
||||
assert(i == kChunkSize35-1);
|
||||
|
||||
/*
|
||||
* Output the nibble data.
|
||||
*/
|
||||
for (i = 0; i < kChunkSize35; i++) {
|
||||
twos = ((part0[i] & 0xc0) >> 2) |
|
||||
((part1[i] & 0xc0) >> 4) |
|
||||
((part2[i] & 0xc0) >> 6);
|
||||
|
||||
*outBuf++ = kDiskBytes62[twos];
|
||||
*outBuf++ = kDiskBytes62[part0[i] & 0x3f];
|
||||
*outBuf++ = kDiskBytes62[part1[i] & 0x3f];
|
||||
if (i != kChunkSize35 -1)
|
||||
*outBuf++ = kDiskBytes62[part2[i] & 0x3f];
|
||||
}
|
||||
|
||||
/*
|
||||
* Output the checksum.
|
||||
*/
|
||||
twos = ((chk0 & 0xc0) >> 6) | ((chk1 & 0xc0) >> 4) | ((chk2 & 0xc0) >> 2);
|
||||
*outBuf++ = kDiskBytes62[twos];
|
||||
*outBuf++ = kDiskBytes62[chk2 & 0x3f];
|
||||
*outBuf++ = kDiskBytes62[chk1 & 0x3f];
|
||||
*outBuf++ = kDiskBytes62[chk0 & 0x3f];
|
||||
|
||||
assert(outBuf - outBufStart == kNibblizedOutputLen);
|
||||
}
|
1504
diskimg/OuterWrapper.cpp
Normal file
1504
diskimg/OuterWrapper.cpp
Normal file
File diff suppressed because it is too large
Load Diff
320
diskimg/OzDOS.cpp
Normal file
320
diskimg/OzDOS.cpp
Normal file
@ -0,0 +1,320 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Implementation of DiskFSOzDOS class.
|
||||
*
|
||||
* It would make life MUCH EASIER to have the DiskImg recognize this as
|
||||
* a file format and just rearrange the blocks into linear order for us,
|
||||
* but unfortunately that's not going to happen.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* DiskFSOzDOS
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
const int kExpectedNumBlocks = 1600;
|
||||
const int kExpectedTracks = 50; // 50 tracks of 32 sectors == 400K
|
||||
const int kExpectedSectors = 32;
|
||||
const int kVTOCTrack = 17;
|
||||
const int kVTOCSector = 0;
|
||||
const int kSctSize = 256;
|
||||
|
||||
const int kCatalogEntrySize = 0x23; // length in bytes of catalog entries
|
||||
const int kCatalogEntriesPerSect = 7; // #of entries per catalog sector
|
||||
const int kMaxTSPairs = 0x7a; // 122 entries for 256-byte sectors
|
||||
const int kTSOffset = 0x0c; // first T/S entry in a T/S list
|
||||
|
||||
const int kMaxTSIterations = 32;
|
||||
const int kMaxCatalogIterations = 64;
|
||||
|
||||
|
||||
/*
|
||||
* Read a track/sector, adjusting for 32-sector disks being treated as
|
||||
* if they were 16-sector.
|
||||
*/
|
||||
static DIError ReadTrackSectorAdjusted(DiskImg* pImg, int track, int sector,
|
||||
int sectorOffset, uint8_t* buf, DiskImg::SectorOrder imageOrder)
|
||||
{
|
||||
track *= 4;
|
||||
sector = sector * 2 + sectorOffset;
|
||||
while (sector >= 16) {
|
||||
track++;
|
||||
sector -= 16;
|
||||
}
|
||||
return pImg->ReadTrackSectorSwapped(track, sector, buf, imageOrder,
|
||||
DiskImg::kSectorOrderDOS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test for presence of 400K OzDOS 3.3 volumes.
|
||||
*/
|
||||
static DIError TestImageHalf(DiskImg* pImg, int sectorOffset,
|
||||
DiskImg::SectorOrder imageOrder)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t sctBuf[kSctSize];
|
||||
int numTracks, numSectors;
|
||||
int catTrack, catSect;
|
||||
int foundGood = 0;
|
||||
int iterations = 0;
|
||||
|
||||
assert(sectorOffset == 0 || sectorOffset == 1);
|
||||
|
||||
dierr = ReadTrackSectorAdjusted(pImg, kVTOCTrack, kVTOCSector,
|
||||
sectorOffset, sctBuf, imageOrder);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
catTrack = sctBuf[0x01];
|
||||
catSect = sctBuf[0x02];
|
||||
numTracks = sctBuf[0x34];
|
||||
numSectors = sctBuf[0x35];
|
||||
|
||||
if (!(sctBuf[0x27] == kMaxTSPairs) ||
|
||||
/*!(sctBuf[0x36] == 0 && sctBuf[0x37] == 1) ||*/ // bytes per sect
|
||||
!(numTracks == kExpectedTracks) ||
|
||||
!(numSectors == 32) ||
|
||||
!(catTrack < numTracks && catSect < numSectors) ||
|
||||
0)
|
||||
{
|
||||
LOGI(" OzDOS header test %d failed", sectorOffset);
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Walk through the catalog track to try to figure out ordering.
|
||||
*/
|
||||
while (catTrack != 0 && catSect != 0 && iterations < kMaxCatalogIterations)
|
||||
{
|
||||
dierr = ReadTrackSectorAdjusted(pImg, catTrack, catSect,
|
||||
sectorOffset, sctBuf, imageOrder);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail_ok; /* allow it if not fully broken */
|
||||
|
||||
if (sctBuf[1] == catTrack && sctBuf[2] == catSect-1)
|
||||
foundGood++;
|
||||
|
||||
catTrack = sctBuf[1];
|
||||
catSect = sctBuf[2];
|
||||
iterations++; // watch for infinite loops
|
||||
}
|
||||
if (iterations >= kMaxCatalogIterations) {
|
||||
dierr = kDIErrDirectoryLoop;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
bail_ok:
|
||||
LOGI(" OzDOS foundGood=%d off=%d swap=%d", foundGood, sectorOffset,
|
||||
imageOrder);
|
||||
/* foundGood hits 3 even when swap is wrong */
|
||||
if (foundGood > 4)
|
||||
dierr = kDIErrNone;
|
||||
else
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test both of the DOS partitions.
|
||||
*/
|
||||
static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder)
|
||||
{
|
||||
DIError dierr;
|
||||
|
||||
LOGI(" OzDOS checking first half (swap=%d)", imageOrder);
|
||||
dierr = TestImageHalf(pImg, 0, imageOrder);
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
|
||||
LOGI(" OzDOS checking second half (swap=%d)", imageOrder);
|
||||
dierr = TestImageHalf(pImg, 1, imageOrder);
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test to see if the image is a OzDOS volume.
|
||||
*/
|
||||
/*static*/ DIError DiskFSOzDOS::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
||||
DiskImg::FSFormat* pFormat, FSLeniency leniency)
|
||||
{
|
||||
/* only on 800K disks (at the least, insist on numTracks being even) */
|
||||
if (pImg->GetNumBlocks() != kExpectedNumBlocks)
|
||||
return kDIErrFilesystemNotFound;
|
||||
|
||||
/* if a value is specified, try that first -- useful for OverrideFormat */
|
||||
if (*pOrder != DiskImg::kSectorOrderUnknown) {
|
||||
if (TestImage(pImg, *pOrder) == kDIErrNone) {
|
||||
LOGI(" OzDOS accepted FirstTry value");
|
||||
return kDIErrNone;
|
||||
}
|
||||
}
|
||||
|
||||
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
|
||||
|
||||
DiskImg::GetSectorOrderArray(ordering, *pOrder);
|
||||
|
||||
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
|
||||
if (ordering[i] == DiskImg::kSectorOrderUnknown)
|
||||
continue;
|
||||
if (TestImage(pImg, ordering[i]) == kDIErrNone) {
|
||||
*pOrder = ordering[i];
|
||||
*pFormat = DiskImg::kFormatOzDOS;
|
||||
return kDIErrNone;
|
||||
}
|
||||
}
|
||||
|
||||
LOGI(" OzDOS didn't find valid FS");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/*
|
||||
* Test to see if the image is a 'wide' (32-sector) DOS3.3 volume, i.e.
|
||||
* half of a OzDOS volume.
|
||||
*/
|
||||
/*static*/ DIError DiskFS::TestOzWideDOS33(const DiskImg* pImg,
|
||||
DiskImg::SectorOrder* pOrder)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
|
||||
/* only on 400K disks (at the least, insist on numTracks being even) */
|
||||
if (pImg->GetNumBlocks() != kExpectedNumBlocks/2)
|
||||
return kDIErrFilesystemNotFound;
|
||||
|
||||
/* if a value is specified, try that first -- useful for OverrideFormat */
|
||||
if (*pOrder != DiskImg::kSectorOrderUnknown) {
|
||||
if (TestImageHalf(pImg, 0, *pOrder) == kDIErrNone) {
|
||||
LOGI(" WideDOS accepted FirstTry value");
|
||||
return kDIErrNone;
|
||||
}
|
||||
}
|
||||
|
||||
if (TestImageHalf(pImg, 0, DiskImg::kSectorOrderDOS) == kDIErrNone) {
|
||||
*pOrder = DiskImg::kSectorOrderDOS;
|
||||
} else if (TestImageHalf(pImg, 0, DiskImg::kSectorOrderProDOS) == kDIErrNone) {
|
||||
*pOrder = DiskImg::kSectorOrderProDOS;
|
||||
} else if (TestImageHalf(pImg, 0, DiskImg::kSectorOrderPhysical) == kDIErrNone) {
|
||||
*pOrder = DiskImg::kSectorOrderPhysical;
|
||||
} else {
|
||||
LOGI(" FS didn't find valid 'wide' DOS3.3");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Set up our sub-volumes.
|
||||
*/
|
||||
DIError DiskFSOzDOS::Initialize(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
|
||||
if (fScanForSubVolumes != kScanSubDisabled) {
|
||||
dierr = OpenSubVolume(0);
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
|
||||
dierr = OpenSubVolume(1);
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
} else {
|
||||
LOGI(" OzDOS not scanning for sub-volumes");
|
||||
}
|
||||
|
||||
SetVolumeUsageMap();
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Open up one of the DOS 3.3 sub-volumes.
|
||||
*/
|
||||
DIError DiskFSOzDOS::OpenSubVolume(int idx)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
DiskFS* pNewFS = NULL;
|
||||
DiskImg* pNewImg = NULL;
|
||||
|
||||
pNewImg = new DiskImg;
|
||||
if (pNewImg == NULL) {
|
||||
dierr = kDIErrMalloc;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
// open the full 800K; SetPairedSectors cuts it in half
|
||||
dierr = pNewImg->OpenImage(fpImg, 0, 0,
|
||||
2 * kExpectedTracks * kExpectedSectors);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" OzSub: OpenImage(%d,0,%d) failed (err=%d)",
|
||||
0, 2 * kExpectedTracks * kExpectedSectors, dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
assert(idx == 0 || idx == 1);
|
||||
pNewImg->SetPairedSectors(true, 1-idx);
|
||||
|
||||
LOGI(" OzSub: testing for recognizable volume in idx=%d", idx);
|
||||
dierr = pNewImg->AnalyzeImage();
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" OzSub: analysis failed (err=%d)", dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
|
||||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
|
||||
{
|
||||
LOGI(" OzSub: unable to identify filesystem");
|
||||
dierr = kDIErrUnsupportedFSFmt;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* open a DiskFS for the sub-image */
|
||||
LOGI(" UNISub %d succeeded!", idx);
|
||||
pNewFS = pNewImg->OpenAppropriateDiskFS();
|
||||
if (pNewFS == NULL) {
|
||||
LOGI(" OzSub: OpenAppropriateDiskFS failed");
|
||||
dierr = kDIErrUnsupportedFSFmt;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* load the files from the sub-image */
|
||||
dierr = pNewFS->Initialize(pNewImg, kInitFull);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGE(" OzSub: error %d reading list of files from disk", dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* if this really is DOS 3.3, override the "volume name" */
|
||||
if (pNewImg->GetFSFormat() == DiskImg::kFormatDOS33) {
|
||||
DiskFSDOS33* pDOS = (DiskFSDOS33*) pNewFS; /* eek, a downcast */
|
||||
pDOS->SetDiskVolumeNum(idx+1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Success, add it to the sub-volume list.
|
||||
*/
|
||||
AddSubVolumeToList(pNewImg, pNewFS);
|
||||
|
||||
bail:
|
||||
if (dierr != kDIErrNone) {
|
||||
delete pNewFS;
|
||||
delete pNewImg;
|
||||
}
|
||||
return dierr;
|
||||
}
|
1863
diskimg/Pascal.cpp
Normal file
1863
diskimg/Pascal.cpp
Normal file
File diff suppressed because it is too large
Load Diff
5183
diskimg/ProDOS.cpp
Normal file
5183
diskimg/ProDOS.cpp
Normal file
File diff suppressed because it is too large
Load Diff
732
diskimg/RDOS.cpp
Normal file
732
diskimg/RDOS.cpp
Normal file
@ -0,0 +1,732 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Implementation of DiskFSRDOS class.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* DiskFSRDOS
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
const int kSctSize = 256;
|
||||
const int kCatTrack = 1;
|
||||
const int kNumCatSectors = 11; // 0 through 10
|
||||
const int kDirectoryEntryLen = 32;
|
||||
const int kNumDirEntryPerSect = (256 / kDirectoryEntryLen); // 8
|
||||
|
||||
/*
|
||||
* See if this looks like a RDOS volume.
|
||||
*
|
||||
* There are three variants:
|
||||
* RDOS32 (e.g. ComputerAmbush.nib):
|
||||
* 13-sector disk
|
||||
* sector (1,0) starts with "RDOS 2"
|
||||
* sector (1,12) has catalog code, CHAIN in (1,11)
|
||||
* uses "physical" ordering
|
||||
* NOTE: track 0 may be unreadable with RDOS 3.2 NibbleDescr
|
||||
* RDOS33 (e.g. disk #199):
|
||||
* 16-sector disk
|
||||
* sector (1,0) starts with "RDOS 3"
|
||||
* sector (1,12) has catalog code
|
||||
* uses "ProDOS" ordering
|
||||
* RDOS3 (e.g. disk #108):
|
||||
* 16-sector disk, but only 13 sectors of each track are used
|
||||
* sector (1,0) starts with "RDOS 2"
|
||||
* sector (0,1) has catalog code
|
||||
* uses "physical" orering
|
||||
*
|
||||
* In all cases:
|
||||
* catalog found on (1,0) through (1,10)
|
||||
*
|
||||
* The initial value of "pFormatFound" is ignored, because we can reliably
|
||||
* detect which variant we're looking at.
|
||||
*/
|
||||
static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder,
|
||||
DiskImg::FSFormat* pFormatFound)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t sctBuf[kSctSize];
|
||||
|
||||
|
||||
if (pImg->GetNumSectPerTrack() == 13) {
|
||||
/* must be a nibble image; check it for RDOS 3.2 */
|
||||
dierr = pImg->ReadTrackSectorSwapped(kCatTrack, 0, sctBuf,
|
||||
imageOrder, DiskImg::kSectorOrderPhysical);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
} else if (pImg->GetNumSectPerTrack() == 16) {
|
||||
/* could be RDOS3 or RDOS 3.3 */
|
||||
dierr = pImg->ReadTrackSectorSwapped(kCatTrack, 0, sctBuf,
|
||||
imageOrder, DiskImg::kSectorOrderPhysical);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
} else {
|
||||
LOGI(" RDOS neither 13 nor 16 sector, bailing");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* check for RDOS string and correct #of blocks */
|
||||
if (!( sctBuf[0] == 'R'+0x80 &&
|
||||
sctBuf[1] == 'D'+0x80 &&
|
||||
sctBuf[2] == 'O'+0x80 &&
|
||||
sctBuf[3] == 'S'+0x80 &&
|
||||
sctBuf[4] == ' '+0x80) ||
|
||||
!(sctBuf[25] == 26 || sctBuf[25] == 32))
|
||||
{
|
||||
LOGI(" RDOS no signature found on (%d,0)", kCatTrack);
|
||||
dierr = kDIErrGeneric;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/*
|
||||
* Guess at the format based on the first catalog entry, which usually
|
||||
* begins "RDOS 2.0", "RDOS 2.1", or "RDOS 3.3".
|
||||
*/
|
||||
if (pImg->GetNumSectPerTrack() == 13) {
|
||||
*pFormatFound = DiskImg::kFormatRDOS32;
|
||||
} else {
|
||||
if (sctBuf[5] == '2'+0x80)
|
||||
*pFormatFound = DiskImg::kFormatRDOS3;
|
||||
else
|
||||
*pFormatFound = DiskImg::kFormatRDOS33;
|
||||
}
|
||||
|
||||
/*
|
||||
* The above came from sector 0, which doesn't help us figure out the
|
||||
* sector ordering. Look for the catalog code.
|
||||
*/
|
||||
{
|
||||
int track, sector, offset;
|
||||
uint8_t orMask;
|
||||
static const char* kCompare = "<NAME>";
|
||||
DiskImg::SectorOrder order;
|
||||
|
||||
if (*pFormatFound == DiskImg::kFormatRDOS32 ||
|
||||
*pFormatFound == DiskImg::kFormatRDOS3)
|
||||
{
|
||||
track = 1;
|
||||
sector = 12;
|
||||
offset = 0xa2;
|
||||
orMask = 0x80;
|
||||
order = DiskImg::kSectorOrderPhysical;
|
||||
} else {
|
||||
track = 0;
|
||||
sector = 1;
|
||||
offset = 0x98;
|
||||
orMask = 0;
|
||||
order = DiskImg::kSectorOrderProDOS;
|
||||
}
|
||||
|
||||
dierr = pImg->ReadTrackSectorSwapped(track, sector, sctBuf,
|
||||
imageOrder, order);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
int i;
|
||||
for (i = strlen(kCompare)-1; i >= 0; i--) {
|
||||
if (sctBuf[offset+i] != ((uint8_t)kCompare[i] | orMask))
|
||||
break;
|
||||
}
|
||||
if (i >= 0) {
|
||||
dierr = kDIErrGeneric;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
LOGI(" RDOS found '%s' signature (order=%d)", kCompare, imageOrder);
|
||||
}
|
||||
|
||||
dierr = kDIErrNone;
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Common RDOS test code.
|
||||
*/
|
||||
/*static*/ DIError DiskFSRDOS::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
||||
DiskImg::FSFormat* pFormat, FSLeniency leniency)
|
||||
{
|
||||
if (!pImg->GetHasSectors()) {
|
||||
LOGI(" RDOS - image doesn't have sectors, not trying");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
if (pImg->GetNumTracks() != 35) {
|
||||
LOGI(" RDOS - not a 35-track disk, not trying");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
DiskImg::FSFormat formatFound;
|
||||
|
||||
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
|
||||
|
||||
DiskImg::GetSectorOrderArray(ordering, *pOrder);
|
||||
|
||||
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
|
||||
if (ordering[i] == DiskImg::kSectorOrderUnknown)
|
||||
continue;
|
||||
if (TestImage(pImg, ordering[i], &formatFound) == kDIErrNone) {
|
||||
*pFormat = formatFound;
|
||||
*pOrder = ordering[i];
|
||||
//*pFormat = DiskImg::kFormatXXX;
|
||||
return kDIErrNone;
|
||||
}
|
||||
}
|
||||
|
||||
LOGI(" RDOS didn't find valid FS");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/*
|
||||
* Test to see if the image is an RDOS 3.3 disk.
|
||||
*/
|
||||
/*static*/ DIError DiskFSRDOS::TestFS33(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
||||
FSLeniency leniency)
|
||||
{
|
||||
DIError dierr;
|
||||
DiskImg::FSFormat formatFound = DiskImg::kFormatUnknown;
|
||||
|
||||
dierr = TestCommon(pImg, pOrder, leniency, &formatFound);
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
if (formatFound != DiskImg::kFormatRDOS33) {
|
||||
LOGI(" RDOS found RDOS but wrong type");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test to see if the image is an RDOS 3.2 disk.
|
||||
*/
|
||||
/*static*/ DIError DiskFSRDOS::TestFS32(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
||||
FSLeniency leniency)
|
||||
{
|
||||
DIError dierr;
|
||||
DiskImg::FSFormat formatFound = DiskImg::kFormatUnknown;
|
||||
|
||||
dierr = TestCommon(pImg, pOrder, leniency, &formatFound);
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
if (formatFound != DiskImg::kFormatRDOS32) {
|
||||
LOGI(" RDOS found RDOS but wrong type");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test to see if the image is an RDOS 3 (cracked 3.2) disk.
|
||||
*/
|
||||
/*static*/ DIError DiskFSRDOS::TestFS3(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
||||
FSLeniency leniency)
|
||||
{
|
||||
DIError dierr;
|
||||
DiskImg::FSFormat formatFound = DiskImg::kFormatUnknown;
|
||||
|
||||
dierr = TestCommon(pImg, pOrder, leniency, &formatFound);
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
if (formatFound != DiskImg::kFormatRDOS3) {
|
||||
LOGI(" RDOS found RDOS but wrong type");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* Get things rolling.
|
||||
*
|
||||
* Since we're assured that this is a valid disk, errors encountered from here
|
||||
* on out must be handled somehow, possibly by claiming that the disk is
|
||||
* completely full and has no files on it.
|
||||
*/
|
||||
DIError DiskFSRDOS::Initialize(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
const char* volStr;
|
||||
|
||||
switch (GetDiskImg()->GetFSFormat()) {
|
||||
case DiskImg::kFormatRDOS33:
|
||||
volStr = "RDOS 3.3";
|
||||
fOurSectPerTrack = 16;
|
||||
break;
|
||||
case DiskImg::kFormatRDOS32:
|
||||
volStr = "RDOS 3.2";
|
||||
fOurSectPerTrack = 13;
|
||||
break;
|
||||
case DiskImg::kFormatRDOS3:
|
||||
volStr = "RDOS 3";
|
||||
fOurSectPerTrack = 13;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
return kDIErrInternal;
|
||||
}
|
||||
assert(strlen(volStr) < sizeof(fVolumeName));
|
||||
strcpy(fVolumeName, volStr);
|
||||
|
||||
dierr = ReadCatalog();
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
fVolumeUsage.Create(fpImg->GetNumTracks(), fOurSectPerTrack);
|
||||
dierr = ScanFileUsage();
|
||||
if (dierr != kDIErrNone) {
|
||||
/* this might not be fatal; just means that *some* files are bad */
|
||||
goto bail;
|
||||
}
|
||||
fVolumeUsage.Dump();
|
||||
|
||||
//A2File* pFile;
|
||||
//pFile = GetNextFile(NULL);
|
||||
//while (pFile != NULL) {
|
||||
// pFile->Dump();
|
||||
// pFile = GetNextFile(pFile);
|
||||
//}
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Read the catalog from the disk.
|
||||
*
|
||||
* To make life easy we slurp the whole thing into memory.
|
||||
*/
|
||||
DIError DiskFSRDOS::ReadCatalog(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t* dir = NULL;
|
||||
uint8_t* dirPtr;
|
||||
int track, sector;
|
||||
|
||||
dir = new uint8_t[kSctSize * kNumCatSectors];
|
||||
if (dir == NULL) {
|
||||
dierr = kDIErrMalloc;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
track = kCatTrack;
|
||||
dirPtr = dir;
|
||||
for (sector = 0; sector < kNumCatSectors; sector++) {
|
||||
dierr = fpImg->ReadTrackSector(track, sector, dirPtr);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
dirPtr += kSctSize;
|
||||
}
|
||||
|
||||
int i;
|
||||
A2FileRDOS* pFile;
|
||||
dirPtr = dir;
|
||||
for (i = 0; i < kNumCatSectors * kNumDirEntryPerSect;
|
||||
i++, dirPtr += kDirectoryEntryLen)
|
||||
{
|
||||
if (dirPtr[0] == 0x80 || dirPtr[24] == 0xa0) // deleted file
|
||||
continue;
|
||||
if (dirPtr[24] == 0x00) // unused entry; must be at end of catalog
|
||||
break;
|
||||
|
||||
pFile = new A2FileRDOS(this);
|
||||
|
||||
memcpy(pFile->fRawFileName, dirPtr, A2FileRDOS::kMaxFileName);
|
||||
pFile->fRawFileName[A2FileRDOS::kMaxFileName] = '\0';
|
||||
|
||||
memcpy(pFile->fFileName, dirPtr, A2FileRDOS::kMaxFileName);
|
||||
pFile->fFileName[A2FileRDOS::kMaxFileName] = '\0';
|
||||
pFile->FixFilename();
|
||||
|
||||
switch (dirPtr[24]) {
|
||||
case 'A'+0x80: pFile->fFileType = A2FileRDOS::kTypeApplesoft; break;
|
||||
case 'B'+0x80: pFile->fFileType = A2FileRDOS::kTypeBinary; break;
|
||||
case 'T'+0x80: pFile->fFileType = A2FileRDOS::kTypeText; break;
|
||||
// 0x00 is end of catalog, ' '+0x80 is deleted file, both handled above
|
||||
default: pFile->fFileType = A2FileRDOS::kTypeUnknown; break;
|
||||
}
|
||||
pFile->fNumSectors = dirPtr[25];
|
||||
pFile->fLoadAddr = GetShortLE(&dirPtr[26]);
|
||||
pFile->fLength = GetShortLE(&dirPtr[28]);
|
||||
pFile->fStartSector = GetShortLE(&dirPtr[30]);
|
||||
|
||||
if (pFile->fStartSector + pFile->fNumSectors >
|
||||
fpImg->GetNumTracks() * fOurSectPerTrack)
|
||||
{
|
||||
LOGI(" RDOS invalid start/count (%d + %d) (max %ld) '%s'",
|
||||
pFile->fStartSector, pFile->fNumSectors, fpImg->GetNumBlocks(),
|
||||
pFile->fFileName);
|
||||
pFile->fStartSector = pFile->fNumSectors = 0;
|
||||
pFile->fLength = 0;
|
||||
pFile->SetQuality(A2File::kQualityDamaged);
|
||||
}
|
||||
|
||||
AddFileToList(pFile);
|
||||
}
|
||||
|
||||
bail:
|
||||
delete[] dir;
|
||||
return dierr;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Create the volume usage map. Since RDOS volumes have neither
|
||||
* in-use maps nor index blocks, this is pretty straightforward.
|
||||
*/
|
||||
DIError DiskFSRDOS::ScanFileUsage(void)
|
||||
{
|
||||
int track, sector, block, count;
|
||||
|
||||
A2FileRDOS* pFile;
|
||||
pFile = (A2FileRDOS*) GetNextFile(NULL);
|
||||
while (pFile != NULL) {
|
||||
block = pFile->fStartSector;
|
||||
count = pFile->fNumSectors;
|
||||
while (count--) {
|
||||
track = block / fOurSectPerTrack;
|
||||
sector = block % fOurSectPerTrack;
|
||||
|
||||
SetSectorUsage(track, sector, VolumeUsage::kChunkPurposeUserData);
|
||||
|
||||
block++;
|
||||
}
|
||||
|
||||
pFile = (A2FileRDOS*) GetNextFile(pFile);
|
||||
}
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update an entry in the usage map.
|
||||
*/
|
||||
void DiskFSRDOS::SetSectorUsage(long track, long sector,
|
||||
VolumeUsage::ChunkPurpose purpose)
|
||||
{
|
||||
VolumeUsage::ChunkState cstate;
|
||||
|
||||
fVolumeUsage.GetChunkState(track, sector, &cstate);
|
||||
if (cstate.isUsed) {
|
||||
cstate.purpose = VolumeUsage::kChunkPurposeConflict;
|
||||
LOGI(" RDOS conflicting uses for sct=(%ld,%ld)", track, sector);
|
||||
} else {
|
||||
cstate.isUsed = true;
|
||||
cstate.isMarkedUsed = true;
|
||||
cstate.purpose = purpose;
|
||||
}
|
||||
fVolumeUsage.SetChunkState(track, sector, &cstate);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* A2FileRDOS
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Convert RDOS file type to ProDOS file type.
|
||||
*/
|
||||
uint32_t A2FileRDOS::GetFileType(void) const
|
||||
{
|
||||
uint32_t retval;
|
||||
|
||||
switch (fFileType) {
|
||||
case kTypeText: retval = 0x04; break; // TXT
|
||||
case kTypeApplesoft: retval = 0xfc; break; // BAS
|
||||
case kTypeBinary: retval = 0x06; break; // BIN
|
||||
case kTypeUnknown:
|
||||
default: retval = 0x00; break; // NON
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dump the contents of the A2File structure.
|
||||
*/
|
||||
void A2FileRDOS::Dump(void) const
|
||||
{
|
||||
LOGI("A2FileRDOS '%s' (type=%d)", fFileName, fFileType);
|
||||
LOGI(" start=%d num=%d len=%d addr=0x%04x",
|
||||
fStartSector, fNumSectors, fLength, fLoadAddr);
|
||||
}
|
||||
|
||||
/*
|
||||
* "Fix" an RDOS filename. Convert DOS-ASCII to normal ASCII, and strip
|
||||
* trailing spaces.
|
||||
*
|
||||
* It's possible that RDOS 3.3 forces the filename to high-ASCII, because
|
||||
* one disk (#938A) has a file left by the crackers whose name is in
|
||||
* low-ASCII. The inverse-mode correction turns it into punctuation, but
|
||||
* I don't see a good way around it. Or any particular need to fix it.
|
||||
*/
|
||||
void A2FileRDOS::FixFilename(void)
|
||||
{
|
||||
DiskFSDOS33::LowerASCII((uint8_t*)fFileName, kMaxFileName);
|
||||
TrimTrailingSpaces(fFileName);
|
||||
}
|
||||
|
||||
/*
|
||||
* Trim the spaces off the end of a filename.
|
||||
*
|
||||
* Assumes the filename has already been converted to low ASCII.
|
||||
*/
|
||||
void A2FileRDOS::TrimTrailingSpaces(char* filename)
|
||||
{
|
||||
char* lastspc = filename + strlen(filename);
|
||||
|
||||
assert(*lastspc == '\0');
|
||||
|
||||
while (--lastspc) {
|
||||
if (*lastspc != ' ')
|
||||
break;
|
||||
}
|
||||
|
||||
*(lastspc+1) = '\0';
|
||||
}
|
||||
|
||||
/*
|
||||
* Not a whole lot to do, since there's no fancy index blocks.
|
||||
*/
|
||||
DIError A2FileRDOS::Open(A2FileDescr** ppOpenFile, bool readOnly,
|
||||
bool rsrcFork /*=false*/)
|
||||
{
|
||||
if (fpOpenFile != NULL)
|
||||
return kDIErrAlreadyOpen;
|
||||
if (rsrcFork)
|
||||
return kDIErrForkNotFound;
|
||||
assert(readOnly == true);
|
||||
|
||||
A2FDRDOS* pOpenFile = new A2FDRDOS(this);
|
||||
|
||||
pOpenFile->fOffset = 0;
|
||||
//fOpen = true;
|
||||
|
||||
fpOpenFile = pOpenFile;
|
||||
*ppOpenFile = pOpenFile;
|
||||
pOpenFile = NULL;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the raw filename.
|
||||
*
|
||||
* If a pointer to a size_t is passed in, it will be filled with the
|
||||
* raw filename length.
|
||||
*/
|
||||
const char* A2FileRDOS::GetRawFileName(size_t* size) const {
|
||||
if (size) {
|
||||
*size = strlen(fRawFileName);
|
||||
}
|
||||
return fRawFileName;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* A2FDRDOS
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
* Read a chunk of data from the current offset.
|
||||
*/
|
||||
DIError A2FDRDOS::Read(void* buf, size_t len, size_t* pActual)
|
||||
{
|
||||
LOGD(" RDOS reading %lu bytes from '%s' (offset=%ld)",
|
||||
(unsigned long) len, fpFile->GetPathName(), (long) fOffset);
|
||||
//if (!fOpen)
|
||||
// return kDIErrNotReady;
|
||||
|
||||
A2FileRDOS* pFile = (A2FileRDOS*) fpFile;
|
||||
|
||||
/* don't allow them to read past the end of the file */
|
||||
if (fOffset + (long)len > pFile->fLength) {
|
||||
if (pActual == NULL)
|
||||
return kDIErrDataUnderrun;
|
||||
len = (size_t) (pFile->fLength - fOffset);
|
||||
}
|
||||
if (pActual != NULL)
|
||||
*pActual = len;
|
||||
long incrLen = len;
|
||||
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t sctBuf[kSctSize];
|
||||
long block = pFile->fStartSector + (long) (fOffset / kSctSize);
|
||||
int bufOffset = (int) (fOffset % kSctSize); // (& 0xff)
|
||||
int ourSectPerTrack = GetOurSectPerTrack();
|
||||
size_t thisCount;
|
||||
|
||||
if (len == 0) {
|
||||
///* one block allocated for empty file */
|
||||
//SetLastBlock(block, true);
|
||||
return kDIErrNone;
|
||||
}
|
||||
assert(pFile->fLength != 0);
|
||||
|
||||
while (len) {
|
||||
assert(block >= pFile->fStartSector &&
|
||||
block < pFile->fStartSector + pFile->fNumSectors);
|
||||
|
||||
dierr = pFile->GetDiskFS()->GetDiskImg()->ReadTrackSector(block / ourSectPerTrack,
|
||||
block % ourSectPerTrack, sctBuf);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" RDOS error reading file '%s'", pFile->fFileName);
|
||||
return dierr;
|
||||
}
|
||||
thisCount = kSctSize - bufOffset;
|
||||
if (thisCount > len)
|
||||
thisCount = len;
|
||||
|
||||
memcpy(buf, sctBuf + bufOffset, thisCount);
|
||||
len -= thisCount;
|
||||
buf = (char*)buf + thisCount;
|
||||
|
||||
bufOffset = 0;
|
||||
block++;
|
||||
}
|
||||
|
||||
fOffset += incrLen;
|
||||
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write data at the current offset.
|
||||
*/
|
||||
DIError A2FDRDOS::Write(const void* buf, size_t len, size_t* pActual)
|
||||
{
|
||||
//if (!fOpen)
|
||||
// return kDIErrNotReady;
|
||||
return kDIErrNotSupported;
|
||||
}
|
||||
|
||||
/*
|
||||
* Seek to a new offset.
|
||||
*/
|
||||
DIError A2FDRDOS::Seek(di_off_t offset, DIWhence whence)
|
||||
{
|
||||
//if (!fOpen)
|
||||
// return kDIErrNotReady;
|
||||
|
||||
long fileLen = ((A2FileRDOS*) fpFile)->fLength;
|
||||
|
||||
switch (whence) {
|
||||
case kSeekSet:
|
||||
if (offset < 0 || offset > fileLen)
|
||||
return kDIErrInvalidArg;
|
||||
fOffset = offset;
|
||||
break;
|
||||
case kSeekEnd:
|
||||
if (offset > 0 || offset < -fileLen)
|
||||
return kDIErrInvalidArg;
|
||||
fOffset = fileLen + offset;
|
||||
break;
|
||||
case kSeekCur:
|
||||
if (offset < -fOffset ||
|
||||
offset >= (fileLen - fOffset))
|
||||
{
|
||||
return kDIErrInvalidArg;
|
||||
}
|
||||
fOffset += offset;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
return kDIErrInvalidArg;
|
||||
}
|
||||
|
||||
assert(fOffset >= 0 && fOffset <= fileLen);
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return current offset.
|
||||
*/
|
||||
di_off_t A2FDRDOS::Tell(void)
|
||||
{
|
||||
//if (!fOpen)
|
||||
// return kDIErrNotReady;
|
||||
|
||||
return fOffset;
|
||||
}
|
||||
|
||||
/*
|
||||
* Release file state, such as it is.
|
||||
*/
|
||||
DIError A2FDRDOS::Close(void)
|
||||
{
|
||||
fpFile->CloseDescr(this);
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the #of sectors/blocks in the file.
|
||||
*/
|
||||
long A2FDRDOS::GetSectorCount(void) const
|
||||
{
|
||||
//if (!fOpen)
|
||||
// return kDIErrNotReady;
|
||||
return ((A2FileRDOS*) fpFile)->fNumSectors;
|
||||
}
|
||||
|
||||
long A2FDRDOS::GetBlockCount(void) const
|
||||
{
|
||||
//if (!fOpen)
|
||||
// return kDIErrNotReady;
|
||||
return ((A2FileRDOS*) fpFile)->fNumSectors / 2;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the Nth track/sector in this file.
|
||||
*/
|
||||
DIError A2FDRDOS::GetStorage(long sectorIdx, long* pTrack, long* pSector) const
|
||||
{
|
||||
//if (!fOpen)
|
||||
// return kDIErrNotReady;
|
||||
A2FileRDOS* pFile = (A2FileRDOS*) fpFile;
|
||||
long rdosBlock = pFile->fStartSector + sectorIdx;
|
||||
int ourSectPerTrack = GetOurSectPerTrack();
|
||||
if (rdosBlock >= pFile->fStartSector + pFile->fNumSectors)
|
||||
return kDIErrInvalidIndex;
|
||||
|
||||
*pTrack = rdosBlock / ourSectPerTrack;
|
||||
*pSector = rdosBlock % ourSectPerTrack;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the Nth 512-byte block in this file.
|
||||
*/
|
||||
DIError A2FDRDOS::GetStorage(long blockIdx, long* pBlock) const
|
||||
{
|
||||
//if (!fOpen)
|
||||
// return kDIErrNotReady;
|
||||
A2FileRDOS* pFile = (A2FileRDOS*) fpFile;
|
||||
long rdosBlock = pFile->fStartSector + blockIdx*2;
|
||||
if (rdosBlock >= pFile->fStartSector + pFile->fNumSectors)
|
||||
return kDIErrInvalidIndex;
|
||||
|
||||
*pBlock = rdosBlock / 2;
|
||||
|
||||
if (pFile->GetDiskFS()->GetDiskImg()->GetHasBlocks()) {
|
||||
assert(*pBlock < pFile->GetDiskFS()->GetDiskImg()->GetNumBlocks());
|
||||
}
|
||||
return kDIErrNone;
|
||||
}
|
245
diskimg/README.md
Normal file
245
diskimg/README.md
Normal file
@ -0,0 +1,245 @@
|
||||
CiderPress Disk Image Library
|
||||
=============================
|
||||
|
||||
This library provides access to files stored in Apple II disk images. It
|
||||
was developed as part of CiderPress, but can be used independently. It
|
||||
builds on Windows and Linux.
|
||||
|
||||
The MDC (Multi-Disk Catalog) application uses the DiskImg DLL (on Windows)
|
||||
or library (on Linux) to examine disk image files.
|
||||
|
||||
|
||||
Disk Image Structure
|
||||
--------------------
|
||||
|
||||
The Apple II supported a number of different filesystems and physical
|
||||
formats. There are several different disk image formats, some supported
|
||||
by Apple II software, some only usable by emulators. This section
|
||||
provides a quick summary.
|
||||
|
||||
#### Filesystems ####
|
||||
|
||||
- DOS 3.2/3.3. The classic Apple II filesystems. The typical DOS 3.3
|
||||
disk has 35 tracks, 16 sectors per track, 256 bytes per sector, for a
|
||||
total of 140K. The format allows up to 32 sectors and 50 tracks (400K).
|
||||
It worked very well on 5.25" floppy disks, but was awkward to use on
|
||||
larger volumes. Filenames could be up to 30 characters, and sometimes
|
||||
embedded control characters or used flashing/inverse character values.
|
||||
|
||||
- ProDOS. Designed to work on larger media, ProDOS addresses data as
|
||||
512-byte blocks, and leaves any track/sector mapping to device-specific
|
||||
code. Block numbers were stored as unsigned 16-bit values, allowing
|
||||
volumes up to 32MB. Filenames were limited to 15 upper-case or numeric
|
||||
ASCII values. Later versions added support for forked files and
|
||||
case-preserved (but still case-insensitive) filenames.
|
||||
|
||||
- UCSD Pascal. Another block-oriented filesystem, used with the UCSD
|
||||
Pascal operating system. Very simple, and very efficient when reading,
|
||||
but required explicit defragmentation from time to time.
|
||||
|
||||
- HFS. Originally developed for the Macintosh, it was often used on
|
||||
the Apple IIgs as hard drive sizes increased. HFS supports forked files,
|
||||
and "MacRoman" filenames up to 31 characters.
|
||||
|
||||
- CP/M. Z-80 cards allowed Apple II users to run CP/M software, using
|
||||
the established CP/M filesystem layout. It featured 1K blocks and 8.3
|
||||
filenames.
|
||||
|
||||
- SSI RDOS. A custom format developed by Strategic Simulations, Inc. for
|
||||
use with their games. This was used on 13-sector and 16-sector 5.25"
|
||||
disks. The operating system used Applesoft ampersand commands, and was
|
||||
ported to ProDOS.
|
||||
|
||||
- Gutenberg. A custom format developed by Micromation Limited for use
|
||||
with the Gutenberg word processor.
|
||||
|
||||
DOS, ProDOS, HFS, and UCSD Pascal are fully supported by the DiskImg
|
||||
library. CP/M, RDOS, and Gutenberg are treated as read-only.
|
||||
|
||||
|
||||
#### Disk Image Formats ####
|
||||
|
||||
Disk image files can be "unadorned", meaning they're just a series of
|
||||
blocks or sectors, or they can have fancy headers and compressed contents.
|
||||
Block-oriented images are easy to deal with, as they're generally just
|
||||
the blocks in sequential order. Sector-oriented images can be tricky,
|
||||
because the sector order is subject to interpretation.
|
||||
|
||||
The most common sector orderings are "DOS" and "ProDOS". If you read
|
||||
a 5.25" disk sequentially from DOS, starting with track 0 sector 0, and
|
||||
wrote the contents to a file, you would end up with a DOS-ordered image.
|
||||
If you read that same disk from ProDOS, starting with block 0, you would
|
||||
end up with a ProDOS-ordered image. The difference occurs because ProDOS
|
||||
blocks are 512 bytes -- two DOS sectors -- and ProDOS interleaves the
|
||||
sectors as an optimization. While you might expect ProDOS block 0 to be
|
||||
comprised of DOS T0 S0 and T0 S1, it's actually T0 S0 and T0 S2.
|
||||
|
||||
There have been various attempts at defining storage formats for disk
|
||||
images over the years. The library handles most of them.
|
||||
|
||||
- Unadorned block/sector files (.po, .do, .d13, .raw, .hdv, .iso, most .dc6).
|
||||
The image file holds data from the file and nothing else.
|
||||
|
||||
- Unadorned nibble-format files (.nib, .nb2). Some 5.25" disks were a
|
||||
bit "creative" with their physical format, so some image formats allow
|
||||
for extraction of the data as bits directly off the disk. Such formats
|
||||
are unusual in that it's possible to have "bad sectors" in a disk image.
|
||||
|
||||
- Universal Disk Image (.2mg, .2img). The format was designed specifically
|
||||
for Apple II emulators. It supports DOS-order, ProDOS-order, and nibble
|
||||
images.
|
||||
|
||||
- Copy ][ Plus (.img). Certain versions of the Copy ][ Plus Apple II
|
||||
utility had the ability to create disk images. The format was simple
|
||||
unadorned sectors, but with a twist: the sectors were in physical order,
|
||||
which is different from DOS and ProDOS.
|
||||
|
||||
- Dalton's Disk Disintegrator (.ddd). DDD was developed as an alternative
|
||||
to "disk slicer" programs for uploading disk images to BBS systems.
|
||||
Because it used compression, 5.25" disk images could be held on 5.25" disks.
|
||||
DOS and ProDOS versions of the program were available. A fancier version,
|
||||
called DDD Deluxe, was developed later.
|
||||
|
||||
- ShrinkIt (.shk, .sdk). ShrinkIt was initially developed as an improved
|
||||
version of DDD, incorporating LZW compression and CRC error checking. It
|
||||
grew into a general-purpose file archiver.
|
||||
|
||||
- DiskCopy 4.2 (.dsk). The format used by the Mac DiskCopy program for
|
||||
making images of 800K floppies. Includes a checksum, but no compression.
|
||||
|
||||
- TrackStar (.app). The TrackStar was essentially an Apple II built
|
||||
into a PC ISA expansion card. The 5.25" disk images use a variable-length
|
||||
nibble format with 40 tracks.
|
||||
|
||||
- Formatted Disk Image (.fdi). Files generated by the Disk2FDI program.
|
||||
These contain raw signal data obtained from a PC floppy drive. It was
|
||||
long said that reading an Apple II floppy from a PC drive was impossible;
|
||||
this program proved otherwise.
|
||||
|
||||
- Sim //e HDV (.hdv). Used for images of ProDOS drives for use with a
|
||||
specific emulator, this is just a ProDOS block image with a short header.
|
||||
|
||||
All of these, with the exception of DDD Deluxe, are fully supported by
|
||||
the DiskImg library.
|
||||
|
||||
|
||||
#### Meta-Formats ####
|
||||
|
||||
As disks got larger, older filesystems could no longer use all of the
|
||||
available space with a single volume. Some "meta-formats" were developed.
|
||||
These allow a single disk image to hold multiple filesystems.
|
||||
|
||||
- UNIDOS / AmDOS / OzDOS. These allow two 400K DOS 3.3 volumes to
|
||||
exist on a single 800K disk.
|
||||
|
||||
- ProSel Uni-DOS / DOS Master. These allow multiple 140K DOS 3.3
|
||||
volumes to reside on a ProDOS volume. You can, on a single 800K disk,
|
||||
provide a small ProDOS launcher that will boot into DOS 3.3 and launch a
|
||||
specific file from one of five 140K DOS volumes.
|
||||
|
||||
- Macintosh-style disk partitioning. This was widely used on hard
|
||||
drives, CD-ROMs, and other large disks.
|
||||
|
||||
- CFFA-style disk partitioning. The CFFA card for the Apple II allows
|
||||
the use of Compact Flash cards for storage. There is no partitioning
|
||||
done on the CF card itself -- the CFFA card has a fixed arrangement.
|
||||
|
||||
- ///SHH Systeme MicroDrive partitioning. Another disk partition format,
|
||||
developed for use with the MicroDrive. Allows up to 16 partitions on
|
||||
an IDE hard drive.
|
||||
|
||||
- Parsons Engineering FocusDrive partitioning. Developed for use with
|
||||
the FocusDrive. Allows up to 30 partitions.
|
||||
|
||||
It's also possible to create a "hybrid" DOS / ProDOS disk, because the
|
||||
essential file catalog areas don't overlap. (See HYBRID.CREATE on the
|
||||
Beagle Bros "Extra K" disk.)
|
||||
|
||||
All of these are fully supported by the DiskImg library.
|
||||
|
||||
|
||||
#### Wrapper Formats ####
|
||||
|
||||
While 800K may not seem like a lot these days, it used to be a decent
|
||||
chunk of data, so it was common for disk images that didn't use compression
|
||||
to be compressed with another program. The most common are ZIP and gzip.
|
||||
|
||||
|
||||
#### Apple II File Formats ####
|
||||
|
||||
For filesystems like ProDOS, the body of the file contains just the file
|
||||
contents. The directory entry holds the file type, auxiliary type, and
|
||||
the file's length in bytes.
|
||||
|
||||
For DOS 3.2/3.3, the first few bytes of the file specified details like
|
||||
the load address, and for text files there is no reliable indication of
|
||||
the file length. The DiskImg library does what it can to conceal
|
||||
filesystem quirks.
|
||||
|
||||
|
||||
|
||||
DiskImg Library Classes
|
||||
-----------------------
|
||||
|
||||
The library provides several C++ classes. This section gives an overview
|
||||
of what each does.
|
||||
|
||||
The basic classes are defined in DiskImg.h. Specialized sub-classes are
|
||||
declared in DiskImgDetail.h.
|
||||
|
||||
The API is a bit more complicated than it could be, and there's a bit of
|
||||
redundancy in the filesystem code. Some of this is due to the way the
|
||||
library evolved -- disk image access was originally intended to be read-only,
|
||||
and that didn't change until CiderPress 2.0.
|
||||
|
||||
|
||||
#### DiskImg ####
|
||||
|
||||
The `DiskImg` class represents a single disk image. It may have sub-images,
|
||||
each of which is its own instance of DiskImg. Operations on a DiskImg
|
||||
are similar to what you'd expect from a device driver, e.g. reading and
|
||||
writing individual blocks.
|
||||
|
||||
The DiskImg has several characteristics:
|
||||
|
||||
- OuterFormat. This is the "outer wrapper", which may be ZIP (.zip) or
|
||||
gzip (.gz). The wrapper is handled transparently -- the contents are
|
||||
uncompressed when opened, and recompressed if necessary when closed.
|
||||
|
||||
- FileFormat. The disk image file's format, once the outer wrapper has
|
||||
been stripped away. "Unadorned", 2MG, and ShrinkIt are examples.
|
||||
|
||||
- PhysicalFormat. Identifies whether the data is "raw" or "cooked", i.e.
|
||||
if it's a series of blocks/sectors or raw nibbles.
|
||||
|
||||
- SectorOrder. ProDOS, DOS, Physical.
|
||||
|
||||
- FSFormat. What type of filesystem is in this image. This includes
|
||||
common formats, like DOS 3.3 and ProDOS, as well as meta-formats like
|
||||
UNIDOS and Macintosh partition.
|
||||
|
||||
|
||||
#### DiskFS ####
|
||||
|
||||
A `DiskFS` instance is typically paired with a DiskImg. It represents
|
||||
the filesystem, operating at a level roughly equivalent to a GS/OS
|
||||
File System Translator (FST).
|
||||
|
||||
Using DiskFS, you can read, write, and rename files, format disks,
|
||||
check how much free space is available, and search for sub-volumes.
|
||||
|
||||
|
||||
#### A2File ####
|
||||
|
||||
One instance of `A2File` represents one file on disk. The object holds
|
||||
the filename and attributes, and provides a call to open the file.
|
||||
|
||||
|
||||
#### A2FileDescr ####
|
||||
|
||||
This is essentially a file descriptor for an Apple II file. You can
|
||||
read or write data.
|
||||
|
||||
To make the implementation easier, files must be written with a single
|
||||
write call, and only one fork of a forked file may be open.
|
||||
|
308
diskimg/SCSIDefs.h
Normal file
308
diskimg/SCSIDefs.h
Normal file
@ -0,0 +1,308 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Definitions for SCSI (Small Computer System Interface).
|
||||
*
|
||||
* These structures and defines are passed to the SCSI driver, so they work
|
||||
* equally well for ASPI and SPTI
|
||||
*
|
||||
* Consult the SCSI-2 and MMC-2 specifications for details.
|
||||
*/
|
||||
#ifndef DISKIMG_SCSIDEFS_H
|
||||
#define DISKIMG_SCSIDEFS_H
|
||||
|
||||
/*
|
||||
* SCSI-2 operation codes.
|
||||
*/
|
||||
typedef enum {
|
||||
kScsiOpTestUnitReady = 0x00,
|
||||
kScsiOpRezeroUnit = 0x01,
|
||||
kScsiOpRewind = 0x01,
|
||||
kScsiOpRequestBlockAddr = 0x02,
|
||||
kScsiOpRequestSense = 0x03,
|
||||
kScsiOpFormatUnit = 0x04,
|
||||
kScsiOpReadBlockLimits = 0x05,
|
||||
kScsiOpReassignBlocks = 0x07,
|
||||
kScsiOpRead6 = 0x08,
|
||||
kScsiOpReceive = 0x08,
|
||||
kScsiOpWrite6 = 0x0a,
|
||||
kScsiOpPrint = 0x0a,
|
||||
kScsiOpSend = 0x0a,
|
||||
kScsiOpSeek6 = 0x0b,
|
||||
kScsiOpTrackSelect = 0x0b,
|
||||
kScsiOpSlewPrint = 0x0b,
|
||||
kScsiOpSeekBlock = 0x0c,
|
||||
kScsiOpPartition = 0x0d,
|
||||
kScsiOpReadReverse = 0x0f,
|
||||
kScsiOpWriteFilemarks = 0x10,
|
||||
kScsiOpFlushBuffer = 0x10,
|
||||
kScsiOpSpace = 0x11,
|
||||
kScsiOpInquiry = 0x12,
|
||||
kScsiOpVerify6 = 0x13,
|
||||
kScsiOpRecoverBufferedData = 0x14,
|
||||
kScsiOpModeSelect = 0x15,
|
||||
kScsiOpReserveUnit = 0x16,
|
||||
kScsiOpReleaseUnit = 0x17,
|
||||
kScsiOpCopy = 0x18,
|
||||
kScsiOpErase = 0x19,
|
||||
kScsiOpModeSense = 0x1a,
|
||||
kScsiOpStartStopUnit = 0x1b,
|
||||
kScsiOpStopPrint = 0x1b,
|
||||
kScsiOpLoadUnload = 0x1b,
|
||||
kScsiOpReceiveDiagnosticResults = 0x1c,
|
||||
kScsiOpSendDiagnostic = 0x1d,
|
||||
kScsiOpMediumRemoval = 0x1e,
|
||||
kScsiOpReadFormattedCapacity = 0x23,
|
||||
kScsiOpReadCapacity = 0x25,
|
||||
kScsiOpRead = 0x28, // READ(10)
|
||||
kScsiOpWrite = 0x2a, // WRITE(10)
|
||||
kScsiOpSeek = 0x2b,
|
||||
kScsiOpLocate = 0x2b,
|
||||
kScsiOpPositionToElement = 0x2b,
|
||||
kScsiOpWriteVerify = 0x2e,
|
||||
kScsiOpVerify = 0x2f, // VERIFY(10)
|
||||
kScsiOpSearchDataHigh = 0x30,
|
||||
kScsiOpSearchDataEqual = 0x31,
|
||||
kScsiOpSearchDataLow = 0x32,
|
||||
kScsiOpSetLimits = 0x33,
|
||||
kScsiOpReadPosition = 0x34,
|
||||
kScsiOpSynchronizeCache = 0x35,
|
||||
kScsiOpCompare = 0x39,
|
||||
kScsiOpCopyAndVerify = 0x3a,
|
||||
kScsiOpWriteBuffer = 0x3b,
|
||||
kScsiOpReadBuffer = 0x3c,
|
||||
kScsiOpChangeDefinition = 0x40,
|
||||
kScsiOpReadSubChannel = 0x42,
|
||||
kScsiOpReadTOC = 0x43, // READ TOC/PMA/ATIP
|
||||
kScsiOpReadHeader = 0x44,
|
||||
kScsiOpPlayAudio = 0x45,
|
||||
kScsiOpPlayAudioMSF = 0x47,
|
||||
kScsiOpPlayTrackIndex = 0x48,
|
||||
kScsiOpPlayTrackRelative = 0x49,
|
||||
kScsiOpPauseResume = 0x4b,
|
||||
kScsiOpLogSelect = 0x4c,
|
||||
kScsiOpLogSense = 0x4c,
|
||||
kScsiOpStopPlayScan = 0x4e,
|
||||
kScsiOpReadDiscInformation = 0x51,
|
||||
kScsiOpReadTrackInformation = 0x52,
|
||||
kScsiOpSendOPCInformation = 0x54,
|
||||
kScsiOpModeSelect10 = 0x55,
|
||||
kScsiOpRepairTrack = 0x58,
|
||||
kScsiOpModeSense10 = 0x5a,
|
||||
kScsiOpReportLuns = 0xa0,
|
||||
kScsiOpVerify12 = 0xa2,
|
||||
kScsiOpSendKey = 0xa3,
|
||||
kScsiOpReportKey = 0xa4,
|
||||
kScsiOpMoveMedium = 0xa5,
|
||||
kScsiOpLoadUnloadSlot = 0xa6,
|
||||
kScsiOpExchangeMedium = 0xa6,
|
||||
kScsiOpSetReadAhead = 0xa7,
|
||||
kScsiOpReadDVDStructure = 0xad,
|
||||
kScsiOpWriteAndVerify = 0xae,
|
||||
kScsiOpRequestVolElement = 0xb5,
|
||||
kScsiOpSendVolumeTag = 0xb6,
|
||||
kScsiOpReadElementStatus = 0xb8,
|
||||
kScsiOpReadCDMSF = 0xb9,
|
||||
kScsiOpScanCD = 0xba,
|
||||
kScsiOpSetCDSpeed = 0xbb,
|
||||
kScsiOpPlayCD = 0xbc,
|
||||
kScsiOpMechanismStatus = 0xbd,
|
||||
kScsiOpReadCD = 0xbe,
|
||||
kScsiOpInitElementRange = 0xe7,
|
||||
} SCSIOperationCode;
|
||||
|
||||
|
||||
/*
|
||||
* SCSI status codes.
|
||||
*/
|
||||
typedef enum {
|
||||
kScsiStatGood = 0x00,
|
||||
kScsiStatCheckCondition = 0x02,
|
||||
kScsiStatConditionMet = 0x04,
|
||||
kScsiStatBusy = 0x08,
|
||||
kScsiStatIntermediate = 0x10,
|
||||
kScsiStatIntermediateCondMet = 0x14,
|
||||
kScsiStatReservationConflict = 0x18,
|
||||
kScsiStatCommandTerminated = 0x22,
|
||||
kScsiStatQueueFull = 0x28,
|
||||
} SCSIStatus;
|
||||
|
||||
/*
|
||||
* SCSI sense codes.
|
||||
*/
|
||||
typedef enum {
|
||||
kScsiSenseNoSense = 0x00,
|
||||
kScsiSenseRecoveredError = 0x01,
|
||||
kScsiSenseNotReady = 0x02,
|
||||
kScsiSenseMediumError = 0x03,
|
||||
kScsiSenseHardwareError = 0x04,
|
||||
kScsiSenseIllegalRequest = 0x05,
|
||||
kScsiSenseUnitAttention = 0x06,
|
||||
kScsiSenseDataProtect = 0x07,
|
||||
kScsiSenseBlankCheck = 0x08,
|
||||
kScsiSenseUnqiue = 0x09,
|
||||
kScsiSenseCopyAborted = 0x0a,
|
||||
kScsiSenseAbortedCommand = 0x0b,
|
||||
kScsiSenseEqual = 0x0c,
|
||||
kScsiSenseVolOverflow = 0x0d,
|
||||
kScsiSenseMiscompare = 0x0e,
|
||||
kScsiSenseReserved = 0x0f,
|
||||
} SCSISenseCode;
|
||||
|
||||
|
||||
/*
|
||||
* SCSI additional sense codes.
|
||||
*/
|
||||
typedef enum {
|
||||
kScsiAdSenseNoSense = 0x00,
|
||||
kScsiAdSenseInvalidMedia = 0x30,
|
||||
kScsiAdSenseNoMediaInDevice = 0x3a,
|
||||
} SCSIAdSenseCode;
|
||||
|
||||
/*
|
||||
* SCSI device types.
|
||||
*/
|
||||
typedef enum {
|
||||
kScsiDevTypeDASD = 0x00, // Disk Device
|
||||
kScsiDevTypeSEQD = 0x01, // Tape Device
|
||||
kScsiDevTypePRNT = 0x02, // Printer
|
||||
kScsiDevTypePROC = 0x03, // Processor
|
||||
kScsiDevTypeWORM = 0x04, // Write-once read-multiple
|
||||
kScsiDevTypeCDROM = 0x05, // CD-ROM device
|
||||
kScsiDevTypeSCAN = 0x06, // Scanner device
|
||||
kScsiDevTypeOPTI = 0x07, // Optical memory device
|
||||
kScsiDevTypeJUKE = 0x08, // Medium Changer device
|
||||
kScsiDevTypeCOMM = 0x09, // Communications device
|
||||
kScsiDevTypeRESL = 0x0a, // Reserved (low)
|
||||
kScsiDevTypeRESH = 0x1e, // Reserved (high)
|
||||
kScsiDevTypeUNKNOWN = 0x1f, // Unknown or no device type
|
||||
} SCSIDeviceType;
|
||||
|
||||
/*
|
||||
* Generic 6-byte request block.
|
||||
*/
|
||||
typedef struct CDB6 {
|
||||
unsigned char operationCode;
|
||||
unsigned char immediate : 1;
|
||||
unsigned char commandUniqueBits : 4;
|
||||
unsigned char logicalUnitNumber : 3;
|
||||
unsigned char commandUniqueBytes[3];
|
||||
unsigned char link : 1;
|
||||
unsigned char flag : 1;
|
||||
unsigned char reserved : 4;
|
||||
unsigned char vendorUnique : 2;
|
||||
} CDB6;
|
||||
|
||||
/*
|
||||
* Generic 10-byte request block.
|
||||
*
|
||||
* Use for READ(10), READ CAPACITY.
|
||||
*/
|
||||
typedef struct CDB10 {
|
||||
unsigned char operationCode;
|
||||
unsigned char relativeAddress : 1;
|
||||
unsigned char reserved1 : 2;
|
||||
unsigned char forceUnitAccess : 1;
|
||||
unsigned char disablePageOut : 1;
|
||||
unsigned char logicalUnitNumber : 3;
|
||||
unsigned char logicalBlockAddr0; // MSB
|
||||
unsigned char logicalBlockAddr1;
|
||||
unsigned char logicalBlockAddr2;
|
||||
unsigned char logicalBlockAddr3; // LSB
|
||||
unsigned char reserved2;
|
||||
unsigned char transferLength0; // MSB
|
||||
unsigned char transferLength1; // LSB
|
||||
unsigned char control;
|
||||
} CDB10;
|
||||
|
||||
/*
|
||||
* INQUIRY request block.
|
||||
*/
|
||||
typedef struct CDB6Inquiry {
|
||||
unsigned char operationCode;
|
||||
unsigned char EVPD : 1;
|
||||
unsigned char reserved1 : 4;
|
||||
unsigned char logicalUnitNumber : 3;
|
||||
unsigned char pageCode;
|
||||
unsigned char reserved2;
|
||||
unsigned char allocationLength;
|
||||
unsigned char control;
|
||||
} CDB6Inquiry;
|
||||
|
||||
/*
|
||||
* Sense data (ASPI SenseArea).
|
||||
*/
|
||||
typedef struct CDB_SenseData {
|
||||
unsigned char errorCode:7;
|
||||
unsigned char valid:1;
|
||||
unsigned char segmentNumber;
|
||||
unsigned char senseKey:4;
|
||||
unsigned char reserved:1;
|
||||
unsigned char incorrectLength:1;
|
||||
unsigned char endOfMedia:1;
|
||||
unsigned char fileMark:1;
|
||||
unsigned char information[4];
|
||||
unsigned char additionalSenseLength;
|
||||
unsigned char commandSpecificInformation[4];
|
||||
unsigned char additionalSenseCode; // ASC
|
||||
unsigned char additionalSenseCodeQualifier; // ASCQ
|
||||
unsigned char fieldReplaceableUnitCode;
|
||||
unsigned char senseKeySpecific[3];
|
||||
} CDB_SenseData;
|
||||
|
||||
/*
|
||||
* Default sense buffer size.
|
||||
*/
|
||||
#define kSenseBufferSize 18
|
||||
|
||||
|
||||
//#define INQUIRYDATABUFFERSIZE 36
|
||||
|
||||
/*
|
||||
* Result from INQUIRY.
|
||||
*/
|
||||
typedef struct CDB_InquiryData {
|
||||
unsigned char deviceType : 5;
|
||||
unsigned char deviceTypeQualifier : 3;
|
||||
unsigned char deviceTypeModifier : 7;
|
||||
unsigned char removableMedia : 1;
|
||||
unsigned char versions;
|
||||
unsigned char responseDataFormat : 4;
|
||||
unsigned char reserved1 : 2;
|
||||
unsigned char trmIOP : 1;
|
||||
unsigned char AENC : 1;
|
||||
unsigned char additionalLength;
|
||||
unsigned char reserved2[2];
|
||||
unsigned char softReset : 1;
|
||||
unsigned char commandQueue : 1;
|
||||
unsigned char reserved3 : 1;
|
||||
unsigned char linkedCommands : 1;
|
||||
unsigned char synchronous : 1;
|
||||
unsigned char wide16Bit : 1;
|
||||
unsigned char wide32Bit : 1;
|
||||
unsigned char relativeAddressing : 1;
|
||||
unsigned char vendorId[8];
|
||||
unsigned char productId[16];
|
||||
unsigned char productRevisionLevel[4];
|
||||
unsigned char vendorSpecific[20];
|
||||
unsigned char reserved4[40];
|
||||
} CDB_InquiryData;
|
||||
|
||||
/*
|
||||
* Result from READ CAPACITY.
|
||||
*/
|
||||
typedef struct CDB_ReadCapacityData {
|
||||
unsigned char logicalBlockAddr0; // MSB
|
||||
unsigned char logicalBlockAddr1;
|
||||
unsigned char logicalBlockAddr2;
|
||||
unsigned char logicalBlockAddr3; // LSB
|
||||
unsigned char bytesPerBlock0; // MSB
|
||||
unsigned char bytesPerBlock1;
|
||||
unsigned char bytesPerBlock2;
|
||||
unsigned char bytesPerBlock3; // LSB
|
||||
} CDB_ReadCapacityData;
|
||||
|
||||
#endif /*DISKIMG_SCSIDEFS_H*/
|
137
diskimg/SPTI.cpp
Normal file
137
diskimg/SPTI.cpp
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Implementation of some SPTI functions.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "DiskImgPriv.h"
|
||||
#include "SCSIDefs.h"
|
||||
#include "CP_ntddscsi.h"
|
||||
#include "SPTI.h"
|
||||
|
||||
|
||||
/*
|
||||
* Get the capacity of the device.
|
||||
*
|
||||
* Returns the LBA of the last valid block and the device's block size.
|
||||
*/
|
||||
/*static*/ DIError SPTI::GetDeviceCapacity(HANDLE handle, uint32_t* pLastBlock,
|
||||
uint32_t* pBlockSize)
|
||||
{
|
||||
SCSI_PASS_THROUGH_DIRECT sptd;
|
||||
uint32_t lba, blockLen;
|
||||
CDB_ReadCapacityData dataBuf;
|
||||
DWORD cb;
|
||||
BOOL status;
|
||||
|
||||
assert(sizeof(dataBuf) == 8); // READ CAPACITY returns two longs
|
||||
|
||||
memset(&sptd, 0, sizeof(sptd));
|
||||
sptd.Length = sizeof(sptd);
|
||||
sptd.PathId = 0; // SCSI card ID filled in by ioctl
|
||||
sptd.TargetId = 0; // SCSI target ID filled in by ioctl
|
||||
sptd.Lun = 0; // SCSI lun ID filled in by ioctl
|
||||
sptd.CdbLength = 10; // CDB size is 10 for READ CAPACITY
|
||||
sptd.SenseInfoLength = 0; // don't return any sense data
|
||||
sptd.DataIn = SCSI_IOCTL_DATA_IN; // will be data from drive
|
||||
sptd.DataTransferLength = sizeof(dataBuf);
|
||||
sptd.TimeOutValue = 10; // SCSI timeout value, in seconds
|
||||
sptd.DataBuffer = (PVOID) &dataBuf;
|
||||
sptd.SenseInfoOffset = 0; // offset to request-sense buffer
|
||||
|
||||
CDB10* pCdb = (CDB10*) &sptd.Cdb;
|
||||
pCdb->operationCode = kScsiOpReadCapacity;
|
||||
// rest of CDB is zero
|
||||
|
||||
status = ::DeviceIoControl(handle, IOCTL_SCSI_PASS_THROUGH_DIRECT,
|
||||
&sptd, sizeof(sptd), NULL, 0, &cb, NULL);
|
||||
|
||||
if (!status) {
|
||||
DWORD lastError = ::GetLastError();
|
||||
LOGE("DeviceIoControl(SCSI READ CAPACITY) failed, err=%ld",
|
||||
::GetLastError());
|
||||
if (lastError == ERROR_IO_DEVICE) // no disc in drive
|
||||
return kDIErrDeviceNotReady;
|
||||
else
|
||||
return kDIErrSPTIFailure;
|
||||
}
|
||||
|
||||
lba = (uint32_t) dataBuf.logicalBlockAddr0 << 24 |
|
||||
(uint32_t) dataBuf.logicalBlockAddr1 << 16 |
|
||||
(uint32_t) dataBuf.logicalBlockAddr2 << 8 |
|
||||
(uint32_t) dataBuf.logicalBlockAddr3;
|
||||
blockLen = (uint32_t) dataBuf.bytesPerBlock0 << 24 |
|
||||
(uint32_t) dataBuf.bytesPerBlock1 << 16 |
|
||||
(uint32_t) dataBuf.bytesPerBlock2 << 8 |
|
||||
(uint32_t) dataBuf.bytesPerBlock3;
|
||||
|
||||
*pLastBlock = lba;
|
||||
*pBlockSize = blockLen;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Read one or more blocks from the specified SCSI device.
|
||||
*
|
||||
* "buf" must be able to hold (numBlocks * blockSize) bytes.
|
||||
*/
|
||||
/*static*/ DIError SPTI::ReadBlocks(HANDLE handle, long startBlock,
|
||||
short numBlocks, long blockSize, void* buf)
|
||||
{
|
||||
SCSI_PASS_THROUGH_DIRECT sptd;
|
||||
DWORD cb;
|
||||
BOOL status;
|
||||
|
||||
assert(startBlock >= 0);
|
||||
assert(numBlocks > 0);
|
||||
assert(buf != NULL);
|
||||
|
||||
LOGD(" SPTI phys read block (%ld) %d", startBlock, numBlocks);
|
||||
|
||||
memset(&sptd, 0, sizeof(sptd));
|
||||
sptd.Length = sizeof(sptd); // size of struct (+ request-sense buffer)
|
||||
sptd.ScsiStatus = 0;
|
||||
sptd.PathId = 0; // SCSI card ID filled in by ioctl
|
||||
sptd.TargetId = 0; // SCSI target ID filled in by ioctl
|
||||
sptd.Lun = 0; // SCSI lun ID filled in by ioctl
|
||||
sptd.CdbLength = 10; // CDB size is 10 for READ CAPACITY
|
||||
sptd.SenseInfoLength = 0; // don't return any sense data
|
||||
sptd.DataIn = SCSI_IOCTL_DATA_IN; // will be data from drive
|
||||
sptd.DataTransferLength = blockSize * numBlocks;
|
||||
sptd.TimeOutValue = 10; // SCSI timeout value (in seconds)
|
||||
sptd.DataBuffer = (PVOID) buf;
|
||||
sptd.SenseInfoOffset = 0; // offset from start of struct to request-sense
|
||||
|
||||
CDB10* pCdb = (CDB10*) &sptd.Cdb;
|
||||
pCdb->operationCode = kScsiOpRead;
|
||||
pCdb->logicalBlockAddr0 = (uint8_t) (startBlock >> 24); // MSB
|
||||
pCdb->logicalBlockAddr1 = (uint8_t) (startBlock >> 16);
|
||||
pCdb->logicalBlockAddr2 = (uint8_t) (startBlock >> 8);
|
||||
pCdb->logicalBlockAddr3 = (uint8_t) startBlock; // LSB
|
||||
pCdb->transferLength0 = (uint8_t) (numBlocks >> 8); // MSB
|
||||
pCdb->transferLength1 = (uint8_t) numBlocks; // LSB
|
||||
|
||||
status = ::DeviceIoControl(handle, IOCTL_SCSI_PASS_THROUGH_DIRECT,
|
||||
&sptd, sizeof(sptd), NULL, 0, &cb, NULL);
|
||||
|
||||
if (!status) {
|
||||
LOGE("DeviceIoControl(SCSI READ(10)) failed, err=%ld",
|
||||
::GetLastError());
|
||||
return kDIErrReadFailed; // close enough
|
||||
}
|
||||
if (sptd.ScsiStatus != 0) {
|
||||
LOGE("SCSI READ(10) failed, status=%d", sptd.ScsiStatus);
|
||||
return kDIErrReadFailed;
|
||||
}
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
#endif /*_WIN32*/
|
40
diskimg/SPTI.h
Normal file
40
diskimg/SPTI.h
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Declarations for the Win32 SCSI Pass-Through Interface.
|
||||
*/
|
||||
#ifndef DISKIMG_SPTI_H
|
||||
#define DISKIMG_SPTI_H
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
namespace DiskImgLib {
|
||||
|
||||
/*
|
||||
* This is currently implemented as a set of static functions. Do not
|
||||
* instantiate the class.
|
||||
*/
|
||||
class DISKIMG_API SPTI {
|
||||
public:
|
||||
// Read blocks from the device.
|
||||
static DIError ReadBlocks(HANDLE handle, long startBlock, short numBlocks,
|
||||
long blockSize, void* buf);
|
||||
|
||||
// Get the capacity, expressed as the highest-available LBA and the device
|
||||
// block size.
|
||||
static DIError GetDeviceCapacity(HANDLE handle, uint32_t* pLastBlock,
|
||||
uint32_t* pBlockSize);
|
||||
|
||||
private:
|
||||
SPTI(void) {}
|
||||
~SPTI(void) {}
|
||||
};
|
||||
|
||||
} // namespace DiskImgLib
|
||||
|
||||
#endif /*_WIN32*/
|
||||
|
||||
#endif /*DISKIMG_SPTI_H*/
|
13
diskimg/StdAfx.cpp
Normal file
13
diskimg/StdAfx.cpp
Normal file
@ -0,0 +1,13 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
// stdafx.cpp : source file that includes just the standard includes
|
||||
// diskimg.pch will be the pre-compiled header
|
||||
// stdafx.obj will contain the pre-compiled type information
|
||||
|
||||
#include "StdAfx.h"
|
||||
|
||||
// TODO: reference any additional headers you need in STDAFX.H
|
||||
// and not in this file
|
75
diskimg/StdAfx.h
Normal file
75
diskimg/StdAfx.h
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
// stdafx.h : include file for standard system include files,
|
||||
// or project specific include files that are used frequently, but
|
||||
// are changed infrequently
|
||||
//
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
/* UNIX includes */
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <fcntl.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#define O_BINARY 0
|
||||
|
||||
#define HAVE_VSNPRINTF
|
||||
#define HAVE_FSEEKO
|
||||
#define HAVE_FTRUNCATE
|
||||
|
||||
// gcc wants special compile options; just ignore this for now
|
||||
#define override
|
||||
|
||||
#else /*_WIN32*/
|
||||
|
||||
#if !defined(AFX_STDAFX_H__1CB7B33E_42BF_4A98_B814_4198EA8ACC58__INCLUDED_)
|
||||
#define AFX_STDAFX_H__1CB7B33E_42BF_4A98_B814_4198EA8ACC58__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#define HAVE_WINDOWS_CDROM // enable CD-ROM access under Windows
|
||||
#define HAVE_CHSIZE
|
||||
|
||||
|
||||
// Insert your headers here
|
||||
# define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
|
||||
|
||||
#include "../app/targetver.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <atlstr.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#ifdef HAVE_WINDOWS_CDROM
|
||||
# include <winioctl.h>
|
||||
#endif
|
||||
|
||||
#ifndef _SSIZE_T_DEFINED
|
||||
typedef unsigned int ssize_t;
|
||||
#define _SSIZE_T_DEFINED
|
||||
#endif
|
||||
|
||||
#define HAVE__VSNPRINTF
|
||||
#define strcasecmp stricmp
|
||||
#define snprintf _snprintf
|
||||
|
||||
//{{AFX_INSERT_LOCATION}}
|
||||
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
|
||||
|
||||
#endif // !defined(AFX_STDAFX_H__1CB7B33E_42BF_4A98_B814_4198EA8ACC58__INCLUDED_)
|
||||
#endif /*_WIN32*/
|
567
diskimg/TwoImg.cpp
Normal file
567
diskimg/TwoImg.cpp
Normal file
@ -0,0 +1,567 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Support for 2MG/2IMG wrapper files.
|
||||
*
|
||||
* This needs to be directly accessible from external applications, so try not
|
||||
* to hook into private DiskImg state.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "TwoImg.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
///*static*/ const char* TwoImgHeader::kMagic = "2IMG"; // file magic #
|
||||
///*static*/ const char* TwoImgHeader::kCreator = "CdrP"; // our "creator" ID
|
||||
|
||||
|
||||
/*
|
||||
* Initialize a header to default values, using the supplied image length
|
||||
* where appropriate.
|
||||
*
|
||||
* Sets up a header for a 2MG file with the disk data and nothing else.
|
||||
*
|
||||
* Returns 0 on success, -1 if one of the arguments was bad.
|
||||
*/
|
||||
int TwoImgHeader::InitHeader(int imageFormat, uint32_t imageSize,
|
||||
uint32_t imageBlockCount)
|
||||
{
|
||||
if (imageSize == 0)
|
||||
return -1;
|
||||
if (imageFormat < (int) kImageFormatDOS || imageFormat > (int) kImageFormatNibble)
|
||||
return -1;
|
||||
|
||||
if (imageFormat != kImageFormatNibble &&
|
||||
imageSize != imageBlockCount * 512)
|
||||
{
|
||||
LOGW("2MG InitHeader: bad sizes %d %u %u", imageFormat,
|
||||
imageSize, imageBlockCount);
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(fComment == NULL);
|
||||
|
||||
//memcpy(fMagic, kMagic, 4);
|
||||
//memcpy(fCreator, kCreator, 4);
|
||||
fMagic = kMagic;
|
||||
fCreator = kCreatorCiderPress;
|
||||
fHeaderLen = kOurHeaderLen;
|
||||
fVersion = kOurVersion;
|
||||
fImageFormat = imageFormat;
|
||||
fFlags = 0;
|
||||
fNumBlocks = imageBlockCount;
|
||||
fDataOffset = kOurHeaderLen;
|
||||
fDataLen = imageSize;
|
||||
fCmtOffset = 0;
|
||||
fCmtLen = 0;
|
||||
fCreatorOffset = 0;
|
||||
fCreatorLen = 0;
|
||||
fSpare[0] = fSpare[1] = fSpare[2] = fSpare[3] = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the DOS volume number.
|
||||
*
|
||||
* If not set, we currently return the initial value (-1), rather than the
|
||||
* default volume number. For the way we currently make use of this, this
|
||||
* makes the most sense.
|
||||
*/
|
||||
int16_t TwoImgHeader::GetDOSVolumeNum(void) const
|
||||
{
|
||||
assert(fFlags & kDOSVolumeSet);
|
||||
return fDOSVolumeNum;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the DOS volume number.
|
||||
*/
|
||||
void TwoImgHeader::SetDOSVolumeNum(short dosVolumeNum)
|
||||
{
|
||||
assert(dosVolumeNum >= 0 && dosVolumeNum < 256);
|
||||
fFlags |= dosVolumeNum;
|
||||
fFlags |= kDOSVolumeSet;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the comment.
|
||||
*/
|
||||
void TwoImgHeader::SetComment(const char* comment)
|
||||
{
|
||||
delete[] fComment;
|
||||
if (comment == NULL) {
|
||||
fComment = NULL;
|
||||
} else {
|
||||
fComment = new char[strlen(comment)+1];
|
||||
if (fComment != NULL)
|
||||
strcpy(fComment, comment);
|
||||
// else throw alloc failure
|
||||
}
|
||||
|
||||
if (fComment == NULL) {
|
||||
fCmtLen = 0;
|
||||
fCmtOffset = 0;
|
||||
if (fCreatorOffset > 0)
|
||||
fCreatorOffset = fDataOffset + fDataLen;
|
||||
} else {
|
||||
fCmtLen = strlen(fComment);
|
||||
fCmtOffset = fDataOffset + fDataLen;
|
||||
if (fCreatorOffset > 0)
|
||||
fCreatorOffset = fCmtOffset + fCmtLen;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the creator chunk.
|
||||
*/
|
||||
void TwoImgHeader::SetCreatorChunk(const void* chunk, long len)
|
||||
{
|
||||
assert(len >= 0);
|
||||
|
||||
delete[] fCreatorChunk;
|
||||
if (chunk == NULL || len == 0) {
|
||||
fCreatorChunk = NULL;
|
||||
} else {
|
||||
fCreatorChunk = new char[len];
|
||||
if (fCreatorChunk != NULL)
|
||||
memcpy(fCreatorChunk, chunk, len);
|
||||
// else throw alloc failure
|
||||
}
|
||||
|
||||
if (fCreatorChunk == NULL) {
|
||||
fCreatorLen = 0;
|
||||
fCreatorOffset = 0;
|
||||
} else {
|
||||
fCreatorLen = len;
|
||||
if (fCmtOffset > 0)
|
||||
fCreatorOffset = fCmtOffset + fCmtLen;
|
||||
else
|
||||
fCreatorOffset = fDataOffset + fDataLen;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the header from a 2IMG file. Pass in "totalLength" as a sanity check.
|
||||
*
|
||||
* THOUGHT: provide a simple GenericFD conversion for FILE*, and then just
|
||||
* call the GenericFD version of ReadHeader.
|
||||
*
|
||||
* Returns 0 on success, nonzero on error or invalid header.
|
||||
*/
|
||||
int TwoImgHeader::ReadHeader(FILE* fp, uint32_t totalLength)
|
||||
{
|
||||
uint8_t buf[kOurHeaderLen];
|
||||
|
||||
fread(buf, kOurHeaderLen, 1, fp);
|
||||
if (ferror(fp))
|
||||
return errno ? errno : -1;
|
||||
|
||||
if (UnpackHeader(buf, totalLength) != 0)
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* Extract the comment, if any.
|
||||
*/
|
||||
if (fCmtOffset > 0 && fCmtLen > 0) {
|
||||
if (GetChunk(fp, fCmtOffset - kOurHeaderLen, fCmtLen,
|
||||
(void**) &fComment) != 0)
|
||||
{
|
||||
LOGI("Throwing comment away");
|
||||
fCmtLen = 0;
|
||||
fCmtOffset = 0;
|
||||
} else {
|
||||
LOGI("Got comment: '%s'", fComment);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract the creator chunk, if any.
|
||||
*/
|
||||
if (fCreatorOffset > 0 && fCreatorLen > 0) {
|
||||
if (GetChunk(fp, fCreatorOffset - kOurHeaderLen, fCreatorLen,
|
||||
(void**) &fCreatorChunk) != 0)
|
||||
{
|
||||
LOGI("Throwing creator chunk away");
|
||||
fCreatorLen = 0;
|
||||
fCreatorOffset = 0;
|
||||
} else {
|
||||
//LOGI("Got creator chunk: '%s'", fCreatorChunk);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the header from a 2IMG file. Pass in "totalLength" as a sanity check.
|
||||
*
|
||||
* Returns 0 on success, nonzero on error or invalid header.
|
||||
*/
|
||||
int TwoImgHeader::ReadHeader(GenericFD* pGFD, uint32_t totalLength)
|
||||
{
|
||||
DIError dierr;
|
||||
uint8_t buf[kOurHeaderLen];
|
||||
|
||||
dierr = pGFD->Read(buf, kOurHeaderLen);
|
||||
if (dierr != kDIErrNone)
|
||||
return -1;
|
||||
|
||||
if (UnpackHeader(buf, totalLength) != 0)
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* Extract the comment, if any.
|
||||
*/
|
||||
if (fCmtOffset > 0 && fCmtLen > 0) {
|
||||
if (GetChunk(pGFD, fCmtOffset - kOurHeaderLen, fCmtLen,
|
||||
(void**) &fComment) != 0)
|
||||
{
|
||||
LOGI("Throwing comment away");
|
||||
fCmtLen = 0;
|
||||
fCmtOffset = 0;
|
||||
} else {
|
||||
LOGI("Got comment: '%s'", fComment);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract the creator chunk, if any.
|
||||
*/
|
||||
if (fCreatorOffset > 0 && fCreatorLen > 0) {
|
||||
if (GetChunk(pGFD, fCreatorOffset - kOurHeaderLen, fCreatorLen,
|
||||
(void**) &fCreatorChunk) != 0)
|
||||
{
|
||||
LOGI("Throwing creator chunk away");
|
||||
fCreatorLen = 0;
|
||||
fCreatorOffset = 0;
|
||||
} else {
|
||||
//LOGI("Got creator chunk: '%s'", fCreatorChunk);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Grab a chunk of data from a relative offset.
|
||||
*/
|
||||
int TwoImgHeader::GetChunk(GenericFD* pGFD, di_off_t relOffset, long len,
|
||||
void** pBuf)
|
||||
{
|
||||
DIError dierr;
|
||||
di_off_t curPos;
|
||||
|
||||
/* remember current offset */
|
||||
curPos = pGFD->Tell();
|
||||
|
||||
/* seek out to chunk and grab it */
|
||||
dierr = pGFD->Seek(relOffset, kSeekCur);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI("2MG seek to chunk failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(*pBuf == NULL);
|
||||
*pBuf = new char[len+1]; // one extra, for null termination
|
||||
|
||||
dierr = pGFD->Read(*pBuf, len);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI("2MG chunk read failed");
|
||||
delete[] (char*) (*pBuf);
|
||||
*pBuf = NULL;
|
||||
(void) pGFD->Seek(curPos, kSeekSet);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* null-terminate, in case this was a string */
|
||||
((char*) *pBuf)[len] = '\0';
|
||||
|
||||
/* seek back to where we were */
|
||||
(void) pGFD->Seek(curPos, kSeekSet);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Grab a chunk of data from a relative offset.
|
||||
*/
|
||||
int TwoImgHeader::GetChunk(FILE* fp, di_off_t relOffset, long len,
|
||||
void** pBuf)
|
||||
{
|
||||
long curPos;
|
||||
int count;
|
||||
|
||||
/* remember current offset */
|
||||
curPos = ftell(fp);
|
||||
LOGI("Current offset=%ld", curPos);
|
||||
|
||||
/* seek out to chunk and grab it */
|
||||
if (fseek(fp, (long) relOffset, SEEK_CUR) == -1) {
|
||||
LOGI("2MG seek to chunk failed");
|
||||
return errno ? errno : -1;;
|
||||
}
|
||||
|
||||
assert(*pBuf == NULL);
|
||||
*pBuf = new char[len+1]; // one extra, for null termination
|
||||
|
||||
count = fread(*pBuf, len, 1, fp);
|
||||
if (!count || ferror(fp) || feof(fp)) {
|
||||
LOGI("2MG chunk read failed");
|
||||
delete[] (char*) (*pBuf);
|
||||
*pBuf = NULL;
|
||||
(void) fseek(fp, curPos, SEEK_SET);
|
||||
clearerr(fp);
|
||||
return errno ? errno : -1;;
|
||||
}
|
||||
|
||||
/* null-terminate, in case this was a string */
|
||||
((char*) *pBuf)[len] = '\0';
|
||||
|
||||
/* seek back to where we were */
|
||||
(void) fseek(fp, curPos, SEEK_SET);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Unpack the 64-byte 2MG header.
|
||||
*
|
||||
* Performs some sanity checks. Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
int TwoImgHeader::UnpackHeader(const uint8_t* buf, uint32_t totalLength)
|
||||
{
|
||||
fMagic = GetLongBE(&buf[0x00]);
|
||||
fCreator = GetLongBE(&buf[0x04]);
|
||||
fHeaderLen = GetShortLE(&buf[0x08]);
|
||||
fVersion = GetShortLE(&buf[0x0a]);
|
||||
fImageFormat = GetLongLE(&buf[0x0c]);
|
||||
fFlags = GetLongLE(&buf[0x10]);
|
||||
fNumBlocks = GetLongLE(&buf[0x14]);
|
||||
fDataOffset = GetLongLE(&buf[0x18]);
|
||||
fDataLen = GetLongLE(&buf[0x1c]);
|
||||
fCmtOffset = GetLongLE(&buf[0x20]);
|
||||
fCmtLen = GetLongLE(&buf[0x24]);
|
||||
fCreatorOffset = GetLongLE(&buf[0x28]);
|
||||
fCreatorLen = GetLongLE(&buf[0x2c]);
|
||||
fSpare[0] = GetLongLE(&buf[0x30]);
|
||||
fSpare[1] = GetLongLE(&buf[0x34]);
|
||||
fSpare[2] = GetLongLE(&buf[0x38]);
|
||||
fSpare[3] = GetLongLE(&buf[0x3c]);
|
||||
|
||||
fMagicStr[0] = (char) (fMagic >> 24);
|
||||
fMagicStr[1] = (char) (fMagic >> 16);
|
||||
fMagicStr[2] = (char) (fMagic >> 8);
|
||||
fMagicStr[3] = (char) fMagic;
|
||||
fMagicStr[4] = '\0';
|
||||
fCreatorStr[0] = (char) (fCreator >> 24);
|
||||
fCreatorStr[1] = (char) (fCreator >> 16);
|
||||
fCreatorStr[2] = (char) (fCreator >> 8);
|
||||
fCreatorStr[3] = (char) fCreator;
|
||||
fCreatorStr[4] = '\0';
|
||||
|
||||
if (fMagic != kMagic) {
|
||||
LOGI("Magic number does not match 2IMG");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fVersion > 1) {
|
||||
LOGW("ERROR: unsupported version=%d", fVersion);
|
||||
return -1; // bad header until I hear otherwise
|
||||
}
|
||||
|
||||
if (fFlags & kDOSVolumeSet)
|
||||
fDOSVolumeNum = fFlags & kDOSVolumeMask;
|
||||
|
||||
DumpHeader();
|
||||
|
||||
/* fix broken 'WOOF' images from Sweet-16 */
|
||||
if (fCreator == kCreatorSweet16 && fDataLen == 0 &&
|
||||
fImageFormat != kImageFormatNibble)
|
||||
{
|
||||
fDataLen = fNumBlocks * kBlockSize;
|
||||
LOGI("NOTE: fixing zero dataLen in 'WOOF' image (set to %u)",
|
||||
fDataLen);
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform some sanity checks.
|
||||
*/
|
||||
if (fImageFormat != kImageFormatNibble &&
|
||||
fNumBlocks * kBlockSize != fDataLen)
|
||||
{
|
||||
LOGW("numBlocks/dataLen mismatch (%u vs %u)",
|
||||
fNumBlocks * kBlockSize, fDataLen);
|
||||
return -1;
|
||||
}
|
||||
if (fDataLen + fDataOffset > totalLength) {
|
||||
LOGW("Invalid dataLen/offset/fileLength (dl=%u, off=%u, tlen=%u)",
|
||||
fDataLen, fDataOffset, totalLength);
|
||||
return -1;
|
||||
}
|
||||
if (fImageFormat < kImageFormatDOS || fImageFormat > kImageFormatNibble) {
|
||||
LOGW("Invalid image format %u", fImageFormat);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fCmtOffset > 0 && fCmtOffset < fDataOffset + fDataLen) {
|
||||
LOGW("2MG comment is inside the data section (off=%u, data end=%u)",
|
||||
fCmtOffset, fDataOffset+fDataLen);
|
||||
DebugBreak();
|
||||
// ignore the comment
|
||||
fCmtOffset = 0;
|
||||
fCmtLen = 0;
|
||||
}
|
||||
if (fCreatorOffset > 0 && fCreatorLen > 0) {
|
||||
uint32_t prevEnd = fDataOffset + fDataLen + fCmtLen;
|
||||
|
||||
if (fCreatorOffset < prevEnd) {
|
||||
LOGW("2MG creator chunk is inside prev data (off=%u, data end=%u)",
|
||||
fCreatorOffset, prevEnd);
|
||||
DebugBreak();
|
||||
// ignore the creator chunk
|
||||
fCreatorOffset = 0;
|
||||
fCreatorLen = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write the header to a 2IMG file.
|
||||
*
|
||||
* Returns 0 on success, or an errno value on failure.
|
||||
*/
|
||||
int
|
||||
TwoImgHeader::WriteHeader(FILE* fp) const
|
||||
{
|
||||
uint8_t buf[kOurHeaderLen];
|
||||
|
||||
PackHeader(buf);
|
||||
if (fwrite(buf, kOurHeaderLen, 1, fp) != 1)
|
||||
return errno ? errno : -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write the header to a 2IMG file.
|
||||
*
|
||||
* Returns 0 on success, or an errno value on failure.
|
||||
*/
|
||||
int
|
||||
TwoImgHeader::WriteHeader(GenericFD* pGFD) const
|
||||
{
|
||||
uint8_t buf[kOurHeaderLen];
|
||||
|
||||
PackHeader(buf);
|
||||
|
||||
if (pGFD->Write(buf, kOurHeaderLen) != kDIErrNone)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write the footer. File must be seeked to end of data chunk.
|
||||
*/
|
||||
int
|
||||
TwoImgHeader::WriteFooter(FILE* fp) const
|
||||
{
|
||||
LOGI("Writing footer at offset=%ld", (long) ftell(fp));
|
||||
|
||||
if (fCmtLen) {
|
||||
fwrite(fComment, fCmtLen, 1, fp);
|
||||
}
|
||||
if (fCreatorLen) {
|
||||
fwrite(fCreatorChunk, fCreatorLen, 1, fp);
|
||||
}
|
||||
if (ferror(fp))
|
||||
return errno ? errno : -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Write the footer. File must be seeked to end of data chunk.
|
||||
*/
|
||||
int
|
||||
TwoImgHeader::WriteFooter(GenericFD* pGFD) const
|
||||
{
|
||||
LOGI("Writing footer at offset=%ld", (long) pGFD->Tell());
|
||||
|
||||
if (fCmtLen) {
|
||||
if (pGFD->Write(fComment, fCmtLen) != kDIErrNone)
|
||||
return -1;
|
||||
}
|
||||
if (fCreatorLen) {
|
||||
if (pGFD->Write(fCreatorChunk, fCreatorLen) != kDIErrNone)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Pack the header values into a 64-byte buffer.
|
||||
*/
|
||||
void
|
||||
TwoImgHeader::PackHeader(uint8_t* buf) const
|
||||
{
|
||||
if (fCmtLen > 0 && fCmtOffset == 0) {
|
||||
assert(false);
|
||||
}
|
||||
if (fCreatorLen > 0 && fCreatorOffset == 0) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
PutLongBE(&buf[0x00], fMagic);
|
||||
PutLongBE(&buf[0x04], fCreator);
|
||||
PutShortLE(&buf[0x08], fHeaderLen);
|
||||
PutShortLE(&buf[0x0a], fVersion);
|
||||
PutLongLE(&buf[0x0c], fImageFormat);
|
||||
PutLongLE(&buf[0x10], fFlags);
|
||||
PutLongLE(&buf[0x14], fNumBlocks);
|
||||
PutLongLE(&buf[0x18], fDataOffset);
|
||||
PutLongLE(&buf[0x1c], fDataLen);
|
||||
PutLongLE(&buf[0x20], fCmtOffset);
|
||||
PutLongLE(&buf[0x24], fCmtLen);
|
||||
PutLongLE(&buf[0x28], fCreatorOffset);
|
||||
PutLongLE(&buf[0x2c], fCreatorLen);
|
||||
PutLongLE(&buf[0x30], fSpare[0]);
|
||||
PutLongLE(&buf[0x34], fSpare[1]);
|
||||
PutLongLE(&buf[0x38], fSpare[2]);
|
||||
PutLongLE(&buf[0x3c], fSpare[3]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dump the contents of an ImgHeader.
|
||||
*/
|
||||
void
|
||||
TwoImgHeader::DumpHeader(void) const
|
||||
{
|
||||
LOGI("--- header contents:");
|
||||
LOGI("\tmagic = '%s' (0x%08x)", fMagicStr, fMagic);
|
||||
LOGI("\tcreator = '%s' (0x%08x)", fCreatorStr, fCreator);
|
||||
LOGI("\theaderLen = %u", fHeaderLen);
|
||||
LOGI("\tversion = %u", fVersion);
|
||||
LOGI("\timageFormat = %u", fImageFormat);
|
||||
LOGI("\tflags = 0x%08x", fFlags);
|
||||
LOGI("\t locked = %s",
|
||||
(fFlags & kFlagLocked) ? "true" : "false");
|
||||
LOGI("\t DOS volume = %s (%d)",
|
||||
(fFlags & kDOSVolumeSet) ? "true" : "false",
|
||||
fFlags & kDOSVolumeMask);
|
||||
LOGI("\tnumBlocks = %u", fNumBlocks);
|
||||
LOGI("\tdataOffset = %u", fDataOffset);
|
||||
LOGI("\tdataLen = %u", fDataLen);
|
||||
LOGI("\tcmtOffset = %u", fCmtOffset);
|
||||
LOGI("\tcmtLen = %u", fCmtLen);
|
||||
LOGI("\tcreatorOffset = %u", fCreatorOffset);
|
||||
LOGI("\tcreatorLen = %u", fCreatorLen);
|
||||
LOGI("---");
|
||||
}
|
154
diskimg/TwoImg.h
Normal file
154
diskimg/TwoImg.h
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Support for the "2MG"/"2IMG" disk image format.
|
||||
*
|
||||
* This gets its own header because CiderPress uses these definitions and
|
||||
* functions directly.
|
||||
*/
|
||||
#ifndef DISKIMG_TWOIMG_H
|
||||
#define DISKIMG_TWOIMG_H
|
||||
|
||||
#include "DiskImg.h"
|
||||
|
||||
namespace DiskImgLib {
|
||||
|
||||
/*
|
||||
* 2IMG header definition (was on http://www.magnet.ch/emutech/Tech/,
|
||||
* now on http://www.a2central.com/programming/filetypes/ftne00130.html
|
||||
* as filetype $e0/$0130).
|
||||
*
|
||||
* Meaning of "flags":
|
||||
* bit 31 : disk is "locked"; used by emulators as write-protect sticker.
|
||||
* bit 8 : if set, bits 0-7 specify DOS 3.3 volume number
|
||||
* bit 0-7: if bit 8 is set, use this as DOS volume; else use 254
|
||||
*
|
||||
* All values are stored little-endian.
|
||||
*/
|
||||
class DISKIMG_API TwoImgHeader {
|
||||
public:
|
||||
TwoImgHeader(void) :
|
||||
fMagic(0),
|
||||
fCreator(0),
|
||||
fHeaderLen(0),
|
||||
fVersion(0),
|
||||
fImageFormat(0),
|
||||
fFlags(0),
|
||||
fNumBlocks(0),
|
||||
fDataOffset(0),
|
||||
fDataLen(0),
|
||||
fCmtOffset(0),
|
||||
fCmtLen(0),
|
||||
fCreatorOffset(0),
|
||||
fCreatorLen(0),
|
||||
fSpare(),
|
||||
|
||||
fDOSVolumeNum(-1),
|
||||
fMagicStr(),
|
||||
fCreatorStr(),
|
||||
fComment(NULL),
|
||||
fCreatorChunk(NULL)
|
||||
{}
|
||||
virtual ~TwoImgHeader(void) {
|
||||
delete[] fComment;
|
||||
delete[] fCreatorChunk;
|
||||
}
|
||||
|
||||
/*
|
||||
* Header fields.
|
||||
*/
|
||||
//char fMagic[4];
|
||||
//char fCreator[4];
|
||||
uint32_t fMagic;
|
||||
uint32_t fCreator;
|
||||
uint16_t fHeaderLen;
|
||||
uint16_t fVersion;
|
||||
uint32_t fImageFormat;
|
||||
uint32_t fFlags; // may include DOS volume num
|
||||
uint32_t fNumBlocks; // 512-byte blocks
|
||||
uint32_t fDataOffset;
|
||||
uint32_t fDataLen;
|
||||
uint32_t fCmtOffset;
|
||||
uint32_t fCmtLen;
|
||||
uint32_t fCreatorOffset;
|
||||
uint32_t fCreatorLen;
|
||||
uint32_t fSpare[4];
|
||||
|
||||
/*
|
||||
* Related constants.
|
||||
*/
|
||||
enum {
|
||||
// imageFormat
|
||||
kImageFormatDOS = 0,
|
||||
kImageFormatProDOS = 1,
|
||||
kImageFormatNibble = 2,
|
||||
// flags
|
||||
kFlagLocked = (1L<<31),
|
||||
kDOSVolumeSet = (1L<<8),
|
||||
kDOSVolumeMask = (0xff),
|
||||
kDefaultVolumeNum = 254,
|
||||
|
||||
// constants used when creating a new header
|
||||
kOurHeaderLen = 64,
|
||||
kOurVersion = 1,
|
||||
|
||||
kBlockSize = 512,
|
||||
kMagic = 0x32494d47, // 2IMG
|
||||
kCreatorCiderPress = 0x43647250, // CdrP
|
||||
kCreatorSweet16 = 0x574f4f46, // WOOF
|
||||
};
|
||||
|
||||
/*
|
||||
* Basic functions.
|
||||
*
|
||||
* The read header function will read the comment, but the write
|
||||
* header function will not. This is because the GenericFD functions
|
||||
* don't allow seeking past the current EOF.
|
||||
*
|
||||
* ReadHeader/WriteHeader expect the file to be seeked to the initial
|
||||
* offset. WriteFooter expects the file to be seeked just past the
|
||||
* end of the data section. This is done in case the file has some
|
||||
* sort of wrapper outside the 2MG header.
|
||||
*/
|
||||
int InitHeader(int imageFormat, uint32_t imageSize, uint32_t imageBlockCount);
|
||||
int ReadHeader(FILE* fp, uint32_t totalLength);
|
||||
int ReadHeader(GenericFD* pGFD, uint32_t totalLength);
|
||||
int WriteHeader(FILE* fp) const;
|
||||
int WriteHeader(GenericFD* pGFD) const;
|
||||
int WriteFooter(FILE* fp) const;
|
||||
int WriteFooter(GenericFD* pGFD) const;
|
||||
void DumpHeader(void) const; // for debugging
|
||||
|
||||
/*
|
||||
* Getters & setters.
|
||||
*/
|
||||
const char* GetMagicStr(void) const { return fMagicStr; }
|
||||
const char* GetCreatorStr(void) const { return fCreatorStr; }
|
||||
|
||||
int16_t GetDOSVolumeNum(void) const;
|
||||
void SetDOSVolumeNum(short dosVolumeNum);
|
||||
const char* GetComment(void) const { return fComment; }
|
||||
void SetComment(const char* comment);
|
||||
const void* GetCreatorChunk(void) const { return fCreatorChunk; }
|
||||
void SetCreatorChunk(const void* creatorBlock, long len);
|
||||
|
||||
private:
|
||||
int UnpackHeader(const uint8_t* buf, uint32_t totalLength);
|
||||
void PackHeader(uint8_t* buf) const;
|
||||
int GetChunk(GenericFD* pGFD, di_off_t relOffset, long len, void** pBuf);
|
||||
int GetChunk(FILE* fp, di_off_t relOffset, long len, void** pBuf);
|
||||
|
||||
int16_t fDOSVolumeNum; // 8-bit volume number, or -1
|
||||
char fMagicStr[5];
|
||||
char fCreatorStr[5];
|
||||
|
||||
char* fComment;
|
||||
char* fCreatorChunk;
|
||||
};
|
||||
|
||||
} // namespace DiskImgLib
|
||||
|
||||
#endif /*DISKIMG_TWOIMG_H*/
|
359
diskimg/UNIDOS.cpp
Normal file
359
diskimg/UNIDOS.cpp
Normal file
@ -0,0 +1,359 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Implementation of DiskFSUNIDOS class.
|
||||
*
|
||||
* The "UNIDOS" filesystem doesn't actually hold files. Instead, it holds
|
||||
* two 400K DOS 3.3 volumes on an 800K disk.
|
||||
*
|
||||
* We do have a test here for "wide" DOS 3.3, which is largely a clone of
|
||||
* the standard DOS 3.3 test. The trick is that we have to adjust our
|
||||
* detection to account for 32-sector tracks, and do so while the object
|
||||
* is still in a state where it believes it has 16 sectors per track.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
|
||||
/*
|
||||
* ===========================================================================
|
||||
* DiskFSUNIDOS
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
const int kExpectedNumBlocks = 1600;
|
||||
const int kExpectedTracks = 50;
|
||||
const int kExpectedSectors = 32;
|
||||
const int kVTOCTrack = 17;
|
||||
const int kVTOCSector = 0;
|
||||
const int kSctSize = 256;
|
||||
|
||||
const int kCatalogEntrySize = 0x23; // length in bytes of catalog entries
|
||||
const int kCatalogEntriesPerSect = 7; // #of entries per catalog sector
|
||||
const int kMaxTSPairs = 0x7a; // 122 entries for 256-byte sectors
|
||||
const int kTSOffset = 0x0c; // first T/S entry in a T/S list
|
||||
|
||||
const int kMaxTSIterations = 32;
|
||||
const int kMaxCatalogIterations = 64;
|
||||
|
||||
|
||||
/*
|
||||
* Read a track/sector, adjusting for 32-sector disks being treated as
|
||||
* if they were 16-sector.
|
||||
*/
|
||||
static DIError ReadTrackSectorAdjusted(DiskImg* pImg, int track, int sector,
|
||||
int trackOffset, uint8_t* buf, DiskImg::SectorOrder imageOrder)
|
||||
{
|
||||
track += trackOffset;
|
||||
track *= 2;
|
||||
if (sector >= 16) {
|
||||
track++;
|
||||
sector -= 16;
|
||||
}
|
||||
return pImg->ReadTrackSectorSwapped(track, sector, buf, imageOrder,
|
||||
DiskImg::kSectorOrderDOS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test for presence of 400K DOS 3.3 volumes.
|
||||
*/
|
||||
static DIError TestImageHalf(DiskImg* pImg, int trackOffset,
|
||||
DiskImg::SectorOrder imageOrder, int* pGoodCount)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
uint8_t sctBuf[kSctSize];
|
||||
int numTracks, numSectors;
|
||||
int catTrack, catSect;
|
||||
int foundGood = 0;
|
||||
int iterations = 0;
|
||||
|
||||
*pGoodCount = 0;
|
||||
|
||||
dierr = ReadTrackSectorAdjusted(pImg, kVTOCTrack, kVTOCSector,
|
||||
trackOffset, sctBuf, imageOrder);
|
||||
if (dierr != kDIErrNone)
|
||||
goto bail;
|
||||
|
||||
catTrack = sctBuf[0x01];
|
||||
catSect = sctBuf[0x02];
|
||||
numTracks = sctBuf[0x34];
|
||||
numSectors = sctBuf[0x35];
|
||||
|
||||
if (!(sctBuf[0x27] == kMaxTSPairs) ||
|
||||
/*!(sctBuf[0x36] == 0 && sctBuf[0x37] == 1) ||*/ // bytes per sect
|
||||
!(numTracks == kExpectedTracks) ||
|
||||
!(numSectors == 32) ||
|
||||
!(catTrack < numTracks && catSect < numSectors) ||
|
||||
0)
|
||||
{
|
||||
LOGI(" UNI/Wide DOS header test failed");
|
||||
dierr = kDIErrFilesystemNotFound;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
foundGood++; // score one for a valid-looking VTOC
|
||||
|
||||
/*
|
||||
* Walk through the catalog track to try to figure out ordering.
|
||||
*/
|
||||
while (catTrack != 0 && catSect != 0 &&
|
||||
iterations < DiskFSDOS33::kMaxCatalogSectors)
|
||||
{
|
||||
dierr = ReadTrackSectorAdjusted(pImg, catTrack, catSect,
|
||||
trackOffset, sctBuf, imageOrder);
|
||||
if (dierr != kDIErrNone) {
|
||||
dierr = kDIErrNone;
|
||||
break; /* allow it if earlier stuff was okay */
|
||||
}
|
||||
|
||||
if (catTrack == sctBuf[1] && catSect == sctBuf[2] +1)
|
||||
foundGood++;
|
||||
else if (catTrack == sctBuf[1] && catSect == sctBuf[2]) {
|
||||
LOGI(" WideDOS detected self-reference on cat (%d,%d)",
|
||||
catTrack, catSect);
|
||||
break;
|
||||
}
|
||||
|
||||
catTrack = sctBuf[1];
|
||||
catSect = sctBuf[2];
|
||||
iterations++; // watch for infinite loops
|
||||
}
|
||||
if (iterations >= DiskFSDOS33::kMaxCatalogSectors) {
|
||||
dierr = kDIErrDirectoryLoop;
|
||||
LOGI(" WideDOS directory links cause a loop");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
LOGI(" WideDOS foundGood=%d off=%d swap=%d", foundGood,
|
||||
trackOffset, imageOrder);
|
||||
*pGoodCount = foundGood;
|
||||
|
||||
bail:
|
||||
return dierr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test both of the DOS partitions.
|
||||
*/
|
||||
static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder,
|
||||
int* pGoodCount)
|
||||
{
|
||||
DIError dierr;
|
||||
int goodCount1, goodCount2;
|
||||
|
||||
*pGoodCount = 0;
|
||||
|
||||
LOGI(" UNIDOS checking first half (imageOrder=%d)", imageOrder);
|
||||
dierr = TestImageHalf(pImg, 0, imageOrder, &goodCount1);
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
|
||||
LOGI(" UNIDOS checking second half (imageOrder=%d)", imageOrder);
|
||||
dierr = TestImageHalf(pImg, kExpectedTracks, imageOrder, &goodCount2);
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
|
||||
if (goodCount1 > goodCount2)
|
||||
*pGoodCount = goodCount1;
|
||||
else
|
||||
*pGoodCount = goodCount2;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test to see if the image is a UNIDOS volume.
|
||||
*/
|
||||
/*static*/ DIError DiskFSUNIDOS::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
||||
DiskImg::FSFormat* pFormat, FSLeniency leniency)
|
||||
{
|
||||
/* only on 800K disks (at the least, insist on numTracks being even) */
|
||||
if (pImg->GetNumBlocks() != kExpectedNumBlocks)
|
||||
return kDIErrFilesystemNotFound;
|
||||
|
||||
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
|
||||
|
||||
DiskImg::GetSectorOrderArray(ordering, *pOrder);
|
||||
|
||||
DiskImg::SectorOrder bestOrder = DiskImg::kSectorOrderUnknown;
|
||||
int bestCount = 0;
|
||||
|
||||
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
|
||||
int goodCount = 0;
|
||||
|
||||
if (ordering[i] == DiskImg::kSectorOrderUnknown)
|
||||
continue;
|
||||
if (TestImage(pImg, ordering[i], &goodCount) == kDIErrNone) {
|
||||
if (goodCount > bestCount) {
|
||||
bestCount = goodCount;
|
||||
bestOrder = ordering[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestCount >= 4 ||
|
||||
(leniency == kLeniencyVery && bestCount >= 2))
|
||||
{
|
||||
LOGI(" WideDOS test: bestCount=%d for order=%d", bestCount, bestOrder);
|
||||
assert(bestOrder != DiskImg::kSectorOrderUnknown);
|
||||
*pOrder = bestOrder;
|
||||
*pFormat = DiskImg::kFormatUNIDOS;
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
LOGI(" UNIDOS didn't find valid FS");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test to see if the image is a 'wide' (32-sector) DOS3.3 volume, i.e.
|
||||
* half of a UNIDOS volume (usually found embedded in ProDOS).
|
||||
*
|
||||
* Trying all possible formats is important here, because the wrong value for
|
||||
* swap can return a "good" value of 7 (much less than the expected 30, but
|
||||
* above a threshold of reasonableness).
|
||||
*/
|
||||
/*static*/ DIError DiskFSUNIDOS::TestWideFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
|
||||
DiskImg::FSFormat* pFormat, FSLeniency leniency)
|
||||
{
|
||||
/* only on 400K "disks" */
|
||||
if (pImg->GetNumBlocks() != kExpectedNumBlocks/2) {
|
||||
LOGI(" WideDOS ignoring volume (numBlocks=%ld)",
|
||||
pImg->GetNumBlocks());
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
|
||||
|
||||
DiskImg::GetSectorOrderArray(ordering, *pOrder);
|
||||
|
||||
DiskImg::SectorOrder bestOrder = DiskImg::kSectorOrderUnknown;
|
||||
int bestCount = 0;
|
||||
|
||||
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
|
||||
int goodCount = 0;
|
||||
|
||||
if (ordering[i] == DiskImg::kSectorOrderUnknown)
|
||||
continue;
|
||||
if (TestImageHalf(pImg, 0, ordering[i], &goodCount) == kDIErrNone) {
|
||||
if (goodCount > bestCount) {
|
||||
bestCount = goodCount;
|
||||
bestOrder = ordering[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestCount >= 4 ||
|
||||
(leniency == kLeniencyVery && bestCount >= 2))
|
||||
{
|
||||
LOGI(" UNI/Wide test: bestCount=%d for order=%d", bestCount, bestOrder);
|
||||
assert(bestOrder != DiskImg::kSectorOrderUnknown);
|
||||
*pOrder = bestOrder;
|
||||
*pFormat = DiskImg::kFormatDOS33;
|
||||
// up to the caller to adjust numTracks/numSectPerTrack
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
LOGI(" UNI/Wide didn't find valid FS");
|
||||
return kDIErrFilesystemNotFound;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Set up our sub-volumes.
|
||||
*/
|
||||
DIError DiskFSUNIDOS::Initialize(void)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
|
||||
if (fScanForSubVolumes != kScanSubDisabled) {
|
||||
dierr = OpenSubVolume(0);
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
|
||||
dierr = OpenSubVolume(1);
|
||||
if (dierr != kDIErrNone)
|
||||
return dierr;
|
||||
} else {
|
||||
LOGI(" UNIDOS not scanning for sub-volumes");
|
||||
}
|
||||
|
||||
SetVolumeUsageMap();
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Open up one of the DOS 3.3 sub-volumes.
|
||||
*/
|
||||
DIError DiskFSUNIDOS::OpenSubVolume(int idx)
|
||||
{
|
||||
DIError dierr = kDIErrNone;
|
||||
DiskFS* pNewFS = NULL;
|
||||
DiskImg* pNewImg = NULL;
|
||||
|
||||
pNewImg = new DiskImg;
|
||||
if (pNewImg == NULL) {
|
||||
dierr = kDIErrMalloc;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
dierr = pNewImg->OpenImage(fpImg, kExpectedTracks * idx, 0,
|
||||
kExpectedTracks * kExpectedSectors);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" UNISub: OpenImage(%d,0,%d) failed (err=%d)",
|
||||
kExpectedTracks * idx, kExpectedTracks * kExpectedSectors, dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
dierr = pNewImg->AnalyzeImage();
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGI(" UNISub: analysis failed (err=%d)", dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
|
||||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
|
||||
{
|
||||
LOGI(" UNISub: unable to identify filesystem");
|
||||
dierr = kDIErrUnsupportedFSFmt;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* open a DiskFS for the sub-image */
|
||||
LOGI(" UNISub %d succeeded!", idx);
|
||||
pNewFS = pNewImg->OpenAppropriateDiskFS();
|
||||
if (pNewFS == NULL) {
|
||||
LOGI(" UNISub: OpenAppropriateDiskFS failed");
|
||||
dierr = kDIErrUnsupportedFSFmt;
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* load the files from the sub-image */
|
||||
dierr = pNewFS->Initialize(pNewImg, kInitFull);
|
||||
if (dierr != kDIErrNone) {
|
||||
LOGE(" UNISub: error %d reading list of files from disk", dierr);
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* if this really is DOS 3.3, override the "volume name" */
|
||||
if (pNewImg->GetFSFormat() == DiskImg::kFormatDOS33) {
|
||||
DiskFSDOS33* pDOS = (DiskFSDOS33*) pNewFS; /* eek, a downcast */
|
||||
pDOS->SetDiskVolumeNum(idx+1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Success, add it to the sub-volume list.
|
||||
*/
|
||||
AddSubVolumeToList(pNewImg, pNewFS);
|
||||
|
||||
bail:
|
||||
if (dierr != kDIErrNone) {
|
||||
delete pNewFS;
|
||||
delete pNewImg;
|
||||
}
|
||||
return dierr;
|
||||
}
|
269
diskimg/VolumeUsage.cpp
Normal file
269
diskimg/VolumeUsage.cpp
Normal file
@ -0,0 +1,269 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Support for the VolumeUsage sub-class in DiskFS.
|
||||
*/
|
||||
#include "StdAfx.h"
|
||||
#include "DiskImgPriv.h"
|
||||
|
||||
|
||||
/*
|
||||
* Initialize structures for a block-structured disk.
|
||||
*/
|
||||
DIError DiskFS::VolumeUsage::Create(long numBlocks)
|
||||
{
|
||||
if (numBlocks <= 0 || numBlocks > 32*1024*1024) // 16GB
|
||||
return kDIErrInvalidArg;
|
||||
|
||||
fByBlocks = true;
|
||||
fNumSectors = -1;
|
||||
fTotalChunks = numBlocks;
|
||||
fListSize = numBlocks;
|
||||
fList = new unsigned char[fListSize];
|
||||
if (fList == NULL)
|
||||
return kDIErrMalloc;
|
||||
|
||||
memset(fList, 0, fListSize);
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize structures for a track/sector-structured disk.
|
||||
*/
|
||||
DIError DiskFS::VolumeUsage::Create(long numTracks, long numSectors)
|
||||
{
|
||||
long count = numTracks * numSectors;
|
||||
if (numTracks <= 0 || count <= 0 || count > 32*1024*1024)
|
||||
return kDIErrInvalidArg;
|
||||
|
||||
fByBlocks = false;
|
||||
fNumSectors = numSectors;
|
||||
fTotalChunks = count;
|
||||
fListSize = count;
|
||||
fList = new unsigned char[fListSize];
|
||||
if (fList == NULL)
|
||||
return kDIErrMalloc;
|
||||
|
||||
memset(fList, 0, fListSize);
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the state of a particular chunk.
|
||||
*/
|
||||
DIError DiskFS::VolumeUsage::GetChunkState(long block, ChunkState* pState) const
|
||||
{
|
||||
if (!fByBlocks)
|
||||
return kDIErrInvalidArg;
|
||||
return GetChunkStateIdx(block, pState);
|
||||
}
|
||||
|
||||
DIError DiskFS::VolumeUsage::GetChunkState(long track, long sector,
|
||||
ChunkState* pState) const
|
||||
{
|
||||
if (fByBlocks)
|
||||
return kDIErrInvalidArg;
|
||||
if (track < 0 || sector < 0 || sector >= fNumSectors)
|
||||
return kDIErrInvalidArg;
|
||||
return GetChunkStateIdx(track * fNumSectors + sector, pState);
|
||||
}
|
||||
|
||||
DIError DiskFS::VolumeUsage::GetChunkStateIdx(int idx, ChunkState* pState) const
|
||||
{
|
||||
if (fList == NULL || idx < 0 || idx >= fListSize) {
|
||||
assert(false);
|
||||
return kDIErrInvalidArg;
|
||||
}
|
||||
|
||||
unsigned char val = fList[idx];
|
||||
pState->isUsed = (val & kChunkUsedFlag) != 0;
|
||||
pState->isMarkedUsed = (val & kChunkMarkedUsedFlag) != 0;
|
||||
pState->purpose = (ChunkPurpose)(val & kChunkPurposeMask);
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the state of a particular chunk.
|
||||
*/
|
||||
DIError DiskFS::VolumeUsage::SetChunkState(long block, const ChunkState* pState)
|
||||
{
|
||||
if (!fByBlocks)
|
||||
return kDIErrInvalidArg;
|
||||
return SetChunkStateIdx(block, pState);
|
||||
}
|
||||
|
||||
DIError DiskFS::VolumeUsage::SetChunkState(long track, long sector,
|
||||
const ChunkState* pState)
|
||||
{
|
||||
if (fByBlocks)
|
||||
return kDIErrInvalidArg;
|
||||
if (track < 0 || sector < 0 || sector >= fNumSectors)
|
||||
return kDIErrInvalidArg;
|
||||
return SetChunkStateIdx(track * fNumSectors + sector, pState);
|
||||
}
|
||||
|
||||
DIError DiskFS::VolumeUsage::SetChunkStateIdx(int idx, const ChunkState* pState)
|
||||
{
|
||||
if (fList == NULL || idx < 0 || idx >= fListSize) {
|
||||
assert(false);
|
||||
return kDIErrInvalidArg;
|
||||
}
|
||||
|
||||
unsigned char val = 0;
|
||||
if (pState->isUsed) {
|
||||
if ((pState->purpose & ~kChunkPurposeMask) != 0) {
|
||||
assert(false);
|
||||
return kDIErrInvalidArg;
|
||||
}
|
||||
val |= kChunkUsedFlag;
|
||||
val |= (int)pState->purpose;
|
||||
}
|
||||
if (pState->isMarkedUsed)
|
||||
val |= kChunkMarkedUsedFlag;
|
||||
|
||||
fList[idx] = val;
|
||||
|
||||
return kDIErrNone;
|
||||
}
|
||||
|
||||
/*
|
||||
* Count up the #of free chunks.
|
||||
*/
|
||||
long DiskFS::VolumeUsage::GetActualFreeChunks(void) const
|
||||
{
|
||||
ChunkState cstate; // could probably do this bitwise...
|
||||
int freeCount = 0;
|
||||
int funkyCount = 0;
|
||||
|
||||
for (int i = 0; i < fTotalChunks; i++) {
|
||||
if (GetChunkStateIdx(i, &cstate) != kDIErrNone) {
|
||||
assert(false);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!cstate.isUsed && !cstate.isMarkedUsed)
|
||||
freeCount++;
|
||||
|
||||
if ((!cstate.isUsed && cstate.isMarkedUsed) ||
|
||||
(cstate.isUsed && !cstate.isMarkedUsed) ||
|
||||
(cstate.isUsed && cstate.purpose == kChunkPurposeConflict))
|
||||
{
|
||||
funkyCount++;
|
||||
}
|
||||
}
|
||||
|
||||
LOGI(" VU total=%ld free=%d funky=%d",
|
||||
fTotalChunks, freeCount, funkyCount);
|
||||
|
||||
return freeCount;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a ChunkState into a single, hopefully meaningful, character.
|
||||
*
|
||||
* Possible states:
|
||||
* '.' - !inuse, !marked (free space)
|
||||
* 'X' - !inuse, marked (could be embedded volume)
|
||||
* '!' - inuse, !marked (danger!)
|
||||
* '#' - inuse, marked, used by more than one thing
|
||||
* 'S' - inuse, marked, used by system (directories, volume bit map)
|
||||
* 'I' - inuse, marked, used by file structure (index block)
|
||||
* 'F' - inuse, marked, used by file
|
||||
*/
|
||||
char DiskFS::VolumeUsage::StateToChar(ChunkState* pState) const
|
||||
{
|
||||
if (!pState->isUsed && !pState->isMarkedUsed)
|
||||
return '.';
|
||||
if (!pState->isUsed && pState->isMarkedUsed)
|
||||
return 'X';
|
||||
if (pState->isUsed && !pState->isMarkedUsed)
|
||||
return '!';
|
||||
assert(pState->isUsed && pState->isMarkedUsed);
|
||||
if (pState->purpose == kChunkPurposeUnknown)
|
||||
return '?';
|
||||
if (pState->purpose == kChunkPurposeConflict)
|
||||
return '#';
|
||||
if (pState->purpose == kChunkPurposeSystem)
|
||||
return 'S';
|
||||
if (pState->purpose == kChunkPurposeVolumeDir)
|
||||
return 'V';
|
||||
if (pState->purpose == kChunkPurposeSubdir)
|
||||
return 'D';
|
||||
if (pState->purpose == kChunkPurposeUserData)
|
||||
return 'F';
|
||||
if (pState->purpose == kChunkPurposeFileStruct)
|
||||
return 'I';
|
||||
if (pState->purpose == kChunkPurposeEmbedded)
|
||||
return 'E';
|
||||
|
||||
assert(false);
|
||||
return '?';
|
||||
}
|
||||
|
||||
/*
|
||||
* Dump the list.
|
||||
*/
|
||||
void DiskFS::VolumeUsage::Dump(void) const
|
||||
{
|
||||
#define kMapInit "--------------------------------"
|
||||
if (fList == NULL) {
|
||||
LOGI(" VU asked to dump empty list?");
|
||||
return;
|
||||
}
|
||||
|
||||
LOGI(" VU VolumeUsage dump (%ld free chunks):",
|
||||
GetActualFreeChunks());
|
||||
|
||||
if (fByBlocks) {
|
||||
ChunkState cstate;
|
||||
char freemap[32+1] = kMapInit;
|
||||
int block;
|
||||
const int kEntriesPerLine = 32; // use 20 to match Copy][+
|
||||
|
||||
for (block = 0; block < fTotalChunks; block++) {
|
||||
if (GetChunkState(block, &cstate) != kDIErrNone) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
freemap[block % kEntriesPerLine] = StateToChar(&cstate);
|
||||
if ((block % kEntriesPerLine) == kEntriesPerLine-1) {
|
||||
LOGI(" 0x%04x: %s", block-(kEntriesPerLine-1), freemap);
|
||||
}
|
||||
}
|
||||
if ((block % kEntriesPerLine) != 0) {
|
||||
memset(freemap + (block % kEntriesPerLine), '-',
|
||||
kEntriesPerLine - (block % kEntriesPerLine));
|
||||
LOGI(" 0x%04x: %s", block-(kEntriesPerLine-1), freemap);
|
||||
}
|
||||
} else {
|
||||
ChunkState cstate;
|
||||
char freemap[32+1] = kMapInit;
|
||||
long numTracks = fTotalChunks / fNumSectors;
|
||||
int track, sector;
|
||||
|
||||
if (fNumSectors > 32) {
|
||||
LOGI(" VU too many sectors (%ld)", fNumSectors);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGI(" map 0123456789abcdef");
|
||||
|
||||
for (track = 0; track < numTracks; track++) {
|
||||
for (sector = 0; sector < fNumSectors; sector++) {
|
||||
if (GetChunkState(track, sector, &cstate) != kDIErrNone) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
freemap[sector] = StateToChar(&cstate);
|
||||
}
|
||||
LOGI(" %2d: %s", track, freemap);
|
||||
}
|
||||
}
|
||||
}
|
2127
diskimg/Win32BlockIO.cpp
Normal file
2127
diskimg/Win32BlockIO.cpp
Normal file
File diff suppressed because it is too large
Load Diff
413
diskimg/Win32BlockIO.h
Normal file
413
diskimg/Win32BlockIO.h
Normal file
@ -0,0 +1,413 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
/*
|
||||
* Structures and functions for performing block-level I/O on Win32 logical
|
||||
* and physical volumes.
|
||||
*
|
||||
* Under Win2K/XP this is pretty straightforward: open the volume and
|
||||
* issue seek and read calls. It's not quite that simple -- reads need to
|
||||
* be in 512-byte sectors for floppy and hard drives, seeks need to be on
|
||||
* sector boundaries, and you can't seek from the end, which makes it hard
|
||||
* to figure out how big the volume is -- but it's palatable.
|
||||
*
|
||||
* Under Win95/Win98/WinME, life is more difficult. You need to use the
|
||||
* Int21h/7305h services to access logical volumes. Of course, those weren't
|
||||
* available until Win95 OSR2, before which you used Int25h/6000h, but those
|
||||
* don't work with FAT32 volumes. Access to physical devices requires Int13h,
|
||||
* which is fine for floppies but requires 16-bit flat thunks for hard drives
|
||||
* (see Q137176: "DeviceIoControl Int 13h Does Not Support Hard Disks").
|
||||
*
|
||||
* If Win98 can't recognize the volume on a floppy, it tries to reacquire
|
||||
* the volume information every time you ask it to read a sector. This makes
|
||||
* things *VERY* slow. The solution is to use the physical drive Int13h
|
||||
* services. These come in two variants, one of which will work on just
|
||||
* about any machine but only works with floppies. The other will work on
|
||||
* anything built since about 1996.
|
||||
*
|
||||
* Figuring out whether something is or isn't a floppy requires yet
|
||||
* another call. All things considered it's quite an ordeal. The block I/O
|
||||
* functions are wrapped up in classes so nobody else has to worry about all
|
||||
* this mess.
|
||||
*
|
||||
* Implementation note: this class is broken down by how the devices are
|
||||
* opened, e.g. logical, physical, or ASPI address. Breaking it down by device
|
||||
* type seems more appropriate, but Win98 vs Win2K can require completely
|
||||
* different approaches (e.g. physical vs. logical for floppy disk, logical
|
||||
* vs. ASPI for CD-ROM). There is no perfect decomposition.
|
||||
*
|
||||
* Summary:
|
||||
* Win9x/ME physical drive: Int13h (doesn't work for hard drives)
|
||||
* Win9x/ME logical drive: Int21h/7305h
|
||||
* Win9x/ME SCSI drive or CD-ROM drive: ASPI
|
||||
* Win2K/XP physical drive: CreateFile("\\.\PhysicalDriveN")
|
||||
* Win2K/XP logical drive: CreateFile("\\.\X")
|
||||
* Win2K/XP SCSI drive or CD-ROM drive: SPTI
|
||||
*/
|
||||
#ifndef DISKIMG_WIN32BLOCKIO_H
|
||||
#define DISKIMG_WIN32BLOCKIO_H
|
||||
|
||||
|
||||
namespace DiskImgLib {
|
||||
|
||||
extern bool IsWin9x(void);
|
||||
|
||||
|
||||
/*
|
||||
* Cache a contiguous set of blocks. This was originally motivated by poor
|
||||
* write performance, but that problem was largely solved in other ways.
|
||||
* It's still handy to write an entire track at once under Win98 though.
|
||||
*
|
||||
* Only storing continuous runs of blocks makes the cache less useful, but
|
||||
* much easier to write, and hence less likely to break in unpleasant ways.
|
||||
*
|
||||
* This class just manages the blocks. The FlushCache() function in
|
||||
* Win32LogicalVolume is responsible for actually pushing the writes through.
|
||||
*
|
||||
* (I'm not entirely happy with this, especially since it doesn't take into
|
||||
* account the underlying device block size. This could've been a good place
|
||||
* to handle the 2048-byte CD-ROM block size, rather than caching it again in
|
||||
* the CD-ROM handler.)
|
||||
*/
|
||||
class CBCache {
|
||||
public:
|
||||
CBCache(void) : fFirstBlock(kEmpty), fNumBlocks(0)
|
||||
{
|
||||
for (int i = 0; i < kMaxCachedBlocks; i++)
|
||||
fDirty[i] = false;
|
||||
}
|
||||
virtual ~CBCache(void) { Purge(); }
|
||||
|
||||
enum { kEmpty = -1 };
|
||||
|
||||
// is the block we want in the cache?
|
||||
bool IsBlockInCache(long blockNum) const;
|
||||
// read block out of cache (after verifying that it's present)
|
||||
DIError GetFromCache(long blockNum, void* buf);
|
||||
// can the cache store this block?
|
||||
bool IsRoomInCache(long blockNum) const;
|
||||
// write block to cache (after verifying that it will fit)
|
||||
DIError PutInCache(long blockNum, const void* buf, bool isDirty);
|
||||
|
||||
// are there any dirty blocks in the cache?
|
||||
bool IsDirty(void) const;
|
||||
// get start, count, and buffer so we can write the cached data
|
||||
void GetCachePointer(long* pFirstBlock, int* pNumBlocks, void** pBuf) const;
|
||||
// clear all the dirty flags
|
||||
void Scrub(void);
|
||||
// purge all cache entries (ideally after writing w/help from GetCachePtr)
|
||||
void Purge(void);
|
||||
|
||||
private:
|
||||
enum {
|
||||
kMaxCachedBlocks = 18, // one track on 1.4MB floppy
|
||||
kBlockSize = 512, // must match with Win32LogicalVolume::
|
||||
};
|
||||
|
||||
long fFirstBlock; // set to kEmpty when cache is empty
|
||||
int fNumBlocks;
|
||||
bool fDirty[kMaxCachedBlocks];
|
||||
unsigned char fCache[kMaxCachedBlocks * kBlockSize];
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* This class encapsulates block access to a logical or physical volume.
|
||||
*/
|
||||
class Win32VolumeAccess {
|
||||
public:
|
||||
Win32VolumeAccess(void) :
|
||||
fTotalBlocks(-1),
|
||||
fpBlockAccess(NULL)
|
||||
{}
|
||||
virtual ~Win32VolumeAccess(void) {
|
||||
if (fpBlockAccess != NULL) {
|
||||
FlushCache(true);
|
||||
fpBlockAccess->Close();
|
||||
}
|
||||
}
|
||||
|
||||
// "deviceName" has the form "X:\" (logical), "81:\" (physical), or
|
||||
// "ASPI:x:y:z\" (ASPI)
|
||||
DIError Open(const WCHAR* deviceName, bool readOnly);
|
||||
// close the device
|
||||
void Close(void);
|
||||
// is the device open and working?
|
||||
bool Ready(void) const { return fpBlockAccess != NULL; }
|
||||
|
||||
// return the volume's EOF
|
||||
long GetTotalBlocks(void) const { return fTotalBlocks; }
|
||||
// return the block size for this volume (always a power of 2)
|
||||
int GetBlockSize(void) const { return BlockAccess::kBlockSize; }
|
||||
|
||||
// read one or more consecutive blocks
|
||||
DIError ReadBlocks(long startBlock, short blockCount, void* buf);
|
||||
// write one or more consecutive blocks
|
||||
DIError WriteBlocks(long startBlock, short blockCount, const void* buf);
|
||||
// flush our internal cache
|
||||
DIError FlushCache(bool purge);
|
||||
|
||||
private:
|
||||
/*
|
||||
* Abstract base class with some handy functions.
|
||||
*/
|
||||
class BlockAccess {
|
||||
public:
|
||||
BlockAccess(void) { fIsWin9x = DiskImgLib::IsWin9x(); }
|
||||
virtual ~BlockAccess(void) {}
|
||||
|
||||
typedef struct {
|
||||
int numCyls;
|
||||
int numHeads;
|
||||
int numSectors;
|
||||
long blockCount; // total #of blocks on this kind of disk
|
||||
} DiskGeometry;
|
||||
|
||||
// generic interfaces
|
||||
virtual DIError Open(const WCHAR* deviceName, bool readOnly) = 0;
|
||||
virtual DIError DetectCapacity(long* pNumBlocks) = 0;
|
||||
virtual DIError ReadBlocks(long startBlock, short blockCount,
|
||||
void* buf) = 0;
|
||||
virtual DIError WriteBlocks(long startBlock, short blockCount,
|
||||
const void* buf) = 0;
|
||||
virtual DIError Close(void) = 0;
|
||||
|
||||
static bool BlockToCylinderHeadSector(long blockNum,
|
||||
const DiskGeometry* pGeometry, int* pCylinder, int* pHead,
|
||||
int* pSector, long* pLastBlockOnTrack);
|
||||
|
||||
enum {
|
||||
kNumLogicalVolumes = 26, // A-Z
|
||||
kBlockSize = 512,
|
||||
kCDROMSectorSize = 2048,
|
||||
kMaxFloppyRetries = 3, // retry floppy reads/writes
|
||||
};
|
||||
|
||||
// BIOS floppy disk drive type; doubles here as media type
|
||||
typedef enum {
|
||||
kFloppyUnknown = 0,
|
||||
kFloppy525_360 = 1,
|
||||
kFloppy525_1200 = 2,
|
||||
kFloppy35_720 = 3,
|
||||
kFloppy35_1440 = 4,
|
||||
kFloppy35_2880 = 5,
|
||||
|
||||
kFloppyMax
|
||||
} FloppyKind;
|
||||
|
||||
protected:
|
||||
static DIError GetFloppyDriveKind(HANDLE handle, int unitNum,
|
||||
FloppyKind* pKind);
|
||||
// detect the #of blocks on the volume
|
||||
static DIError ScanCapacity(BlockAccess* pThis, long* pNumBlocks);
|
||||
// determine whether a block is readable
|
||||
static bool CanReadBlock(BlockAccess* pThis, long blockNum);
|
||||
// try to detect device capacity using SPTI
|
||||
DIError DetectCapacitySPTI(HANDLE handle,
|
||||
bool isCDROM, long* pNumBlocks);
|
||||
|
||||
static int ReadBlocksInt13h(HANDLE handle, int unitNum,
|
||||
int cylinder, int head, int sector, short blockCount, void* buf);
|
||||
static DIError ReadBlocksInt13h(HANDLE handle, int unitNum,
|
||||
const DiskGeometry* pGeometry, long startBlock, short blockCount,
|
||||
void* buf);
|
||||
static int WriteBlocksInt13h(HANDLE handle, int unitNum,
|
||||
int cylinder, int head, int sector, short blockCount,
|
||||
const void* buf);
|
||||
static DIError WriteBlocksInt13h(HANDLE handle, int unitNum,
|
||||
const DiskGeometry* pGeometry, long startBlock, short blockCount,
|
||||
const void* buf);
|
||||
|
||||
static DIError ReadBlocksInt21h(HANDLE handle, int driveNum,
|
||||
long startBlock, short blockCount, void* buf);
|
||||
static DIError WriteBlocksInt21h(HANDLE handle, int driveNum,
|
||||
long startBlock, short blockCount, const void* buf);
|
||||
|
||||
static DIError ReadBlocksWin2K(HANDLE handle,
|
||||
long startBlock, short blockCount, void* buf);
|
||||
static DIError WriteBlocksWin2K(HANDLE handle,
|
||||
long startBlock, short blockCount, const void* buf);
|
||||
|
||||
bool fIsWin9x; // Win9x/ME=true, Win2K/XP=false
|
||||
};
|
||||
|
||||
/*
|
||||
* Access to a logical volume (e.g. "C:\") under Win9x and Win2K/XP.
|
||||
*/
|
||||
class LogicalBlockAccess : public BlockAccess {
|
||||
public:
|
||||
LogicalBlockAccess(void) : fHandle(NULL), fIsCDROM(false),
|
||||
fDriveNum(-1), fLastSectorCache(NULL), fLastSectorNum(-1)
|
||||
{}
|
||||
virtual ~LogicalBlockAccess(void) {
|
||||
if (fHandle != NULL) {
|
||||
//LOGI("HEY: LogicalBlockAccess: forcing close");
|
||||
Close();
|
||||
}
|
||||
delete[] fLastSectorCache;
|
||||
}
|
||||
|
||||
virtual DIError Open(const WCHAR* deviceName, bool readOnly);
|
||||
virtual DIError DetectCapacity(long* pNumBlocks) {
|
||||
/* use SCSI length value if at all possible */
|
||||
DIError dierr;
|
||||
dierr = DetectCapacitySPTI(fHandle, fIsCDROM, pNumBlocks);
|
||||
if (fIsCDROM)
|
||||
return dierr; // SPTI should always work for CD-ROM
|
||||
if (dierr != kDIErrNone)
|
||||
return ScanCapacity(this, pNumBlocks); // fall back on scan
|
||||
else
|
||||
return dierr;
|
||||
}
|
||||
virtual DIError ReadBlocks(long startBlock, short blockCount,
|
||||
void* buf)
|
||||
{
|
||||
if (fIsCDROM)
|
||||
return ReadBlocksCDROM(fHandle, startBlock, blockCount, buf);
|
||||
if (fIsWin9x)
|
||||
return ReadBlocksInt21h(fHandle, fDriveNum, startBlock,
|
||||
blockCount, buf);
|
||||
else
|
||||
return ReadBlocksWin2K(fHandle, startBlock, blockCount, buf);
|
||||
}
|
||||
virtual DIError WriteBlocks(long startBlock, short blockCount,
|
||||
const void* buf)
|
||||
{
|
||||
if (fIsCDROM)
|
||||
return kDIErrWriteProtected;
|
||||
if (fIsWin9x)
|
||||
return WriteBlocksInt21h(fHandle, fDriveNum, startBlock,
|
||||
blockCount, buf);
|
||||
else
|
||||
return WriteBlocksWin2K(fHandle, startBlock, blockCount, buf);
|
||||
}
|
||||
virtual DIError Close(void);
|
||||
|
||||
private:
|
||||
//DIError DetectCapacitySPTI(long* pNumBlocks);
|
||||
DIError ReadBlocksCDROM(HANDLE handle,
|
||||
long startBlock, short numBlocks, void* buf);
|
||||
|
||||
// Win2K/XP and Win9x/ME
|
||||
HANDLE fHandle;
|
||||
bool fIsCDROM; // set for CD-ROM devices
|
||||
// Win9x/ME
|
||||
int fDriveNum; // 1=A, 3=C, etc
|
||||
// CD-ROM goodies
|
||||
unsigned char* fLastSectorCache;
|
||||
long fLastSectorNum;
|
||||
};
|
||||
|
||||
/*
|
||||
* Access to a physical volume (e.g. 00h or 80h) under Win9x and
|
||||
* Win2K/XP.
|
||||
*/
|
||||
class PhysicalBlockAccess : public BlockAccess {
|
||||
public:
|
||||
PhysicalBlockAccess(void) :
|
||||
fHandle(NULL),
|
||||
fInt13Unit(-1),
|
||||
fFloppyKind(kFloppyUnknown)
|
||||
{}
|
||||
virtual ~PhysicalBlockAccess(void) {}
|
||||
|
||||
virtual DIError Open(const WCHAR* deviceName, bool readOnly);
|
||||
virtual DIError DetectCapacity(long* pNumBlocks) {
|
||||
/* try SPTI in case it happens to work */
|
||||
DIError dierr;
|
||||
dierr = DetectCapacitySPTI(fHandle, false, pNumBlocks);
|
||||
if (dierr != kDIErrNone)
|
||||
return ScanCapacity(this, pNumBlocks);
|
||||
else
|
||||
return dierr;
|
||||
}
|
||||
virtual DIError ReadBlocks(long startBlock, short blockCount,
|
||||
void* buf)
|
||||
{
|
||||
if (fIsWin9x)
|
||||
return ReadBlocksInt13h(fHandle, fInt13Unit,
|
||||
&fGeometry, startBlock, blockCount, buf);
|
||||
else
|
||||
return ReadBlocksWin2K(fHandle,
|
||||
startBlock, blockCount, buf);
|
||||
}
|
||||
virtual DIError WriteBlocks(long startBlock, short blockCount,
|
||||
const void* buf)
|
||||
{
|
||||
if (fIsWin9x)
|
||||
return WriteBlocksInt13h(fHandle, fInt13Unit,
|
||||
&fGeometry, startBlock, blockCount, buf);
|
||||
else
|
||||
return WriteBlocksWin2K(fHandle,
|
||||
startBlock, blockCount, buf);
|
||||
}
|
||||
virtual DIError Close(void);
|
||||
|
||||
private:
|
||||
DIError DetectFloppyGeometry(void);
|
||||
|
||||
// Win2K/XP
|
||||
HANDLE fHandle;
|
||||
// Win9x/ME
|
||||
int fInt13Unit; // 00h=floppy #1, 80h=HD#1
|
||||
FloppyKind fFloppyKind;
|
||||
DiskGeometry fGeometry;
|
||||
};
|
||||
|
||||
#ifdef WANT_ASPI
|
||||
/*
|
||||
* Access to a SCSI volume via the ASPI interface.
|
||||
*/
|
||||
class ASPIBlockAccess : public BlockAccess {
|
||||
public:
|
||||
ASPIBlockAccess(void) : fpASPI(NULL),
|
||||
fAdapter(0xff), fTarget(0xff), fLun(0xff), fReadOnly(false),
|
||||
fLastChunkCache(NULL), fLastChunkNum(-1), fChunkSize(-1)
|
||||
{}
|
||||
virtual ~ASPIBlockAccess(void) { delete[] fLastChunkCache; }
|
||||
|
||||
virtual DIError Open(const char* deviceName, bool readOnly);
|
||||
virtual DIError DetectCapacity(long* pNumBlocks);
|
||||
virtual DIError ReadBlocks(long startBlock, short blockCount,
|
||||
void* buf);
|
||||
virtual DIError WriteBlocks(long startBlock, short blockCount,
|
||||
const void* buf);
|
||||
virtual DIError Close(void);
|
||||
|
||||
private:
|
||||
int ExtractInt(const char** pStr, int* pResult);
|
||||
|
||||
ASPI* fpASPI;
|
||||
unsigned char fAdapter;
|
||||
unsigned char fTarget;
|
||||
unsigned char fLun;
|
||||
|
||||
bool fReadOnly;
|
||||
|
||||
// block cache
|
||||
unsigned char* fLastChunkCache;
|
||||
long fLastChunkNum;
|
||||
long fChunkSize; // set by DetectCapacity
|
||||
};
|
||||
#endif /*WANT_ASPI*/
|
||||
|
||||
// write a series of blocks to the volume
|
||||
DIError DoWriteBlocks(long startBlock, short blockCount, const void* buf)
|
||||
{
|
||||
return fpBlockAccess->WriteBlocks(startBlock, blockCount, buf);
|
||||
}
|
||||
|
||||
long fTotalBlocks;
|
||||
BlockAccess* fpBlockAccess; // really LogicalBA or PhysicalBA
|
||||
CBCache fBlockCache;
|
||||
};
|
||||
|
||||
|
||||
}; // namespace DiskImgLib
|
||||
|
||||
#endif /*DISKIMG_WIN32BLOCKIO_H*/
|
||||
|
||||
#endif /*_WIN32*/
|
60
diskimg/Win32Extra.h
Normal file
60
diskimg/Win32Extra.h
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* CiderPress
|
||||
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||||
* See the file LICENSE for distribution terms.
|
||||
*/
|
||||
/*
|
||||
* Visual C++ 6.0 doesn't have this definition, because it wasn't added until
|
||||
* WinXP.
|
||||
*
|
||||
* (Do we want IOCTL_DISK_GET_DRIVE_LAYOUT_EX too?)
|
||||
*/
|
||||
#ifndef DISKIMG_WIN32EXTRA_H
|
||||
#define DISKIMG_WIN32EXTRA_H
|
||||
|
||||
#include <winioctl.h> // base definitions
|
||||
|
||||
#ifndef IOCTL_DISK_GET_DRIVE_GEOMETRY_EX
|
||||
|
||||
/*
|
||||
BOOL DeviceIoControl(
|
||||
(HANDLE) hDevice, // handle to volume
|
||||
IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, // dwIoControlCode
|
||||
NULL, // lpInBuffer
|
||||
0, // nInBufferSize
|
||||
(LPVOID) lpOutBuffer, // output buffer
|
||||
(DWORD) nOutBufferSize, // size of output buffer
|
||||
(LPDWORD) lpBytesReturned, // number of bytes returned
|
||||
(LPOVERLAPPED) lpOverlapped // OVERLAPPED structure
|
||||
);
|
||||
*/
|
||||
|
||||
#define IOCTL_DISK_GET_DRIVE_GEOMETRY_EX \
|
||||
CTL_CODE(IOCTL_DISK_BASE, 0x0028, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
typedef struct _DISK_GEOMETRY_EX {
|
||||
DISK_GEOMETRY Geometry;
|
||||
LARGE_INTEGER DiskSize;
|
||||
BYTE Data[1];
|
||||
} DISK_GEOMETRY_EX, *PDISK_GEOMETRY_EX;
|
||||
|
||||
#if 0
|
||||
typedef struct _DISK_DETECTION_INFO {
|
||||
DWORD SizeOfDetectInfo;
|
||||
DETECTION_TYPE DetectionType;
|
||||
union {
|
||||
struct {
|
||||
DISK_INT13_INFO Int13;
|
||||
DISK_EX_INT13_INFO ExInt13;
|
||||
};
|
||||
};
|
||||
} DISK_DETECTION_INFO, *PDISK_DETECTION_INFO;
|
||||
|
||||
PDISK_DETECTION_INFO DiskGeometryGetDetect(PDISK_GEOMETRY_EX Geometry);
|
||||
PDISK_PARTITION_INFO DiskGeometryGetPartition(PDISK_GEOMETRY_EX Geometry);
|
||||
#endif
|
||||
|
||||
|
||||
#endif /*IOCTL_DISK_GET_DRIVE_GEOMETRY_EX*/
|
||||
|
||||
#endif /*DISKIMG_WIN32EXTRA_H*/
|
216
diskimg/diskimg.vcxproj
Normal file
216
diskimg/diskimg.vcxproj
Normal file
@ -0,0 +1,216 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<SccProjectName />
|
||||
<SccLocalPath />
|
||||
<ProjectGuid>{0CFE6FAD-0126-4E99-8625-C807D1D2AAF4}</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<UseOfMfc>Dynamic</UseOfMfc>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<UseOfMfc>false</UseOfMfc>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<_ProjectFileVersion>12.0.30501.0</_ProjectFileVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
|
||||
<IntDir>$(Configuration)\</IntDir>
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<TargetName>diskimg5</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
|
||||
<IntDir>$(Configuration)\</IntDir>
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<TargetName>diskimg5</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUGX;_WINDOWS;_USRDLL;DISKIMG_EXPORTS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<StringPooling>true</StringPooling>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)$(TargetName).pch</PrecompiledHeaderOutputFile>
|
||||
<AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
|
||||
<ObjectFileName>$(IntDir)</ObjectFileName>
|
||||
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
<SuppressStartupBanner>false</SuppressStartupBanner>
|
||||
<ProgramDatabaseFile>$(OutDir)$(TargetName).pdb</ProgramDatabaseFile>
|
||||
<ImportLibrary>$(OutDir)$(TargetName).lib</ImportLibrary>
|
||||
<TargetMachine>MachineX86</TargetMachine>
|
||||
<ImageHasSafeExceptionHandlers />
|
||||
<SubSystem>Windows</SubSystem>
|
||||
</Link>
|
||||
<Midl>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<MkTypLibCompatible>true</MkTypLibCompatible>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
<TargetEnvironment>Win32</TargetEnvironment>
|
||||
<TypeLibraryName>.\Release/diskimg.tlb</TypeLibraryName>
|
||||
<HeaderFileName />
|
||||
</Midl>
|
||||
<PostBuildEvent>
|
||||
<Message>
|
||||
</Message>
|
||||
<Command>
|
||||
</Command>
|
||||
</PostBuildEvent>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Culture>0x0409</Culture>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;DISKIMG_EXPORTS;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)$(TargetName).pch</PrecompiledHeaderOutputFile>
|
||||
<AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
|
||||
<ObjectFileName>$(IntDir)</ObjectFileName>
|
||||
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
<SuppressStartupBanner>false</SuppressStartupBanner>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<ProgramDatabaseFile>$(OutDir)$(TargetName).pdb</ProgramDatabaseFile>
|
||||
<ImportLibrary>$(OutDir)$(TargetName).lib</ImportLibrary>
|
||||
<TargetMachine>MachineX86</TargetMachine>
|
||||
<ImageHasSafeExceptionHandlers />
|
||||
<SubSystem>Windows</SubSystem>
|
||||
</Link>
|
||||
<Midl>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<MkTypLibCompatible>true</MkTypLibCompatible>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
<TargetEnvironment>Win32</TargetEnvironment>
|
||||
<TypeLibraryName>.\Debug/diskimg.tlb</TypeLibraryName>
|
||||
<HeaderFileName />
|
||||
</Midl>
|
||||
<PostBuildEvent>
|
||||
<Message>
|
||||
</Message>
|
||||
<Command>
|
||||
</Command>
|
||||
</PostBuildEvent>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Culture>0x0409</Culture>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="CP_ntddscsi.h" />
|
||||
<ClInclude Include="CP_wnaspi32.h" />
|
||||
<ClInclude Include="DiskImg.h" />
|
||||
<ClInclude Include="DiskImgDetail.h" />
|
||||
<ClInclude Include="DiskImgPriv.h" />
|
||||
<ClInclude Include="GenericFD.h" />
|
||||
<ClInclude Include="SCSIDefs.h" />
|
||||
<ClInclude Include="SPTI.h" />
|
||||
<ClInclude Include="StdAfx.h" />
|
||||
<ClInclude Include="TwoImg.h" />
|
||||
<ClInclude Include="Win32BlockIO.h" />
|
||||
<ClInclude Include="Win32Extra.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\nufxlib\nufxlib.vcxproj">
|
||||
<Project>{c48ae53b-3dcb-43b1-9207-b7c5b6bb78af}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\zlib\zlib.vcxproj">
|
||||
<Project>{b66109f4-217b-43c0-86aa-eb55657e5ac0}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="libhfs\libhfs.vcxproj">
|
||||
<Project>{0fa742e9-8c07-43dd-aff8-ce31faf70821}</Project>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ASPI.cpp" />
|
||||
<ClCompile Include="CFFA.cpp" />
|
||||
<ClCompile Include="Container.cpp" />
|
||||
<ClCompile Include="CPM.cpp" />
|
||||
<ClCompile Include="DDD.cpp" />
|
||||
<ClCompile Include="DiskFS.cpp" />
|
||||
<ClCompile Include="DiskImg.cpp" />
|
||||
<ClCompile Include="DIUtil.cpp" />
|
||||
<ClCompile Include="DOS33.cpp" />
|
||||
<ClCompile Include="DOSImage.cpp" />
|
||||
<ClCompile Include="FAT.cpp" />
|
||||
<ClCompile Include="FDI.cpp" />
|
||||
<ClCompile Include="FocusDrive.cpp" />
|
||||
<ClCompile Include="GenericFD.cpp" />
|
||||
<ClCompile Include="Global.cpp" />
|
||||
<ClCompile Include="Gutenberg.cpp" />
|
||||
<ClCompile Include="HFS.cpp" />
|
||||
<ClCompile Include="ImageWrapper.cpp" />
|
||||
<ClCompile Include="MacPart.cpp" />
|
||||
<ClCompile Include="MicroDrive.cpp" />
|
||||
<ClCompile Include="Nibble.cpp" />
|
||||
<ClCompile Include="Nibble35.cpp" />
|
||||
<ClCompile Include="OuterWrapper.cpp" />
|
||||
<ClCompile Include="OzDOS.cpp" />
|
||||
<ClCompile Include="Pascal.cpp" />
|
||||
<ClCompile Include="ProDOS.cpp" />
|
||||
<ClCompile Include="RDOS.cpp" />
|
||||
<ClCompile Include="SPTI.cpp" />
|
||||
<ClCompile Include="StdAfx.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TwoImg.cpp" />
|
||||
<ClCompile Include="UNIDOS.cpp" />
|
||||
<ClCompile Include="VolumeUsage.cpp" />
|
||||
<ClCompile Include="Win32BlockIO.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
156
diskimg/diskimg.vcxproj.filters
Normal file
156
diskimg/diskimg.vcxproj.filters
Normal file
@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{b6ac9831-b535-4474-a308-d3bf6fdf4488}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cxx;rc;def;r;odl;idl;hpj;bat</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{def4def1-c9b0-48b3-a80b-c137f7ebf9bd}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{ed4624b4-6280-4672-8e7f-e8aac6e178ef}</UniqueIdentifier>
|
||||
<Extensions>ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="CP_ntddscsi.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CP_wnaspi32.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="DiskImg.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="DiskImgDetail.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="DiskImgPriv.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GenericFD.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SCSIDefs.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="SPTI.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="StdAfx.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TwoImg.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Win32BlockIO.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Win32Extra.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ASPI.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CFFA.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Container.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CPM.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DDD.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DiskFS.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DiskImg.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DIUtil.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DOS33.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="DOSImage.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FAT.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FDI.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="FocusDrive.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GenericFD.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Global.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Gutenberg.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HFS.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ImageWrapper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MacPart.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="MicroDrive.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Nibble.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Nibble35.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="OuterWrapper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="OzDOS.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Pascal.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ProDOS.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="RDOS.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="SPTI.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="TwoImg.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="UNIDOS.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="VolumeUsage.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Win32BlockIO.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="StdAfx.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
4
diskimg/diskimg.vcxproj.user
Normal file
4
diskimg/diskimg.vcxproj.user
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
</Project>
|
54
libhfs/CMakeLists.txt
Normal file
54
libhfs/CMakeLists.txt
Normal file
@ -0,0 +1,54 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
set(CMAKE_BUILD_TYPE DEBUG)
|
||||
|
||||
set(BASE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
set(PROJECT_NAME hfs)
|
||||
set(PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
project(${PROJECT_NAME})
|
||||
|
||||
|
||||
set(ALL_DEFINES "-Wwrite-strings -Wno-pointer-sign -Wpointer-arith -Wshadow -Wstrict-prototypes -D_FILE_OFFSET_BITS=64" )
|
||||
set(CONFIG_DEF "-DSTDC_HEADERS=1 -DHAVE_MKTIME=1 -DHAVE_FCNTL_H=1 -DHAVE_UNISTD_H=1" )
|
||||
set(DEBUG_OPT "-O0 -g3 " )
|
||||
set(RELEASE_OPT "-O3 " )
|
||||
|
||||
set(CMAKE_C_FLAGS "-Wall ${ALL_DEFINES} ${CONFIG_DEF}")
|
||||
set(CMAKE_CXX_FLAGS "-Wall ${ALL_DEFINES} ${CONFIG_DEF}")
|
||||
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${DEBUG_OPT} ")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${RELEASE_OPT}")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${DEBUG_OPT} ")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${RELEASE_OPT}")
|
||||
|
||||
set(FIND_LIBRARY_USE_LIB64_PATHS TRUE)
|
||||
|
||||
set (SOURCE
|
||||
block.c
|
||||
btree.c
|
||||
data.c
|
||||
file.c
|
||||
hfs.c
|
||||
low.c
|
||||
medium.c
|
||||
memcmp.c
|
||||
node.c
|
||||
os.c
|
||||
record.c
|
||||
version.c
|
||||
volume.c
|
||||
)
|
||||
|
||||
include_directories(BEFORE
|
||||
${PROJECT_ROOT}
|
||||
)
|
||||
|
||||
add_library( ${PROJECT_NAME} SHARED ${SOURCE})
|
||||
add_library( ${PROJECT_NAME}_static STATIC ${SOURCE})
|
||||
|
||||
target_link_libraries (
|
||||
${PROJECT_NAME}
|
||||
)
|
||||
|
||||
|
||||
|
21
libhfs/COPYRIGHT
Normal file
21
libhfs/COPYRIGHT
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
hfsutils - tools for reading and writing Macintosh HFS volumes
|
||||
Copyright (C) 1996-1998 Robert Leslie
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
If you would like to negotiate alternate licensing terms, you may do
|
||||
so by contacting the author: Robert Leslie <rob@mars.org>
|
||||
|
39
libhfs/Makefile
Normal file
39
libhfs/Makefile
Normal file
@ -0,0 +1,39 @@
|
||||
#
|
||||
# DiskImg libhfs makefile for Linux.
|
||||
#
|
||||
SHELL = /bin/sh
|
||||
CC = gcc
|
||||
AR = ar
|
||||
OPT = -g -DHAVE_CONFIG_H
|
||||
#OPT = -g -O2 -DHAVE_CONFIG_H
|
||||
GCC_FLAGS = -Wall -Wwrite-strings -Wpointer-arith -Wshadow -Wstrict-prototypes
|
||||
CFLAGS = $(OPT) $(GCC_FLAGS) -D_FILE_OFFSET_BITS=64
|
||||
|
||||
SRCS = os.c data.c block.c low.c medium.c file.c btree.c node.c \
|
||||
record.c volume.c hfs.c version.c
|
||||
OBJS = os.o data.o block.o low.o medium.o file.o btree.o node.o \
|
||||
record.o volume.o hfs.o version.o
|
||||
|
||||
STATIC_PRODUCT = libhfs.a
|
||||
PRODUCT = $(STATIC_PRODUCT)
|
||||
|
||||
all: $(PRODUCT)
|
||||
@true
|
||||
|
||||
$(STATIC_PRODUCT): $(OBJS)
|
||||
-rm -f $(STATIC_PRODUCT)
|
||||
$(AR) rcv $@ $(OBJS)
|
||||
|
||||
clean:
|
||||
-rm -f *.o core
|
||||
-rm -f $(STATIC_PRODUCT)
|
||||
-rm -f Makefile.bak
|
||||
|
||||
tags::
|
||||
@ctags -R --totals *
|
||||
|
||||
depend:
|
||||
makedepend -- $(CFLAGS) -- $(SRCS)
|
||||
|
||||
# DO NOT DELETE THIS LINE -- make depend depends on it.
|
||||
|
5
libhfs/README
Normal file
5
libhfs/README
Normal file
@ -0,0 +1,5 @@
|
||||
HFS utility library, part of hfsutils v3.2.6 by Robert Leslie.
|
||||
|
||||
Adapted for use with CiderPress by Andy McFadden. The os_* functions
|
||||
have been replaced with a callback mechanism, and code that uses global
|
||||
variables has been (mostly) removed.
|
272
libhfs/apple.h
Normal file
272
libhfs/apple.h
Normal file
@ -0,0 +1,272 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
typedef signed char Char;
|
||||
typedef unsigned char UChar;
|
||||
typedef signed char SignedByte;
|
||||
typedef signed short Integer;
|
||||
typedef unsigned short UInteger;
|
||||
typedef signed long LongInt;
|
||||
typedef unsigned long ULongInt;
|
||||
typedef char Str15[16];
|
||||
typedef char Str31[32];
|
||||
typedef long OSType;
|
||||
|
||||
typedef struct {
|
||||
Integer sbSig; /* device signature (should be 0x4552) */
|
||||
Integer sbBlkSize; /* block size of the device (in bytes) */
|
||||
LongInt sbBlkCount; /* number of blocks on the device */
|
||||
Integer sbDevType; /* reserved */
|
||||
Integer sbDevId; /* reserved */
|
||||
LongInt sbData; /* reserved */
|
||||
Integer sbDrvrCount; /* number of driver descriptor entries */
|
||||
LongInt ddBlock; /* first driver's starting block */
|
||||
Integer ddSize; /* size of the driver, in 512-byte blocks */
|
||||
Integer ddType; /* driver operating system type (MacOS = 1) */
|
||||
Integer ddPad[243]; /* additional drivers, if any */
|
||||
} Block0;
|
||||
|
||||
typedef struct {
|
||||
Integer pmSig; /* partition signature (0x504d or 0x5453) */
|
||||
Integer pmSigPad; /* reserved */
|
||||
LongInt pmMapBlkCnt; /* number of blocks in partition map */
|
||||
LongInt pmPyPartStart; /* first physical block of partition */
|
||||
LongInt pmPartBlkCnt; /* number of blocks in partition */
|
||||
Char pmPartName[33]; /* partition name */
|
||||
Char pmParType[33]; /* partition type */
|
||||
LongInt pmLgDataStart; /* first logical block of data area */
|
||||
LongInt pmDataCnt; /* number of blocks in data area */
|
||||
LongInt pmPartStatus; /* partition status information */
|
||||
LongInt pmLgBootStart; /* first logical block of boot code */
|
||||
LongInt pmBootSize; /* size of boot code, in bytes */
|
||||
LongInt pmBootAddr; /* boot code load address */
|
||||
LongInt pmBootAddr2; /* reserved */
|
||||
LongInt pmBootEntry; /* boot code entry point */
|
||||
LongInt pmBootEntry2; /* reserved */
|
||||
LongInt pmBootCksum; /* boot code checksum */
|
||||
Char pmProcessor[17];/* processor type */
|
||||
Integer pmPad[188]; /* reserved */
|
||||
} Partition;
|
||||
|
||||
typedef struct {
|
||||
Integer bbID; /* boot blocks signature */
|
||||
LongInt bbEntry; /* entry point to boot code */
|
||||
Integer bbVersion; /* boot blocks version number */
|
||||
Integer bbPageFlags; /* used internally */
|
||||
Str15 bbSysName; /* System filename */
|
||||
Str15 bbShellName; /* Finder filename */
|
||||
Str15 bbDbg1Name; /* debugger filename */
|
||||
Str15 bbDbg2Name; /* debugger filename */
|
||||
Str15 bbScreenName; /* name of startup screen */
|
||||
Str15 bbHelloName; /* name of startup program */
|
||||
Str15 bbScrapName; /* name of system scrap file */
|
||||
Integer bbCntFCBs; /* number of FCBs to allocate */
|
||||
Integer bbCntEvts; /* number of event queue elements */
|
||||
LongInt bb128KSHeap; /* system heap size on 128K Mac */
|
||||
LongInt bb256KSHeap; /* used internally */
|
||||
LongInt bbSysHeapSize; /* system heap size on all machines */
|
||||
Integer filler; /* reserved */
|
||||
LongInt bbSysHeapExtra; /* additional system heap space */
|
||||
LongInt bbSysHeapFract; /* fraction of RAM for system heap */
|
||||
} BootBlkHdr;
|
||||
|
||||
typedef struct {
|
||||
UInteger xdrStABN; /* first allocation block */
|
||||
UInteger xdrNumABlks; /* number of allocation blocks */
|
||||
} ExtDescriptor;
|
||||
|
||||
typedef ExtDescriptor ExtDataRec[3];
|
||||
|
||||
typedef struct {
|
||||
SignedByte xkrKeyLen; /* key length */
|
||||
SignedByte xkrFkType; /* fork type (0x00/0xff == data/resource */
|
||||
ULongInt xkrFNum; /* file number */
|
||||
UInteger xkrFABN; /* starting file allocation block */
|
||||
} ExtKeyRec;
|
||||
|
||||
typedef struct {
|
||||
SignedByte ckrKeyLen; /* key length */
|
||||
SignedByte ckrResrv1; /* reserved */
|
||||
ULongInt ckrParID; /* parent directory ID */
|
||||
Str31 ckrCName; /* catalog node name */
|
||||
} CatKeyRec;
|
||||
|
||||
typedef struct {
|
||||
Integer v; /* vertical coordinate */
|
||||
Integer h; /* horizontal coordinate */
|
||||
} Point;
|
||||
|
||||
typedef struct {
|
||||
Integer top; /* top edge of rectangle */
|
||||
Integer left; /* left edge */
|
||||
Integer bottom; /* bottom edge */
|
||||
Integer right; /* right edge */
|
||||
} Rect;
|
||||
|
||||
typedef struct {
|
||||
Rect frRect; /* folder's rectangle */
|
||||
Integer frFlags; /* flags */
|
||||
Point frLocation; /* folder's location */
|
||||
Integer frView; /* folder's view */
|
||||
} DInfo;
|
||||
|
||||
typedef struct {
|
||||
Point frScroll; /* scroll position */
|
||||
LongInt frOpenChain; /* directory ID chain of open folders */
|
||||
Integer frUnused; /* reserved */
|
||||
Integer frComment; /* comment ID */
|
||||
LongInt frPutAway; /* directory ID */
|
||||
} DXInfo;
|
||||
|
||||
typedef struct {
|
||||
OSType fdType; /* file type */
|
||||
OSType fdCreator; /* file's creator */
|
||||
Integer fdFlags; /* flags */
|
||||
Point fdLocation; /* file's location */
|
||||
Integer fdFldr; /* file's window */
|
||||
} FInfo;
|
||||
|
||||
typedef struct {
|
||||
Integer fdIconID; /* icon ID */
|
||||
Integer fdUnused[4]; /* reserved */
|
||||
Integer fdComment; /* comment ID */
|
||||
LongInt fdPutAway; /* home directory ID */
|
||||
} FXInfo;
|
||||
|
||||
typedef struct {
|
||||
Integer drSigWord; /* volume signature (0x4244 for HFS) */
|
||||
LongInt drCrDate; /* date and time of volume creation */
|
||||
LongInt drLsMod; /* date and time of last modification */
|
||||
Integer drAtrb; /* volume attributes */
|
||||
UInteger drNmFls; /* number of files in root directory */
|
||||
UInteger drVBMSt; /* first block of volume bit map (always 3) */
|
||||
UInteger drAllocPtr; /* start of next allocation search */
|
||||
UInteger drNmAlBlks; /* number of allocation blocks in volume */
|
||||
ULongInt drAlBlkSiz; /* size (in bytes) of allocation blocks */
|
||||
ULongInt drClpSiz; /* default clump size */
|
||||
UInteger drAlBlSt; /* first allocation block in volume */
|
||||
LongInt drNxtCNID; /* next unused catalog node ID (dir/file ID) */
|
||||
UInteger drFreeBks; /* number of unused allocation blocks */
|
||||
char drVN[28]; /* volume name (1-27 chars) */
|
||||
LongInt drVolBkUp; /* date and time of last backup */
|
||||
Integer drVSeqNum; /* volume backup sequence number */
|
||||
ULongInt drWrCnt; /* volume write count */
|
||||
ULongInt drXTClpSiz; /* clump size for extents overflow file */
|
||||
ULongInt drCTClpSiz; /* clump size for catalog file */
|
||||
UInteger drNmRtDirs; /* number of directories in root directory */
|
||||
ULongInt drFilCnt; /* number of files in volume */
|
||||
ULongInt drDirCnt; /* number of directories in volume */
|
||||
LongInt drFndrInfo[8]; /* information used by the Finder */
|
||||
UInteger drEmbedSigWord; /* type of embedded volume */
|
||||
ExtDescriptor drEmbedExtent; /* location of embedded volume */
|
||||
ULongInt drXTFlSize; /* size (in bytes) of extents overflow file */
|
||||
ExtDataRec drXTExtRec; /* first extent record for extents file */
|
||||
ULongInt drCTFlSize; /* size (in bytes) of catalog file */
|
||||
ExtDataRec drCTExtRec; /* first extent record for catalog file */
|
||||
} MDB;
|
||||
|
||||
typedef enum {
|
||||
cdrDirRec = 1,
|
||||
cdrFilRec = 2,
|
||||
cdrThdRec = 3,
|
||||
cdrFThdRec = 4
|
||||
} CatDataType;
|
||||
|
||||
typedef struct {
|
||||
SignedByte cdrType; /* record type */
|
||||
SignedByte cdrResrv2; /* reserved */
|
||||
union {
|
||||
struct { /* cdrDirRec */
|
||||
Integer dirFlags; /* directory flags */
|
||||
UInteger dirVal; /* directory valence */
|
||||
ULongInt dirDirID; /* directory ID */
|
||||
LongInt dirCrDat; /* date and time of creation */
|
||||
LongInt dirMdDat; /* date and time of last modification */
|
||||
LongInt dirBkDat; /* date and time of last backup */
|
||||
DInfo dirUsrInfo; /* Finder information */
|
||||
DXInfo dirFndrInfo; /* additional Finder information */
|
||||
LongInt dirResrv[4]; /* reserved */
|
||||
} dir;
|
||||
struct { /* cdrFilRec */
|
||||
SignedByte
|
||||
filFlags; /* file flags */
|
||||
SignedByte
|
||||
filTyp; /* file type */
|
||||
FInfo filUsrWds; /* Finder information */
|
||||
ULongInt filFlNum; /* file ID */
|
||||
UInteger filStBlk; /* first alloc block of data fork */
|
||||
ULongInt filLgLen; /* logical EOF of data fork */
|
||||
ULongInt filPyLen; /* physical EOF of data fork */
|
||||
UInteger filRStBlk; /* first alloc block of resource fork */
|
||||
ULongInt filRLgLen; /* logical EOF of resource fork */
|
||||
ULongInt filRPyLen; /* physical EOF of resource fork */
|
||||
LongInt filCrDat; /* date and time of creation */
|
||||
LongInt filMdDat; /* date and time of last modification */
|
||||
LongInt filBkDat; /* date and time of last backup */
|
||||
FXInfo filFndrInfo; /* additional Finder information */
|
||||
UInteger filClpSize; /* file clump size */
|
||||
ExtDataRec
|
||||
filExtRec; /* first data fork extent record */
|
||||
ExtDataRec
|
||||
filRExtRec; /* first resource fork extent record */
|
||||
LongInt filResrv; /* reserved */
|
||||
} fil;
|
||||
struct { /* cdrThdRec */
|
||||
LongInt thdResrv[2]; /* reserved */
|
||||
ULongInt thdParID; /* parent ID for this directory */
|
||||
Str31 thdCName; /* name of this directory */
|
||||
} dthd;
|
||||
struct { /* cdrFThdRec */
|
||||
LongInt fthdResrv[2]; /* reserved */
|
||||
ULongInt fthdParID; /* parent ID for this file */
|
||||
Str31 fthdCName; /* name of this file */
|
||||
} fthd;
|
||||
} u;
|
||||
} CatDataRec;
|
||||
|
||||
typedef struct {
|
||||
ULongInt ndFLink; /* forward link */
|
||||
ULongInt ndBLink; /* backward link */
|
||||
SignedByte ndType; /* node type */
|
||||
SignedByte ndNHeight; /* node level */
|
||||
UInteger ndNRecs; /* number of records in node */
|
||||
Integer ndResv2; /* reserved */
|
||||
} NodeDescriptor;
|
||||
|
||||
enum {
|
||||
ndIndxNode = (SignedByte) 0x00,
|
||||
ndHdrNode = (SignedByte) 0x01,
|
||||
ndMapNode = (SignedByte) 0x02,
|
||||
ndLeafNode = (SignedByte) 0xff
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
UInteger bthDepth; /* current depth of tree */
|
||||
ULongInt bthRoot; /* number of root node */
|
||||
ULongInt bthNRecs; /* number of leaf records in tree */
|
||||
ULongInt bthFNode; /* number of first leaf node */
|
||||
ULongInt bthLNode; /* number of last leaf node */
|
||||
UInteger bthNodeSize; /* size of a node */
|
||||
UInteger bthKeyLen; /* maximum length of a key */
|
||||
ULongInt bthNNodes; /* total number of nodes in tree */
|
||||
ULongInt bthFree; /* number of free nodes */
|
||||
SignedByte bthResv[76]; /* reserved */
|
||||
} BTHdrRec;
|
807
libhfs/block.c
Normal file
807
libhfs/block.c
Normal file
@ -0,0 +1,807 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
# ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
# endif
|
||||
|
||||
# include <stdlib.h>
|
||||
# include <string.h>
|
||||
# include <errno.h>
|
||||
|
||||
# include "libhfs.h"
|
||||
# include "volume.h"
|
||||
# include "block.h"
|
||||
# include "os.h"
|
||||
|
||||
# define INUSE(b) ((b)->flags & HFS_BUCKET_INUSE)
|
||||
# define DIRTY(b) ((b)->flags & HFS_BUCKET_DIRTY)
|
||||
|
||||
/*
|
||||
* NAME: block->init()
|
||||
* DESCRIPTION: initialize a volume's block cache
|
||||
*/
|
||||
int b_init(hfsvol *vol)
|
||||
{
|
||||
bcache *cache;
|
||||
int i;
|
||||
|
||||
ASSERT(vol->cache == 0);
|
||||
|
||||
cache = ALLOC(bcache, 1);
|
||||
if (cache == 0)
|
||||
ERROR(ENOMEM, 0);
|
||||
|
||||
vol->cache = cache;
|
||||
|
||||
cache->vol = vol;
|
||||
cache->tail = &cache->chain[HFS_CACHESZ - 1];
|
||||
|
||||
cache->hits = 0;
|
||||
cache->misses = 0;
|
||||
|
||||
for (i = 0; i < HFS_CACHESZ; ++i)
|
||||
{
|
||||
bucket *b = &cache->chain[i];
|
||||
|
||||
b->flags = 0;
|
||||
b->count = 0;
|
||||
|
||||
b->bnum = 0;
|
||||
b->data = &cache->pool[i];
|
||||
|
||||
b->cnext = b + 1;
|
||||
b->cprev = b - 1;
|
||||
|
||||
b->hnext = 0;
|
||||
b->hprev = 0;
|
||||
}
|
||||
|
||||
cache->chain[0].cprev = cache->tail;
|
||||
cache->tail->cnext = &cache->chain[0];
|
||||
|
||||
for (i = 0; i < HFS_HASHSZ; ++i)
|
||||
cache->hash[i] = 0;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
# ifdef DEBUG
|
||||
/*
|
||||
* NAME: block->showstats()
|
||||
* DESCRIPTION: output cache hit/miss ratio
|
||||
*/
|
||||
void b_showstats(const bcache *cache)
|
||||
{
|
||||
fprintf(stderr, "BLOCK: CACHE vol 0x%lx \"%s\" hit/miss ratio = %.3f\n",
|
||||
(unsigned long) cache->vol, cache->vol->mdb.drVN,
|
||||
(float) cache->hits / (float) cache->misses);
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: block->dumpcache()
|
||||
* DESCRIPTION: dump the cache tables for a volume
|
||||
*/
|
||||
void b_dumpcache(const bcache *cache)
|
||||
{
|
||||
const bucket *b;
|
||||
int i;
|
||||
|
||||
fprintf(stderr, "BLOCK CACHE DUMP:\n");
|
||||
|
||||
for (i = 0, b = cache->tail->cnext; i < HFS_CACHESZ; ++i, b = b->cnext)
|
||||
{
|
||||
if (INUSE(b))
|
||||
{
|
||||
fprintf(stderr, "\t %lu", b->bnum);
|
||||
if (DIRTY(b))
|
||||
fprintf(stderr, "*");
|
||||
|
||||
fprintf(stderr, ":%u", b->count);
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "\n");
|
||||
|
||||
fprintf(stderr, "BLOCK HASH DUMP:\n");
|
||||
|
||||
for (i = 0; i < HFS_HASHSZ; ++i)
|
||||
{
|
||||
int seen = 0;
|
||||
|
||||
for (b = cache->hash[i]; b; b = b->hnext)
|
||||
{
|
||||
if (! seen)
|
||||
fprintf(stderr, " %d:", i);
|
||||
|
||||
if (INUSE(b))
|
||||
{
|
||||
fprintf(stderr, " %lu", b->bnum);
|
||||
if (DIRTY(b))
|
||||
fprintf(stderr, "*");
|
||||
|
||||
fprintf(stderr, ":%u", b->count);
|
||||
}
|
||||
|
||||
seen = 1;
|
||||
}
|
||||
|
||||
if (seen)
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
}
|
||||
# endif
|
||||
|
||||
/*
|
||||
* NAME: fillchain()
|
||||
* DESCRIPTION: fill a chain of bucket buffers with a single read
|
||||
*/
|
||||
static
|
||||
int fillchain(hfsvol *vol, bucket **bptr, unsigned int *count)
|
||||
{
|
||||
bucket *blist[HFS_BLOCKBUFSZ], **start = bptr;
|
||||
unsigned long bnum;
|
||||
unsigned int len, i;
|
||||
|
||||
for (len = 0; len < HFS_BLOCKBUFSZ &&
|
||||
(unsigned int) (bptr - start) < *count; ++bptr)
|
||||
{
|
||||
if (INUSE(*bptr))
|
||||
continue;
|
||||
|
||||
if (len > 0 && (*bptr)->bnum != bnum)
|
||||
break;
|
||||
|
||||
blist[len++] = *bptr;
|
||||
bnum = (*bptr)->bnum + 1;
|
||||
}
|
||||
|
||||
*count = bptr - start;
|
||||
|
||||
if (len == 0)
|
||||
goto done;
|
||||
else if (len == 1)
|
||||
{
|
||||
if (b_readpb(vol, vol->vstart + blist[0]->bnum,
|
||||
blist[0]->data, 1) == -1)
|
||||
goto fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
block buffer[HFS_BLOCKBUFSZ];
|
||||
|
||||
if (b_readpb(vol, vol->vstart + blist[0]->bnum, buffer, len) == -1)
|
||||
goto fail;
|
||||
|
||||
for (i = 0; i < len; ++i)
|
||||
memcpy(blist[i]->data, buffer[i], HFS_BLOCKSZ);
|
||||
}
|
||||
|
||||
for (i = 0; i < len; ++i)
|
||||
{
|
||||
blist[i]->flags |= HFS_BUCKET_INUSE;
|
||||
blist[i]->flags &= ~HFS_BUCKET_DIRTY;
|
||||
}
|
||||
|
||||
done:
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: flushchain()
|
||||
* DESCRIPTION: store a chain of bucket buffers with a single write
|
||||
*/
|
||||
static
|
||||
int flushchain(hfsvol *vol, bucket **bptr, unsigned int *count)
|
||||
{
|
||||
bucket *blist[HFS_BLOCKBUFSZ], **start = bptr;
|
||||
unsigned long bnum;
|
||||
unsigned int len, i;
|
||||
|
||||
for (len = 0; len < HFS_BLOCKBUFSZ &&
|
||||
(unsigned int) (bptr - start) < *count; ++bptr)
|
||||
{
|
||||
if (! INUSE(*bptr) || ! DIRTY(*bptr))
|
||||
continue;
|
||||
|
||||
if (len > 0 && (*bptr)->bnum != bnum)
|
||||
break;
|
||||
|
||||
blist[len++] = *bptr;
|
||||
bnum = (*bptr)->bnum + 1;
|
||||
}
|
||||
|
||||
*count = bptr - start;
|
||||
|
||||
if (len == 0)
|
||||
goto done;
|
||||
else if (len == 1)
|
||||
{
|
||||
if (b_writepb(vol, vol->vstart + blist[0]->bnum,
|
||||
blist[0]->data, 1) == -1)
|
||||
goto fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
block buffer[HFS_BLOCKBUFSZ];
|
||||
|
||||
for (i = 0; i < len; ++i)
|
||||
memcpy(buffer[i], blist[i]->data, HFS_BLOCKSZ);
|
||||
|
||||
if (b_writepb(vol, vol->vstart + blist[0]->bnum, buffer, len) == -1)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; ++i)
|
||||
blist[i]->flags &= ~HFS_BUCKET_DIRTY;
|
||||
|
||||
done:
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: compare()
|
||||
* DESCRIPTION: comparison function for qsort of cache bucket pointers
|
||||
*/
|
||||
static
|
||||
int compare(const bucket **b1, const bucket **b2)
|
||||
{
|
||||
long diff;
|
||||
|
||||
diff = (*b1)->bnum - (*b2)->bnum;
|
||||
|
||||
if (diff < 0)
|
||||
return -1;
|
||||
else if (diff > 0)
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: dobuckets()
|
||||
* DESCRIPTION: fill or flush an array of cache buckets to a volume
|
||||
*/
|
||||
static
|
||||
int dobuckets(hfsvol *vol, bucket **chain, unsigned int len,
|
||||
int (*func)(hfsvol *, bucket **, unsigned int *))
|
||||
{
|
||||
unsigned int count, i;
|
||||
int result = 0;
|
||||
|
||||
qsort(chain, len, sizeof(*chain),
|
||||
(int (*)(const void *, const void *)) compare);
|
||||
|
||||
for (i = 0; i < len; i += count)
|
||||
{
|
||||
count = len - i;
|
||||
if (func(vol, chain + i, &count) == -1)
|
||||
result = -1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
# define fillbuckets(vol, chain, len) dobuckets(vol, chain, len, fillchain)
|
||||
# define flushbuckets(vol, chain, len) dobuckets(vol, chain, len, flushchain)
|
||||
|
||||
/*
|
||||
* NAME: block->flush()
|
||||
* DESCRIPTION: commit dirty cache blocks to a volume
|
||||
*/
|
||||
int b_flush(hfsvol *vol)
|
||||
{
|
||||
bcache *cache = vol->cache;
|
||||
bucket *chain[HFS_CACHESZ];
|
||||
int i;
|
||||
|
||||
if (cache == 0 || (vol->flags & HFS_VOL_READONLY))
|
||||
goto done;
|
||||
|
||||
for (i = 0; i < HFS_CACHESZ; ++i)
|
||||
chain[i] = &cache->chain[i];
|
||||
|
||||
if (flushbuckets(vol, chain, HFS_CACHESZ) == -1)
|
||||
goto fail;
|
||||
|
||||
done:
|
||||
# ifdef DEBUG
|
||||
if (cache)
|
||||
b_showstats(cache);
|
||||
# endif
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: block->finish()
|
||||
* DESCRIPTION: commit and free a volume's block cache
|
||||
*/
|
||||
int b_finish(hfsvol *vol)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
if (vol->cache == 0)
|
||||
goto done;
|
||||
|
||||
# ifdef DEBUG
|
||||
b_dumpcache(vol->cache);
|
||||
# endif
|
||||
|
||||
result = b_flush(vol);
|
||||
|
||||
FREE(vol->cache);
|
||||
vol->cache = 0;
|
||||
|
||||
done:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: findbucket()
|
||||
* DESCRIPTION: locate a bucket in the cache, and/or its hash slot
|
||||
*/
|
||||
static
|
||||
bucket *findbucket(bcache *cache, unsigned long bnum, bucket ***hslot)
|
||||
{
|
||||
bucket *b;
|
||||
|
||||
*hslot = &cache->hash[bnum & (HFS_HASHSZ - 1)];
|
||||
|
||||
for (b = **hslot; b; b = b->hnext)
|
||||
{
|
||||
if (INUSE(b) && b->bnum == bnum)
|
||||
break;
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: reuse()
|
||||
* DESCRIPTION: free a bucket for reuse, flushing if necessary
|
||||
*/
|
||||
static
|
||||
int reuse(bcache *cache, bucket *b, unsigned long bnum)
|
||||
{
|
||||
bucket *chain[HFS_BLOCKBUFSZ], *bptr;
|
||||
int i;
|
||||
|
||||
# ifdef DEBUG
|
||||
if (INUSE(b))
|
||||
fprintf(stderr, "BLOCK: CACHE reusing bucket containing "
|
||||
"vol 0x%lx block %lu:%u\n",
|
||||
(unsigned long) cache->vol, b->bnum, b->count);
|
||||
# endif
|
||||
|
||||
if (INUSE(b) && DIRTY(b))
|
||||
{
|
||||
/* flush most recently unused buckets */
|
||||
|
||||
for (bptr = b, i = 0; i < HFS_BLOCKBUFSZ; ++i)
|
||||
{
|
||||
chain[i] = bptr;
|
||||
bptr = bptr->cprev;
|
||||
}
|
||||
|
||||
if (flushbuckets(cache->vol, chain, HFS_BLOCKBUFSZ) == -1)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
b->flags &= ~HFS_BUCKET_INUSE;
|
||||
b->count = 1;
|
||||
b->bnum = bnum;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: cplace()
|
||||
* DESCRIPTION: move a bucket to an appropriate place near head of the chain
|
||||
*/
|
||||
static
|
||||
void cplace(bcache *cache, bucket *b)
|
||||
{
|
||||
bucket *p;
|
||||
|
||||
for (p = cache->tail->cnext; p->count > 1; p = p->cnext)
|
||||
--p->count;
|
||||
|
||||
b->cnext->cprev = b->cprev;
|
||||
b->cprev->cnext = b->cnext;
|
||||
|
||||
if (cache->tail == b)
|
||||
cache->tail = b->cprev;
|
||||
|
||||
b->cprev = p->cprev;
|
||||
b->cnext = p;
|
||||
|
||||
p->cprev->cnext = b;
|
||||
p->cprev = b;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: hplace()
|
||||
* DESCRIPTION: move a bucket to the head of its hash slot
|
||||
*/
|
||||
static
|
||||
void hplace(bucket **hslot, bucket *b)
|
||||
{
|
||||
if (*hslot != b)
|
||||
{
|
||||
if (b->hprev)
|
||||
*b->hprev = b->hnext;
|
||||
if (b->hnext)
|
||||
b->hnext->hprev = b->hprev;
|
||||
|
||||
b->hprev = hslot;
|
||||
b->hnext = *hslot;
|
||||
|
||||
if (*hslot)
|
||||
(*hslot)->hprev = &b->hnext;
|
||||
|
||||
*hslot = b;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: getbucket()
|
||||
* DESCRIPTION: fetch a bucket from the cache, or an empty one to be filled
|
||||
*/
|
||||
static
|
||||
bucket *getbucket(bcache *cache, unsigned long bnum, int fill)
|
||||
{
|
||||
bucket **hslot, *b, *p, *bptr,
|
||||
*chain[HFS_BLOCKBUFSZ], **slots[HFS_BLOCKBUFSZ];
|
||||
|
||||
b = findbucket(cache, bnum, &hslot);
|
||||
|
||||
if (b)
|
||||
{
|
||||
/* cache hit; move towards head of cache chain */
|
||||
|
||||
++cache->hits;
|
||||
|
||||
if (++b->count > b->cprev->count &&
|
||||
b != cache->tail->cnext)
|
||||
{
|
||||
p = b->cprev;
|
||||
|
||||
p->cprev->cnext = b;
|
||||
b->cnext->cprev = p;
|
||||
|
||||
p->cnext = b->cnext;
|
||||
b->cprev = p->cprev;
|
||||
|
||||
p->cprev = b;
|
||||
b->cnext = p;
|
||||
|
||||
if (cache->tail == b)
|
||||
cache->tail = p;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* cache miss; reuse least-used cache bucket */
|
||||
|
||||
++cache->misses;
|
||||
|
||||
b = cache->tail;
|
||||
|
||||
if (reuse(cache, b, bnum) == -1)
|
||||
goto fail;
|
||||
|
||||
if (fill)
|
||||
{
|
||||
unsigned int len = 0;
|
||||
|
||||
chain[len] = b;
|
||||
slots[len++] = hslot;
|
||||
|
||||
for (bptr = b->cprev;
|
||||
len < (HFS_BLOCKBUFSZ >> 1) && ++bnum < cache->vol->vlen;
|
||||
bptr = bptr->cprev)
|
||||
{
|
||||
if (findbucket(cache, bnum, &hslot))
|
||||
break;
|
||||
|
||||
if (reuse(cache, bptr, bnum) == -1)
|
||||
goto fail;
|
||||
|
||||
chain[len] = bptr;
|
||||
slots[len++] = hslot;
|
||||
}
|
||||
|
||||
if (fillbuckets(cache->vol, chain, len) == -1)
|
||||
goto fail;
|
||||
|
||||
while (--len)
|
||||
{
|
||||
cplace(cache, chain[len]);
|
||||
hplace(slots[len], chain[len]);
|
||||
}
|
||||
|
||||
hslot = slots[0];
|
||||
}
|
||||
|
||||
/* move bucket to appropriate place in chain */
|
||||
|
||||
cplace(cache, b);
|
||||
}
|
||||
|
||||
/* insert at front of hash chain */
|
||||
|
||||
hplace(hslot, b);
|
||||
|
||||
return b;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: block->readpb()
|
||||
* DESCRIPTION: read blocks from the physical medium (bypassing cache)
|
||||
*/
|
||||
int b_readpb(hfsvol *vol, unsigned long bnum, block *bp, unsigned int blen)
|
||||
{
|
||||
unsigned long nblocks;
|
||||
|
||||
# ifdef DEBUG
|
||||
fprintf(stderr, "BLOCK: READ vol 0x%lx block %lu",
|
||||
(unsigned long) vol, bnum);
|
||||
if (blen > 1)
|
||||
fprintf(stderr, "+%u[..%lu]\n", blen - 1, bnum + blen - 1);
|
||||
else
|
||||
fprintf(stderr, "\n");
|
||||
# endif
|
||||
|
||||
nblocks = os_seek(&vol->priv, bnum);
|
||||
if (nblocks == (unsigned long) -1)
|
||||
goto fail;
|
||||
|
||||
if (nblocks != bnum)
|
||||
ERROR(EIO, "block seek failed for read");
|
||||
|
||||
nblocks = os_read(&vol->priv, bp, blen);
|
||||
if (nblocks == (unsigned long) -1)
|
||||
goto fail;
|
||||
|
||||
if (nblocks != blen)
|
||||
ERROR(EIO, "incomplete block read");
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: block->writepb()
|
||||
* DESCRIPTION: write blocks to the physical medium (bypassing cache)
|
||||
*/
|
||||
int b_writepb(hfsvol *vol, unsigned long bnum, const block *bp,
|
||||
unsigned int blen)
|
||||
{
|
||||
unsigned long nblocks;
|
||||
|
||||
# ifdef DEBUG
|
||||
fprintf(stderr, "BLOCK: WRITE vol 0x%lx block %lu",
|
||||
(unsigned long) vol, bnum);
|
||||
if (blen > 1)
|
||||
fprintf(stderr, "+%u[..%lu]\n", blen - 1, bnum + blen - 1);
|
||||
else
|
||||
fprintf(stderr, "\n");
|
||||
# endif
|
||||
|
||||
nblocks = os_seek(&vol->priv, bnum);
|
||||
if (nblocks == (unsigned long) -1)
|
||||
goto fail;
|
||||
|
||||
if (nblocks != bnum)
|
||||
ERROR(EIO, "block seek failed for write");
|
||||
|
||||
nblocks = os_write(&vol->priv, bp, blen);
|
||||
if (nblocks == (unsigned long) -1)
|
||||
goto fail;
|
||||
|
||||
if (nblocks != blen)
|
||||
ERROR(EIO, "incomplete block write");
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: block->readlb()
|
||||
* DESCRIPTION: read a logical block from a volume (or from the cache)
|
||||
*/
|
||||
int b_readlb(hfsvol *vol, unsigned long bnum, block *bp)
|
||||
{
|
||||
if (vol->vlen > 0 && bnum >= vol->vlen)
|
||||
ERROR(EIO, "read nonexistent logical block");
|
||||
|
||||
if (vol->cache)
|
||||
{
|
||||
bucket *b;
|
||||
|
||||
b = getbucket(vol->cache, bnum, 1);
|
||||
if (b == 0)
|
||||
goto fail;
|
||||
|
||||
memcpy(bp, b->data, HFS_BLOCKSZ);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (b_readpb(vol, vol->vstart + bnum, bp, 1) == -1)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: block->writelb()
|
||||
* DESCRIPTION: write a logical block to a volume (or to the cache)
|
||||
*/
|
||||
int b_writelb(hfsvol *vol, unsigned long bnum, const block *bp)
|
||||
{
|
||||
if (vol->vlen > 0 && bnum >= vol->vlen)
|
||||
ERROR(EIO, "write nonexistent logical block");
|
||||
|
||||
if (vol->cache)
|
||||
{
|
||||
bucket *b;
|
||||
|
||||
b = getbucket(vol->cache, bnum, 0);
|
||||
if (b == 0)
|
||||
goto fail;
|
||||
|
||||
if (! INUSE(b) ||
|
||||
memcmp(b->data, bp, HFS_BLOCKSZ) != 0)
|
||||
{
|
||||
memcpy(b->data, bp, HFS_BLOCKSZ);
|
||||
b->flags |= HFS_BUCKET_INUSE | HFS_BUCKET_DIRTY;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (b_writepb(vol, vol->vstart + bnum, bp, 1) == -1)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: block->readab()
|
||||
* DESCRIPTION: read a block from an allocation block from a volume
|
||||
*/
|
||||
int b_readab(hfsvol *vol, unsigned int anum, unsigned int idx, block *bp)
|
||||
{
|
||||
/* verify the allocation block exists and is marked as in-use */
|
||||
|
||||
if (anum >= vol->mdb.drNmAlBlks)
|
||||
ERROR(EIO, "read nonexistent allocation block");
|
||||
else if (vol->vbm && ! BMTST(vol->vbm, anum))
|
||||
ERROR(EIO, "read unallocated block");
|
||||
|
||||
return b_readlb(vol, vol->mdb.drAlBlSt + anum * vol->lpa + idx, bp);
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: block->writeab()
|
||||
* DESCRIPTION: write a block to an allocation block to a volume
|
||||
*/
|
||||
int b_writeab(hfsvol *vol,
|
||||
unsigned int anum, unsigned int idx, const block *bp)
|
||||
{
|
||||
/* verify the allocation block exists and is marked as in-use */
|
||||
|
||||
if (anum >= vol->mdb.drNmAlBlks)
|
||||
ERROR(EIO, "write nonexistent allocation block");
|
||||
else if (vol->vbm && ! BMTST(vol->vbm, anum))
|
||||
ERROR(EIO, "write unallocated block");
|
||||
|
||||
if (v_dirty(vol) == -1)
|
||||
goto fail;
|
||||
|
||||
return b_writelb(vol, vol->mdb.drAlBlSt + anum * vol->lpa + idx, bp);
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: block->size()
|
||||
* DESCRIPTION: return the number of physical blocks on a volume's medium
|
||||
*/
|
||||
unsigned long b_size(hfsvol *vol)
|
||||
{
|
||||
unsigned long low, high, mid;
|
||||
block b;
|
||||
|
||||
high = os_seek(&vol->priv, -1);
|
||||
|
||||
if (high != (unsigned long) -1 && high > 0)
|
||||
return high;
|
||||
|
||||
/* manual size detection: first check there is at least 1 block in medium */
|
||||
|
||||
if (b_readpb(vol, 0, &b, 1) == -1)
|
||||
ERROR(EIO, "size of medium indeterminable or empty");
|
||||
|
||||
for (low = 0, high = 2880;
|
||||
high > 0 && b_readpb(vol, high - 1, &b, 1) != -1;
|
||||
high <<= 1)
|
||||
low = high - 1;
|
||||
|
||||
if (high == 0)
|
||||
ERROR(EIO, "size of medium indeterminable or too large");
|
||||
|
||||
/* common case: 1440K floppy */
|
||||
|
||||
if (low == 2879 && b_readpb(vol, 2880, &b, 1) == -1)
|
||||
return 2880;
|
||||
|
||||
/* binary search for other sizes */
|
||||
|
||||
while (low < high - 1)
|
||||
{
|
||||
mid = (low + high) >> 1;
|
||||
|
||||
if (b_readpb(vol, mid, &b, 1) == -1)
|
||||
high = mid;
|
||||
else
|
||||
low = mid;
|
||||
}
|
||||
|
||||
return low + 1;
|
||||
|
||||
fail:
|
||||
return 0;
|
||||
}
|
40
libhfs/block.h
Normal file
40
libhfs/block.h
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
int b_init(hfsvol *);
|
||||
int b_flush(hfsvol *);
|
||||
int b_finish(hfsvol *);
|
||||
|
||||
int b_readpb(hfsvol *, unsigned long, block *, unsigned int);
|
||||
int b_writepb(hfsvol *, unsigned long, const block *, unsigned int);
|
||||
|
||||
int b_readlb(hfsvol *, unsigned long, block *);
|
||||
int b_writelb(hfsvol *, unsigned long, const block *);
|
||||
|
||||
int b_readab(hfsvol *, unsigned int, unsigned int, block *);
|
||||
int b_writeab(hfsvol *, unsigned int, unsigned int, const block *);
|
||||
|
||||
unsigned long b_size(hfsvol *);
|
||||
|
||||
# ifdef DEBUG
|
||||
void b_showstats(const bcache *);
|
||||
void b_dumpcache(const bcache *);
|
||||
# endif
|
700
libhfs/btree.c
Normal file
700
libhfs/btree.c
Normal file
@ -0,0 +1,700 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
# ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
# endif
|
||||
|
||||
# include <stdlib.h>
|
||||
# include <string.h>
|
||||
# include <errno.h>
|
||||
|
||||
# include "libhfs.h"
|
||||
# include "btree.h"
|
||||
# include "data.h"
|
||||
# include "file.h"
|
||||
# include "block.h"
|
||||
# include "node.h"
|
||||
|
||||
/*
|
||||
* NAME: btree->getnode()
|
||||
* DESCRIPTION: retrieve a numbered node from a B*-tree file
|
||||
*/
|
||||
int bt_getnode(node *np, btree *bt, unsigned long nnum)
|
||||
{
|
||||
block *bp = &np->data;
|
||||
const byte *ptr;
|
||||
int i;
|
||||
|
||||
np->bt = bt;
|
||||
np->nnum = nnum;
|
||||
|
||||
# if 0
|
||||
fprintf(stderr, "BTREE: GET vol \"%s\" btree \"%s\" node %lu\n",
|
||||
bt->f.vol->mdb.drVN, bt->f.name, np->nnum);
|
||||
# endif
|
||||
|
||||
/* verify the node exists and is marked as in-use */
|
||||
|
||||
if (nnum > 0 && nnum >= bt->hdr.bthNNodes)
|
||||
ERROR(EIO, "read nonexistent b*-tree node");
|
||||
else if (bt->map && ! BMTST(bt->map, nnum))
|
||||
ERROR(EIO, "read unallocated b*-tree node");
|
||||
|
||||
if (f_getblock(&bt->f, nnum, bp) == -1)
|
||||
goto fail;
|
||||
|
||||
ptr = *bp;
|
||||
|
||||
d_fetchul(&ptr, &np->nd.ndFLink);
|
||||
d_fetchul(&ptr, &np->nd.ndBLink);
|
||||
d_fetchsb(&ptr, &np->nd.ndType);
|
||||
d_fetchsb(&ptr, &np->nd.ndNHeight);
|
||||
d_fetchuw(&ptr, &np->nd.ndNRecs);
|
||||
d_fetchsw(&ptr, &np->nd.ndResv2);
|
||||
|
||||
if (np->nd.ndNRecs > HFS_MAX_NRECS)
|
||||
ERROR(EIO, "too many b*-tree node records");
|
||||
|
||||
i = np->nd.ndNRecs + 1;
|
||||
|
||||
ptr = *bp + HFS_BLOCKSZ - (2 * i);
|
||||
|
||||
while (i--)
|
||||
d_fetchuw(&ptr, &np->roff[i]);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: btree->putnode()
|
||||
* DESCRIPTION: store a numbered node into a B*-tree file
|
||||
*/
|
||||
int bt_putnode(node *np)
|
||||
{
|
||||
btree *bt = np->bt;
|
||||
block *bp = &np->data;
|
||||
byte *ptr;
|
||||
int i;
|
||||
|
||||
# if 0
|
||||
fprintf(stderr, "BTREE: PUT vol \"%s\" btree \"%s\" node %lu\n",
|
||||
bt->f.vol->mdb.drVN, bt->f.name, np->nnum);
|
||||
# endif
|
||||
|
||||
/* verify the node exists and is marked as in-use */
|
||||
|
||||
if (np->nnum > 0 && np->nnum >= bt->hdr.bthNNodes)
|
||||
ERROR(EIO, "write nonexistent b*-tree node");
|
||||
else if (bt->map && ! BMTST(bt->map, np->nnum))
|
||||
ERROR(EIO, "write unallocated b*-tree node");
|
||||
|
||||
ptr = *bp;
|
||||
|
||||
d_storeul(&ptr, np->nd.ndFLink);
|
||||
d_storeul(&ptr, np->nd.ndBLink);
|
||||
d_storesb(&ptr, np->nd.ndType);
|
||||
d_storesb(&ptr, np->nd.ndNHeight);
|
||||
d_storeuw(&ptr, np->nd.ndNRecs);
|
||||
d_storesw(&ptr, np->nd.ndResv2);
|
||||
|
||||
if (np->nd.ndNRecs > HFS_MAX_NRECS)
|
||||
ERROR(EIO, "too many b*-tree node records");
|
||||
|
||||
i = np->nd.ndNRecs + 1;
|
||||
|
||||
ptr = *bp + HFS_BLOCKSZ - (2 * i);
|
||||
|
||||
while (i--)
|
||||
d_storeuw(&ptr, np->roff[i]);
|
||||
|
||||
return f_putblock(&bt->f, np->nnum, bp);
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: btree->readhdr()
|
||||
* DESCRIPTION: read the header node of a B*-tree
|
||||
*/
|
||||
int bt_readhdr(btree *bt)
|
||||
{
|
||||
const byte *ptr;
|
||||
byte *map = 0;
|
||||
int i;
|
||||
unsigned long nnum;
|
||||
|
||||
if (bt_getnode(&bt->hdrnd, bt, 0) == -1)
|
||||
goto fail;
|
||||
|
||||
if (bt->hdrnd.nd.ndType != ndHdrNode ||
|
||||
bt->hdrnd.nd.ndNRecs != 3 ||
|
||||
bt->hdrnd.roff[0] != 0x00e ||
|
||||
bt->hdrnd.roff[1] != 0x078 ||
|
||||
bt->hdrnd.roff[2] != 0x0f8 ||
|
||||
bt->hdrnd.roff[3] != 0x1f8)
|
||||
ERROR(EIO, "malformed b*-tree header node");
|
||||
|
||||
/* read header record */
|
||||
|
||||
ptr = HFS_NODEREC(bt->hdrnd, 0);
|
||||
|
||||
d_fetchuw(&ptr, &bt->hdr.bthDepth);
|
||||
d_fetchul(&ptr, &bt->hdr.bthRoot);
|
||||
d_fetchul(&ptr, &bt->hdr.bthNRecs);
|
||||
d_fetchul(&ptr, &bt->hdr.bthFNode);
|
||||
d_fetchul(&ptr, &bt->hdr.bthLNode);
|
||||
d_fetchuw(&ptr, &bt->hdr.bthNodeSize);
|
||||
d_fetchuw(&ptr, &bt->hdr.bthKeyLen);
|
||||
d_fetchul(&ptr, &bt->hdr.bthNNodes);
|
||||
d_fetchul(&ptr, &bt->hdr.bthFree);
|
||||
|
||||
for (i = 0; i < 76; ++i)
|
||||
d_fetchsb(&ptr, &bt->hdr.bthResv[i]);
|
||||
|
||||
if (bt->hdr.bthNodeSize != HFS_BLOCKSZ)
|
||||
ERROR(EINVAL, "unsupported b*-tree node size");
|
||||
|
||||
/* read map record; construct btree bitmap */
|
||||
/* don't set bt->map until we're done, since getnode() checks it */
|
||||
|
||||
map = ALLOC(byte, HFS_MAP1SZ);
|
||||
if (map == 0)
|
||||
ERROR(ENOMEM, 0);
|
||||
|
||||
memcpy(map, HFS_NODEREC(bt->hdrnd, 2), HFS_MAP1SZ);
|
||||
bt->mapsz = HFS_MAP1SZ;
|
||||
|
||||
/* read continuation map records, if any */
|
||||
|
||||
nnum = bt->hdrnd.nd.ndFLink;
|
||||
|
||||
while (nnum)
|
||||
{
|
||||
node n;
|
||||
byte *newmap;
|
||||
|
||||
if (bt_getnode(&n, bt, nnum) == -1)
|
||||
goto fail;
|
||||
|
||||
if (n.nd.ndType != ndMapNode ||
|
||||
n.nd.ndNRecs != 1 ||
|
||||
n.roff[0] != 0x00e ||
|
||||
n.roff[1] != 0x1fa)
|
||||
ERROR(EIO, "malformed b*-tree map node");
|
||||
|
||||
newmap = REALLOC(map, byte, bt->mapsz + HFS_MAPXSZ);
|
||||
if (newmap == 0)
|
||||
ERROR(ENOMEM, 0);
|
||||
|
||||
map = newmap;
|
||||
|
||||
memcpy(map + bt->mapsz, HFS_NODEREC(n, 0), HFS_MAPXSZ);
|
||||
bt->mapsz += HFS_MAPXSZ;
|
||||
|
||||
nnum = n.nd.ndFLink;
|
||||
}
|
||||
|
||||
bt->map = map;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
FREE(map);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: btree->writehdr()
|
||||
* DESCRIPTION: write the header node of a B*-tree
|
||||
*/
|
||||
int bt_writehdr(btree *bt)
|
||||
{
|
||||
byte *ptr, *map;
|
||||
unsigned long mapsz, nnum;
|
||||
int i;
|
||||
|
||||
ASSERT(bt->hdrnd.bt == bt &&
|
||||
bt->hdrnd.nnum == 0 &&
|
||||
bt->hdrnd.nd.ndType == ndHdrNode &&
|
||||
bt->hdrnd.nd.ndNRecs == 3);
|
||||
|
||||
ptr = HFS_NODEREC(bt->hdrnd, 0);
|
||||
|
||||
d_storeuw(&ptr, bt->hdr.bthDepth);
|
||||
d_storeul(&ptr, bt->hdr.bthRoot);
|
||||
d_storeul(&ptr, bt->hdr.bthNRecs);
|
||||
d_storeul(&ptr, bt->hdr.bthFNode);
|
||||
d_storeul(&ptr, bt->hdr.bthLNode);
|
||||
d_storeuw(&ptr, bt->hdr.bthNodeSize);
|
||||
d_storeuw(&ptr, bt->hdr.bthKeyLen);
|
||||
d_storeul(&ptr, bt->hdr.bthNNodes);
|
||||
d_storeul(&ptr, bt->hdr.bthFree);
|
||||
|
||||
for (i = 0; i < 76; ++i)
|
||||
d_storesb(&ptr, bt->hdr.bthResv[i]);
|
||||
|
||||
memcpy(HFS_NODEREC(bt->hdrnd, 2), bt->map, HFS_MAP1SZ);
|
||||
|
||||
if (bt_putnode(&bt->hdrnd) == -1)
|
||||
goto fail;
|
||||
|
||||
map = bt->map + HFS_MAP1SZ;
|
||||
mapsz = bt->mapsz - HFS_MAP1SZ;
|
||||
|
||||
nnum = bt->hdrnd.nd.ndFLink;
|
||||
|
||||
while (mapsz)
|
||||
{
|
||||
node n;
|
||||
|
||||
if (nnum == 0)
|
||||
ERROR(EIO, "truncated b*-tree map");
|
||||
|
||||
if (bt_getnode(&n, bt, nnum) == -1)
|
||||
goto fail;
|
||||
|
||||
if (n.nd.ndType != ndMapNode ||
|
||||
n.nd.ndNRecs != 1 ||
|
||||
n.roff[0] != 0x00e ||
|
||||
n.roff[1] != 0x1fa)
|
||||
ERROR(EIO, "malformed b*-tree map node");
|
||||
|
||||
memcpy(HFS_NODEREC(n, 0), map, HFS_MAPXSZ);
|
||||
|
||||
if (bt_putnode(&n) == -1)
|
||||
goto fail;
|
||||
|
||||
map += HFS_MAPXSZ;
|
||||
mapsz -= HFS_MAPXSZ;
|
||||
|
||||
nnum = n.nd.ndFLink;
|
||||
}
|
||||
|
||||
bt->flags &= ~HFS_BT_UPDATE_HDR;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* High-Level B*-Tree Routines ============================================= */
|
||||
|
||||
/*
|
||||
* NAME: btree->space()
|
||||
* DESCRIPTION: assert space for new records, or extend the file
|
||||
*/
|
||||
int bt_space(btree *bt, unsigned int nrecs)
|
||||
{
|
||||
unsigned int nnodes;
|
||||
long space;
|
||||
|
||||
nnodes = nrecs * (bt->hdr.bthDepth + 1);
|
||||
|
||||
if (nnodes <= bt->hdr.bthFree)
|
||||
goto done;
|
||||
|
||||
/* make sure the extents tree has room too */
|
||||
|
||||
if (bt != &bt->f.vol->ext)
|
||||
{
|
||||
if (bt_space(&bt->f.vol->ext, 1) == -1)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
space = f_alloc(&bt->f);
|
||||
if (space == -1)
|
||||
goto fail;
|
||||
|
||||
nnodes = space * (bt->f.vol->mdb.drAlBlkSiz / bt->hdr.bthNodeSize);
|
||||
|
||||
bt->hdr.bthNNodes += nnodes;
|
||||
bt->hdr.bthFree += nnodes;
|
||||
|
||||
bt->flags |= HFS_BT_UPDATE_HDR;
|
||||
|
||||
bt->f.vol->flags |= HFS_VOL_UPDATE_ALTMDB;
|
||||
|
||||
while (bt->hdr.bthNNodes > bt->mapsz * 8)
|
||||
{
|
||||
byte *newmap;
|
||||
node mapnd;
|
||||
|
||||
/* extend tree map */
|
||||
|
||||
newmap = REALLOC(bt->map, byte, bt->mapsz + HFS_MAPXSZ);
|
||||
if (newmap == 0)
|
||||
ERROR(ENOMEM, 0);
|
||||
|
||||
memset(newmap + bt->mapsz, 0, HFS_MAPXSZ);
|
||||
|
||||
bt->map = newmap;
|
||||
bt->mapsz += HFS_MAPXSZ;
|
||||
|
||||
n_init(&mapnd, bt, ndMapNode, 0);
|
||||
if (n_new(&mapnd) == -1)
|
||||
goto fail;
|
||||
|
||||
mapnd.nd.ndNRecs = 1;
|
||||
mapnd.roff[1] = 0x1fa;
|
||||
|
||||
/* link the new map node */
|
||||
|
||||
if (bt->hdrnd.nd.ndFLink == 0)
|
||||
{
|
||||
bt->hdrnd.nd.ndFLink = mapnd.nnum;
|
||||
mapnd.nd.ndBLink = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
node n;
|
||||
unsigned long nnum;
|
||||
|
||||
nnum = bt->hdrnd.nd.ndFLink;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (bt_getnode(&n, bt, nnum) == -1)
|
||||
goto fail;
|
||||
|
||||
if (n.nd.ndFLink == 0)
|
||||
break;
|
||||
|
||||
nnum = n.nd.ndFLink;
|
||||
}
|
||||
|
||||
n.nd.ndFLink = mapnd.nnum;
|
||||
mapnd.nd.ndBLink = n.nnum;
|
||||
|
||||
if (bt_putnode(&n) == -1)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (bt_putnode(&mapnd) == -1)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
done:
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: insertx()
|
||||
* DESCRIPTION: recursively locate a node and insert a record
|
||||
*/
|
||||
static
|
||||
int insertx(node *np, byte *record, int *reclen)
|
||||
{
|
||||
node child;
|
||||
byte *rec;
|
||||
int result = 0;
|
||||
|
||||
if (n_search(np, record))
|
||||
ERROR(EIO, "b*-tree record already exists");
|
||||
|
||||
switch (np->nd.ndType)
|
||||
{
|
||||
case ndIndxNode:
|
||||
if (np->rnum == -1)
|
||||
rec = HFS_NODEREC(*np, 0);
|
||||
else
|
||||
rec = HFS_NODEREC(*np, np->rnum);
|
||||
|
||||
if (bt_getnode(&child, np->bt, d_getul(HFS_RECDATA(rec))) == -1 ||
|
||||
insertx(&child, record, reclen) == -1)
|
||||
goto fail;
|
||||
|
||||
if (np->rnum == -1)
|
||||
{
|
||||
n_index(&child, rec, 0);
|
||||
if (*reclen == 0)
|
||||
{
|
||||
result = bt_putnode(np);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (*reclen)
|
||||
result = n_insert(np, record, reclen);
|
||||
|
||||
break;
|
||||
|
||||
case ndLeafNode:
|
||||
result = n_insert(np, record, reclen);
|
||||
break;
|
||||
|
||||
default:
|
||||
ERROR(EIO, "unexpected b*-tree node");
|
||||
}
|
||||
|
||||
done:
|
||||
return result;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: btree->insert()
|
||||
* DESCRIPTION: insert a new node record into a tree
|
||||
*/
|
||||
int bt_insert(btree *bt, const byte *record, unsigned int reclen)
|
||||
{
|
||||
node root;
|
||||
byte newrec[HFS_MAX_RECLEN];
|
||||
|
||||
if (bt->hdr.bthRoot == 0)
|
||||
{
|
||||
/* create root node */
|
||||
|
||||
n_init(&root, bt, ndLeafNode, 1);
|
||||
if (n_new(&root) == -1 ||
|
||||
bt_putnode(&root) == -1)
|
||||
goto fail;
|
||||
|
||||
bt->hdr.bthDepth = 1;
|
||||
bt->hdr.bthRoot = root.nnum;
|
||||
bt->hdr.bthFNode = root.nnum;
|
||||
bt->hdr.bthLNode = root.nnum;
|
||||
|
||||
bt->flags |= HFS_BT_UPDATE_HDR;
|
||||
}
|
||||
else if (bt_getnode(&root, bt, bt->hdr.bthRoot) == -1)
|
||||
goto fail;
|
||||
|
||||
memcpy(newrec, record, reclen);
|
||||
|
||||
if (insertx(&root, newrec, &reclen) == -1)
|
||||
goto fail;
|
||||
|
||||
if (reclen)
|
||||
{
|
||||
byte oroot[HFS_MAX_RECLEN];
|
||||
unsigned int orootlen;
|
||||
|
||||
/* root node was split; create a new root */
|
||||
|
||||
n_index(&root, oroot, &orootlen);
|
||||
|
||||
n_init(&root, bt, ndIndxNode, root.nd.ndNHeight + 1);
|
||||
if (n_new(&root) == -1)
|
||||
goto fail;
|
||||
|
||||
++bt->hdr.bthDepth;
|
||||
bt->hdr.bthRoot = root.nnum;
|
||||
|
||||
bt->flags |= HFS_BT_UPDATE_HDR;
|
||||
|
||||
/* insert index records for new root */
|
||||
|
||||
n_search(&root, oroot);
|
||||
n_insertx(&root, oroot, orootlen);
|
||||
|
||||
n_search(&root, newrec);
|
||||
n_insertx(&root, newrec, reclen);
|
||||
|
||||
if (bt_putnode(&root) == -1)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
++bt->hdr.bthNRecs;
|
||||
bt->flags |= HFS_BT_UPDATE_HDR;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: deletex()
|
||||
* DESCRIPTION: recursively locate a node and delete a record
|
||||
*/
|
||||
static
|
||||
int deletex(node *np, const byte *key, byte *record, int *flag)
|
||||
{
|
||||
node child;
|
||||
byte *rec;
|
||||
int found, result = 0;
|
||||
|
||||
found = n_search(np, key);
|
||||
|
||||
switch (np->nd.ndType)
|
||||
{
|
||||
case ndIndxNode:
|
||||
if (np->rnum == -1)
|
||||
ERROR(EIO, "b*-tree record not found");
|
||||
|
||||
rec = HFS_NODEREC(*np, np->rnum);
|
||||
|
||||
if (bt_getnode(&child, np->bt, d_getul(HFS_RECDATA(rec))) == -1 ||
|
||||
deletex(&child, key, rec, flag) == -1)
|
||||
goto fail;
|
||||
|
||||
if (*flag)
|
||||
{
|
||||
*flag = 0;
|
||||
|
||||
if (HFS_RECKEYLEN(rec) == 0)
|
||||
{
|
||||
result = n_delete(np, record, flag);
|
||||
break;
|
||||
}
|
||||
|
||||
if (np->rnum == 0)
|
||||
{
|
||||
/* propagate index record change into parent */
|
||||
|
||||
n_index(np, record, 0);
|
||||
*flag = 1;
|
||||
}
|
||||
|
||||
result = bt_putnode(np);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ndLeafNode:
|
||||
if (found == 0)
|
||||
ERROR(EIO, "b*-tree record not found");
|
||||
|
||||
result = n_delete(np, record, flag);
|
||||
break;
|
||||
|
||||
default:
|
||||
ERROR(EIO, "unexpected b*-tree node");
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: btree->delete()
|
||||
* DESCRIPTION: remove a node record from a tree
|
||||
*/
|
||||
int bt_delete(btree *bt, const byte *key)
|
||||
{
|
||||
node root;
|
||||
byte record[HFS_MAX_RECLEN];
|
||||
int flag = 0;
|
||||
|
||||
if (bt->hdr.bthRoot == 0)
|
||||
ERROR(EIO, "empty b*-tree");
|
||||
|
||||
if (bt_getnode(&root, bt, bt->hdr.bthRoot) == -1 ||
|
||||
deletex(&root, key, record, &flag) == -1)
|
||||
goto fail;
|
||||
|
||||
if (bt->hdr.bthDepth > 1 && root.nd.ndNRecs == 1)
|
||||
{
|
||||
const byte *rec;
|
||||
|
||||
/* root only has one record; eliminate it and decrease the tree depth */
|
||||
|
||||
rec = HFS_NODEREC(root, 0);
|
||||
|
||||
--bt->hdr.bthDepth;
|
||||
bt->hdr.bthRoot = d_getul(HFS_RECDATA(rec));
|
||||
|
||||
if (n_free(&root) == -1)
|
||||
goto fail;
|
||||
}
|
||||
else if (bt->hdr.bthDepth == 1 && root.nd.ndNRecs == 0)
|
||||
{
|
||||
/* root node was deleted */
|
||||
|
||||
bt->hdr.bthDepth = 0;
|
||||
bt->hdr.bthRoot = 0;
|
||||
}
|
||||
|
||||
--bt->hdr.bthNRecs;
|
||||
bt->flags |= HFS_BT_UPDATE_HDR;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: btree->search()
|
||||
* DESCRIPTION: locate a data record given a search key
|
||||
*/
|
||||
int bt_search(btree *bt, const byte *key, node *np)
|
||||
{
|
||||
int found = 0;
|
||||
unsigned long nnum;
|
||||
|
||||
nnum = bt->hdr.bthRoot;
|
||||
|
||||
if (nnum == 0)
|
||||
ERROR(ENOENT, 0);
|
||||
|
||||
while (1)
|
||||
{
|
||||
const byte *rec;
|
||||
|
||||
if (bt_getnode(np, bt, nnum) == -1)
|
||||
{
|
||||
found = -1;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
found = n_search(np, key);
|
||||
|
||||
switch (np->nd.ndType)
|
||||
{
|
||||
case ndIndxNode:
|
||||
if (np->rnum == -1)
|
||||
ERROR(ENOENT, 0);
|
||||
|
||||
rec = HFS_NODEREC(*np, np->rnum);
|
||||
nnum = d_getul(HFS_RECDATA(rec));
|
||||
|
||||
break;
|
||||
|
||||
case ndLeafNode:
|
||||
if (! found)
|
||||
ERROR(ENOENT, 0);
|
||||
|
||||
goto done;
|
||||
|
||||
default:
|
||||
found = -1;
|
||||
ERROR(EIO, "unexpected b*-tree node");
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
fail:
|
||||
return found;
|
||||
}
|
33
libhfs/btree.h
Normal file
33
libhfs/btree.h
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
int bt_getnode(node *, btree *, unsigned long);
|
||||
int bt_putnode(node *);
|
||||
|
||||
int bt_readhdr(btree *);
|
||||
int bt_writehdr(btree *);
|
||||
|
||||
int bt_space(btree *, unsigned int);
|
||||
|
||||
int bt_insert(btree *, const byte *, unsigned int);
|
||||
int bt_delete(btree *, const byte *);
|
||||
|
||||
int bt_search(btree *, const byte *, node *);
|
60
libhfs/config.h
Normal file
60
libhfs/config.h
Normal file
@ -0,0 +1,60 @@
|
||||
/* config.h. Generated automatically by configure. */
|
||||
/* config.h.in. Generated automatically from configure.in by autoheader. */
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
/*****************************************************************************
|
||||
* Definitions selected automatically by `configure' *
|
||||
*****************************************************************************/
|
||||
|
||||
/* Define to empty if the keyword does not work. */
|
||||
/* #undef const */
|
||||
|
||||
/* Define to `unsigned' if <sys/types.h> doesn't define. */
|
||||
/* #undef size_t */
|
||||
|
||||
/* Define if you have the ANSI C header files. */
|
||||
#define STDC_HEADERS 1
|
||||
|
||||
/* Define if your <sys/time.h> declares struct tm. */
|
||||
/* #undef TM_IN_SYS_TIME */
|
||||
|
||||
/* Define if you want to enable diagnostic debugging support. */
|
||||
/* #undef DEBUG */
|
||||
|
||||
/* Define if you have the mktime function. */
|
||||
#define HAVE_MKTIME 1
|
||||
|
||||
/* Define if you have the <fcntl.h> header file. */
|
||||
#define HAVE_FCNTL_H 1
|
||||
|
||||
/* Define if you have the <unistd.h> header file. */
|
||||
#ifndef _WIN32
|
||||
# define HAVE_UNISTD_H 1
|
||||
#endif
|
||||
|
||||
/*****************************************************************************
|
||||
* End of automatically configured definitions *
|
||||
*****************************************************************************/
|
||||
|
||||
# ifdef DEBUG
|
||||
# include <stdio.h>
|
||||
# endif
|
485
libhfs/data.c
Normal file
485
libhfs/data.c
Normal file
@ -0,0 +1,485 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
# ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
# endif
|
||||
|
||||
# include <string.h>
|
||||
# include <time.h>
|
||||
|
||||
# ifdef TM_IN_SYS_TIME
|
||||
# include <sys/time.h>
|
||||
# endif
|
||||
|
||||
# include "data.h"
|
||||
|
||||
# define TIMEDIFF 2082844800UL
|
||||
|
||||
static
|
||||
time_t tzdiff = -1;
|
||||
|
||||
const
|
||||
unsigned char hfs_charorder[256] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
|
||||
0x20, 0x22, 0x23, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
|
||||
0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
|
||||
0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
|
||||
0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
|
||||
0x47, 0x48, 0x58, 0x5a, 0x5e, 0x60, 0x67, 0x69,
|
||||
0x6b, 0x6d, 0x73, 0x75, 0x77, 0x79, 0x7b, 0x7f,
|
||||
0x8d, 0x8f, 0x91, 0x93, 0x96, 0x98, 0x9f, 0xa1,
|
||||
0xa3, 0xa5, 0xa8, 0xaa, 0xab, 0xac, 0xad, 0xae,
|
||||
|
||||
0x54, 0x48, 0x58, 0x5a, 0x5e, 0x60, 0x67, 0x69,
|
||||
0x6b, 0x6d, 0x73, 0x75, 0x77, 0x79, 0x7b, 0x7f,
|
||||
0x8d, 0x8f, 0x91, 0x93, 0x96, 0x98, 0x9f, 0xa1,
|
||||
0xa3, 0xa5, 0xa8, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3,
|
||||
|
||||
0x4c, 0x50, 0x5c, 0x62, 0x7d, 0x81, 0x9a, 0x55,
|
||||
0x4a, 0x56, 0x4c, 0x4e, 0x50, 0x5c, 0x62, 0x64,
|
||||
0x65, 0x66, 0x6f, 0x70, 0x71, 0x72, 0x7d, 0x89,
|
||||
0x8a, 0x8b, 0x81, 0x83, 0x9c, 0x9d, 0x9e, 0x9a,
|
||||
|
||||
0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0x95,
|
||||
0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0x52, 0x85,
|
||||
0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8,
|
||||
0xc9, 0xca, 0xcb, 0x57, 0x8c, 0xcc, 0x52, 0x85,
|
||||
|
||||
0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0x26,
|
||||
0x27, 0xd4, 0x20, 0x4a, 0x4e, 0x83, 0x87, 0x87,
|
||||
0xd5, 0xd6, 0x24, 0x25, 0x2d, 0x2e, 0xd7, 0xd8,
|
||||
0xa7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
|
||||
|
||||
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
|
||||
0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
|
||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
|
||||
0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
|
||||
};
|
||||
|
||||
/*
|
||||
* NAME: data->getsb()
|
||||
* DESCRIPTION: marshal 1 signed byte into local host format
|
||||
*/
|
||||
signed char d_getsb(register const unsigned char *ptr)
|
||||
{
|
||||
return ptr[0];
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->getub()
|
||||
* DESCRIPTION: marshal 1 unsigned byte into local host format
|
||||
*/
|
||||
unsigned char d_getub(register const unsigned char *ptr)
|
||||
{
|
||||
return ptr[0];
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->getsw()
|
||||
* DESCRIPTION: marshal 2 signed bytes into local host format
|
||||
*/
|
||||
signed short d_getsw(register const unsigned char *ptr)
|
||||
{
|
||||
return
|
||||
((( signed short) ptr[0] << 8) |
|
||||
((unsigned short) ptr[1] << 0));
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->getuw()
|
||||
* DESCRIPTION: marshal 2 unsigned bytes into local host format
|
||||
*/
|
||||
unsigned short d_getuw(register const unsigned char *ptr)
|
||||
{
|
||||
return
|
||||
(((unsigned short) ptr[0] << 8) |
|
||||
((unsigned short) ptr[1] << 0));
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->getsl()
|
||||
* DESCRIPTION: marshal 4 signed bytes into local host format
|
||||
*/
|
||||
signed long d_getsl(register const unsigned char *ptr)
|
||||
{
|
||||
return
|
||||
((( signed long) ptr[0] << 24) |
|
||||
((unsigned long) ptr[1] << 16) |
|
||||
((unsigned long) ptr[2] << 8) |
|
||||
((unsigned long) ptr[3] << 0));
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->getul()
|
||||
* DESCRIPTION: marshal 4 unsigned bytes into local host format
|
||||
*/
|
||||
unsigned long d_getul(register const unsigned char *ptr)
|
||||
{
|
||||
return
|
||||
(((unsigned long) ptr[0] << 24) |
|
||||
((unsigned long) ptr[1] << 16) |
|
||||
((unsigned long) ptr[2] << 8) |
|
||||
((unsigned long) ptr[3] << 0));
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->putsb()
|
||||
* DESCRIPTION: marshal 1 signed byte out in big-endian format
|
||||
*/
|
||||
void d_putsb(register unsigned char *ptr,
|
||||
register signed char data)
|
||||
{
|
||||
*ptr = data;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->putub()
|
||||
* DESCRIPTION: marshal 1 unsigned byte out in big-endian format
|
||||
*/
|
||||
void d_putub(register unsigned char *ptr,
|
||||
register unsigned char data)
|
||||
{
|
||||
*ptr = data;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->putsw()
|
||||
* DESCRIPTION: marshal 2 signed bytes out in big-endian format
|
||||
*/
|
||||
void d_putsw(register unsigned char *ptr,
|
||||
register signed short data)
|
||||
{
|
||||
*ptr++ = ((unsigned short) data & 0xff00) >> 8;
|
||||
*ptr = ((unsigned short) data & 0x00ff) >> 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->putuw()
|
||||
* DESCRIPTION: marshal 2 unsigned bytes out in big-endian format
|
||||
*/
|
||||
void d_putuw(register unsigned char *ptr,
|
||||
register unsigned short data)
|
||||
{
|
||||
*ptr++ = (data & 0xff00) >> 8;
|
||||
*ptr = (data & 0x00ff) >> 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->putsl()
|
||||
* DESCRIPTION: marshal 4 signed bytes out in big-endian format
|
||||
*/
|
||||
void d_putsl(register unsigned char *ptr,
|
||||
register signed long data)
|
||||
{
|
||||
*ptr++ = ((unsigned long) data & 0xff000000UL) >> 24;
|
||||
*ptr++ = ((unsigned long) data & 0x00ff0000UL) >> 16;
|
||||
*ptr++ = ((unsigned long) data & 0x0000ff00UL) >> 8;
|
||||
*ptr = ((unsigned long) data & 0x000000ffUL) >> 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->putul()
|
||||
* DESCRIPTION: marshal 4 unsigned bytes out in big-endian format
|
||||
*/
|
||||
void d_putul(register unsigned char *ptr,
|
||||
register unsigned long data)
|
||||
{
|
||||
*ptr++ = (data & 0xff000000UL) >> 24;
|
||||
*ptr++ = (data & 0x00ff0000UL) >> 16;
|
||||
*ptr++ = (data & 0x0000ff00UL) >> 8;
|
||||
*ptr = (data & 0x000000ffUL) >> 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->fetchsb()
|
||||
* DESCRIPTION: incrementally retrieve a signed byte of data
|
||||
*/
|
||||
void d_fetchsb(register const unsigned char **ptr,
|
||||
register signed char *dest)
|
||||
{
|
||||
*dest = *(*ptr)++;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->fetchub()
|
||||
* DESCRIPTION: incrementally retrieve an unsigned byte of data
|
||||
*/
|
||||
void d_fetchub(register const unsigned char **ptr,
|
||||
register unsigned char *dest)
|
||||
{
|
||||
*dest = *(*ptr)++;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->fetchsw()
|
||||
* DESCRIPTION: incrementally retrieve a signed word of data
|
||||
*/
|
||||
void d_fetchsw(register const unsigned char **ptr,
|
||||
register signed short *dest)
|
||||
{
|
||||
*dest =
|
||||
((( signed short) (*ptr)[0] << 8) |
|
||||
((unsigned short) (*ptr)[1] << 0));
|
||||
*ptr += 2;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->fetchuw()
|
||||
* DESCRIPTION: incrementally retrieve an unsigned word of data
|
||||
*/
|
||||
void d_fetchuw(register const unsigned char **ptr,
|
||||
register unsigned short *dest)
|
||||
{
|
||||
*dest =
|
||||
(((unsigned short) (*ptr)[0] << 8) |
|
||||
((unsigned short) (*ptr)[1] << 0));
|
||||
*ptr += 2;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->fetchsl()
|
||||
* DESCRIPTION: incrementally retrieve a signed long word of data
|
||||
*/
|
||||
void d_fetchsl(register const unsigned char **ptr,
|
||||
register signed long *dest)
|
||||
{
|
||||
*dest =
|
||||
((( signed long) (*ptr)[0] << 24) |
|
||||
((unsigned long) (*ptr)[1] << 16) |
|
||||
((unsigned long) (*ptr)[2] << 8) |
|
||||
((unsigned long) (*ptr)[3] << 0));
|
||||
*ptr += 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->fetchul()
|
||||
* DESCRIPTION: incrementally retrieve an unsigned long word of data
|
||||
*/
|
||||
void d_fetchul(register const unsigned char **ptr,
|
||||
register unsigned long *dest)
|
||||
{
|
||||
*dest =
|
||||
(((unsigned long) (*ptr)[0] << 24) |
|
||||
((unsigned long) (*ptr)[1] << 16) |
|
||||
((unsigned long) (*ptr)[2] << 8) |
|
||||
((unsigned long) (*ptr)[3] << 0));
|
||||
*ptr += 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->storesb()
|
||||
* DESCRIPTION: incrementally store a signed byte of data
|
||||
*/
|
||||
void d_storesb(register unsigned char **ptr,
|
||||
register signed char data)
|
||||
{
|
||||
*(*ptr)++ = data;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->storeub()
|
||||
* DESCRIPTION: incrementally store an unsigned byte of data
|
||||
*/
|
||||
void d_storeub(register unsigned char **ptr,
|
||||
register unsigned char data)
|
||||
{
|
||||
*(*ptr)++ = data;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->storesw()
|
||||
* DESCRIPTION: incrementally store a signed word of data
|
||||
*/
|
||||
void d_storesw(register unsigned char **ptr,
|
||||
register signed short data)
|
||||
{
|
||||
*(*ptr)++ = ((unsigned short) data & 0xff00) >> 8;
|
||||
*(*ptr)++ = ((unsigned short) data & 0x00ff) >> 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->storeuw()
|
||||
* DESCRIPTION: incrementally store an unsigned word of data
|
||||
*/
|
||||
void d_storeuw(register unsigned char **ptr,
|
||||
register unsigned short data)
|
||||
{
|
||||
*(*ptr)++ = (data & 0xff00) >> 8;
|
||||
*(*ptr)++ = (data & 0x00ff) >> 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->storesl()
|
||||
* DESCRIPTION: incrementally store a signed long word of data
|
||||
*/
|
||||
void d_storesl(register unsigned char **ptr,
|
||||
register signed long data)
|
||||
{
|
||||
*(*ptr)++ = ((unsigned long) data & 0xff000000UL) >> 24;
|
||||
*(*ptr)++ = ((unsigned long) data & 0x00ff0000UL) >> 16;
|
||||
*(*ptr)++ = ((unsigned long) data & 0x0000ff00UL) >> 8;
|
||||
*(*ptr)++ = ((unsigned long) data & 0x000000ffUL) >> 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->storeul()
|
||||
* DESCRIPTION: incrementally store an unsigned long word of data
|
||||
*/
|
||||
void d_storeul(register unsigned char **ptr,
|
||||
register unsigned long data)
|
||||
{
|
||||
*(*ptr)++ = (data & 0xff000000UL) >> 24;
|
||||
*(*ptr)++ = (data & 0x00ff0000UL) >> 16;
|
||||
*(*ptr)++ = (data & 0x0000ff00UL) >> 8;
|
||||
*(*ptr)++ = (data & 0x000000ffUL) >> 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->fetchstr()
|
||||
* DESCRIPTION: incrementally retrieve a string
|
||||
*/
|
||||
void d_fetchstr(const unsigned char **ptr, char *dest, unsigned size)
|
||||
{
|
||||
unsigned len;
|
||||
|
||||
len = d_getub(*ptr);
|
||||
|
||||
if (len > 0 && len < size)
|
||||
memcpy(dest, *ptr + 1, len);
|
||||
else
|
||||
len = 0;
|
||||
|
||||
dest[len] = 0;
|
||||
|
||||
*ptr += size;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->storestr()
|
||||
* DESCRIPTION: incrementally store a string
|
||||
*/
|
||||
void d_storestr(unsigned char **ptr, const char *src, unsigned size)
|
||||
{
|
||||
unsigned len;
|
||||
|
||||
len = strlen(src);
|
||||
if (len > --size)
|
||||
len = 0;
|
||||
|
||||
d_storeub(ptr, (unsigned char) len);
|
||||
|
||||
memcpy(*ptr, src, len);
|
||||
memset(*ptr + len, 0, size - len);
|
||||
|
||||
*ptr += size;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->relstring()
|
||||
* DESCRIPTION: compare two strings as per MacOS for HFS
|
||||
*/
|
||||
int d_relstring(const char *str1, const char *str2)
|
||||
{
|
||||
register int diff;
|
||||
|
||||
while (*str1 && *str2)
|
||||
{
|
||||
diff = hfs_charorder[(unsigned char) *str1] -
|
||||
hfs_charorder[(unsigned char) *str2];
|
||||
|
||||
if (diff)
|
||||
return diff;
|
||||
|
||||
++str1, ++str2;
|
||||
}
|
||||
|
||||
if (! *str1 && *str2)
|
||||
return -1;
|
||||
else if (*str1 && ! *str2)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: calctzdiff()
|
||||
* DESCRIPTION: calculate the timezone difference between local time and UTC
|
||||
*/
|
||||
static
|
||||
void calctzdiff(void)
|
||||
{
|
||||
# ifdef HAVE_MKTIME
|
||||
|
||||
time_t t;
|
||||
int isdst;
|
||||
struct tm tm;
|
||||
const struct tm *tmp;
|
||||
|
||||
time(&t);
|
||||
isdst = localtime(&t)->tm_isdst;
|
||||
|
||||
tmp = gmtime(&t);
|
||||
if (tmp)
|
||||
{
|
||||
tm = *tmp;
|
||||
tm.tm_isdst = isdst;
|
||||
|
||||
tzdiff = t - mktime(&tm);
|
||||
}
|
||||
else
|
||||
tzdiff = 0;
|
||||
|
||||
# else
|
||||
|
||||
tzdiff = 0;
|
||||
|
||||
# endif
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->ltime()
|
||||
* DESCRIPTION: convert MacOS time to local time
|
||||
*/
|
||||
time_t d_ltime(unsigned long mtime)
|
||||
{
|
||||
if (tzdiff == -1)
|
||||
calctzdiff();
|
||||
|
||||
return (time_t) (mtime - TIMEDIFF) - tzdiff;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: data->mtime()
|
||||
* DESCRIPTION: convert local time to MacOS time
|
||||
*/
|
||||
unsigned long d_mtime(time_t ltime)
|
||||
{
|
||||
if (tzdiff == -1)
|
||||
calctzdiff();
|
||||
|
||||
return (unsigned long) (ltime + tzdiff) + TIMEDIFF;
|
||||
}
|
58
libhfs/data.h
Normal file
58
libhfs/data.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
extern const unsigned char hfs_charorder[];
|
||||
|
||||
signed char d_getsb(register const unsigned char *);
|
||||
unsigned char d_getub(register const unsigned char *);
|
||||
signed short d_getsw(register const unsigned char *);
|
||||
unsigned short d_getuw(register const unsigned char *);
|
||||
signed long d_getsl(register const unsigned char *);
|
||||
unsigned long d_getul(register const unsigned char *);
|
||||
|
||||
void d_putsb(register unsigned char *, register signed char);
|
||||
void d_putub(register unsigned char *, register unsigned char);
|
||||
void d_putsw(register unsigned char *, register signed short);
|
||||
void d_putuw(register unsigned char *, register unsigned short);
|
||||
void d_putsl(register unsigned char *, register signed long);
|
||||
void d_putul(register unsigned char *, register unsigned long);
|
||||
|
||||
void d_fetchsb(register const unsigned char **, register signed char *);
|
||||
void d_fetchub(register const unsigned char **, register unsigned char *);
|
||||
void d_fetchsw(register const unsigned char **, register signed short *);
|
||||
void d_fetchuw(register const unsigned char **, register unsigned short *);
|
||||
void d_fetchsl(register const unsigned char **, register signed long *);
|
||||
void d_fetchul(register const unsigned char **, register unsigned long *);
|
||||
|
||||
void d_storesb(register unsigned char **, register signed char);
|
||||
void d_storeub(register unsigned char **, register unsigned char);
|
||||
void d_storesw(register unsigned char **, register signed short);
|
||||
void d_storeuw(register unsigned char **, register unsigned short);
|
||||
void d_storesl(register unsigned char **, register signed long);
|
||||
void d_storeul(register unsigned char **, register unsigned long);
|
||||
|
||||
void d_fetchstr(const unsigned char **, char *, unsigned);
|
||||
void d_storestr(unsigned char **, const char *, unsigned);
|
||||
|
||||
int d_relstring(const char *, const char *);
|
||||
|
||||
time_t d_ltime(unsigned long);
|
||||
unsigned long d_mtime(time_t);
|
520
libhfs/file.c
Normal file
520
libhfs/file.c
Normal file
@ -0,0 +1,520 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
# ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
# endif
|
||||
|
||||
# include <string.h>
|
||||
# include <errno.h>
|
||||
|
||||
# include "libhfs.h"
|
||||
# include "file.h"
|
||||
# include "btree.h"
|
||||
# include "record.h"
|
||||
# include "volume.h"
|
||||
|
||||
/*
|
||||
* NAME: file->init()
|
||||
* DESCRIPTION: initialize file structure
|
||||
*/
|
||||
void f_init(hfsfile *file, hfsvol *vol, long cnid, const char *name)
|
||||
{
|
||||
int i;
|
||||
|
||||
file->vol = vol;
|
||||
file->parid = 0;
|
||||
|
||||
strcpy(file->name, name);
|
||||
|
||||
file->cat.cdrType = cdrFilRec;
|
||||
file->cat.cdrResrv2 = 0;
|
||||
|
||||
file->cat.u.fil.filFlags = 0;
|
||||
file->cat.u.fil.filTyp = 0;
|
||||
|
||||
file->cat.u.fil.filUsrWds.fdType = 0;
|
||||
file->cat.u.fil.filUsrWds.fdCreator = 0;
|
||||
file->cat.u.fil.filUsrWds.fdFlags = 0;
|
||||
file->cat.u.fil.filUsrWds.fdLocation.v = 0;
|
||||
file->cat.u.fil.filUsrWds.fdLocation.h = 0;
|
||||
file->cat.u.fil.filUsrWds.fdFldr = 0;
|
||||
|
||||
file->cat.u.fil.filFlNum = cnid;
|
||||
file->cat.u.fil.filStBlk = 0;
|
||||
file->cat.u.fil.filLgLen = 0;
|
||||
file->cat.u.fil.filPyLen = 0;
|
||||
file->cat.u.fil.filRStBlk = 0;
|
||||
file->cat.u.fil.filRLgLen = 0;
|
||||
file->cat.u.fil.filRPyLen = 0;
|
||||
file->cat.u.fil.filCrDat = 0;
|
||||
file->cat.u.fil.filMdDat = 0;
|
||||
file->cat.u.fil.filBkDat = 0;
|
||||
|
||||
file->cat.u.fil.filFndrInfo.fdIconID = 0;
|
||||
for (i = 0; i < 4; ++i)
|
||||
file->cat.u.fil.filFndrInfo.fdUnused[i] = 0;
|
||||
file->cat.u.fil.filFndrInfo.fdComment = 0;
|
||||
file->cat.u.fil.filFndrInfo.fdPutAway = 0;
|
||||
|
||||
file->cat.u.fil.filClpSize = 0;
|
||||
|
||||
for (i = 0; i < 3; ++i)
|
||||
{
|
||||
file->cat.u.fil.filExtRec[i].xdrStABN = 0;
|
||||
file->cat.u.fil.filExtRec[i].xdrNumABlks = 0;
|
||||
|
||||
file->cat.u.fil.filRExtRec[i].xdrStABN = 0;
|
||||
file->cat.u.fil.filRExtRec[i].xdrNumABlks = 0;
|
||||
}
|
||||
|
||||
file->cat.u.fil.filResrv = 0;
|
||||
|
||||
f_selectfork(file, fkData);
|
||||
|
||||
file->flags = 0;
|
||||
|
||||
file->prev = 0;
|
||||
file->next = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: file->selectfork()
|
||||
* DESCRIPTION: choose a fork for file operations
|
||||
*/
|
||||
void f_selectfork(hfsfile *file, int fork)
|
||||
{
|
||||
file->fork = fork;
|
||||
|
||||
memcpy(&file->ext, fork == fkData ?
|
||||
&file->cat.u.fil.filExtRec : &file->cat.u.fil.filRExtRec,
|
||||
sizeof(ExtDataRec));
|
||||
|
||||
file->fabn = 0;
|
||||
file->pos = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: file->getptrs()
|
||||
* DESCRIPTION: make pointers to the current fork's lengths and extents
|
||||
*/
|
||||
void f_getptrs(hfsfile *file, ExtDataRec **extrec,
|
||||
unsigned long **lglen, unsigned long **pylen)
|
||||
{
|
||||
if (file->fork == fkData)
|
||||
{
|
||||
if (extrec)
|
||||
*extrec = &file->cat.u.fil.filExtRec;
|
||||
if (lglen)
|
||||
*lglen = &file->cat.u.fil.filLgLen;
|
||||
if (pylen)
|
||||
*pylen = &file->cat.u.fil.filPyLen;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (extrec)
|
||||
*extrec = &file->cat.u.fil.filRExtRec;
|
||||
if (lglen)
|
||||
*lglen = &file->cat.u.fil.filRLgLen;
|
||||
if (pylen)
|
||||
*pylen = &file->cat.u.fil.filRPyLen;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: file->doblock()
|
||||
* DESCRIPTION: read or write a numbered block from a file
|
||||
*/
|
||||
int f_doblock(hfsfile *file, unsigned long num, block *bp,
|
||||
int (*func)(hfsvol *, unsigned int, unsigned int, block *))
|
||||
{
|
||||
unsigned int abnum;
|
||||
unsigned int blnum;
|
||||
unsigned int fabn;
|
||||
int i;
|
||||
|
||||
abnum = num / file->vol->lpa;
|
||||
blnum = num % file->vol->lpa;
|
||||
|
||||
/* locate the appropriate extent record */
|
||||
|
||||
fabn = file->fabn;
|
||||
|
||||
if (abnum < fabn)
|
||||
{
|
||||
ExtDataRec *extrec;
|
||||
|
||||
f_getptrs(file, &extrec, 0, 0);
|
||||
|
||||
fabn = file->fabn = 0;
|
||||
memcpy(&file->ext, extrec, sizeof(ExtDataRec));
|
||||
}
|
||||
else
|
||||
abnum -= fabn;
|
||||
|
||||
while (1)
|
||||
{
|
||||
unsigned int n;
|
||||
|
||||
for (i = 0; i < 3; ++i)
|
||||
{
|
||||
n = file->ext[i].xdrNumABlks;
|
||||
|
||||
if (abnum < n)
|
||||
return func(file->vol, file->ext[i].xdrStABN + abnum, blnum, bp);
|
||||
|
||||
fabn += n;
|
||||
abnum -= n;
|
||||
}
|
||||
|
||||
if (v_extsearch(file, fabn, &file->ext, 0) <= 0)
|
||||
goto fail;
|
||||
|
||||
file->fabn = fabn;
|
||||
}
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: file->addextent()
|
||||
* DESCRIPTION: add an extent to a file
|
||||
*/
|
||||
int f_addextent(hfsfile *file, ExtDescriptor *blocks)
|
||||
{
|
||||
hfsvol *vol = file->vol;
|
||||
ExtDataRec *extrec;
|
||||
unsigned long *pylen;
|
||||
unsigned int start, end;
|
||||
node n;
|
||||
int i;
|
||||
|
||||
f_getptrs(file, &extrec, 0, &pylen);
|
||||
|
||||
start = file->fabn;
|
||||
end = *pylen / vol->mdb.drAlBlkSiz;
|
||||
|
||||
n.nnum = 0;
|
||||
i = -1;
|
||||
|
||||
while (start < end)
|
||||
{
|
||||
for (i = 0; i < 3; ++i)
|
||||
{
|
||||
unsigned int num;
|
||||
|
||||
num = file->ext[i].xdrNumABlks;
|
||||
start += num;
|
||||
|
||||
if (start == end)
|
||||
break;
|
||||
else if (start > end)
|
||||
ERROR(EIO, "file extents exceed file physical length");
|
||||
else if (num == 0)
|
||||
ERROR(EIO, "empty file extent");
|
||||
}
|
||||
|
||||
if (start == end)
|
||||
break;
|
||||
|
||||
if (v_extsearch(file, start, &file->ext, &n) <= 0)
|
||||
goto fail;
|
||||
|
||||
file->fabn = start;
|
||||
}
|
||||
|
||||
if (i >= 0 &&
|
||||
file->ext[i].xdrStABN + file->ext[i].xdrNumABlks == blocks->xdrStABN)
|
||||
file->ext[i].xdrNumABlks += blocks->xdrNumABlks;
|
||||
else
|
||||
{
|
||||
/* create a new extent descriptor */
|
||||
|
||||
if (++i < 3)
|
||||
file->ext[i] = *blocks;
|
||||
else
|
||||
{
|
||||
ExtKeyRec key;
|
||||
byte record[HFS_MAX_EXTRECLEN];
|
||||
unsigned int reclen;
|
||||
|
||||
/* record is full; create a new one */
|
||||
|
||||
file->ext[0] = *blocks;
|
||||
|
||||
for (i = 1; i < 3; ++i)
|
||||
{
|
||||
file->ext[i].xdrStABN = 0;
|
||||
file->ext[i].xdrNumABlks = 0;
|
||||
}
|
||||
|
||||
file->fabn = start;
|
||||
|
||||
r_makeextkey(&key, file->fork, file->cat.u.fil.filFlNum, end);
|
||||
r_packextrec(&key, &file->ext, record, &reclen);
|
||||
|
||||
if (bt_insert(&vol->ext, record, reclen) == -1)
|
||||
goto fail;
|
||||
|
||||
i = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= 0)
|
||||
{
|
||||
/* store the modified extent record */
|
||||
|
||||
if (file->fabn)
|
||||
{
|
||||
if ((n.nnum == 0 &&
|
||||
v_extsearch(file, file->fabn, 0, &n) <= 0) ||
|
||||
v_putextrec(&file->ext, &n) == -1)
|
||||
goto fail;
|
||||
}
|
||||
else
|
||||
memcpy(extrec, &file->ext, sizeof(ExtDataRec));
|
||||
}
|
||||
|
||||
*pylen += blocks->xdrNumABlks * vol->mdb.drAlBlkSiz;
|
||||
|
||||
file->flags |= HFS_FILE_UPDATE_CATREC;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: file->alloc()
|
||||
* DESCRIPTION: reserve allocation blocks for a file
|
||||
*/
|
||||
long f_alloc(hfsfile *file)
|
||||
{
|
||||
hfsvol *vol = file->vol;
|
||||
unsigned long clumpsz;
|
||||
ExtDescriptor blocks;
|
||||
|
||||
clumpsz = file->cat.u.fil.filClpSize;
|
||||
if (clumpsz == 0)
|
||||
{
|
||||
if (file == &vol->ext.f)
|
||||
clumpsz = vol->mdb.drXTClpSiz;
|
||||
else if (file == &vol->cat.f)
|
||||
clumpsz = vol->mdb.drCTClpSiz;
|
||||
else
|
||||
clumpsz = vol->mdb.drClpSiz;
|
||||
}
|
||||
|
||||
blocks.xdrNumABlks = clumpsz / vol->mdb.drAlBlkSiz;
|
||||
|
||||
if (v_allocblocks(vol, &blocks) == -1)
|
||||
goto fail;
|
||||
|
||||
if (f_addextent(file, &blocks) == -1)
|
||||
{
|
||||
v_freeblocks(vol, &blocks);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return blocks.xdrNumABlks;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: file->trunc()
|
||||
* DESCRIPTION: release allocation blocks unneeded by a file
|
||||
*/
|
||||
int f_trunc(hfsfile *file)
|
||||
{
|
||||
hfsvol *vol = file->vol;
|
||||
ExtDataRec *extrec;
|
||||
unsigned long *lglen, *pylen, alblksz, newpylen;
|
||||
unsigned int dlen, start, end;
|
||||
node n;
|
||||
int i;
|
||||
|
||||
if (vol->flags & HFS_VOL_READONLY)
|
||||
goto done;
|
||||
|
||||
f_getptrs(file, &extrec, &lglen, &pylen);
|
||||
|
||||
alblksz = vol->mdb.drAlBlkSiz;
|
||||
newpylen = (*lglen / alblksz + (*lglen % alblksz != 0)) * alblksz;
|
||||
|
||||
if (newpylen > *pylen)
|
||||
ERROR(EIO, "file size exceeds physical length");
|
||||
else if (newpylen == *pylen)
|
||||
goto done;
|
||||
|
||||
dlen = (*pylen - newpylen) / alblksz;
|
||||
|
||||
start = file->fabn;
|
||||
end = newpylen / alblksz;
|
||||
|
||||
if (start >= end)
|
||||
{
|
||||
start = file->fabn = 0;
|
||||
memcpy(&file->ext, extrec, sizeof(ExtDataRec));
|
||||
}
|
||||
|
||||
n.nnum = 0;
|
||||
i = -1;
|
||||
|
||||
while (start < end)
|
||||
{
|
||||
for (i = 0; i < 3; ++i)
|
||||
{
|
||||
unsigned int num;
|
||||
|
||||
num = file->ext[i].xdrNumABlks;
|
||||
start += num;
|
||||
|
||||
if (start >= end)
|
||||
break;
|
||||
else if (num == 0)
|
||||
ERROR(EIO, "empty file extent");
|
||||
}
|
||||
|
||||
if (start >= end)
|
||||
break;
|
||||
|
||||
if (v_extsearch(file, start, &file->ext, &n) <= 0)
|
||||
goto fail;
|
||||
|
||||
file->fabn = start;
|
||||
}
|
||||
|
||||
if (start > end)
|
||||
{
|
||||
ExtDescriptor blocks;
|
||||
|
||||
file->ext[i].xdrNumABlks -= start - end;
|
||||
dlen -= start - end;
|
||||
|
||||
blocks.xdrStABN = file->ext[i].xdrStABN + file->ext[i].xdrNumABlks;
|
||||
blocks.xdrNumABlks = start - end;
|
||||
|
||||
if (v_freeblocks(vol, &blocks) == -1)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
*pylen = newpylen;
|
||||
|
||||
file->flags |= HFS_FILE_UPDATE_CATREC;
|
||||
|
||||
do
|
||||
{
|
||||
while (dlen && ++i < 3)
|
||||
{
|
||||
unsigned int num;
|
||||
|
||||
num = file->ext[i].xdrNumABlks;
|
||||
start += num;
|
||||
|
||||
if (num == 0)
|
||||
ERROR(EIO, "empty file extent");
|
||||
else if (num > dlen)
|
||||
ERROR(EIO, "file extents exceed physical size");
|
||||
|
||||
dlen -= num;
|
||||
|
||||
if (v_freeblocks(vol, &file->ext[i]) == -1)
|
||||
goto fail;
|
||||
|
||||
file->ext[i].xdrStABN = 0;
|
||||
file->ext[i].xdrNumABlks = 0;
|
||||
}
|
||||
|
||||
if (file->fabn)
|
||||
{
|
||||
if (n.nnum == 0 &&
|
||||
v_extsearch(file, file->fabn, 0, &n) <= 0)
|
||||
goto fail;
|
||||
|
||||
if (file->ext[0].xdrNumABlks)
|
||||
{
|
||||
if (v_putextrec(&file->ext, &n) == -1)
|
||||
goto fail;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bt_delete(&vol->ext, HFS_NODEREC(n, n.rnum)) == -1)
|
||||
goto fail;
|
||||
|
||||
n.nnum = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
memcpy(extrec, &file->ext, sizeof(ExtDataRec));
|
||||
|
||||
if (dlen)
|
||||
{
|
||||
if (v_extsearch(file, start, &file->ext, &n) <= 0)
|
||||
goto fail;
|
||||
|
||||
file->fabn = start;
|
||||
i = -1;
|
||||
}
|
||||
}
|
||||
while (dlen);
|
||||
|
||||
done:
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: file->flush()
|
||||
* DESCRIPTION: flush all pending changes to an open file
|
||||
*/
|
||||
int f_flush(hfsfile *file)
|
||||
{
|
||||
hfsvol *vol = file->vol;
|
||||
|
||||
if (vol->flags & HFS_VOL_READONLY)
|
||||
goto done;
|
||||
|
||||
if (file->flags & HFS_FILE_UPDATE_CATREC)
|
||||
{
|
||||
node n;
|
||||
|
||||
file->cat.u.fil.filStBlk = file->cat.u.fil.filExtRec[0].xdrStABN;
|
||||
file->cat.u.fil.filRStBlk = file->cat.u.fil.filRExtRec[0].xdrStABN;
|
||||
|
||||
if (v_catsearch(vol, file->parid, file->name, 0, 0, &n) <= 0 ||
|
||||
v_putcatrec(&file->cat, &n) == -1)
|
||||
goto fail;
|
||||
|
||||
file->flags &= ~HFS_FILE_UPDATE_CATREC;
|
||||
}
|
||||
|
||||
done:
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
45
libhfs/file.h
Normal file
45
libhfs/file.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
enum {
|
||||
fkData = 0x00,
|
||||
fkRsrc = 0xff
|
||||
};
|
||||
|
||||
void f_init(hfsfile *, hfsvol *, long, const char *);
|
||||
void f_selectfork(hfsfile *, int);
|
||||
void f_getptrs(hfsfile *, ExtDataRec **, unsigned long **, unsigned long **);
|
||||
|
||||
int f_doblock(hfsfile *, unsigned long, block *,
|
||||
int (*)(hfsvol *, unsigned int, unsigned int, block *));
|
||||
|
||||
# define f_getblock(file, num, bp) \
|
||||
f_doblock((file), (num), (bp), b_readab)
|
||||
# define f_putblock(file, num, bp) \
|
||||
f_doblock((file), (num), (bp), \
|
||||
(int (*)(hfsvol *, unsigned int, unsigned int, block *)) \
|
||||
b_writeab)
|
||||
|
||||
int f_addextent(hfsfile *, ExtDescriptor *);
|
||||
long f_alloc(hfsfile *);
|
||||
|
||||
int f_trunc(hfsfile *);
|
||||
int f_flush(hfsfile *);
|
1993
libhfs/hfs.c
Normal file
1993
libhfs/hfs.c
Normal file
File diff suppressed because it is too large
Load Diff
201
libhfs/hfs.h
Normal file
201
libhfs/hfs.h
Normal file
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
# include <time.h>
|
||||
|
||||
# define HFS_BLOCKSZ 512
|
||||
# define HFS_BLOCKSZ_BITS 9
|
||||
|
||||
# define HFS_MAX_FLEN 31
|
||||
# define HFS_MAX_VLEN 27
|
||||
|
||||
typedef struct _hfsvol_ hfsvol;
|
||||
typedef struct _hfsfile_ hfsfile;
|
||||
typedef struct _hfsdir_ hfsdir;
|
||||
|
||||
typedef struct {
|
||||
char name[HFS_MAX_VLEN + 1]; /* name of volume (MacOS Standard Roman) */
|
||||
int flags; /* volume flags */
|
||||
|
||||
unsigned long totbytes; /* total bytes on volume */
|
||||
unsigned long freebytes; /* free bytes on volume */
|
||||
|
||||
unsigned long alblocksz; /* volume allocation block size */
|
||||
unsigned long clumpsz; /* default file clump size */
|
||||
|
||||
unsigned long numfiles; /* number of files in volume */
|
||||
unsigned long numdirs; /* number of directories in volume */
|
||||
|
||||
time_t crdate; /* volume creation date */
|
||||
time_t mddate; /* last volume modification date */
|
||||
time_t bkdate; /* last volume backup date */
|
||||
|
||||
unsigned long blessed; /* CNID of MacOS System Folder */
|
||||
} hfsvolent;
|
||||
|
||||
typedef struct {
|
||||
char name[HFS_MAX_FLEN + 1]; /* catalog name (MacOS Standard Roman) */
|
||||
int flags; /* bit flags */
|
||||
unsigned long cnid; /* catalog node id (CNID) */
|
||||
unsigned long parid; /* CNID of parent directory */
|
||||
|
||||
time_t crdate; /* date of creation */
|
||||
time_t mddate; /* date of last modification */
|
||||
time_t bkdate; /* date of last backup */
|
||||
|
||||
short fdflags; /* Macintosh Finder flags */
|
||||
|
||||
struct {
|
||||
signed short v; /* Finder icon vertical coordinate */
|
||||
signed short h; /* horizontal coordinate */
|
||||
} fdlocation;
|
||||
|
||||
union {
|
||||
struct {
|
||||
unsigned long dsize; /* size of data fork */
|
||||
unsigned long rsize; /* size of resource fork */
|
||||
|
||||
char type[5]; /* file type code (plus null) */
|
||||
char creator[5]; /* file creator code (plus null) */
|
||||
} file;
|
||||
|
||||
struct {
|
||||
unsigned short valence; /* number of items in directory */
|
||||
|
||||
struct {
|
||||
signed short top; /* top edge of folder's rectangle */
|
||||
signed short left; /* left edge */
|
||||
signed short bottom; /* bottom edge */
|
||||
signed short right; /* right edge */
|
||||
} rect;
|
||||
} dir;
|
||||
} u;
|
||||
} hfsdirent;
|
||||
|
||||
# define HFS_ISDIR 0x0001
|
||||
# define HFS_ISLOCKED 0x0002
|
||||
|
||||
# define HFS_CNID_ROOTPAR 1
|
||||
# define HFS_CNID_ROOTDIR 2
|
||||
# define HFS_CNID_EXT 3
|
||||
# define HFS_CNID_CAT 4
|
||||
# define HFS_CNID_BADALLOC 5
|
||||
|
||||
# define HFS_FNDR_ISONDESK (1 << 0)
|
||||
# define HFS_FNDR_COLOR 0x0e
|
||||
# define HFS_FNDR_COLORRESERVED (1 << 4)
|
||||
# define HFS_FNDR_REQUIRESSWITCHLAUNCH (1 << 5)
|
||||
# define HFS_FNDR_ISSHARED (1 << 6)
|
||||
# define HFS_FNDR_HASNOINITS (1 << 7)
|
||||
# define HFS_FNDR_HASBEENINITED (1 << 8)
|
||||
# define HFS_FNDR_RESERVED (1 << 9)
|
||||
# define HFS_FNDR_HASCUSTOMICON (1 << 10)
|
||||
# define HFS_FNDR_ISSTATIONERY (1 << 11)
|
||||
# define HFS_FNDR_NAMELOCKED (1 << 12)
|
||||
# define HFS_FNDR_HASBUNDLE (1 << 13)
|
||||
# define HFS_FNDR_ISINVISIBLE (1 << 14)
|
||||
# define HFS_FNDR_ISALIAS (1 << 15)
|
||||
|
||||
extern const char *hfs_error;
|
||||
extern const unsigned char hfs_charorder[];
|
||||
|
||||
# define HFS_MODE_RDONLY 0
|
||||
# define HFS_MODE_RDWR 1
|
||||
# define HFS_MODE_ANY 2
|
||||
|
||||
# define HFS_MODE_MASK 0x0003
|
||||
|
||||
# define HFS_OPT_NOCACHE 0x0100
|
||||
# define HFS_OPT_2048 0x0200
|
||||
# define HFS_OPT_ZERO 0x0400
|
||||
|
||||
# define HFS_SEEK_SET 0
|
||||
# define HFS_SEEK_CUR 1
|
||||
# define HFS_SEEK_END 2
|
||||
|
||||
#ifdef CP_NO_STATIC
|
||||
hfsvol *hfs_mount(const char *, int, int);
|
||||
#endif
|
||||
int hfs_flush(hfsvol *);
|
||||
#ifdef CP_NO_STATIC
|
||||
void hfs_flushall(void);
|
||||
int hfs_umount(hfsvol *);
|
||||
void hfs_umountall(void);
|
||||
hfsvol *hfs_getvol(const char *);
|
||||
void hfs_setvol(hfsvol *);
|
||||
#endif
|
||||
|
||||
int hfs_vstat(hfsvol *, hfsvolent *);
|
||||
int hfs_vsetattr(hfsvol *, hfsvolent *);
|
||||
|
||||
int hfs_chdir(hfsvol *, const char *);
|
||||
unsigned long hfs_getcwd(hfsvol *);
|
||||
int hfs_setcwd(hfsvol *, unsigned long);
|
||||
int hfs_dirinfo(hfsvol *, unsigned long *, char *);
|
||||
|
||||
hfsdir *hfs_opendir(hfsvol *, const char *);
|
||||
int hfs_readdir(hfsdir *, hfsdirent *);
|
||||
int hfs_closedir(hfsdir *);
|
||||
|
||||
hfsfile *hfs_create(hfsvol *, const char *, const char *, const char *);
|
||||
hfsfile *hfs_open(hfsvol *, const char *);
|
||||
int hfs_setfork(hfsfile *, int);
|
||||
int hfs_getfork(hfsfile *);
|
||||
unsigned long hfs_read(hfsfile *, void *, unsigned long);
|
||||
unsigned long hfs_write(hfsfile *, const void *, unsigned long);
|
||||
int hfs_truncate(hfsfile *, unsigned long);
|
||||
unsigned long hfs_seek(hfsfile *, long, int);
|
||||
int hfs_close(hfsfile *);
|
||||
|
||||
int hfs_stat(hfsvol *, const char *, hfsdirent *);
|
||||
int hfs_fstat(hfsfile *, hfsdirent *);
|
||||
int hfs_setattr(hfsvol *, const char *, const hfsdirent *);
|
||||
int hfs_fsetattr(hfsfile *, const hfsdirent *);
|
||||
|
||||
int hfs_mkdir(hfsvol *, const char *);
|
||||
int hfs_rmdir(hfsvol *, const char *);
|
||||
|
||||
int hfs_delete(hfsvol *, const char *);
|
||||
int hfs_rename(hfsvol *, const char *, const char *);
|
||||
|
||||
int hfs_zero(const char *, unsigned int, unsigned long *);
|
||||
int hfs_mkpart(const char *, unsigned long);
|
||||
int hfs_nparts(const char *);
|
||||
|
||||
int hfs_format(const char *, int, int,
|
||||
const char *, unsigned int, const unsigned long []);
|
||||
|
||||
/* CiderPress callback interface */
|
||||
enum { HFS_CB_VOLSIZE, HFS_CB_READ, HFS_CB_WRITE, HFS_CB_SEEK };
|
||||
typedef unsigned long (*oscallback)(void* cookie, int op, unsigned long arg1,
|
||||
void* arg2);
|
||||
hfsvol* hfs_callback_open(oscallback func, void* cookie, int mode);
|
||||
int hfs_callback_close(hfsvol* vol);
|
||||
int hfs_callback_format(oscallback func, void* cookie, int mode,
|
||||
const char* vname);
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
227
libhfs/libhfs.h
Normal file
227
libhfs/libhfs.h
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
# include "hfs.h"
|
||||
# include "apple.h"
|
||||
|
||||
//extern int errno;
|
||||
# include <errno.h>
|
||||
|
||||
# define ERROR(code, str) \
|
||||
do { hfs_error = (str), errno = (code); goto fail; } while (0)
|
||||
|
||||
# ifdef DEBUG
|
||||
# define ASSERT(cond) do { if (! (cond)) abort(); } while (0)
|
||||
# else
|
||||
# define ASSERT(cond) /* nothing */
|
||||
# endif
|
||||
|
||||
# define SIZE(type, n) ((size_t) (sizeof(type) * (n)))
|
||||
# define ALLOC(type, n) ((type *) malloc(SIZE(type, n)))
|
||||
# define ALLOCX(type, n) ((n) ? ALLOC(type, n) : (type *) 0)
|
||||
# define FREE(ptr) ((ptr) ? (void) free((void *) ptr) : (void) 0)
|
||||
|
||||
# define REALLOC(ptr, type, n) \
|
||||
((type *) ((ptr) ? realloc(ptr, SIZE(type, n)) : malloc(SIZE(type, n))))
|
||||
# define REALLOCX(ptr, type, n) \
|
||||
((n) ? REALLOC(ptr, type, n) : (FREE(ptr), (type *) 0))
|
||||
|
||||
# define BMTST(bm, num) \
|
||||
(((const byte *) (bm))[(num) >> 3] & (0x80 >> ((num) & 0x07)))
|
||||
# define BMSET(bm, num) \
|
||||
(((byte *) (bm))[(num) >> 3] |= (0x80 >> ((num) & 0x07)))
|
||||
# define BMCLR(bm, num) \
|
||||
(((byte *) (bm))[(num) >> 3] &= ~(0x80 >> ((num) & 0x07)))
|
||||
|
||||
# define STRINGIZE(x) #x
|
||||
# define STR(x) STRINGIZE(x)
|
||||
|
||||
typedef unsigned char byte;
|
||||
typedef byte block[HFS_BLOCKSZ];
|
||||
|
||||
typedef struct _bucket_ {
|
||||
int flags; /* bit flags */
|
||||
unsigned int count; /* number of times this block is requested */
|
||||
|
||||
unsigned long bnum; /* logical block number */
|
||||
block *data; /* pointer to block contents */
|
||||
|
||||
struct _bucket_ *cnext; /* next bucket in cache chain */
|
||||
struct _bucket_ *cprev; /* previous bucket in cache chain */
|
||||
|
||||
struct _bucket_ *hnext; /* next bucket in hash chain */
|
||||
struct _bucket_ **hprev; /* previous bucket's pointer to this bucket */
|
||||
} bucket;
|
||||
|
||||
# define HFS_BUCKET_INUSE 0x01
|
||||
# define HFS_BUCKET_DIRTY 0x02
|
||||
|
||||
# define HFS_CACHESZ 128
|
||||
# define HFS_HASHSZ 32
|
||||
# define HFS_BLOCKBUFSZ 16
|
||||
|
||||
typedef struct {
|
||||
struct _hfsvol_ *vol; /* volume to which cache belongs */
|
||||
bucket *tail; /* end of bucket chain */
|
||||
|
||||
unsigned int hits; /* number of cache hits */
|
||||
unsigned int misses; /* number of cache misses */
|
||||
|
||||
bucket chain[HFS_CACHESZ]; /* cache bucket chain */
|
||||
bucket *hash[HFS_HASHSZ]; /* hash table for bucket chain */
|
||||
|
||||
block pool[HFS_CACHESZ]; /* physical blocks in cache */
|
||||
} bcache;
|
||||
|
||||
# define HFS_MAP1SZ 256
|
||||
# define HFS_MAPXSZ 492
|
||||
|
||||
# define HFS_NODEREC(nd, rnum) ((nd).data + (nd).roff[rnum])
|
||||
# define HFS_RECLEN(nd, rnum) ((nd).roff[(rnum) + 1] - (nd).roff[rnum])
|
||||
|
||||
# define HFS_RECKEYLEN(ptr) (*(const byte *) (ptr))
|
||||
# define HFS_RECKEYSKIP(ptr) ((size_t) ((1 + HFS_RECKEYLEN(ptr) + 1) & ~1))
|
||||
# define HFS_RECDATA(ptr) ((ptr) + HFS_RECKEYSKIP(ptr))
|
||||
|
||||
# define HFS_SETKEYLEN(ptr, x) (*(byte *) (ptr) = (x))
|
||||
|
||||
# define HFS_CATDATALEN sizeof(CatDataRec)
|
||||
# define HFS_EXTDATALEN sizeof(ExtDataRec)
|
||||
# define HFS_MAX_DATALEN (HFS_CATDATALEN > HFS_EXTDATALEN ? \
|
||||
HFS_CATDATALEN : HFS_EXTDATALEN)
|
||||
|
||||
# define HFS_CATKEYLEN sizeof(CatKeyRec)
|
||||
# define HFS_EXTKEYLEN sizeof(ExtKeyRec)
|
||||
# define HFS_MAX_KEYLEN (HFS_CATKEYLEN > HFS_EXTKEYLEN ? \
|
||||
HFS_CATKEYLEN : HFS_EXTKEYLEN)
|
||||
|
||||
# define HFS_MAX_CATRECLEN (HFS_CATKEYLEN + HFS_CATDATALEN)
|
||||
# define HFS_MAX_EXTRECLEN (HFS_EXTKEYLEN + HFS_EXTDATALEN)
|
||||
# define HFS_MAX_RECLEN (HFS_MAX_KEYLEN + HFS_MAX_DATALEN)
|
||||
|
||||
# define HFS_SIGWORD 0x4244
|
||||
# define HFS_SIGWORD_MFS ((Integer) 0xd2d7)
|
||||
|
||||
# define HFS_ATRB_BUSY (1 << 6)
|
||||
# define HFS_ATRB_HLOCKED (1 << 7)
|
||||
# define HFS_ATRB_UMOUNTED (1 << 8)
|
||||
# define HFS_ATRB_BBSPARED (1 << 9)
|
||||
# define HFS_ATRB_BVINCONSIS (1 << 11)
|
||||
# define HFS_ATRB_COPYPROT (1 << 14)
|
||||
# define HFS_ATRB_SLOCKED (1 << 15)
|
||||
|
||||
struct _hfsfile_ {
|
||||
struct _hfsvol_ *vol; /* pointer to volume descriptor */
|
||||
unsigned long parid; /* parent directory ID of this file */
|
||||
char name[HFS_MAX_FLEN + 1]; /* catalog name of this file */
|
||||
CatDataRec cat; /* catalog information */
|
||||
ExtDataRec ext; /* current extent record */
|
||||
unsigned int fabn; /* starting file allocation block number */
|
||||
int fork; /* current selected fork for I/O */
|
||||
unsigned long pos; /* current file seek pointer */
|
||||
int flags; /* bit flags */
|
||||
|
||||
struct _hfsfile_ *prev;
|
||||
struct _hfsfile_ *next;
|
||||
};
|
||||
|
||||
# define HFS_FILE_UPDATE_CATREC 0x01
|
||||
|
||||
# define HFS_MAX_NRECS 35 /* maximum based on minimum record size */
|
||||
|
||||
typedef struct _node_ {
|
||||
struct _btree_ *bt; /* btree to which this node belongs */
|
||||
unsigned long nnum; /* node index */
|
||||
NodeDescriptor nd; /* node descriptor */
|
||||
int rnum; /* current record index */
|
||||
UInteger roff[HFS_MAX_NRECS + 1];
|
||||
/* record offsets */
|
||||
block data; /* raw contents of node */
|
||||
} node;
|
||||
|
||||
struct _hfsdir_ {
|
||||
struct _hfsvol_ *vol; /* associated volume */
|
||||
unsigned long dirid; /* directory ID of interest (or 0) */
|
||||
|
||||
node n; /* current B*-tree node */
|
||||
struct _hfsvol_ *vptr; /* current volume pointer */
|
||||
|
||||
struct _hfsdir_ *prev;
|
||||
struct _hfsdir_ *next;
|
||||
};
|
||||
|
||||
typedef void (*keyunpackfunc)(const byte *, void *);
|
||||
typedef int (*keycomparefunc)(const void *, const void *);
|
||||
|
||||
typedef struct _btree_ {
|
||||
hfsfile f; /* subset file information */
|
||||
node hdrnd; /* header node */
|
||||
BTHdrRec hdr; /* header record */
|
||||
byte *map; /* usage bitmap */
|
||||
unsigned long mapsz; /* number of bytes in bitmap */
|
||||
int flags; /* bit flags */
|
||||
|
||||
keyunpackfunc keyunpack; /* key unpacking function */
|
||||
keycomparefunc keycompare; /* key comparison function */
|
||||
} btree;
|
||||
|
||||
# define HFS_BT_UPDATE_HDR 0x01
|
||||
|
||||
struct _hfsvol_ {
|
||||
void *priv; /* OS-dependent private descriptor data */
|
||||
int flags; /* bit flags */
|
||||
|
||||
int pnum; /* ordinal HFS partition number */
|
||||
unsigned long vstart; /* logical block offset to start of volume */
|
||||
unsigned long vlen; /* number of logical blocks in volume */
|
||||
unsigned int lpa; /* number of logical blocks per allocation block */
|
||||
|
||||
bcache *cache; /* cache of recently used blocks */
|
||||
|
||||
MDB mdb; /* master directory block */
|
||||
block *vbm; /* volume bitmap */
|
||||
unsigned short vbmsz; /* number of blocks in bitmap */
|
||||
|
||||
btree ext; /* B*-tree control block for extents overflow file */
|
||||
btree cat; /* B*-tree control block for catalog file */
|
||||
|
||||
unsigned long cwd; /* directory id of current working directory */
|
||||
|
||||
int refs; /* number of external references to this volume */
|
||||
hfsfile *files; /* list of open files */
|
||||
hfsdir *dirs; /* list of open directories */
|
||||
|
||||
struct _hfsvol_ *prev;
|
||||
struct _hfsvol_ *next;
|
||||
};
|
||||
|
||||
# define HFS_VOL_OPEN 0x0001
|
||||
# define HFS_VOL_MOUNTED 0x0002
|
||||
# define HFS_VOL_READONLY 0x0004
|
||||
# define HFS_VOL_USINGCACHE 0x0008
|
||||
|
||||
# define HFS_VOL_UPDATE_MDB 0x0010
|
||||
# define HFS_VOL_UPDATE_ALTMDB 0x0020
|
||||
# define HFS_VOL_UPDATE_VBM 0x0040
|
||||
|
||||
# define HFS_VOL_OPT_MASK 0xff00
|
||||
|
||||
extern hfsvol *hfs_mounts;
|
136
libhfs/libhfs.vcxproj
Normal file
136
libhfs/libhfs.vcxproj
Normal file
@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{0FA742E9-8C07-43DD-AFF8-CE31FAF70821}</ProjectGuid>
|
||||
<SccProjectName />
|
||||
<SccLocalPath />
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<UseOfMfc>false</UseOfMfc>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<UseOfMfc>Dynamic</UseOfMfc>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<_ProjectFileVersion>12.0.30501.0</_ProjectFileVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
|
||||
<IntDir>$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
|
||||
<IntDir>$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;HAVE_CONFIG_H;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<PrecompiledHeader />
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)$(TargetName).pch</PrecompiledHeaderOutputFile>
|
||||
<AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
|
||||
<ObjectFileName>$(IntDir)</ObjectFileName>
|
||||
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
|
||||
<BrowseInformation>true</BrowseInformation>
|
||||
<WarningLevel>Level2</WarningLevel>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
|
||||
</ClCompile>
|
||||
<Lib>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
</Lib>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Culture>0x0409</Culture>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;HAVE_CONFIG_H;_CRT_SECURE_NO_WARNINGS;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<StringPooling>true</StringPooling>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<PrecompiledHeader />
|
||||
<PrecompiledHeaderOutputFile>$(IntDir)$(TargetName).pch</PrecompiledHeaderOutputFile>
|
||||
<AssemblerListingLocation>$(IntDir)</AssemblerListingLocation>
|
||||
<ObjectFileName>$(IntDir)</ObjectFileName>
|
||||
<ProgramDataBaseFileName>$(IntDir)vc$(PlatformToolsetVersion).pdb</ProgramDataBaseFileName>
|
||||
<WarningLevel>Level2</WarningLevel>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
</ClCompile>
|
||||
<Lib>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
</Lib>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Culture>0x0409</Culture>
|
||||
</ResourceCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="apple.h" />
|
||||
<ClInclude Include="block.h" />
|
||||
<ClInclude Include="btree.h" />
|
||||
<ClInclude Include="config.h" />
|
||||
<ClInclude Include="data.h" />
|
||||
<ClInclude Include="file.h" />
|
||||
<ClInclude Include="hfs.h" />
|
||||
<ClInclude Include="libhfs.h" />
|
||||
<ClInclude Include="low.h" />
|
||||
<ClInclude Include="medium.h" />
|
||||
<ClInclude Include="node.h" />
|
||||
<ClInclude Include="os.h" />
|
||||
<ClInclude Include="record.h" />
|
||||
<ClInclude Include="version.h" />
|
||||
<ClInclude Include="volume.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="block.c" />
|
||||
<ClCompile Include="btree.c" />
|
||||
<ClCompile Include="data.c" />
|
||||
<ClCompile Include="file.c" />
|
||||
<ClCompile Include="hfs.c" />
|
||||
<ClCompile Include="low.c" />
|
||||
<ClCompile Include="medium.c" />
|
||||
<ClCompile Include="node.c" />
|
||||
<ClCompile Include="os.c" />
|
||||
<ClCompile Include="record.c" />
|
||||
<ClCompile Include="version.c" />
|
||||
<ClCompile Include="volume.c" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
98
libhfs/libhfs.vcxproj.filters
Normal file
98
libhfs/libhfs.vcxproj.filters
Normal file
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{2d85999b-987f-4814-9a1a-50647f99d4a7}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cxx;rc;def;r;odl;idl;hpj;bat</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{3de635b8-d8d0-4540-8bc6-f060838e8e8e}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="apple.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="block.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="btree.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="config.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="data.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="file.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="hfs.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="libhfs.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="low.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="medium.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="node.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="os.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="record.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="version.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="volume.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="block.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="btree.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="data.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="file.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="hfs.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="low.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="medium.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="node.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="os.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="record.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="version.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="volume.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
470
libhfs/low.c
Normal file
470
libhfs/low.c
Normal file
@ -0,0 +1,470 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
# ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
# endif
|
||||
|
||||
# include <stdlib.h>
|
||||
# include <string.h>
|
||||
# include <errno.h>
|
||||
|
||||
# include "libhfs.h"
|
||||
# include "low.h"
|
||||
# include "data.h"
|
||||
# include "block.h"
|
||||
# include "file.h"
|
||||
|
||||
/*
|
||||
* NAME: low->getddr()
|
||||
* DESCRIPTION: read a driver descriptor record
|
||||
*/
|
||||
int l_getddr(hfsvol *vol, Block0 *ddr)
|
||||
{
|
||||
block b;
|
||||
const byte *ptr = b;
|
||||
int i;
|
||||
|
||||
if (b_readpb(vol, 0, &b, 1) == -1)
|
||||
goto fail;
|
||||
|
||||
d_fetchsw(&ptr, &ddr->sbSig);
|
||||
d_fetchsw(&ptr, &ddr->sbBlkSize);
|
||||
d_fetchsl(&ptr, &ddr->sbBlkCount);
|
||||
d_fetchsw(&ptr, &ddr->sbDevType);
|
||||
d_fetchsw(&ptr, &ddr->sbDevId);
|
||||
d_fetchsl(&ptr, &ddr->sbData);
|
||||
d_fetchsw(&ptr, &ddr->sbDrvrCount);
|
||||
d_fetchsl(&ptr, &ddr->ddBlock);
|
||||
d_fetchsw(&ptr, &ddr->ddSize);
|
||||
d_fetchsw(&ptr, &ddr->ddType);
|
||||
|
||||
for (i = 0; i < 243; ++i)
|
||||
d_fetchsw(&ptr, &ddr->ddPad[i]);
|
||||
|
||||
ASSERT(ptr - b == HFS_BLOCKSZ);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: low->putddr()
|
||||
* DESCRIPTION: write a driver descriptor record
|
||||
*/
|
||||
int l_putddr(hfsvol *vol, const Block0 *ddr)
|
||||
{
|
||||
block b;
|
||||
byte *ptr = b;
|
||||
int i;
|
||||
|
||||
d_storesw(&ptr, ddr->sbSig);
|
||||
d_storesw(&ptr, ddr->sbBlkSize);
|
||||
d_storesl(&ptr, ddr->sbBlkCount);
|
||||
d_storesw(&ptr, ddr->sbDevType);
|
||||
d_storesw(&ptr, ddr->sbDevId);
|
||||
d_storesl(&ptr, ddr->sbData);
|
||||
d_storesw(&ptr, ddr->sbDrvrCount);
|
||||
d_storesl(&ptr, ddr->ddBlock);
|
||||
d_storesw(&ptr, ddr->ddSize);
|
||||
d_storesw(&ptr, ddr->ddType);
|
||||
|
||||
for (i = 0; i < 243; ++i)
|
||||
d_storesw(&ptr, ddr->ddPad[i]);
|
||||
|
||||
ASSERT(ptr - b == HFS_BLOCKSZ);
|
||||
|
||||
if (b_writepb(vol, 0, &b, 1) == -1)
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: low->getpmentry()
|
||||
* DESCRIPTION: read a partition map entry
|
||||
*/
|
||||
int l_getpmentry(hfsvol *vol, Partition *map, unsigned long bnum)
|
||||
{
|
||||
block b;
|
||||
const byte *ptr = b;
|
||||
int i;
|
||||
|
||||
if (b_readpb(vol, bnum, &b, 1) == -1)
|
||||
goto fail;
|
||||
|
||||
d_fetchsw(&ptr, &map->pmSig);
|
||||
d_fetchsw(&ptr, &map->pmSigPad);
|
||||
d_fetchsl(&ptr, &map->pmMapBlkCnt);
|
||||
d_fetchsl(&ptr, &map->pmPyPartStart);
|
||||
d_fetchsl(&ptr, &map->pmPartBlkCnt);
|
||||
|
||||
strncpy((char *) map->pmPartName, (const char *) ptr, 32);
|
||||
map->pmPartName[32] = 0;
|
||||
ptr += 32;
|
||||
|
||||
strncpy((char *) map->pmParType, (const char *) ptr, 32);
|
||||
map->pmParType[32] = 0;
|
||||
ptr += 32;
|
||||
|
||||
d_fetchsl(&ptr, &map->pmLgDataStart);
|
||||
d_fetchsl(&ptr, &map->pmDataCnt);
|
||||
d_fetchsl(&ptr, &map->pmPartStatus);
|
||||
d_fetchsl(&ptr, &map->pmLgBootStart);
|
||||
d_fetchsl(&ptr, &map->pmBootSize);
|
||||
d_fetchsl(&ptr, &map->pmBootAddr);
|
||||
d_fetchsl(&ptr, &map->pmBootAddr2);
|
||||
d_fetchsl(&ptr, &map->pmBootEntry);
|
||||
d_fetchsl(&ptr, &map->pmBootEntry2);
|
||||
d_fetchsl(&ptr, &map->pmBootCksum);
|
||||
|
||||
strncpy((char *) map->pmProcessor, (const char *) ptr, 16);
|
||||
map->pmProcessor[16] = 0;
|
||||
ptr += 16;
|
||||
|
||||
for (i = 0; i < 188; ++i)
|
||||
d_fetchsw(&ptr, &map->pmPad[i]);
|
||||
|
||||
ASSERT(ptr - b == HFS_BLOCKSZ);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: low->putpmentry()
|
||||
* DESCRIPTION: write a partition map entry
|
||||
*/
|
||||
int l_putpmentry(hfsvol *vol, const Partition *map, unsigned long bnum)
|
||||
{
|
||||
block b;
|
||||
byte *ptr = b;
|
||||
int i;
|
||||
|
||||
d_storesw(&ptr, map->pmSig);
|
||||
d_storesw(&ptr, map->pmSigPad);
|
||||
d_storesl(&ptr, map->pmMapBlkCnt);
|
||||
d_storesl(&ptr, map->pmPyPartStart);
|
||||
d_storesl(&ptr, map->pmPartBlkCnt);
|
||||
|
||||
memset(ptr, 0, 32);
|
||||
strncpy((char *) ptr, (const char *) map->pmPartName, 32);
|
||||
ptr += 32;
|
||||
|
||||
memset(ptr, 0, 32);
|
||||
strncpy((char *) ptr, (const char *) map->pmParType, 32);
|
||||
ptr += 32;
|
||||
|
||||
d_storesl(&ptr, map->pmLgDataStart);
|
||||
d_storesl(&ptr, map->pmDataCnt);
|
||||
d_storesl(&ptr, map->pmPartStatus);
|
||||
d_storesl(&ptr, map->pmLgBootStart);
|
||||
d_storesl(&ptr, map->pmBootSize);
|
||||
d_storesl(&ptr, map->pmBootAddr);
|
||||
d_storesl(&ptr, map->pmBootAddr2);
|
||||
d_storesl(&ptr, map->pmBootEntry);
|
||||
d_storesl(&ptr, map->pmBootEntry2);
|
||||
d_storesl(&ptr, map->pmBootCksum);
|
||||
|
||||
memset(ptr, 0, 16);
|
||||
strncpy((char *) ptr, (const char *) map->pmProcessor, 16);
|
||||
ptr += 16;
|
||||
|
||||
for (i = 0; i < 188; ++i)
|
||||
d_storesw(&ptr, map->pmPad[i]);
|
||||
|
||||
ASSERT(ptr - b == HFS_BLOCKSZ);
|
||||
|
||||
if (b_writepb(vol, bnum, &b, 1) == -1)
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: low->getbb()
|
||||
* DESCRIPTION: read a volume's boot blocks
|
||||
*/
|
||||
int l_getbb(hfsvol *vol, BootBlkHdr *bb, byte *bootcode)
|
||||
{
|
||||
block b;
|
||||
const byte *ptr = b;
|
||||
|
||||
if (b_readlb(vol, 0, &b) == -1)
|
||||
goto fail;
|
||||
|
||||
d_fetchsw(&ptr, &bb->bbID);
|
||||
d_fetchsl(&ptr, &bb->bbEntry);
|
||||
d_fetchsw(&ptr, &bb->bbVersion);
|
||||
d_fetchsw(&ptr, &bb->bbPageFlags);
|
||||
|
||||
d_fetchstr(&ptr, bb->bbSysName, sizeof(bb->bbSysName));
|
||||
d_fetchstr(&ptr, bb->bbShellName, sizeof(bb->bbShellName));
|
||||
d_fetchstr(&ptr, bb->bbDbg1Name, sizeof(bb->bbDbg1Name));
|
||||
d_fetchstr(&ptr, bb->bbDbg2Name, sizeof(bb->bbDbg2Name));
|
||||
d_fetchstr(&ptr, bb->bbScreenName, sizeof(bb->bbScreenName));
|
||||
d_fetchstr(&ptr, bb->bbHelloName, sizeof(bb->bbHelloName));
|
||||
d_fetchstr(&ptr, bb->bbScrapName, sizeof(bb->bbScrapName));
|
||||
|
||||
d_fetchsw(&ptr, &bb->bbCntFCBs);
|
||||
d_fetchsw(&ptr, &bb->bbCntEvts);
|
||||
d_fetchsl(&ptr, &bb->bb128KSHeap);
|
||||
d_fetchsl(&ptr, &bb->bb256KSHeap);
|
||||
d_fetchsl(&ptr, &bb->bbSysHeapSize);
|
||||
d_fetchsw(&ptr, &bb->filler);
|
||||
d_fetchsl(&ptr, &bb->bbSysHeapExtra);
|
||||
d_fetchsl(&ptr, &bb->bbSysHeapFract);
|
||||
|
||||
ASSERT(ptr - b == 148);
|
||||
|
||||
if (bootcode)
|
||||
{
|
||||
memcpy(bootcode, ptr, HFS_BOOTCODE1LEN);
|
||||
|
||||
if (b_readlb(vol, 1, &b) == -1)
|
||||
goto fail;
|
||||
|
||||
memcpy(bootcode + HFS_BOOTCODE1LEN, b, HFS_BOOTCODE2LEN);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: low->putbb()
|
||||
* DESCRIPTION: write a volume's boot blocks
|
||||
*/
|
||||
int l_putbb(hfsvol *vol, const BootBlkHdr *bb, const byte *bootcode)
|
||||
{
|
||||
block b;
|
||||
byte *ptr = b;
|
||||
|
||||
d_storesw(&ptr, bb->bbID);
|
||||
d_storesl(&ptr, bb->bbEntry);
|
||||
d_storesw(&ptr, bb->bbVersion);
|
||||
d_storesw(&ptr, bb->bbPageFlags);
|
||||
|
||||
d_storestr(&ptr, bb->bbSysName, sizeof(bb->bbSysName));
|
||||
d_storestr(&ptr, bb->bbShellName, sizeof(bb->bbShellName));
|
||||
d_storestr(&ptr, bb->bbDbg1Name, sizeof(bb->bbDbg1Name));
|
||||
d_storestr(&ptr, bb->bbDbg2Name, sizeof(bb->bbDbg2Name));
|
||||
d_storestr(&ptr, bb->bbScreenName, sizeof(bb->bbScreenName));
|
||||
d_storestr(&ptr, bb->bbHelloName, sizeof(bb->bbHelloName));
|
||||
d_storestr(&ptr, bb->bbScrapName, sizeof(bb->bbScrapName));
|
||||
|
||||
d_storesw(&ptr, bb->bbCntFCBs);
|
||||
d_storesw(&ptr, bb->bbCntEvts);
|
||||
d_storesl(&ptr, bb->bb128KSHeap);
|
||||
d_storesl(&ptr, bb->bb256KSHeap);
|
||||
d_storesl(&ptr, bb->bbSysHeapSize);
|
||||
d_storesw(&ptr, bb->filler);
|
||||
d_storesl(&ptr, bb->bbSysHeapExtra);
|
||||
d_storesl(&ptr, bb->bbSysHeapFract);
|
||||
|
||||
ASSERT(ptr - b == 148);
|
||||
|
||||
if (bootcode)
|
||||
memcpy(ptr, bootcode, HFS_BOOTCODE1LEN);
|
||||
else
|
||||
memset(ptr, 0, HFS_BOOTCODE1LEN);
|
||||
|
||||
if (b_writelb(vol, 0, &b) == -1)
|
||||
goto fail;
|
||||
|
||||
if (bootcode)
|
||||
memcpy(&b, bootcode + HFS_BOOTCODE1LEN, HFS_BOOTCODE2LEN);
|
||||
else
|
||||
memset(&b, 0, HFS_BOOTCODE2LEN);
|
||||
|
||||
if (b_writelb(vol, 1, &b) == -1)
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: low->getmdb()
|
||||
* DESCRIPTION: read a master directory block
|
||||
*/
|
||||
int l_getmdb(hfsvol *vol, MDB *mdb, int backup)
|
||||
{
|
||||
block b;
|
||||
const byte *ptr = b;
|
||||
int i;
|
||||
|
||||
if (b_readlb(vol, backup ? vol->vlen - 2 : 2, &b) == -1)
|
||||
goto fail;
|
||||
|
||||
d_fetchsw(&ptr, &mdb->drSigWord);
|
||||
d_fetchsl(&ptr, &mdb->drCrDate);
|
||||
d_fetchsl(&ptr, &mdb->drLsMod);
|
||||
d_fetchsw(&ptr, &mdb->drAtrb);
|
||||
d_fetchuw(&ptr, &mdb->drNmFls);
|
||||
d_fetchuw(&ptr, &mdb->drVBMSt);
|
||||
d_fetchuw(&ptr, &mdb->drAllocPtr);
|
||||
d_fetchuw(&ptr, &mdb->drNmAlBlks);
|
||||
d_fetchul(&ptr, &mdb->drAlBlkSiz);
|
||||
d_fetchul(&ptr, &mdb->drClpSiz);
|
||||
d_fetchuw(&ptr, &mdb->drAlBlSt);
|
||||
d_fetchsl(&ptr, &mdb->drNxtCNID);
|
||||
d_fetchuw(&ptr, &mdb->drFreeBks);
|
||||
|
||||
d_fetchstr(&ptr, mdb->drVN, sizeof(mdb->drVN));
|
||||
|
||||
ASSERT(ptr - b == 64);
|
||||
|
||||
d_fetchsl(&ptr, &mdb->drVolBkUp);
|
||||
d_fetchsw(&ptr, &mdb->drVSeqNum);
|
||||
d_fetchul(&ptr, &mdb->drWrCnt);
|
||||
d_fetchul(&ptr, &mdb->drXTClpSiz);
|
||||
d_fetchul(&ptr, &mdb->drCTClpSiz);
|
||||
d_fetchuw(&ptr, &mdb->drNmRtDirs);
|
||||
d_fetchul(&ptr, &mdb->drFilCnt);
|
||||
d_fetchul(&ptr, &mdb->drDirCnt);
|
||||
|
||||
for (i = 0; i < 8; ++i)
|
||||
d_fetchsl(&ptr, &mdb->drFndrInfo[i]);
|
||||
|
||||
ASSERT(ptr - b == 124);
|
||||
|
||||
d_fetchuw(&ptr, &mdb->drEmbedSigWord);
|
||||
d_fetchuw(&ptr, &mdb->drEmbedExtent.xdrStABN);
|
||||
d_fetchuw(&ptr, &mdb->drEmbedExtent.xdrNumABlks);
|
||||
|
||||
d_fetchul(&ptr, &mdb->drXTFlSize);
|
||||
|
||||
for (i = 0; i < 3; ++i)
|
||||
{
|
||||
d_fetchuw(&ptr, &mdb->drXTExtRec[i].xdrStABN);
|
||||
d_fetchuw(&ptr, &mdb->drXTExtRec[i].xdrNumABlks);
|
||||
}
|
||||
|
||||
ASSERT(ptr - b == 146);
|
||||
|
||||
d_fetchul(&ptr, &mdb->drCTFlSize);
|
||||
|
||||
for (i = 0; i < 3; ++i)
|
||||
{
|
||||
d_fetchuw(&ptr, &mdb->drCTExtRec[i].xdrStABN);
|
||||
d_fetchuw(&ptr, &mdb->drCTExtRec[i].xdrNumABlks);
|
||||
}
|
||||
|
||||
ASSERT(ptr - b == 162);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: low->putmdb()
|
||||
* DESCRIPTION: write master directory block(s)
|
||||
*/
|
||||
int l_putmdb(hfsvol *vol, const MDB *mdb, int backup)
|
||||
{
|
||||
block b;
|
||||
byte *ptr = b;
|
||||
int i;
|
||||
|
||||
d_storesw(&ptr, mdb->drSigWord);
|
||||
d_storesl(&ptr, mdb->drCrDate);
|
||||
d_storesl(&ptr, mdb->drLsMod);
|
||||
d_storesw(&ptr, mdb->drAtrb);
|
||||
d_storeuw(&ptr, mdb->drNmFls);
|
||||
d_storeuw(&ptr, mdb->drVBMSt);
|
||||
d_storeuw(&ptr, mdb->drAllocPtr);
|
||||
d_storeuw(&ptr, mdb->drNmAlBlks);
|
||||
d_storeul(&ptr, mdb->drAlBlkSiz);
|
||||
d_storeul(&ptr, mdb->drClpSiz);
|
||||
d_storeuw(&ptr, mdb->drAlBlSt);
|
||||
d_storesl(&ptr, mdb->drNxtCNID);
|
||||
d_storeuw(&ptr, mdb->drFreeBks);
|
||||
|
||||
d_storestr(&ptr, mdb->drVN, sizeof(mdb->drVN));
|
||||
|
||||
ASSERT(ptr - b == 64);
|
||||
|
||||
d_storesl(&ptr, mdb->drVolBkUp);
|
||||
d_storesw(&ptr, mdb->drVSeqNum);
|
||||
d_storeul(&ptr, mdb->drWrCnt);
|
||||
d_storeul(&ptr, mdb->drXTClpSiz);
|
||||
d_storeul(&ptr, mdb->drCTClpSiz);
|
||||
d_storeuw(&ptr, mdb->drNmRtDirs);
|
||||
d_storeul(&ptr, mdb->drFilCnt);
|
||||
d_storeul(&ptr, mdb->drDirCnt);
|
||||
|
||||
for (i = 0; i < 8; ++i)
|
||||
d_storesl(&ptr, mdb->drFndrInfo[i]);
|
||||
|
||||
ASSERT(ptr - b == 124);
|
||||
|
||||
d_storeuw(&ptr, mdb->drEmbedSigWord);
|
||||
d_storeuw(&ptr, mdb->drEmbedExtent.xdrStABN);
|
||||
d_storeuw(&ptr, mdb->drEmbedExtent.xdrNumABlks);
|
||||
|
||||
d_storeul(&ptr, mdb->drXTFlSize);
|
||||
|
||||
for (i = 0; i < 3; ++i)
|
||||
{
|
||||
d_storeuw(&ptr, mdb->drXTExtRec[i].xdrStABN);
|
||||
d_storeuw(&ptr, mdb->drXTExtRec[i].xdrNumABlks);
|
||||
}
|
||||
|
||||
ASSERT(ptr - b == 146);
|
||||
|
||||
d_storeul(&ptr, mdb->drCTFlSize);
|
||||
|
||||
for (i = 0; i < 3; ++i)
|
||||
{
|
||||
d_storeuw(&ptr, mdb->drCTExtRec[i].xdrStABN);
|
||||
d_storeuw(&ptr, mdb->drCTExtRec[i].xdrNumABlks);
|
||||
}
|
||||
|
||||
ASSERT(ptr - b == 162);
|
||||
|
||||
memset(ptr, 0, HFS_BLOCKSZ - (ptr - b));
|
||||
|
||||
if (b_writelb(vol, 2, &b) == -1 ||
|
||||
(backup && b_writelb(vol, vol->vlen - 2, &b) == -1))
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
44
libhfs/low.h
Normal file
44
libhfs/low.h
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
# define HFS_DDR_SIGWORD 0x4552
|
||||
|
||||
# define HFS_PM_SIGWORD 0x504d
|
||||
# define HFS_PM_SIGWORD_OLD 0x5453
|
||||
|
||||
# define HFS_BB_SIGWORD 0x4c4b
|
||||
|
||||
# define HFS_BOOTCODE1LEN (HFS_BLOCKSZ - 148)
|
||||
# define HFS_BOOTCODE2LEN HFS_BLOCKSZ
|
||||
|
||||
# define HFS_BOOTCODELEN (HFS_BOOTCODE1LEN + HFS_BOOTCODE2LEN)
|
||||
|
||||
int l_getddr(hfsvol *, Block0 *);
|
||||
int l_putddr(hfsvol *, const Block0 *);
|
||||
|
||||
int l_getpmentry(hfsvol *, Partition *, unsigned long);
|
||||
int l_putpmentry(hfsvol *, const Partition *, unsigned long);
|
||||
|
||||
int l_getbb(hfsvol *, BootBlkHdr *, byte *);
|
||||
int l_putbb(hfsvol *, const BootBlkHdr *, const byte *);
|
||||
|
||||
int l_getmdb(hfsvol *, MDB *, int);
|
||||
int l_putmdb(hfsvol *, const MDB *, int);
|
318
libhfs/medium.c
Normal file
318
libhfs/medium.c
Normal file
@ -0,0 +1,318 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
# ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
# endif
|
||||
|
||||
# include <stdlib.h>
|
||||
# include <string.h>
|
||||
# include <errno.h>
|
||||
|
||||
# include "libhfs.h"
|
||||
# include "block.h"
|
||||
# include "low.h"
|
||||
# include "medium.h"
|
||||
|
||||
/* Driver Descriptor Record Routines ======================================= */
|
||||
|
||||
/*
|
||||
* NAME: medium->zeroddr()
|
||||
* DESCRIPTION: write a new/empty driver descriptor record
|
||||
*/
|
||||
int m_zeroddr(hfsvol *vol)
|
||||
{
|
||||
Block0 ddr;
|
||||
int i;
|
||||
|
||||
ASSERT(vol->pnum == 0 && vol->vlen != 0);
|
||||
|
||||
ddr.sbSig = HFS_DDR_SIGWORD;
|
||||
ddr.sbBlkSize = HFS_BLOCKSZ;
|
||||
ddr.sbBlkCount = vol->vlen;
|
||||
|
||||
ddr.sbDevType = 0;
|
||||
ddr.sbDevId = 0;
|
||||
ddr.sbData = 0;
|
||||
|
||||
ddr.sbDrvrCount = 0;
|
||||
|
||||
ddr.ddBlock = 0;
|
||||
ddr.ddSize = 0;
|
||||
ddr.ddType = 0;
|
||||
|
||||
for (i = 0; i < 243; ++i)
|
||||
ddr.ddPad[i] = 0;
|
||||
|
||||
return l_putddr(vol, &ddr);
|
||||
}
|
||||
|
||||
/* Partition Map Routines ================================================== */
|
||||
|
||||
/*
|
||||
* NAME: medium->zeropm()
|
||||
* DESCRIPTION: write new/empty partition map
|
||||
*/
|
||||
int m_zeropm(hfsvol *vol, unsigned int maxparts)
|
||||
{
|
||||
Partition map;
|
||||
unsigned int i;
|
||||
|
||||
ASSERT(vol->pnum == 0 && vol->vlen != 0);
|
||||
|
||||
if (maxparts < 2)
|
||||
ERROR(EINVAL, "must allow at least 2 partitions");
|
||||
|
||||
/* first entry: partition map itself */
|
||||
|
||||
map.pmSig = HFS_PM_SIGWORD;
|
||||
map.pmSigPad = 0;
|
||||
map.pmMapBlkCnt = 2;
|
||||
|
||||
map.pmPyPartStart = 1;
|
||||
map.pmPartBlkCnt = maxparts;
|
||||
|
||||
strcpy((char *) map.pmPartName, "Apple");
|
||||
strcpy((char *) map.pmParType, "Apple_partition_map");
|
||||
|
||||
map.pmLgDataStart = 0;
|
||||
map.pmDataCnt = map.pmPartBlkCnt;
|
||||
|
||||
map.pmPartStatus = 0;
|
||||
|
||||
map.pmLgBootStart = 0;
|
||||
map.pmBootSize = 0;
|
||||
map.pmBootAddr = 0;
|
||||
map.pmBootAddr2 = 0;
|
||||
map.pmBootEntry = 0;
|
||||
map.pmBootEntry2 = 0;
|
||||
map.pmBootCksum = 0;
|
||||
|
||||
strcpy((char *) map.pmProcessor, "");
|
||||
|
||||
for (i = 0; i < 188; ++i)
|
||||
map.pmPad[i] = 0;
|
||||
|
||||
if (l_putpmentry(vol, &map, 1) == -1)
|
||||
goto fail;
|
||||
|
||||
/* second entry: rest of medium */
|
||||
|
||||
map.pmPyPartStart = 1 + maxparts;
|
||||
map.pmPartBlkCnt = vol->vlen - 1 - maxparts;
|
||||
|
||||
strcpy((char *) map.pmPartName, "Extra");
|
||||
strcpy((char *) map.pmParType, "Apple_Free");
|
||||
|
||||
map.pmDataCnt = map.pmPartBlkCnt;
|
||||
|
||||
if (l_putpmentry(vol, &map, 2) == -1)
|
||||
goto fail;
|
||||
|
||||
/* zero rest of partition map's partition */
|
||||
|
||||
if (maxparts > 2)
|
||||
{
|
||||
block b;
|
||||
|
||||
memset(&b, 0, sizeof(b));
|
||||
|
||||
for (i = 3; i <= maxparts; ++i)
|
||||
{
|
||||
if (b_writepb(vol, i, &b, 1) == -1)
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: medium->findpmentry()
|
||||
* DESCRIPTION: locate a partition map entry
|
||||
*/
|
||||
int m_findpmentry(hfsvol *vol, const char *type,
|
||||
Partition *map, unsigned long *start)
|
||||
{
|
||||
unsigned long bnum;
|
||||
int found = 0;
|
||||
|
||||
if (start && *start > 0)
|
||||
{
|
||||
bnum = *start;
|
||||
|
||||
if (bnum++ >= (unsigned long) map->pmMapBlkCnt)
|
||||
ERROR(EINVAL, "partition not found");
|
||||
}
|
||||
else
|
||||
bnum = 1;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (l_getpmentry(vol, map, bnum) == -1)
|
||||
{
|
||||
found = -1;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (map->pmSig != HFS_PM_SIGWORD)
|
||||
{
|
||||
found = -1;
|
||||
|
||||
if (map->pmSig == HFS_PM_SIGWORD_OLD)
|
||||
ERROR(EINVAL, "old partition map format not supported");
|
||||
else
|
||||
ERROR(EINVAL, "invalid partition map");
|
||||
}
|
||||
|
||||
if (strcmp((char *) map->pmParType, type) == 0)
|
||||
{
|
||||
found = 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (bnum++ >= (unsigned long) map->pmMapBlkCnt)
|
||||
ERROR(EINVAL, "partition not found");
|
||||
}
|
||||
|
||||
done:
|
||||
if (start)
|
||||
*start = bnum;
|
||||
|
||||
fail:
|
||||
return found;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: medium->mkpart()
|
||||
* DESCRIPTION: create a new partition from available free space
|
||||
*/
|
||||
int m_mkpart(hfsvol *vol,
|
||||
const char *name, const char *type, unsigned long len)
|
||||
{
|
||||
Partition map;
|
||||
unsigned int nparts, maxparts;
|
||||
unsigned long bnum, start, remain;
|
||||
int found;
|
||||
|
||||
if (strlen(name) > 32 ||
|
||||
strlen(type) > 32)
|
||||
ERROR(EINVAL, "partition name/type can each be at most 32 chars");
|
||||
|
||||
if (len == 0)
|
||||
ERROR(EINVAL, "partition length must be > 0");
|
||||
|
||||
found = m_findpmentry(vol, "Apple_partition_map", &map, 0);
|
||||
if (found == -1)
|
||||
goto fail;
|
||||
|
||||
if (! found)
|
||||
ERROR(EIO, "cannot find partition map's partition");
|
||||
|
||||
nparts = map.pmMapBlkCnt;
|
||||
maxparts = map.pmPartBlkCnt;
|
||||
|
||||
bnum = 0;
|
||||
do
|
||||
{
|
||||
found = m_findpmentry(vol, "Apple_Free", &map, &bnum);
|
||||
if (found == -1)
|
||||
goto fail;
|
||||
|
||||
if (! found)
|
||||
ERROR(ENOSPC, "no available partitions");
|
||||
}
|
||||
while (len > (unsigned long) map.pmPartBlkCnt);
|
||||
|
||||
start = (unsigned long) map.pmPyPartStart + len;
|
||||
remain = (unsigned long) map.pmPartBlkCnt - len;
|
||||
|
||||
if (remain && nparts >= maxparts)
|
||||
ERROR(EINVAL, "must allocate all blocks in free space");
|
||||
|
||||
map.pmPartBlkCnt = len;
|
||||
|
||||
strcpy((char *) map.pmPartName, name);
|
||||
strcpy((char *) map.pmParType, type);
|
||||
|
||||
map.pmLgDataStart = 0;
|
||||
map.pmDataCnt = len;
|
||||
|
||||
map.pmPartStatus = 0;
|
||||
|
||||
if (l_putpmentry(vol, &map, bnum) == -1)
|
||||
goto fail;
|
||||
|
||||
if (remain)
|
||||
{
|
||||
map.pmPyPartStart = start;
|
||||
map.pmPartBlkCnt = remain;
|
||||
|
||||
strcpy((char *) map.pmPartName, "Extra");
|
||||
strcpy((char *) map.pmParType, "Apple_Free");
|
||||
|
||||
map.pmDataCnt = remain;
|
||||
|
||||
if (l_putpmentry(vol, &map, ++nparts) == -1)
|
||||
goto fail;
|
||||
|
||||
for (bnum = 1; bnum <= nparts; ++bnum)
|
||||
{
|
||||
if (l_getpmentry(vol, &map, bnum) == -1)
|
||||
goto fail;
|
||||
|
||||
map.pmMapBlkCnt = nparts;
|
||||
|
||||
if (l_putpmentry(vol, &map, bnum) == -1)
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Boot Blocks Routines ==================================================== */
|
||||
|
||||
/*
|
||||
* NAME: medium->zerobb()
|
||||
* DESCRIPTION: write new/empty volume boot blocks
|
||||
*/
|
||||
int m_zerobb(hfsvol *vol)
|
||||
{
|
||||
block b;
|
||||
|
||||
memset(&b, 0, sizeof(b));
|
||||
|
||||
if (b_writelb(vol, 0, &b) == -1 ||
|
||||
b_writelb(vol, 1, &b) == -1)
|
||||
goto fail;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
42
libhfs/medium.h
Normal file
42
libhfs/medium.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
/*
|
||||
* Partition Types:
|
||||
*
|
||||
* "Apple_partition_map" partition map
|
||||
* "Apple_Driver" device driver
|
||||
* "Apple_Driver43" SCSI Manager 4.3 device driver
|
||||
* "Apple_MFS" Macintosh 64K ROM filesystem
|
||||
* "Apple_HFS" Macintosh hierarchical filesystem
|
||||
* "Apple_Unix_SVR2" Unix filesystem
|
||||
* "Apple_PRODOS" ProDOS filesystem
|
||||
* "Apple_Free" unused
|
||||
* "Apple_Scratch" empty
|
||||
*/
|
||||
|
||||
int m_zeroddr(hfsvol *);
|
||||
|
||||
int m_zeropm(hfsvol *, unsigned int);
|
||||
int m_findpmentry(hfsvol *, const char *, Partition *, unsigned long *);
|
||||
int m_mkpart(hfsvol *, const char *, const char *, unsigned long);
|
||||
|
||||
int m_zerobb(hfsvol *);
|
50
libhfs/memcmp.c
Normal file
50
libhfs/memcmp.c
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
# ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
# endif
|
||||
|
||||
# include <sys/types.h>
|
||||
|
||||
/*
|
||||
* NAME: memcmp()
|
||||
* DESCRIPTION: compare memory areas
|
||||
*/
|
||||
int memcmp(const void *s1, const void *s2, size_t n)
|
||||
{
|
||||
register const unsigned char *c1, *c2;
|
||||
|
||||
c1 = s1;
|
||||
c2 = s2;
|
||||
|
||||
while (n--)
|
||||
{
|
||||
register int diff;
|
||||
|
||||
diff = *c1++ - *c2++;
|
||||
|
||||
if (diff)
|
||||
return diff;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
473
libhfs/node.c
Normal file
473
libhfs/node.c
Normal file
@ -0,0 +1,473 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
# ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
# endif
|
||||
|
||||
# include <stdlib.h>
|
||||
# include <string.h>
|
||||
# include <errno.h>
|
||||
|
||||
# include "libhfs.h"
|
||||
# include "node.h"
|
||||
# include "data.h"
|
||||
# include "btree.h"
|
||||
|
||||
/* total bytes used by records (NOT including record offsets) */
|
||||
|
||||
# define NODEUSED(n) \
|
||||
((size_t) ((n).roff[(n).nd.ndNRecs] - (n).roff[0]))
|
||||
|
||||
/* total bytes available for new records (INCLUDING record offsets) */
|
||||
|
||||
# define NODEFREE(n) \
|
||||
((size_t) (HFS_BLOCKSZ - (n).roff[(n).nd.ndNRecs] - \
|
||||
2 * ((n).nd.ndNRecs + 1)))
|
||||
|
||||
/*
|
||||
* NAME: node->init()
|
||||
* DESCRIPTION: construct an empty node
|
||||
*/
|
||||
void n_init(node *np, btree *bt, int type, int height)
|
||||
{
|
||||
np->bt = bt;
|
||||
np->nnum = (unsigned long) -1;
|
||||
|
||||
np->nd.ndFLink = 0;
|
||||
np->nd.ndBLink = 0;
|
||||
np->nd.ndType = type;
|
||||
np->nd.ndNHeight = height;
|
||||
np->nd.ndNRecs = 0;
|
||||
np->nd.ndResv2 = 0;
|
||||
|
||||
np->rnum = -1;
|
||||
np->roff[0] = 0x00e;
|
||||
|
||||
memset(&np->data, 0, sizeof(np->data));
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: node->new()
|
||||
* DESCRIPTION: allocate a new b*-tree node
|
||||
*/
|
||||
int n_new(node *np)
|
||||
{
|
||||
btree *bt = np->bt;
|
||||
unsigned long num;
|
||||
|
||||
if (bt->hdr.bthFree == 0)
|
||||
ERROR(EIO, "b*-tree full");
|
||||
|
||||
num = 0;
|
||||
while (num < bt->hdr.bthNNodes && BMTST(bt->map, num))
|
||||
++num;
|
||||
|
||||
if (num == bt->hdr.bthNNodes)
|
||||
ERROR(EIO, "free b*-tree node not found");
|
||||
|
||||
np->nnum = num;
|
||||
|
||||
BMSET(bt->map, num);
|
||||
--bt->hdr.bthFree;
|
||||
|
||||
bt->flags |= HFS_BT_UPDATE_HDR;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: node->free()
|
||||
* DESCRIPTION: deallocate and remove a b*-tree node
|
||||
*/
|
||||
int n_free(node *np)
|
||||
{
|
||||
btree *bt = np->bt;
|
||||
node sib;
|
||||
|
||||
if (bt->hdr.bthFNode == np->nnum)
|
||||
bt->hdr.bthFNode = np->nd.ndFLink;
|
||||
|
||||
if (bt->hdr.bthLNode == np->nnum)
|
||||
bt->hdr.bthLNode = np->nd.ndBLink;
|
||||
|
||||
if (np->nd.ndFLink > 0)
|
||||
{
|
||||
if (bt_getnode(&sib, bt, np->nd.ndFLink) == -1)
|
||||
goto fail;
|
||||
|
||||
sib.nd.ndBLink = np->nd.ndBLink;
|
||||
|
||||
if (bt_putnode(&sib) == -1)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (np->nd.ndBLink > 0)
|
||||
{
|
||||
if (bt_getnode(&sib, bt, np->nd.ndBLink) == -1)
|
||||
goto fail;
|
||||
|
||||
sib.nd.ndFLink = np->nd.ndFLink;
|
||||
|
||||
if (bt_putnode(&sib) == -1)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
BMCLR(bt->map, np->nnum);
|
||||
++bt->hdr.bthFree;
|
||||
|
||||
bt->flags |= HFS_BT_UPDATE_HDR;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: compact()
|
||||
* DESCRIPTION: clean up a node, removing deleted records
|
||||
*/
|
||||
static
|
||||
void compact(node *np)
|
||||
{
|
||||
byte *ptr;
|
||||
int offset, nrecs, i;
|
||||
|
||||
offset = 0x00e;
|
||||
ptr = np->data + offset;
|
||||
nrecs = 0;
|
||||
|
||||
for (i = 0; i < np->nd.ndNRecs; ++i)
|
||||
{
|
||||
const byte *rec;
|
||||
int reclen;
|
||||
|
||||
rec = HFS_NODEREC(*np, i);
|
||||
reclen = HFS_RECLEN(*np, i);
|
||||
|
||||
if (HFS_RECKEYLEN(rec) > 0)
|
||||
{
|
||||
np->roff[nrecs++] = offset;
|
||||
offset += reclen;
|
||||
|
||||
if (ptr == rec)
|
||||
ptr += reclen;
|
||||
else
|
||||
{
|
||||
while (reclen--)
|
||||
*ptr++ = *rec++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
np->roff[nrecs] = offset;
|
||||
np->nd.ndNRecs = nrecs;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: node->search()
|
||||
* DESCRIPTION: locate a record in a node, or the record it should follow
|
||||
*/
|
||||
int n_search(node *np, const byte *pkey)
|
||||
{
|
||||
const btree *bt = np->bt;
|
||||
byte key1[HFS_MAX_KEYLEN], key2[HFS_MAX_KEYLEN];
|
||||
int i, comp = -1;
|
||||
|
||||
bt->keyunpack(pkey, key2);
|
||||
|
||||
for (i = np->nd.ndNRecs; i--; )
|
||||
{
|
||||
const byte *rec;
|
||||
|
||||
rec = HFS_NODEREC(*np, i);
|
||||
|
||||
if (HFS_RECKEYLEN(rec) == 0)
|
||||
continue; /* deleted record */
|
||||
|
||||
bt->keyunpack(rec, key1);
|
||||
comp = bt->keycompare(key1, key2);
|
||||
|
||||
if (comp <= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
np->rnum = i;
|
||||
|
||||
return comp == 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: node->index()
|
||||
* DESCRIPTION: create an index record from a key and node pointer
|
||||
*/
|
||||
void n_index(const node *np, byte *record, unsigned int *reclen)
|
||||
{
|
||||
const byte *key = HFS_NODEREC(*np, 0);
|
||||
|
||||
if (np->bt == &np->bt->f.vol->cat)
|
||||
{
|
||||
/* force the key length to be 0x25 */
|
||||
|
||||
HFS_SETKEYLEN(record, 0x25);
|
||||
memset(record + 1, 0, 0x25);
|
||||
memcpy(record + 1, key + 1, HFS_RECKEYLEN(key));
|
||||
}
|
||||
else
|
||||
memcpy(record, key, HFS_RECKEYSKIP(key));
|
||||
|
||||
d_putul(HFS_RECDATA(record), np->nnum);
|
||||
|
||||
if (reclen)
|
||||
*reclen = HFS_RECKEYSKIP(record) + 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: split()
|
||||
* DESCRIPTION: divide a node into two and insert a record
|
||||
*/
|
||||
static
|
||||
int split(node *left, byte *record, unsigned int *reclen)
|
||||
{
|
||||
btree *bt = left->bt;
|
||||
node n, *right = &n, *side = 0;
|
||||
int mark, i;
|
||||
|
||||
/* create a second node by cloning the first */
|
||||
|
||||
*right = *left;
|
||||
|
||||
if (n_new(right) == -1)
|
||||
goto fail;
|
||||
|
||||
left->nd.ndFLink = right->nnum;
|
||||
right->nd.ndBLink = left->nnum;
|
||||
|
||||
/* divide all records evenly between the two nodes */
|
||||
|
||||
mark = (NODEUSED(*left) + 2 * left->nd.ndNRecs + *reclen + 2) >> 1;
|
||||
|
||||
if (left->rnum == -1)
|
||||
{
|
||||
side = left;
|
||||
mark -= *reclen + 2;
|
||||
}
|
||||
|
||||
for (i = 0; i < left->nd.ndNRecs; ++i)
|
||||
{
|
||||
node *np;
|
||||
byte *rec;
|
||||
|
||||
np = (mark > 0) ? right : left;
|
||||
rec = HFS_NODEREC(*np, i);
|
||||
|
||||
mark -= HFS_RECLEN(*np, i) + 2;
|
||||
|
||||
HFS_SETKEYLEN(rec, 0);
|
||||
|
||||
if (left->rnum == i)
|
||||
{
|
||||
side = (mark > 0) ? left : right;
|
||||
mark -= *reclen + 2;
|
||||
}
|
||||
}
|
||||
|
||||
compact(left);
|
||||
compact(right);
|
||||
|
||||
/* insert the new record and store the modified nodes */
|
||||
|
||||
ASSERT(side);
|
||||
|
||||
n_search(side, record);
|
||||
n_insertx(side, record, *reclen);
|
||||
|
||||
if (bt_putnode(left) == -1 ||
|
||||
bt_putnode(right) == -1)
|
||||
goto fail;
|
||||
|
||||
/* create an index record in the parent for the new node */
|
||||
|
||||
n_index(right, record, reclen);
|
||||
|
||||
/* update link pointers */
|
||||
|
||||
if (bt->hdr.bthLNode == left->nnum)
|
||||
{
|
||||
bt->hdr.bthLNode = right->nnum;
|
||||
bt->flags |= HFS_BT_UPDATE_HDR;
|
||||
}
|
||||
|
||||
if (right->nd.ndFLink > 0)
|
||||
{
|
||||
node sib;
|
||||
|
||||
if (bt_getnode(&sib, right->bt, right->nd.ndFLink) == -1)
|
||||
goto fail;
|
||||
|
||||
sib.nd.ndBLink = right->nnum;
|
||||
|
||||
if (bt_putnode(&sib) == -1)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: node->insertx()
|
||||
* DESCRIPTION: insert a record into a node (which must already have room)
|
||||
*/
|
||||
void n_insertx(node *np, const byte *record, unsigned int reclen)
|
||||
{
|
||||
int rnum, i;
|
||||
byte *ptr;
|
||||
|
||||
rnum = np->rnum + 1;
|
||||
|
||||
/* push other records down to make room */
|
||||
|
||||
for (ptr = HFS_NODEREC(*np, np->nd.ndNRecs) + reclen;
|
||||
ptr > HFS_NODEREC(*np, rnum) + reclen; --ptr)
|
||||
*(ptr - 1) = *(ptr - 1 - reclen);
|
||||
|
||||
++np->nd.ndNRecs;
|
||||
|
||||
for (i = np->nd.ndNRecs; i > rnum; --i)
|
||||
np->roff[i] = np->roff[i - 1] + reclen;
|
||||
|
||||
/* write the new record */
|
||||
|
||||
memcpy(HFS_NODEREC(*np, rnum), record, reclen);
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: node->insert()
|
||||
* DESCRIPTION: insert a new record into a node; return a record for parent
|
||||
*/
|
||||
int n_insert(node *np, byte *record, unsigned int *reclen)
|
||||
{
|
||||
/* check for free space */
|
||||
|
||||
if (np->nd.ndNRecs >= HFS_MAX_NRECS ||
|
||||
*reclen + 2 > NODEFREE(*np))
|
||||
return split(np, record, reclen);
|
||||
|
||||
n_insertx(np, record, *reclen);
|
||||
*reclen = 0;
|
||||
|
||||
return bt_putnode(np);
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: join()
|
||||
* DESCRIPTION: combine two nodes into a single node
|
||||
*/
|
||||
static
|
||||
int join(node *left, node *right, byte *record, int *flag)
|
||||
{
|
||||
int i, offset;
|
||||
|
||||
/* copy records and offsets */
|
||||
|
||||
memcpy(HFS_NODEREC(*left, left->nd.ndNRecs),
|
||||
HFS_NODEREC(*right, 0), NODEUSED(*right));
|
||||
|
||||
offset = left->roff[left->nd.ndNRecs] - right->roff[0];
|
||||
|
||||
for (i = 1; i <= right->nd.ndNRecs; ++i)
|
||||
left->roff[++left->nd.ndNRecs] = offset + right->roff[i];
|
||||
|
||||
if (bt_putnode(left) == -1)
|
||||
goto fail;
|
||||
|
||||
/* eliminate node and update link pointers */
|
||||
|
||||
if (n_free(right) == -1)
|
||||
goto fail;
|
||||
|
||||
HFS_SETKEYLEN(record, 0);
|
||||
*flag = 1;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: node->delete()
|
||||
* DESCRIPTION: remove a record from a node
|
||||
*/
|
||||
int n_delete(node *np, byte *record, int *flag)
|
||||
{
|
||||
byte *rec;
|
||||
|
||||
rec = HFS_NODEREC(*np, np->rnum);
|
||||
|
||||
HFS_SETKEYLEN(rec, 0);
|
||||
compact(np);
|
||||
|
||||
if (np->nd.ndNRecs == 0)
|
||||
{
|
||||
if (n_free(np) == -1)
|
||||
goto fail;
|
||||
|
||||
HFS_SETKEYLEN(record, 0);
|
||||
*flag = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* see if we can join with our left sibling */
|
||||
|
||||
if (np->nd.ndBLink > 0)
|
||||
{
|
||||
node left;
|
||||
|
||||
if (bt_getnode(&left, np->bt, np->nd.ndBLink) == -1)
|
||||
goto fail;
|
||||
|
||||
if (np->nd.ndNRecs + left.nd.ndNRecs <= HFS_MAX_NRECS &&
|
||||
NODEUSED(*np) + 2 * np->nd.ndNRecs <= NODEFREE(left))
|
||||
return join(&left, np, record, flag);
|
||||
}
|
||||
|
||||
if (np->rnum == 0)
|
||||
{
|
||||
/* special case: first record changed; update parent record key */
|
||||
|
||||
n_index(np, record, 0);
|
||||
*flag = 1;
|
||||
}
|
||||
|
||||
return bt_putnode(np);
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
34
libhfs/node.h
Normal file
34
libhfs/node.h
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
void n_init(node *, btree *, int, int);
|
||||
|
||||
int n_new(node *);
|
||||
int n_free(node *);
|
||||
|
||||
int n_search(node *, const byte *);
|
||||
|
||||
void n_index(const node *, byte *, unsigned int *);
|
||||
|
||||
void n_insertx(node *, const byte *, unsigned int);
|
||||
int n_insert(node *, byte *, unsigned int *);
|
||||
|
||||
int n_delete(node *, byte *, int *);
|
182
libhfs/os.c
Normal file
182
libhfs/os.c
Normal file
@ -0,0 +1,182 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
# ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
# endif
|
||||
|
||||
# ifdef HAVE_FCNTL_H
|
||||
# include <fcntl.h>
|
||||
# else
|
||||
int open(const char *, int, ...);
|
||||
int fcntl(int, int, ...);
|
||||
# endif
|
||||
|
||||
# ifdef HAVE_UNISTD_H
|
||||
# include <unistd.h>
|
||||
# else
|
||||
#ifndef _WIN32
|
||||
int close(int);
|
||||
off_t lseek(int, off_t, int);
|
||||
ssize_t read(int, void *, size_t);
|
||||
ssize_t write(int, const char *, size_t);
|
||||
int stat(const char *, struct stat *);
|
||||
int fstat(int, struct stat *);
|
||||
#endif
|
||||
# endif
|
||||
|
||||
# include <errno.h>
|
||||
# include <sys/stat.h>
|
||||
# include <stdlib.h>
|
||||
# include <stdio.h> /* debug */
|
||||
|
||||
# include "libhfs.h"
|
||||
# include "os.h"
|
||||
|
||||
typedef struct cp_private {
|
||||
oscallback func; /* function to call */
|
||||
void* cookie; /* magic cookie to pass in */
|
||||
long cur_block; /* current seek offset */
|
||||
} cp_private;
|
||||
|
||||
/*
|
||||
* NAME: os->callback_open()
|
||||
* DESCRIPTION: open and lock a new descriptor from the given path and mode
|
||||
*/
|
||||
int os_callback_open(void **priv, oscallback func, void* cookie)
|
||||
{
|
||||
cp_private* mypriv;
|
||||
|
||||
mypriv = malloc(sizeof(*mypriv));
|
||||
mypriv->func = func;
|
||||
mypriv->cookie = cookie;
|
||||
mypriv->cur_block = 0;
|
||||
|
||||
*priv = mypriv;
|
||||
//fprintf(stderr, "ALLOC %p->%p\n", priv, *priv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: os->close()
|
||||
* DESCRIPTION: close an open descriptor
|
||||
*/
|
||||
int os_close(void **priv)
|
||||
{
|
||||
//fprintf(stderr, "FREEING %p->%p\n", priv, *priv);
|
||||
free(*priv);
|
||||
*priv = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CP_NOT_USED
|
||||
/*
|
||||
* NAME: os->same()
|
||||
* DESCRIPTION: return 1 iff path is same as the open descriptor
|
||||
*/
|
||||
int os_same(void **priv, const char *path)
|
||||
{
|
||||
return 0;
|
||||
|
||||
int fd = (int) *priv;
|
||||
struct stat fdev, dev;
|
||||
|
||||
if (fstat(fd, &fdev) == -1 ||
|
||||
stat(path, &dev) == -1)
|
||||
ERROR(errno, "can't get path information");
|
||||
|
||||
return fdev.st_dev == dev.st_dev &&
|
||||
fdev.st_ino == dev.st_ino;
|
||||
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* NAME: os->seek()
|
||||
* DESCRIPTION: set a descriptor's seek pointer (offset in blocks)
|
||||
*/
|
||||
unsigned long os_seek(void **priv, unsigned long offset)
|
||||
{
|
||||
cp_private* mypriv = (cp_private*) *priv;
|
||||
unsigned long result;
|
||||
|
||||
if (offset == (unsigned long) -1) {
|
||||
result = (*mypriv->func)(mypriv->cookie, HFS_CB_VOLSIZE, 0, 0);
|
||||
} else {
|
||||
result = (*mypriv->func)(mypriv->cookie, HFS_CB_SEEK, offset, 0);
|
||||
if (result != -1)
|
||||
mypriv->cur_block = offset;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: os->read()
|
||||
* DESCRIPTION: read blocks from an open descriptor
|
||||
*/
|
||||
unsigned long os_read(void **priv, void *buf, unsigned long len)
|
||||
{
|
||||
cp_private* mypriv = (cp_private*) *priv;
|
||||
unsigned long result;
|
||||
unsigned long success = 0;
|
||||
|
||||
while (len--) {
|
||||
result = (*mypriv->func)(mypriv->cookie, HFS_CB_READ,
|
||||
mypriv->cur_block, buf);
|
||||
if (result == -1)
|
||||
break;
|
||||
|
||||
mypriv->cur_block++;
|
||||
buf = ((unsigned char*) buf) + HFS_BLOCKSZ;
|
||||
success++;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: os->write()
|
||||
* DESCRIPTION: write blocks to an open descriptor
|
||||
*/
|
||||
unsigned long os_write(void **priv, const void *buf, unsigned long len)
|
||||
{
|
||||
cp_private* mypriv = (cp_private*) *priv;
|
||||
unsigned long result;
|
||||
unsigned long success = 0;
|
||||
|
||||
while (len--) {
|
||||
result = (*mypriv->func)(mypriv->cookie, HFS_CB_WRITE,
|
||||
mypriv->cur_block, (void*)buf);
|
||||
if (result == -1)
|
||||
break;
|
||||
|
||||
mypriv->cur_block++;
|
||||
buf = ((unsigned char*) buf) + HFS_BLOCKSZ;
|
||||
success++;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
34
libhfs/os.h
Normal file
34
libhfs/os.h
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
#ifdef CP_NOT_USED
|
||||
int os_open(void **, const char *, int);
|
||||
#endif
|
||||
int os_callback_open(void **priv, oscallback func, void* cookie);
|
||||
int os_close(void **);
|
||||
|
||||
#ifdef CP_NOT_USED
|
||||
int os_same(void **, const char *);
|
||||
#endif
|
||||
|
||||
unsigned long os_seek(void **, unsigned long);
|
||||
unsigned long os_read(void **, void *, unsigned long);
|
||||
unsigned long os_write(void **, const void *, unsigned long);
|
557
libhfs/record.c
Normal file
557
libhfs/record.c
Normal file
@ -0,0 +1,557 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
# ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
# endif
|
||||
|
||||
# include <string.h>
|
||||
|
||||
# include "libhfs.h"
|
||||
# include "record.h"
|
||||
# include "data.h"
|
||||
|
||||
/*
|
||||
* NAME: record->packcatkey()
|
||||
* DESCRIPTION: pack a catalog record key
|
||||
*/
|
||||
void r_packcatkey(const CatKeyRec *key, byte *pkey, unsigned int *len)
|
||||
{
|
||||
const byte *start = pkey;
|
||||
|
||||
d_storesb(&pkey, key->ckrKeyLen);
|
||||
d_storesb(&pkey, key->ckrResrv1);
|
||||
d_storeul(&pkey, key->ckrParID);
|
||||
|
||||
d_storestr(&pkey, key->ckrCName, sizeof(key->ckrCName));
|
||||
|
||||
if (len)
|
||||
*len = HFS_RECKEYSKIP(start);
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->unpackcatkey()
|
||||
* DESCRIPTION: unpack a catalog record key
|
||||
*/
|
||||
void r_unpackcatkey(const byte *pkey, CatKeyRec *key)
|
||||
{
|
||||
d_fetchsb(&pkey, &key->ckrKeyLen);
|
||||
d_fetchsb(&pkey, &key->ckrResrv1);
|
||||
d_fetchul(&pkey, &key->ckrParID);
|
||||
|
||||
d_fetchstr(&pkey, key->ckrCName, sizeof(key->ckrCName));
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->packextkey()
|
||||
* DESCRIPTION: pack an extents record key
|
||||
*/
|
||||
void r_packextkey(const ExtKeyRec *key, byte *pkey, unsigned int *len)
|
||||
{
|
||||
const byte *start = pkey;
|
||||
|
||||
d_storesb(&pkey, key->xkrKeyLen);
|
||||
d_storesb(&pkey, key->xkrFkType);
|
||||
d_storeul(&pkey, key->xkrFNum);
|
||||
d_storeuw(&pkey, key->xkrFABN);
|
||||
|
||||
if (len)
|
||||
*len = HFS_RECKEYSKIP(start);
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->unpackextkey()
|
||||
* DESCRIPTION: unpack an extents record key
|
||||
*/
|
||||
void r_unpackextkey(const byte *pkey, ExtKeyRec *key)
|
||||
{
|
||||
d_fetchsb(&pkey, &key->xkrKeyLen);
|
||||
d_fetchsb(&pkey, &key->xkrFkType);
|
||||
d_fetchul(&pkey, &key->xkrFNum);
|
||||
d_fetchuw(&pkey, &key->xkrFABN);
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->comparecatkeys()
|
||||
* DESCRIPTION: compare two (packed) catalog record keys
|
||||
*/
|
||||
int r_comparecatkeys(const CatKeyRec *key1, const CatKeyRec *key2)
|
||||
{
|
||||
int diff;
|
||||
|
||||
diff = key1->ckrParID - key2->ckrParID;
|
||||
if (diff)
|
||||
return diff;
|
||||
|
||||
return d_relstring(key1->ckrCName, key2->ckrCName);
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->compareextkeys()
|
||||
* DESCRIPTION: compare two (packed) extents record keys
|
||||
*/
|
||||
int r_compareextkeys(const ExtKeyRec *key1, const ExtKeyRec *key2)
|
||||
{
|
||||
int diff;
|
||||
|
||||
diff = key1->xkrFNum - key2->xkrFNum;
|
||||
if (diff)
|
||||
return diff;
|
||||
|
||||
diff = (unsigned char) key1->xkrFkType -
|
||||
(unsigned char) key2->xkrFkType;
|
||||
if (diff)
|
||||
return diff;
|
||||
|
||||
return key1->xkrFABN - key2->xkrFABN;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->packcatdata()
|
||||
* DESCRIPTION: pack catalog record data
|
||||
*/
|
||||
void r_packcatdata(const CatDataRec *data, byte *pdata, unsigned int *len)
|
||||
{
|
||||
const byte *start = pdata;
|
||||
int i;
|
||||
|
||||
d_storesb(&pdata, data->cdrType);
|
||||
d_storesb(&pdata, data->cdrResrv2);
|
||||
|
||||
switch (data->cdrType)
|
||||
{
|
||||
case cdrDirRec:
|
||||
d_storesw(&pdata, data->u.dir.dirFlags);
|
||||
d_storeuw(&pdata, data->u.dir.dirVal);
|
||||
d_storeul(&pdata, data->u.dir.dirDirID);
|
||||
d_storesl(&pdata, data->u.dir.dirCrDat);
|
||||
d_storesl(&pdata, data->u.dir.dirMdDat);
|
||||
d_storesl(&pdata, data->u.dir.dirBkDat);
|
||||
|
||||
d_storesw(&pdata, data->u.dir.dirUsrInfo.frRect.top);
|
||||
d_storesw(&pdata, data->u.dir.dirUsrInfo.frRect.left);
|
||||
d_storesw(&pdata, data->u.dir.dirUsrInfo.frRect.bottom);
|
||||
d_storesw(&pdata, data->u.dir.dirUsrInfo.frRect.right);
|
||||
d_storesw(&pdata, data->u.dir.dirUsrInfo.frFlags);
|
||||
d_storesw(&pdata, data->u.dir.dirUsrInfo.frLocation.v);
|
||||
d_storesw(&pdata, data->u.dir.dirUsrInfo.frLocation.h);
|
||||
d_storesw(&pdata, data->u.dir.dirUsrInfo.frView);
|
||||
|
||||
d_storesw(&pdata, data->u.dir.dirFndrInfo.frScroll.v);
|
||||
d_storesw(&pdata, data->u.dir.dirFndrInfo.frScroll.h);
|
||||
d_storesl(&pdata, data->u.dir.dirFndrInfo.frOpenChain);
|
||||
d_storesw(&pdata, data->u.dir.dirFndrInfo.frUnused);
|
||||
d_storesw(&pdata, data->u.dir.dirFndrInfo.frComment);
|
||||
d_storesl(&pdata, data->u.dir.dirFndrInfo.frPutAway);
|
||||
|
||||
for (i = 0; i < 4; ++i)
|
||||
d_storesl(&pdata, data->u.dir.dirResrv[i]);
|
||||
|
||||
break;
|
||||
|
||||
case cdrFilRec:
|
||||
d_storesb(&pdata, data->u.fil.filFlags);
|
||||
d_storesb(&pdata, data->u.fil.filTyp);
|
||||
|
||||
d_storesl(&pdata, data->u.fil.filUsrWds.fdType);
|
||||
d_storesl(&pdata, data->u.fil.filUsrWds.fdCreator);
|
||||
d_storesw(&pdata, data->u.fil.filUsrWds.fdFlags);
|
||||
d_storesw(&pdata, data->u.fil.filUsrWds.fdLocation.v);
|
||||
d_storesw(&pdata, data->u.fil.filUsrWds.fdLocation.h);
|
||||
d_storesw(&pdata, data->u.fil.filUsrWds.fdFldr);
|
||||
|
||||
d_storeul(&pdata, data->u.fil.filFlNum);
|
||||
|
||||
d_storeuw(&pdata, data->u.fil.filStBlk);
|
||||
d_storeul(&pdata, data->u.fil.filLgLen);
|
||||
d_storeul(&pdata, data->u.fil.filPyLen);
|
||||
|
||||
d_storeuw(&pdata, data->u.fil.filRStBlk);
|
||||
d_storeul(&pdata, data->u.fil.filRLgLen);
|
||||
d_storeul(&pdata, data->u.fil.filRPyLen);
|
||||
|
||||
d_storesl(&pdata, data->u.fil.filCrDat);
|
||||
d_storesl(&pdata, data->u.fil.filMdDat);
|
||||
d_storesl(&pdata, data->u.fil.filBkDat);
|
||||
|
||||
d_storesw(&pdata, data->u.fil.filFndrInfo.fdIconID);
|
||||
for (i = 0; i < 4; ++i)
|
||||
d_storesw(&pdata, data->u.fil.filFndrInfo.fdUnused[i]);
|
||||
d_storesw(&pdata, data->u.fil.filFndrInfo.fdComment);
|
||||
d_storesl(&pdata, data->u.fil.filFndrInfo.fdPutAway);
|
||||
|
||||
d_storeuw(&pdata, data->u.fil.filClpSize);
|
||||
|
||||
for (i = 0; i < 3; ++i)
|
||||
{
|
||||
d_storeuw(&pdata, data->u.fil.filExtRec[i].xdrStABN);
|
||||
d_storeuw(&pdata, data->u.fil.filExtRec[i].xdrNumABlks);
|
||||
}
|
||||
|
||||
for (i = 0; i < 3; ++i)
|
||||
{
|
||||
d_storeuw(&pdata, data->u.fil.filRExtRec[i].xdrStABN);
|
||||
d_storeuw(&pdata, data->u.fil.filRExtRec[i].xdrNumABlks);
|
||||
}
|
||||
|
||||
d_storesl(&pdata, data->u.fil.filResrv);
|
||||
|
||||
break;
|
||||
|
||||
case cdrThdRec:
|
||||
for (i = 0; i < 2; ++i)
|
||||
d_storesl(&pdata, data->u.dthd.thdResrv[i]);
|
||||
|
||||
d_storeul(&pdata, data->u.dthd.thdParID);
|
||||
|
||||
d_storestr(&pdata, data->u.dthd.thdCName,
|
||||
sizeof(data->u.dthd.thdCName));
|
||||
|
||||
break;
|
||||
|
||||
case cdrFThdRec:
|
||||
for (i = 0; i < 2; ++i)
|
||||
d_storesl(&pdata, data->u.fthd.fthdResrv[i]);
|
||||
|
||||
d_storeul(&pdata, data->u.fthd.fthdParID);
|
||||
|
||||
d_storestr(&pdata, data->u.fthd.fthdCName,
|
||||
sizeof(data->u.fthd.fthdCName));
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
ASSERT(0);
|
||||
}
|
||||
|
||||
if (len)
|
||||
*len += pdata - start;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->unpackcatdata()
|
||||
* DESCRIPTION: unpack catalog record data
|
||||
*/
|
||||
void r_unpackcatdata(const byte *pdata, CatDataRec *data)
|
||||
{
|
||||
int i;
|
||||
|
||||
d_fetchsb(&pdata, &data->cdrType);
|
||||
d_fetchsb(&pdata, &data->cdrResrv2);
|
||||
|
||||
switch (data->cdrType)
|
||||
{
|
||||
case cdrDirRec:
|
||||
d_fetchsw(&pdata, &data->u.dir.dirFlags);
|
||||
d_fetchuw(&pdata, &data->u.dir.dirVal);
|
||||
d_fetchul(&pdata, &data->u.dir.dirDirID);
|
||||
d_fetchsl(&pdata, &data->u.dir.dirCrDat);
|
||||
d_fetchsl(&pdata, &data->u.dir.dirMdDat);
|
||||
d_fetchsl(&pdata, &data->u.dir.dirBkDat);
|
||||
|
||||
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frRect.top);
|
||||
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frRect.left);
|
||||
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frRect.bottom);
|
||||
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frRect.right);
|
||||
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frFlags);
|
||||
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frLocation.v);
|
||||
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frLocation.h);
|
||||
d_fetchsw(&pdata, &data->u.dir.dirUsrInfo.frView);
|
||||
|
||||
d_fetchsw(&pdata, &data->u.dir.dirFndrInfo.frScroll.v);
|
||||
d_fetchsw(&pdata, &data->u.dir.dirFndrInfo.frScroll.h);
|
||||
d_fetchsl(&pdata, &data->u.dir.dirFndrInfo.frOpenChain);
|
||||
d_fetchsw(&pdata, &data->u.dir.dirFndrInfo.frUnused);
|
||||
d_fetchsw(&pdata, &data->u.dir.dirFndrInfo.frComment);
|
||||
d_fetchsl(&pdata, &data->u.dir.dirFndrInfo.frPutAway);
|
||||
|
||||
for (i = 0; i < 4; ++i)
|
||||
d_fetchsl(&pdata, &data->u.dir.dirResrv[i]);
|
||||
|
||||
break;
|
||||
|
||||
case cdrFilRec:
|
||||
d_fetchsb(&pdata, &data->u.fil.filFlags);
|
||||
d_fetchsb(&pdata, &data->u.fil.filTyp);
|
||||
|
||||
d_fetchsl(&pdata, &data->u.fil.filUsrWds.fdType);
|
||||
d_fetchsl(&pdata, &data->u.fil.filUsrWds.fdCreator);
|
||||
d_fetchsw(&pdata, &data->u.fil.filUsrWds.fdFlags);
|
||||
d_fetchsw(&pdata, &data->u.fil.filUsrWds.fdLocation.v);
|
||||
d_fetchsw(&pdata, &data->u.fil.filUsrWds.fdLocation.h);
|
||||
d_fetchsw(&pdata, &data->u.fil.filUsrWds.fdFldr);
|
||||
|
||||
d_fetchul(&pdata, &data->u.fil.filFlNum);
|
||||
|
||||
d_fetchuw(&pdata, &data->u.fil.filStBlk);
|
||||
d_fetchul(&pdata, &data->u.fil.filLgLen);
|
||||
d_fetchul(&pdata, &data->u.fil.filPyLen);
|
||||
|
||||
d_fetchuw(&pdata, &data->u.fil.filRStBlk);
|
||||
d_fetchul(&pdata, &data->u.fil.filRLgLen);
|
||||
d_fetchul(&pdata, &data->u.fil.filRPyLen);
|
||||
|
||||
d_fetchsl(&pdata, &data->u.fil.filCrDat);
|
||||
d_fetchsl(&pdata, &data->u.fil.filMdDat);
|
||||
d_fetchsl(&pdata, &data->u.fil.filBkDat);
|
||||
|
||||
d_fetchsw(&pdata, &data->u.fil.filFndrInfo.fdIconID);
|
||||
for (i = 0; i < 4; ++i)
|
||||
d_fetchsw(&pdata, &data->u.fil.filFndrInfo.fdUnused[i]);
|
||||
d_fetchsw(&pdata, &data->u.fil.filFndrInfo.fdComment);
|
||||
d_fetchsl(&pdata, &data->u.fil.filFndrInfo.fdPutAway);
|
||||
|
||||
d_fetchuw(&pdata, &data->u.fil.filClpSize);
|
||||
|
||||
for (i = 0; i < 3; ++i)
|
||||
{
|
||||
d_fetchuw(&pdata, &data->u.fil.filExtRec[i].xdrStABN);
|
||||
d_fetchuw(&pdata, &data->u.fil.filExtRec[i].xdrNumABlks);
|
||||
}
|
||||
|
||||
for (i = 0; i < 3; ++i)
|
||||
{
|
||||
d_fetchuw(&pdata, &data->u.fil.filRExtRec[i].xdrStABN);
|
||||
d_fetchuw(&pdata, &data->u.fil.filRExtRec[i].xdrNumABlks);
|
||||
}
|
||||
|
||||
d_fetchsl(&pdata, &data->u.fil.filResrv);
|
||||
|
||||
break;
|
||||
|
||||
case cdrThdRec:
|
||||
for (i = 0; i < 2; ++i)
|
||||
d_fetchsl(&pdata, &data->u.dthd.thdResrv[i]);
|
||||
|
||||
d_fetchul(&pdata, &data->u.dthd.thdParID);
|
||||
|
||||
d_fetchstr(&pdata, data->u.dthd.thdCName,
|
||||
sizeof(data->u.dthd.thdCName));
|
||||
|
||||
break;
|
||||
|
||||
case cdrFThdRec:
|
||||
for (i = 0; i < 2; ++i)
|
||||
d_fetchsl(&pdata, &data->u.fthd.fthdResrv[i]);
|
||||
|
||||
d_fetchul(&pdata, &data->u.fthd.fthdParID);
|
||||
|
||||
d_fetchstr(&pdata, data->u.fthd.fthdCName,
|
||||
sizeof(data->u.fthd.fthdCName));
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->packextdata()
|
||||
* DESCRIPTION: pack extent record data
|
||||
*/
|
||||
void r_packextdata(const ExtDataRec *data, byte *pdata, unsigned int *len)
|
||||
{
|
||||
const byte *start = pdata;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 3; ++i)
|
||||
{
|
||||
d_storeuw(&pdata, (*data)[i].xdrStABN);
|
||||
d_storeuw(&pdata, (*data)[i].xdrNumABlks);
|
||||
}
|
||||
|
||||
if (len)
|
||||
*len += pdata - start;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->unpackextdata()
|
||||
* DESCRIPTION: unpack extent record data
|
||||
*/
|
||||
void r_unpackextdata(const byte *pdata, ExtDataRec *data)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 3; ++i)
|
||||
{
|
||||
d_fetchuw(&pdata, &(*data)[i].xdrStABN);
|
||||
d_fetchuw(&pdata, &(*data)[i].xdrNumABlks);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->makecatkey()
|
||||
* DESCRIPTION: construct a catalog record key
|
||||
*/
|
||||
void r_makecatkey(CatKeyRec *key, unsigned long parid, const char *name)
|
||||
{
|
||||
int len;
|
||||
|
||||
len = strlen(name) + 1;
|
||||
|
||||
key->ckrKeyLen = 0x05 + len + (len & 1);
|
||||
key->ckrResrv1 = 0;
|
||||
key->ckrParID = parid;
|
||||
|
||||
strcpy(key->ckrCName, name);
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->makeextkey()
|
||||
* DESCRIPTION: construct an extents record key
|
||||
*/
|
||||
void r_makeextkey(ExtKeyRec *key,
|
||||
int fork, unsigned long fnum, unsigned int fabn)
|
||||
{
|
||||
key->xkrKeyLen = 0x07;
|
||||
key->xkrFkType = fork;
|
||||
key->xkrFNum = fnum;
|
||||
key->xkrFABN = fabn;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->packcatrec()
|
||||
* DESCRIPTION: create a packed catalog record
|
||||
*/
|
||||
void r_packcatrec(const CatKeyRec *key, const CatDataRec *data,
|
||||
byte *precord, unsigned int *len)
|
||||
{
|
||||
r_packcatkey(key, precord, len);
|
||||
r_packcatdata(data, HFS_RECDATA(precord), len);
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->packextrec()
|
||||
* DESCRIPTION: create a packed extents record
|
||||
*/
|
||||
void r_packextrec(const ExtKeyRec *key, const ExtDataRec *data,
|
||||
byte *precord, unsigned int *len)
|
||||
{
|
||||
r_packextkey(key, precord, len);
|
||||
r_packextdata(data, HFS_RECDATA(precord), len);
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->packdirent()
|
||||
* DESCRIPTION: make changes to a catalog record
|
||||
*/
|
||||
void r_packdirent(CatDataRec *data, const hfsdirent *ent)
|
||||
{
|
||||
switch (data->cdrType)
|
||||
{
|
||||
case cdrDirRec:
|
||||
data->u.dir.dirCrDat = d_mtime(ent->crdate);
|
||||
data->u.dir.dirMdDat = d_mtime(ent->mddate);
|
||||
data->u.dir.dirBkDat = d_mtime(ent->bkdate);
|
||||
|
||||
data->u.dir.dirUsrInfo.frFlags = ent->fdflags;
|
||||
data->u.dir.dirUsrInfo.frLocation.v = ent->fdlocation.v;
|
||||
data->u.dir.dirUsrInfo.frLocation.h = ent->fdlocation.h;
|
||||
|
||||
data->u.dir.dirUsrInfo.frRect.top = ent->u.dir.rect.top;
|
||||
data->u.dir.dirUsrInfo.frRect.left = ent->u.dir.rect.left;
|
||||
data->u.dir.dirUsrInfo.frRect.bottom = ent->u.dir.rect.bottom;
|
||||
data->u.dir.dirUsrInfo.frRect.right = ent->u.dir.rect.right;
|
||||
|
||||
break;
|
||||
|
||||
case cdrFilRec:
|
||||
if (ent->flags & HFS_ISLOCKED)
|
||||
data->u.fil.filFlags |= (1 << 0);
|
||||
else
|
||||
data->u.fil.filFlags &= ~(1 << 0);
|
||||
|
||||
data->u.fil.filCrDat = d_mtime(ent->crdate);
|
||||
data->u.fil.filMdDat = d_mtime(ent->mddate);
|
||||
data->u.fil.filBkDat = d_mtime(ent->bkdate);
|
||||
|
||||
data->u.fil.filUsrWds.fdFlags = ent->fdflags;
|
||||
data->u.fil.filUsrWds.fdLocation.v = ent->fdlocation.v;
|
||||
data->u.fil.filUsrWds.fdLocation.h = ent->fdlocation.h;
|
||||
|
||||
data->u.fil.filUsrWds.fdType =
|
||||
d_getsl((const unsigned char *) ent->u.file.type);
|
||||
data->u.fil.filUsrWds.fdCreator =
|
||||
d_getsl((const unsigned char *) ent->u.file.creator);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* NAME: record->unpackdirent()
|
||||
* DESCRIPTION: unpack catalog information into hfsdirent structure
|
||||
*/
|
||||
void r_unpackdirent(unsigned long parid, const char *name,
|
||||
const CatDataRec *data, hfsdirent *ent)
|
||||
{
|
||||
strcpy(ent->name, name);
|
||||
ent->parid = parid;
|
||||
|
||||
switch (data->cdrType)
|
||||
{
|
||||
case cdrDirRec:
|
||||
ent->flags = HFS_ISDIR;
|
||||
ent->cnid = data->u.dir.dirDirID;
|
||||
|
||||
ent->crdate = d_ltime(data->u.dir.dirCrDat);
|
||||
ent->mddate = d_ltime(data->u.dir.dirMdDat);
|
||||
ent->bkdate = d_ltime(data->u.dir.dirBkDat);
|
||||
|
||||
ent->fdflags = data->u.dir.dirUsrInfo.frFlags;
|
||||
ent->fdlocation.v = data->u.dir.dirUsrInfo.frLocation.v;
|
||||
ent->fdlocation.h = data->u.dir.dirUsrInfo.frLocation.h;
|
||||
|
||||
ent->u.dir.valence = data->u.dir.dirVal;
|
||||
|
||||
ent->u.dir.rect.top = data->u.dir.dirUsrInfo.frRect.top;
|
||||
ent->u.dir.rect.left = data->u.dir.dirUsrInfo.frRect.left;
|
||||
ent->u.dir.rect.bottom = data->u.dir.dirUsrInfo.frRect.bottom;
|
||||
ent->u.dir.rect.right = data->u.dir.dirUsrInfo.frRect.right;
|
||||
|
||||
break;
|
||||
|
||||
case cdrFilRec:
|
||||
ent->flags = (data->u.fil.filFlags & (1 << 0)) ? HFS_ISLOCKED : 0;
|
||||
ent->cnid = data->u.fil.filFlNum;
|
||||
|
||||
ent->crdate = d_ltime(data->u.fil.filCrDat);
|
||||
ent->mddate = d_ltime(data->u.fil.filMdDat);
|
||||
ent->bkdate = d_ltime(data->u.fil.filBkDat);
|
||||
|
||||
ent->fdflags = data->u.fil.filUsrWds.fdFlags;
|
||||
ent->fdlocation.v = data->u.fil.filUsrWds.fdLocation.v;
|
||||
ent->fdlocation.h = data->u.fil.filUsrWds.fdLocation.h;
|
||||
|
||||
ent->u.file.dsize = data->u.fil.filLgLen;
|
||||
ent->u.file.rsize = data->u.fil.filRLgLen;
|
||||
|
||||
d_putsl((unsigned char *) ent->u.file.type,
|
||||
data->u.fil.filUsrWds.fdType);
|
||||
d_putsl((unsigned char *) ent->u.file.creator,
|
||||
data->u.fil.filUsrWds.fdCreator);
|
||||
|
||||
ent->u.file.type[4] = ent->u.file.creator[4] = 0;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
47
libhfs/record.h
Normal file
47
libhfs/record.h
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* libhfs - library for reading and writing Macintosh HFS volumes
|
||||
* Copyright (C) 1996-1998 Robert Leslie
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
void r_packcatkey(const CatKeyRec *, byte *, unsigned int *);
|
||||
void r_unpackcatkey(const byte *, CatKeyRec *);
|
||||
|
||||
void r_packextkey(const ExtKeyRec *, byte *, unsigned int *);
|
||||
void r_unpackextkey(const byte *, ExtKeyRec *);
|
||||
|
||||
int r_comparecatkeys(const CatKeyRec *, const CatKeyRec *);
|
||||
int r_compareextkeys(const ExtKeyRec *, const ExtKeyRec *);
|
||||
|
||||
void r_packcatdata(const CatDataRec *, byte *, unsigned int *);
|
||||
void r_unpackcatdata(const byte *, CatDataRec *);
|
||||
|
||||
void r_packextdata(const ExtDataRec *, byte *, unsigned int *);
|
||||
void r_unpackextdata(const byte *, ExtDataRec *);
|
||||
|
||||
void r_makecatkey(CatKeyRec *, unsigned long, const char *);
|
||||
void r_makeextkey(ExtKeyRec *, int, unsigned long, unsigned int);
|
||||
|
||||
void r_packcatrec(const CatKeyRec *, const CatDataRec *,
|
||||
byte *, unsigned int *);
|
||||
void r_packextrec(const ExtKeyRec *, const ExtDataRec *,
|
||||
byte *, unsigned int *);
|
||||
|
||||
void r_packdirent(CatDataRec *, const hfsdirent *);
|
||||
void r_unpackdirent(unsigned long, const char *,
|
||||
const CatDataRec *, hfsdirent *);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user