diff --git a/.cvsignore b/.cvsignore
new file mode 100644
index 0000000..cd5dc09
--- /dev/null
+++ b/.cvsignore
@@ -0,0 +1,5 @@
+*~
+*#
+*.class
+.DS_Store
+Thumbs.db
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..24db480
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,176 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/buildall.bat b/buildall.bat
index 92b40bf..bf0104e 100644
--- a/buildall.bat
+++ b/buildall.bat
@@ -1,33 +1,28 @@
-@echo off
-:begin
-setlocal
-
-set SOURCES_DIR=src
-set CLASSFILES_DIR=build.~
-set LIBRARY_PATH=lib
-set JARFILE=dmgextractor.jar
-set MANIFEST=meta\metafile.txt
-
-pushd %~dp0
-echo Removing all class files...
-if not exist %CLASSFILES_DIR% mkdir %CLASSFILES_DIR%
-del /f /q %CLASSFILES_DIR%\*.*
-echo Incrementing build number...
-java -cp .\buildenumerator BuildEnumerator src\BuildNumber.java
-echo Compiling...
-javac -sourcepath %SOURCES_DIR% -d %CLASSFILES_DIR% -Xlint:unchecked %SOURCES_DIR%\*.java
-set JAVAC_EXIT_CODE=%ERRORLEVEL%
-if not "%JAVAC_EXIT_CODE%"=="0" goto error
-echo Building jar-file...
-if not exist %LIBRARY_PATH% mkdir %LIBRARY_PATH%
-jar cvfm %LIBRARY_PATH%\%JARFILE% %MANIFEST% -C %CLASSFILES_DIR% . >NUL:
-if "%ERRORLEVEL%"=="0" (echo Done!) else echo Problems while building jar-file...
-popd
-goto end
-
-:error
-echo There were errors...
-goto end
-
-:end
-endlocal
+@echo off
+:begin
+setlocal
+
+set SOURCES_DIR=%~dp0src
+set BUILDTOOLS_CP=%~dp0lib\buildenumerator.jar
+
+pushd %~dp0
+
+echo Incrementing build number...
+java -cp "%BUILDTOOLS_CP%" BuildEnumerator "%SOURCES_DIR%\org\catacombae\dmgextractor\BuildNumber.java" 1
+if not "%ERRORLEVEL%"=="0" goto error
+
+echo Building with ant...
+call ant build-application
+if "%ERRORLEVEL%"=="0" (echo Done!) else echo Problems while building with ant... && goto error
+
+popd
+goto end
+
+:error
+echo There were errors...
+echo Decrementing build number...
+java -cp "%BUILDTOOLS_CP%" BuildEnumerator "%SOURCES_DIR%\org\catacombae\dmgextractor\BuildNumber.java" -1
+goto end
+
+:end
+endlocal
diff --git a/buildall.sh b/buildall.sh
old mode 100644
new mode 100755
index 9e04556..6140484
--- a/buildall.sh
+++ b/buildall.sh
@@ -1,60 +1,45 @@
-#!/bin/sh
+#!/bin/bash
+
+SOURCES_DIR=src
+BUILDTOOLS_CP=lib/buildenumerator.jar
error() {
echo "There were errors..."
- echo "Decrementing build number..."
- java -cp $BUILDTOOLS_CP BuildEnumerator $SOURCES_DIR/org/catacombae/dmgx/BuildNumber.java -1
+ decrement_buildnumber
+ exit 1
+}
+jobCompleted() {
+ echo "Done!"
}
-SOURCES_DIR=src
-CLASSFILES_DIR=build.~
-LIBRARY_PATH=lib
-MANIFEST=meta/metafile.txt
-BUILD_CP=$CLASSFILES_DIR
-#:$LIBRARY_PATH/filedrop.jar
-BUILDTOOLS_CP=buildenumerator/buildenumerator.jar
-JARFILE=dmgextractor.jar
-
-if [ -d "$CLASSFILES_DIR" ]; then # if exists $CLASSFILES_DIR...
- echo "Removing all class files..."
- rm -r $CLASSFILES_DIR
-fi
-mkdir $CLASSFILES_DIR
-
-echo "Extracting swing-layout to classfiles directory..."
-cd $CLASSFILES_DIR
-jar xf "../$LIBRARY_PATH/swing-layout-1.0.1-stripped.jar"
-cd ..
+increment_buildnumber() {
+ echo "Incrementing build number..."
+ java -cp $BUILDTOOLS_CP BuildEnumerator $SOURCES_DIR/org/catacombae/dmgextractor/BuildNumber.java 1
+}
-echo "Extracting filedrop to classfiles directory..."
-cd $CLASSFILES_DIR
-jar xf "../$LIBRARY_PATH/filedrop.jar"
-cd ..
+decrement_buildnumber() {
+ echo "Decrementing build number..."
+ java -cp $BUILDTOOLS_CP BuildEnumerator $SOURCES_DIR/org/catacombae/dmgextractor/BuildNumber.java -1
+}
-#echo "Extracting base64 to classfiles directory..."
-#cd $CLASSFILES_DIR
-#jar xf "../$LIBRARY_PATH/base64.jar"
-#cd ..
+ant_build() {
+ ant build-application
+ return $?
+}
-echo "Incrementing build number..."
-java -cp $BUILDTOOLS_CP BuildEnumerator $SOURCES_DIR/org/catacombae/dmgx/BuildNumber.java 1
-echo "Compiling org.catacombae.dmgx..."
-javac -cp $BUILD_CP -sourcepath $SOURCES_DIR -d $CLASSFILES_DIR -Xlint:deprecation -Xlint:unchecked $SOURCES_DIR/org/catacombae/dmgx/*.java
-echo "Compiling org.catacombae.dmgx.gui..."
-javac -cp $BUILD_CP -sourcepath $SOURCES_DIR -d $CLASSFILES_DIR -Xlint:deprecation -Xlint:unchecked $SOURCES_DIR/org/catacombae/dmgx/gui/*.java
-JAVAC_EXIT_CODE=$?
-if [ "$JAVAC_EXIT_CODE" == 0 ]; then
- echo "Building jar-file..."
- if [ ! -d "$LIBRARY_PATH" ]; then # if not exists $LIBRARY_PATH...
- echo "Making library path"
- mkdir $LIBRARY_PATH
- fi
- jar cfm $LIBRARY_PATH/$JARFILE $MANIFEST -C $CLASSFILES_DIR .
+main() {
+ increment_buildnumber
if [ "$?" == 0 ]; then
- echo Done!
+ ant_build
+ if [ "$?" == 0 ]; then
+ jobCompleted
+ else
+ error
+ fi
else
error
fi
-else
- error
-fi
+}
+
+# Entry point
+main
diff --git a/buildenumerator/BuildEnumerator.java b/buildenumerator/BuildEnumerator.java
deleted file mode 100644
index 2570096..0000000
--- a/buildenumerator/BuildEnumerator.java
+++ /dev/null
@@ -1,26 +0,0 @@
-import java.io.*;
-
-public class BuildEnumerator {
- public static void main(String[] args) throws IOException {
- RandomAccessFile raf = new RandomAccessFile(args[0], "rw");
- String line1 = raf.readLine();
- String line2 = raf.readLine();
- long currentBuild = Long.parseLong(raf.readLine());
- String line4 = raf.readLine();
- String line5 = raf.readLine();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- PrintStream ps = new PrintStream(baos);
- ps.println(line1);
- ps.println(line2);
- ps.println(++currentBuild + "");
- System.err.println("Current build number: " + currentBuild);
- ps.println(line4);
- ps.println(line5);
- ps.flush();
- byte[] newBytes = baos.toByteArray();
- raf.setLength(newBytes.length);
- raf.seek(0);
- raf.write(newBytes);
- raf.close();
- }
-}
diff --git a/buildhfsxlib.bat b/buildhfsxlib.bat
new file mode 100644
index 0000000..82bcafe
--- /dev/null
+++ b/buildhfsxlib.bat
@@ -0,0 +1,21 @@
+@echo off
+:begin
+setlocal
+
+set SOURCES_DIR=%~dp0src
+
+pushd %~dp0
+
+echo Building with ant...
+call ant build-hfsxlib
+if "%ERRORLEVEL%"=="0" (echo Done!) else echo Problems while building with ant... && goto error
+
+popd
+goto end
+
+:error
+echo There were errors...
+goto end
+
+:end
+endlocal
diff --git a/buildhfsxlib.sh b/buildhfsxlib.sh
new file mode 100755
index 0000000..e96cfdb
--- /dev/null
+++ b/buildhfsxlib.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+SOURCES_DIR=src
+
+error() {
+ echo "There were errors..."
+ exit 1
+}
+jobCompleted() {
+ echo "Done!"
+}
+
+ant_build() {
+ ant build-hfsxlib
+ return $?
+}
+
+main() {
+ ant_build
+ if [ "$?" == 0 ]; then
+ jobCompleted
+ else
+ error
+ fi
+}
+
+# Entry point
+main
diff --git a/buildstandalone.bat b/buildstandalone.bat
new file mode 100644
index 0000000..cfd26cb
--- /dev/null
+++ b/buildstandalone.bat
@@ -0,0 +1,21 @@
+@echo off
+:begin
+setlocal
+
+set SOURCES_DIR=%~dp0src
+
+pushd %~dp0
+
+echo Building with ant...
+call ant build-standalone
+if "%ERRORLEVEL%"=="0" (echo Done!) else echo Problems while building with ant... && goto error
+
+popd
+goto end
+
+:error
+echo There were errors...
+goto end
+
+:end
+endlocal
diff --git a/buildstandalone.sh b/buildstandalone.sh
new file mode 100755
index 0000000..050fdbd
--- /dev/null
+++ b/buildstandalone.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+SOURCES_DIR=src
+
+error() {
+ echo "There were errors..."
+ exit 1
+}
+jobCompleted() {
+ echo "Done!"
+}
+
+ant_build() {
+ ant build-standalone
+ return $?
+}
+
+main() {
+ ant_build
+ if [ "$?" == 0 ]; then
+ jobCompleted
+ else
+ error
+ fi
+}
+
+# Entry point
+main
diff --git a/createjavadoc.bat b/createjavadoc.bat
new file mode 100644
index 0000000..9c3ecd6
--- /dev/null
+++ b/createjavadoc.bat
@@ -0,0 +1 @@
+@call ant javadoc
diff --git a/createjavadoc.sh b/createjavadoc.sh
new file mode 100755
index 0000000..5bcf5cf
--- /dev/null
+++ b/createjavadoc.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+ant javadoc
diff --git a/definevars.bat b/definevars.bat
new file mode 100644
index 0000000..2fbe27d
--- /dev/null
+++ b/definevars.bat
@@ -0,0 +1,3 @@
+set LIBDIR=%~dp0targets\application\lib
+
+set DMGX_CLASSPATH=%LIBDIR%\dmgextractor.jar
diff --git a/definevars.sh b/definevars.sh
new file mode 100755
index 0000000..fbb9385
--- /dev/null
+++ b/definevars.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+export LIBDIR="targets/application/lib"
+
+export DMGX_CLASSPATH="$LIBDIR/dmgextractor.jar"
+echo "$DMGX_CLASSPATH"
diff --git a/dmginfo.sh b/dmginfo.sh
old mode 100644
new mode 100755
index ea58931..9554da0
--- a/dmginfo.sh
+++ b/dmginfo.sh
@@ -1,2 +1,5 @@
#!/bin/sh
-java -cp lib/dmgextractor.jar org.catacombae.dmgx.DMGInfoWindow $1 $2 $3 $4 $5 $6 $7 $8 $9
+
+DMGX_CLASSPATH=`./definevars.sh`
+
+java -cp "$DMGX_CLASSPATH" org.catacombae.dmgx.DMGInfoWindow $1 $2 $3 $4 $5 $6 $7 $8 $9
diff --git a/dmgx.bat b/dmgx.bat
index a495b63..0e49be9 100644
--- a/dmgx.bat
+++ b/dmgx.bat
@@ -1,4 +1 @@
-@echo off
-pushd %~dp0
-java -cp lib\dmgextractor.jar DMGExtractor -startupcommand dmgx %1 %2 %3 %4 %5 %6 %7 %8 %9
-popd
+@call "%~dp0targets\application\bin\dmgx.bat" %1 %2 %3 %4 %5 %6 %7 %8 %9
diff --git a/dmgx.sh b/dmgx.sh
new file mode 100755
index 0000000..5c33c47
--- /dev/null
+++ b/dmgx.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+DMGX_CLASSPATH=`./definevars.sh`
+
+java -cp "$DMGX_CLASSPATH" org.catacombae.dmgextractor.DMGExtractor -startupcommand "$0" "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9"
diff --git a/doc/changelog.txt b/doc/changelog.txt
new file mode 100644
index 0000000..f6c604d
--- /dev/null
+++ b/doc/changelog.txt
@@ -0,0 +1,50 @@
+DMGExtractor changelog
+----------------------
+
+0.70 (build 437)
+----------------
+- Support for AES-128 encrypted .dmg images added after studying VileFault
+ ( http://crypto.nsa.org/vilefault/ ).
+- Program now suggests a sensible default output file.
+- User interaction slightly overhauled when restructuring the program. Should be more
+ informative and practical in many cases.
+- Program now accepts raw .dmg files, though a warning is issued telling the user that
+ essentially the program will only copy the file contents from one location to another.
+- Developers: The package org.catacombae.udif moved to org.catacombae.dmg.udif, and a new
+ package called org.catacombae.dmg.encrypted was created where the implementation of the
+ CEncryptedEncoding layer resides. Developers can easily read encrypted .dmg format files
+ with the class:
+ org.catacombae.dmg.encrypted.ReadableCEncryptedEncodingStream
+
+0.60 (build 386)
+----------------
+- Upgraded license to GPL version 3 in order to use the Apache bzip2 code which is released
+ under the Apache Software License Version 2.0, incompatible with GPLv2, but compatible
+ with GPLv3.
+- Support for bzip2-compressed images (type UDBZ) through the Apache Ant bzip2 library.
+- APX XML parser overhaul, reducing memory footprint and working with streams/readers
+ instead of arrays when getting results from the parser. This should result in an ability
+ to extract larger DMG files.
+- Made DMGExtractor startable through Java Web Start.
+- Restructured the app, separating the application and the library part. For developers
+ that wish to process UDIF disk images there are two simple classes that you should take a
+ look at:
+ org.catacombae.udif.UDIFRandomAccessStream (for random read access to UDIF disk images)
+ org.catacombae.udif.UDIFInputStream (for just reading the contents sequentially)
+
+0.51pre1 (build 226)
+--------------------
+- Switched XML-parser to a homebrew parser after getting frustrated with not being able
+ to turn off the SAX parser's inability to work in offline conditions, just because it
+ needed to contact www.apple.com every single time to get a DTD.
+ If my own XML parser bugs out, you can always supply the switch -saxparser to the command
+ line to use the SAX parser.
+- Worked further on compatibility issues... discovered a new block type which supposedly
+ also means zero fill... at least in the cases I tested. The new version should be more
+ compatible than the previous (I have no DMG images in my possession that it should handle
+ and yet doesn't).
+- Code cleanup started, but needs more work. It's a mess...
+
+0.5 (build 48)
+--------------
+- First release
diff --git a/extractplist.sh b/extractplist.sh
new file mode 100755
index 0000000..5e12762
--- /dev/null
+++ b/extractplist.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+DMGX_CLASSPATH=`./definevars.sh`
+
+java -cp "$DMGX_CLASSPATH" org.catacombae.dmgx.ExtractPlist $1 $2 $3 $4 $5 $6 $7 $8 $9
diff --git a/hfsx.bat b/hfsx.bat
deleted file mode 100644
index 8c8b93a..0000000
--- a/hfsx.bat
+++ /dev/null
@@ -1,4 +0,0 @@
-@echo off
-pushd %~dp0
-java -cp lib\dmgexplorer.jar HFSExplorer %1 %2 %3 %4 %5 %6 %7 %8 %9
-popd
diff --git a/lib/.cvsignore b/lib/.cvsignore
new file mode 100644
index 0000000..38d196e
--- /dev/null
+++ b/lib/.cvsignore
@@ -0,0 +1,4 @@
+*~
+*.class
+.DS_Store
+Thumbs.db
diff --git a/buildenumerator/buildenumerator.jar b/lib/buildenumerator.jar
similarity index 100%
rename from buildenumerator/buildenumerator.jar
rename to lib/buildenumerator.jar
diff --git a/lib/dmgextractor.jar b/lib/dmgextractor.jar
deleted file mode 100644
index d9da8e1..0000000
Binary files a/lib/dmgextractor.jar and /dev/null differ
diff --git a/lib/swing-layout-1.0.1-stripped.jar b/lib/swing-layout-1.0.1-stripped.jar
deleted file mode 100644
index 04bcad4..0000000
Binary files a/lib/swing-layout-1.0.1-stripped.jar and /dev/null differ
diff --git a/makebindist.sh b/makebindist.sh
new file mode 100755
index 0000000..05bb32f
--- /dev/null
+++ b/makebindist.sh
@@ -0,0 +1,99 @@
+#!/bin/sh
+
+match () {
+ local SUBFILE="$1"
+ local DIRNAME="$2"
+ CMPRES=`expr "${SUBFILE}" : "${DIRNAME}"`
+ RETVAL="$?"
+ if [ "${RETVAL}" -eq 0 ]; then
+ if [ -d "${SUBFILE}" ]; then
+ echo "`pwd`/${SUBFILE}/"
+ rm -r "${SUBFILE}"
+ if [ ! $? -eq 0 ]; then return 1; fi
+ else
+ echo "`pwd`/${SUBFILE}"
+ rm "${SUBFILE}"
+ if [ ! $? -eq 0 ]; then return 1; fi
+ fi
+ elif [ "${RETVAL}" -eq 1 ]; then
+ if [ -d "${SUBFILE}" ]; then
+ recursiveRmdir "${DIRNAME}" "${SUBFILE}"
+ if [ ! $? -eq 0 ]; then return 1; fi
+ fi
+ fi
+ return 0
+}
+
+recursiveRmdir () {
+ local DIRNAME="$1"
+ #local DIRNAMELEN="${#DIRNAME}"
+ shift 1
+ while [ ! $# -eq 0 ]; do
+ local FILE="$1"
+ shift 1
+
+ if [ -d "${FILE}" ]; then
+
+ pushd "${FILE}" > /dev/null
+
+ for SUBFILE in .*; do
+ if [ ! "${SUBFILE}" = ".." ] && [ ! "${SUBFILE}" = "." ]; then
+ match "${SUBFILE}" "${DIRNAME}"
+ if [ ! $? -eq 0 ]; then return 1; fi
+ fi
+ done
+
+ for SUBFILE in *; do
+ if [ ! "${SUBFILE}" = "*" ]; then
+ match "${SUBFILE}" "${DIRNAME}"
+ if [ ! $? -eq 0 ]; then return 1; fi
+ fi
+ done
+
+ popd > /dev/null
+ else
+ match "${FILE}" "${DIRNAME}"
+ if [ ! $? -eq 0 ]; then return 1; fi
+ fi
+ done
+ return 0
+}
+
+error () {
+ echo "There were errors..."
+ exit 1
+}
+
+checkerror () {
+ if [ ! $1 -eq 0 ]; then error; fi
+}
+
+TEMPDIR="disttemp.~"
+DISTDIR="targets/application"
+OUTFILE="releases/current-bin.zip"
+
+echo "Cleaning temp dir..."
+rm -r "${TEMPDIR}"
+mkdir "${TEMPDIR}"
+checkerror $?
+
+echo "Copying files..."
+cp -r ${DISTDIR}/* "${TEMPDIR}"
+checkerror $?
+
+echo "Setting execute permissions for shell scripts..."
+chmod a+x ${TEMPDIR}/bin/*.sh
+checkerror $?
+
+echo "Removing CVS directories..."
+recursiveRmdir "^CVS$" "${TEMPDIR}"
+checkerror $?
+
+echo "Building zip file..."
+cd "${TEMPDIR}"
+rm "../${OUTFILE}"
+zip -9 -r "../${OUTFILE}" *
+checkerror $?
+cd ..
+
+echo "Done! Zip file generated in ${OUTFILE}"
diff --git a/makesrcdist.sh b/makesrcdist.sh
new file mode 100755
index 0000000..197110d
--- /dev/null
+++ b/makesrcdist.sh
@@ -0,0 +1,130 @@
+#!/bin/bash
+
+match () {
+ local SUBFILE="$1"
+ local DIRNAME="$2"
+ CMPRES=`expr "${SUBFILE}" : "${DIRNAME}"`
+ RETVAL="$?"
+ if [ "${RETVAL}" -eq 0 ]; then
+ if [ -d "${SUBFILE}" ]; then
+ echo "`pwd`/${SUBFILE}/"
+ rm -r "${SUBFILE}"
+ if [ ! $? -eq 0 ]; then return 1; fi
+ else
+ echo "`pwd`/${SUBFILE}"
+ rm "${SUBFILE}"
+ if [ ! $? -eq 0 ]; then return 1; fi
+ fi
+ elif [ "${RETVAL}" -eq 1 ]; then
+ if [ -d "${SUBFILE}" ]; then
+ recursiveRmdir "${DIRNAME}" "${SUBFILE}"
+ if [ ! $? -eq 0 ]; then return 1; fi
+ fi
+ fi
+ return 0
+}
+
+recursiveRmdir () {
+ local DIRNAME="$1"
+ #local DIRNAMELEN="${#DIRNAME}"
+ shift 1
+ while [ ! $# -eq 0 ]; do
+ local FILE="$1"
+ shift 1
+
+ if [ -d "${FILE}" ]; then
+
+ pushd "${FILE}" > /dev/null
+
+ for SUBFILE in .*; do
+ if [ ! "${SUBFILE}" = ".." ] && [ ! "${SUBFILE}" = "." ]; then
+ match "${SUBFILE}" "${DIRNAME}"
+ if [ ! $? -eq 0 ]; then return 1; fi
+ fi
+ done
+
+ for SUBFILE in *; do
+ if [ ! "${SUBFILE}" = "*" ]; then
+ match "${SUBFILE}" "${DIRNAME}"
+ if [ ! $? -eq 0 ]; then return 1; fi
+ fi
+ done
+
+ popd > /dev/null
+ else
+ match "${FILE}" "${DIRNAME}"
+ if [ ! $? -eq 0 ]; then return 1; fi
+ fi
+ done
+ return 0
+}
+
+error () {
+ echo "There were errors..."
+ exit 1
+}
+
+checkerror () {
+ if [ ! $1 -eq 0 ]; then error; fi
+}
+
+TEMPDIR="srcdisttemp.~"
+OUTFILE="releases/current-src.zip"
+
+copydir () {
+ cp -r $1 "${TEMPDIR}"
+ checkerror $?
+}
+
+echo "Cleaning temp dir..."
+rm -r "${TEMPDIR}"
+mkdir "${TEMPDIR}"
+checkerror $?
+
+echo "Copying files..."
+cp *.sh ${TEMPDIR}
+checkerror $?
+cp *.bat ${TEMPDIR}
+checkerror $?
+cp *.xml ${TEMPDIR}
+checkerror $?
+
+copydir lib
+copydir src
+copydir src.JNLP-INF
+copydir src.META-INF
+copydir targets
+
+echo "Removing CVS directories..."
+recursiveRmdir "^CVS$" "${TEMPDIR}"
+checkerror $?
+echo "Removing emacs backup files (*~)..."
+recursiveRmdir ".*~$" "${TEMPDIR}"
+checkerror $?
+echo "Removing emacs temporary files (#*#)..."
+recursiveRmdir "^#.*#$" "${TEMPDIR}"
+checkerror $?
+echo "Removing Thumbs.db files..."
+recursiveRmdir "^Thumbs\.db$" "${TEMPDIR}"
+checkerror $?
+echo "Removing .DS_Store files..."
+recursiveRmdir "^\.DS_Store$" "${TEMPDIR}"
+checkerror $?
+echo "Removing .cvsignore files..."
+recursiveRmdir "^\.cvsignore$" "${TEMPDIR}"
+checkerror $?
+
+echo "Setting execute permissions for shell scripts..."
+chmod a+x ${TEMPDIR}/*.sh
+checkerror $?
+chmod a+x ${TEMPDIR}/targets/application/bin/*.sh
+checkerror $?
+
+echo "Building zip file..."
+cd "${TEMPDIR}"
+rm "../${OUTFILE}"
+zip -9 -r "../${OUTFILE}" *
+checkerror $?
+cd ..
+
+echo "Done! Zip file generated in ${OUTFILE}"
diff --git a/meta/metafile.txt b/meta/metafile.txt
deleted file mode 100644
index 9a57cee..0000000
--- a/meta/metafile.txt
+++ /dev/null
@@ -1 +0,0 @@
-Main-Class: DMGExtractorGraphical
diff --git a/src.JNLP-INF/standalone/dmgextractor.jnlp b/src.JNLP-INF/standalone/dmgextractor.jnlp
new file mode 100644
index 0000000..c99f216
--- /dev/null
+++ b/src.JNLP-INF/standalone/dmgextractor.jnlp
@@ -0,0 +1,24 @@
+
+
+
+
+ DMGExtractor
+ Catacombae Software
+ An application that extracts the contents of UDIF disk images, usually with file extension .dmg, to raw data (like .iso files).
+
+ Extracts the contents of .dmg files.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src.JNLP-INF/standalone/jnlp-notes.txt b/src.JNLP-INF/standalone/jnlp-notes.txt
new file mode 100644
index 0000000..beb2dcf
--- /dev/null
+++ b/src.JNLP-INF/standalone/jnlp-notes.txt
@@ -0,0 +1,4 @@
+För att få en jnlp-fil att köra med alla privilegier som ett vanligt Java-program har
+behöver man dels se till att en exakt kopia av filen ligger lagrad i huvud-JARen under
+JNLP-INF\APPLICATION.JNLP (uppercase är viktigt), och dels måste man signera JAR-filen.
+Har gjort ett litet script signjar.bat för det ändamålet.
diff --git a/src.META-INF/application/MANIFEST.MF b/src.META-INF/application/MANIFEST.MF
new file mode 100644
index 0000000..0b96277
--- /dev/null
+++ b/src.META-INF/application/MANIFEST.MF
@@ -0,0 +1,2 @@
+Main-Class: org.catacombae.dmgextractor.DMGExtractorGraphical
+Class-Path: csframework.jar apache-ant-1.7.0-bzip2.jar iharder-base64.jar swing-layout-1.0.3.jar
diff --git a/src.META-INF/standalone/MANIFEST.MF b/src.META-INF/standalone/MANIFEST.MF
new file mode 100644
index 0000000..e0767f7
--- /dev/null
+++ b/src.META-INF/standalone/MANIFEST.MF
@@ -0,0 +1 @@
+Main-Class: org.catacombae.dmgextractor.DMGExtractorGraphical
diff --git a/src/.cvsignore b/src/.cvsignore
new file mode 100644
index 0000000..cd5dc09
--- /dev/null
+++ b/src/.cvsignore
@@ -0,0 +1,5 @@
+*~
+*#
+*.class
+.DS_Store
+Thumbs.db
diff --git a/src/Base64.java b/src/Base64.java
deleted file mode 100644
index c6d79b6..0000000
--- a/src/Base64.java
+++ /dev/null
@@ -1,1449 +0,0 @@
-/**
- * Encodes and decodes to and from Base64 notation.
- *
- *
- * Change Log:
- *
- *
- *
v2.1 - Cleaned up javadoc comments and unused variables and methods. Added
- * some convenience methods for reading and writing to and from files.
- *
v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems
- * with other encodings (like EBCDIC).
- *
v2.0.1 - Fixed an error when decoding a single byte, that is, when the
- * encoded data was a single byte.
- *
v2.0 - I got rid of methods that used booleans to set options.
- * Now everything is more consolidated and cleaner. The code now detects
- * when data that's being decoded is gzip-compressed and will decompress it
- * automatically. Generally things are cleaner. You'll probably have to
- * change some method calls that you were making to support the new
- * options format (ints that you "OR" together).
- *
v1.5.1 - Fixed bug when decompressing and decoding to a
- * byte[] using decode( String s, boolean gzipCompressed ).
- * Added the ability to "suspend" encoding in the Output Stream so
- * you can turn on and off the encoding if you need to embed base64
- * data in an otherwise "normal" stream (like an XML file).
- *
v1.5 - Output stream pases on flush() command but doesn't do anything itself.
- * This helps when using GZIP streams.
- * Added the ability to GZip-compress objects before encoding them.
- *
v1.4 - Added helper methods to read/write files.
- *
v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
- *
v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream
- * where last buffer being read, if not completely full, was not returned.
- *
v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
- *
v1.3.3 - Fixed I/O streams which were totally messed up.
- *
- *
- *
- * I am placing this code in the Public Domain. Do with it as you will.
- * This software comes with no guarantees or warranties but with
- * plenty of well-wishing instead!
- * Please visit http://iharder.net/base64
- * periodically to check for updates or to contribute improvements.
- *
- *
- * @author Robert Harder
- * @author rob@iharder.net
- * @version 2.1
- */
-public class Base64
-{
-
-/* ******** P U B L I C F I E L D S ******** */
-
-
- /** No options specified. Value is zero. */
- public final static int NO_OPTIONS = 0;
-
- /** Specify encoding. */
- public final static int ENCODE = 1;
-
-
- /** Specify decoding. */
- public final static int DECODE = 0;
-
-
- /** Specify that data should be gzip-compressed. */
- public final static int GZIP = 2;
-
-
- /** Don't break lines when encoding (violates strict Base64 specification) */
- public final static int DONT_BREAK_LINES = 8;
-
-
-/* ******** P R I V A T E F I E L D S ******** */
-
-
- /** Maximum line length (76) of Base64 output. */
- private final static int MAX_LINE_LENGTH = 76;
-
-
- /** The equals sign (=) as a byte. */
- private final static byte EQUALS_SIGN = (byte)'=';
-
-
- /** The new line character (\n) as a byte. */
- private final static byte NEW_LINE = (byte)'\n';
-
-
- /** Preferred encoding. */
- private final static String PREFERRED_ENCODING = "UTF-8";
-
-
- /** The 64 valid Base64 values. */
- private final static byte[] ALPHABET;
- private final static byte[] _NATIVE_ALPHABET = /* May be something funny like EBCDIC */
- {
- (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
- (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
- (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
- (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
- (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
- (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
- (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
- (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
- (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
- (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/'
- };
-
- /** Determine which ALPHABET to use. */
- static
- {
- byte[] __bytes;
- try
- {
- __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes( PREFERRED_ENCODING );
- } // end try
- catch (java.io.UnsupportedEncodingException use)
- {
- __bytes = _NATIVE_ALPHABET; // Fall back to native encoding
- } // end catch
- ALPHABET = __bytes;
- } // end static
-
-
- /**
- * Translates a Base64 value to either its 6-bit reconstruction value
- * or a negative number indicating some other meaning.
- **/
- private final static byte[] DECODABET =
- {
- -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8
- -5,-5, // Whitespace: Tab and Linefeed
- -9,-9, // Decimal 11 - 12
- -5, // Whitespace: Carriage Return
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26
- -9,-9,-9,-9,-9, // Decimal 27 - 31
- -5, // Whitespace: Space
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42
- 62, // Plus sign at decimal 43
- -9,-9,-9, // Decimal 44 - 46
- 63, // Slash at decimal 47
- 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine
- -9,-9,-9, // Decimal 58 - 60
- -1, // Equals sign at decimal 61
- -9,-9,-9, // Decimal 62 - 64
- 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N'
- 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z'
- -9,-9,-9,-9,-9,-9, // Decimal 91 - 96
- 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm'
- 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z'
- -9,-9,-9,-9 // Decimal 123 - 126
- /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
- };
-
- // I think I end up not using the BAD_ENCODING indicator.
- //private final static byte BAD_ENCODING = -9; // Indicates error in encoding
- private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
- private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
-
-
- /** Defeats instantiation. */
- private Base64(){}
-
-
-
-/* ******** E N C O D I N G M E T H O D S ******** */
-
-
- /**
- * Encodes up to the first three bytes of array threeBytes
- * and returns a four-byte array in Base64 notation.
- * The actual number of significant bytes in your array is
- * given by numSigBytes.
- * The array threeBytes needs only be as big as
- * numSigBytes.
- * Code can reuse a byte array by passing a four-byte array as b4.
- *
- * @param b4 A reusable byte array to reduce array instantiation
- * @param threeBytes the array to convert
- * @param numSigBytes the number of significant bytes in your array
- * @return four byte array in Base64 notation.
- * @since 1.5.1
- */
- private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes )
- {
- encode3to4( threeBytes, 0, numSigBytes, b4, 0 );
- return b4;
- } // end encode3to4
-
-
- /**
- * Encodes up to three bytes of the array source
- * and writes the resulting four Base64 bytes to destination.
- * The source and destination arrays can be manipulated
- * anywhere along their length by specifying
- * srcOffset and destOffset.
- * This method does not check to make sure your arrays
- * are large enough to accomodate srcOffset + 3 for
- * the source array or destOffset + 4 for
- * the destination array.
- * The actual number of significant bytes in your array is
- * given by numSigBytes.
- *
- * @param source the array to convert
- * @param srcOffset the index where conversion begins
- * @param numSigBytes the number of significant bytes in your array
- * @param destination the array to hold the conversion
- * @param destOffset the index where output will be put
- * @return the destination array
- * @since 1.3
- */
- private static byte[] encode3to4(
- byte[] source, int srcOffset, int numSigBytes,
- byte[] destination, int destOffset )
- {
- // 1 2 3
- // 01234567890123456789012345678901 Bit position
- // --------000000001111111122222222 Array position from threeBytes
- // --------| || || || | Six bit groups to index ALPHABET
- // >>18 >>12 >> 6 >> 0 Right shift necessary
- // 0x3f 0x3f 0x3f Additional AND
-
- // Create buffer with zero-padding if there are only one or two
- // significant bytes passed in the array.
- // We have to shift left 24 in order to flush out the 1's that appear
- // when Java treats a value as negative that is cast from a byte to an int.
- int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 )
- | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 )
- | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 );
-
- switch( numSigBytes )
- {
- case 3:
- destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
- destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
- destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ];
- destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ];
- return destination;
-
- case 2:
- destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
- destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
- destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ];
- destination[ destOffset + 3 ] = EQUALS_SIGN;
- return destination;
-
- case 1:
- destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
- destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
- destination[ destOffset + 2 ] = EQUALS_SIGN;
- destination[ destOffset + 3 ] = EQUALS_SIGN;
- return destination;
-
- default:
- return destination;
- } // end switch
- } // end encode3to4
-
-
-
- /**
- * Serializes an object and returns the Base64-encoded
- * version of that serialized object. If the object
- * cannot be serialized or there is another error,
- * the method will return null.
- * The object is not GZip-compressed before being encoded.
- *
- * @param serializableObject The object to encode
- * @return The Base64-encoded object
- * @since 1.4
- */
- public static String encodeObject( java.io.Serializable serializableObject )
- {
- return encodeObject( serializableObject, NO_OPTIONS );
- } // end encodeObject
-
-
-
- /**
- * Serializes an object and returns the Base64-encoded
- * version of that serialized object. If the object
- * cannot be serialized or there is another error,
- * the method will return null.
- *
- * Valid options:
- * GZIP: gzip-compresses object before encoding it.
- * DONT_BREAK_LINES: don't break lines at 76 characters
- * Note: Technically, this makes your encoding non-compliant.
- *
- *
- * Example: encodeObject( myObj, Base64.GZIP ) or
- *
- * Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES )
- *
- * @param serializableObject The object to encode
- * @param options Specified options
- * @return The Base64-encoded object
- * @see Base64#GZIP
- * @see Base64#DONT_BREAK_LINES
- * @since 2.0
- */
- public static String encodeObject( java.io.Serializable serializableObject, int options )
- {
- // Streams
- java.io.ByteArrayOutputStream baos = null;
- java.io.OutputStream b64os = null;
- java.io.ObjectOutputStream oos = null;
- java.util.zip.GZIPOutputStream gzos = null;
-
- // Isolate options
- int gzip = (options & GZIP);
- int dontBreakLines = (options & DONT_BREAK_LINES);
-
- try
- {
- // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
- baos = new java.io.ByteArrayOutputStream();
- b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines );
-
- // GZip?
- if( gzip == GZIP )
- {
- gzos = new java.util.zip.GZIPOutputStream( b64os );
- oos = new java.io.ObjectOutputStream( gzos );
- } // end if: gzip
- else
- oos = new java.io.ObjectOutputStream( b64os );
-
- oos.writeObject( serializableObject );
- } // end try
- catch( java.io.IOException e )
- {
- e.printStackTrace();
- return null;
- } // end catch
- finally
- {
- try{ oos.close(); } catch( Exception e ){}
- try{ gzos.close(); } catch( Exception e ){}
- try{ b64os.close(); } catch( Exception e ){}
- try{ baos.close(); } catch( Exception e ){}
- } // end finally
-
- // Return value according to relevant encoding.
- try
- {
- return new String( baos.toByteArray(), PREFERRED_ENCODING );
- } // end try
- catch (java.io.UnsupportedEncodingException uue)
- {
- return new String( baos.toByteArray() );
- } // end catch
-
- } // end encode
-
-
-
- /**
- * Encodes a byte array into Base64 notation.
- * Does not GZip-compress data.
- *
- * @param source The data to convert
- * @since 1.4
- */
- public static String encodeBytes( byte[] source )
- {
- return encodeBytes( source, 0, source.length, NO_OPTIONS );
- } // end encodeBytes
-
-
-
- /**
- * Encodes a byte array into Base64 notation.
- *
- * Valid options:
- * GZIP: gzip-compresses object before encoding it.
- * DONT_BREAK_LINES: don't break lines at 76 characters
- * Note: Technically, this makes your encoding non-compliant.
- *
- *
- * Example: encodeBytes( myData, Base64.GZIP ) or
- *
- * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )
- *
- *
- * @param source The data to convert
- * @param options Specified options
- * @see Base64#GZIP
- * @see Base64#DONT_BREAK_LINES
- * @since 2.0
- */
- public static String encodeBytes( byte[] source, int options )
- {
- return encodeBytes( source, 0, source.length, options );
- } // end encodeBytes
-
-
- /**
- * Encodes a byte array into Base64 notation.
- * Does not GZip-compress data.
- *
- * @param source The data to convert
- * @param off Offset in array where conversion should begin
- * @param len Length of data to convert
- * @since 1.4
- */
- public static String encodeBytes( byte[] source, int off, int len )
- {
- return encodeBytes( source, off, len, NO_OPTIONS );
- } // end encodeBytes
-
-
-
- /**
- * Encodes a byte array into Base64 notation.
- *
- * Valid options:
- * GZIP: gzip-compresses object before encoding it.
- * DONT_BREAK_LINES: don't break lines at 76 characters
- * Note: Technically, this makes your encoding non-compliant.
- *
- *
- * Example: encodeBytes( myData, Base64.GZIP ) or
- *
- * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )
- *
- *
- * @param source The data to convert
- * @param off Offset in array where conversion should begin
- * @param len Length of data to convert
- * @param options Specified options
- * @see Base64#GZIP
- * @see Base64#DONT_BREAK_LINES
- * @since 2.0
- */
- public static String encodeBytes( byte[] source, int off, int len, int options )
- {
- // Isolate options
- int dontBreakLines = ( options & DONT_BREAK_LINES );
- int gzip = ( options & GZIP );
-
- // Compress?
- if( gzip == GZIP )
- {
- java.io.ByteArrayOutputStream baos = null;
- java.util.zip.GZIPOutputStream gzos = null;
- Base64.OutputStream b64os = null;
-
-
- try
- {
- // GZip -> Base64 -> ByteArray
- baos = new java.io.ByteArrayOutputStream();
- b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines );
- gzos = new java.util.zip.GZIPOutputStream( b64os );
-
- gzos.write( source, off, len );
- gzos.close();
- } // end try
- catch( java.io.IOException e )
- {
- e.printStackTrace();
- return null;
- } // end catch
- finally
- {
- try{ gzos.close(); } catch( Exception e ){}
- try{ b64os.close(); } catch( Exception e ){}
- try{ baos.close(); } catch( Exception e ){}
- } // end finally
-
- // Return value according to relevant encoding.
- try
- {
- return new String( baos.toByteArray(), PREFERRED_ENCODING );
- } // end try
- catch (java.io.UnsupportedEncodingException uue)
- {
- return new String( baos.toByteArray() );
- } // end catch
- } // end if: compress
-
- // Else, don't compress. Better not to use streams at all then.
- else
- {
- // Convert option to boolean in way that code likes it.
- boolean breakLines = dontBreakLines == 0;
-
- int len43 = len * 4 / 3;
- byte[] outBuff = new byte[ ( len43 ) // Main 4:3
- + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding
- + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines
- int d = 0;
- int e = 0;
- int len2 = len - 2;
- int lineLength = 0;
- for( ; d < len2; d+=3, e+=4 )
- {
- encode3to4( source, d+off, 3, outBuff, e );
-
- lineLength += 4;
- if( breakLines && lineLength == MAX_LINE_LENGTH )
- {
- outBuff[e+4] = NEW_LINE;
- e++;
- lineLength = 0;
- } // end if: end of line
- } // en dfor: each piece of array
-
- if( d < len )
- {
- encode3to4( source, d+off, len - d, outBuff, e );
- e += 4;
- } // end if: some padding needed
-
-
- // Return value according to relevant encoding.
- try
- {
- return new String( outBuff, 0, e, PREFERRED_ENCODING );
- } // end try
- catch (java.io.UnsupportedEncodingException uue)
- {
- return new String( outBuff, 0, e );
- } // end catch
-
- } // end else: don't compress
-
- } // end encodeBytes
-
-
-
-
-
-/* ******** D E C O D I N G M E T H O D S ******** */
-
-
- /**
- * Decodes four bytes from array source
- * and writes the resulting bytes (up to three of them)
- * to destination.
- * The source and destination arrays can be manipulated
- * anywhere along their length by specifying
- * srcOffset and destOffset.
- * This method does not check to make sure your arrays
- * are large enough to accomodate srcOffset + 4 for
- * the source array or destOffset + 3 for
- * the destination array.
- * This method returns the actual number of bytes that
- * were converted from the Base64 encoding.
- *
- *
- * @param source the array to convert
- * @param srcOffset the index where conversion begins
- * @param destination the array to hold the conversion
- * @param destOffset the index where output will be put
- * @return the number of decoded bytes converted
- * @since 1.3
- */
- private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset )
- {
- // Example: Dk==
- if( source[ srcOffset + 2] == EQUALS_SIGN )
- {
- // Two ways to do the same thing. Don't know which way I like best.
- //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
- // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
- int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
- | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 );
-
- destination[ destOffset ] = (byte)( outBuff >>> 16 );
- return 1;
- }
-
- // Example: DkL=
- else if( source[ srcOffset + 3 ] == EQUALS_SIGN )
- {
- // Two ways to do the same thing. Don't know which way I like best.
- //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
- // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
- // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
- int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
- | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
- | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 );
-
- destination[ destOffset ] = (byte)( outBuff >>> 16 );
- destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 );
- return 2;
- }
-
- // Example: DkLE
- else
- {
- try{
- // Two ways to do the same thing. Don't know which way I like best.
- //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
- // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
- // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
- // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
- int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
- | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
- | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6)
- | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) );
-
-
- destination[ destOffset ] = (byte)( outBuff >> 16 );
- destination[ destOffset + 1 ] = (byte)( outBuff >> 8 );
- destination[ destOffset + 2 ] = (byte)( outBuff );
-
- return 3;
- }catch( Exception e){
- System.out.println(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) );
- System.out.println(""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) );
- System.out.println(""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) );
- System.out.println(""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) );
- return -1;
- } //e nd catch
- }
- } // end decodeToBytes
-
-
-
-
- /**
- * Very low-level access to decoding ASCII characters in
- * the form of a byte array. Does not support automatically
- * gunzipping or any other "fancy" features.
- *
- * @param source The Base64 encoded data
- * @param off The offset of where to begin decoding
- * @param len The length of characters to decode
- * @return decoded data
- * @since 1.3
- */
- public static byte[] decode( byte[] source, int off, int len )
- {
- int len34 = len * 3 / 4;
- byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output
- int outBuffPosn = 0;
-
- byte[] b4 = new byte[4];
- int b4Posn = 0;
- int i = 0;
- byte sbiCrop = 0;
- byte sbiDecode = 0;
- for( i = off; i < off+len; i++ )
- {
- sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits
- sbiDecode = DECODABET[ sbiCrop ];
-
- if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better
- {
- if( sbiDecode >= EQUALS_SIGN_ENC )
- {
- b4[ b4Posn++ ] = sbiCrop;
- if( b4Posn > 3 )
- {
- outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn );
- b4Posn = 0;
-
- // If that was the equals sign, break out of 'for' loop
- if( sbiCrop == EQUALS_SIGN )
- break;
- } // end if: quartet built
-
- } // end if: equals sign or better
-
- } // end if: white space, equals sign or better
- else
- {
- System.err.println( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" );
- return null;
- } // end else:
- } // each input character
-
- byte[] out = new byte[ outBuffPosn ];
- System.arraycopy( outBuff, 0, out, 0, outBuffPosn );
- return out;
- } // end decode
-
-
-
-
- /**
- * Decodes data from Base64 notation, automatically
- * detecting gzip-compressed data and decompressing it.
- *
- * @param s the string to decode
- * @return the decoded data
- * @since 1.4
- */
- public static byte[] decode( String s )
- {
- byte[] bytes;
- try
- {
- bytes = s.getBytes( PREFERRED_ENCODING );
- } // end try
- catch( java.io.UnsupportedEncodingException uee )
- {
- bytes = s.getBytes();
- } // end catch
- //
-
- // Decode
- bytes = decode( bytes, 0, bytes.length );
-
-
- // Check to see if it's gzip-compressed
- // GZIP Magic Two-Byte Number: 0x8b1f (35615)
- if( bytes != null && bytes.length >= 4 )
- {
-
- int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
- if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head )
- {
- java.io.ByteArrayInputStream bais = null;
- java.util.zip.GZIPInputStream gzis = null;
- java.io.ByteArrayOutputStream baos = null;
- byte[] buffer = new byte[2048];
- int length = 0;
-
- try
- {
- baos = new java.io.ByteArrayOutputStream();
- bais = new java.io.ByteArrayInputStream( bytes );
- gzis = new java.util.zip.GZIPInputStream( bais );
-
- while( ( length = gzis.read( buffer ) ) >= 0 )
- {
- baos.write(buffer,0,length);
- } // end while: reading input
-
- // No error? Get new bytes.
- bytes = baos.toByteArray();
-
- } // end try
- catch( java.io.IOException e )
- {
- // Just return originally-decoded bytes
- } // end catch
- finally
- {
- try{ baos.close(); } catch( Exception e ){}
- try{ gzis.close(); } catch( Exception e ){}
- try{ bais.close(); } catch( Exception e ){}
- } // end finally
-
- } // end if: gzipped
- } // end if: bytes.length >= 2
-
- return bytes;
- } // end decode
-
-
-
-
- /**
- * Attempts to decode Base64 data and deserialize a Java
- * Object within. Returns null if there was an error.
- *
- * @param encodedObject The Base64 data to decode
- * @return The decoded and deserialized object
- * @since 1.5
- */
- public static Object decodeToObject( String encodedObject )
- {
- // Decode and gunzip if necessary
- byte[] objBytes = decode( encodedObject );
-
- java.io.ByteArrayInputStream bais = null;
- java.io.ObjectInputStream ois = null;
- Object obj = null;
-
- try
- {
- bais = new java.io.ByteArrayInputStream( objBytes );
- ois = new java.io.ObjectInputStream( bais );
-
- obj = ois.readObject();
- } // end try
- catch( java.io.IOException e )
- {
- e.printStackTrace();
- obj = null;
- } // end catch
- catch( java.lang.ClassNotFoundException e )
- {
- e.printStackTrace();
- obj = null;
- } // end catch
- finally
- {
- try{ bais.close(); } catch( Exception e ){}
- try{ ois.close(); } catch( Exception e ){}
- } // end finally
-
- return obj;
- } // end decodeObject
-
-
-
- /**
- * Convenience method for encoding data to a file.
- *
- * @param dataToEncode byte array of data to encode in base64 form
- * @param filename Filename for saving encoded data
- * @return true if successful, false otherwise
- *
- * @since 2.1
- */
- public static boolean encodeToFile( byte[] dataToEncode, String filename )
- {
- boolean success = false;
- Base64.OutputStream bos = null;
- try
- {
- bos = new Base64.OutputStream(
- new java.io.FileOutputStream( filename ), Base64.ENCODE );
- bos.write( dataToEncode );
- success = true;
- } // end try
- catch( java.io.IOException e )
- {
-
- success = false;
- } // end catch: IOException
- finally
- {
- try{ bos.close(); } catch( Exception e ){}
- } // end finally
-
- return success;
- } // end encodeToFile
-
-
- /**
- * Convenience method for decoding data to a file.
- *
- * @param dataToDecode Base64-encoded data as a string
- * @param filename Filename for saving decoded data
- * @return true if successful, false otherwise
- *
- * @since 2.1
- */
- public static boolean decodeToFile( String dataToDecode, String filename )
- {
- boolean success = false;
- Base64.OutputStream bos = null;
- try
- {
- bos = new Base64.OutputStream(
- new java.io.FileOutputStream( filename ), Base64.DECODE );
- bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) );
- success = true;
- } // end try
- catch( java.io.IOException e )
- {
- success = false;
- } // end catch: IOException
- finally
- {
- try{ bos.close(); } catch( Exception e ){}
- } // end finally
-
- return success;
- } // end decodeToFile
-
-
-
-
- /**
- * Convenience method for reading a base64-encoded
- * file and decoding it.
- *
- * @param filename Filename for reading encoded data
- * @return decoded byte array or null if unsuccessful
- *
- * @since 2.1
- */
- public static byte[] decodeFromFile( String filename )
- {
- byte[] decodedData = null;
- Base64.InputStream bis = null;
- try
- {
- // Set up some useful variables
- java.io.File file = new java.io.File( filename );
- byte[] buffer = null;
- int length = 0;
- int numBytes = 0;
-
- // Check for size of file
- if( file.length() > Integer.MAX_VALUE )
- {
- System.err.println( "File is too big for this convenience method (" + file.length() + " bytes)." );
- return null;
- } // end if: file too big for int index
- buffer = new byte[ (int)file.length() ];
-
- // Open a stream
- bis = new Base64.InputStream(
- new java.io.BufferedInputStream(
- new java.io.FileInputStream( file ) ), Base64.DECODE );
-
- // Read until done
- while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 )
- length += numBytes;
-
- // Save in a variable to return
- decodedData = new byte[ length ];
- System.arraycopy( buffer, 0, decodedData, 0, length );
-
- } // end try
- catch( java.io.IOException e )
- {
- System.err.println( "Error decoding from file " + filename );
- } // end catch: IOException
- finally
- {
- try{ bis.close(); } catch( Exception e) {}
- } // end finally
-
- return decodedData;
- } // end decodeFromFile
-
-
-
- /**
- * Convenience method for reading a binary file
- * and base64-encoding it.
- *
- * @param filename Filename for reading binary data
- * @return base64-encoded string or null if unsuccessful
- *
- * @since 2.1
- */
- public static String encodeFromFile( String filename )
- {
- String encodedData = null;
- Base64.InputStream bis = null;
- try
- {
- // Set up some useful variables
- java.io.File file = new java.io.File( filename );
- byte[] buffer = new byte[ (int)(file.length() * 1.4) ];
- int length = 0;
- int numBytes = 0;
-
- // Open a stream
- bis = new Base64.InputStream(
- new java.io.BufferedInputStream(
- new java.io.FileInputStream( file ) ), Base64.ENCODE );
-
- // Read until done
- while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 )
- length += numBytes;
-
- // Save in a variable to return
- encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING );
-
- } // end try
- catch( java.io.IOException e )
- {
- System.err.println( "Error encoding from file " + filename );
- } // end catch: IOException
- finally
- {
- try{ bis.close(); } catch( Exception e) {}
- } // end finally
-
- return encodedData;
- } // end encodeFromFile
-
-
-
-
- /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */
-
-
-
- /**
- * A {@link Base64.InputStream} will read data from another
- * java.io.InputStream, given in the constructor,
- * and encode/decode to/from Base64 notation on the fly.
- *
- * @see Base64
- * @since 1.3
- */
- public static class InputStream extends java.io.FilterInputStream
- {
- private boolean encode; // Encoding or decoding
- private int position; // Current position in the buffer
- private byte[] buffer; // Small buffer holding converted data
- private int bufferLength; // Length of buffer (3 or 4)
- private int numSigBytes; // Number of meaningful bytes in the buffer
- private int lineLength;
- private boolean breakLines; // Break lines at less than 80 characters
-
-
- /**
- * Constructs a {@link Base64.InputStream} in DECODE mode.
- *
- * @param in the java.io.InputStream from which to read data.
- * @since 1.3
- */
- public InputStream( java.io.InputStream in )
- {
- this( in, DECODE );
- } // end constructor
-
-
- /**
- * Constructs a {@link Base64.InputStream} in
- * either ENCODE or DECODE mode.
- *
- * Valid options:
- * ENCODE or DECODE: Encode or Decode as data is read.
- * DONT_BREAK_LINES: don't break lines at 76 characters
- * (only meaningful when encoding)
- * Note: Technically, this makes your encoding non-compliant.
- *
- *
- * Example: new Base64.InputStream( in, Base64.DECODE )
- *
- *
- * @param in the java.io.InputStream from which to read data.
- * @param options Specified options
- * @see Base64#ENCODE
- * @see Base64#DECODE
- * @see Base64#DONT_BREAK_LINES
- * @since 2.0
- */
- public InputStream( java.io.InputStream in, int options )
- {
- super( in );
- this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
- this.encode = (options & ENCODE) == ENCODE;
- this.bufferLength = encode ? 4 : 3;
- this.buffer = new byte[ bufferLength ];
- this.position = -1;
- this.lineLength = 0;
- } // end constructor
-
- /**
- * Reads enough of the input stream to convert
- * to/from Base64 and returns the next byte.
- *
- * @return next byte
- * @since 1.3
- */
- public int read() throws java.io.IOException
- {
- // Do we need to get data?
- if( position < 0 )
- {
- if( encode )
- {
- byte[] b3 = new byte[3];
- int numBinaryBytes = 0;
- for( int i = 0; i < 3; i++ )
- {
- try
- {
- int b = in.read();
-
- // If end of stream, b is -1.
- if( b >= 0 )
- {
- b3[i] = (byte)b;
- numBinaryBytes++;
- } // end if: not end of stream
-
- } // end try: read
- catch( java.io.IOException e )
- {
- // Only a problem if we got no data at all.
- if( i == 0 )
- throw e;
-
- } // end catch
- } // end for: each needed input byte
-
- if( numBinaryBytes > 0 )
- {
- encode3to4( b3, 0, numBinaryBytes, buffer, 0 );
- position = 0;
- numSigBytes = 4;
- } // end if: got data
- else
- {
- return -1;
- } // end else
- } // end if: encoding
-
- // Else decoding
- else
- {
- byte[] b4 = new byte[4];
- int i = 0;
- for( i = 0; i < 4; i++ )
- {
- // Read four "meaningful" bytes:
- int b = 0;
- do{ b = in.read(); }
- while( b >= 0 && DECODABET[ b & 0x7f ] <= WHITE_SPACE_ENC );
-
- if( b < 0 )
- break; // Reads a -1 if end of stream
-
- b4[i] = (byte)b;
- } // end for: each needed input byte
-
- if( i == 4 )
- {
- numSigBytes = decode4to3( b4, 0, buffer, 0 );
- position = 0;
- } // end if: got four characters
- else if( i == 0 ){
- return -1;
- } // end else if: also padded correctly
- else
- {
- // Must have broken out from above.
- throw new java.io.IOException( "Improperly padded Base64 input." );
- } // end
-
- } // end else: decode
- } // end else: get data
-
- // Got data?
- if( position >= 0 )
- {
- // End of relevant data?
- if( /*!encode &&*/ position >= numSigBytes )
- return -1;
-
- if( encode && breakLines && lineLength >= MAX_LINE_LENGTH )
- {
- lineLength = 0;
- return '\n';
- } // end if
- else
- {
- lineLength++; // This isn't important when decoding
- // but throwing an extra "if" seems
- // just as wasteful.
-
- int b = buffer[ position++ ];
-
- if( position >= bufferLength )
- position = -1;
-
- return b & 0xFF; // This is how you "cast" a byte that's
- // intended to be unsigned.
- } // end else
- } // end if: position >= 0
-
- // Else error
- else
- {
- // When JDK1.4 is more accepted, use an assertion here.
- throw new java.io.IOException( "Error in Base64 code reading stream." );
- } // end else
- } // end read
-
-
- /**
- * Calls {@link #read()} repeatedly until the end of stream
- * is reached or len bytes are read.
- * Returns number of bytes read into array or -1 if
- * end of stream is encountered.
- *
- * @param dest array to hold values
- * @param off offset for array
- * @param len max number of bytes to read into array
- * @return bytes read into array or -1 if end of stream is encountered.
- * @since 1.3
- */
- public int read( byte[] dest, int off, int len ) throws java.io.IOException
- {
- int i;
- int b;
- for( i = 0; i < len; i++ )
- {
- b = read();
-
- //if( b < 0 && i == 0 )
- // return -1;
-
- if( b >= 0 )
- dest[off + i] = (byte)b;
- else if( i == 0 )
- return -1;
- else
- break; // Out of 'for' loop
- } // end for: each byte read
- return i;
- } // end read
-
- } // end inner class InputStream
-
-
-
-
-
-
- /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */
-
-
-
- /**
- * A {@link Base64.OutputStream} will write data to another
- * java.io.OutputStream, given in the constructor,
- * and encode/decode to/from Base64 notation on the fly.
- *
- * @see Base64
- * @since 1.3
- */
- public static class OutputStream extends java.io.FilterOutputStream
- {
- private boolean encode;
- private int position;
- private byte[] buffer;
- private int bufferLength;
- private int lineLength;
- private boolean breakLines;
- private byte[] b4; // Scratch used in a few places
- private boolean suspendEncoding;
-
- /**
- * Constructs a {@link Base64.OutputStream} in ENCODE mode.
- *
- * @param out the java.io.OutputStream to which data will be written.
- * @since 1.3
- */
- public OutputStream( java.io.OutputStream out )
- {
- this( out, ENCODE );
- } // end constructor
-
-
- /**
- * Constructs a {@link Base64.OutputStream} in
- * either ENCODE or DECODE mode.
- *
- * Valid options:
- * ENCODE or DECODE: Encode or Decode as data is read.
- * DONT_BREAK_LINES: don't break lines at 76 characters
- * (only meaningful when encoding)
- * Note: Technically, this makes your encoding non-compliant.
- *
- *
- * Example: new Base64.OutputStream( out, Base64.ENCODE )
- *
- * @param out the java.io.OutputStream to which data will be written.
- * @param options Specified options.
- * @see Base64#ENCODE
- * @see Base64#DECODE
- * @see Base64#DONT_BREAK_LINES
- * @since 1.3
- */
- public OutputStream( java.io.OutputStream out, int options )
- {
- super( out );
- this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
- this.encode = (options & ENCODE) == ENCODE;
- this.bufferLength = encode ? 3 : 4;
- this.buffer = new byte[ bufferLength ];
- this.position = 0;
- this.lineLength = 0;
- this.suspendEncoding = false;
- this.b4 = new byte[4];
- } // end constructor
-
-
- /**
- * Writes the byte to the output stream after
- * converting to/from Base64 notation.
- * When encoding, bytes are buffered three
- * at a time before the output stream actually
- * gets a write() call.
- * When decoding, bytes are buffered four
- * at a time.
- *
- * @param theByte the byte to write
- * @since 1.3
- */
- public void write(int theByte) throws java.io.IOException
- {
- // Encoding suspended?
- if( suspendEncoding )
- {
- super.out.write( theByte );
- return;
- } // end if: supsended
-
- // Encode?
- if( encode )
- {
- buffer[ position++ ] = (byte)theByte;
- if( position >= bufferLength ) // Enough to encode.
- {
- out.write( encode3to4( b4, buffer, bufferLength ) );
-
- lineLength += 4;
- if( breakLines && lineLength >= MAX_LINE_LENGTH )
- {
- out.write( NEW_LINE );
- lineLength = 0;
- } // end if: end of line
-
- position = 0;
- } // end if: enough to output
- } // end if: encoding
-
- // Else, Decoding
- else
- {
- // Meaningful Base64 character?
- if( DECODABET[ theByte & 0x7f ] > WHITE_SPACE_ENC )
- {
- buffer[ position++ ] = (byte)theByte;
- if( position >= bufferLength ) // Enough to output.
- {
- int len = Base64.decode4to3( buffer, 0, b4, 0 );
- out.write( b4, 0, len );
- //out.write( Base64.decode4to3( buffer ) );
- position = 0;
- } // end if: enough to output
- } // end if: meaningful base64 character
- else if( DECODABET[ theByte & 0x7f ] != WHITE_SPACE_ENC )
- {
- throw new java.io.IOException( "Invalid character in Base64 data." );
- } // end else: not white space either
- } // end else: decoding
- } // end write
-
-
-
- /**
- * Calls {@link #write(int)} repeatedly until len
- * bytes are written.
- *
- * @param theBytes array from which to read bytes
- * @param off offset for array
- * @param len max number of bytes to read into array
- * @since 1.3
- */
- public void write( byte[] theBytes, int off, int len ) throws java.io.IOException
- {
- // Encoding suspended?
- if( suspendEncoding )
- {
- super.out.write( theBytes, off, len );
- return;
- } // end if: supsended
-
- for( int i = 0; i < len; i++ )
- {
- write( theBytes[ off + i ] );
- } // end for: each byte written
-
- } // end write
-
-
-
- /**
- * Method added by PHIL. [Thanks, PHIL. -Rob]
- * This pads the buffer without closing the stream.
- */
- public void flushBase64() throws java.io.IOException
- {
- if( position > 0 )
- {
- if( encode )
- {
- out.write( encode3to4( b4, buffer, position ) );
- position = 0;
- } // end if: encoding
- else
- {
- throw new java.io.IOException( "Base64 input not properly padded." );
- } // end else: decoding
- } // end if: buffer partially full
-
- } // end flush
-
-
- /**
- * Flushes and closes (I think, in the superclass) the stream.
- *
- * @since 1.3
- */
- public void close() throws java.io.IOException
- {
- // 1. Ensure that pending characters are written
- flushBase64();
-
- // 2. Actually close the stream
- // Base class both flushes and closes.
- super.close();
-
- buffer = null;
- out = null;
- } // end close
-
-
-
- /**
- * Suspends encoding of the stream.
- * May be helpful if you need to embed a piece of
- * base640-encoded data in a stream.
- *
- * @since 1.5.1
- */
- public void suspendEncoding() throws java.io.IOException
- {
- flushBase64();
- this.suspendEncoding = true;
- } // end suspendEncoding
-
-
- /**
- * Resumes encoding of the stream.
- * May be helpful if you need to embed a piece of
- * base640-encoded data in a stream.
- *
- * @since 1.5.1
- */
- public void resumeEncoding()
- {
- this.suspendEncoding = false;
- } // end resumeEncoding
-
-
-
- } // end inner class OutputStream
-
-
-} // end class Base64
diff --git a/src/DMGExtractor.java b/src/DMGExtractor.java
deleted file mode 100644
index c33dcd3..0000000
--- a/src/DMGExtractor.java
+++ /dev/null
@@ -1,826 +0,0 @@
-/*-
- * Copyright (C) 2006 Erik Larsson
- * (C) 2004 vu1tur (not the actual code but...)
- *
- * All rights reserved.
- *
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-import java.io.*;
-import java.util.LinkedList;
-import java.util.Iterator;
-import java.util.zip.Inflater;
-import java.util.zip.DataFormatException;
-import javax.xml.parsers.SAXParserFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.swing.JOptionPane;
-import javax.swing.JFileChooser;
-import javax.swing.ProgressMonitor;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-public class DMGExtractor {
- public static final String APPNAME = "DMGExtractor 0.51pre";
- public static final String BUILDSTRING = "(Build #" + BuildNumber.BUILD_NUMBER + ")";
- public static final boolean DEBUG = false;
- // Constants defining block types in the dmg file
- public static final int BT_ADC = 0x80000004;
- public static final int BT_ZLIB = 0x80000005;
- public static final int BT_BZIP2 = 0x80000006;
- public static final int BT_COPY = 0x00000001;
- public static final int BT_ZERO = 0x00000002;
- public static final int BT_END = 0xffffffff;
- public static final int BT_UNKNOWN = 0x7ffffffe;
- public static final long PLIST_ADDRESS_1 = 0x1E0;
- public static final long PLIST_ADDRESS_2 = 0x128;
- public static final String BACKSPACE79 = "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";
-// public static PrintStream stdout = System.out;
-// public static PrintStream stderr = System.err;
- public static BufferedReader stdin =
- new BufferedReader(new InputStreamReader(System.in));
-
- public static boolean verbose = false;
- public static boolean graphical = false;
- public static String startupCommand = "java DMGExtractor";
- public static File dmgFile = null;
- public static File isoFile = null;
-
- public static ProgressMonitor progmon;
-
- public static void main(String[] args) throws Exception {
- try {
- notmain(args);
- } catch(Exception e) {
- if(graphical)
- JOptionPane.showMessageDialog(null, "The program encountered an unexpected error: " + e.toString() +
- "\nClosing...", "Error", JOptionPane.ERROR_MESSAGE);
- throw e;
- }
- }
-
- public static void notmain(String[] args) throws Exception {
- System.setProperty("swing.aatext", "true"); //Antialiased text
- try { javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName()); }
- catch(Exception e) {}
-
- if(DEBUG) verbose = true;
-
- parseArgs(args);
-
- printlnVerbose("Processing: " + dmgFile);
- RandomAccessFile dmgRaf = new RandomAccessFile(dmgFile, "r");
- RandomAccessFile isoRaf = null;
- boolean testOnly = false;
- if(isoFile != null) {
- isoRaf = new RandomAccessFile(isoFile, "rw");
- isoRaf.setLength(0);
- printlnVerbose("Extracting to: " + isoFile);
- }
- else {
- testOnly = true;
- printlnVerbose("Simulating extraction...");
- }
-
- dmgRaf.seek(dmgRaf.length()-PLIST_ADDRESS_1);
- long plistBegin1 = dmgRaf.readLong();
- long plistEnd = dmgRaf.readLong();
- dmgRaf.seek(dmgRaf.length()-PLIST_ADDRESS_2);
- long plistBegin2 = dmgRaf.readLong();
- long plistSize = dmgRaf.readLong();
-
- if(DEBUG) {
- println("Read addresses:",
- " " + plistBegin1,
- " " + plistBegin2);
- }
- if(plistBegin1 != plistBegin2) {
- println("Addresses not equal! Assumption broken... =/",
- plistBegin1 + " != " + plistBegin2);
- System.exit(0);
- }
- if(plistSize != (plistEnd-plistBegin1)) {
- println("plistSize field does not match plistEnd marker!",
- "plistSize=" + plistSize + " plistBegin1=" + plistBegin1 + " plistEnd=" + plistEnd + " plistEnd-plistBegin1=" + (plistEnd-plistBegin1));
- }
- printlnVerbose("Jumping to address...");
- dmgRaf.seek(plistBegin1);
- byte[] buffer = new byte[(int)plistSize];
- dmgRaf.read(buffer);
-
- InputStream is = new ByteArrayInputStream(buffer);
-
- NodeBuilder handler = new NodeBuilder();
- SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
- try {
-// System.out.println("validation: " + saxParser.getProperty("validation"));
-// System.out.println("external-general-entities: " + saxParser.getProperty("external-general-entities"));
-// System.out.println("external-parameter-entities: " + saxParser.getProperty("external-parameter-entities"));
-// System.out.println("is-standalone: " + saxParser.getProperty("is-standalone"));
-// System.out.println("lexical-handler: " + saxParser.getProperty("lexical-handler"));
-// System.out.println("parameter-entities: " + saxParser.getProperty("parameter-entities"));
-// System.out.println("namespaces: " + saxParser.getProperty("namespaces"));
-// System.out.println("namespace-prefixes: " + saxParser.getProperty("namespace-prefixes"));
-// System.out.println(": " + saxParser.getProperty(""));
-// System.out.println(": " + saxParser.getProperty(""));
-// System.out.println(": " + saxParser.getProperty(""));
-// System.out.println(": " + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
- System.out.println("isValidating: " + saxParser.isValidating());
- saxParser.parse(is, handler);
- } catch(SAXException se) {
- se.printStackTrace();
- System.err.println("Could not read the partition list... exiting.");
- System.exit(1);
- }
-
- XMLNode[] rootNodes = handler.getRoots();
- if(rootNodes.length != 1) {
- println("Could not parse DMG-file!");
- System.exit(0);
- }
-
- /* Ok, now we have a tree built from the XML-document. Let's walk to the right place. */
- /* cd plist
- cd dict
- cdkey resource-fork (type:dict)
- cdkey blkx (type:array) */
- XMLNode current;
- XMLElement[] children;
- boolean keyFound;
- current = rootNodes[0]; //We are at plist... probably (there should be only one root node)
-
- current = current.cd("dict");
- current = current.cdkey("resource-fork");
- current = current.cdkey("blkx");
- printlnVerbose("Found " + current.getChildren().length + " partitions:");
-
- byte[] tmp = new byte[0x40000];
- byte[] otmp = new byte[0x40000];
-
- byte[] zeroblock = new byte[4096];
- /* I think java always zeroes its arrays on creation...
- but let's play safe. */
- for(int y = 0; y < zeroblock.length; ++y)
- zeroblock[y] = 0;
-
- LinkedList blocks = new LinkedList();
-
- //long lastOffs = 0;
- long lastOutOffset = 0;
- long lastInOffset = 0;
- long totalSize = 0;
- boolean errorsFound = false;
- reportProgress(0);
- for(XMLElement xe : current.getChildren()) {
- if(progmon != null && progmon.isCanceled()) System.exit(0);
- if(xe instanceof XMLNode) {
- XMLNode xn = (XMLNode)xe;
- byte[] data = Base64.decode(xn.getKeyValue("Data"));
-
- long partitionSize = calculatePartitionSize(data);
- totalSize += partitionSize;
-
- printlnVerbose(" " + xn.getKeyValue("Name"));
- printlnVerbose(" ID: " + xn.getKeyValue("ID"));
- printlnVerbose(" Attributes: " + xn.getKeyValue("Attributes"));
- printlnVerbose(" Partition map data length: " + data.length + " bytes");
- printlnVerbose(" Partition size: " + partitionSize + " bytes");
- if(verbose) {
- printlnVerbose(" Dumping blkx...");
- FileOutputStream fos = new FileOutputStream(xn.getKeyValue("ID") + ".blkx");
- fos.write(data);
- fos.close();
- }
-
- if(DEBUG) {
- File dumpFile = new File("data " + xn.getKeyValue("ID") + ".bin");
- println(" Dumping partition map to file: " + dumpFile);
-
- FileOutputStream dump = new FileOutputStream(dumpFile);
- dump.write(data);
- dump.close();
- }
-
- int offset = 0xCC;
- int blockType = 0;
-
- /* Offset of the input data for the current block in the input file */
- long inOffset = 0;
- /* Size of the input data for the current block */
- long inSize = 0;
- /* Offset of the output data for the current block in the output file */
- long outOffset = 0;
- /* Size of the output data (possibly larger than inSize because of
- decompression, zero expansion...) */
- long outSize = 0;
-
- long lastByteReadInBlock = -1;
-
- boolean addInOffset = false;
-
- //, lastInOffs = 0;
- int blockCount = 0;
- long previousPercentage = -1;
- while(blockType != BT_END) {
- if(progmon != null && progmon.isCanceled()) System.exit(0);
- DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
- int bytesSkipped = 0;
- while(bytesSkipped < offset)
- bytesSkipped += dis.skipBytes(offset-bytesSkipped);
-
- blockType = dis.readInt();
- int skipped = dis.readInt(); //Skip 4 bytes forward
- outOffset = dis.readLong()*0x200;//(dis.readInt() & 0xffffffffL)*0x200; //unsigned int -> long
- //dis.readInt(); //Skip 4 bytes forward
- outSize = dis.readLong()*0x200;//(dis.readInt() & 0xffffffffL)*0x200; //unsigned int -> long
- inOffset = dis.readLong();// & 0xffffffffL; //unsigned int -> long
- //dis.readInt(); //Skip 4 bytes forward
- inSize = dis.readLong();//dis.readInt() & 0xffffffffL; //unsigned int -> long
-
- blocks.add(new DMGBlock(blockType, skipped, outOffset, outSize, inOffset, inSize));
-
- if(lastByteReadInBlock == -1)
- lastByteReadInBlock = inOffset;
- lastByteReadInBlock += inSize;
-
- /* The lines below are a "hack" that I had to do to make dmgx work with
- certain dmg-files. I don't understand the issue at all, which is why
- this hack is here, but sometimes inOffset == 0 means that it is 0
- relative to the previous partition's last inOffset. And sometimes it
- doesn't (meaning the actual position 0 in the dmg file). */
- if(addInOffset)
- inOffset += lastInOffset;
- else if(inOffset == 0) {
- addInOffset = true;
- inOffset += lastInOffset;
- }
- outOffset += lastOutOffset;
-
- if(DEBUG) {
- println("outOffset=" + outOffset + " outSize=" + outSize +
- " inOffset=" + inOffset + " inSize=" + inSize +
- " lastOutOffset=" + lastOutOffset + " lastInOffset=" + lastInOffset
- /*+ " lastInOffs=" + lastInOffs + " lastOffs=" + lastOffs*/);
- }
-
- if(blockType == BT_ADC) {
- println(" " + blockCount + ". BT_ADC not supported.");
- if(!testOnly)
- System.exit(0);
- }
- else if(blockType == BT_ZLIB) {
- if(DEBUG)
- println(" " + blockCount + ". BT_ZLIB processing...");
-
- if(!testOnly && isoRaf.getFilePointer() != outOffset)
- println(" " + blockCount + ". BT_ZLIB FP != outOffset (" +
- isoRaf.getFilePointer() + " != " + outOffset + ")");
-
- dmgRaf.seek(/*lastOffs+*/inOffset);
-
- if(tmp.length < inSize)
- tmp = new byte[(int)inSize];
-
- long totalBytesRead = 0;
- while(totalBytesRead < inSize) {
- totalBytesRead += dmgRaf.read(tmp, (int)totalBytesRead, Math.min((int)(inSize-totalBytesRead), tmp.length));
- }
- long progressPercentage = dmgRaf.getFilePointer()*100/dmgRaf.length();
- if(progressPercentage != previousPercentage) {
- reportProgress(progressPercentage);
- previousPercentage = progressPercentage;
- }
-
- Inflater inflater = new Inflater();
- inflater.setInput(tmp, 0, (int)totalBytesRead);
-
- if(otmp.length < outSize)
- otmp = new byte[(int)outSize];
-
- int bytesInflated = 0;
- while(true) {
- try {
- int counter = 0;
- while(bytesInflated < outSize && counter++ < 10) {
- int old = bytesInflated;
- bytesInflated += inflater.inflate(otmp, bytesInflated, (int)(outSize-bytesInflated));
- if(old == bytesInflated)
- println("Nothing new! finished()=" + inflater.finished() + " needsInput()=" + inflater.needsInput() + " needsDictionary()=" + inflater.needsDictionary() + " getAdler()=" + inflater.getAdler() + " getBytesRead()=" + inflater.getBytesRead() + " getBytesWritten()=" + inflater.getBytesWritten() + " getRemaining()=" + inflater.getRemaining());
- }
- //System.out.println(" Inflated " + bytesInflated + " bytes. Left in buffer: " + inflater.getRemaining() + " bytes inSize="+inSize+" outSize="+outSize);
-
- if(inflater.getRemaining() == 0) {
- //System.out.println(" done!");
- break;
- }
- else {
- println(" " + blockCount + ". BT_ZLIB ERROR: otmp contents lost! (should not happen...)",
- " outSize=" + outSize + " inSize=" + inSize + " tmp.length=" + tmp.length + " otmp.length=" + otmp.length + " bytesInflated=" + bytesInflated + " inflater.getRemaining()=" + inflater.getRemaining());
-// if(bytesInflated == 0)
- throw new RuntimeException("WTF");
- }
- }
- catch(DataFormatException dfe) {
- println(" " + blockCount + ". BT_ZLIB Could not decode...");
- if(!DEBUG) {
- println("outOffset=" + outOffset + " outSize=" + outSize +
- " inOffset=" + inOffset + " inSize=" + inSize +
- " lastOutOffset=" + lastOutOffset + " lastInOffset=" + lastInOffset);
- }
- dfe.printStackTrace();
- if(!testOnly)
- System.exit(0);
- else {
- println(" Testing mode, so continuing...");
- //System.exit(0);
- errorsFound = true;
- break;
- }
- }
-
- }
- inflater.end();
-
- if(!testOnly)
- isoRaf.write(otmp, 0, (int)outSize);
-
- //lastInOffs = inOffset+inSize;
- }
- else if(blockType == BT_BZIP2) {
- println(" " + blockCount + ". BT_BZIP2 not currently supported.");
- if(!testOnly)
- System.exit(0);
- }
- else if(blockType == BT_COPY) {
- if(DEBUG)
- println(" " + blockCount + ". BT_COPY processing...");
-
- if(!testOnly && isoRaf.getFilePointer() != outOffset)
- println(" " + blockCount + ". BT_COPY FP != outOffset (" + isoRaf.getFilePointer() + " != " + outOffset + ")");
- dmgRaf.seek(/*lastOffs+*/inOffset);
-
- int bytesRead = dmgRaf.read(tmp, 0, Math.min((int)inSize, tmp.length));
- long totalBytesRead = bytesRead;
- while(bytesRead != -1) {
- long progressPercentage = dmgRaf.getFilePointer()*100/dmgRaf.length();
- if(progressPercentage != previousPercentage) {
- reportProgress(progressPercentage);
- previousPercentage = progressPercentage;
- }
-
-
- if(!testOnly)
- isoRaf.write(tmp, 0, bytesRead);
- if(totalBytesRead >= inSize)
- break;
- bytesRead = dmgRaf.read(tmp, 0, Math.min((int)(inSize-totalBytesRead), tmp.length));
- if(bytesRead > 0)
- totalBytesRead += bytesRead;
- }
-
- //lastInOffs = inOffset+inSize;
- }
- else if(blockType == BT_ZERO) {
- if(DEBUG)
- println(" " + blockCount + ". BT_ZERO processing...");
- if(!testOnly && isoRaf.getFilePointer() != outOffset)
- println(" " + blockCount + ". BT_ZERO FP != outOffset (" +
- isoRaf.getFilePointer() + " != " + outOffset + ")");
-
- long progressPercentage = dmgRaf.getFilePointer()*100/dmgRaf.length();
- if(progressPercentage != previousPercentage) {
- reportProgress(progressPercentage);
- previousPercentage = progressPercentage;
- }
-
- long numberOfZeroBlocks = outSize/zeroblock.length;
- int numberOfRemainingBytes = (int)(outSize%zeroblock.length);
- for(int j = 0; j < numberOfZeroBlocks; ++j) {
- if(!testOnly)
- isoRaf.write(zeroblock);
- }
- if(!testOnly)
- isoRaf.write(zeroblock, 0, numberOfRemainingBytes);
-
- //lastInOffs = inOffset+inSize;
- }
- else if(blockType == BT_UNKNOWN) {
- /* I have no idea what this blocktype is... but it's common, and usually
- doesn't appear more than 2-3 times in a dmg. As long as its input and
- output sizes are 0, there's no reason to complain... is there? */
- if(DEBUG)
- println(" " + blockCount + ". BT_UNKNOWN processing...");
- if(!(inSize == 0 && outSize == 0)) {
- println(" " + blockCount + ". WARNING! Blocktype BT_UNKNOWN had non-zero sizes...",
- " inSize=" + inSize + ", outSize=" + outSize);
- //println(" The author of the program would be pleased if you contacted him about this.");
- // ...or would I?
- }
- }
- else if(blockType == BT_END) {
- if(DEBUG)
- println(" " + blockCount + ". BT_END processing...");
- if(!testOnly && isoRaf.getFilePointer() != outOffset)
- println(" " + blockCount + ". BT_END FP != outOffset (" +
- isoRaf.getFilePointer() + " != " + outOffset + ")");
-
- //lastOffs += lastInOffs;
- lastOutOffset = outOffset;
- lastInOffset += lastByteReadInBlock;
- }
- else {
- println(" " + blockCount + ". WARNING: previously unseen blocktype " + blockType + " [0x" + Integer.toHexString(blockType) + "]",
- " " + blockCount + ". outOffset=" + outOffset + " outSize=" + outSize + " inOffset=" + inOffset + " inSize=" + inSize);
-
- if(!testOnly && isoRaf.getFilePointer() != outOffset)
- println(" " + blockCount + ". unknown blocktype FP != outOffset (" +
- isoRaf.getFilePointer() + " != " + outOffset + ")");
-
- }
-
- offset += 0x28;
- ++blockCount;
- }
- }
- }
- //printlnVerbose("Progress: 100% Done!");
- reportProgress(100);
- String errors = errorsFound?"There were errors...":"No errors reported.";
- if(!graphical) {
- newline();
- println(errors);
- printlnVerbose("Total extracted bytes: " + totalSize + " B");
- }
- else {
- progmon.close();
- JOptionPane.showMessageDialog(null, "Extraction complete! " + errors + "\n" +
- "Total extracted bytes: " + totalSize + " B",
- "Information", JOptionPane.INFORMATION_MESSAGE);
- System.exit(0);
- }
-// System.out.println("blocks.size()=" + blocks.size());
-// for(DMGBlock b : blocks)
-// System.out.println(" " + b.toString());
- LinkedList merged = mergeBlocks(blocks);
-// System.out.println("merged.size()=" + merged.size());
-// for(DMGBlock b : merged)
-// System.out.println(" " + b.toString());
- System.out.println("Extracting all the parts not containing block data from source file:");
- int i = 1;
- DMGBlock previous = null;
- for(DMGBlock b : merged) {
- if(previous == null && b.inOffset > 0) {
- String filename = i++ + ".block";
- System.out.print(" " + filename + "...");
- FileOutputStream curFos = new FileOutputStream(new File(filename));
- dmgRaf.seek(0);
- byte[] data = new byte[(int)(b.inOffset)];
- dmgRaf.read(data);
- curFos.write(data);
- curFos.close();
- }
- else if(previous != null) {
- String filename = i++ + ".block";
- System.out.print(" " + filename + "...");
- FileOutputStream curFos = new FileOutputStream(new File(filename));
- dmgRaf.seek(previous.inOffset+previous.inSize);
- byte[] data = new byte[(int)(b.inOffset-(previous.inOffset+previous.inSize))];
- dmgRaf.read(data);
- curFos.write(data);
- curFos.close();
- }
- previous = b;
- }
- if(previous.inOffset+previous.inSize != dmgRaf.length()) {
- String filename = i++ + ".block";
- System.out.print(" " + filename + "...");
- FileOutputStream curFos = new FileOutputStream(new File(filename));
- dmgRaf.seek(previous.inOffset+previous.inSize);
- byte[] data = new byte[(int)(dmgRaf.length()-(previous.inOffset+previous.inSize))];
- dmgRaf.read(data);
- curFos.write(data);
- curFos.close();
- }
- dmgRaf.close();
- System.out.println("done!");
- }
-
- public static void parseArgs(String[] args) {
- boolean parseSuccessful = false;
- try {
- /* Take care of the options... */
- int i;
- for(i = 0; i < args.length; ++i) {
- String cur = args[i];
- if(!cur.startsWith("-"))
- break;
- else if(cur.equals("-gui"))
- graphical = true;
- else if(cur.equals("-v"))
- verbose = true;
- else if(cur.equals("-startupcommand")) {
- startupCommand = args[i+1];
- ++i;
- }
- }
-
- println(APPNAME + " " + BUILDSTRING,
- "Copyright (c) 2006 Erik Larsson ",
- " written from the source code to the original dmg2iso program",
- " Copyright (c) 2004 vu1tur ",
- " also using the iharder Base64 Encoder/Decoder ",
- "",
- "This program is distributed under the GNU General Public License version 2 or",
- "later.",
- "See for the details.",
- "");
-
- if(i == args.length) {
- dmgFile = getInputFileFromUser();
- if(dmgFile == null)
- System.exit(0);
- if(getOutputConfirmationFromUser()) {
- isoFile = getOutputFileFromUser();
- if(isoFile == null)
- System.exit(0);
- }
- }
- else {
- dmgFile = new File(args[i++]);
- if(!dmgFile.exists()) {
- println("File \"" + dmgFile + "\" could not be found!");
- System.exit(0);
- }
-
- if(i == args.length-1)
- isoFile = new File(args[i]);
- else if(i != args.length)
- throw new Exception();
- }
-
- parseSuccessful = true;
- } catch(Exception e) {
- println();
- println(" usage: " + startupCommand + " [options] []");
- println(" if an iso-file is not supplied, the program will simulate an extraction");
- println(" (useful for detecting errors in dmg-files)");
- println();
- System.exit(0);
- }
- }
-
- public static long calculatePartitionSize(byte[] data) throws IOException {
- long partitionSize = 0;
- DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
- long totalBytesRead;
- totalBytesRead = 0;
- while(totalBytesRead < 0xCC)
- totalBytesRead += dis.skip(0xCC);
-
- while(totalBytesRead < data.length) {
- int bytesRead = 0;
- while(bytesRead < 0x10)
- bytesRead += dis.skip(0x10-bytesRead);
-
- partitionSize += dis.readLong()*0x200;
- bytesRead += 0x8;
-
- while(bytesRead < 0x28)
- bytesRead += dis.skip(0x28-bytesRead);
- totalBytesRead += bytesRead;
- }
- return partitionSize;
- }
-
- /** Never used. Java is big-endian. */
- public static int swapEndian(int i) {
- return
- ((i & 0xff000000) >> 24) |
- ((i & 0x00ff0000) >> 8 ) |
- ((i & 0x0000ff00) << 8 ) |
- ((i & 0x000000ff) << 24);
- }
-
- public static void printCurrentLine(String s) {
- System.out.print(BACKSPACE79);
- System.out.print(s);
- }
- public static void println() {
- System.out.print(BACKSPACE79);
- System.out.println();
- }
- public static void println(String... lines) {
- if(!graphical) {
- System.out.print(BACKSPACE79);
- for(String s : lines)
- System.out.println(s);
- }
- else {
- String resultString = null;
- for(String s : lines) {
- if(resultString == null)
- resultString = s;
- else
- resultString += "\n" + s;
- }
- JOptionPane.showMessageDialog(null, resultString,
- APPNAME, JOptionPane.INFORMATION_MESSAGE);
- }
- }
- public static void printlnVerbose() {
- if(verbose) {
- System.out.print(BACKSPACE79);
- System.out.println();
- }
- }
- public static void printlnVerbose(String... lines) {
- if(verbose) {
- System.out.print(BACKSPACE79);
- for(String s : lines)
- System.out.println(s);
- }
- }
-
- public static void newline() {
- System.out.println();
- }
-
- public static void reportProgress(long progressPercentage) {
- if(!graphical) {
- printCurrentLine("--->Progress: " + progressPercentage + "%");
- }
- else {
- if(progmon == null) {
- progmon = new ProgressMonitor(null, "Extracting dmg to iso...", "0%", 0, 100);
- progmon.setProgress(0);
- progmon.setMillisToPopup(0);
- }
- progmon.setProgress((int)progressPercentage);
- progmon.setNote(progressPercentage + "%");
- }
- }
-
- public static File getInputFileFromUser() throws IOException {
- if(!graphical) {
- //String s = "";
- while(true) {
- printCurrentLine("Please specify the path to the dmg file to extract from: ");
- File f = new File(stdin.readLine().trim());
- while(!f.exists()) {
- println("File does not exist!");
- printCurrentLine("Please specify the path to the dmg file to extract from: ");
- f = new File(stdin.readLine().trim());
- }
- return f;
- }
- }
- else {
- SimpleFileFilter sff = new SimpleFileFilter();
- sff.addExtension("dmg");
- sff.setDescription("DMG disk image files");
- JFileChooser jfc = new JFileChooser();
- jfc.setFileFilter(sff);
- jfc.setMultiSelectionEnabled(false);
- jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
- jfc.setDialogTitle("Choose the dmg-file to read...");
- while(true) {
- if(jfc.showDialog(null, "Open") == JFileChooser.APPROVE_OPTION) {
- File f = jfc.getSelectedFile();
- if(f.exists())
- return f;
- else
- JOptionPane.showMessageDialog(null, "The file does not exist! Choose again...",
- "Error", JOptionPane.ERROR_MESSAGE);
- }
- else
- return null;
- }
- }
- }
- public static boolean getOutputConfirmationFromUser() throws IOException {
- if(!graphical) {
- String s = "";
- while(true) {
- printCurrentLine("Do you want to specify an output file (y/n)? ");
- s = stdin.readLine().trim();
- if(s.equalsIgnoreCase("y"))
- return true;
- else if(s.equalsIgnoreCase("n"))
- return false;
- }
- }
- else {
- return JOptionPane.showConfirmDialog(null, "Do you want to specify an output file?",
- "Confirmation", JOptionPane.YES_NO_OPTION,
- JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;
- }
- }
- public static File getOutputFileFromUser() throws IOException {
- final String msg1 = "Please specify the path of the iso file to extract to: ";
- final String msg2 = "The file already exists. Do you want to overwrite?";
- if(!graphical) {
- while(true) {
- printCurrentLine(msg1);
- File f = new File(stdin.readLine().trim());
- while(f.exists()) {
- while(true) {
- printCurrentLine(msg2 + " (y/n)? ");
- String s = stdin.readLine().trim();
- if(s.equalsIgnoreCase("y"))
- return f;
- else if(s.equalsIgnoreCase("n"))
- break;
- }
- printCurrentLine(msg1);
- f = new File(stdin.readLine().trim());
- }
- return f;
- }
- }
- else {
- JFileChooser jfc = new JFileChooser();
- jfc.setMultiSelectionEnabled(false);
- jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
- jfc.setDialogTitle("Choose the output iso-file...");
- while(true) {
- if(jfc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
- File f = jfc.getSelectedFile();
- if(!f.exists())
- return f;
- else if(JOptionPane.showConfirmDialog(null, msg2, "Confirmation", JOptionPane.YES_NO_OPTION,
- JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) {
- return f;
- }
- }
- else
- return null;
- }
- }
- }
-
- public static LinkedList mergeBlocks(LinkedList blockList) {
- LinkedList result = new LinkedList();
- Iterator it = blockList.iterator();
- DMGBlock previous = it.next();
- DMGBlock current;
- while(it.hasNext()) {
- current = it.next();
- if(current.inSize != 0) {
- if(current.inOffset == previous.inOffset+previous.inSize) {
- DMGBlock mergedBlock = new DMGBlock(previous.blockType, previous.skipped, previous.outOffset, previous.outSize+current.outSize, previous.inOffset, previous.inSize+current.inSize);
- previous = mergedBlock;
- }
- else {
- result.addLast(previous);
- previous = current;
- }
- }
- }
- result.addLast(previous);
- return result;
- }
-
- public static class DMGBlock {
- public int blockType;
- public int skipped;
- public long outOffset;
- public long outSize;
- public long inOffset;
- public long inSize;
-
- public DMGBlock(int blockType, int skipped, long outOffset, long outSize, long inOffset, long inSize) {
- this.blockType = blockType;
- this.skipped = skipped;
- this.outOffset = outOffset;
- this.outSize = outSize;
- this.inOffset = inOffset;
- this.inSize = inSize;
- }
-
- public String toString() {
- return "[type: 0x" + Integer.toHexString(blockType) + " skipped: 0x" + Integer.toHexString(skipped) + " outOffset: " + outOffset + " outSize: " + outSize + " inOffset: " + inOffset + " inSize: " + inSize + "]";
- }
- }
-}
-
diff --git a/src/DMGInfo.java b/src/DMGInfo.java
deleted file mode 100644
index 48b32e9..0000000
--- a/src/DMGInfo.java
+++ /dev/null
@@ -1,147 +0,0 @@
-import java.io.*;
-
-public class DMGInfo {
- public static void main(String[] args) throws IOException {
- RandomAccessFile inRaf = new RandomAccessFile(args[0], "r");
-
- // Check opening signature "koly"
- inRaf.seek(inRaf.length()-512);
- byte[] koly = new byte[4];
- inRaf.readFully(koly);
- String kolySignature = new String(koly, "US-ASCII");
- if(!kolySignature.equals("koly"))
- System.out.println("ERROR: Signature incorrect. Found \"" + kolySignature + "\" instead of \"koly\".");
- else
- System.out.println("\"koly\" signature OK.");
-
- // Read partition list start location 1 and end location
- inRaf.seek(inRaf.length()-0x1E0);
-
- // -0x1E0: address to plist xml structure (8 bytes)
- long plistAddress1 = inRaf.readLong();
- System.out.println("Address to plist: 0x" + Long.toHexString(plistAddress1));
-
-
- // -0x1D8: address to end of plist xml structure (8 bytes)
- long plistEndAddress = inRaf.readLong();
- System.out.println("Address to end of plist: 0x" + Long.toHexString(plistEndAddress));
- System.out.println(" Implication: plist size = " + (plistEndAddress-plistAddress1) + " B");
-
-
- long unknown_0x1D0 = inRaf.readLong();
-
-
- long unknown_0x1C8 = inRaf.readLong();
- if(unknown_0x1C8 != 0x0000000100000001L)
- System.out.println("Assertion failed! unknown_0x1C8 == 0x" +
- Long.toHexString(unknown_0x1C8) + " and not 0x0000000100000001");
-
-
- long unknown_0x1C0 = inRaf.readLong();
-
-
- long unknown_0x1B8 = inRaf.readLong();
- System.out.println("Some kind of signature? Value: 0x" + Long.toHexString(unknown_0x1B8));
-
-
- long unknown_0x1B0 = inRaf.readLong();
- if(unknown_0x1B0 != 0x0000000200000020L)
- System.out.println("Assertion failed! unknown_0x1B0 == 0x" +
- Long.toHexString(unknown_0x1B0) + " and not 0x0000000200000020");
-
-
- int unknown_0x1A8 = inRaf.readInt();
-
-
- int unknown_0x1A4 = inRaf.readInt();
- System.out.println("Some kind of unit size? Value: 0x" +
- Integer.toHexString(unknown_0x1A4) + " / " + unknown_0x1A4);
-
-
- // Unknown chunk of data (120 bytes)
- byte[] unknown_0x1A0 = new byte[120];
- inRaf.readFully(unknown_0x1A0);
-
-
- // -0x128: address to beginning of plist xml structure (second occurrence) (8 bytes)
- long plistAddress2 = inRaf.readLong();
- System.out.println("Address to plist (2): 0x" + Long.toHexString(plistAddress2));
-
-
- // -0x120: size of plist xml structure (8 bytes)
- long plistSize = inRaf.readLong();
- System.out.println("plist size: " + plistSize + " B");
-
-
- // Unknown chunk of data (120 bytes)
- byte[] unknown_0x118 = new byte[120];
- inRaf.readFully(unknown_0x118);
-
-
- // -0x0A0: Checksum type identifier (4 bytes)
- System.out.print("Checksum type");
- int cs_type = inRaf.readInt();
- if(cs_type == 0x00000002)
- System.out.println(": CRC-32");
- else if(cs_type == 0x00000004)
- System.out.println(": MD5");
- else
- System.out.println(" unknown! Data: 0x" + Integer.toHexString(cs_type));
-
-
- // -0x09C: Length of checksum in bits (4 bytes)
- int cs_length = inRaf.readInt();
- System.out.println("Checksum length: " + cs_length + " bits");
-
-
- // -0x098: Checksum ((cs_length/8) bytes)
- byte[] checksum = new byte[cs_length/8];
- inRaf.readFully(checksum);
- System.out.println("Checksum: 0x" + byteArrayToHexString(checksum).toUpperCase());
-
- /*
- if(unknown_0x != 0xL)
- System.out.println("Assertion failed! unknown_0x == 0x" + Long.toHexString(unknown_0x) + " and not 0xL");
- */
- }
-
- public static String byteArrayToHexString(byte[] array) {
- StringBuilder result = new StringBuilder();
- for(byte b : array) {
- String s = Integer.toHexString(b & 0xFF);
- if(s.length() == 1)
- s = "0" + s;
- result.append(s);
- }
- return result.toString();
- }
-}
-
-class DMGInfoFrame extends JFrame {
- private JTabbedPane mainPane;
-
- public DMGInfoFrame() {
- this("DMGInfo");
-
- mainPane = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
-
- StatisticsPanel statisticsPanel;
- statisticsPanel = new StatisticsPanel();
- mainPane.addTab(statisticsPanel, "Statistics");
- }
-}
-
-class StatisticsPanel extends JPanel {
- JPanel blocktypeCountPanel;
-
- public StatisticsPanel(DMGFile dmgFile) {}
-}
-
-class DMGFile extends RandomAccessFile {
- public DMGFile(File file, String mode) {
- super(file, mode);
- }
- public DMGFile(String name, String mode) {
- super(name, mode);
- }
-}
diff --git a/src/DMGMetadata.java b/src/DMGMetadata.java
deleted file mode 100644
index 1027d51..0000000
--- a/src/DMGMetadata.java
+++ /dev/null
@@ -1,346 +0,0 @@
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInput;
-import java.io.DataInputStream;
-import java.io.DataOutput;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.io.RandomAccessFile;
-import java.io.UnsupportedEncodingException;
-import java.util.LinkedList;
-
-public class DMGMetadata {
- public static final long PLIST_ADDRESS_1 = 0x1E0;
- public static final long PLIST_ADDRESS_2 = 0x128;
-
- public final byte[] rawData;
-
- public final byte[] plistXmlData;
- public final byte[] unknown1_256;
- public PartitionBlockList[] blockLists;
- public final byte[] unknown2_12;
- public APMPartition[] partitions;
- public final byte[] unknown3_unknown;
- public final byte[] koly;
-
- public DMGMetadata(RandomAccessFile dmgFile) throws IOException {
- dmgFile.seek(dmgFile.length()-PLIST_ADDRESS_1);
- long plistBegin1 = dmgFile.readLong();
- long plistEnd = dmgFile.readLong();
- dmgFile.seek(dmgFile.length()-PLIST_ADDRESS_2);
- long plistBegin2 = dmgFile.readLong();
- long plistSize = dmgFile.readLong();
-
- rawData = new byte[(int)(dmgFile.length()-plistBegin1)];
- dmgFile.seek(plistBegin1);
- dmgFile.readFully(rawData);
-
- plistXmlData = new byte[(int)plistSize];
- dmgFile.seek(plistBegin1);
- dmgFile.readFully(plistXmlData);
-
- unknown1_256 = new byte[256];
- dmgFile.readFully(unknown1_256);
-
- LinkedList blockListList = new LinkedList();
- int length = dmgFile.readInt();
- byte[] fourcc = new byte[4];
- dmgFile.readFully(fourcc);
- String fourccString = new String(fourcc, "US-ASCII");
- dmgFile.seek(dmgFile.getFilePointer()-4);
- while(fourccString.equals("mish")) {
- blockListList.add(new PartitionBlockList(dmgFile, length));
- length = dmgFile.readInt();
- dmgFile.readFully(fourcc);
- fourccString = new String(fourcc, "US-ASCII");
- dmgFile.seek(dmgFile.getFilePointer()-4);
- }
- blockLists = blockListList.toArray(new PartitionBlockList[blockListList.size()]);
-
- unknown2_12 = new byte[12];
- dmgFile.readFully(unknown2_12);
-
- LinkedList partitionList = new LinkedList();
- byte[] currentPartitionEntry = new byte[0x200];
- dmgFile.readFully(currentPartitionEntry);
- byte[] pmSig = new byte[2];
- pmSig[0] = currentPartitionEntry[0];
- pmSig[1] = currentPartitionEntry[1];
- while(new String(pmSig, "US-ASCII").equals("PM")) {
- partitionList.addLast(new APMPartition(currentPartitionEntry));
- dmgFile.readFully(currentPartitionEntry);
- pmSig[0] = currentPartitionEntry[0];
- pmSig[1] = currentPartitionEntry[1];
- }
- while(onlyZeros(currentPartitionEntry))
- dmgFile.readFully(currentPartitionEntry);
- partitions = partitionList.toArray(new APMPartition[partitionList.size()]);
-
- unknown3_unknown = new byte[(int)(dmgFile.length()-dmgFile.getFilePointer()-512)];
- dmgFile.readFully(unknown3_unknown);
-
- koly = new byte[512];
- dmgFile.seek(dmgFile.length()-koly.length);
- dmgFile.readFully(koly);
-
- if(dmgFile.getFilePointer() != dmgFile.length())
- System.out.println("MISCALCULATION! FP=" + dmgFile.getFilePointer() + " LENGTH=" + dmgFile.length());
- }
-
- public void printInfo(PrintStream ps) {
- ps.println("block list:");
- for(PartitionBlockList pbl : blockLists)
- pbl.printInfo(ps);
- ps.println("partitions:");
- for(APMPartition ap : partitions)
- ap.printPartitionInfo(ps);
- }
-
- private static boolean onlyZeros(byte[] array) {
- for(int i = 0; i < array.length; ++i) {
- if(array[i] != 0)
- return false;
- }
- return true;
- }
-
- public static class PartitionBlockList {
- public final byte[] header = new byte[0xCC];
- public final BlockDescriptor[] descriptors;
-
- public PartitionBlockList(byte[] entryData) throws IOException {
- this(new DataInputStream(new ByteArrayInputStream(entryData)), entryData.length);
- }
-
- public PartitionBlockList(DataInput di, int length) throws IOException {
- int position = 0;
- di.readFully(header);
- position += header.length;
- LinkedList descs = new LinkedList();
- while(position < length) {
- descs.addLast(new BlockDescriptor(di));
- position += 0x28;
- }
- descriptors = descs.toArray(new BlockDescriptor[descs.size()]);
- }
-
- public void printInfo(PrintStream ps) {
- for(BlockDescriptor bd : descriptors)
- ps.println(bd.toString());
- }
- }
-
- public static class BlockDescriptor {
- // Known block types
- public static final int BT_COPY = 0x00000001;
- public static final int BT_ZERO = 0x00000002;
- public static final int BT_ZLIB = 0x80000005;
- public static final int BT_END = 0xffffffff;
- public static final int BT_UNKNOWN1 = 0x7ffffffe;
- private static final int[] KNOWN_BLOCK_TYPES = { BT_COPY,
- BT_ZERO,
- BT_ZLIB,
- BT_END,
- BT_UNKNOWN1 };
- private static final String[] KNOWN_BLOCK_TYPE_NAMES = { "BT_COPY",
- "BT_ZERO",
- "BT_ZLIB",
- "BT_END",
- "BT_UNKNOWN1" };
-
- private int blockType;
- private int unknown;
- private long outOffset;
- private long outSize;
- private long inOffset;
- private long inSize;
-
- public BlockDescriptor() {}
-
- public BlockDescriptor(byte[] entryData) throws IOException {
- this(new DataInputStream(new ByteArrayInputStream(entryData)));
- }
-
- public BlockDescriptor(DataInput dataIn) throws IOException {
- blockType = dataIn.readInt();
- unknown = dataIn.readInt();
- outOffset = dataIn.readLong()*0x200;
- outSize = dataIn.readLong()*0x200;
- inOffset = dataIn.readLong();
- inSize = dataIn.readLong();
- }
-
- public byte[] toBytes() throws IOException {
- ByteArrayOutputStream result = new ByteArrayOutputStream(0x28);
- DataOutputStream dataOut = new DataOutputStream(result);
-
- dataOut.writeInt(blockType); // 4 bytes
- dataOut.writeInt(unknown); // 4 bytes
- if((outOffset % 0x200) != 0)
- throw new RuntimeException("Out offset must be aligned to 0x200 block size!");
- dataOut.writeLong(outOffset/0x200); // 8 bytes
- if((outSize % 0x200) != 0)
- throw new RuntimeException("Out size must be aligned to 0x200 block size!");
- dataOut.writeLong(outSize/0x200); // 8 bytes
- dataOut.writeLong(inOffset); // 8 bytes
- dataOut.writeLong(inSize); // 8 bytes
- // sum = 4 + 4 + 8 + 8 + 8 + 8 = 40 = 0x28
-
- dataOut.flush();
- dataOut.close();
- return result.toByteArray();
- }
-
- public int getBlockType() { return blockType; }
- public int getUnknown() { return unknown; }
- public String getUnknownAsString() throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream(4);
- DataOutputStream dos = new DataOutputStream(baos);
- dos.write(unknown);
- dos.close();
- return new String(baos.toByteArray(), "US-ASCII");
- }
- public long getOutOffset() { return outOffset; }
- public long getOutSize() { return outSize; }
- public long getInOffset() { return inOffset; }
- public long getInSize() { return inSize; }
-
- public void setBlockType(int blockType) { this.blockType = blockType; }
- public void setUnknown(int unknown) { this.unknown = unknown; }
- public void setOutOffset(long outOffset) {
- if((outOffset % 0x200) != 0)
- throw new RuntimeException("Out offset must be aligned to 0x200 block size!");
- this.outOffset = outOffset;
- }
- public void setOutSize(long outSize) {
- if((outSize % 0x200) != 0)
- throw new RuntimeException("Out size must be aligned to 0x200 block size!");
- this.outSize = outSize;
- }
- public void setInOffset(long inOffset) { this.inOffset = inOffset; }
- public void setInSize(long inSize) { this.inSize = inSize; }
-
- public boolean hasKnownBlockType() {
- for(int current : KNOWN_BLOCK_TYPES) {
- if(blockType == current)
- return true;
- }
- return false;
- }
-
- public String getBlockTypeName() {
- for(int i = 0; i < KNOWN_BLOCK_TYPES.length; ++i) {
- int current = KNOWN_BLOCK_TYPES[i];
- if(blockType == current)
- return KNOWN_BLOCK_TYPE_NAMES[i];
- }
- return null;
- }
-
- public String toString() {
- StringBuilder result = new StringBuilder("[BlockDescriptor");
-
- String blockTypeString = "\"" + getBlockTypeName() + "\"";
- if(blockTypeString == null)
- blockTypeString = "0x" + Integer.toHexString(blockType) + " (unknown type)";
-
- result.append(" blockType=" + blockTypeString);
- result.append(" unknown=" + Integer.toHexString(unknown));
- result.append(" outOffset=" + outOffset);
- result.append(" outSize=" + outSize);
- result.append(" inOffset=" + inOffset);
- result.append(" inSize=" + inSize);
- result.append("]");
-
- return result.toString();
- }
- }
-
- // Saxat från HFSExplorer.java
- public static class APMPartition {
- public int pmSig; // {partition signature}
- public int pmSigPad; // {reserved}
- public long pmMapBlkCnt; // {number of blocks in partition map}
- public long pmPyPartStart; // {first physical block of partition}
- public long pmPartBlkCnt; // {number of blocks in partition}
- public final byte[] pmPartName = new byte[32]; // {partition name}
- public final byte[] pmParType = new byte[32]; // {partition type}
- public long pmLgDataStart; // {first logical block of data area}
- public long pmDataCnt; // {number of blocks in data area}
- public long pmPartStatus; // {partition status information}
- public long pmLgBootStart; // {first logical block of boot code}
- public long pmBootSize; // {size of boot code, in bytes}
- public long pmBootAddr; // {boot code load address}
- public long pmBootAddr2; // {reserved}
- public long pmBootEntry; // {boot code entry point}
- public long pmBootEntry2; // {reserved}
- public long pmBootCksum; // {boot code checksum}
- public final byte[] pmProcessor = new byte[16]; // {processor type}
- public final int[] pmPad = new int[188]; // {reserved}
-
- public APMPartition(byte[] entryData) throws IOException {
- this(new DataInputStream(new ByteArrayInputStream(entryData)));
- }
-
- public APMPartition(DataInput di) throws IOException {
- // 2*2 + 4*3 + 32*2 + 10*4 + 16 + 188*2 = 512
- pmSig = di.readShort() & 0xffff;
- pmSigPad = di.readShort() & 0xffff;
- pmMapBlkCnt = di.readInt() & 0xffffffffL;
- pmPyPartStart = di.readInt() & 0xffffffffL;
- pmPartBlkCnt = di.readInt() & 0xffffffffL;
- di.readFully(pmPartName);
- di.readFully(pmParType);
- pmLgDataStart = di.readInt() & 0xffffffffL;
- pmDataCnt = di.readInt() & 0xffffffffL;
- pmPartStatus = di.readInt() & 0xffffffffL;
- pmLgBootStart = di.readInt() & 0xffffffffL;
- pmBootSize = di.readInt() & 0xffffffffL;
- pmBootAddr = di.readInt() & 0xffffffffL;
- pmBootAddr2 = di.readInt() & 0xffffffffL;
- pmBootEntry = di.readInt() & 0xffffffffL;
- pmBootEntry2 = di.readInt() & 0xffffffffL;
- pmBootCksum = di.readInt() & 0xffffffffL;
- di.readFully(pmProcessor);
- for(int i = 0; i < pmPad.length; ++i)
- pmPad[i] = di.readShort() & 0xffff;
- }
-
- public void printPartitionInfo(PrintStream ps) {
-// String result = "";
-// result += "Partition name: \"" + new String(pmPartName) + "\"\n";
-// result += "Partition type: \"" + new String(pmParType) + "\"\n";
-// result += "Processor type: \"" + new String(pmProcessor) + "\"\n";
-// return result;
- try {
- ps.println("pmSig: " + pmSig);
- ps.println("pmSigPad: " + pmSigPad);
- ps.println("pmMapBlkCnt: " + pmMapBlkCnt);
- ps.println("pmPyPartStart: " + pmPyPartStart);
- ps.println("pmPartBlkCnt: " + pmPartBlkCnt);
- ps.println("pmPartName: \"" + new String(pmPartName, "US-ASCII") + "\"");
- ps.println("pmParType: \"" + new String(pmParType, "US-ASCII") + "\"");
- ps.println("pmLgDataStart: " + pmLgDataStart);
- ps.println("pmDataCnt: " + pmDataCnt);
- ps.println("pmPartStatus: " + pmPartStatus);
- ps.println("pmLgBootStart: " + pmLgBootStart);
- ps.println("pmBootSize: " + pmBootSize);
- ps.println("pmBootAddr: " + pmBootAddr);
- ps.println("pmBootAddr2: " + pmBootAddr2);
- ps.println("pmBootEntry: " + pmBootEntry);
- ps.println("pmBootEntry2: " + pmBootEntry2);
- ps.println("pmBootCksum: " + pmBootCksum);
- ps.println("pmProcessor: \"" + new String(pmProcessor, "US-ASCII") + "\"");
- ps.println("pmPad: " + pmPad);
- } catch(UnsupportedEncodingException uee) {
- uee.printStackTrace();
- } // Will never happen. Ever. Period.
- }
- }
-
- public static void main(String[] args) throws IOException {
- DMGMetadata meta = new DMGMetadata(new RandomAccessFile(args[0], "r"));
- meta.printInfo(System.out);
- }
-}
diff --git a/src/XMLNode.java b/src/XMLNode.java
deleted file mode 100644
index bfee766..0000000
--- a/src/XMLNode.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*-
- * Copyright (C) 2006 Erik Larsson
- *
- * All rights reserved.
- *
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-import java.util.LinkedList;
-import java.io.PrintStream;
-
-class XMLNode extends XMLElement {
- public final String namespaceURI;
- public final String sName;
- public final String qName;
- public final Attribute[] attrs;
- public final XMLNode parent;
- private final LinkedList children;
-
- public XMLNode(String namespaceURI, String sName,
- String qName, Attribute[] attrs, XMLNode parent) {
- this.namespaceURI = namespaceURI;
- this.sName = sName;
- this.qName = qName;
- this.attrs = attrs;
- this.parent = parent;
- this.children = new LinkedList();
- }
-
- public void addChild(XMLElement x) {
- children.addLast(x);
- }
-
- public void printTree(PrintStream pw) {
- _printTree(pw, 0);
- }
-
- protected void _printTree(PrintStream pw, int level) {
- for(int i = 0; i < level; ++i)
- pw.print(" ");
- pw.print("<");
- pw.print(qName);
- for(Attribute a : attrs)
- pw.print(" " + a.qName + "=" + a.value);
- pw.println(">");
- for(XMLElement xe : children)
- xe._printTree(pw, level+1);
-
- for(int i = 0; i < level; ++i)
- pw.print(" ");
- pw.println("" + qName + ">");
- }
- public XMLElement[] getChildren() {
- return children.toArray(new XMLElement[children.size()]);
- }
-
- /**
- * The concept of "changing directory" in a tree is perhabs not a
- * perfect way to describe things. But this method will look up the
- * first subnode of our node that is of the type type
- * and return it.
- * If you have more than one of the same type, tough luck. You only
- * get the first.
- */
- public XMLNode cd(String type) {
- for(XMLElement xn : getChildren()) {
- if(xn instanceof XMLNode && ((XMLNode)xn).qName.equals(type))
- return (XMLNode)xn;
- }
- return null;
- }
- /**
- * This is different from the cd method in that it
- * searches for a node of the type "key", and looks up the
- * XMLText within. It then compares the text with the String
- * key. If they match, it returns the node coming
- * after the key node. Else it continues to search. If no match is
- * found, null is returned.
- */
- public XMLNode cdkey(String key) {
- boolean keyFound = false;
- for(XMLElement xn : getChildren()) {
- if(xn instanceof XMLNode) {
- if(keyFound)
- return (XMLNode)xn;
-
- else if(((XMLNode)xn).qName.equals("key")) {
- for(XMLElement xn2 : ((XMLNode)xn).getChildren()) {
- if(xn2 instanceof XMLText && ((XMLText)xn2).text.equals(key))
- keyFound = true;
- }
- }
- }
- }
- return null;
- }
- public String getKeyValue(String key) {
- XMLNode keyNode = cdkey(key);
- StringBuilder returnString = new StringBuilder();
- for(XMLElement xe : keyNode.getChildren()) {
- if(xe instanceof XMLText)
- returnString.append(((XMLText)xe).text);
- }
- if(returnString.length() == 0)
- return null;
- else
- return returnString.toString();
- }
-}
diff --git a/src/XMLParse.java b/src/XMLParse.java
deleted file mode 100644
index d8d2826..0000000
--- a/src/XMLParse.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
-public class XMLParse {
- public String encoding = "US-ASCII";
-
- public XMLElement parse(InputStream is) throws IOException {
- readElement(is);
- }
-
- public void readElement(InputStream is) {
- LinkedList buf = new LinkedList();
- int currentByte = is.read();
- if(currentByte != '<')
- throw new RuntimeException();
-
- while(currentByte != '>' && currentByte != -1) {
- buf.add(currentByte);
- currentByte = is.read();
- }
- if(currentByte == -1)
- throw new RuntimeException();
- else {
- buf.add(currentByte);
- byte[] bytes = new byte[buf.size()];
- int i = 0;
- for(int cur : buf)
- bytes[i++] = cur;
- String s = new String(bytes, encoding);
- }
- }
-}
-*/
diff --git a/src/XMLText.java b/src/XMLText.java
deleted file mode 100644
index c7fa922..0000000
--- a/src/XMLText.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*-
- * Copyright (C) 2006 Erik Larsson
- *
- * All rights reserved.
- *
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-import java.io.PrintStream;
-
-class XMLText extends XMLElement {
- public final String text;
- public XMLText(String text) {
- this.text = text;
- }
- protected void _printTree(PrintStream pw, int level) {
- for(int i = 0; i < level; ++i)
- pw.print(" ");
- pw.println(text);
- }
-
- public static void main(String[] args) {
- System.out.println(args[0] + " " + args[1]);
- }
-}
diff --git a/src/net/iharder/Base64.java b/src/net/iharder/Base64.java
deleted file mode 100644
index a52d7af..0000000
--- a/src/net/iharder/Base64.java
+++ /dev/null
@@ -1,1452 +0,0 @@
-/**
- * Encodes and decodes to and from Base64 notation.
- *
- *
- * Change Log:
- *
- *
- *
v2.1 - Cleaned up javadoc comments and unused variables and methods. Added
- * some convenience methods for reading and writing to and from files.
- *
v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems
- * with other encodings (like EBCDIC).
- *
v2.0.1 - Fixed an error when decoding a single byte, that is, when the
- * encoded data was a single byte.
- *
v2.0 - I got rid of methods that used booleans to set options.
- * Now everything is more consolidated and cleaner. The code now detects
- * when data that's being decoded is gzip-compressed and will decompress it
- * automatically. Generally things are cleaner. You'll probably have to
- * change some method calls that you were making to support the new
- * options format (ints that you "OR" together).
- *
v1.5.1 - Fixed bug when decompressing and decoding to a
- * byte[] using decode( String s, boolean gzipCompressed ).
- * Added the ability to "suspend" encoding in the Output Stream so
- * you can turn on and off the encoding if you need to embed base64
- * data in an otherwise "normal" stream (like an XML file).
- *
v1.5 - Output stream pases on flush() command but doesn't do anything itself.
- * This helps when using GZIP streams.
- * Added the ability to GZip-compress objects before encoding them.
- *
v1.4 - Added helper methods to read/write files.
- *
v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
- *
v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream
- * where last buffer being read, if not completely full, was not returned.
- *
v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
- *
v1.3.3 - Fixed I/O streams which were totally messed up.
- *
- *
- *
- * I am placing this code in the Public Domain. Do with it as you will.
- * This software comes with no guarantees or warranties but with
- * plenty of well-wishing instead!
- * Please visit http://iharder.net/base64
- * periodically to check for updates or to contribute improvements.
- *
- *
- * @author Robert Harder
- * @author rob@iharder.net
- * @version 2.1
- */
-
-package net.iharder;
-
-public class Base64
-{
-
-/* ******** P U B L I C F I E L D S ******** */
-
-
- /** No options specified. Value is zero. */
- public final static int NO_OPTIONS = 0;
-
- /** Specify encoding. */
- public final static int ENCODE = 1;
-
-
- /** Specify decoding. */
- public final static int DECODE = 0;
-
-
- /** Specify that data should be gzip-compressed. */
- public final static int GZIP = 2;
-
-
- /** Don't break lines when encoding (violates strict Base64 specification) */
- public final static int DONT_BREAK_LINES = 8;
-
-
-/* ******** P R I V A T E F I E L D S ******** */
-
-
- /** Maximum line length (76) of Base64 output. */
- private final static int MAX_LINE_LENGTH = 76;
-
-
- /** The equals sign (=) as a byte. */
- private final static byte EQUALS_SIGN = (byte)'=';
-
-
- /** The new line character (\n) as a byte. */
- private final static byte NEW_LINE = (byte)'\n';
-
-
- /** Preferred encoding. */
- private final static String PREFERRED_ENCODING = "UTF-8";
-
-
- /** The 64 valid Base64 values. */
- private final static byte[] ALPHABET;
- private final static byte[] _NATIVE_ALPHABET = /* May be something funny like EBCDIC */
- {
- (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
- (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
- (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
- (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
- (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
- (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
- (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
- (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
- (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
- (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/'
- };
-
- /** Determine which ALPHABET to use. */
- static
- {
- byte[] __bytes;
- try
- {
- __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes( PREFERRED_ENCODING );
- } // end try
- catch (java.io.UnsupportedEncodingException use)
- {
- __bytes = _NATIVE_ALPHABET; // Fall back to native encoding
- } // end catch
- ALPHABET = __bytes;
- } // end static
-
-
- /**
- * Translates a Base64 value to either its 6-bit reconstruction value
- * or a negative number indicating some other meaning.
- **/
- private final static byte[] DECODABET =
- {
- -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8
- -5,-5, // Whitespace: Tab and Linefeed
- -9,-9, // Decimal 11 - 12
- -5, // Whitespace: Carriage Return
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26
- -9,-9,-9,-9,-9, // Decimal 27 - 31
- -5, // Whitespace: Space
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42
- 62, // Plus sign at decimal 43
- -9,-9,-9, // Decimal 44 - 46
- 63, // Slash at decimal 47
- 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine
- -9,-9,-9, // Decimal 58 - 60
- -1, // Equals sign at decimal 61
- -9,-9,-9, // Decimal 62 - 64
- 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N'
- 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z'
- -9,-9,-9,-9,-9,-9, // Decimal 91 - 96
- 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm'
- 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z'
- -9,-9,-9,-9 // Decimal 123 - 126
- /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
- -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
- };
-
- // I think I end up not using the BAD_ENCODING indicator.
- //private final static byte BAD_ENCODING = -9; // Indicates error in encoding
- private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
- private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
-
-
- /** Defeats instantiation. */
- private Base64(){}
-
-
-
-/* ******** E N C O D I N G M E T H O D S ******** */
-
-
- /**
- * Encodes up to the first three bytes of array threeBytes
- * and returns a four-byte array in Base64 notation.
- * The actual number of significant bytes in your array is
- * given by numSigBytes.
- * The array threeBytes needs only be as big as
- * numSigBytes.
- * Code can reuse a byte array by passing a four-byte array as b4.
- *
- * @param b4 A reusable byte array to reduce array instantiation
- * @param threeBytes the array to convert
- * @param numSigBytes the number of significant bytes in your array
- * @return four byte array in Base64 notation.
- * @since 1.5.1
- */
- private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes )
- {
- encode3to4( threeBytes, 0, numSigBytes, b4, 0 );
- return b4;
- } // end encode3to4
-
-
- /**
- * Encodes up to three bytes of the array source
- * and writes the resulting four Base64 bytes to destination.
- * The source and destination arrays can be manipulated
- * anywhere along their length by specifying
- * srcOffset and destOffset.
- * This method does not check to make sure your arrays
- * are large enough to accomodate srcOffset + 3 for
- * the source array or destOffset + 4 for
- * the destination array.
- * The actual number of significant bytes in your array is
- * given by numSigBytes.
- *
- * @param source the array to convert
- * @param srcOffset the index where conversion begins
- * @param numSigBytes the number of significant bytes in your array
- * @param destination the array to hold the conversion
- * @param destOffset the index where output will be put
- * @return the destination array
- * @since 1.3
- */
- private static byte[] encode3to4(
- byte[] source, int srcOffset, int numSigBytes,
- byte[] destination, int destOffset )
- {
- // 1 2 3
- // 01234567890123456789012345678901 Bit position
- // --------000000001111111122222222 Array position from threeBytes
- // --------| || || || | Six bit groups to index ALPHABET
- // >>18 >>12 >> 6 >> 0 Right shift necessary
- // 0x3f 0x3f 0x3f Additional AND
-
- // Create buffer with zero-padding if there are only one or two
- // significant bytes passed in the array.
- // We have to shift left 24 in order to flush out the 1's that appear
- // when Java treats a value as negative that is cast from a byte to an int.
- int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 )
- | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 )
- | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 );
-
- switch( numSigBytes )
- {
- case 3:
- destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
- destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
- destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ];
- destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ];
- return destination;
-
- case 2:
- destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
- destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
- destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ];
- destination[ destOffset + 3 ] = EQUALS_SIGN;
- return destination;
-
- case 1:
- destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ];
- destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
- destination[ destOffset + 2 ] = EQUALS_SIGN;
- destination[ destOffset + 3 ] = EQUALS_SIGN;
- return destination;
-
- default:
- return destination;
- } // end switch
- } // end encode3to4
-
-
-
- /**
- * Serializes an object and returns the Base64-encoded
- * version of that serialized object. If the object
- * cannot be serialized or there is another error,
- * the method will return null.
- * The object is not GZip-compressed before being encoded.
- *
- * @param serializableObject The object to encode
- * @return The Base64-encoded object
- * @since 1.4
- */
- public static String encodeObject( java.io.Serializable serializableObject )
- {
- return encodeObject( serializableObject, NO_OPTIONS );
- } // end encodeObject
-
-
-
- /**
- * Serializes an object and returns the Base64-encoded
- * version of that serialized object. If the object
- * cannot be serialized or there is another error,
- * the method will return null.
- *
- * Valid options:
- * GZIP: gzip-compresses object before encoding it.
- * DONT_BREAK_LINES: don't break lines at 76 characters
- * Note: Technically, this makes your encoding non-compliant.
- *
- *
- * Example: encodeObject( myObj, Base64.GZIP ) or
- *
- * Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES )
- *
- * @param serializableObject The object to encode
- * @param options Specified options
- * @return The Base64-encoded object
- * @see Base64#GZIP
- * @see Base64#DONT_BREAK_LINES
- * @since 2.0
- */
- public static String encodeObject( java.io.Serializable serializableObject, int options )
- {
- // Streams
- java.io.ByteArrayOutputStream baos = null;
- java.io.OutputStream b64os = null;
- java.io.ObjectOutputStream oos = null;
- java.util.zip.GZIPOutputStream gzos = null;
-
- // Isolate options
- int gzip = (options & GZIP);
- int dontBreakLines = (options & DONT_BREAK_LINES);
-
- try
- {
- // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
- baos = new java.io.ByteArrayOutputStream();
- b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines );
-
- // GZip?
- if( gzip == GZIP )
- {
- gzos = new java.util.zip.GZIPOutputStream( b64os );
- oos = new java.io.ObjectOutputStream( gzos );
- } // end if: gzip
- else
- oos = new java.io.ObjectOutputStream( b64os );
-
- oos.writeObject( serializableObject );
- } // end try
- catch( java.io.IOException e )
- {
- e.printStackTrace();
- return null;
- } // end catch
- finally
- {
- try{ oos.close(); } catch( Exception e ){}
- try{ gzos.close(); } catch( Exception e ){}
- try{ b64os.close(); } catch( Exception e ){}
- try{ baos.close(); } catch( Exception e ){}
- } // end finally
-
- // Return value according to relevant encoding.
- try
- {
- return new String( baos.toByteArray(), PREFERRED_ENCODING );
- } // end try
- catch (java.io.UnsupportedEncodingException uue)
- {
- return new String( baos.toByteArray() );
- } // end catch
-
- } // end encode
-
-
-
- /**
- * Encodes a byte array into Base64 notation.
- * Does not GZip-compress data.
- *
- * @param source The data to convert
- * @since 1.4
- */
- public static String encodeBytes( byte[] source )
- {
- return encodeBytes( source, 0, source.length, NO_OPTIONS );
- } // end encodeBytes
-
-
-
- /**
- * Encodes a byte array into Base64 notation.
- *
- * Valid options:
- * GZIP: gzip-compresses object before encoding it.
- * DONT_BREAK_LINES: don't break lines at 76 characters
- * Note: Technically, this makes your encoding non-compliant.
- *
- *
- * Example: encodeBytes( myData, Base64.GZIP ) or
- *
- * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )
- *
- *
- * @param source The data to convert
- * @param options Specified options
- * @see Base64#GZIP
- * @see Base64#DONT_BREAK_LINES
- * @since 2.0
- */
- public static String encodeBytes( byte[] source, int options )
- {
- return encodeBytes( source, 0, source.length, options );
- } // end encodeBytes
-
-
- /**
- * Encodes a byte array into Base64 notation.
- * Does not GZip-compress data.
- *
- * @param source The data to convert
- * @param off Offset in array where conversion should begin
- * @param len Length of data to convert
- * @since 1.4
- */
- public static String encodeBytes( byte[] source, int off, int len )
- {
- return encodeBytes( source, off, len, NO_OPTIONS );
- } // end encodeBytes
-
-
-
- /**
- * Encodes a byte array into Base64 notation.
- *
- * Valid options:
- * GZIP: gzip-compresses object before encoding it.
- * DONT_BREAK_LINES: don't break lines at 76 characters
- * Note: Technically, this makes your encoding non-compliant.
- *
- *
- * Example: encodeBytes( myData, Base64.GZIP ) or
- *
- * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )
- *
- *
- * @param source The data to convert
- * @param off Offset in array where conversion should begin
- * @param len Length of data to convert
- * @param options Specified options
- * @see Base64#GZIP
- * @see Base64#DONT_BREAK_LINES
- * @since 2.0
- */
- public static String encodeBytes( byte[] source, int off, int len, int options )
- {
- // Isolate options
- int dontBreakLines = ( options & DONT_BREAK_LINES );
- int gzip = ( options & GZIP );
-
- // Compress?
- if( gzip == GZIP )
- {
- java.io.ByteArrayOutputStream baos = null;
- java.util.zip.GZIPOutputStream gzos = null;
- Base64.OutputStream b64os = null;
-
-
- try
- {
- // GZip -> Base64 -> ByteArray
- baos = new java.io.ByteArrayOutputStream();
- b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines );
- gzos = new java.util.zip.GZIPOutputStream( b64os );
-
- gzos.write( source, off, len );
- gzos.close();
- } // end try
- catch( java.io.IOException e )
- {
- e.printStackTrace();
- return null;
- } // end catch
- finally
- {
- try{ gzos.close(); } catch( Exception e ){}
- try{ b64os.close(); } catch( Exception e ){}
- try{ baos.close(); } catch( Exception e ){}
- } // end finally
-
- // Return value according to relevant encoding.
- try
- {
- return new String( baos.toByteArray(), PREFERRED_ENCODING );
- } // end try
- catch (java.io.UnsupportedEncodingException uue)
- {
- return new String( baos.toByteArray() );
- } // end catch
- } // end if: compress
-
- // Else, don't compress. Better not to use streams at all then.
- else
- {
- // Convert option to boolean in way that code likes it.
- boolean breakLines = dontBreakLines == 0;
-
- int len43 = len * 4 / 3;
- byte[] outBuff = new byte[ ( len43 ) // Main 4:3
- + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding
- + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines
- int d = 0;
- int e = 0;
- int len2 = len - 2;
- int lineLength = 0;
- for( ; d < len2; d+=3, e+=4 )
- {
- encode3to4( source, d+off, 3, outBuff, e );
-
- lineLength += 4;
- if( breakLines && lineLength == MAX_LINE_LENGTH )
- {
- outBuff[e+4] = NEW_LINE;
- e++;
- lineLength = 0;
- } // end if: end of line
- } // en dfor: each piece of array
-
- if( d < len )
- {
- encode3to4( source, d+off, len - d, outBuff, e );
- e += 4;
- } // end if: some padding needed
-
-
- // Return value according to relevant encoding.
- try
- {
- return new String( outBuff, 0, e, PREFERRED_ENCODING );
- } // end try
- catch (java.io.UnsupportedEncodingException uue)
- {
- return new String( outBuff, 0, e );
- } // end catch
-
- } // end else: don't compress
-
- } // end encodeBytes
-
-
-
-
-
-/* ******** D E C O D I N G M E T H O D S ******** */
-
-
- /**
- * Decodes four bytes from array source
- * and writes the resulting bytes (up to three of them)
- * to destination.
- * The source and destination arrays can be manipulated
- * anywhere along their length by specifying
- * srcOffset and destOffset.
- * This method does not check to make sure your arrays
- * are large enough to accomodate srcOffset + 4 for
- * the source array or destOffset + 3 for
- * the destination array.
- * This method returns the actual number of bytes that
- * were converted from the Base64 encoding.
- *
- *
- * @param source the array to convert
- * @param srcOffset the index where conversion begins
- * @param destination the array to hold the conversion
- * @param destOffset the index where output will be put
- * @return the number of decoded bytes converted
- * @since 1.3
- */
- private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset )
- {
- // Example: Dk==
- if( source[ srcOffset + 2] == EQUALS_SIGN )
- {
- // Two ways to do the same thing. Don't know which way I like best.
- //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
- // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
- int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
- | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 );
-
- destination[ destOffset ] = (byte)( outBuff >>> 16 );
- return 1;
- }
-
- // Example: DkL=
- else if( source[ srcOffset + 3 ] == EQUALS_SIGN )
- {
- // Two ways to do the same thing. Don't know which way I like best.
- //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
- // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
- // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
- int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
- | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
- | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 );
-
- destination[ destOffset ] = (byte)( outBuff >>> 16 );
- destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 );
- return 2;
- }
-
- // Example: DkLE
- else
- {
- try{
- // Two ways to do the same thing. Don't know which way I like best.
- //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
- // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
- // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
- // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
- int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 )
- | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
- | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6)
- | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) );
-
-
- destination[ destOffset ] = (byte)( outBuff >> 16 );
- destination[ destOffset + 1 ] = (byte)( outBuff >> 8 );
- destination[ destOffset + 2 ] = (byte)( outBuff );
-
- return 3;
- }catch( Exception e){
- System.out.println(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) );
- System.out.println(""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) );
- System.out.println(""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) );
- System.out.println(""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) );
- return -1;
- } //e nd catch
- }
- } // end decodeToBytes
-
-
-
-
- /**
- * Very low-level access to decoding ASCII characters in
- * the form of a byte array. Does not support automatically
- * gunzipping or any other "fancy" features.
- *
- * @param source The Base64 encoded data
- * @param off The offset of where to begin decoding
- * @param len The length of characters to decode
- * @return decoded data
- * @since 1.3
- */
- public static byte[] decode( byte[] source, int off, int len )
- {
- int len34 = len * 3 / 4;
- byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output
- int outBuffPosn = 0;
-
- byte[] b4 = new byte[4];
- int b4Posn = 0;
- int i = 0;
- byte sbiCrop = 0;
- byte sbiDecode = 0;
- for( i = off; i < off+len; i++ )
- {
- sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits
- sbiDecode = DECODABET[ sbiCrop ];
-
- if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better
- {
- if( sbiDecode >= EQUALS_SIGN_ENC )
- {
- b4[ b4Posn++ ] = sbiCrop;
- if( b4Posn > 3 )
- {
- outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn );
- b4Posn = 0;
-
- // If that was the equals sign, break out of 'for' loop
- if( sbiCrop == EQUALS_SIGN )
- break;
- } // end if: quartet built
-
- } // end if: equals sign or better
-
- } // end if: white space, equals sign or better
- else
- {
- System.err.println( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" );
- return null;
- } // end else:
- } // each input character
-
- byte[] out = new byte[ outBuffPosn ];
- System.arraycopy( outBuff, 0, out, 0, outBuffPosn );
- return out;
- } // end decode
-
-
-
-
- /**
- * Decodes data from Base64 notation, automatically
- * detecting gzip-compressed data and decompressing it.
- *
- * @param s the string to decode
- * @return the decoded data
- * @since 1.4
- */
- public static byte[] decode( String s )
- {
- byte[] bytes;
- try
- {
- bytes = s.getBytes( PREFERRED_ENCODING );
- } // end try
- catch( java.io.UnsupportedEncodingException uee )
- {
- bytes = s.getBytes();
- } // end catch
- //
-
- // Decode
- bytes = decode( bytes, 0, bytes.length );
-
-
- // Check to see if it's gzip-compressed
- // GZIP Magic Two-Byte Number: 0x8b1f (35615)
- if( bytes != null && bytes.length >= 4 )
- {
-
- int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
- if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head )
- {
- java.io.ByteArrayInputStream bais = null;
- java.util.zip.GZIPInputStream gzis = null;
- java.io.ByteArrayOutputStream baos = null;
- byte[] buffer = new byte[2048];
- int length = 0;
-
- try
- {
- baos = new java.io.ByteArrayOutputStream();
- bais = new java.io.ByteArrayInputStream( bytes );
- gzis = new java.util.zip.GZIPInputStream( bais );
-
- while( ( length = gzis.read( buffer ) ) >= 0 )
- {
- baos.write(buffer,0,length);
- } // end while: reading input
-
- // No error? Get new bytes.
- bytes = baos.toByteArray();
-
- } // end try
- catch( java.io.IOException e )
- {
- // Just return originally-decoded bytes
- } // end catch
- finally
- {
- try{ baos.close(); } catch( Exception e ){}
- try{ gzis.close(); } catch( Exception e ){}
- try{ bais.close(); } catch( Exception e ){}
- } // end finally
-
- } // end if: gzipped
- } // end if: bytes.length >= 2
-
- return bytes;
- } // end decode
-
-
-
-
- /**
- * Attempts to decode Base64 data and deserialize a Java
- * Object within. Returns null if there was an error.
- *
- * @param encodedObject The Base64 data to decode
- * @return The decoded and deserialized object
- * @since 1.5
- */
- public static Object decodeToObject( String encodedObject )
- {
- // Decode and gunzip if necessary
- byte[] objBytes = decode( encodedObject );
-
- java.io.ByteArrayInputStream bais = null;
- java.io.ObjectInputStream ois = null;
- Object obj = null;
-
- try
- {
- bais = new java.io.ByteArrayInputStream( objBytes );
- ois = new java.io.ObjectInputStream( bais );
-
- obj = ois.readObject();
- } // end try
- catch( java.io.IOException e )
- {
- e.printStackTrace();
- obj = null;
- } // end catch
- catch( java.lang.ClassNotFoundException e )
- {
- e.printStackTrace();
- obj = null;
- } // end catch
- finally
- {
- try{ bais.close(); } catch( Exception e ){}
- try{ ois.close(); } catch( Exception e ){}
- } // end finally
-
- return obj;
- } // end decodeObject
-
-
-
- /**
- * Convenience method for encoding data to a file.
- *
- * @param dataToEncode byte array of data to encode in base64 form
- * @param filename Filename for saving encoded data
- * @return true if successful, false otherwise
- *
- * @since 2.1
- */
- public static boolean encodeToFile( byte[] dataToEncode, String filename )
- {
- boolean success = false;
- Base64.OutputStream bos = null;
- try
- {
- bos = new Base64.OutputStream(
- new java.io.FileOutputStream( filename ), Base64.ENCODE );
- bos.write( dataToEncode );
- success = true;
- } // end try
- catch( java.io.IOException e )
- {
-
- success = false;
- } // end catch: IOException
- finally
- {
- try{ bos.close(); } catch( Exception e ){}
- } // end finally
-
- return success;
- } // end encodeToFile
-
-
- /**
- * Convenience method for decoding data to a file.
- *
- * @param dataToDecode Base64-encoded data as a string
- * @param filename Filename for saving decoded data
- * @return true if successful, false otherwise
- *
- * @since 2.1
- */
- public static boolean decodeToFile( String dataToDecode, String filename )
- {
- boolean success = false;
- Base64.OutputStream bos = null;
- try
- {
- bos = new Base64.OutputStream(
- new java.io.FileOutputStream( filename ), Base64.DECODE );
- bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) );
- success = true;
- } // end try
- catch( java.io.IOException e )
- {
- success = false;
- } // end catch: IOException
- finally
- {
- try{ bos.close(); } catch( Exception e ){}
- } // end finally
-
- return success;
- } // end decodeToFile
-
-
-
-
- /**
- * Convenience method for reading a base64-encoded
- * file and decoding it.
- *
- * @param filename Filename for reading encoded data
- * @return decoded byte array or null if unsuccessful
- *
- * @since 2.1
- */
- public static byte[] decodeFromFile( String filename )
- {
- byte[] decodedData = null;
- Base64.InputStream bis = null;
- try
- {
- // Set up some useful variables
- java.io.File file = new java.io.File( filename );
- byte[] buffer = null;
- int length = 0;
- int numBytes = 0;
-
- // Check for size of file
- if( file.length() > Integer.MAX_VALUE )
- {
- System.err.println( "File is too big for this convenience method (" + file.length() + " bytes)." );
- return null;
- } // end if: file too big for int index
- buffer = new byte[ (int)file.length() ];
-
- // Open a stream
- bis = new Base64.InputStream(
- new java.io.BufferedInputStream(
- new java.io.FileInputStream( file ) ), Base64.DECODE );
-
- // Read until done
- while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 )
- length += numBytes;
-
- // Save in a variable to return
- decodedData = new byte[ length ];
- System.arraycopy( buffer, 0, decodedData, 0, length );
-
- } // end try
- catch( java.io.IOException e )
- {
- System.err.println( "Error decoding from file " + filename );
- } // end catch: IOException
- finally
- {
- try{ bis.close(); } catch( Exception e) {}
- } // end finally
-
- return decodedData;
- } // end decodeFromFile
-
-
-
- /**
- * Convenience method for reading a binary file
- * and base64-encoding it.
- *
- * @param filename Filename for reading binary data
- * @return base64-encoded string or null if unsuccessful
- *
- * @since 2.1
- */
- public static String encodeFromFile( String filename )
- {
- String encodedData = null;
- Base64.InputStream bis = null;
- try
- {
- // Set up some useful variables
- java.io.File file = new java.io.File( filename );
- byte[] buffer = new byte[ (int)(file.length() * 1.4) ];
- int length = 0;
- int numBytes = 0;
-
- // Open a stream
- bis = new Base64.InputStream(
- new java.io.BufferedInputStream(
- new java.io.FileInputStream( file ) ), Base64.ENCODE );
-
- // Read until done
- while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 )
- length += numBytes;
-
- // Save in a variable to return
- encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING );
-
- } // end try
- catch( java.io.IOException e )
- {
- System.err.println( "Error encoding from file " + filename );
- } // end catch: IOException
- finally
- {
- try{ bis.close(); } catch( Exception e) {}
- } // end finally
-
- return encodedData;
- } // end encodeFromFile
-
-
-
-
- /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */
-
-
-
- /**
- * A {@link Base64.InputStream} will read data from another
- * java.io.InputStream, given in the constructor,
- * and encode/decode to/from Base64 notation on the fly.
- *
- * @see Base64
- * @since 1.3
- */
- public static class InputStream extends java.io.FilterInputStream
- {
- private boolean encode; // Encoding or decoding
- private int position; // Current position in the buffer
- private byte[] buffer; // Small buffer holding converted data
- private int bufferLength; // Length of buffer (3 or 4)
- private int numSigBytes; // Number of meaningful bytes in the buffer
- private int lineLength;
- private boolean breakLines; // Break lines at less than 80 characters
-
-
- /**
- * Constructs a {@link Base64.InputStream} in DECODE mode.
- *
- * @param in the java.io.InputStream from which to read data.
- * @since 1.3
- */
- public InputStream( java.io.InputStream in )
- {
- this( in, DECODE );
- } // end constructor
-
-
- /**
- * Constructs a {@link Base64.InputStream} in
- * either ENCODE or DECODE mode.
- *
- * Valid options:
- * ENCODE or DECODE: Encode or Decode as data is read.
- * DONT_BREAK_LINES: don't break lines at 76 characters
- * (only meaningful when encoding)
- * Note: Technically, this makes your encoding non-compliant.
- *
- *
- * Example: new Base64.InputStream( in, Base64.DECODE )
- *
- *
- * @param in the java.io.InputStream from which to read data.
- * @param options Specified options
- * @see Base64#ENCODE
- * @see Base64#DECODE
- * @see Base64#DONT_BREAK_LINES
- * @since 2.0
- */
- public InputStream( java.io.InputStream in, int options )
- {
- super( in );
- this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
- this.encode = (options & ENCODE) == ENCODE;
- this.bufferLength = encode ? 4 : 3;
- this.buffer = new byte[ bufferLength ];
- this.position = -1;
- this.lineLength = 0;
- } // end constructor
-
- /**
- * Reads enough of the input stream to convert
- * to/from Base64 and returns the next byte.
- *
- * @return next byte
- * @since 1.3
- */
- public int read() throws java.io.IOException
- {
- // Do we need to get data?
- if( position < 0 )
- {
- if( encode )
- {
- byte[] b3 = new byte[3];
- int numBinaryBytes = 0;
- for( int i = 0; i < 3; i++ )
- {
- try
- {
- int b = in.read();
-
- // If end of stream, b is -1.
- if( b >= 0 )
- {
- b3[i] = (byte)b;
- numBinaryBytes++;
- } // end if: not end of stream
-
- } // end try: read
- catch( java.io.IOException e )
- {
- // Only a problem if we got no data at all.
- if( i == 0 )
- throw e;
-
- } // end catch
- } // end for: each needed input byte
-
- if( numBinaryBytes > 0 )
- {
- encode3to4( b3, 0, numBinaryBytes, buffer, 0 );
- position = 0;
- numSigBytes = 4;
- } // end if: got data
- else
- {
- return -1;
- } // end else
- } // end if: encoding
-
- // Else decoding
- else
- {
- byte[] b4 = new byte[4];
- int i = 0;
- for( i = 0; i < 4; i++ )
- {
- // Read four "meaningful" bytes:
- int b = 0;
- do{ b = in.read(); }
- while( b >= 0 && DECODABET[ b & 0x7f ] <= WHITE_SPACE_ENC );
-
- if( b < 0 )
- break; // Reads a -1 if end of stream
-
- b4[i] = (byte)b;
- } // end for: each needed input byte
-
- if( i == 4 )
- {
- numSigBytes = decode4to3( b4, 0, buffer, 0 );
- position = 0;
- } // end if: got four characters
- else if( i == 0 ){
- return -1;
- } // end else if: also padded correctly
- else
- {
- // Must have broken out from above.
- throw new java.io.IOException( "Improperly padded Base64 input." );
- } // end
-
- } // end else: decode
- } // end else: get data
-
- // Got data?
- if( position >= 0 )
- {
- // End of relevant data?
- if( /*!encode &&*/ position >= numSigBytes )
- return -1;
-
- if( encode && breakLines && lineLength >= MAX_LINE_LENGTH )
- {
- lineLength = 0;
- return '\n';
- } // end if
- else
- {
- lineLength++; // This isn't important when decoding
- // but throwing an extra "if" seems
- // just as wasteful.
-
- int b = buffer[ position++ ];
-
- if( position >= bufferLength )
- position = -1;
-
- return b & 0xFF; // This is how you "cast" a byte that's
- // intended to be unsigned.
- } // end else
- } // end if: position >= 0
-
- // Else error
- else
- {
- // When JDK1.4 is more accepted, use an assertion here.
- throw new java.io.IOException( "Error in Base64 code reading stream." );
- } // end else
- } // end read
-
-
- /**
- * Calls {@link #read()} repeatedly until the end of stream
- * is reached or len bytes are read.
- * Returns number of bytes read into array or -1 if
- * end of stream is encountered.
- *
- * @param dest array to hold values
- * @param off offset for array
- * @param len max number of bytes to read into array
- * @return bytes read into array or -1 if end of stream is encountered.
- * @since 1.3
- */
- public int read( byte[] dest, int off, int len ) throws java.io.IOException
- {
- int i;
- int b;
- for( i = 0; i < len; i++ )
- {
- b = read();
-
- //if( b < 0 && i == 0 )
- // return -1;
-
- if( b >= 0 )
- dest[off + i] = (byte)b;
- else if( i == 0 )
- return -1;
- else
- break; // Out of 'for' loop
- } // end for: each byte read
- return i;
- } // end read
-
- } // end inner class InputStream
-
-
-
-
-
-
- /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */
-
-
-
- /**
- * A {@link Base64.OutputStream} will write data to another
- * java.io.OutputStream, given in the constructor,
- * and encode/decode to/from Base64 notation on the fly.
- *
- * @see Base64
- * @since 1.3
- */
- public static class OutputStream extends java.io.FilterOutputStream
- {
- private boolean encode;
- private int position;
- private byte[] buffer;
- private int bufferLength;
- private int lineLength;
- private boolean breakLines;
- private byte[] b4; // Scratch used in a few places
- private boolean suspendEncoding;
-
- /**
- * Constructs a {@link Base64.OutputStream} in ENCODE mode.
- *
- * @param out the java.io.OutputStream to which data will be written.
- * @since 1.3
- */
- public OutputStream( java.io.OutputStream out )
- {
- this( out, ENCODE );
- } // end constructor
-
-
- /**
- * Constructs a {@link Base64.OutputStream} in
- * either ENCODE or DECODE mode.
- *
- * Valid options:
- * ENCODE or DECODE: Encode or Decode as data is read.
- * DONT_BREAK_LINES: don't break lines at 76 characters
- * (only meaningful when encoding)
- * Note: Technically, this makes your encoding non-compliant.
- *
- *
- * Example: new Base64.OutputStream( out, Base64.ENCODE )
- *
- * @param out the java.io.OutputStream to which data will be written.
- * @param options Specified options.
- * @see Base64#ENCODE
- * @see Base64#DECODE
- * @see Base64#DONT_BREAK_LINES
- * @since 1.3
- */
- public OutputStream( java.io.OutputStream out, int options )
- {
- super( out );
- this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
- this.encode = (options & ENCODE) == ENCODE;
- this.bufferLength = encode ? 3 : 4;
- this.buffer = new byte[ bufferLength ];
- this.position = 0;
- this.lineLength = 0;
- this.suspendEncoding = false;
- this.b4 = new byte[4];
- } // end constructor
-
-
- /**
- * Writes the byte to the output stream after
- * converting to/from Base64 notation.
- * When encoding, bytes are buffered three
- * at a time before the output stream actually
- * gets a write() call.
- * When decoding, bytes are buffered four
- * at a time.
- *
- * @param theByte the byte to write
- * @since 1.3
- */
- public void write(int theByte) throws java.io.IOException
- {
- // Encoding suspended?
- if( suspendEncoding )
- {
- super.out.write( theByte );
- return;
- } // end if: supsended
-
- // Encode?
- if( encode )
- {
- buffer[ position++ ] = (byte)theByte;
- if( position >= bufferLength ) // Enough to encode.
- {
- out.write( encode3to4( b4, buffer, bufferLength ) );
-
- lineLength += 4;
- if( breakLines && lineLength >= MAX_LINE_LENGTH )
- {
- out.write( NEW_LINE );
- lineLength = 0;
- } // end if: end of line
-
- position = 0;
- } // end if: enough to output
- } // end if: encoding
-
- // Else, Decoding
- else
- {
- // Meaningful Base64 character?
- if( DECODABET[ theByte & 0x7f ] > WHITE_SPACE_ENC )
- {
- buffer[ position++ ] = (byte)theByte;
- if( position >= bufferLength ) // Enough to output.
- {
- int len = Base64.decode4to3( buffer, 0, b4, 0 );
- out.write( b4, 0, len );
- //out.write( Base64.decode4to3( buffer ) );
- position = 0;
- } // end if: enough to output
- } // end if: meaningful base64 character
- else if( DECODABET[ theByte & 0x7f ] != WHITE_SPACE_ENC )
- {
- throw new java.io.IOException( "Invalid character in Base64 data." );
- } // end else: not white space either
- } // end else: decoding
- } // end write
-
-
-
- /**
- * Calls {@link #write(int)} repeatedly until len
- * bytes are written.
- *
- * @param theBytes array from which to read bytes
- * @param off offset for array
- * @param len max number of bytes to read into array
- * @since 1.3
- */
- public void write( byte[] theBytes, int off, int len ) throws java.io.IOException
- {
- // Encoding suspended?
- if( suspendEncoding )
- {
- super.out.write( theBytes, off, len );
- return;
- } // end if: supsended
-
- for( int i = 0; i < len; i++ )
- {
- write( theBytes[ off + i ] );
- } // end for: each byte written
-
- } // end write
-
-
-
- /**
- * Method added by PHIL. [Thanks, PHIL. -Rob]
- * This pads the buffer without closing the stream.
- */
- public void flushBase64() throws java.io.IOException
- {
- if( position > 0 )
- {
- if( encode )
- {
- out.write( encode3to4( b4, buffer, position ) );
- position = 0;
- } // end if: encoding
- else
- {
- throw new java.io.IOException( "Base64 input not properly padded." );
- } // end else: decoding
- } // end if: buffer partially full
-
- } // end flush
-
-
- /**
- * Flushes and closes (I think, in the superclass) the stream.
- *
- * @since 1.3
- */
- public void close() throws java.io.IOException
- {
- // 1. Ensure that pending characters are written
- flushBase64();
-
- // 2. Actually close the stream
- // Base class both flushes and closes.
- super.close();
-
- buffer = null;
- out = null;
- } // end close
-
-
-
- /**
- * Suspends encoding of the stream.
- * May be helpful if you need to embed a piece of
- * base640-encoded data in a stream.
- *
- * @since 1.5.1
- */
- public void suspendEncoding() throws java.io.IOException
- {
- flushBase64();
- this.suspendEncoding = true;
- } // end suspendEncoding
-
-
- /**
- * Resumes encoding of the stream.
- * May be helpful if you need to embed a piece of
- * base640-encoded data in a stream.
- *
- * @since 1.5.1
- */
- public void resumeEncoding()
- {
- this.suspendEncoding = false;
- } // end resumeEncoding
-
-
-
- } // end inner class OutputStream
-
-
-} // end class Base64
diff --git a/src/org/.cvsignore b/src/org/.cvsignore
new file mode 100644
index 0000000..cd5dc09
--- /dev/null
+++ b/src/org/.cvsignore
@@ -0,0 +1,5 @@
+*~
+*#
+*.class
+.DS_Store
+Thumbs.db
diff --git a/src/org/catacombae/.cvsignore b/src/org/catacombae/.cvsignore
new file mode 100644
index 0000000..cd5dc09
--- /dev/null
+++ b/src/org/catacombae/.cvsignore
@@ -0,0 +1,5 @@
+*~
+*#
+*.class
+.DS_Store
+Thumbs.db
diff --git a/src/org/catacombae/dmg/encrypted/.cvsignore b/src/org/catacombae/dmg/encrypted/.cvsignore
new file mode 100644
index 0000000..90d40b4
--- /dev/null
+++ b/src/org/catacombae/dmg/encrypted/.cvsignore
@@ -0,0 +1,6 @@
+*~
+*#
+*.class
+.DS_Store
+Thumbs.db
+SecretPassword.java
diff --git a/src/org/catacombae/dmg/encrypted/Assert.java b/src/org/catacombae/dmg/encrypted/Assert.java
new file mode 100644
index 0000000..9f2ee55
--- /dev/null
+++ b/src/org/catacombae/dmg/encrypted/Assert.java
@@ -0,0 +1,37 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.catacombae.dmg.encrypted;
+
+/**
+ *
+ * @author erik
+ */
+public class Assert {
+ public static void eq(long a, long b) {
+ eq(a, b, null);
+ }
+ public static void eq(long a, long b, String message) {
+ if(a != b)
+ throw new InvalidAssertionException("Equality asserion " + a +
+ " == " + b + " failed!" +
+ (message!=null ? " Message: "+message : ""));
+ }
+ public static void neq(long a, long b) {
+ neq(a, b, null);
+ }
+ public static void neq(long a, long b, String message) {
+ if(a == b)
+ throw new InvalidAssertionException("Non-equality asserion " + a +
+ " != " + b + " failed!" +
+ (message!=null ? " Message: "+message : ""));
+ }
+
+ public static class InvalidAssertionException extends RuntimeException {
+ public InvalidAssertionException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/src/org/catacombae/dmg/encrypted/CEncryptedEncodingUtil.java b/src/org/catacombae/dmg/encrypted/CEncryptedEncodingUtil.java
new file mode 100644
index 0000000..e9e3291
--- /dev/null
+++ b/src/org/catacombae/dmg/encrypted/CEncryptedEncodingUtil.java
@@ -0,0 +1,57 @@
+/*-
+ * Copyright (C) 2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmg.encrypted;
+
+import org.catacombae.dmgextractor.Util;
+import org.catacombae.io.ReadableRandomAccessStream;
+
+/**
+ *
+ * @author erik
+ */
+class CEncryptedEncodingUtil {
+ private static final String V1_SIGNATURE = "cdsaencr";
+ private static final String V2_SIGNATURE = "encrcdsa";
+
+ public static int detectVersion(ReadableRandomAccessStream stream) {
+ byte[] signatureBytes = new byte[8];
+ try {
+ stream.seek(0);
+ stream.readFully(signatureBytes);
+ if(Util.toASCIIString(signatureBytes).equals(V2_SIGNATURE))
+ return 2;
+ } catch(Exception e) {
+ System.err.println("Non-critical exception while detecting version 2" +
+ " CEncryptedEncoding header:");
+ e.printStackTrace();
+ }
+
+ try {
+ stream.seek(stream.length()-signatureBytes.length);
+ stream.readFully(signatureBytes);
+ if(Util.toASCIIString(signatureBytes).equals(V1_SIGNATURE))
+ return 1;
+ } catch(Exception e) {
+ System.err.println("Non-critical exception while detecting version 1" +
+ " CEncryptedEncoding header:");
+ e.printStackTrace();
+ }
+
+ return -1;
+ }
+}
diff --git a/src/org/catacombae/dmg/encrypted/CommonCEncryptedEncodingHeader.java b/src/org/catacombae/dmg/encrypted/CommonCEncryptedEncodingHeader.java
new file mode 100644
index 0000000..ae19574
--- /dev/null
+++ b/src/org/catacombae/dmg/encrypted/CommonCEncryptedEncodingHeader.java
@@ -0,0 +1,289 @@
+/*-
+ * Copyright (C) 2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ *
+ * This code was written from studying vfdecrypt, Copyright (c) 2006
+ * Ralf-Philipp Weinmann
+ * Jacob Appelbaum
+ * Christian Fromme
+ *
+ * [I'm not sure if their copyright and license terms need to be applied,
+ * but in case they do, the original license terms are reprinted below
+ * as required by the license.]
+ *
+ * The vfdecrypt license says:
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ */
+
+package org.catacombae.dmg.encrypted;
+
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import org.catacombae.dmgextractor.Util;
+
+/**
+ *
+ * @author erik
+ */
+public abstract class CommonCEncryptedEncodingHeader {
+ public static CommonCEncryptedEncodingHeader create(V1Header header) {
+ return new V1Implementation(header);
+ }
+
+ public static CommonCEncryptedEncodingHeader create(V2Header header) {
+ return new V2Implementation(header);
+ }
+
+ public abstract int getBlockSize();
+ public abstract long getBlockDataStart();
+ /**
+ * Returns the salt for the key derivation function.
+ * @return the salt for the key derivation function.
+ */
+ public abstract byte[] getKdfSalt();
+
+ /**
+ * Returns the iteration count for the key derivation function.
+ * @return the iteration count for the key derivation function.
+ */
+ public abstract int getKdfIterationCount();
+
+
+ /**
+ * Returns the initialization vector for the key decryption cipher.
+ * @return the initialization vector for the key decryption cipher.
+ */
+ public abstract byte[] getUnwrapInitializationVector();
+
+ /**
+ * Returns the amount of bytes at the end of the stream that are not part
+ * of the block data area.
+ * @return the amount of bytes at the end of the stream that are not part
+ * of the block data area.
+ */
+ public abstract long getTrailingReservedBytes();
+
+ /**
+ * Returns the length of the data that has been encrypted. This length may
+ * not be aligned with the encryption block size, in which case the last
+ * encryption block will have been padded at the end
+ * @return the length of the data that has been encrypted.
+ */
+ public abstract long getEncryptedDataLength();
+
+ public abstract KeySet unwrapKeys(Key derivedKey, Cipher cph)
+ throws GeneralSecurityException, InvalidKeyException,
+ InvalidAlgorithmParameterException;
+
+ public static class KeySet {
+ private final byte[] aesKey;
+ private final byte[] hmacSha1Key;
+
+ private KeySet(byte[] aesKey, byte[] hmacSha1Key) {
+ this.aesKey = aesKey;
+ this.hmacSha1Key = hmacSha1Key;
+ }
+
+ public byte[] getAesKey() { return aesKey; }
+ public byte[] getHmacSha1Key() { return hmacSha1Key; }
+
+ public void clearData() {
+ Util.zero(aesKey);
+ Util.zero(hmacSha1Key);
+ }
+ }
+
+ private static class V1Implementation extends CommonCEncryptedEncodingHeader {
+ private final V1Header header;
+
+ public V1Implementation(V1Header header) {
+ this.header = header;
+ }
+
+ @Override
+ public int getBlockSize() {
+ return header.getBlockSize();
+ }
+
+ @Override
+ public long getBlockDataStart() {
+ return 0; // By design (I think).
+ }
+
+ @Override
+ public long getTrailingReservedBytes() {
+ return header.length();
+ }
+
+ @Override
+ public byte[] getKdfSalt() {
+ return Util.createCopy(header.getKdfSalt(), 0, header.getKdfSaltLen());
+ }
+
+ @Override
+ public int getKdfIterationCount() {
+ return header.getKdfIterationCount();
+ }
+
+ @Override
+ public byte[] getUnwrapInitializationVector() {
+ return header.getUnwrapIv();
+ }
+
+ @Override
+ public long getEncryptedDataLength() {
+ return header.getDecryptedDataLength(); // Confusion!
+ }
+
+ @Override
+ public KeySet unwrapKeys(Key derivedKey, Cipher cph)
+ throws GeneralSecurityException, InvalidKeyException,
+ InvalidAlgorithmParameterException {
+ byte[] aesKey = unwrapIndividualKey(derivedKey, cph,
+ Util.createCopy(header.getWrappedAesKey(), 0, header.getLenWrappedAesKey()));
+
+ byte[] hmacSha1Key = unwrapIndividualKey(derivedKey, cph,
+ Util.createCopy(header.getWrappedHmacSha1Key(), 0, header.getLenWrappedHmacSha1Key()));
+ return new KeySet(aesKey, hmacSha1Key);
+ }
+
+ public byte[] unwrapIndividualKey(Key key, Cipher cph, byte[] wrappedKey)
+ throws InvalidKeyException, InvalidAlgorithmParameterException,
+ GeneralSecurityException {
+ Debug.print("unwrapIndividualKey(" + key + ", " + cph + ", byte[" + wrappedKey.length + "]);");
+ Debug.print(" wrappedKey: 0x" + Util.byteArrayToHexString(wrappedKey));
+
+ final byte[] initialIv = new byte[] {
+ (byte) 0x4a, (byte) 0xdd, (byte) 0xa2, (byte) 0x2c,
+ (byte) 0x79, (byte) 0xe8, (byte) 0x21, (byte) 0x05
+ };
+ // irX = intermediate result X
+
+ byte[] ir1 = new byte[wrappedKey.length];
+ cph.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(initialIv));
+ int ir1Len = cph.doFinal(wrappedKey, 0, wrappedKey.length, ir1, 0);
+ Debug.print(" ir1: 0x" + Util.byteArrayToHexString(ir1, 0, ir1Len));
+ Debug.print(" ir1Len: " + ir1Len);
+
+ byte[] ir2 = new byte[ir1Len];
+ for(int i = 0; i < ir1Len; ++i)
+ ir2[i] = ir1[ir1Len-1-i];
+ Debug.print(" ir2: 0x" + Util.byteArrayToHexString(ir2));
+ Debug.print(" ir2.length: " + ir2.length);
+
+ byte[] ir3 = new byte[ir2.length-8];
+ cph.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ir2, 0, 8));
+ int ir3Len = cph.doFinal(ir2, 8, ir2.length-8, ir3, 0);
+ Debug.print(" ir3: 0x" + Util.byteArrayToHexString(ir3, 0, ir3Len));
+ Debug.print(" ir3Len: " + ir3Len);
+
+ byte[] result = Util.createCopy(ir3, 4, ir3Len-4);
+ Util.zero(ir1, ir2, ir3);
+ return result;
+ }
+ }
+
+ private static class V2Implementation extends CommonCEncryptedEncodingHeader {
+ private final V2Header header;
+
+ public V2Implementation(V2Header header) {
+ this.header = header;
+ }
+
+ @Override
+ public int getBlockSize() {
+ return header.getBlockSize();
+ }
+
+ @Override
+ public long getBlockDataStart() {
+ return header.getOffsetToDataStart();
+ }
+
+ @Override
+ public long getTrailingReservedBytes() {
+ return 0;
+ }
+
+ @Override
+ public byte[] getKdfSalt() {
+ return Util.createCopy(header.getKdfSalt(), 0, header.getKdfSaltLen());
+ }
+
+ @Override
+ public int getKdfIterationCount() {
+ return header.getKdfIterationCount();
+ }
+
+ @Override
+ public byte[] getUnwrapInitializationVector() {
+ return Util.createCopy(header.getBlobEncIv(), 0, header.getBlobEncIvSize());
+ }
+
+ private byte[] getEncryptedKeyBlob() {
+ return Util.createCopy(header.getEncryptedKeyblob(), 0, header.getEncryptedKeyblobSize());
+ }
+
+ @Override
+ public long getEncryptedDataLength() {
+ return header.getEncryptedDataLength();
+ }
+
+ @Override
+ public KeySet unwrapKeys(Key derivedKey, Cipher cph)
+ throws InvalidKeyException, InvalidAlgorithmParameterException, GeneralSecurityException {
+ Debug.print("V2Implementation.unwrapKeys(" + derivedKey + ", " + cph + ");");
+ cph.init(Cipher.DECRYPT_MODE, derivedKey, new IvParameterSpec(getUnwrapInitializationVector()));
+
+ byte[] encryptedKeyBlob = getEncryptedKeyBlob();
+ Debug.print(" encryptedKeyBlob.length=" + encryptedKeyBlob.length);
+ byte[] decryptedKeyBlob = new byte[encryptedKeyBlob.length];
+ Debug.print(" doing update....");
+ int bp = cph.update(encryptedKeyBlob, 0, encryptedKeyBlob.length, decryptedKeyBlob);
+ Debug.print(" bp == " + bp);
+ Debug.print(" doing final....");
+ bp += cph.doFinal(decryptedKeyBlob, bp);
+ Debug.print(" bp == " + bp);
+
+ Debug.print(" decryptedKeyBlob: 0x" + Util.byteArrayToHexString(decryptedKeyBlob));
+ byte[] aesKey = new byte[16];
+ byte[] hmacSha1Key = new byte[20];
+ System.arraycopy(decryptedKeyBlob, 0, aesKey, 0, 16);
+ Debug.print(" aesKey: 0x" + Util.byteArrayToHexString(aesKey));
+ System.arraycopy(decryptedKeyBlob, 16, hmacSha1Key, 0, 20);
+ Debug.print(" hmacSha1Key: 0x" + Util.byteArrayToHexString(hmacSha1Key));
+
+ Util.zero(decryptedKeyBlob); // No unused secret data in memory.
+ Debug.print(" decryptedKeyBlob: 0x" + Util.byteArrayToHexString(decryptedKeyBlob));
+
+ Debug.print("returning from V2Implementation.unwrapKeys...");
+ return new KeySet(aesKey, hmacSha1Key);
+ }
+ }
+}
diff --git a/src/org/catacombae/dmg/encrypted/Debug.java b/src/org/catacombae/dmg/encrypted/Debug.java
new file mode 100644
index 0000000..f16098f
--- /dev/null
+++ b/src/org/catacombae/dmg/encrypted/Debug.java
@@ -0,0 +1,50 @@
+/*-
+ * Copyright (C) 2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmg.encrypted;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ *
+ * @author erik
+ */
+class Debug {
+ private static class NullOutputStream extends OutputStream {
+
+ @Override
+ public void write(int b) throws IOException {}
+
+ }
+ private static boolean debugEnabled = false;
+ public static final PrintStream ps;
+
+ static {
+ if(debugEnabled)
+ ps = System.err;
+ else
+ ps = new PrintStream(new NullOutputStream());
+ }
+
+ public static void print(String s) {
+ if(debugEnabled)
+ ps.println(s);
+ }
+}
diff --git a/src/org/catacombae/dmg/encrypted/ExperimentalV1Header.java b/src/org/catacombae/dmg/encrypted/ExperimentalV1Header.java
new file mode 100644
index 0000000..0bf84ca
--- /dev/null
+++ b/src/org/catacombae/dmg/encrypted/ExperimentalV1Header.java
@@ -0,0 +1,277 @@
+package org.catacombae.dmg.encrypted;
+
+import java.io.PrintStream;
+import org.catacombae.dmgextractor.Util;
+
+/** This class was generated by CStructToJavaClass. */
+public class ExperimentalV1Header {
+ /*
+ * struct ExperimentalV1Header
+ * size: 1276 bytes
+ * description:
+ *
+ * BP Size Type Identifier Description
+ * -------------------------------------------------------------------------------------------------------------------------------
+ * 0 1*16 uint8_t[16] unknown0 Unknown data.
+ * 16 4 uint32_t blockSize Block size of the encrypted block data.
+ * 20 4 uint32_t unknownInt20 Unknown integer.
+ * 24 4 uint32_t unknownInt24 Unknown integer.
+ * 28 4 uint32_t unknownInt28 Unknown integer.
+ * 32 4 uint32_t unknownInt32 Unknown integer.
+ * 36 4 uint32_t unknownInt36 Unknown integer.
+ * 40 4 uint32_t unknownInt40 Unknown integer.
+ * 44 4 uint32_t unknownInt44 Unknown integer.
+ * 48 4 uint32_t kdfIterationCount Iteration count for the key derivation function (normally 1000).
+ * 52 4 uint32_t kdfSaltLen Length of kdfSalt in bytes.
+ * 56 1*32 uint8_t[32] kdfSalt Salt value for the key derivation function
+ * 88 4 uint32_t laban Unknown variable with observed value 16/0x10.
+ * 92 4 uint32_t edward Unknown variable with observed value 5/0x5.
+ * 96 4 uint32_t palle Unknown variable with observed value 0x80000001.
+ * 100 4 uint32_t lisa Unknown variable with observed value 128/0x80.
+ * 104 1*32 uint8_t[32] unwrapIv Initialization Vector for encryption-key unwrapping.
+ * 136 4 uint32_t lenWrappedAesKey Length of wrappedAesKey in bytes (max 256).
+ * 140 1*256 uint8_t[256] wrappedAesKey The AES key (wrapped).
+ * 396 4 uint32_t unknownInt396 Unknown integer (observed value: 91/0x5B).
+ * 400 4 uint32_t unknownInt400 Unknown integer (observed value: 160/0xA0).
+ * 404 1*32 uint8_t[32] unknown404 Unknown data (observed: 8 bytes filled, rest 0).
+ * 436 4 uint32_t lenWrappedHmacSha1Key Length of wrappedHmacSha1Key in bytes (max 256).
+ * 440 1*256 uint8_t[256] wrappedHmacSha1Key The HMAC SHA-1 key (wrapped).
+ * 696 4 uint32_t unknownInt696 Unknown integer (observed value: 91/0x5B).
+ * 700 4 uint32_t unknownInt700 Unknown integer (observed value: 160/0xA0).
+ * 704 1*32 uint8_t[32] unknown704 Unknown data (obs. 8 bytes filled, rest 0).
+ * 736 4 uint32_t lenWrappedIntegrityKey Length of wrappedIntegrityKey.
+ * 740 1*256 uint8_t[256] wrappedIntegrityKey Integrity key.
+ * 996 4 uint32_t lenUnknown1000 Length of unknown1000.
+ * 1000 1*256 uint8_t[256] unknown1000 Unknown key-like field with length specified by unknownRnd95GaLen (max 256).
+ * 1256 8 uint64_t decryptedDataLength Length in bytes of the underlying data stream.
+ * 1264 4 uint32_t possibleHeaderVersion Could be a variable indicating header version (observed: 1/0x1).
+ * 1268 1*8 uint8_t[8] signature Header signature (ASCII: 'cdsaencr').
+ */
+
+ public static final int STRUCTSIZE = 1276;
+
+ private final byte[] unknown0 = new byte[1*16];
+ private final byte[] blockSize = new byte[4];
+ private final byte[] unknownInt20 = new byte[4];
+ private final byte[] unknownInt24 = new byte[4];
+ private final byte[] unknownInt28 = new byte[4];
+ private final byte[] unknownInt32 = new byte[4];
+ private final byte[] unknownInt36 = new byte[4];
+ private final byte[] unknownInt40 = new byte[4];
+ private final byte[] unknownInt44 = new byte[4];
+ private final byte[] kdfIterationCount = new byte[4];
+ private final byte[] kdfSaltLen = new byte[4];
+ private final byte[] kdfSalt = new byte[1*32];
+ private final byte[] laban = new byte[4];
+ private final byte[] edward = new byte[4];
+ private final byte[] palle = new byte[4];
+ private final byte[] lisa = new byte[4];
+ private final byte[] unwrapIv = new byte[1*32];
+ private final byte[] lenWrappedAesKey = new byte[4];
+ private final byte[] wrappedAesKey = new byte[1*256];
+ private final byte[] unknownInt396 = new byte[4];
+ private final byte[] unknownInt400 = new byte[4];
+ private final byte[] unknown404 = new byte[1*32];
+ private final byte[] lenWrappedHmacSha1Key = new byte[4];
+ private final byte[] wrappedHmacSha1Key = new byte[1*256];
+ private final byte[] unknownInt696 = new byte[4];
+ private final byte[] unknownInt700 = new byte[4];
+ private final byte[] unknown704 = new byte[1*32];
+ private final byte[] lenWrappedIntegrityKey = new byte[4];
+ private final byte[] wrappedIntegrityKey = new byte[1*256];
+ private final byte[] lenUnknown1000 = new byte[4];
+ private final byte[] unknown1000 = new byte[1*256];
+ private final byte[] decryptedDataLength = new byte[8];
+ private final byte[] possibleHeaderVersion = new byte[4];
+ private final byte[] signature = new byte[1*8];
+
+ public ExperimentalV1Header(byte[] data, int offset) {
+ System.arraycopy(data, offset+0, unknown0, 0, 1*16);
+ System.arraycopy(data, offset+16, blockSize, 0, 4);
+ System.arraycopy(data, offset+20, unknownInt20, 0, 4);
+ System.arraycopy(data, offset+24, unknownInt24, 0, 4);
+ System.arraycopy(data, offset+28, unknownInt28, 0, 4);
+ System.arraycopy(data, offset+32, unknownInt32, 0, 4);
+ System.arraycopy(data, offset+36, unknownInt36, 0, 4);
+ System.arraycopy(data, offset+40, unknownInt40, 0, 4);
+ System.arraycopy(data, offset+44, unknownInt44, 0, 4);
+ System.arraycopy(data, offset+48, kdfIterationCount, 0, 4);
+ System.arraycopy(data, offset+52, kdfSaltLen, 0, 4);
+ System.arraycopy(data, offset+56, kdfSalt, 0, 1*32);
+ System.arraycopy(data, offset+88, laban, 0, 4);
+ System.arraycopy(data, offset+92, edward, 0, 4);
+ System.arraycopy(data, offset+96, palle, 0, 4);
+ System.arraycopy(data, offset+100, lisa, 0, 4);
+ System.arraycopy(data, offset+104, unwrapIv, 0, 1*32);
+ System.arraycopy(data, offset+136, lenWrappedAesKey, 0, 4);
+ System.arraycopy(data, offset+140, wrappedAesKey, 0, 1*256);
+ System.arraycopy(data, offset+396, unknownInt396, 0, 4);
+ System.arraycopy(data, offset+400, unknownInt400, 0, 4);
+ System.arraycopy(data, offset+404, unknown404, 0, 1*32);
+ System.arraycopy(data, offset+436, lenWrappedHmacSha1Key, 0, 4);
+ System.arraycopy(data, offset+440, wrappedHmacSha1Key, 0, 1*256);
+ System.arraycopy(data, offset+696, unknownInt696, 0, 4);
+ System.arraycopy(data, offset+700, unknownInt700, 0, 4);
+ System.arraycopy(data, offset+704, unknown704, 0, 1*32);
+ System.arraycopy(data, offset+736, lenWrappedIntegrityKey, 0, 4);
+ System.arraycopy(data, offset+740, wrappedIntegrityKey, 0, 1*256);
+ System.arraycopy(data, offset+996, lenUnknown1000, 0, 4);
+ System.arraycopy(data, offset+1000, unknown1000, 0, 1*256);
+ System.arraycopy(data, offset+1256, decryptedDataLength, 0, 8);
+ System.arraycopy(data, offset+1264, possibleHeaderVersion, 0, 4);
+ System.arraycopy(data, offset+1268, signature, 0, 1*8);
+ }
+
+ public static int length() { return STRUCTSIZE; }
+
+ /** Unknown data. */
+ public byte[] getUnknown0() { return Util.readByteArrayBE(unknown0); }
+ /** Block size of the encrypted block data. */
+ public int getBlockSize() { return Util.readIntBE(blockSize); }
+ /** Unknown integer. */
+ public int getUnknownInt20() { return Util.readIntBE(unknownInt20); }
+ /** Unknown integer. */
+ public int getUnknownInt24() { return Util.readIntBE(unknownInt24); }
+ /** Unknown integer. */
+ public int getUnknownInt28() { return Util.readIntBE(unknownInt28); }
+ /** Unknown integer. */
+ public int getUnknownInt32() { return Util.readIntBE(unknownInt32); }
+ /** Unknown integer. */
+ public int getUnknownInt36() { return Util.readIntBE(unknownInt36); }
+ /** Unknown integer. */
+ public int getUnknownInt40() { return Util.readIntBE(unknownInt40); }
+ /** Unknown integer. */
+ public int getUnknownInt44() { return Util.readIntBE(unknownInt44); }
+ /** Iteration count for the key derivation function (normally 1000). */
+ public int getKdfIterationCount() { return Util.readIntBE(kdfIterationCount); }
+ /** Length of kdfSalt in bytes. */
+ public int getKdfSaltLen() { return Util.readIntBE(kdfSaltLen); }
+ /** Salt value for the key derivation function */
+ public byte[] getKdfSalt() { return Util.readByteArrayBE(kdfSalt); }
+ /** Unknown variable with observed value 16/0x10. */
+ public int getLaban() { return Util.readIntBE(laban); }
+ /** Unknown variable with observed value 5/0x5. */
+ public int getEdward() { return Util.readIntBE(edward); }
+ /** Unknown variable with observed value 0x80000001. */
+ public int getPalle() { return Util.readIntBE(palle); }
+ /** Unknown variable with observed value 128/0x80. */
+ public int getLisa() { return Util.readIntBE(lisa); }
+ /** Initialization Vector for encryption-key unwrapping. */
+ public byte[] getUnwrapIv() { return Util.readByteArrayBE(unwrapIv); }
+ /** Length of wrappedAesKey in bytes (max 256). */
+ public int getLenWrappedAesKey() { return Util.readIntBE(lenWrappedAesKey); }
+ /** The AES key (wrapped). */
+ public byte[] getWrappedAesKey() { return Util.readByteArrayBE(wrappedAesKey); }
+ /** Unknown integer (observed value: 91/0x5B). */
+ public int getUnknownInt396() { return Util.readIntBE(unknownInt396); }
+ /** Unknown integer (observed value: 160/0xA0). */
+ public int getUnknownInt400() { return Util.readIntBE(unknownInt400); }
+ /** Unknown data (observed: 8 bytes filled, rest 0). */
+ public byte[] getUnknown404() { return Util.readByteArrayBE(unknown404); }
+ /** Length of wrappedHmacSha1Key in bytes (max 256). */
+ public int getLenWrappedHmacSha1Key() { return Util.readIntBE(lenWrappedHmacSha1Key); }
+ /** The HMAC SHA-1 key (wrapped). */
+ public byte[] getWrappedHmacSha1Key() { return Util.readByteArrayBE(wrappedHmacSha1Key); }
+ /** Unknown integer (observed value: 91/0x5B). */
+ public int getUnknownInt696() { return Util.readIntBE(unknownInt696); }
+ /** Unknown integer (observed value: 160/0xA0). */
+ public int getUnknownInt700() { return Util.readIntBE(unknownInt700); }
+ /** Unknown data (obs. 8 bytes filled, rest 0). */
+ public byte[] getUnknown704() { return Util.readByteArrayBE(unknown704); }
+ /** Length of wrappedIntegrityKey. */
+ public int getLenWrappedIntegrityKey() { return Util.readIntBE(lenWrappedIntegrityKey); }
+ /** Integrity key. */
+ public byte[] getWrappedIntegrityKey() { return Util.readByteArrayBE(wrappedIntegrityKey); }
+ /** Length of unknown1000. */
+ public int getLenUnknown1000() { return Util.readIntBE(lenUnknown1000); }
+ /** Unknown key-like field with length specified by unknownRnd95GaLen (max 256). */
+ public byte[] getUnknown1000() { return Util.readByteArrayBE(unknown1000); }
+ /** Length in bytes of the underlying data stream. */
+ public long getDecryptedDataLength() { return Util.readLongBE(decryptedDataLength); }
+ /** Could be a variable indicating header version (observed: 1/0x1). */
+ public int getPossibleHeaderVersion() { return Util.readIntBE(possibleHeaderVersion); }
+ /** Header signature (ASCII: 'cdsaencr'). */
+ public byte[] getSignature() { return Util.readByteArrayBE(signature); }
+
+ public void printFields(PrintStream ps, String prefix) {
+ ps.println(prefix + " unknown0: " + getUnknown0());
+ ps.println(prefix + " blockSize: " + getBlockSize());
+ ps.println(prefix + " unknownInt20: " + getUnknownInt20());
+ ps.println(prefix + " unknownInt24: " + getUnknownInt24());
+ ps.println(prefix + " unknownInt28: " + getUnknownInt28());
+ ps.println(prefix + " unknownInt32: " + getUnknownInt32());
+ ps.println(prefix + " unknownInt36: " + getUnknownInt36());
+ ps.println(prefix + " unknownInt40: " + getUnknownInt40());
+ ps.println(prefix + " unknownInt44: " + getUnknownInt44());
+ ps.println(prefix + " kdfIterationCount: " + getKdfIterationCount());
+ ps.println(prefix + " kdfSaltLen: " + getKdfSaltLen());
+ ps.println(prefix + " kdfSalt: " + getKdfSalt());
+ ps.println(prefix + " laban: " + getLaban());
+ ps.println(prefix + " edward: " + getEdward());
+ ps.println(prefix + " palle: " + getPalle());
+ ps.println(prefix + " lisa: " + getLisa());
+ ps.println(prefix + " unwrapIv: " + getUnwrapIv());
+ ps.println(prefix + " lenWrappedAesKey: " + getLenWrappedAesKey());
+ ps.println(prefix + " wrappedAesKey: " + getWrappedAesKey());
+ ps.println(prefix + " unknownInt396: " + getUnknownInt396());
+ ps.println(prefix + " unknownInt400: " + getUnknownInt400());
+ ps.println(prefix + " unknown404: " + getUnknown404());
+ ps.println(prefix + " lenWrappedHmacSha1Key: " + getLenWrappedHmacSha1Key());
+ ps.println(prefix + " wrappedHmacSha1Key: " + getWrappedHmacSha1Key());
+ ps.println(prefix + " unknownInt696: " + getUnknownInt696());
+ ps.println(prefix + " unknownInt700: " + getUnknownInt700());
+ ps.println(prefix + " unknown704: " + getUnknown704());
+ ps.println(prefix + " lenWrappedIntegrityKey: " + getLenWrappedIntegrityKey());
+ ps.println(prefix + " wrappedIntegrityKey: " + getWrappedIntegrityKey());
+ ps.println(prefix + " lenUnknown1000: " + getLenUnknown1000());
+ ps.println(prefix + " unknown1000: " + getUnknown1000());
+ ps.println(prefix + " decryptedDataLength: " + getDecryptedDataLength());
+ ps.println(prefix + " possibleHeaderVersion: " + getPossibleHeaderVersion());
+ ps.println(prefix + " signature: " + getSignature());
+ }
+
+ public void print(PrintStream ps, String prefix) {
+ ps.println(prefix + "ExperimentalV1Header:");
+ printFields(ps, prefix);
+ }
+
+ public byte[] getBytes() {
+ byte[] result = new byte[length()];
+ int offset = 0;
+ System.arraycopy(this.unknown0, 0, result, offset, this.unknown0.length); offset += this.unknown0.length;
+ System.arraycopy(this.blockSize, 0, result, offset, this.blockSize.length); offset += this.blockSize.length;
+ System.arraycopy(this.unknownInt20, 0, result, offset, this.unknownInt20.length); offset += this.unknownInt20.length;
+ System.arraycopy(this.unknownInt24, 0, result, offset, this.unknownInt24.length); offset += this.unknownInt24.length;
+ System.arraycopy(this.unknownInt28, 0, result, offset, this.unknownInt28.length); offset += this.unknownInt28.length;
+ System.arraycopy(this.unknownInt32, 0, result, offset, this.unknownInt32.length); offset += this.unknownInt32.length;
+ System.arraycopy(this.unknownInt36, 0, result, offset, this.unknownInt36.length); offset += this.unknownInt36.length;
+ System.arraycopy(this.unknownInt40, 0, result, offset, this.unknownInt40.length); offset += this.unknownInt40.length;
+ System.arraycopy(this.unknownInt44, 0, result, offset, this.unknownInt44.length); offset += this.unknownInt44.length;
+ System.arraycopy(this.kdfIterationCount, 0, result, offset, this.kdfIterationCount.length); offset += this.kdfIterationCount.length;
+ System.arraycopy(this.kdfSaltLen, 0, result, offset, this.kdfSaltLen.length); offset += this.kdfSaltLen.length;
+ System.arraycopy(this.kdfSalt, 0, result, offset, this.kdfSalt.length); offset += this.kdfSalt.length;
+ System.arraycopy(this.laban, 0, result, offset, this.laban.length); offset += this.laban.length;
+ System.arraycopy(this.edward, 0, result, offset, this.edward.length); offset += this.edward.length;
+ System.arraycopy(this.palle, 0, result, offset, this.palle.length); offset += this.palle.length;
+ System.arraycopy(this.lisa, 0, result, offset, this.lisa.length); offset += this.lisa.length;
+ System.arraycopy(this.unwrapIv, 0, result, offset, this.unwrapIv.length); offset += this.unwrapIv.length;
+ System.arraycopy(this.lenWrappedAesKey, 0, result, offset, this.lenWrappedAesKey.length); offset += this.lenWrappedAesKey.length;
+ System.arraycopy(this.wrappedAesKey, 0, result, offset, this.wrappedAesKey.length); offset += this.wrappedAesKey.length;
+ System.arraycopy(this.unknownInt396, 0, result, offset, this.unknownInt396.length); offset += this.unknownInt396.length;
+ System.arraycopy(this.unknownInt400, 0, result, offset, this.unknownInt400.length); offset += this.unknownInt400.length;
+ System.arraycopy(this.unknown404, 0, result, offset, this.unknown404.length); offset += this.unknown404.length;
+ System.arraycopy(this.lenWrappedHmacSha1Key, 0, result, offset, this.lenWrappedHmacSha1Key.length); offset += this.lenWrappedHmacSha1Key.length;
+ System.arraycopy(this.wrappedHmacSha1Key, 0, result, offset, this.wrappedHmacSha1Key.length); offset += this.wrappedHmacSha1Key.length;
+ System.arraycopy(this.unknownInt696, 0, result, offset, this.unknownInt696.length); offset += this.unknownInt696.length;
+ System.arraycopy(this.unknownInt700, 0, result, offset, this.unknownInt700.length); offset += this.unknownInt700.length;
+ System.arraycopy(this.unknown704, 0, result, offset, this.unknown704.length); offset += this.unknown704.length;
+ System.arraycopy(this.lenWrappedIntegrityKey, 0, result, offset, this.lenWrappedIntegrityKey.length); offset += this.lenWrappedIntegrityKey.length;
+ System.arraycopy(this.wrappedIntegrityKey, 0, result, offset, this.wrappedIntegrityKey.length); offset += this.wrappedIntegrityKey.length;
+ System.arraycopy(this.lenUnknown1000, 0, result, offset, this.lenUnknown1000.length); offset += this.lenUnknown1000.length;
+ System.arraycopy(this.unknown1000, 0, result, offset, this.unknown1000.length); offset += this.unknown1000.length;
+ System.arraycopy(this.decryptedDataLength, 0, result, offset, this.decryptedDataLength.length); offset += this.decryptedDataLength.length;
+ System.arraycopy(this.possibleHeaderVersion, 0, result, offset, this.possibleHeaderVersion.length); offset += this.possibleHeaderVersion.length;
+ System.arraycopy(this.signature, 0, result, offset, this.signature.length); offset += this.signature.length;
+ return result;
+ }
+}
diff --git a/src/org/catacombae/dmg/encrypted/ExperimentalV1Header.struct b/src/org/catacombae/dmg/encrypted/ExperimentalV1Header.struct
new file mode 100644
index 0000000..efc839c
--- /dev/null
+++ b/src/org/catacombae/dmg/encrypted/ExperimentalV1Header.struct
@@ -0,0 +1,36 @@
+struct ExperimentalV1Header {
+ uint8_t unknown0[16]; Unknown data.
+ uint32_t blockSize; Block size of the encrypted block data.
+ uint32_t unknownInt20; Unknown integer.
+ uint32_t unknownInt24; Unknown integer.
+ uint32_t unknownInt28; Unknown integer.
+ uint32_t unknownInt32; Unknown integer.
+ uint32_t unknownInt36; Unknown integer.
+ uint32_t kdfAlgorithm; Algorithm of the key derivation function.
+ uint32_t kdfPrngAlgorithm; Some other algorithm?
+ uint32_t kdfIterationCount; Iteration count for the key derivation function (normally 1000).
+ uint32_t kdfSaltLen; Length of kdfSalt in bytes.
+ uint8_t kdfSalt[32]; Salt value for the key derivation function
+ uint32_t laban; Unknown variable with observed value 16/0x10.
+ uint32_t edward; Unknown variable with observed value 5/0x5.
+ uint32_t palle; Unknown variable with observed value 0x80000001.
+ uint32_t lisa; Unknown variable with observed value 128/0x80.
+ uint8_t unwrapIv[32]; Initialization Vector for encryption-key unwrapping.
+ uint32_t lenWrappedAesKey; Length of wrappedAesKey in bytes (max 256).
+ uint8_t wrappedAesKey[256]; The AES key (wrapped).
+ uint32_t unknownInt396; Unknown integer (observed value: 91/0x5B).
+ uint32_t unknownInt400; Unknown integer (observed value: 160/0xA0).
+ uint8_t unknown404[32]; Unknown data (observed: 8 bytes filled, rest 0).
+ uint32_t lenWrappedHmacSha1Key; Length of wrappedHmacSha1Key in bytes (max 256).
+ uint8_t wrappedHmacSha1Key[256]; The HMAC SHA-1 key (wrapped).
+ uint32_t unknownInt696; Unknown integer (observed value: 91/0x5B).
+ uint32_t unknownInt700; Unknown integer (observed value: 160/0xA0).
+ uint8_t unknown704[32]; Unknown data (obs. 8 bytes filled, rest 0).
+ uint32_t lenWrappedIntegrityKey; Length of wrappedIntegrityKey.
+ uint8_t wrappedIntegrityKey[256]; Integrity key.
+ uint32_t lenUnknown1000; Length of unknown1000.
+ uint8_t unknown1000[256]; Unknown key-like field with length specified by unknownRnd95GaLen (max 256).
+ uint64_t decryptedDataLength; Length in bytes of the underlying data stream.
+ uint32_t possibleHeaderVersion; Could be a variable indicating header version (observed: 1/0x1).
+ uint8_t signature[8]; Header signature (ASCII: 'cdsaencr').
+};
\ No newline at end of file
diff --git a/src/org/catacombae/dmg/encrypted/ExperimentalV2Header.java b/src/org/catacombae/dmg/encrypted/ExperimentalV2Header.java
new file mode 100644
index 0000000..579cdd5
--- /dev/null
+++ b/src/org/catacombae/dmg/encrypted/ExperimentalV2Header.java
@@ -0,0 +1,242 @@
+package org.catacombae.dmg.encrypted;
+
+import java.io.PrintStream;
+import org.catacombae.dmgextractor.Util;
+
+/** This class was generated by CStructToJavaClass. */
+public class ExperimentalV2Header {
+ /*
+ * struct ExperimentalV2Header
+ * size: 248 bytes
+ * description:
+ *
+ * BP Size Type Identifier Description
+ * ------------------------------------------------------------------------------------------------------------
+ * 0 1*8 uint8_t[8] signature Header signature (ASCII: 'encrcdsa').
+ * 8 4 uint32_t possibleHeaderVersion Possibly the version of the encrypted volume format.
+ * 12 4 uint32_t laban Unknown variable with observed value 16/0x10.
+ * 16 4 uint32_t edward Unknown variable with observed value 5/0x5.
+ * 20 4 uint32_t palle Unknown variable with observed value 0x80000001.
+ * 24 4 uint32_t lisa Unknown variable with observed value 128/0x80.
+ * 28 4 uint32_t unknownInt28 Unknown variable with observed value 91/0x5B.
+ * 32 4 uint32_t unknownInt32 Unknown variable with observed value 160/0xA0.
+ * 36 1*16 uint8_t[16] unknown1 Unknown binary data.
+ * 52 4 uint32_t blockSize Block size of the encrypted block data.
+ * 56 8 uint64_t encryptedDataLength Length in bytes of the data that has been encrypted.
+ * 64 8 uint64_t offsetToDataStart Offset to the start of the encrypted block data.
+ * 72 4 uint32_t unknownInt72 Unknown variable with observed value 1/0x1.
+ * 76 4 uint32_t unknownInt76 Unknown variable with observed value 1/0x1.
+ * 80 8 uint64_t possiblePointerToKdfAlgorithm Could be a pointer to where kdf_algorithm is located.
+ * 88 8 uint64_t unknownLong88 Unknown variable with observed value 616/0x268.
+ * 96 4 uint32_t kdfAlgorithm Algorithm of the key derivation function.
+ * 100 4 uint32_t kdfPrngAlgorithm ?
+ * 104 4 uint32_t kdfIterationCount Iteration count (normally 1000).
+ * 108 4 uint32_t kdfSaltLen Length of kdfSalt (in bytes).
+ * 112 1*32 uint8_t[32] kdfSalt Salt value for key derivation.
+ * 144 4 uint32_t blobEncIvSize Size of blobEncIv.
+ * 148 1*32 uint8_t[32] blobEncIv Initialization Vector for encryption-key unwrapping.
+ * 180 4 uint32_t blobEncKeyBits Number of bits in the keyblob's encryption key.
+ * 184 4 uint32_t blobEncAlgorithm Encryption algorithm used to encrypt the key blob.
+ * 188 4 uint32_t blobEncPadding Padding. (?)
+ * 192 4 uint32_t blobEncMode Encryption mode for the algorithm.
+ * 196 4 uint32_t encryptedKeyblobSize Size of encryptedKeyBlob.
+ * 200 1*48 uint8_t[48] encryptedKeyblob The encrypted key blob, containing all keys.
+ */
+
+ public static final int STRUCTSIZE = 248;
+
+ private final byte[] signature = new byte[1*8];
+ private final byte[] possibleHeaderVersion = new byte[4];
+ private final byte[] laban = new byte[4];
+ private final byte[] edward = new byte[4];
+ private final byte[] palle = new byte[4];
+ private final byte[] lisa = new byte[4];
+ private final byte[] unknownInt28 = new byte[4];
+ private final byte[] unknownInt32 = new byte[4];
+ private final byte[] unknown1 = new byte[1*16];
+ private final byte[] blockSize = new byte[4];
+ private final byte[] encryptedDataLength = new byte[8];
+ private final byte[] offsetToDataStart = new byte[8];
+ private final byte[] unknownInt72 = new byte[4];
+ private final byte[] unknownInt76 = new byte[4];
+ private final byte[] possiblePointerToKdfAlgorithm = new byte[8];
+ private final byte[] unknownLong88 = new byte[8];
+ private final byte[] kdfAlgorithm = new byte[4];
+ private final byte[] kdfPrngAlgorithm = new byte[4];
+ private final byte[] kdfIterationCount = new byte[4];
+ private final byte[] kdfSaltLen = new byte[4];
+ private final byte[] kdfSalt = new byte[1*32];
+ private final byte[] blobEncIvSize = new byte[4];
+ private final byte[] blobEncIv = new byte[1*32];
+ private final byte[] blobEncKeyBits = new byte[4];
+ private final byte[] blobEncAlgorithm = new byte[4];
+ private final byte[] blobEncPadding = new byte[4];
+ private final byte[] blobEncMode = new byte[4];
+ private final byte[] encryptedKeyblobSize = new byte[4];
+ private final byte[] encryptedKeyblob = new byte[1*48];
+
+ public ExperimentalV2Header(byte[] data, int offset) {
+ System.arraycopy(data, offset+0, signature, 0, 1*8);
+ System.arraycopy(data, offset+8, possibleHeaderVersion, 0, 4);
+ System.arraycopy(data, offset+12, laban, 0, 4);
+ System.arraycopy(data, offset+16, edward, 0, 4);
+ System.arraycopy(data, offset+20, palle, 0, 4);
+ System.arraycopy(data, offset+24, lisa, 0, 4);
+ System.arraycopy(data, offset+28, unknownInt28, 0, 4);
+ System.arraycopy(data, offset+32, unknownInt32, 0, 4);
+ System.arraycopy(data, offset+36, unknown1, 0, 1*16);
+ System.arraycopy(data, offset+52, blockSize, 0, 4);
+ System.arraycopy(data, offset+56, encryptedDataLength, 0, 8);
+ System.arraycopy(data, offset+64, offsetToDataStart, 0, 8);
+ System.arraycopy(data, offset+72, unknownInt72, 0, 4);
+ System.arraycopy(data, offset+76, unknownInt76, 0, 4);
+ System.arraycopy(data, offset+80, possiblePointerToKdfAlgorithm, 0, 8);
+ System.arraycopy(data, offset+88, unknownLong88, 0, 8);
+ System.arraycopy(data, offset+96, kdfAlgorithm, 0, 4);
+ System.arraycopy(data, offset+100, kdfPrngAlgorithm, 0, 4);
+ System.arraycopy(data, offset+104, kdfIterationCount, 0, 4);
+ System.arraycopy(data, offset+108, kdfSaltLen, 0, 4);
+ System.arraycopy(data, offset+112, kdfSalt, 0, 1*32);
+ System.arraycopy(data, offset+144, blobEncIvSize, 0, 4);
+ System.arraycopy(data, offset+148, blobEncIv, 0, 1*32);
+ System.arraycopy(data, offset+180, blobEncKeyBits, 0, 4);
+ System.arraycopy(data, offset+184, blobEncAlgorithm, 0, 4);
+ System.arraycopy(data, offset+188, blobEncPadding, 0, 4);
+ System.arraycopy(data, offset+192, blobEncMode, 0, 4);
+ System.arraycopy(data, offset+196, encryptedKeyblobSize, 0, 4);
+ System.arraycopy(data, offset+200, encryptedKeyblob, 0, 1*48);
+ }
+
+ public static int length() { return STRUCTSIZE; }
+
+ /** Header signature (ASCII: 'encrcdsa'). */
+ public byte[] getSignature() { return Util.readByteArrayBE(signature); }
+ /** Possibly the version of the encrypted volume format. */
+ public int getPossibleHeaderVersion() { return Util.readIntBE(possibleHeaderVersion); }
+ /** Unknown variable with observed value 16/0x10. */
+ public int getLaban() { return Util.readIntBE(laban); }
+ /** Unknown variable with observed value 5/0x5. */
+ public int getEdward() { return Util.readIntBE(edward); }
+ /** Unknown variable with observed value 0x80000001. */
+ public int getPalle() { return Util.readIntBE(palle); }
+ /** Unknown variable with observed value 128/0x80. */
+ public int getLisa() { return Util.readIntBE(lisa); }
+ /** Unknown variable with observed value 91/0x5B. */
+ public int getUnknownInt28() { return Util.readIntBE(unknownInt28); }
+ /** Unknown variable with observed value 160/0xA0. */
+ public int getUnknownInt32() { return Util.readIntBE(unknownInt32); }
+ /** Unknown binary data. */
+ public byte[] getUnknown1() { return Util.readByteArrayBE(unknown1); }
+ /** Block size of the encrypted block data. */
+ public int getBlockSize() { return Util.readIntBE(blockSize); }
+ /** Length in bytes of the data that has been encrypted. */
+ public long getEncryptedDataLength() { return Util.readLongBE(encryptedDataLength); }
+ /** Offset to the start of the encrypted block data. */
+ public long getOffsetToDataStart() { return Util.readLongBE(offsetToDataStart); }
+ /** Unknown variable with observed value 1/0x1. */
+ public int getUnknownInt72() { return Util.readIntBE(unknownInt72); }
+ /** Unknown variable with observed value 1/0x1. */
+ public int getUnknownInt76() { return Util.readIntBE(unknownInt76); }
+ /** Could be a pointer to where kdf_algorithm is located. */
+ public long getPossiblePointerToKdfAlgorithm() { return Util.readLongBE(possiblePointerToKdfAlgorithm); }
+ /** Unknown variable with observed value 616/0x268. */
+ public long getUnknownLong88() { return Util.readLongBE(unknownLong88); }
+ /** Algorithm of the key derivation function. */
+ public int getKdfAlgorithm() { return Util.readIntBE(kdfAlgorithm); }
+ /** ? */
+ public int getKdfPrngAlgorithm() { return Util.readIntBE(kdfPrngAlgorithm); }
+ /** Iteration count (normally 1000). */
+ public int getKdfIterationCount() { return Util.readIntBE(kdfIterationCount); }
+ /** Length of kdfSalt (in bytes). */
+ public int getKdfSaltLen() { return Util.readIntBE(kdfSaltLen); }
+ /** Salt value for key derivation. */
+ public byte[] getKdfSalt() { return Util.readByteArrayBE(kdfSalt); }
+ /** Size of blobEncIv. */
+ public int getBlobEncIvSize() { return Util.readIntBE(blobEncIvSize); }
+ /** Initialization Vector for encryption-key unwrapping. */
+ public byte[] getBlobEncIv() { return Util.readByteArrayBE(blobEncIv); }
+ /** Number of bits in the keyblob's encryption key. */
+ public int getBlobEncKeyBits() { return Util.readIntBE(blobEncKeyBits); }
+ /** Encryption algorithm used to encrypt the key blob. */
+ public int getBlobEncAlgorithm() { return Util.readIntBE(blobEncAlgorithm); }
+ /** Padding. (?) */
+ public int getBlobEncPadding() { return Util.readIntBE(blobEncPadding); }
+ /** Encryption mode for the algorithm. */
+ public int getBlobEncMode() { return Util.readIntBE(blobEncMode); }
+ /** Size of encryptedKeyBlob. */
+ public int getEncryptedKeyblobSize() { return Util.readIntBE(encryptedKeyblobSize); }
+ /** The encrypted key blob, containing all keys. */
+ public byte[] getEncryptedKeyblob() { return Util.readByteArrayBE(encryptedKeyblob); }
+
+ public void printFields(PrintStream ps, String prefix) {
+ ps.println(prefix + " signature: " + getSignature());
+ ps.println(prefix + " possibleHeaderVersion: " + getPossibleHeaderVersion());
+ ps.println(prefix + " laban: " + getLaban());
+ ps.println(prefix + " edward: " + getEdward());
+ ps.println(prefix + " palle: " + getPalle());
+ ps.println(prefix + " lisa: " + getLisa());
+ ps.println(prefix + " unknownInt28: " + getUnknownInt28());
+ ps.println(prefix + " unknownInt32: " + getUnknownInt32());
+ ps.println(prefix + " unknown1: " + getUnknown1());
+ ps.println(prefix + " blockSize: " + getBlockSize());
+ ps.println(prefix + " encryptedDataLength: " + getEncryptedDataLength());
+ ps.println(prefix + " offsetToDataStart: " + getOffsetToDataStart());
+ ps.println(prefix + " unknownInt72: " + getUnknownInt72());
+ ps.println(prefix + " unknownInt76: " + getUnknownInt76());
+ ps.println(prefix + " possiblePointerToKdfAlgorithm: " + getPossiblePointerToKdfAlgorithm());
+ ps.println(prefix + " unknownLong88: " + getUnknownLong88());
+ ps.println(prefix + " kdfAlgorithm: " + getKdfAlgorithm());
+ ps.println(prefix + " kdfPrngAlgorithm: " + getKdfPrngAlgorithm());
+ ps.println(prefix + " kdfIterationCount: " + getKdfIterationCount());
+ ps.println(prefix + " kdfSaltLen: " + getKdfSaltLen());
+ ps.println(prefix + " kdfSalt: " + getKdfSalt());
+ ps.println(prefix + " blobEncIvSize: " + getBlobEncIvSize());
+ ps.println(prefix + " blobEncIv: " + getBlobEncIv());
+ ps.println(prefix + " blobEncKeyBits: " + getBlobEncKeyBits());
+ ps.println(prefix + " blobEncAlgorithm: " + getBlobEncAlgorithm());
+ ps.println(prefix + " blobEncPadding: " + getBlobEncPadding());
+ ps.println(prefix + " blobEncMode: " + getBlobEncMode());
+ ps.println(prefix + " encryptedKeyblobSize: " + getEncryptedKeyblobSize());
+ ps.println(prefix + " encryptedKeyblob: " + getEncryptedKeyblob());
+ }
+
+ public void print(PrintStream ps, String prefix) {
+ ps.println(prefix + "ExperimentalV2Header:");
+ printFields(ps, prefix);
+ }
+
+ public byte[] getBytes() {
+ byte[] result = new byte[length()];
+ int offset = 0;
+ System.arraycopy(this.signature, 0, result, offset, this.signature.length); offset += this.signature.length;
+ System.arraycopy(this.possibleHeaderVersion, 0, result, offset, this.possibleHeaderVersion.length); offset += this.possibleHeaderVersion.length;
+ System.arraycopy(this.laban, 0, result, offset, this.laban.length); offset += this.laban.length;
+ System.arraycopy(this.edward, 0, result, offset, this.edward.length); offset += this.edward.length;
+ System.arraycopy(this.palle, 0, result, offset, this.palle.length); offset += this.palle.length;
+ System.arraycopy(this.lisa, 0, result, offset, this.lisa.length); offset += this.lisa.length;
+ System.arraycopy(this.unknownInt28, 0, result, offset, this.unknownInt28.length); offset += this.unknownInt28.length;
+ System.arraycopy(this.unknownInt32, 0, result, offset, this.unknownInt32.length); offset += this.unknownInt32.length;
+ System.arraycopy(this.unknown1, 0, result, offset, this.unknown1.length); offset += this.unknown1.length;
+ System.arraycopy(this.blockSize, 0, result, offset, this.blockSize.length); offset += this.blockSize.length;
+ System.arraycopy(this.encryptedDataLength, 0, result, offset, this.encryptedDataLength.length); offset += this.encryptedDataLength.length;
+ System.arraycopy(this.offsetToDataStart, 0, result, offset, this.offsetToDataStart.length); offset += this.offsetToDataStart.length;
+ System.arraycopy(this.unknownInt72, 0, result, offset, this.unknownInt72.length); offset += this.unknownInt72.length;
+ System.arraycopy(this.unknownInt76, 0, result, offset, this.unknownInt76.length); offset += this.unknownInt76.length;
+ System.arraycopy(this.possiblePointerToKdfAlgorithm, 0, result, offset, this.possiblePointerToKdfAlgorithm.length); offset += this.possiblePointerToKdfAlgorithm.length;
+ System.arraycopy(this.unknownLong88, 0, result, offset, this.unknownLong88.length); offset += this.unknownLong88.length;
+ System.arraycopy(this.kdfAlgorithm, 0, result, offset, this.kdfAlgorithm.length); offset += this.kdfAlgorithm.length;
+ System.arraycopy(this.kdfPrngAlgorithm, 0, result, offset, this.kdfPrngAlgorithm.length); offset += this.kdfPrngAlgorithm.length;
+ System.arraycopy(this.kdfIterationCount, 0, result, offset, this.kdfIterationCount.length); offset += this.kdfIterationCount.length;
+ System.arraycopy(this.kdfSaltLen, 0, result, offset, this.kdfSaltLen.length); offset += this.kdfSaltLen.length;
+ System.arraycopy(this.kdfSalt, 0, result, offset, this.kdfSalt.length); offset += this.kdfSalt.length;
+ System.arraycopy(this.blobEncIvSize, 0, result, offset, this.blobEncIvSize.length); offset += this.blobEncIvSize.length;
+ System.arraycopy(this.blobEncIv, 0, result, offset, this.blobEncIv.length); offset += this.blobEncIv.length;
+ System.arraycopy(this.blobEncKeyBits, 0, result, offset, this.blobEncKeyBits.length); offset += this.blobEncKeyBits.length;
+ System.arraycopy(this.blobEncAlgorithm, 0, result, offset, this.blobEncAlgorithm.length); offset += this.blobEncAlgorithm.length;
+ System.arraycopy(this.blobEncPadding, 0, result, offset, this.blobEncPadding.length); offset += this.blobEncPadding.length;
+ System.arraycopy(this.blobEncMode, 0, result, offset, this.blobEncMode.length); offset += this.blobEncMode.length;
+ System.arraycopy(this.encryptedKeyblobSize, 0, result, offset, this.encryptedKeyblobSize.length); offset += this.encryptedKeyblobSize.length;
+ System.arraycopy(this.encryptedKeyblob, 0, result, offset, this.encryptedKeyblob.length); offset += this.encryptedKeyblob.length;
+ return result;
+ }
+}
diff --git a/src/org/catacombae/dmg/encrypted/ExperimentalV2Header.struct b/src/org/catacombae/dmg/encrypted/ExperimentalV2Header.struct
new file mode 100644
index 0000000..b381725
--- /dev/null
+++ b/src/org/catacombae/dmg/encrypted/ExperimentalV2Header.struct
@@ -0,0 +1,31 @@
+struct ExperimentalV2Header {
+ uint8_t signature[8]; Header signature (ASCII: 'encrcdsa').
+ uint32_t possibleHeaderVersion; Possibly the version of the encrypted volume format.
+ uint32_t laban; Unknown variable with observed value 16/0x10.
+ uint32_t edward; Unknown variable with observed value 5/0x5.
+ uint32_t palle; Unknown variable with observed value 0x80000001.
+ uint32_t lisa; Unknown variable with observed value 128/0x80.
+ uint32_t unknownInt28; Unknown variable with observed value 91/0x5B.
+ uint32_t unknownInt32; Unknown variable with observed value 160/0xA0.
+ uint8_t unknown1[16]; Unknown binary data.
+ uint32_t blockSize; Block size of the encrypted block data.
+ uint64_t encryptedDataLength; Length in bytes of the data that has been encrypted.
+ uint64_t offsetToDataStart; Offset to the start of the encrypted block data.
+ uint32_t unknownInt72; Unknown variable with observed value 1/0x1.
+ uint32_t unknownInt76; Unknown variable with observed value 1/0x1.
+ uint64_t possiblePointerToKdfAlgorithm; Could be a pointer to where kdf_algorithm is located.
+ uint64_t unknownLong88; Unknown variable with observed value 616/0x268.
+ uint32_t kdfAlgorithm; Algorithm of the key derivation function.
+ uint32_t kdfPrngAlgorithm; Some other algorithm?
+ uint32_t kdfIterationCount; Iteration count (normally 1000).
+ uint32_t kdfSaltLen; Length of kdfSalt (in bytes).
+ uint8_t kdfSalt[32]; Salt value for key derivation.
+ uint32_t blobEncIvSize; Size of blobEncIv.
+ uint8_t blobEncIv[32]; Initialization Vector for encryption-key unwrapping.
+ uint32_t blobEncKeyBits; Number of bits in the keyblob's encryption key.
+ uint32_t blobEncAlgorithm; Encryption algorithm used to encrypt the key blob.
+ uint32_t blobEncPadding; Padding. (?)
+ uint32_t blobEncMode; Encryption mode for the algorithm.
+ uint32_t encryptedKeyblobSize; Size of encryptedKeyBlob.
+ uint8_t encryptedKeyblob[48]; The encrypted key blob, containing all keys.
+};
\ No newline at end of file
diff --git a/src/org/catacombae/dmg/encrypted/ReadableCEncryptedEncodingStream.java b/src/org/catacombae/dmg/encrypted/ReadableCEncryptedEncodingStream.java
new file mode 100644
index 0000000..f88b90d
--- /dev/null
+++ b/src/org/catacombae/dmg/encrypted/ReadableCEncryptedEncodingStream.java
@@ -0,0 +1,432 @@
+/*-
+ * Copyright (C) 2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ *
+ * This code was written from studying vfdecrypt, Copyright (c) 2006
+ * Ralf-Philipp Weinmann
+ * Jacob Appelbaum
+ * Christian Fromme
+ *
+ * [I'm not sure if their copyright and license terms need to be applied,
+ * but in case they do, the original license terms are reprinted below
+ * as required by the license.]
+ *
+ * The vfdecrypt license says:
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ */
+
+package org.catacombae.dmg.encrypted;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.Key;
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESedeKeySpec;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+import org.catacombae.dmgextractor.Util;
+import org.catacombae.dmg.encrypted.CommonCEncryptedEncodingHeader.KeySet;
+import org.catacombae.io.BasicReadableRandomAccessStream;
+import org.catacombae.io.ReadableFileStream;
+import org.catacombae.io.ReadableRandomAccessStream;
+import org.catacombae.io.RuntimeIOException;
+
+/**
+ * Filtering stream that takes the data of a Mac OS X encrypted disk image and a password as input
+ * and acts as a transparent decryption layer, allowing the user to access the unencrypted
+ * underlying disk image data. (The encryption format isn't disk image specific, so it might be used
+ * by other parts of Mac OS X as well, making this filter even more useful...)
+ *
+ * Documentation on how encrypted disk images work was retrieved from the "Unlocking
+ * FileVault" slides, published by Jacob Appelbaum and Ralf-Philipp Weinmann, and the source code of
+ * the utility vfdecrypt in VileFault, copyright Ralf-Philipp Weinmann, Jacob Appelbaum and
+ * Christian Fromme.
+ *
+ * @author Erik Larsson
+ */
+public class ReadableCEncryptedEncodingStream extends BasicReadableRandomAccessStream {
+ private final ReadableRandomAccessStream backingStream;
+ private final CommonCEncryptedEncodingHeader header;
+ private final SecretKeySpec aesKey;
+ private final SecretKeySpec hmacSha1Key;
+ private final Mac hmacSha1;
+ private final Cipher aesCipher;
+ private final long streamLength;
+
+ // Tracker variables
+ private long blockNumber = 0;
+ private int posInBlock = 0;
+
+ public ReadableCEncryptedEncodingStream(ReadableRandomAccessStream backingStream,
+ char[] password) throws RuntimeIOException {
+ Debug.print("ReadableCEncryptedEncodingStream(" + backingStream + ", " + password +");");
+ this.backingStream = backingStream;
+ int headerVersion = CEncryptedEncodingUtil.detectVersion(backingStream);
+ Debug.print(" headerVersion = " + headerVersion);
+ switch(headerVersion) {
+ case 1:
+ byte[] v1HeaderData = new byte[V1Header.length()];
+ backingStream.seek(backingStream.length()-V1Header.length());
+ backingStream.readFully(v1HeaderData);
+ V1Header v1header = new V1Header(v1HeaderData, 0);
+ Debug.print(" V1 header:");
+ v1header.print(Debug.ps, " ");
+ header = CommonCEncryptedEncodingHeader.create(v1header);
+ break;
+ case 2:
+ byte[] v2HeaderData = new byte[V2Header.length()];
+ backingStream.seek(0);
+ backingStream.readFully(v2HeaderData);
+ V2Header v2header = new V2Header(v2HeaderData, 0);
+ Debug.print(" V2 header:");
+ v2header.print(Debug.ps, " ");
+ header = CommonCEncryptedEncodingHeader.create(v2header);
+ break;
+ case -1:
+ throw new RuntimeException("No CEncryptedEncoding header found!");
+ default:
+ throw new RuntimeException("Unknown header version: " + headerVersion);
+ }
+
+ this.streamLength = header.getEncryptedDataLength();
+ /*
+ if(this.length % header.getBlockSize() != 0) {
+ System.err.println("WARNING: Block data area length (" + this.length +
+ ") is not aligned to block size (" + header.getBlockSize() + ")!");
+ }
+ * */
+
+ try {
+ final String pbeAlgorithmName = "PBKDF2WithHmacSHA1"; //"PBEWithHmacSHA1AndDESede";
+
+ // Derive the proper key from our password.
+ PBEKeySpec ks = new PBEKeySpec(password, header.getKdfSalt(),
+ header.getKdfIterationCount(), 192);
+ SecretKeyFactory fact = SecretKeyFactory.getInstance(pbeAlgorithmName);
+ Key k = fact.generateSecret(ks);
+
+ byte[] keyData = k.getEncoded();
+ Debug.print("Derived key: 0x" + Util.byteArrayToHexString(keyData));
+
+ // Set up the cipher
+ final String cipherAlgorithmName = "DESede/CBC/PKCS5Padding";
+ Cipher keyDecryptionCipher = Cipher.getInstance(cipherAlgorithmName);
+ SecretKeyFactory fact2 = SecretKeyFactory.getInstance("DESede");
+ Key k2 = fact2.generateSecret(new DESedeKeySpec(keyData));
+
+ // Call the version specific unwrap function.
+ KeySet keys = header.unwrapKeys(k2, keyDecryptionCipher);
+
+ Debug.print("AES key: 0x" + Util.byteArrayToHexString(keys.getAesKey()));
+ Debug.print("HmacSHA1 key: 0x" + Util.byteArrayToHexString(keys.getHmacSha1Key()));
+
+ this.aesKey = new SecretKeySpec(keys.getAesKey(), "AES");
+ this.hmacSha1Key = new SecretKeySpec(keys.getHmacSha1Key(), "HmacSHA1");
+
+ keys.clearData(); // No unused keys in memory please.
+
+ this.hmacSha1 = Mac.getInstance("HmacSHA1");
+ this.hmacSha1.init(hmacSha1Key);
+
+ this.aesCipher = Cipher.getInstance("AES/CBC/NoPadding");
+ } catch(Exception e) {
+ throw new RuntimeException("Exception while trying to decrypt keys.", e);
+ }
+ }
+
+ /**
+ * Tells whether stream is encoded with CEncryptedEncoding or not. If this method
+ * returns true, the stream can be fed to the ReadableCEncryptedEncoding constructor.
+ *
+ * @param stream the stream to check for the signatures of a CEncryptedEncoding.
+ * @return whether stream is encoded with CEncryptedEncoding or not.
+ */
+ public static boolean isCEncryptedEncoding(ReadableRandomAccessStream stream) {
+ int version = CEncryptedEncodingUtil.detectVersion(stream);
+ return version == 1 || version == 2;
+ }
+
+ @Override
+ public void close() throws RuntimeIOException {
+ backingStream.close();
+ }
+
+ @Override
+ public void seek(long pos) throws RuntimeIOException {
+ if(pos < 0)
+ throw new IllegalArgumentException("Negative seek request: pos (" + pos + ") < 0");
+ else if(pos > streamLength) {
+ // throw new IllegalArgumentException("Trying to seek beyond EOF: pos (" + pos +
+ // ") > length (" + length + ")");
+
+ // Let's just seek to the end of file instead of throwing stuff around us.
+ this.blockNumber = streamLength/header.getBlockSize();
+ this.posInBlock = 0;
+ }
+ else {
+ long nextBlockNumber = pos / header.getBlockSize();
+ int nextPosInBlock = (int) (pos % header.getBlockSize());
+
+ /*
+ if(header.getBlockDataStart() + (nextBlockNumber+1)*header.getBlockSize() > backingStream.length()) {
+ nextBlockNumber = (backingStream.length()-header.getBlockDataStart())/header.getBlockSize();
+ nextPosInBlock = 0;
+ }
+ * */
+
+ this.blockNumber = nextBlockNumber;
+ this.posInBlock = nextPosInBlock;
+ }
+ }
+
+ @Override
+ public long length() throws RuntimeIOException {
+ return streamLength;
+ }
+
+ @Override
+ public long getFilePointer() throws RuntimeIOException {
+ return blockNumber*header.getBlockSize() + posInBlock;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws RuntimeIOException {
+ //
+ if(len == 0)
+ return 0;
+ else if(len < 0)
+ throw new IndexOutOfBoundsException("len (" + len + ") < 0");
+ else if(off < 0)
+ throw new IndexOutOfBoundsException("off (" + off + ") < 0");
+ else if(off+len > b.length)
+ throw new IndexOutOfBoundsException("off+len (" + (off+len) +
+ ") > b.length (" + b.length + ")");
+ //
+
+ backingStream.seek(header.getBlockDataStart() + blockNumber*header.getBlockSize());
+
+ byte[] encBlockData = new byte[header.getBlockSize()];
+ byte[] decBlockData = new byte[encBlockData.length];
+
+ try {
+ int totalBytesRead = 0;
+ while(totalBytesRead < len && blockNumber*header.getBlockSize() < streamLength) {
+ int bytesRead = backingStream.read(encBlockData);
+ if(bytesRead != encBlockData.length) {
+ if(bytesRead > 0)
+ System.err.println("WARNING: Could not read entire block! " +
+ "blockNumber=" + blockNumber + ", bytesRead=" + bytesRead);
+ break;
+ }
+
+ int bytesDecrypted = decrypt(encBlockData, decBlockData, blockNumber);
+ Assert.eq(bytesDecrypted, decBlockData.length);
+
+ final long bytesLeftInStream = streamLength - blockNumber*header.getBlockSize();
+ final int blockSize =
+ (int)(bytesLeftInStream < decBlockData.length ? bytesLeftInStream : decBlockData.length);
+
+
+ final int bytesLeftToRead = len-totalBytesRead;
+ final int bytesLeftInBlock = blockSize-posInBlock;
+ int bytesToCopy = bytesLeftToRead < bytesLeftInBlock ? bytesLeftToRead : bytesLeftInBlock;
+
+ System.arraycopy(decBlockData, posInBlock, b, off + totalBytesRead, bytesToCopy);
+
+ totalBytesRead += bytesToCopy;
+
+ if(bytesToCopy == bytesLeftInBlock) {
+ ++blockNumber;
+ posInBlock = 0;
+ }
+ else {
+ posInBlock += bytesLeftToRead;
+ }
+ }
+
+ if(totalBytesRead > 0)
+ return totalBytesRead;
+ else
+ return -1;
+ } finally {
+ Util.zero(encBlockData);
+ Util.zero(decBlockData);
+ }
+ }
+
+ private int decrypt(byte[] encBlockData, byte[] decBlockData, long blockNumber) {
+ Debug.print("decrypt(byte[" + encBlockData.length + "], byte[" +
+ decBlockData.length + "], " + blockNumber + ");");
+ if(blockNumber < 0 || blockNumber > Integer.MAX_VALUE)
+ throw new RuntimeException("Block number out of range: " + blockNumber);
+ int blockNumberInt = (int)(blockNumber & 0xFFFFFFFF);
+ hmacSha1.reset();
+ hmacSha1.update(Util.toByteArrayBE(blockNumberInt));
+ byte[] iv = new byte[16];
+
+ /* The 160-bit MAC value is truncated to 16 bytes (128 bits) to be
+ * used as the cipher's IV. */
+ System.arraycopy(hmacSha1.doFinal(), 0, iv, 0, iv.length);
+ //Debug.print(" iv: 0x" + Util.byteArrayToHexString(iv));
+
+ try {
+ aesCipher.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(iv));
+ int bytesDecrypted =
+ aesCipher.doFinal(encBlockData, 0, encBlockData.length, decBlockData, 0);
+
+ return bytesDecrypted;
+ } catch(Exception e) {
+ throw new RuntimeException("Unexpected exception when trying to " +
+ "decrypt block " + blockNumber + ".", e);
+ } finally {
+ Util.zero(iv);
+ }
+ }
+
+ private static void printHelp() {
+ System.err.println("usage: " + ReadableCEncryptedEncodingStream.class.getName() +
+ " -i in-file -p password -o out-file");
+ System.exit(-1);
+ }
+ public static void main(String[] args) throws IOException {
+ /*
+ boolean debugMode = true;
+ if(debugMode && args.length == 0) {
+ String imageprefix = "v2";
+ File inFile = new File("/Users/erik/devel/reference/vilefault/vfdecrypt/" +
+ imageprefix + "image.dmg");
+ File outFile = new File("/Users/erik/devel/reference/vilefault/vfdecrypt/" +
+ imageprefix + "image_javadec.dmg");
+ char[] password = SecretPassword.PASSWORD;
+ runTest(inFile, outFile, password);
+ }
+ * */
+
+ String inputFilename = null;
+ String outputFilename = null;
+ String password = null;
+ for(int i = 0; i < args.length; ++i) {
+ String curArg = args[i];
+ if(curArg.startsWith("-i")) {
+ if(i+1 < args.length)
+ inputFilename = args[i+1];
+ else
+ printHelp();
+ }
+ else if(curArg.startsWith("-p")) {
+ if(i+1 < args.length)
+ password = args[i+1];
+ else
+ printHelp();
+ }
+ else if(curArg.startsWith("-o")) {
+ if(i+1 < args.length)
+ outputFilename = args[i+1];
+ else
+ printHelp();
+ }
+ }
+ if(inputFilename == null || outputFilename == null || password == null)
+ printHelp();
+
+ runTest(inputFilename, outputFilename, password);
+ }
+
+ private static void runTest(String inputFilename, String outputFilename, String password) throws IOException {
+ ReadableRandomAccessStream backingStream = new ReadableFileStream(inputFilename);
+
+ ReadableRandomAccessStream rras =
+ new ReadableCEncryptedEncodingStream(backingStream, password.toCharArray());
+
+ System.out.println("Length of encrypted data: " + rras.length() + " bytes");
+
+ byte[] lastBlock = new byte[4096];
+ rras.seek(rras.length()-4096);
+ rras.readFully(lastBlock);
+ System.out.println("Last block: 0x" + Util.byteArrayToHexString(lastBlock));
+
+ byte[] sig = new byte[2];
+ rras.seek(0);
+ rras.readFully(sig);
+ System.out.println("Signature: " + Util.toASCIIString(sig));
+ System.out.println("fp=" + rras.getFilePointer());
+ byte[] following = new byte[3];
+ rras.readFully(following);
+ System.out.println("Following(" + following.length + "): 0x" + Util.byteArrayToHexString(following));
+ System.out.println("fp=" + rras.getFilePointer());
+ rras.readFully(following);
+ System.out.println("Following(" + following.length + "): 0x" + Util.byteArrayToHexString(following));
+ System.out.println("fp=" + rras.getFilePointer());
+ rras.readFully(following);
+ System.out.println("Following(" + following.length + "): 0x" + Util.byteArrayToHexString(following));
+ System.out.println("fp=" + rras.getFilePointer());
+
+ rras.seek(33792);
+ rras.readFully(sig);
+ System.out.println("Signature: " + Util.toASCIIString(sig));
+ System.out.println("fp=" + rras.getFilePointer());
+
+ System.out.println("Checking boundary passage:");
+ byte[] boundaryBytes = new byte[9];
+ rras.seek(36859);
+ rras.readFully(boundaryBytes);
+ System.out.println("boundaryBytes(" + boundaryBytes.length + "): 0x" + Util.byteArrayToHexString(boundaryBytes));
+ System.out.println("fp=" + rras.getFilePointer());
+
+ System.out.println("Checking reading until eof:");
+ {
+ byte[] buffer = new byte[5001];
+ rras.seek(rras.length()-4096*3);
+ int bytesRead = rras.read(buffer);
+ long totBytesRead = 0;
+ while(bytesRead != -1) {
+ System.out.println("Read " + bytesRead + " bytes.");
+ totBytesRead += bytesRead;
+ bytesRead = rras.read(buffer);
+ }
+ System.out.println("Finished. bytesRead=" + bytesRead + " totBytesRead=" + totBytesRead);
+ }
+
+ //System.exit(0);
+
+ FileOutputStream out = new FileOutputStream(outputFilename);
+ System.out.println("Extracting encrypted data to file: " + outputFilename);
+ rras.seek(0);
+ byte[] buffer = new byte[9119];
+ int bytesRead = rras.read(buffer);
+ long totalBytesWritten = 0;
+ while(bytesRead > 0) {
+ System.out.println("Read " + bytesRead + " bytes.");
+ out.write(buffer, 0, bytesRead);
+ totalBytesWritten += bytesRead;
+ bytesRead = rras.read(buffer);
+ }
+ System.out.println("Wrote " + totalBytesWritten + " bytes.");
+ out.close();
+ }
+}
diff --git a/src/org/catacombae/dmg/encrypted/V1Header.java b/src/org/catacombae/dmg/encrypted/V1Header.java
new file mode 100644
index 0000000..cd115e0
--- /dev/null
+++ b/src/org/catacombae/dmg/encrypted/V1Header.java
@@ -0,0 +1,316 @@
+/*-
+ * Copyright (C) 2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ *
+ * This code was written from studying vfdecrypt, Copyright (c) 2006
+ * Ralf-Philipp Weinmann
+ * Jacob Appelbaum
+ * Christian Fromme
+ *
+ * [I'm not sure if their copyright and license terms need to be applied,
+ * but in case they do, the original license terms are reprinted below
+ * as required by the license.]
+ *
+ * The vfdecrypt license says:
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ */
+
+package org.catacombae.dmg.encrypted;
+
+import java.io.PrintStream;
+import org.catacombae.dmgextractor.Util;
+
+/** This class was generated by CStructToJavaClass. */
+public class V1Header {
+ /*
+ * struct V1Header
+ * size: 1276 bytes
+ * description:
+ *
+ * BP Size Type Identifier Description
+ * -------------------------------------------------------------------------------------------------------------------------------
+ * 0 1*16 uint8_t[16] unknown0 Unknown data.
+ * 16 4 uint32_t blockSize Block size of the encrypted block data.
+ * 20 4 uint32_t unknownInt20 Unknown integer.
+ * 24 4 uint32_t unknownInt24 Unknown integer.
+ * 28 4 uint32_t unknownInt28 Unknown integer.
+ * 32 4 uint32_t unknownInt32 Unknown integer.
+ * 36 4 uint32_t unknownInt36 Unknown integer.
+ * 40 4 uint32_t unknownInt40 Unknown integer.
+ * 44 4 uint32_t unknownInt44 Unknown integer.
+ * 48 4 uint32_t kdfIterationCount Iteration count for the key derivation function (normally 1000).
+ * 52 4 uint32_t kdfSaltLen Length of kdfSalt in bytes.
+ * 56 1*32 uint8_t[32] kdfSalt Salt value for the key derivation function
+ * 88 4 uint32_t unknownInt88 Unknown integer.
+ * 92 4 uint32_t unknownInt92 Unknown integer.
+ * 96 4 uint32_t unknownInt96 Unknown integer.
+ * 100 4 uint32_t unknownInt100 Unknown integer.
+ * 104 1*32 uint8_t[32] unwrapIv Initialization Vector for encryption-key unwrapping.
+ * 136 4 uint32_t lenWrappedAesKey Length of wrappedAesKey in bytes (max 256).
+ * 140 1*256 uint8_t[256] wrappedAesKey The AES key (wrapped).
+ * 396 4 uint32_t unknownInt396 Unknown integer (observed value: 91/0x5B).
+ * 400 4 uint32_t unknownInt400 Unknown integer (observed value: 160/0xA0).
+ * 404 1*32 uint8_t[32] unknown404 Unknown data (observed: 8 bytes filled, rest 0).
+ * 436 4 uint32_t lenWrappedHmacSha1Key Length of wrappedHmacSha1Key in bytes (max 256).
+ * 440 1*256 uint8_t[256] wrappedHmacSha1Key The HMAC SHA-1 key (wrapped).
+ * 696 4 uint32_t unknownInt696 Unknown integer (observed value: 91/0x5B).
+ * 700 4 uint32_t unknownInt700 Unknown integer (observed value: 160/0xA0).
+ * 704 1*32 uint8_t[32] unknown704 Unknown data (obs. 8 bytes filled, rest 0).
+ * 736 4 uint32_t lenWrappedIntegrityKey Length of wrappedIntegrityKey.
+ * 740 1*256 uint8_t[256] wrappedIntegrityKey Integrity key.
+ * 996 4 uint32_t lenUnknown1000 Length of unknown1000.
+ * 1000 1*256 uint8_t[256] unknown1000 Unknown key-like field with length specified by unknownRnd95GaLen (max 256).
+ * 1256 8 uint64_t decryptedDataLength Length in bytes of the underlying data stream.
+ * 1264 4 uint32_t possibleHeaderVersion Could be a variable indicating header version (observed: 1/0x1).
+ * 1268 1*8 uint8_t[8] signature Header signature (ASCII: 'cdsaencr').
+ */
+
+ public static final int STRUCTSIZE = 1276;
+
+ private final byte[] unknown0 = new byte[1*16];
+ private final byte[] blockSize = new byte[4];
+ private final byte[] unknownInt20 = new byte[4];
+ private final byte[] unknownInt24 = new byte[4];
+ private final byte[] unknownInt28 = new byte[4];
+ private final byte[] unknownInt32 = new byte[4];
+ private final byte[] unknownInt36 = new byte[4];
+ private final byte[] unknownInt40 = new byte[4];
+ private final byte[] unknownInt44 = new byte[4];
+ private final byte[] kdfIterationCount = new byte[4];
+ private final byte[] kdfSaltLen = new byte[4];
+ private final byte[] kdfSalt = new byte[1*32];
+ private final byte[] unknownInt88 = new byte[4];
+ private final byte[] unknownInt92 = new byte[4];
+ private final byte[] unknownInt96 = new byte[4];
+ private final byte[] unknownInt100 = new byte[4];
+ private final byte[] unwrapIv = new byte[1*32];
+ private final byte[] lenWrappedAesKey = new byte[4];
+ private final byte[] wrappedAesKey = new byte[1*256];
+ private final byte[] unknownInt396 = new byte[4];
+ private final byte[] unknownInt400 = new byte[4];
+ private final byte[] unknown404 = new byte[1*32];
+ private final byte[] lenWrappedHmacSha1Key = new byte[4];
+ private final byte[] wrappedHmacSha1Key = new byte[1*256];
+ private final byte[] unknownInt696 = new byte[4];
+ private final byte[] unknownInt700 = new byte[4];
+ private final byte[] unknown704 = new byte[1*32];
+ private final byte[] lenWrappedIntegrityKey = new byte[4];
+ private final byte[] wrappedIntegrityKey = new byte[1*256];
+ private final byte[] lenUnknown1000 = new byte[4];
+ private final byte[] unknown1000 = new byte[1*256];
+ private final byte[] decryptedDataLength = new byte[8];
+ private final byte[] possibleHeaderVersion = new byte[4];
+ private final byte[] signature = new byte[1*8];
+
+ public V1Header(byte[] data, int offset) {
+ System.arraycopy(data, offset+0, unknown0, 0, 1*16);
+ System.arraycopy(data, offset+16, blockSize, 0, 4);
+ System.arraycopy(data, offset+20, unknownInt20, 0, 4);
+ System.arraycopy(data, offset+24, unknownInt24, 0, 4);
+ System.arraycopy(data, offset+28, unknownInt28, 0, 4);
+ System.arraycopy(data, offset+32, unknownInt32, 0, 4);
+ System.arraycopy(data, offset+36, unknownInt36, 0, 4);
+ System.arraycopy(data, offset+40, unknownInt40, 0, 4);
+ System.arraycopy(data, offset+44, unknownInt44, 0, 4);
+ System.arraycopy(data, offset+48, kdfIterationCount, 0, 4);
+ System.arraycopy(data, offset+52, kdfSaltLen, 0, 4);
+ System.arraycopy(data, offset+56, kdfSalt, 0, 1*32);
+ System.arraycopy(data, offset+88, unknownInt88, 0, 4);
+ System.arraycopy(data, offset+92, unknownInt92, 0, 4);
+ System.arraycopy(data, offset+96, unknownInt96, 0, 4);
+ System.arraycopy(data, offset+100, unknownInt100, 0, 4);
+ System.arraycopy(data, offset+104, unwrapIv, 0, 1*32);
+ System.arraycopy(data, offset+136, lenWrappedAesKey, 0, 4);
+ System.arraycopy(data, offset+140, wrappedAesKey, 0, 1*256);
+ System.arraycopy(data, offset+396, unknownInt396, 0, 4);
+ System.arraycopy(data, offset+400, unknownInt400, 0, 4);
+ System.arraycopy(data, offset+404, unknown404, 0, 1*32);
+ System.arraycopy(data, offset+436, lenWrappedHmacSha1Key, 0, 4);
+ System.arraycopy(data, offset+440, wrappedHmacSha1Key, 0, 1*256);
+ System.arraycopy(data, offset+696, unknownInt696, 0, 4);
+ System.arraycopy(data, offset+700, unknownInt700, 0, 4);
+ System.arraycopy(data, offset+704, unknown704, 0, 1*32);
+ System.arraycopy(data, offset+736, lenWrappedIntegrityKey, 0, 4);
+ System.arraycopy(data, offset+740, wrappedIntegrityKey, 0, 1*256);
+ System.arraycopy(data, offset+996, lenUnknown1000, 0, 4);
+ System.arraycopy(data, offset+1000, unknown1000, 0, 1*256);
+ System.arraycopy(data, offset+1256, decryptedDataLength, 0, 8);
+ System.arraycopy(data, offset+1264, possibleHeaderVersion, 0, 4);
+ System.arraycopy(data, offset+1268, signature, 0, 1*8);
+ }
+
+ public static int length() { return STRUCTSIZE; }
+
+ /** Unknown data. */
+ public byte[] getUnknown0() { return Util.readByteArrayBE(unknown0); }
+ /** Block size of the encrypted block data. */
+ public int getBlockSize() { return Util.readIntBE(blockSize); }
+ /** Unknown integer. */
+ public int getUnknownInt20() { return Util.readIntBE(unknownInt20); }
+ /** Unknown integer. */
+ public int getUnknownInt24() { return Util.readIntBE(unknownInt24); }
+ /** Unknown integer. */
+ public int getUnknownInt28() { return Util.readIntBE(unknownInt28); }
+ /** Unknown integer. */
+ public int getUnknownInt32() { return Util.readIntBE(unknownInt32); }
+ /** Unknown integer. */
+ public int getUnknownInt36() { return Util.readIntBE(unknownInt36); }
+ /** Unknown integer. */
+ public int getUnknownInt40() { return Util.readIntBE(unknownInt40); }
+ /** Unknown integer. */
+ public int getUnknownInt44() { return Util.readIntBE(unknownInt44); }
+ /** Iteration count for the key derivation function (normally 1000). */
+ public int getKdfIterationCount() { return Util.readIntBE(kdfIterationCount); }
+ /** Length of kdfSalt in bytes. */
+ public int getKdfSaltLen() { return Util.readIntBE(kdfSaltLen); }
+ /** Salt value for the key derivation function */
+ public byte[] getKdfSalt() { return Util.readByteArrayBE(kdfSalt); }
+ /** Unknown integer. */
+ public int getUnknownInt88() { return Util.readIntBE(unknownInt88); }
+ /** Unknown integer. */
+ public int getUnknownInt92() { return Util.readIntBE(unknownInt92); }
+ /** Unknown integer. */
+ public int getUnknownInt96() { return Util.readIntBE(unknownInt96); }
+ /** Unknown integer. */
+ public int getUnknownInt100() { return Util.readIntBE(unknownInt100); }
+ /** Initialization Vector for encryption-key unwrapping. */
+ public byte[] getUnwrapIv() { return Util.readByteArrayBE(unwrapIv); }
+ /** Length of wrappedAesKey in bytes (max 256). */
+ public int getLenWrappedAesKey() { return Util.readIntBE(lenWrappedAesKey); }
+ /** The AES key (wrapped). */
+ public byte[] getWrappedAesKey() { return Util.readByteArrayBE(wrappedAesKey); }
+ /** Unknown integer (observed value: 91/0x5B). */
+ public int getUnknownInt396() { return Util.readIntBE(unknownInt396); }
+ /** Unknown integer (observed value: 160/0xA0). */
+ public int getUnknownInt400() { return Util.readIntBE(unknownInt400); }
+ /** Unknown data (observed: 8 bytes filled, rest 0). */
+ public byte[] getUnknown404() { return Util.readByteArrayBE(unknown404); }
+ /** Length of wrappedHmacSha1Key in bytes (max 256). */
+ public int getLenWrappedHmacSha1Key() { return Util.readIntBE(lenWrappedHmacSha1Key); }
+ /** The HMAC SHA-1 key (wrapped). */
+ public byte[] getWrappedHmacSha1Key() { return Util.readByteArrayBE(wrappedHmacSha1Key); }
+ /** Unknown integer (observed value: 91/0x5B). */
+ public int getUnknownInt696() { return Util.readIntBE(unknownInt696); }
+ /** Unknown integer (observed value: 160/0xA0). */
+ public int getUnknownInt700() { return Util.readIntBE(unknownInt700); }
+ /** Unknown data (obs. 8 bytes filled, rest 0). */
+ public byte[] getUnknown704() { return Util.readByteArrayBE(unknown704); }
+ /** Length of wrappedIntegrityKey. */
+ public int getLenWrappedIntegrityKey() { return Util.readIntBE(lenWrappedIntegrityKey); }
+ /** Integrity key. */
+ public byte[] getWrappedIntegrityKey() { return Util.readByteArrayBE(wrappedIntegrityKey); }
+ /** Length of unknown1000. */
+ public int getLenUnknown1000() { return Util.readIntBE(lenUnknown1000); }
+ /** Unknown key-like field with length specified by unknownRnd95GaLen (max 256). */
+ public byte[] getUnknown1000() { return Util.readByteArrayBE(unknown1000); }
+ /** Length in bytes of the underlying data stream. */
+ public long getDecryptedDataLength() { return Util.readLongBE(decryptedDataLength); }
+ /** Could be a variable indicating header version (observed: 1/0x1). */
+ public int getPossibleHeaderVersion() { return Util.readIntBE(possibleHeaderVersion); }
+ /** Header signature (ASCII: 'cdsaencr'). */
+ public byte[] getSignature() { return Util.readByteArrayBE(signature); }
+
+ public void printFields(PrintStream ps, String prefix) {
+ ps.println(prefix + " unknown0: 0x" + Util.byteArrayToHexString(getUnknown0()));
+ ps.println(prefix + " blockSize: " + Util.unsign(getBlockSize()));
+ ps.println(prefix + " unknownInt20: " + getUnknownInt20());
+ ps.println(prefix + " unknownInt24: " + getUnknownInt24());
+ ps.println(prefix + " unknownInt28: " + getUnknownInt28());
+ ps.println(prefix + " unknownInt32: " + getUnknownInt32());
+ ps.println(prefix + " unknownInt36: " + getUnknownInt36());
+ ps.println(prefix + " unknownInt40: " + getUnknownInt40());
+ ps.println(prefix + " unknownInt44: " + getUnknownInt44());
+ ps.println(prefix + " kdfIterationCount: " + Util.unsign(getKdfIterationCount()));
+ ps.println(prefix + " kdfSaltLen: " + Util.unsign(getKdfSaltLen()));
+ ps.println(prefix + " kdfSalt: 0x" + Util.byteArrayToHexString(getKdfSalt()));
+ ps.println(prefix + " unknownInt88: " + getUnknownInt88());
+ ps.println(prefix + " unknownInt92: " + getUnknownInt92());
+ ps.println(prefix + " unknownInt96: " + getUnknownInt96());
+ ps.println(prefix + " unknownInt100: " + getUnknownInt100());
+ ps.println(prefix + " unwrapIv: 0x" + Util.byteArrayToHexString(getUnwrapIv()));
+ ps.println(prefix + " lenWrappedAesKey: " + Util.unsign(getLenWrappedAesKey()));
+ ps.println(prefix + " wrappedAesKey: 0x" + Util.byteArrayToHexString(getWrappedAesKey()));
+ ps.println(prefix + " unknownInt396: " + getUnknownInt396());
+ ps.println(prefix + " unknownInt400: " + getUnknownInt400());
+ ps.println(prefix + " unknown404: 0x" + Util.byteArrayToHexString(getUnknown404()));
+ ps.println(prefix + " lenWrappedHmacSha1Key: " + Util.unsign(getLenWrappedHmacSha1Key()));
+ ps.println(prefix + " wrappedHmacSha1Key: 0x" + Util.byteArrayToHexString(getWrappedHmacSha1Key()));
+ ps.println(prefix + " unknownInt696: " + getUnknownInt696());
+ ps.println(prefix + " unknownInt700: " + getUnknownInt700());
+ ps.println(prefix + " unknown704: 0x" + Util.byteArrayToHexString(getUnknown704()));
+ ps.println(prefix + " lenWrappedIntegrityKey: " + Util.unsign(getLenWrappedIntegrityKey()));
+ ps.println(prefix + " wrappedIntegrityKey: 0x" + Util.byteArrayToHexString(getWrappedIntegrityKey()));
+ ps.println(prefix + " lenUnknown1000: " + getLenUnknown1000());
+ ps.println(prefix + " unknown1000: 0x" + Util.byteArrayToHexString(getUnknown1000()));
+ ps.println(prefix + " decryptedDataLength: " + getDecryptedDataLength());
+ ps.println(prefix + " possibleHeaderVersion: " + getPossibleHeaderVersion());
+ ps.println(prefix + " signature: \"" + Util.toASCIIString(getSignature()) + "\"");
+ }
+
+ public void print(PrintStream ps, String prefix) {
+ ps.println(prefix + "V1Header:");
+ printFields(ps, prefix);
+ }
+
+ public byte[] getBytes() {
+ byte[] result = new byte[length()];
+ int offset = 0;
+ System.arraycopy(this.unknown0, 0, result, offset, this.unknown0.length); offset += this.unknown0.length;
+ System.arraycopy(this.blockSize, 0, result, offset, this.blockSize.length); offset += this.blockSize.length;
+ System.arraycopy(this.unknownInt20, 0, result, offset, this.unknownInt20.length); offset += this.unknownInt20.length;
+ System.arraycopy(this.unknownInt24, 0, result, offset, this.unknownInt24.length); offset += this.unknownInt24.length;
+ System.arraycopy(this.unknownInt28, 0, result, offset, this.unknownInt28.length); offset += this.unknownInt28.length;
+ System.arraycopy(this.unknownInt32, 0, result, offset, this.unknownInt32.length); offset += this.unknownInt32.length;
+ System.arraycopy(this.unknownInt36, 0, result, offset, this.unknownInt36.length); offset += this.unknownInt36.length;
+ System.arraycopy(this.unknownInt40, 0, result, offset, this.unknownInt40.length); offset += this.unknownInt40.length;
+ System.arraycopy(this.unknownInt44, 0, result, offset, this.unknownInt44.length); offset += this.unknownInt44.length;
+ System.arraycopy(this.kdfIterationCount, 0, result, offset, this.kdfIterationCount.length); offset += this.kdfIterationCount.length;
+ System.arraycopy(this.kdfSaltLen, 0, result, offset, this.kdfSaltLen.length); offset += this.kdfSaltLen.length;
+ System.arraycopy(this.kdfSalt, 0, result, offset, this.kdfSalt.length); offset += this.kdfSalt.length;
+ System.arraycopy(this.unknownInt88, 0, result, offset, this.unknownInt88.length); offset += this.unknownInt88.length;
+ System.arraycopy(this.unknownInt92, 0, result, offset, this.unknownInt92.length); offset += this.unknownInt92.length;
+ System.arraycopy(this.unknownInt96, 0, result, offset, this.unknownInt96.length); offset += this.unknownInt96.length;
+ System.arraycopy(this.unknownInt100, 0, result, offset, this.unknownInt100.length); offset += this.unknownInt100.length;
+ System.arraycopy(this.unwrapIv, 0, result, offset, this.unwrapIv.length); offset += this.unwrapIv.length;
+ System.arraycopy(this.lenWrappedAesKey, 0, result, offset, this.lenWrappedAesKey.length); offset += this.lenWrappedAesKey.length;
+ System.arraycopy(this.wrappedAesKey, 0, result, offset, this.wrappedAesKey.length); offset += this.wrappedAesKey.length;
+ System.arraycopy(this.unknownInt396, 0, result, offset, this.unknownInt396.length); offset += this.unknownInt396.length;
+ System.arraycopy(this.unknownInt400, 0, result, offset, this.unknownInt400.length); offset += this.unknownInt400.length;
+ System.arraycopy(this.unknown404, 0, result, offset, this.unknown404.length); offset += this.unknown404.length;
+ System.arraycopy(this.lenWrappedHmacSha1Key, 0, result, offset, this.lenWrappedHmacSha1Key.length); offset += this.lenWrappedHmacSha1Key.length;
+ System.arraycopy(this.wrappedHmacSha1Key, 0, result, offset, this.wrappedHmacSha1Key.length); offset += this.wrappedHmacSha1Key.length;
+ System.arraycopy(this.unknownInt696, 0, result, offset, this.unknownInt696.length); offset += this.unknownInt696.length;
+ System.arraycopy(this.unknownInt700, 0, result, offset, this.unknownInt700.length); offset += this.unknownInt700.length;
+ System.arraycopy(this.unknown704, 0, result, offset, this.unknown704.length); offset += this.unknown704.length;
+ System.arraycopy(this.lenWrappedIntegrityKey, 0, result, offset, this.lenWrappedIntegrityKey.length); offset += this.lenWrappedIntegrityKey.length;
+ System.arraycopy(this.wrappedIntegrityKey, 0, result, offset, this.wrappedIntegrityKey.length); offset += this.wrappedIntegrityKey.length;
+ System.arraycopy(this.lenUnknown1000, 0, result, offset, this.lenUnknown1000.length); offset += this.lenUnknown1000.length;
+ System.arraycopy(this.unknown1000, 0, result, offset, this.unknown1000.length); offset += this.unknown1000.length;
+ System.arraycopy(this.decryptedDataLength, 0, result, offset, this.decryptedDataLength.length); offset += this.decryptedDataLength.length;
+ System.arraycopy(this.possibleHeaderVersion, 0, result, offset, this.possibleHeaderVersion.length); offset += this.possibleHeaderVersion.length;
+ System.arraycopy(this.signature, 0, result, offset, this.signature.length); offset += this.signature.length;
+ return result;
+ }
+}
diff --git a/src/org/catacombae/dmg/encrypted/V1Header.struct b/src/org/catacombae/dmg/encrypted/V1Header.struct
new file mode 100644
index 0000000..d1ad68e
--- /dev/null
+++ b/src/org/catacombae/dmg/encrypted/V1Header.struct
@@ -0,0 +1,15 @@
+struct V1Header {
+ uint8_t unknown0[48]; Unknown data.
+ uint32_t kdfIterationCount; Iteration count (normally 1000).
+ uint32_t kdfSaltLen; Length of kdfSalt (in bytes).
+ uint8_t kdfSalt[48]; Salt value for key derivation.
+ uint8_t unwrapIv[32]; Initialization Vector for encryption-key unwrapping.
+ uint32_t lenWrappedAesKey; Length of wrappedAesKey (max 296).
+ uint8_t wrappedAesKey[296]; The AES key (wrapped).
+ uint32_t lenWrappedHmacSha1Key; Length of wrappedHmacSha1Key (max 300).
+ uint8_t wrappedHmacSha1Key[300]; The HMAC SHA-1 key (wrapped).
+ uint32_t lenWrappedIntegrityKey; Length of wrappedIntegrityKey.
+ uint8_t wrappedIntegrityKey[48]; Integrity key.
+ uint8_t unknown792[476]; Unknown data.
+ uint8_t signature[8]; Header signature (ASCII: 'cdsaencr').
+};
\ No newline at end of file
diff --git a/src/org/catacombae/dmg/encrypted/V2Header.java b/src/org/catacombae/dmg/encrypted/V2Header.java
new file mode 100644
index 0000000..ef50829
--- /dev/null
+++ b/src/org/catacombae/dmg/encrypted/V2Header.java
@@ -0,0 +1,282 @@
+/*-
+ * Copyright (C) 2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ *
+ * This code was written from studying vfdecrypt, Copyright (c) 2006
+ * Ralf-Philipp Weinmann
+ * Jacob Appelbaum
+ * Christian Fromme
+ *
+ * [I'm not sure if their copyright and license terms need to be applied,
+ * but in case they do, the original license terms are reprinted below
+ * as required by the license.]
+ *
+ * The vfdecrypt license says:
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ */
+
+package org.catacombae.dmg.encrypted;
+
+import java.io.PrintStream;
+import org.catacombae.dmgextractor.Util;
+
+/** This class was generated by CStructToJavaClass. */
+public class V2Header {
+ /*
+ * struct V2Header
+ * size: 248 bytes
+ * description:
+ *
+ * BP Size Type Identifier Description
+ * ------------------------------------------------------------------------------------------------------------
+ * 0 1*8 uint8_t[8] signature Header signature (ASCII: 'encrcdsa').
+ * 8 4 uint32_t possibleHeaderVersion Possibly the version of the encrypted volume format.
+ * 12 4 uint32_t laban Unknown variable with observed value 16/0x10.
+ * 16 4 uint32_t edward Unknown variable with observed value 5/0x5.
+ * 20 4 uint32_t palle Unknown variable with observed value 0x80000001.
+ * 24 4 uint32_t lisa Unknown variable with observed value 128/0x80.
+ * 28 4 uint32_t unknownInt28 Unknown variable with observed value 91/0x5B.
+ * 32 4 uint32_t unknownInt32 Unknown variable with observed value 160/0xA0.
+ * 36 1*16 uint8_t[16] unknown1 Unknown binary data.
+ * 52 4 uint32_t blockSize Block size of the encrypted block data.
+ * 56 8 uint64_t encryptedDataLength Length in bytes of the data that has been encrypted.
+ * 64 8 uint64_t offsetToDataStart Offset to the start of the encrypted block data.
+ * 72 4 uint32_t unknownInt72 Unknown variable with observed value 1/0x1.
+ * 76 4 uint32_t unknownInt76 Unknown variable with observed value 1/0x1.
+ * 80 8 uint64_t possiblePointerToKdfAlgorithm Could be a pointer to where kdf_algorithm is located.
+ * 88 8 uint64_t unknownLong88 Unknown variable with observed value 616/0x268.
+ * 96 4 uint32_t kdfAlgorithm Algorithm of the key derivation function.
+ * 100 4 uint32_t kdfPrngAlgorithm ?
+ * 104 4 uint32_t kdfIterationCount Iteration count (normally 1000).
+ * 108 4 uint32_t kdfSaltLen Length of kdfSalt (in bytes).
+ * 112 1*32 uint8_t[32] kdfSalt Salt value for key derivation.
+ * 144 4 uint32_t blobEncIvSize Size of blobEncIv.
+ * 148 1*32 uint8_t[32] blobEncIv Initialization Vector for encryption-key unwrapping.
+ * 180 4 uint32_t blobEncKeyBits Number of bits in the keyblob's encryption key.
+ * 184 4 uint32_t blobEncAlgorithm Encryption algorithm used to encrypt the key blob.
+ * 188 4 uint32_t blobEncPadding Padding. (?)
+ * 192 4 uint32_t blobEncMode Encryption mode for the algorithm.
+ * 196 4 uint32_t encryptedKeyblobSize Size of encryptedKeyBlob.
+ * 200 1*48 uint8_t[48] encryptedKeyblob The encrypted key blob, containing all keys.
+ */
+
+ public static final int STRUCTSIZE = 248;
+
+ private final byte[] signature = new byte[1*8];
+ private final byte[] possibleHeaderVersion = new byte[4];
+ private final byte[] laban = new byte[4];
+ private final byte[] edward = new byte[4];
+ private final byte[] palle = new byte[4];
+ private final byte[] lisa = new byte[4];
+ private final byte[] unknownInt28 = new byte[4];
+ private final byte[] unknownInt32 = new byte[4];
+ private final byte[] unknown1 = new byte[1*16];
+ private final byte[] blockSize = new byte[4];
+ private final byte[] encryptedDataLength = new byte[8];
+ private final byte[] offsetToDataStart = new byte[8];
+ private final byte[] unknownInt72 = new byte[4];
+ private final byte[] unknownInt76 = new byte[4];
+ private final byte[] possiblePointerToKdfAlgorithm = new byte[8];
+ private final byte[] unknownLong88 = new byte[8];
+ private final byte[] kdfAlgorithm = new byte[4];
+ private final byte[] kdfPrngAlgorithm = new byte[4];
+ private final byte[] kdfIterationCount = new byte[4];
+ private final byte[] kdfSaltLen = new byte[4];
+ private final byte[] kdfSalt = new byte[1*32];
+ private final byte[] blobEncIvSize = new byte[4];
+ private final byte[] blobEncIv = new byte[1*32];
+ private final byte[] blobEncKeyBits = new byte[4];
+ private final byte[] blobEncAlgorithm = new byte[4];
+ private final byte[] blobEncPadding = new byte[4];
+ private final byte[] blobEncMode = new byte[4];
+ private final byte[] encryptedKeyblobSize = new byte[4];
+ private final byte[] encryptedKeyblob = new byte[1*48];
+
+ public V2Header(byte[] data, int offset) {
+ System.arraycopy(data, offset+0, signature, 0, 1*8);
+ System.arraycopy(data, offset+8, possibleHeaderVersion, 0, 4);
+ System.arraycopy(data, offset+12, laban, 0, 4);
+ System.arraycopy(data, offset+16, edward, 0, 4);
+ System.arraycopy(data, offset+20, palle, 0, 4);
+ System.arraycopy(data, offset+24, lisa, 0, 4);
+ System.arraycopy(data, offset+28, unknownInt28, 0, 4);
+ System.arraycopy(data, offset+32, unknownInt32, 0, 4);
+ System.arraycopy(data, offset+36, unknown1, 0, 1*16);
+ System.arraycopy(data, offset+52, blockSize, 0, 4);
+ System.arraycopy(data, offset+56, encryptedDataLength, 0, 8);
+ System.arraycopy(data, offset+64, offsetToDataStart, 0, 8);
+ System.arraycopy(data, offset+72, unknownInt72, 0, 4);
+ System.arraycopy(data, offset+76, unknownInt76, 0, 4);
+ System.arraycopy(data, offset+80, possiblePointerToKdfAlgorithm, 0, 8);
+ System.arraycopy(data, offset+88, unknownLong88, 0, 8);
+ System.arraycopy(data, offset+96, kdfAlgorithm, 0, 4);
+ System.arraycopy(data, offset+100, kdfPrngAlgorithm, 0, 4);
+ System.arraycopy(data, offset+104, kdfIterationCount, 0, 4);
+ System.arraycopy(data, offset+108, kdfSaltLen, 0, 4);
+ System.arraycopy(data, offset+112, kdfSalt, 0, 1*32);
+ System.arraycopy(data, offset+144, blobEncIvSize, 0, 4);
+ System.arraycopy(data, offset+148, blobEncIv, 0, 1*32);
+ System.arraycopy(data, offset+180, blobEncKeyBits, 0, 4);
+ System.arraycopy(data, offset+184, blobEncAlgorithm, 0, 4);
+ System.arraycopy(data, offset+188, blobEncPadding, 0, 4);
+ System.arraycopy(data, offset+192, blobEncMode, 0, 4);
+ System.arraycopy(data, offset+196, encryptedKeyblobSize, 0, 4);
+ System.arraycopy(data, offset+200, encryptedKeyblob, 0, 1*48);
+ }
+
+ public static int length() { return STRUCTSIZE; }
+
+ /** Header signature (ASCII: 'encrcdsa'). */
+ public byte[] getSignature() { return Util.readByteArrayBE(signature); }
+ /** Possibly the version of the encrypted volume format. */
+ public int getPossibleHeaderVersion() { return Util.readIntBE(possibleHeaderVersion); }
+ /** Unknown variable with observed value 16/0x10. */
+ public int getLaban() { return Util.readIntBE(laban); }
+ /** Unknown variable with observed value 5/0x5. */
+ public int getEdward() { return Util.readIntBE(edward); }
+ /** Unknown variable with observed value 0x80000001. */
+ public int getPalle() { return Util.readIntBE(palle); }
+ /** Unknown variable with observed value 128/0x80. */
+ public int getLisa() { return Util.readIntBE(lisa); }
+ /** Unknown variable with observed value 91/0x5B. */
+ public int getUnknownInt28() { return Util.readIntBE(unknownInt28); }
+ /** Unknown variable with observed value 160/0xA0. */
+ public int getUnknownInt32() { return Util.readIntBE(unknownInt32); }
+ /** Unknown binary data. */
+ public byte[] getUnknown1() { return Util.readByteArrayBE(unknown1); }
+ /** Block size of the encrypted block data. */
+ public int getBlockSize() { return Util.readIntBE(blockSize); }
+ /** Length in bytes of the data that has been encrypted. */
+ public long getEncryptedDataLength() { return Util.readLongBE(encryptedDataLength); }
+ /** Offset to the start of the encrypted block data. */
+ public long getOffsetToDataStart() { return Util.readLongBE(offsetToDataStart); }
+ /** Unknown variable with observed value 1/0x1. */
+ public int getUnknownInt72() { return Util.readIntBE(unknownInt72); }
+ /** Unknown variable with observed value 1/0x1. */
+ public int getUnknownInt76() { return Util.readIntBE(unknownInt76); }
+ /** Could be a pointer to where kdf_algorithm is located. */
+ public long getPossiblePointerToKdfAlgorithm() { return Util.readLongBE(possiblePointerToKdfAlgorithm); }
+ /** Unknown variable with observed value 616/0x268. */
+ public long getUnknownLong88() { return Util.readLongBE(unknownLong88); }
+ /** Algorithm of the key derivation function. */
+ public int getKdfAlgorithm() { return Util.readIntBE(kdfAlgorithm); }
+ /** ? */
+ public int getKdfPrngAlgorithm() { return Util.readIntBE(kdfPrngAlgorithm); }
+ /** Iteration count (normally 1000). */
+ public int getKdfIterationCount() { return Util.readIntBE(kdfIterationCount); }
+ /** Length of kdfSalt (in bytes). */
+ public int getKdfSaltLen() { return Util.readIntBE(kdfSaltLen); }
+ /** Salt value for key derivation. */
+ public byte[] getKdfSalt() { return Util.readByteArrayBE(kdfSalt); }
+ /** Size of blobEncIv. */
+ public int getBlobEncIvSize() { return Util.readIntBE(blobEncIvSize); }
+ /** Initialization Vector for encryption-key unwrapping. */
+ public byte[] getBlobEncIv() { return Util.readByteArrayBE(blobEncIv); }
+ /** Number of bits in the keyblob's encryption key. */
+ public int getBlobEncKeyBits() { return Util.readIntBE(blobEncKeyBits); }
+ /** Encryption algorithm used to encrypt the key blob. */
+ public int getBlobEncAlgorithm() { return Util.readIntBE(blobEncAlgorithm); }
+ /** Padding. (?) */
+ public int getBlobEncPadding() { return Util.readIntBE(blobEncPadding); }
+ /** Encryption mode for the algorithm. */
+ public int getBlobEncMode() { return Util.readIntBE(blobEncMode); }
+ /** Size of encryptedKeyBlob. */
+ public int getEncryptedKeyblobSize() { return Util.readIntBE(encryptedKeyblobSize); }
+ /** The encrypted key blob, containing all keys. */
+ public byte[] getEncryptedKeyblob() { return Util.readByteArrayBE(encryptedKeyblob); }
+
+ public void printFields(PrintStream ps, String prefix) {
+ ps.println(prefix + " signature: \"" + Util.toASCIIString(getSignature()) + "\"");
+ ps.println(prefix + " possibleHeaderVersion: " + getPossibleHeaderVersion());
+ ps.println(prefix + " laban: " + getLaban());
+ ps.println(prefix + " edward: " + getEdward());
+ ps.println(prefix + " palle: " + getPalle());
+ ps.println(prefix + " lisa: " + getLisa());
+ ps.println(prefix + " unknownInt28: " + getUnknownInt28());
+ ps.println(prefix + " unknownInt32: " + getUnknownInt32());
+ ps.println(prefix + " unknown1: " + getUnknown1());
+ ps.println(prefix + " blockSize: " + getBlockSize());
+ ps.println(prefix + " encryptedDataLength: " + getEncryptedDataLength());
+ ps.println(prefix + " offsetToDataStart: " + getOffsetToDataStart());
+ ps.println(prefix + " unknownInt72: " + getUnknownInt72());
+ ps.println(prefix + " unknownInt76: " + getUnknownInt76());
+ ps.println(prefix + " possiblePointerToKdfAlgorithm: " + getPossiblePointerToKdfAlgorithm());
+ ps.println(prefix + " unknownLong88: " + getUnknownLong88());
+ ps.println(prefix + " kdfAlgorithm: " + getKdfAlgorithm());
+ ps.println(prefix + " kdfPrngAlgorithm: " + getKdfPrngAlgorithm());
+ ps.println(prefix + " kdfIterationCount: " + getKdfIterationCount());
+ ps.println(prefix + " kdfSaltLen: " + getKdfSaltLen());
+ ps.println(prefix + " kdfSalt: 0x" + Util.byteArrayToHexString(getKdfSalt()));
+ ps.println(prefix + " blobEncIvSize: " + getBlobEncIvSize());
+ ps.println(prefix + " blobEncIv: 0x" + Util.byteArrayToHexString(getBlobEncIv()));
+ ps.println(prefix + " blobEncKeyBits: " + getBlobEncKeyBits());
+ ps.println(prefix + " blobEncAlgorithm: " + getBlobEncAlgorithm());
+ ps.println(prefix + " blobEncPadding: " + getBlobEncPadding());
+ ps.println(prefix + " blobEncMode: " + getBlobEncMode());
+ ps.println(prefix + " encryptedKeyblobSize: " + getEncryptedKeyblobSize());
+ ps.println(prefix + " encryptedKeyblob: 0x" + Util.byteArrayToHexString(getEncryptedKeyblob()));
+ }
+
+ public void print(PrintStream ps, String prefix) {
+ ps.println(prefix + "V2Header:");
+ printFields(ps, prefix);
+ }
+
+ public byte[] getBytes() {
+ byte[] result = new byte[length()];
+ int offset = 0;
+ System.arraycopy(this.signature, 0, result, offset, this.signature.length); offset += this.signature.length;
+ System.arraycopy(this.possibleHeaderVersion, 0, result, offset, this.possibleHeaderVersion.length); offset += this.possibleHeaderVersion.length;
+ System.arraycopy(this.laban, 0, result, offset, this.laban.length); offset += this.laban.length;
+ System.arraycopy(this.edward, 0, result, offset, this.edward.length); offset += this.edward.length;
+ System.arraycopy(this.palle, 0, result, offset, this.palle.length); offset += this.palle.length;
+ System.arraycopy(this.lisa, 0, result, offset, this.lisa.length); offset += this.lisa.length;
+ System.arraycopy(this.unknownInt28, 0, result, offset, this.unknownInt28.length); offset += this.unknownInt28.length;
+ System.arraycopy(this.unknownInt32, 0, result, offset, this.unknownInt32.length); offset += this.unknownInt32.length;
+ System.arraycopy(this.unknown1, 0, result, offset, this.unknown1.length); offset += this.unknown1.length;
+ System.arraycopy(this.blockSize, 0, result, offset, this.blockSize.length); offset += this.blockSize.length;
+ System.arraycopy(this.encryptedDataLength, 0, result, offset, this.encryptedDataLength.length); offset += this.encryptedDataLength.length;
+ System.arraycopy(this.offsetToDataStart, 0, result, offset, this.offsetToDataStart.length); offset += this.offsetToDataStart.length;
+ System.arraycopy(this.unknownInt72, 0, result, offset, this.unknownInt72.length); offset += this.unknownInt72.length;
+ System.arraycopy(this.unknownInt76, 0, result, offset, this.unknownInt76.length); offset += this.unknownInt76.length;
+ System.arraycopy(this.possiblePointerToKdfAlgorithm, 0, result, offset, this.possiblePointerToKdfAlgorithm.length); offset += this.possiblePointerToKdfAlgorithm.length;
+ System.arraycopy(this.unknownLong88, 0, result, offset, this.unknownLong88.length); offset += this.unknownLong88.length;
+ System.arraycopy(this.kdfAlgorithm, 0, result, offset, this.kdfAlgorithm.length); offset += this.kdfAlgorithm.length;
+ System.arraycopy(this.kdfPrngAlgorithm, 0, result, offset, this.kdfPrngAlgorithm.length); offset += this.kdfPrngAlgorithm.length;
+ System.arraycopy(this.kdfIterationCount, 0, result, offset, this.kdfIterationCount.length); offset += this.kdfIterationCount.length;
+ System.arraycopy(this.kdfSaltLen, 0, result, offset, this.kdfSaltLen.length); offset += this.kdfSaltLen.length;
+ System.arraycopy(this.kdfSalt, 0, result, offset, this.kdfSalt.length); offset += this.kdfSalt.length;
+ System.arraycopy(this.blobEncIvSize, 0, result, offset, this.blobEncIvSize.length); offset += this.blobEncIvSize.length;
+ System.arraycopy(this.blobEncIv, 0, result, offset, this.blobEncIv.length); offset += this.blobEncIv.length;
+ System.arraycopy(this.blobEncKeyBits, 0, result, offset, this.blobEncKeyBits.length); offset += this.blobEncKeyBits.length;
+ System.arraycopy(this.blobEncAlgorithm, 0, result, offset, this.blobEncAlgorithm.length); offset += this.blobEncAlgorithm.length;
+ System.arraycopy(this.blobEncPadding, 0, result, offset, this.blobEncPadding.length); offset += this.blobEncPadding.length;
+ System.arraycopy(this.blobEncMode, 0, result, offset, this.blobEncMode.length); offset += this.blobEncMode.length;
+ System.arraycopy(this.encryptedKeyblobSize, 0, result, offset, this.encryptedKeyblobSize.length); offset += this.encryptedKeyblobSize.length;
+ System.arraycopy(this.encryptedKeyblob, 0, result, offset, this.encryptedKeyblob.length); offset += this.encryptedKeyblob.length;
+ return result;
+ }
+
+}
diff --git a/src/org/catacombae/dmg/encrypted/V2Header.struct b/src/org/catacombae/dmg/encrypted/V2Header.struct
new file mode 100644
index 0000000..317c03a
--- /dev/null
+++ b/src/org/catacombae/dmg/encrypted/V2Header.struct
@@ -0,0 +1,23 @@
+struct V2Header {
+ uint8_t signature[8]; Header signature (ASCII: 'encrcdsa').
+ uint8_t unknown1[44]; Unknown data.
+ uint32_t blockSize; Block size of the encrypted block data.
+ uint8_t unknown2[12]; Unknown data.
+ uint32_t offsetToDataStart; Offset to the start of the encrypted block data.
+ uint8_t unknown3[12]; Unknown data.
+ uint32_t possiblePointerToKdfAlgorithm; Could be a pointer to where kdf_algorithm is located.
+ uint8_t unknown4[8]; Unknown data.
+ uint32_t kdfAlgorithm; Algorithm of the key derivation function.
+ uint32_t kdfPrngAlgorithm; ?
+ uint32_t kdfIterationCount; Iteration count (normally 1000).
+ uint32_t kdfSaltLen; Length of kdfSalt (in bytes).
+ uint8_t kdfSalt[32]; Salt value for key derivation.
+ uint32_t blobEncIvSize; Size of blobEncIv.
+ uint8_t blobEncIv[32]; Initialization Vector for encryption-key unwrapping.
+ uint32_t blobEncKeyBits; Number of bits in the keyblob's encryption key.
+ uint32_t blobEncAlgorithm; Encryption algorithm used to encrypt the key blob.
+ uint32_t blobEncPadding; Padding. (?)
+ uint32_t blobEncMode; Encryption mode for the algorithm.
+ uint32_t encryptedKeyblobSize; Size of encryptedKeyBlob.
+ uint8_t encryptedKeyblob[48]; The encrypted key blob, containing all keys.
+};
\ No newline at end of file
diff --git a/src/org/catacombae/dmg/sparsebundle/Band.java b/src/org/catacombae/dmg/sparsebundle/Band.java
new file mode 100644
index 0000000..e4850d6
--- /dev/null
+++ b/src/org/catacombae/dmg/sparsebundle/Band.java
@@ -0,0 +1,78 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.catacombae.dmg.sparsebundle;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileLock;
+import java.util.Arrays;
+
+/**
+ *
+ * @author erik
+ */
+class Band extends BundleMember {
+ private final long bandActualSize;
+ private final long bandVirtualSize;
+
+ public Band(RandomAccessFile tokenFile, FileLock tokenFileLock,
+ long bandSize) throws IOException {
+ super(tokenFile, tokenFileLock);
+
+ this.bandVirtualSize = bandSize;
+ try {
+ this.bandActualSize = tokenFile.length();
+ } catch(IOException ex) {
+ super.close();
+ throw ex;
+ }
+ }
+
+ public int read(long offset, byte[] dest, int destOffset, int destLength)
+ throws IOException {
+ if(offset < 0)
+ throw new IllegalArgumentException("negative offset.");
+ if(dest == null)
+ throw new IllegalArgumentException("dest is null.");
+ if(destOffset < 0 || destOffset > dest.length)
+ throw new IllegalArgumentException("destOffset out of range.");
+ if(destLength < 0 || destLength > (dest.length - destOffset))
+ throw new IllegalArgumentException("destLength out of range.");
+
+ if(offset >= bandVirtualSize)
+ return -1;
+
+ final int readLength;
+ if(destLength > bandVirtualSize - offset)
+ readLength = (int) (bandVirtualSize - offset);
+ else
+ readLength = destLength;
+
+ final int actualLength;
+ if(offset >= bandActualSize)
+ actualLength = 0;
+ else {
+ long remainingActualBytes = bandActualSize - offset;
+ if(readLength > remainingActualBytes)
+ actualLength = (int) remainingActualBytes;
+ else
+ actualLength = readLength;
+ }
+
+ this.file.seek(offset);
+ int bytesRead = this.file.read(dest, destOffset, actualLength);
+ if(bytesRead != actualLength)
+ return bytesRead;
+ else {
+ if(actualLength != readLength) {
+ Arrays.fill(dest, destOffset + actualLength,
+ destOffset + readLength, (byte) 0);
+ }
+
+ return readLength;
+ }
+ }
+}
diff --git a/src/org/catacombae/dmg/sparsebundle/BundleMember.java b/src/org/catacombae/dmg/sparsebundle/BundleMember.java
new file mode 100644
index 0000000..1163d76
--- /dev/null
+++ b/src/org/catacombae/dmg/sparsebundle/BundleMember.java
@@ -0,0 +1,41 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.catacombae.dmg.sparsebundle;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileLock;
+
+/**
+ *
+ * @author erik
+ */
+abstract class BundleMember {
+
+ /* Backing store. */
+ protected RandomAccessFile file;
+ protected FileLock fileLock;
+
+ public BundleMember(RandomAccessFile file, FileLock fileLock) {
+ this.file = file;
+ this.fileLock = fileLock;
+ }
+
+ public void close() {
+ try {
+ fileLock.release();
+ } catch(IOException ioe) {
+ ioe.printStackTrace();
+ }
+
+ try {
+ file.close();
+ } catch(IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+}
diff --git a/src/org/catacombae/dmg/sparsebundle/Dump.java b/src/org/catacombae/dmg/sparsebundle/Dump.java
new file mode 100644
index 0000000..8a50d3b
--- /dev/null
+++ b/src/org/catacombae/dmg/sparsebundle/Dump.java
@@ -0,0 +1,34 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.catacombae.dmg.sparsebundle;
+
+import java.io.File;
+
+/**
+ *
+ * @author erik
+ */
+public class Dump {
+ public static void main(String[] args) {
+ ReadableSparseBundleStream stream =
+ new ReadableSparseBundleStream(new File(args[0]));
+ byte[] buf = new byte[512*1024];
+ long bytesRead = 0;
+
+ while(true) {
+ int curBytesRead = stream.read(buf);
+ if(curBytesRead == -1)
+ break;
+ else if(curBytesRead < 0)
+ throw new RuntimeException("Wtf... curBytesRead=" +
+ curBytesRead);
+
+ bytesRead += curBytesRead;
+
+ System.out.write(buf, 0, curBytesRead);
+ }
+ }
+}
diff --git a/src/org/catacombae/dmg/sparsebundle/Info.java b/src/org/catacombae/dmg/sparsebundle/Info.java
new file mode 100644
index 0000000..dca2534
--- /dev/null
+++ b/src/org/catacombae/dmg/sparsebundle/Info.java
@@ -0,0 +1,141 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.catacombae.dmg.sparsebundle;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.io.Reader;
+import java.nio.channels.FileLock;
+import org.catacombae.plist.PlistNode;
+import org.catacombae.plist.XmlPlist;
+import org.catacombae.util.Util;
+
+/**
+ *
+ * @author erik
+ */
+class Info extends BundleMember {
+ private long bandSize;
+ private long size;
+
+ public Info(RandomAccessFile plistFile, FileLock plistFileLock)
+ throws IOException {
+ super(plistFile, plistFileLock);
+ refresh();
+ }
+
+ public long getBandSize() { return bandSize; }
+ public long getSize() { return size; }
+
+ /**
+ * Re-reads the contents of the .plist file and updates the cached data.
+ *
+ * @throws IOException if there is an I/O error, or the data in the plist is
+ * invalid.
+ */
+ protected void refresh() throws IOException {
+ long fileLength = file.length();
+ if(fileLength > Integer.MAX_VALUE)
+ throw new ArrayIndexOutOfBoundsException("Info.plist is " +
+ "unreasonably large and doesn't fit in memory.");
+
+ byte[] plistData = new byte[(int) fileLength];
+ int bytesRead = file.read(plistData);
+ if(bytesRead != fileLength)
+ throw new IOException("Failed to read entire file. Read " +
+ bytesRead + "/" + fileLength + " bytes.");
+
+ XmlPlist plist = new XmlPlist(plistData, true);
+ PlistNode dictNode = plist.getRootNode().cd("dict");
+ if(dictNode == null) {
+ throw new IOException("Malformed Info.plist file: No 'dict' " +
+ "element at root.");
+ }
+
+ final String cfBundleInfoDictionaryVersionKey =
+ "CFBundleInfoDictionaryVersion";
+ final String bandSizeKey =
+ "band-size";
+ final String bundleBackingstoreVersionKey =
+ "bundle-backingstore-version";
+ final String diskImageBundleTypeKey =
+ "diskimage-bundle-type";
+ final String sizeKey =
+ "size";
+
+ Reader cfBundleInfoDictionaryVersionReader =
+ dictNode.getKeyValue(cfBundleInfoDictionaryVersionKey);
+ Reader bandSizeReader =
+ dictNode.getKeyValue(bandSizeKey);
+ Reader bundleBackingstoreVersionReader =
+ dictNode.getKeyValue(bundleBackingstoreVersionKey);
+ Reader diskImageBundleTypeReader =
+ dictNode.getKeyValue(diskImageBundleTypeKey);
+ Reader sizeReader =
+ dictNode.getKeyValue(sizeKey);
+
+ if(cfBundleInfoDictionaryVersionReader == null)
+ throw new IOException("Could not find '" +
+ cfBundleInfoDictionaryVersionKey + "' key in Info.plist " +
+ "file.");
+ if(bandSizeReader == null)
+ throw new IOException("Could not find '" + bandSizeKey + "' key " +
+ "in Info.plist file.");
+ if(bundleBackingstoreVersionReader == null)
+ throw new IOException("Could not find '" +
+ bundleBackingstoreVersionKey + "' key in Info.plist file.");
+ if(diskImageBundleTypeReader == null)
+ throw new IOException("Could not find '" + diskImageBundleTypeKey +
+ "' key in Info.plist file.");
+ if(sizeReader == null)
+ throw new IOException("Could not find '" + sizeKey + "' key in " +
+ "Info.plist file.");
+
+ // We ignore the value of the dictionary version.
+ //String cfBundleInfoDictionaryVersionString =
+ // Util.readFully(cfBundleInfoDictionaryVersionReader);
+ String bandSizeString =
+ Util.readFully(bandSizeReader);
+ String bundleBackingstoreVersionString =
+ Util.readFully(bundleBackingstoreVersionReader);
+ String diskImageBundleTypeString =
+ Util.readFully(diskImageBundleTypeReader);
+ String sizeString =
+ Util.readFully(sizeReader);
+
+ if(!diskImageBundleTypeString.equals(
+ "com.apple.diskimage.sparsebundle")) {
+ throw new IOException("Unexpected value for '" +
+ diskImageBundleTypeKey + "': " + diskImageBundleTypeString);
+
+ }
+
+ if(!bundleBackingstoreVersionString.equals("1")) {
+ throw new IOException("Unknown backing store version: " +
+ bundleBackingstoreVersionString);
+
+ }
+
+ final long bandSizeLong;
+ try {
+ bandSizeLong = Long.parseLong(bandSizeString);
+ } catch(NumberFormatException nfe) {
+ throw new IOException("Illegal numeric value for " + bandSizeKey +
+ ": " + bandSizeString);
+ }
+
+ final long sizeLong;
+ try {
+ sizeLong = Long.parseLong(sizeString);
+ } catch(NumberFormatException nfe) {
+ throw new IOException("Illegal numeric value for " + sizeKey +
+ ": " + sizeString);
+ }
+
+ this.bandSize = bandSizeLong;
+ this.size = sizeLong;
+ }
+}
diff --git a/src/org/catacombae/dmg/sparsebundle/ReadableSparseBundleStream.java b/src/org/catacombae/dmg/sparsebundle/ReadableSparseBundleStream.java
new file mode 100644
index 0000000..68497d8
--- /dev/null
+++ b/src/org/catacombae/dmg/sparsebundle/ReadableSparseBundleStream.java
@@ -0,0 +1,132 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.catacombae.dmg.sparsebundle;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import org.catacombae.io.BasicReadableRandomAccessStream;
+import org.catacombae.io.RuntimeIOException;
+
+/**
+ *
+ * @author erik
+ */
+public class ReadableSparseBundleStream extends BasicReadableRandomAccessStream
+{
+ private final SparseBundle bundle;
+ private long pos = 0;
+
+ /* Band state variables. */
+ private long bandNumber = 0;
+ private Band band = null;
+
+ public ReadableSparseBundleStream(final File sparseBundleDir) {
+ this(new SparseBundle(sparseBundleDir));
+ }
+
+ ReadableSparseBundleStream(final SparseBundle bundle) {
+ this.bundle = bundle;
+ }
+
+ @Override
+ public void close() throws RuntimeIOException {
+ if(this.band != null)
+ this.band.close();
+ this.bundle.close();
+ }
+
+ @Override
+ public void seek(long pos) throws RuntimeIOException {
+ this.pos = pos;
+ }
+
+ @Override
+ public long length() throws RuntimeIOException {
+ return bundle.getSize();
+ }
+
+ @Override
+ public long getFilePointer() throws RuntimeIOException {
+ return pos;
+ }
+
+ @Override
+ public int read(final byte[] data, final int off, final int len)
+ throws RuntimeIOException {
+ if(data == null)
+ throw new IllegalArgumentException("data is null.");
+ if(off < 0 || off > data.length)
+ throw new IllegalArgumentException("pos out of range.");
+ if(len < 0 || len > (data.length - off))
+ throw new IllegalArgumentException("len out of range.");
+
+ final long bundleSize = bundle.getSize();
+ if(pos >= bundleSize)
+ return -1;
+
+ final long bytesRemainingInStream = bundleSize - pos;
+ final int readSize;
+ if(len > bytesRemainingInStream)
+ readSize = (int) bytesRemainingInStream;
+ else
+ readSize = len;
+
+ final long bandSize = bundle.getBandSize();
+ int curOff = off;
+ int remainingSize = readSize;
+ while(remainingSize > 0) {
+ final long curBandNumber = pos / bandSize;
+ final long posInBand = pos % bandSize;
+
+ if(band == null)
+ band = bundle.lookupBand(curBandNumber);
+ else if(curBandNumber != bandNumber) {
+ band.close();
+ band = bundle.lookupBand(curBandNumber);
+ }
+
+ bandNumber = curBandNumber;
+
+ final long remainingInBand = bandSize - posInBand;
+ final int bytesToRead;
+ if(remainingSize > remainingInBand)
+ bytesToRead = (int) remainingInBand;
+ else
+ bytesToRead = remainingSize;
+
+ final int bytesRead;
+ if(band == null) {
+ Arrays.fill(data, curOff, curOff+bytesToRead, (byte) 0);
+ bytesRead = bytesToRead;
+ }
+ else {
+ try {
+ bytesRead = band.read(posInBand, data, curOff, bytesToRead);
+ } catch(IOException ex) {
+ throw new RuntimeIOException("Exception while reading " +
+ "from band " + bandNumber + ".", ex);
+ }
+
+ if(bytesRead < 0) {
+ if(bytesRead != -1)
+ throw new RuntimeException("Unexpected return value " +
+ "from Band.read: " + bytesRead);
+ break;
+ }
+ }
+
+ curOff += bytesRead;
+ pos += bytesRead;
+ remainingSize -= bytesRead;
+
+ if(bytesRead != bytesToRead)
+ break;
+ }
+
+ return readSize - remainingSize;
+ }
+}
diff --git a/src/org/catacombae/dmg/sparsebundle/SparseBundle.java b/src/org/catacombae/dmg/sparsebundle/SparseBundle.java
new file mode 100644
index 0000000..82bdfb7
--- /dev/null
+++ b/src/org/catacombae/dmg/sparsebundle/SparseBundle.java
@@ -0,0 +1,250 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.catacombae.dmg.sparsebundle;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileLock;
+import org.catacombae.io.RuntimeIOException;
+
+/**
+ *
+ * @author Erik Larsson
+ */
+class SparseBundle {
+ private static final String mainInfoFilename = "Info.plist";
+ private static final String backupInfoFilename = "Info.bckup";
+ private static final String tokenFilename = "token";
+ private static final String bandsDirname = "bands";
+
+ private final Info mainInfo;
+ private final Info backupInfo;
+ private final Token token;
+ private final File bandsDir;
+
+ private long size;
+ private long bandSize;
+ private long bandCount;
+
+ public SparseBundle(final File sparseBundleDir) throws RuntimeIOException {
+ File[] files = sparseBundleDir.listFiles();
+ File mainInfoFile = null;
+ File backupInfoFile = null;
+ File tokenFile = null;
+ File bandsDir = null;
+
+ for(File f : files) {
+ if(f.getName().equals(mainInfoFilename))
+ mainInfoFile = f;
+ else if(f.getName().equals(backupInfoFilename))
+ backupInfoFile = f;
+ else if(f.getName().equals(tokenFilename))
+ tokenFile = f;
+ else if(f.getName().equals(bandsDirname))
+ bandsDir = f;
+ else
+ System.err.println("Warning: Encountered unknown file \"" +
+ f.getName() + " in sparse bundle base dir (\"" +
+ sparseBundleDir.getAbsolutePath() + "\").");
+ }
+
+ if(mainInfoFile == null || backupInfoFile == null ||
+ tokenFile == null || bandsDir == null) {
+ throw new RuntimeIOException("Some files are missing from the " +
+ "sparse bundle directory (\"" +
+ sparseBundleDir.getAbsolutePath() + "\".");
+ }
+ else if(!bandsDir.exists() || !bandsDir.isDirectory())
+ throw new RuntimeIOException("Invalid '" + bandsDirname + "' " +
+ "directory.");
+
+ final RandomAccessFile mainInfoRaf;
+ final RandomAccessFile backupInfoRaf;
+ final RandomAccessFile tokenRaf;
+
+ try {
+ mainInfoRaf = new RandomAccessFile(mainInfoFile, "r");
+ } catch(FileNotFoundException ex) {
+ throw new RuntimeIOException("Failed to open '" + mainInfoFilename +
+ "' for reading.", ex);
+ }
+
+ try {
+ backupInfoRaf = new RandomAccessFile(backupInfoFile, "r");
+ } catch(FileNotFoundException ex) {
+ throw new RuntimeIOException("Failed to open '" +
+ backupInfoFilename + "' for reading.", ex);
+ }
+
+ try {
+ tokenRaf = new RandomAccessFile(tokenFile, "r");
+ } catch(FileNotFoundException ex) {
+ throw new RuntimeIOException("Failed to open '" + tokenFilename +
+ "' for reading.", ex);
+ }
+
+ final FileLock mainInfoLock;
+ final FileLock backupInfoLock;
+ final FileLock tokenLock;
+
+ try {
+ mainInfoLock = mainInfoRaf.getChannel().lock(0L, Long.MAX_VALUE,
+ true);
+ } catch (IOException ex) {
+ throw new RuntimeIOException("Failed to aquire a shared lock on " +
+ "'" + mainInfoFilename + "'.", ex);
+ }
+
+ try {
+ backupInfoLock = backupInfoRaf.getChannel().lock(0L, Long.MAX_VALUE,
+ true);
+ } catch (IOException ex) {
+ throw new RuntimeIOException("Failed to aquire a shared lock on " +
+ "'" + backupInfoFilename + "'.", ex);
+ }
+
+ try {
+ tokenLock = tokenRaf.getChannel().lock(0L, Long.MAX_VALUE,
+ true);
+ } catch (IOException ex) {
+ throw new RuntimeIOException("Failed to aquire a shared lock on " +
+ "'" + tokenFilename + "'.", ex);
+ }
+
+ try { this.mainInfo = new Info(mainInfoRaf, mainInfoLock); }
+ catch(IOException ioe) {
+ throw new RuntimeIOException("Exception while parsing '" +
+ mainInfoFilename + "'.", ioe);
+ }
+
+ try { this.backupInfo = new Info(backupInfoRaf, backupInfoLock); }
+ catch(IOException ioe) {
+ throw new RuntimeIOException("Exception while parsing '" +
+ backupInfoFilename + "'.", ioe);
+ }
+
+ this.token = new Token(tokenRaf, tokenLock);
+
+ this.bandsDir = bandsDir;
+
+ /* Check the 'bands' directory
+
+ /* Cached variables. */
+ this.size = mainInfo.getSize();
+ this.bandSize = mainInfo.getBandSize();
+ this.bandCount = (mainInfo.getSize() + mainInfo.getBandSize() - 1) /
+ mainInfo.getBandSize();
+
+ checkBandsDir();
+ }
+
+ private void checkBandsDir() throws RuntimeIOException {
+ for(File f : bandsDir.listFiles()) {
+ if(!f.isFile())
+ throw new RuntimeIOException("Encountered non-file content " +
+ "inside bands directory.");
+
+ final String curName = f.getName();
+ final long bandNumber;
+ try {
+ bandNumber = Long.parseLong(curName, 16);
+ } catch(NumberFormatException nfe) {
+ throw new RuntimeIOException("Encountered non-parseable " +
+ "filename in bands directory: \"" + curName + "\"");
+ }
+
+ //System.err.println("Found band number: " + bandNumber + " " +
+ // "(filename: \"" + curName + "\")");
+
+ if(bandNumber < 0 || bandNumber > bandCount - 1)
+ throw new RuntimeException("Invalid band number: " +
+ bandNumber);
+ }
+ }
+
+ /**
+ * Returns the size of the virtual device.
+ *
+ * @return the size of the virtual device.
+ */
+ public long getSize() {
+ return size;
+ }
+
+ /**
+ * Returns the size of each band in the sparse bundle.
+ *
+ * @return the size of each band in the sparse bundle.
+ */
+ public long getBandSize() {
+ return bandSize;
+ }
+
+ /**
+ * Returns the number of bands that are part of this sparse bundle.
+ *
+ * @return the number of bands that are part of this sparse bundle.
+ */
+ public long getBandCount() {
+ return bandCount;
+ }
+
+ /**
+ * Looks up and returns the {@link Band} with the specified band number.
+ *
+ * The caller is responsible for closing the {@link Band} when it is done
+ * with it.
+ *
+ * @return the {@link Band} with the specified band number.
+ */
+ Band lookupBand(long bandNumber) throws RuntimeIOException {
+ final String bandFilename = Long.toHexString(bandNumber);
+ final File bandFile = new File(bandsDir, bandFilename);
+ if(!bandFile.exists())
+ return null;
+
+ final RandomAccessFile bandRaf;
+ try {
+ bandRaf = new RandomAccessFile(bandFile, "r");
+ } catch(FileNotFoundException ex) {
+ throw new RuntimeIOException("Failed to open '" + bandFilename +
+ "' for reading.", ex);
+ }
+
+ final FileLock bandLock;
+ try {
+ bandLock = bandRaf.getChannel().lock(0L, Long.MAX_VALUE, true);
+ } catch (IOException ex) {
+ throw new RuntimeIOException("Failed to aquire a shared lock on " +
+ "'" + bandFilename + "'.", ex);
+ }
+
+ final long curBandSize;
+ try { curBandSize = bandRaf.length(); }
+ catch(IOException ioe) {
+ throw new RuntimeIOException("Exception while querying band file " +
+ "length.", ioe);
+ }
+
+ if(curBandSize > bandSize)
+ throw new RuntimeIOException("Invalid band: Size (" + curBandSize +
+ ") is larger than bandSize (" + bandSize + ").");
+
+ try { return new Band(bandRaf, bandLock, bandSize); }
+ catch(IOException ioe) {
+ throw new RuntimeIOException("Exception while creating Band " +
+ "instance.", ioe);
+ }
+ }
+
+ void close() {
+ mainInfo.close();
+ backupInfo.close();
+ token.close();
+ }
+}
diff --git a/src/org/catacombae/dmg/sparsebundle/Test.java b/src/org/catacombae/dmg/sparsebundle/Test.java
new file mode 100644
index 0000000..83bf41f
--- /dev/null
+++ b/src/org/catacombae/dmg/sparsebundle/Test.java
@@ -0,0 +1,52 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.catacombae.dmg.sparsebundle;
+
+import java.io.File;
+
+/**
+ *
+ * @author erik
+ */
+public class Test {
+ public static void main(String[] args) {
+ SparseBundle sb = new SparseBundle(new File(args[0]));
+ System.out.println("image size: " + sb.getSize() + " bytes");
+ System.out.println("band size: " + sb.getBandSize() + " bytes");
+ System.out.println("band count: " + sb.getBandCount() + " bands");
+
+ ReadableSparseBundleStream stream = new ReadableSparseBundleStream(sb);
+ byte[] buf = new byte[91673];
+ long bytesRead = 0;
+ final long startTime = System.currentTimeMillis();
+ long lastTime = startTime;
+
+ while(true) {
+ int curBytesRead = stream.read(buf);
+ if(curBytesRead == -1)
+ break;
+ else if(curBytesRead < 0)
+ throw new RuntimeException("Wtf... curBytesRead=" +
+ curBytesRead);
+
+ bytesRead += curBytesRead;
+
+ final long curTime = System.currentTimeMillis();
+ if(curTime - lastTime > 1000) {
+ System.err.println("Transferred " + bytesRead + " bytes in " +
+ (curTime-startTime)/((double) 1000.0) + " seconds.");
+ lastTime = curTime;
+ }
+ }
+
+ System.err.println("Transfer complete.");
+
+ final long curTime = System.currentTimeMillis();
+ System.err.println("Transferred " + bytesRead + " bytes in " +
+ (curTime-startTime)/((double) 1000.0) + " seconds.");
+
+ }
+}
diff --git a/src/org/catacombae/dmg/sparsebundle/Token.java b/src/org/catacombae/dmg/sparsebundle/Token.java
new file mode 100644
index 0000000..4ae1a04
--- /dev/null
+++ b/src/org/catacombae/dmg/sparsebundle/Token.java
@@ -0,0 +1,20 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.catacombae.dmg.sparsebundle;
+
+import java.io.RandomAccessFile;
+import java.nio.channels.FileLock;
+
+/**
+ *
+ * @author erik
+ */
+class Token extends BundleMember {
+
+ public Token(RandomAccessFile tokenFile, FileLock tokenFileLock) {
+ super(tokenFile, tokenFileLock);
+ }
+}
diff --git a/src/org/catacombae/dmg/udif/Debug.java b/src/org/catacombae/dmg/udif/Debug.java
new file mode 100644
index 0000000..822b3ca
--- /dev/null
+++ b/src/org/catacombae/dmg/udif/Debug.java
@@ -0,0 +1,32 @@
+/*-
+ * Copyright (C) 2007 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmg.udif;
+
+public class Debug {
+ public static boolean debug = false;
+
+ public static void warning(String message) {
+ if(debug)
+ System.err.println(message);
+ }
+
+ public static void notification(String message) {
+ if(debug)
+ System.out.println("------->NOTE: " + message);
+ }
+}
diff --git a/src/org/catacombae/dmg/udif/Koly.java b/src/org/catacombae/dmg/udif/Koly.java
new file mode 100644
index 0000000..7de28bf
--- /dev/null
+++ b/src/org/catacombae/dmg/udif/Koly.java
@@ -0,0 +1,166 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmg.udif;
+
+import org.catacombae.dmgextractor.Util;
+import java.io.PrintStream;
+
+/** This class was generated by CStructToJavaClass. */
+public class Koly {
+ private static final int KOLY_FOURCC = 0x6B6F6C79; // ASCII: 'koly'
+ /*
+ * struct Koly
+ * size: 512 bytes
+ * description:
+ *
+ * BP Size Type Identifier Description
+ * --------------------------------------------------------
+ * 0 4 UInt32 fourCC
+ * 4 1*28 byte[28] unknown1
+ * 32 8 UInt64 plistBegin1
+ * 40 8 UInt64 plistEndSometimes
+ * 48 8 UInt64 unknown2
+ * 56 8 UInt64 unknown3
+ * 64 8 UInt64 unknown4
+ * 72 8 UInt64 unknown5
+ * 80 8 UInt64 possibleChecksumType
+ * 88 4 UInt32 unknown6
+ * 92 4 UInt32 possibleUnitSize
+ * 96 1*120 byte[120] unknown7
+ * 216 8 UInt64 plistBegin2
+ * 224 8 UInt64 plistSize
+ * 232 1*120 byte[120] unknown8
+ * 352 4 UInt32 checksumAlgorithm
+ * 356 4 UInt32 checksumSize
+ * 360 1*152 byte[152] checksumData
+ */
+
+ private final byte[] fourCC = new byte[4];
+ private final byte[] unknown1 = new byte[1*28];
+ private final byte[] plistBegin1 = new byte[8];
+ private final byte[] plistEndSometimes = new byte[8];
+ private final byte[] unknown2 = new byte[8];
+ private final byte[] unknown3 = new byte[8];
+ private final byte[] unknown4 = new byte[8];
+ private final byte[] unknown5 = new byte[8];
+ private final byte[] possibleChecksumType = new byte[8];
+ private final byte[] unknown6 = new byte[4];
+ private final byte[] possibleUnitSize = new byte[4];
+ private final byte[] unknown7 = new byte[1*120];
+ private final byte[] plistBegin2 = new byte[8];
+ private final byte[] plistSize = new byte[8];
+ private final byte[] unknown8 = new byte[1*120];
+ private final byte[] checksumAlgorithm = new byte[4];
+ private final byte[] checksumSize = new byte[4];
+ private final byte[] checksumData = new byte[1*152];
+
+ public Koly(byte[] data, int offset) {
+ System.arraycopy(data, offset+0, fourCC, 0, 4);
+ System.arraycopy(data, offset+4, unknown1, 0, 1*28);
+ System.arraycopy(data, offset+32, plistBegin1, 0, 8);
+ System.arraycopy(data, offset+40, plistEndSometimes, 0, 8);
+ System.arraycopy(data, offset+48, unknown2, 0, 8);
+ System.arraycopy(data, offset+56, unknown3, 0, 8);
+ System.arraycopy(data, offset+64, unknown4, 0, 8);
+ System.arraycopy(data, offset+72, unknown5, 0, 8);
+ System.arraycopy(data, offset+80, possibleChecksumType, 0, 8);
+ System.arraycopy(data, offset+88, unknown6, 0, 4);
+ System.arraycopy(data, offset+92, possibleUnitSize, 0, 4);
+ System.arraycopy(data, offset+96, unknown7, 0, 1*120);
+ System.arraycopy(data, offset+216, plistBegin2, 0, 8);
+ System.arraycopy(data, offset+224, plistSize, 0, 8);
+ System.arraycopy(data, offset+232, unknown8, 0, 1*120);
+ System.arraycopy(data, offset+352, checksumAlgorithm, 0, 4);
+ System.arraycopy(data, offset+356, checksumSize, 0, 4);
+ System.arraycopy(data, offset+360, checksumData, 0, 1*152);
+ }
+
+ public static int length() { return 512; }
+
+ public int getFourCC() { return Util.readIntBE(fourCC); }
+ public byte[] getUnknown1() { return Util.createCopy(unknown1); }
+ public long getPlistBegin1() { return Util.readLongBE(plistBegin1); }
+ public long getPlistEndSometimes() { return Util.readLongBE(plistEndSometimes); }
+ public long getUnknown2() { return Util.readLongBE(unknown2); }
+ public long getUnknown3() { return Util.readLongBE(unknown3); }
+ public long getUnknown4() { return Util.readLongBE(unknown4); }
+ public long getUnknown5() { return Util.readLongBE(unknown5); }
+ public long getPossibleChecksumType() { return Util.readLongBE(possibleChecksumType); }
+ public int getUnknown6() { return Util.readIntBE(unknown6); }
+ public int getPossibleUnitSize() { return Util.readIntBE(possibleUnitSize); }
+ public byte[] getUnknown7() { return Util.createCopy(unknown7); }
+ public long getPlistBegin2() { return Util.readLongBE(plistBegin2); }
+ public long getPlistSize() { return Util.readLongBE(plistSize); }
+ public byte[] getUnknown8() { return Util.createCopy(unknown8); }
+ public int getChecksumAlgorithm() { return Util.readIntBE(checksumAlgorithm); }
+ public int getChecksumSize() { return Util.readIntBE(checksumSize); }
+ public byte[] getChecksumData() { return Util.createCopy(checksumData); }
+
+ public boolean isValid() {
+ return getFourCC() == KOLY_FOURCC;
+ }
+
+ public void printFields(PrintStream ps, String prefix) {
+ ps.println(prefix + " fourCC: \"" + Util.toASCIIString(getFourCC()) + "\"");
+ ps.println(prefix + " unknown1: 0x" + Util.byteArrayToHexString(getUnknown1()));
+ ps.println(prefix + " plistBegin1: " + getPlistBegin1());
+ ps.println(prefix + " plistEndSometimes: " + getPlistEndSometimes());
+ ps.println(prefix + " unknown2: " + getUnknown2());
+ ps.println(prefix + " unknown3: " + getUnknown3());
+ ps.println(prefix + " unknown4: " + getUnknown4());
+ ps.println(prefix + " unknown5: " + getUnknown5());
+ ps.println(prefix + " possibleChecksumType: " + getPossibleChecksumType());
+ ps.println(prefix + " unknown6: " + getUnknown6());
+ ps.println(prefix + " possibleUnitSize: " + getPossibleUnitSize());
+ ps.println(prefix + " unknown7: 0x" + Util.byteArrayToHexString(getUnknown7()));
+ ps.println(prefix + " plistBegin2: " + getPlistBegin2());
+ ps.println(prefix + " plistSize: " + getPlistSize());
+ ps.println(prefix + " unknown8: 0x" + Util.byteArrayToHexString(getUnknown8()));
+ ps.println(prefix + " checksumAlgorithm: " + getChecksumAlgorithm());
+ int checksumSize = getChecksumSize()/8;
+ byte[] checksumData = getChecksumData();
+ ps.println(prefix + " checksumSize: " + checksumSize);
+ ps.println(prefix + " checksumData: 0x" + Util.byteArrayToHexString(checksumData, 0, checksumSize)); // checksumSize is in bits
+ int i;
+ for(i = 0; i+4 <= (checksumData.length-checksumSize); i += 4)
+ ps.println(prefix + " trailing data[" + i + "]: 0x " + Util.byteArrayToHexString(checksumData, checksumSize+i, 4));
+ int bytesLeft = i+4 - (checksumData.length-checksumSize);
+ if(bytesLeft > 0 && bytesLeft < 4) {
+ System.err.println("bytes left: " + bytesLeft);
+ ps.println(prefix + " trailing data[" + i + "]: 0x " + Util.byteArrayToHexString(checksumData, checksumSize+i, (i+4)-(checksumData.length-checksumSize)));
+ }
+ }
+
+ public void print(PrintStream ps, String prefix) {
+ ps.println(prefix + "Koly:");
+ printFields(ps, prefix);
+ }
+
+ /** Test main. Reads the last 512 bytes from the input file (args[0]), creates a
+ Koly object and calls its print method to display the data. */
+ public static void main(String[] args) throws java.io.IOException {
+ byte[] kolyData = new byte[512];
+ java.io.RandomAccessFile raf = new java.io.RandomAccessFile(args[0], "r");
+ raf.seek(raf.length()-512);
+ if(raf.read(kolyData) != kolyData.length)
+ throw new RuntimeException("Could not read entire koly...");
+ raf.close();
+ Koly k = new Koly(kolyData, 0);
+ k.print(System.out, "");
+ }
+}
diff --git a/src/org/catacombae/dmg/udif/Koly.struct b/src/org/catacombae/dmg/udif/Koly.struct
new file mode 100644
index 0000000..0c2c44e
--- /dev/null
+++ b/src/org/catacombae/dmg/udif/Koly.struct
@@ -0,0 +1,20 @@
+struct Koly {
+ UInt32 fourCC;
+ byte unknown1[28];
+ UInt64 plistBegin1;
+ UInt64 plistEndSometimes;
+ UInt64 unknown2;
+ UInt64 unknown3;
+ UInt64 unknown4;
+ UInt64 unknown5;
+ UInt64 possibleChecksumType;
+ UInt32 unknown6;
+ UInt32 possibleUnitSize;
+ byte unknown7[120];
+ UInt64 plistBegin2;
+ UInt64 plistSize;
+ byte unknown8[120];
+ UInt32 checksumAlgorithm;
+ UInt32 checksumSize;
+ byte checksumData[152];
+};
diff --git a/src/org/catacombae/dmg/udif/Plist.java b/src/org/catacombae/dmg/udif/Plist.java
new file mode 100644
index 0000000..a4573a7
--- /dev/null
+++ b/src/org/catacombae/dmg/udif/Plist.java
@@ -0,0 +1,114 @@
+/*-
+ * Copyright (C) 2006-2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmg.udif;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.util.LinkedList;
+import net.iharder.base64.Base64;
+import org.catacombae.dmgextractor.Util;
+import org.catacombae.dmgextractor.io.*;
+import org.catacombae.plist.PlistNode;
+import org.catacombae.plist.XmlPlist;
+
+public class Plist extends XmlPlist {
+
+ public Plist(byte[] data) {
+ this(data, 0, data.length);
+ }
+ public Plist(byte[] data, boolean useSAXParser) {
+ this(data, 0, data.length, useSAXParser);
+ }
+ public Plist(byte[] data, int offset, int length) {
+ this(data, offset, length, false);
+ }
+ public Plist(byte[] data, int offset, int length, boolean useSAXParser) {
+ super(data, offset, length, useSAXParser);
+ }
+
+ //public byte[] getData() { return Util.createCopy(plistData); }
+
+ public PlistPartition[] getPartitions() throws IOException {
+ LinkedList partitionList = new LinkedList();
+ PlistNode current = getRootNode();
+ current = current.cd("dict");
+ current = current.cdkey("resource-fork");
+ current = current.cdkey("blkx");
+
+ // Variables to keep track of the pointers of the previous partition
+ long previousOutOffset = 0;
+ long previousInOffset = 0;
+
+ // Iterate over the partitions and gather data
+ for(PlistNode pn : current.getChildren()) {
+ String partitionName = Util.readFully(pn.getKeyValue("Name"));
+ String partitionID = Util.readFully(pn.getKeyValue("ID"));
+ String partitionAttributes = Util.readFully(pn.getKeyValue("Attributes"));
+ //System.err.println("Retrieving data...");
+ //(new BufferedReader(new InputStreamReader(System.in))).readLine();
+ Reader base64Data = pn.getKeyValue("Data");
+ //System.gc();
+ //System.err.println("Converting data to binary form... free memory: " + Runtime.getRuntime().freeMemory() + " total memory: " + Runtime.getRuntime().totalMemory());
+ //byte[] data = Base64.decode(base64Data);
+
+// try {
+// InputStream yo = new Base64.InputStream(new ReaderInputStream(base64Data, Charset.forName("US-ASCII")));
+// String filename1 = "dump_plist_java-" + System.currentTimeMillis() + ".datadpp";
+// System.err.println("Dumping output from ReaderInputStream to file \"" + filename1 + "\"");
+// FileOutputStream fos = new FileOutputStream(filename1);
+// if(false) { // Standard way
+// byte[] buffer = new byte[4096];
+// int curBytesRead = yo.read(buffer);
+// while(curBytesRead == buffer.length) {
+// fos.write(buffer, 0, curBytesRead);
+// curBytesRead = yo.read(buffer);
+// }
+// if(curBytesRead > 0)
+// fos.write(buffer, 0, curBytesRead);
+// }
+// else { // Simulating PlistPartition constructor
+// byte[] buf1 = new byte[0xCC];
+// byte[] buf2 = new byte[0x28];
+// int curBytesRead = (int)yo.skip(0xCC); // SKIP OPERATION FUCKS UP!one
+// fos.write(buf1, 0, curBytesRead);
+// curBytesRead = yo.read(buf2);
+// while(curBytesRead == buf2.length) {
+// fos.write(buf2, 0, curBytesRead);
+// curBytesRead = yo.read(buf2);
+// }
+// if(curBytesRead > 0)
+// fos.write(buf2, 0, curBytesRead);
+
+// }
+// fos.close();
+// } catch(Exception e) { e.printStackTrace(); }
+
+ InputStream base64DataInputStream = new Base64.InputStream(new ReaderInputStream(base64Data, Charset.forName("US-ASCII")));
+
+ //System.err.println("Creating PlistPartition.");
+ //System.out.println("Block list for partition " + i++ + ":");
+ PlistPartition dpp = new PlistPartition(partitionName, partitionID, partitionAttributes,
+ base64DataInputStream, previousOutOffset, previousInOffset);
+ previousOutOffset = dpp.getFinalOutOffset();
+ previousInOffset = dpp.getFinalInOffset();
+ partitionList.addLast(dpp);
+ }
+
+ return partitionList.toArray(new PlistPartition[partitionList.size()]);
+ }
+}
diff --git a/src/org/catacombae/dmg/udif/PlistPartition.java b/src/org/catacombae/dmg/udif/PlistPartition.java
new file mode 100644
index 0000000..04850d5
--- /dev/null
+++ b/src/org/catacombae/dmg/udif/PlistPartition.java
@@ -0,0 +1,213 @@
+/*-
+ * Copyright (C) 2007 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmg.udif;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+public class PlistPartition {
+ private String name;
+ private String id;
+ private String attributes;
+ private UDIFBlock[] blockList;
+ private long partitionSize;
+
+ // Incoming variables
+ private final long previousOutOffset;
+ private final long previousInOffset;
+
+ // Outgoing variables
+ private long finalOutOffset = -1;
+ private long finalInOffset = -1;
+
+ public PlistPartition(String name, String id, String attributes, byte[] data,
+ long previousOutOffset, long previousInOffset) throws IOException {
+ this(name, id, attributes, new ByteArrayInputStream(data),
+ previousOutOffset, previousInOffset);
+ }
+
+ public PlistPartition(String name, String id, String attributes, InputStream data,
+ long previousOutOffset, long previousInOffset) throws IOException {
+ this.name = name;
+ this.id = id;
+ this.attributes = attributes;
+ this.previousOutOffset = previousOutOffset;
+ this.previousInOffset = previousInOffset;
+
+ this.blockList = parseBlocks(data);
+ this.partitionSize = calculatePartitionSize(blockList);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getID() {
+ return id;
+ }
+
+ public String getAttributes() {
+ return attributes;
+ }
+
+ public long getPartitionSize() {
+ return partitionSize;
+ }
+
+ /** Copies all blocks to a newly allocated array. Might waste some memory. */
+ public UDIFBlock[] getBlocks() {
+ UDIFBlock[] res = new UDIFBlock[blockList.length];
+ for(int i = 0; i < res.length; ++i)
+ res[i] = blockList[i];
+ return res;
+ }
+
+ /** Returns an iterator over all the UDIFBlocks that describe the contents of this partition. */
+ public Iterator getBlockIterator() {
+ return new BlockIterator(blockList);
+ }
+
+ public int getBlockCount() {
+ return blockList.length;
+ }
+
+ public long getFinalOutOffset() {
+ if(finalOutOffset < 0)
+ throw new RuntimeException("parseBlocks has not yet been called!");
+ return finalOutOffset;
+ }
+
+ public long getFinalInOffset() {
+ if(finalInOffset < 0)
+ throw new RuntimeException("parseBlocks has not yet been called!");
+ return finalInOffset;
+ }
+
+ private UDIFBlock[] parseBlocks(InputStream is) throws IOException {
+ long bytesSkipped = is.read(new byte[0xCC]);
+
+ if(bytesSkipped != 0xCC)
+ throw new RuntimeException("Could not skip the desired amount of bytes...");
+
+ int blockNumber = 0; // Increments by one for each block we read (each iteration in the while loop below)
+
+ /* These two variables are part of the "hack" described below. */
+ long lastByteReadInBlock = -1;
+ boolean addInOffset = false;
+
+ byte[] blockData = new byte[UDIFBlock.structSize()];
+
+ LinkedList blocks = new LinkedList();
+
+ int bytesRead = is.read(blockData);
+ while(bytesRead > 0) { //offset <= data.length-UDIFBlock) {
+ //System.err.println("Looping (read " + bytesRead + " bytes)");
+ if(bytesRead != blockData.length)
+ throw new RuntimeException("Could not read the desired amount of bytes... (desired: " + blockData.length + " read: " + bytesRead + ")");
+
+ long inOffset = UDIFBlock.peekInOffset(blockData, 0);
+ long inSize = UDIFBlock.peekInSize(blockData, 0);
+
+ // Set compensation to the end of the output data of the previous partition to get true offset in outfile.
+ long outOffsetCompensation = previousOutOffset;
+
+ // Update pointer to the last byte read in the last block
+ if(lastByteReadInBlock == -1)
+ lastByteReadInBlock = inOffset;
+ lastByteReadInBlock += inSize;
+
+ /*
+ * The lines below are a "hack" that I had to do to make dmgx work with
+ * certain dmg-files. I don't understand the issue at all, which is why
+ * this hack is here, but sometimes inOffset == 0 means that it is 0
+ * relative to the previous partition's last inOffset. And sometimes it
+ * doesn't (meaning the actual position 0 in the dmg file).
+ */
+ if(inOffset == 0 && blockNumber == 0) {
+ Debug.notification("Detected inOffset == 0, setting addInOffset flag.");
+ addInOffset = true;
+ }
+ long inOffsetCompensation = 0;
+ if(addInOffset) {
+ Debug.notification("addInOffset mode: inOffset tranformation " + inOffset + "->" +
+ (inOffset + previousInOffset));
+ inOffsetCompensation = previousInOffset;
+ }
+
+ UDIFBlock currentBlock = new UDIFBlock(blockData, 0, outOffsetCompensation, inOffsetCompensation);
+ blocks.add(currentBlock);
+ ++blockNumber;
+
+ //System.out.println(" " + currentBlock.toString());
+
+ // Return if we have reached the end, and update
+ if(currentBlock.getBlockType() == UDIFBlock.BT_END) {
+ finalOutOffset = currentBlock.getTrueOutOffset();
+ finalInOffset = previousInOffset + lastByteReadInBlock;
+
+ if(is.read() != -1)
+ Debug.warning("Encountered additional data in blkx blob.");
+ return blocks.toArray(new UDIFBlock[blocks.size()]);
+ }
+
+ bytesRead = is.read(blockData);
+ }
+
+ throw new RuntimeException("No BT_END block found!");
+ }
+
+ public static long calculatePartitionSize(UDIFBlock[] data) throws IOException {
+ long partitionSize = 0;
+
+ for(UDIFBlock db : data)
+ partitionSize += db.getOutSize();
+
+ return partitionSize;
+ }
+
+ private class BlockIterator implements Iterator {
+
+ private UDIFBlock[] blocks;
+ private int pointer, endOffset;
+
+ public BlockIterator(UDIFBlock[] blocks) {
+ this(blocks, 0, blocks.length);
+ }
+
+ public BlockIterator(UDIFBlock[] blocks, int offset, int length) {
+ this.blocks = blocks;
+ this.pointer = offset;
+ this.endOffset = offset + length;
+ }
+
+ public boolean hasNext() {
+ return pointer < endOffset;
+ }
+
+ public UDIFBlock next() {
+ return blocks[pointer++];
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/src/org/catacombae/dmg/udif/UDIFBlock.java b/src/org/catacombae/dmg/udif/UDIFBlock.java
new file mode 100644
index 0000000..06e707d
--- /dev/null
+++ b/src/org/catacombae/dmg/udif/UDIFBlock.java
@@ -0,0 +1,208 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmg.udif;
+
+import org.catacombae.dmgextractor.Util;
+
+public class UDIFBlock implements Comparable{
+ /** This blocktype means the data is compressed using some "ADC" algorithm that I have no idea how to decompress... */
+ public static final int BT_ADC = 0x80000004;
+
+ /** This blocktype means the data is compressed with zlib. */
+ public static final int BT_ZLIB = 0x80000005;
+
+ /** This blocktype means the data is compressed with the bzip2 compression algorithm. These blocktypes are unsupported,
+ as I haven't found a GPL-compatible bzip2 decompressor written in Java yet. */
+ public static final int BT_BZIP2 = 0x80000006;
+
+ /** This blocktype means the data is uncompressed and can simply be copied. */
+ public static final int BT_COPY = 0x00000001;
+
+ /** This blocktype represents a fill of zeroes. */
+ public static final int BT_ZERO = 0x00000002;
+
+ /** This blocktype represents a fill of zeroes (the difference between this blocktype and BT_ZERO is not documented,
+ and parsing of these blocks is experimental). */
+ public static final int BT_ZERO2 = 0x00000000;
+
+ /** This blocktype indicates the end of the partition. */
+ public static final int BT_END = 0xffffffff;
+
+ /** This blocktype has been observed, but its purpose is currently unknown. In all the observed cases the outSize was
+ equal to 0, so it's probably some marker, like BT_END. */
+ public static final int BT_UNKNOWN = 0x7ffffffe;
+
+
+ private static final String BT_ADC_STRING = "BT_ADC";
+ private static final String BT_ZLIB_STRING = "BT_ZLIB";
+ private static final String BT_BZIP2_STRING = "BT_BZIP2";
+ private static final String BT_COPY_STRING = "BT_COPY";
+ private static final String BT_ZERO_STRING = "BT_ZERO";
+ private static final String BT_ZERO2_STRING = "BT_ZERO2";
+ private static final String BT_END_STRING = "BT_END";
+ private static final String BT_UNKNOWN_STRING = "BT_UNKNOWN";
+
+ /*
+ * BP Name Size
+ * -------------------
+ * 0 blockType 4
+ * 4 skipped 4
+ * 8 outOffset 8
+ * 16 outSize 8
+ * 24 inOffset 8
+ * 32 inSize 8
+ * -------------------
+ * 40 bytes / 0x28 bytes
+ */
+ private final int blockType;
+ private final int reserved;
+ private final long outOffset;
+ private final long outSize;
+ private final long inOffset;
+ private final long inSize;
+ private final long outOffsetComp;
+ private final long inOffsetComp;
+
+ //private boolean immutable = false;
+
+ public UDIFBlock(byte[] data, int offset, long outOffsetComp, long inOffsetComp) {
+ this(Util.readIntBE(data, offset + 0),
+ Util.readIntBE(data, offset + 4),
+ Util.readLongBE(data, offset + 8) * 0x200,
+ Util.readLongBE(data, offset + 16) * 0x200,
+ Util.readLongBE(data, offset + 24),
+ Util.readLongBE(data, offset + 32),
+ outOffsetComp,
+ inOffsetComp);
+ }
+
+ public UDIFBlock(int blockType, int reserved, long outOffset, long outSize, long inOffset, long inSize,
+ long outOffsetComp, long inOffsetComp) {
+ this.blockType = blockType;
+ this.reserved = reserved;
+ this.outOffset = outOffset;
+ this.outSize = outSize;
+ this.inOffset = inOffset;
+ this.inSize = inSize;
+ this.outOffsetComp = outOffsetComp;
+ this.inOffsetComp = inOffsetComp;
+ }
+
+ public static int structSize() { return 40; }
+
+ public int getBlockType() { return blockType; }
+ public int getReserved() { return reserved; }
+ public long getOutOffset() { return outOffset; }
+ public long getOutSize() { return outSize; }
+ public long getInOffset() { return inOffset; }
+ public long getInSize() { return inSize; }
+
+ public String getBlockTypeAsString() {
+ switch(blockType) {
+ case BT_ADC:
+ return BT_ADC_STRING;
+ case BT_ZLIB:
+ return BT_ZLIB_STRING;
+ case BT_BZIP2:
+ return BT_BZIP2_STRING;
+ case BT_COPY:
+ return BT_COPY_STRING;
+ case BT_ZERO:
+ return BT_ZERO_STRING;
+ case BT_ZERO2:
+ return BT_ZERO2_STRING;
+ case BT_END:
+ return BT_END_STRING;
+ case BT_UNKNOWN:
+ return BT_UNKNOWN_STRING;
+ default:
+ return "[Unknown block type! ID=0x" + Integer.toHexString(blockType) + "]";
+ }
+ }
+
+ /**
+ * This field is not part of the structure itself. It is metadata used to
+ * determine the actual byte position of the out offset.
+ */
+ public long getOutOffsetCompensation() { return outOffsetComp; }
+
+ /**
+ * This field is not part of the structure itself. It is metadata used to
+ * determine the actual byte position of the in offset.
+ */
+ public long getInOffsetCompensation() { return inOffsetComp; }
+
+ //public void setOutOffsetCompensation(long offset) {
+ // if(immutable)
+ // throw new RuntimeException("This block has been toggled immutable!");
+ // outOffsetComp = offset;
+ //}
+ //public void setInOffsetCompensation(long offset) {
+ // if(immutable)
+ // throw new RuntimeException("This block has been toggled immutable!");
+ // inOffsetComp = offset;
+ //}
+
+ /** Convenience method for determining the actual compensated out offset. This is what you should use. */
+ public long getTrueOutOffset() {
+ return outOffset + outOffsetComp;
+ }
+
+ /** Convenience method for determining the actual compensated in offset. This is what you should use. */
+ public long getTrueInOffset() {
+ return inOffset + inOffsetComp;
+ }
+
+ //public void markImmutable() {
+ // immutable = true;
+ //}
+
+ @Override
+ public String toString() {
+ return getBlockTypeAsString() +
+ "(reserved=0x" + Integer.toHexString(reserved) + ",outOffset=" + outOffset +
+ ",outSize=" + outSize + ",inOffset=" + inOffset + ",inSize=" + inSize + ",outOffsetComp=" + outOffsetComp + ",inOffsetComp=" + inOffsetComp + ")";
+ }
+
+ /**
+ * Reads the inOffset field from data at offset
+ * which is supposed to be a valid raw UDIF block structure at 40 bytes.
+ */
+ public static long peekInOffset(byte[] data, int offset) {
+ return Util.readLongBE(data, offset + 24);
+ }
+
+ /**
+ * Reads the inSize field from data at offset
+ * which is supposed to be a valid raw UDIF block structure at 40 bytes.
+ */
+ public static long peekInSize(byte[] data, int offset) {
+ return Util.readLongBE(data, offset + 32);
+ }
+
+ /** Orders blocks according to the "true" InOffset. */
+ public int compareTo(UDIFBlock db) {
+ long res = getTrueInOffset() - db.getTrueInOffset();
+ if(res > Integer.MAX_VALUE)
+ return Integer.MAX_VALUE;
+ else if(res < Integer.MIN_VALUE)
+ return Integer.MIN_VALUE;
+ else
+ return (int) res;
+ }
+}
diff --git a/src/org/catacombae/dmg/udif/UDIFBlockInputStream.java b/src/org/catacombae/dmg/udif/UDIFBlockInputStream.java
new file mode 100644
index 0000000..6afa5b7
--- /dev/null
+++ b/src/org/catacombae/dmg/udif/UDIFBlockInputStream.java
@@ -0,0 +1,460 @@
+/*-
+ * Copyright (C) 2006-2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmg.udif;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+import org.apache.tools.bzip2.CBZip2InputStream;
+import org.catacombae.dmgextractor.Util;
+import org.catacombae.dmgextractor.DmgException;
+import org.catacombae.dmgextractor.io.RandomAccessInputStream;
+import org.catacombae.dmgextractor.io.SynchronizedRandomAccessStream;
+import org.catacombae.io.ReadableRandomAccessStream;
+import org.catacombae.io.RuntimeIOException;
+
+public abstract class UDIFBlockInputStream extends InputStream {
+ protected ReadableRandomAccessStream raf;
+ protected UDIFBlock block;
+ protected final int addInOffset;
+ private long globalBytesRead;
+ // 16 KiB buffer... is it reasonable?
+ protected final byte[] buffer = new byte[16384];
+ protected int bufferPos = 0;
+ // Initializing this to zero will make read call fillBuffer at first call
+ protected int bufferDataLength = 0;
+ private final byte[] skipBuffer = new byte[4096];
+
+ /**
+ * Subclasses use this variable to report how many bytes were read into the
+ * buffer.
+ */
+ protected int fillSize;
+
+ /**
+ * Creates a new UDIFBlockInputStream.
+ *
+ * @param raf the RandomAccessFile representing the UDIF file
+ * @param block the block that we should read (usually obtained via
+ * {@link PlistPartition#getBlocks()})
+ * @param addInOffset the number to add to the block's inOffset to find the
+ * data.
+ */
+ protected UDIFBlockInputStream(ReadableRandomAccessStream raf,
+ UDIFBlock block, int addInOffset) {
+
+ this.raf = raf;
+ this.block = block;
+ this.addInOffset = addInOffset;
+ //fillBuffer();
+ //bufferPos = buffer.length;
+ }
+
+ /**
+ * This method WILL throw a RuntimeException if block has a
+ * type that there is no handler for.
+ */
+ public static UDIFBlockInputStream getStream(ReadableRandomAccessStream raf,
+ UDIFBlock block) throws IOException, RuntimeIOException {
+
+ switch(block.getBlockType()) {
+ case UDIFBlock.BT_ZLIB:
+ return new ZlibBlockInputStream(raf, block, 0);
+ case UDIFBlock.BT_BZIP2:
+ return new Bzip2BlockInputStream(raf, block, 0);
+ case UDIFBlock.BT_COPY:
+ return new CopyBlockInputStream(raf, block, 0);
+ case UDIFBlock.BT_ZERO:
+ case UDIFBlock.BT_ZERO2:
+ return new ZeroBlockInputStream(raf, block, 0);
+ case UDIFBlock.BT_END:
+ case UDIFBlock.BT_UNKNOWN:
+ throw new RuntimeException("Block type is a marker and " +
+ "contains no data.");
+ case UDIFBlock.BT_ADC:
+ default:
+ throw new RuntimeException("No handler for block type " +
+ block.getBlockTypeAsString());
+ }
+ }
+
+ /**
+ * In case the available amount of bytes is larger than Integer.MAX_INT,
+ * Integer.MAX_INT is returned.
+ */
+ @Override
+ public int available() throws IOException {
+ long available = block.getOutSize() - globalBytesRead;
+ if(available > Integer.MAX_VALUE)
+ return Integer.MAX_VALUE;
+ else
+ return (int)available;
+ }
+
+ /**
+ * This method does NOT close the underlying RandomAccessFile. It can be
+ * reused afterwards.
+ */
+ @Override
+ public void close() throws IOException {}
+
+ /** Not supported. */
+ @Override
+ public void mark(int readlimit) {}
+
+ /** Returns false, because it isn't supported. */
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ /** @see java.io.InputStream */
+ @Override
+ public int read() throws IOException {
+ byte[] b = new byte[1];
+ return read(b, 0, 1);
+ }
+
+ /** @see java.io.InputStream */
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ /** @see java.io.InputStream */
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+// System.out.println("UDIFBlockInputStream.read(b, " + off + ", " + len + ") {");
+
+ final int bytesToRead = len;
+
+ int bytesRead = 0;
+ int outPos = off;
+ while(bytesRead < bytesToRead) {
+ int bytesRemainingInBuffer = bufferDataLength - bufferPos;
+ if(bytesRemainingInBuffer == 0) {
+// System.out.println(" first call to fillBuffer");
+ fillBuffer();
+// System.out.println(" bufferDataLength=" + bufferDataLength + ",bufferPos=" + bufferPos);
+ bytesRemainingInBuffer = bufferDataLength - bufferPos;
+ if(bytesRemainingInBuffer == 0) { // We apparently have no more data.
+ if(bytesRead == 0) {
+ //System.out.println("return: -1 }");
+ return -1;
+ }
+ else
+ break;
+ }
+ }
+// System.out.println(" bytesRemainingInBuffer=" +
+// bytesRemainingInBuffer + ",bufferPos=" + bufferPos +
+// ",bufferDataLength=" + bufferDataLength);
+ int bytesToReadFromBuffer =
+ Math.min(bytesToRead - bytesRead, bytesRemainingInBuffer);
+// System.out.println(" bytesToReadFromBuffer=" +
+// bytesToReadFromBuffer);
+// System.out.println(" System.arraycopy(buffer, " + bufferPos +
+// ", b, " + outPos + ", " + bytesToReadFromBuffer + ");");
+ System.arraycopy(buffer, bufferPos, b, outPos, bytesToReadFromBuffer);
+
+ outPos += bytesToReadFromBuffer;
+ bufferPos += bytesToReadFromBuffer;
+
+ bytesRead += bytesToReadFromBuffer;
+ }
+
+ globalBytesRead += bytesRead;
+
+// System.out.println("return: " + bytesRead + " }");
+ return bytesRead;
+ }
+
+ /** Does nothing. Not supported. */
+ @Override
+ public void reset() throws IOException {}
+
+ /**
+ * Skips as many bytes as possible. If end of file is reached, the number
+ * of bytes skipped is returned.
+ */
+ @Override
+ public long skip(long n) throws IOException {
+ long bytesSkipped = 0;
+ while(bytesSkipped < n) {
+ int curSkip = (int) Math.min(n - bytesSkipped, skipBuffer.length);
+ int res = read(skipBuffer, 0, curSkip);
+ if(res > 0)
+ bytesSkipped += res;
+ else
+ break;
+ }
+ return bytesSkipped;
+ }
+
+ protected abstract void fillBuffer() throws IOException;
+
+ public static class ZlibBlockInputStream extends UDIFBlockInputStream {
+
+ private final Inflater inflater;
+ private final byte[] inBuffer;
+ private long inPos;
+
+ public ZlibBlockInputStream(ReadableRandomAccessStream raf,
+ UDIFBlock block, int addInOffset) throws IOException {
+ super(raf, block, addInOffset);
+ inflater = new Inflater(true);
+ inBuffer = new byte[4096];
+ inPos = 0;
+ feedInflater();
+ }
+
+ private void feedInflater() throws IOException {
+ //System.err.println("ZlibBlockInputStream.feedInflater() {");
+ long seekPos = addInOffset + inPos + block.getTrueInOffset();
+ //System.out.println(" seeking to " + seekPos + " (file length: " +
+ // raf.length() + ")");
+ raf.seek(seekPos);
+ long bytesLeftToRead = block.getInSize() - inPos;
+ int bytesToFeed = (int) Math.min(inBuffer.length, bytesLeftToRead);
+ //System.out.println(" bytesToFeed=" + bytesToFeed);
+
+ byte[] buf;
+ int bytesToWrite = bytesToFeed;
+ if(bytesLeftToRead <= inBuffer.length) {
+ buf = new byte[inBuffer.length + 1];
+ bytesToWrite++;
+ }
+ else
+ buf = inBuffer;
+
+ int curBytesRead = raf.read(buf, 0, bytesToFeed);
+ inPos += curBytesRead;
+ inflater.setInput(inBuffer, 0, curBytesRead);
+ //System.out.println(" curBytesRead=" + curBytesRead);
+ //System.out.println("}");
+ }
+
+ protected void fillBuffer() throws RuntimeIOException, IOException {
+ //System.err.println("ZlibBlockInputStream.fillBuffer() {");
+ //if(inflater == null)
+ // System.err.println("INFLATER IS NULL");
+ //if(inBuffer == null)
+ // System.err.println("INBUFFER IS NULL");
+ if(inflater.finished()) {
+ //System.out.println("inflater claims to be finished...");
+ bufferPos = 0;
+ bufferDataLength = 0;
+ }
+ try {
+ int bytesInflated = 0;
+ while(bytesInflated < buffer.length && !inflater.finished()) {
+ if(inflater.needsInput())
+ feedInflater();
+ int res = inflater.inflate(buffer, bytesInflated,
+ buffer.length - bytesInflated);
+ if(res >= 0)
+ bytesInflated += res;
+ else
+ throw new DmgException("Negative return value when " +
+ "inflating");
+ }
+
+ // The fillBuffer method is responsible for updating bufferPos
+ // and bufferDataLength
+ bufferPos = 0;
+ bufferDataLength = bytesInflated;
+ } catch(DataFormatException e) {
+ DmgException re = new DmgException("Invalid zlib data!");
+ re.initCause(e);
+ throw re;
+ }
+ //System.out.println("}");
+ }
+ }
+
+ public static class CopyBlockInputStream extends UDIFBlockInputStream {
+
+ private long inPos = 0;
+
+ public CopyBlockInputStream(ReadableRandomAccessStream raf,
+ UDIFBlock block, int addInOffset) throws RuntimeIOException {
+ super(raf, block, addInOffset);
+ }
+
+ protected void fillBuffer() throws IOException {
+ raf.seek(addInOffset + inPos + block.getTrueInOffset());
+
+ final int bytesToRead = (int) Math.min(block.getInSize() - inPos,
+ buffer.length);
+ int totalBytesRead = 0;
+ while(totalBytesRead < bytesToRead) {
+ int bytesRead = raf.read(buffer, totalBytesRead,
+ bytesToRead - totalBytesRead);
+ if(bytesRead < 0)
+ break;
+ else {
+ totalBytesRead += bytesRead;
+ inPos += bytesRead;
+ }
+ }
+
+ // The fillBuffer method is responsible for updating bufferPos and
+ // bufferDataLength
+ bufferPos = 0;
+ bufferDataLength = totalBytesRead;
+ }
+
+ /** Extremely more efficient skip method! */
+ @Override
+ public long skip(long n) throws IOException {
+ final int bytesToSkip =
+ (int) Math.min(block.getInSize() - inPos, n);
+ inPos += bytesToSkip;
+
+ // make read() refill buffer at next call..
+ bufferPos = 0;
+ bufferDataLength = 0;
+
+ return bytesToSkip;
+ }
+ }
+
+ public static class ZeroBlockInputStream extends UDIFBlockInputStream {
+
+ private long outPos = 0;
+
+ public ZeroBlockInputStream(ReadableRandomAccessStream raf,
+ UDIFBlock block, int addInOffset) throws RuntimeIOException {
+ super(raf, block, addInOffset);
+ }
+
+ protected void fillBuffer() throws IOException {
+ final int bytesToWrite =
+ (int) Math.min(block.getOutSize() - outPos, buffer.length);
+ Util.zero(buffer, 0, bytesToWrite);
+ outPos += bytesToWrite;
+
+ // The fillBuffer method is responsible for updating bufferPos and
+ // bufferDataLength
+ bufferPos = 0;
+ bufferDataLength = bytesToWrite;
+ }
+
+ /** Extremely more efficient skip method! */
+ @Override
+ public long skip(long n) throws IOException {
+ final int bytesToSkip =
+ (int) Math.min(block.getOutSize() - outPos, n);
+ outPos += bytesToSkip;
+
+ // make read() refill buffer at next call..
+ bufferPos = 0;
+ bufferDataLength = 0;
+
+ return bytesToSkip;
+ }
+ }
+
+ public static class Bzip2BlockInputStream extends UDIFBlockInputStream {
+
+ private final byte[] BZIP2_SIGNATURE = { 0x42, 0x5A }; // 'BZ'
+ private InputStream bzip2DataStream;
+ private CBZip2InputStream decompressingStream;
+ private long outPos = 0;
+
+ public Bzip2BlockInputStream(ReadableRandomAccessStream raf,
+ UDIFBlock block, int addInOffset)
+ throws IOException, RuntimeIOException {
+
+ super(raf, block, addInOffset);
+
+ if(false) {
+ byte[] inBuffer = new byte[4096];
+ String basename = System.nanoTime() + "";
+ File outFile = new File(basename + "_bz2.bin");
+ int i = 1;
+ while(outFile.exists())
+ outFile = new File(basename + "_" + i++ + "_bz2.bin");
+ System.err.println("Creating a new Bzip2BlockInputStream. " +
+ "Dumping bzip2 block data to file \"" + outFile + "\"");
+ FileOutputStream outStream = new FileOutputStream(outFile);
+ raf.seek(block.getTrueInOffset());
+ long bytesWritten = 0;
+ long bytesToWrite = block.getInSize();
+ while(bytesWritten < bytesToWrite) {
+ int curBytesRead = raf.read(inBuffer, 0,
+ (int) Math.min(bytesToWrite - bytesWritten,
+ inBuffer.length));
+ if(curBytesRead <= 0)
+ throw new RuntimeException("Unable to read bzip2 " +
+ "block fully.");
+ outStream.write(inBuffer, 0, curBytesRead);
+ bytesWritten += curBytesRead;
+ }
+ outStream.close();
+ }
+
+ bzip2DataStream = new RandomAccessInputStream(
+ new SynchronizedRandomAccessStream(raf),
+ block.getTrueInOffset(), block.getInSize());
+
+ byte[] signature = new byte[2];
+ if(bzip2DataStream.read(signature) != signature.length)
+ throw new RuntimeException("Read error!");
+ if(!Util.arraysEqual(signature, BZIP2_SIGNATURE))
+ throw new RuntimeException("Invalid bzip2 block!");
+
+ /* Buffering needed because of implementation issues in
+ * CBZip2InputStream. */
+ decompressingStream = new CBZip2InputStream(
+ new BufferedInputStream(bzip2DataStream));
+ }
+
+ protected void fillBuffer() throws IOException {
+
+ final int bytesToRead = (int) Math.min(block.getOutSize() - outPos,
+ buffer.length);
+ int totalBytesRead = 0;
+ while(totalBytesRead < bytesToRead) {
+ int bytesRead = decompressingStream.read(buffer, totalBytesRead,
+ bytesToRead - totalBytesRead);
+ if(bytesRead < 0)
+ break;
+ else {
+ totalBytesRead += bytesRead;
+ outPos += bytesRead;
+ }
+ }
+
+ // The fillBuffer method is responsible for updating bufferPos and
+ // bufferDataLength
+ bufferPos = 0;
+ bufferDataLength = totalBytesRead;
+ }
+
+ @Override
+ public void close() throws IOException {
+ decompressingStream.close();
+ bzip2DataStream.close();
+ }
+ }
+}
diff --git a/src/org/catacombae/dmg/udif/UDIFDetector.java b/src/org/catacombae/dmg/udif/UDIFDetector.java
new file mode 100644
index 0000000..e5fcffd
--- /dev/null
+++ b/src/org/catacombae/dmg/udif/UDIFDetector.java
@@ -0,0 +1,58 @@
+/*-
+ * Copyright (C) 2007 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmg.udif;
+
+import java.io.RandomAccessFile;
+import org.catacombae.io.ReadableFileStream;
+import org.catacombae.io.ReadableRandomAccessStream;
+import org.catacombae.io.RuntimeIOException;
+
+/**
+ * Contains a few static utility methods for easily detecting an UDIF encoded
+ * disk image.
+ *
+ * @author Erik Larsson
+ */
+public class UDIFDetector {
+ /**
+ * Convenience method. Equivalent to
+ * isUDIFEncoded(new ReadableFileStream(raf));.
+ */
+ public static boolean isUDIFEncoded(RandomAccessFile raf) throws RuntimeIOException {
+ return isUDIFEncoded(new ReadableFileStream(raf));
+ }
+
+ /**
+ * Searches through the supplied RandomAccessStream for signature data that validates
+ * the data as UDIF encoded.
+ * @throws RuntimeIOException on I/O error
+ */
+ public static boolean isUDIFEncoded(ReadableRandomAccessStream ras) throws RuntimeIOException {
+ if(ras.length() < 512)
+ return false;
+
+ byte[] kolyData = new byte[Koly.length()];
+ ras.seek(ras.length() - 512);
+ if(ras.read(kolyData) != kolyData.length)
+ throw new RuntimeException("Could not read all koly data...");
+ Koly koly = new Koly(kolyData, 0);
+ return koly.isValid() &&
+ koly.getPlistBegin1() >= 0 && koly.getPlistSize() > 0 &&
+ (koly.getPlistBegin1() + koly.getPlistSize()) <= (ras.length() - 512);
+ }
+}
diff --git a/src/org/catacombae/dmg/udif/UDIFFile.java b/src/org/catacombae/dmg/udif/UDIFFile.java
new file mode 100644
index 0000000..1971206
--- /dev/null
+++ b/src/org/catacombae/dmg/udif/UDIFFile.java
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmg.udif;
+
+import org.catacombae.io.ReadableRandomAccessStream;
+
+public class UDIFFile {
+ private ReadableRandomAccessStream stream;
+ private UDIFFileView dmgView;
+
+ public UDIFFile(ReadableRandomAccessStream stream) {
+ this.stream = stream;
+ this.dmgView = new UDIFFileView(stream);
+ }
+
+ public UDIFFileView getView() {
+ return dmgView;
+ }
+
+ public ReadableRandomAccessStream getStream() {
+ return stream;
+ }
+}
diff --git a/src/org/catacombae/dmg/udif/UDIFFileView.java b/src/org/catacombae/dmg/udif/UDIFFileView.java
new file mode 100644
index 0000000..ccd2c8e
--- /dev/null
+++ b/src/org/catacombae/dmg/udif/UDIFFileView.java
@@ -0,0 +1,67 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmg.udif;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import org.catacombae.io.ReadableFileStream;
+import org.catacombae.io.ReadableRandomAccessStream;
+import org.catacombae.io.RuntimeIOException;
+
+public class UDIFFileView {
+ private ReadableRandomAccessStream dmgRaf;
+
+ public UDIFFileView(File file) {
+ try {
+ //this.file = file;
+ this.dmgRaf = new ReadableFileStream(new RandomAccessFile(file, "r"));
+ } catch(IOException ioe) {
+ throw new RuntimeIOException(ioe);
+ }
+ }
+ public UDIFFileView(ReadableRandomAccessStream dmgRaf) {
+ this.dmgRaf = dmgRaf;
+ }
+
+ public byte[] getPlistData() throws RuntimeIOException {
+ Koly koly = getKoly();
+ byte[] plistData = new byte[(int)koly.getPlistSize()]; // Let's hope the plistsize is within int range... (though memory will run out long before that)
+
+ dmgRaf.seek(koly.getPlistBegin1());
+ if(dmgRaf.read(plistData) == plistData.length)
+ return plistData;
+ else
+ throw new RuntimeException("Could not read the entire region of data containing the Plist");
+ }
+
+ public Plist getPlist() throws RuntimeIOException {
+ return new Plist(getPlistData());
+ }
+
+ public Koly getKoly() throws RuntimeIOException {
+ dmgRaf.seek(dmgRaf.length()-512);
+ byte[] kolyData = new byte[512];
+ dmgRaf.read(kolyData);
+ return new Koly(kolyData, 0);
+ }
+
+ public void close() throws RuntimeIOException {
+ dmgRaf.close();
+ }
+}
diff --git a/src/org/catacombae/dmg/udif/UDIFInputStream.java b/src/org/catacombae/dmg/udif/UDIFInputStream.java
new file mode 100644
index 0000000..324c66a
--- /dev/null
+++ b/src/org/catacombae/dmg/udif/UDIFInputStream.java
@@ -0,0 +1,149 @@
+/*-
+ * Copyright (C) 2007-2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmg.udif;
+
+import java.io.*;
+
+/**
+ * An InputStream for reading the "block device" contents of a UDIF disk image file, usually
+ * using the extension .dmg. Note that all files with the extension .dmg are not UDIF
+ * encoded. Some are raw disk images, with no wrapper, and can be burnt or restored directly
+ * to disk/optical disc.
+ * Make sure that no other stream concurrently uses the input file. This class isn't thread
+ * safe, so use external synchronization if needed.
+ */
+public class UDIFInputStream extends InputStream {
+ private UDIFRandomAccessStream wrapped;
+ private long filePointer;
+
+ /** Constructs a new UDIF input stream from a RandomAccessFile. This is a convenience method, and
+ equal to new UDI*/
+ public UDIFInputStream(RandomAccessFile raf) throws IOException {
+ this(new UDIFRandomAccessStream(raf));
+ }
+ public UDIFInputStream(UDIFRandomAccessStream dras) throws IOException {
+ this.wrapped = dras;
+ }
+
+ /** Returns the number of available bytes, or Integer.MAX_VALUE if the value is too large for int. */
+ @Override
+ public int available() throws IOException {
+ long len = wrapped.length()-filePointer;
+ if(len > Integer.MAX_VALUE)
+ return Integer.MAX_VALUE;
+ else if(len < 0)
+ throw new IOException("Internal error! filePointer > wrapped.length! filePointer:" + filePointer + " wrapped.length():" + wrapped.length());
+ else
+ return (int)len;
+ }
+ /** Does nothing. You will have to close the underlying RandomAccessFile or RandomAccessStream manually. */
+ @Override
+ public void close() throws IOException {}
+
+ /** Not supported. */
+ @Override
+ public void mark(int readlimit) {}
+
+ /** Mark is not supported. This method always returns false. */
+ @Override
+ public boolean markSupported() { return false; }
+
+ /** See the general contract of the read method for java.io.InputStream. */
+ @Override
+ public int read() throws IOException {
+ byte[] b = new byte[1];
+ if(read(b, 0, 1) != 1)
+ return -1;
+ else
+ return b[0] & 0xFF;
+ }
+
+ /** See the general contract of the read method for java.io.InputStream. */
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ /** See the general contract of the read method for java.io.InputStream. */
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if(wrapped.getFilePointer() != filePointer)
+ wrapped.seek(filePointer);
+ int bytesRead = wrapped.read(b, off, len);
+ if(bytesRead > 0)
+ filePointer += bytesRead;
+ return bytesRead;
+ }
+
+ /** Not supported. */
+ @Override
+ public void reset() throws IOException {}
+
+ /** See the general contract of the skip method for java.io.InputStream. */
+ @Override
+ public long skip(long n) throws IOException {
+ if(n < 0) throw new IllegalArgumentException("n must be positive");
+ long newPos = filePointer+n;
+ if(newPos > wrapped.length())
+ newPos = wrapped.length();
+ wrapped.seek(newPos);
+ long result = newPos - filePointer;
+ filePointer = newPos;
+ return result;
+ }
+
+ /** Test code. */
+ public static void main(String[] args) throws IOException {
+ if(args.length != 2)
+ System.out.println("usage: java org.catacombae.udif.UDIFInputStream ");
+ File inFile = new File(args[0]);
+ File outFile = new File(args[1]);
+
+ RandomAccessFile inRaf = null;
+ FileOutputStream outFos = null;
+
+ if(inFile.canRead())
+ inRaf = new RandomAccessFile(inFile, "r");
+ else {
+ System.out.println("Can't read from input file!");
+ System.exit(0);
+ }
+
+ if(!outFile.exists())
+ outFos = new FileOutputStream(outFile);
+ else {
+ System.out.println("Output file already exists!");
+ System.exit(0);
+ }
+
+ UDIFInputStream dis = new UDIFInputStream(inRaf);
+ byte[] buffer = new byte[8192];
+ long bytesExtracted = 0;
+ int bytesRead = dis.read(buffer);
+ while(bytesRead > 0) {
+ bytesExtracted += bytesRead;
+ outFos.write(buffer, 0, bytesRead);
+ bytesRead = dis.read(buffer);
+ }
+ dis.close();
+ inRaf.close();
+ outFos.close();
+
+ System.out.println("Extracted " + bytesExtracted + " bytes to \"" + outFile + "\".");
+ }
+}
diff --git a/src/org/catacombae/dmg/udif/UDIFRandomAccessStream.java b/src/org/catacombae/dmg/udif/UDIFRandomAccessStream.java
new file mode 100644
index 0000000..b3404c0
--- /dev/null
+++ b/src/org/catacombae/dmg/udif/UDIFRandomAccessStream.java
@@ -0,0 +1,233 @@
+/*-
+ * Copyright (C) 2007-2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmg.udif;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import org.catacombae.io.BasicReadableRandomAccessStream;
+import org.catacombae.io.ReadableFileStream;
+import org.catacombae.io.ReadableRandomAccessStream;
+import org.catacombae.io.RuntimeIOException;
+
+
+public class UDIFRandomAccessStream extends BasicReadableRandomAccessStream {
+ /*
+ We have a string of data divided into blocks. Different algorithms must be applied to
+ different types of blocks in order to extract the data.
+ */
+ private UDIFFile dmgFile;
+ private UDIFBlock[] allBlocks;
+ private UDIFBlock currentBlock;
+ private UDIFBlockInputStream currentBlockStream;
+
+ private long length;
+ /** This is the pointer to the current position in the virtual file provided by this stream. */
+ private long logicalFilePointer = 0;
+ private boolean seekCalled = false;
+
+ private static void dbg(String s) { System.err.println(s); }
+
+ public UDIFRandomAccessStream(RandomAccessFile raf) throws RuntimeIOException {
+ this(new ReadableFileStream(raf));
+ }
+
+ public UDIFRandomAccessStream(ReadableRandomAccessStream stream) throws RuntimeIOException {
+ this(new UDIFFile(stream));
+ }
+
+ public UDIFRandomAccessStream(UDIFFile dmgFile) throws RuntimeIOException {
+ this.dmgFile = dmgFile;
+ //dbg("dmgFile.getView().getPlist(); free memory: " + Runtime.getRuntime().freeMemory() + " total memory: " + Runtime.getRuntime().totalMemory());
+ Plist plist = dmgFile.getView().getPlist();
+ //dbg("before gc(): free memory: " + Runtime.getRuntime().freeMemory() + " total memory: " + Runtime.getRuntime().totalMemory());
+ //Runtime.getRuntime().gc();
+ //dbg("plist.getPartitions(); free memory: " + Runtime.getRuntime().freeMemory() + " total memory: " + Runtime.getRuntime().totalMemory());
+ try {
+ PlistPartition[] partitions = plist.getPartitions();
+
+ int totalBlockCount = 0;
+ for(PlistPartition pp : partitions) {
+ totalBlockCount += pp.getBlockCount();
+ //dbg("totalBlockCount = " + totalBlockCount);
+ }
+ allBlocks = new UDIFBlock[totalBlockCount];
+ int pos = 0;
+ //dbg("looping for each of " + partitions.length + " partitions...");
+ for(PlistPartition pp : partitions) {
+ UDIFBlock[] blocks = pp.getBlocks();
+ //dbg("Blocks in partition: " + blocks.length);
+ System.arraycopy(blocks, 0, allBlocks, pos, blocks.length);
+ pos += blocks.length;
+ length += pp.getPartitionSize();
+ }
+ if(totalBlockCount > 0) {
+ currentBlock = allBlocks[0];
+ //dbg("Repositioning stream");
+ repositionStream();
+ //dbg("repositioning done.");
+ }
+ else {
+ throw new RuntimeException("Could not find any blocks in the DMG file...");
+ }
+ } catch(IOException ex) {
+ throw new RuntimeIOException(ex);
+ }
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public void close() throws RuntimeIOException {}
+
+ /** @see java.io.RandomAccessFile */
+ public long getFilePointer() throws RuntimeIOException { return logicalFilePointer; }
+
+ /** @see java.io.RandomAccessFile */
+ public long length() throws RuntimeIOException { return length; }
+
+ /** @see java.io.RandomAccessFile */
+ public int read() throws RuntimeIOException {
+ byte[] b = new byte[1];
+ if(read(b, 0, 1) != 1)
+ return -1;
+ else
+ return b[0] & 0xFF;
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public int read(byte[] b) throws RuntimeIOException { return read(b, 0, b.length); }
+
+ /** @see java.io.RandomAccessFile */
+ public int read(byte[] b, int off, int len) throws RuntimeIOException {
+ try {
+ //System.out.println("UDIFRandomAccessStream.read(b.length=" + b.length + ", " + off + ", " + len + ") {");
+ if(seekCalled) {
+ seekCalled = false;
+ //System.out.print(" Repositioning stream after seek (logical file pointer: " + logicalFilePointer + ")...");
+ try { repositionStream(); }
+ catch(RuntimeException re) {
+// System.out.println("return: -1 }");
+ return -1;
+ }
+ //System.out.println("done.");
+ }
+ int bytesRead = 0;
+ while(bytesRead < len) {
+
+ int curBytesRead = currentBlockStream.read(b, off+bytesRead, len-bytesRead);
+ if(curBytesRead < 0) {
+ //System.out.print(" Repositioning stream...");
+ try { repositionStream(); }
+ catch(RuntimeException re) {
+ if(bytesRead == 0)
+ bytesRead = -1; // If no bytes could be read, we must indicate that the stream has no more data
+ break;
+ }
+ //System.out.println("done.");
+ curBytesRead = currentBlockStream.read(b, off+bytesRead, len-bytesRead);
+ if(curBytesRead < 0) {
+ throw new RuntimeException("No bytes could be read, and no exception was thrown! Program error...");
+// if(bytesRead == 0)
+// bytesRead = -1; // If no bytes could be read, we must indicate that the stream has no more data
+// break;
+ }
+ }
+
+// if(curBytesRead >= 0)
+ bytesRead += curBytesRead;
+ logicalFilePointer += curBytesRead;
+ }
+
+
+
+// System.out.println("return: " + bytesRead + " }");
+ return bytesRead;
+ } catch(IOException ex) {
+ throw new RuntimeIOException(ex);
+ }
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public void seek(long pos) throws RuntimeIOException {
+ if(logicalFilePointer != pos) {
+ seekCalled = true;
+ logicalFilePointer = pos;
+ }
+ }
+
+ private void repositionStream() throws RuntimeIOException {
+// System.out.println("");
+ try {
+ // if the global file pointer is not within the bounds of the current block, then find the accurate block
+ if(!(currentBlock.getTrueOutOffset() <= logicalFilePointer &&
+ (currentBlock.getTrueOutOffset()+currentBlock.getOutSize()) > logicalFilePointer)) {
+ UDIFBlock soughtBlock = null;
+ for(UDIFBlock dblk : allBlocks) {
+ long startPos = dblk.getTrueOutOffset();
+ long endPos = startPos + dblk.getOutSize();
+ if(startPos <= logicalFilePointer && endPos > logicalFilePointer) {
+ soughtBlock = dblk;
+ break;
+ }
+ }
+ if(soughtBlock != null) {
+ //System.out.println("REPOSITION " + currentBlock.getBlockTypeAsString() + "(" + currentBlock.getTrueOutOffset() + "," + currentBlock.getOutSize() + ") -> " + soughtBlock.getBlockTypeAsString() + "(" + soughtBlock.getTrueOutOffset() + "," + soughtBlock.getOutSize() + ")");
+// if(soughtBlock.getTrueOutOffset() == currentBlock.getTrueOutOffset()+currentBlock.getOutSize())
+// System.out.println(" Continuous! :)");
+// else
+// System.out.println(" FUCKADSFOA!!!1one");
+ currentBlock = soughtBlock;
+ }
+ else
+ throw new RuntimeException("Trying to seek outside bounds.");
+ }
+
+ currentBlockStream = UDIFBlockInputStream.getStream(dmgFile.getStream(), currentBlock);
+ long bytesToSkip = logicalFilePointer - currentBlock.getTrueOutOffset();
+// System.out.print(" skipping " + bytesToSkip + " bytes...");
+ currentBlockStream.skip(bytesToSkip);
+// System.out.println("done.");
+ } catch(IOException ex) {
+ throw new RuntimeIOException(ex);
+ }
+// System.out.println("");
+ }
+
+ public static void main(String[] args) throws IOException {
+ System.out.println("UDIFRandomAccessStream simple test program");
+ System.out.println("(Simply extracts the contents of a DMG file to a designated output file)");
+ if(args.length != 2)
+ System.out.println(" ERROR: You must supply exactly two arguments: 1. the DMG, 2. the output file");
+ else {
+ byte[] buffer = new byte[4096];
+ UDIFRandomAccessStream dras =
+ new UDIFRandomAccessStream(new UDIFFile(new ReadableFileStream(new RandomAccessFile(args[0], "r"))));
+ FileOutputStream fos = new FileOutputStream(args[1]);
+
+ long totalBytesRead = 0;
+
+ int bytesRead = dras.read(buffer);
+ while(bytesRead > 0) {
+ totalBytesRead += bytesRead;
+ fos.write(buffer, 0, bytesRead);
+ bytesRead = dras.read(buffer);
+ }
+ System.out.println("Done! Extracted " + totalBytesRead + " bytes.");
+ System.out.println("Length: " + dras.length() + " bytes");
+ }
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/BasicUI.java b/src/org/catacombae/dmgextractor/BasicUI.java
new file mode 100644
index 0000000..ad08163
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/BasicUI.java
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (C) 2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor;
+
+/**
+ *
+ * @author erik
+ */
+abstract class BasicUI implements UserInterface {
+
+ protected long totalProgressLength = 0;
+ protected long currentProgress = 0;
+ /** Used to prevent unneccessary updates of the progress meter. */
+ protected long previousPercentage = -1;
+ protected final boolean verbose;
+
+ public BasicUI(boolean verbose) {
+ this.verbose = verbose;
+ }
+
+ /** {@inheritDoc} */
+ public void setTotalProgressLength(long len) {
+ totalProgressLength = len;
+ }
+
+ /** {@inheritDoc} */
+ public void addProgressRaw(long value) {
+ currentProgress += value;
+ reportProgress((int) (currentProgress * 100 / totalProgressLength));
+ }
+
+ /** {@inheritDoc} */
+ public void displayMessageVerbose(String... messageLines) {
+ if(verbose)
+ displayMessage(messageLines);
+ }
+}
diff --git a/src/BuildNumber.java b/src/org/catacombae/dmgextractor/BuildNumber.java
similarity index 54%
rename from src/BuildNumber.java
rename to src/org/catacombae/dmgextractor/BuildNumber.java
index 446a4ad..e3b9029 100644
--- a/src/BuildNumber.java
+++ b/src/org/catacombae/dmgextractor/BuildNumber.java
@@ -1,26 +1,25 @@
/*-
* Copyright (C) 2006 Erik Larsson
*
- * All rights reserved.
- *
- * 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 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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ * along with this program. If not, see .
*/
+package org.catacombae.dmgextractor;
+
public class BuildNumber {
//[BuildEnumerator:Opening] WARNING: The following lines are managed by an external program. Do NOT change.
- public static final long BUILD_NUMBER = 130L;
+ public static final long BUILD_NUMBER = 438L;
//[BuildEnumerator:Closing] The lines managed by an external program end here.
}
diff --git a/src/org/catacombae/dmgextractor/ConcatenatedIterator.java b/src/org/catacombae/dmgextractor/ConcatenatedIterator.java
new file mode 100644
index 0000000..5fec069
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/ConcatenatedIterator.java
@@ -0,0 +1,51 @@
+/*-
+ * Copyright (C) 2007 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor;
+
+import java.util.*;
+
+public class ConcatenatedIterator implements Iterator {
+ private ArrayList> sequence = new ArrayList>();
+ private int index = 0;
+ public void add(Iterator next) { sequence.add(next); }
+
+ public boolean hasNext() {
+ if(index < sequence.size()) {
+ Iterator curIt = sequence.get(index);
+ while(!curIt.hasNext() && (index+1) < sequence.size())
+ curIt = sequence.get(++index);
+ return curIt.hasNext();
+ }
+ else
+ return false;
+ }
+ public E next() {
+ if(index < sequence.size()) {
+ Iterator curIt = sequence.get(index);
+ while(!curIt.hasNext() && (index+1) < sequence.size())
+ curIt = sequence.get(++index);
+ return curIt.next();
+ }
+ else
+ throw new NoSuchElementException();
+
+ }
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/DMGBlockHandlers.java b/src/org/catacombae/dmgextractor/DMGBlockHandlers.java
new file mode 100644
index 0000000..49cf141
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/DMGBlockHandlers.java
@@ -0,0 +1,73 @@
+/*-
+ * Copyright (C) 2006-2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor;
+
+import java.io.IOException;
+import org.catacombae.dmg.udif.UDIFBlockInputStream;
+import org.catacombae.dmg.udif.UDIFBlock;
+import org.catacombae.io.RandomAccessStream;
+import org.catacombae.io.ReadableRandomAccessStream;
+
+/**
+ * Please don't try to use this code with concurrent threads... :) Use external
+ * synchronization to protect the shared data in this class.
+ */
+class DMGBlockHandlers {
+
+ private static byte[] inBuffer = new byte[0x40000];
+
+ /**
+ * Extracts an UDIFBlock describing a region of the file dmgRaf
+ * to the file isoRaf. If the testOnly flag is
+ * set, nothing is written to isoRaf (in fact, it can be null
+ * in this case). ui may not be null. If you do not want user
+ * interaction, use {@link UserInterface.NullUI}.
+ */
+ static long processBlock(UDIFBlock block, ReadableRandomAccessStream dmgRaf,
+ RandomAccessStream isoRaf, boolean testOnly, UserInterface ui)
+ throws IOException {
+
+ UDIFBlockInputStream is = UDIFBlockInputStream.getStream(dmgRaf, block);
+ long res = processStream(is, isoRaf, testOnly, ui);
+ is.close();
+ if(res != block.getOutSize()) {
+ System.err.println("WARNING: Could not extract entire block! " +
+ "Extracted " + res + " of " + block.getOutSize() +
+ " bytes");
+ }
+ return res;
+ }
+
+ private static long processStream(UDIFBlockInputStream is,
+ RandomAccessStream isoRaf, boolean testOnly, UserInterface ui)
+ throws IOException {
+
+ long totalBytesRead = 0;
+ int bytesRead = is.read(inBuffer);
+ while(bytesRead > 0) {
+ totalBytesRead += bytesRead;
+ //ui.reportProgress((int)(dmgRaf.getFilePointer()*100/dmgRaf.length()));
+ ui.addProgressRaw(bytesRead);
+ if(!testOnly) {
+ isoRaf.write(inBuffer, 0, bytesRead);
+ }
+ bytesRead = is.read(inBuffer);
+ }
+ return totalBytesRead;
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/DMGExtractor.java b/src/org/catacombae/dmgextractor/DMGExtractor.java
new file mode 100644
index 0000000..7602e85
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/DMGExtractor.java
@@ -0,0 +1,722 @@
+/*-
+ * Copyright (C) 2006-2008 Erik Larsson
+ * (C) 2004 vu1tur (not the actual java code, but the C-code which
+ * has been used for reference)
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor;
+
+import org.catacombae.dmg.udif.Debug;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.io.RandomAccessFile;
+import java.util.LinkedList;
+import java.util.Iterator;
+import java.util.Collections;
+import javax.swing.JOptionPane;
+import org.catacombae.dmg.encrypted.ReadableCEncryptedEncodingStream;
+import org.catacombae.io.FileStream;
+import org.catacombae.io.ReadableFileStream;
+import org.catacombae.io.ReadableRandomAccessStream;
+import org.catacombae.io.TruncatableRandomAccessStream;
+import org.catacombae.dmg.udif.Koly;
+import org.catacombae.dmg.udif.Plist;
+import org.catacombae.dmg.udif.PlistPartition;
+import org.catacombae.dmg.udif.UDIFBlock;
+import org.catacombae.dmg.udif.UDIFDetector;
+import org.xml.sax.XMLReader;
+
+public class DMGExtractor {
+ public static final String BASE_APP_NAME = "DMGExtractor";
+ public static final String VERSION = "0.70";
+
+ public static final String APPNAME = BASE_APP_NAME + " " + VERSION;
+ public static final String BUILDSTRING = "(Build #" + BuildNumber.BUILD_NUMBER + ")";
+ public static final String[] COPYRIGHT_MESSAGE = new String[] {
+ APPNAME + " " + BUILDSTRING,
+ "Copyright \u00A9 2006-2008 Erik Larsson ",
+ " based on dmg2iso, Copyright \u00A9 2004 vu1tur ",
+ " using the libraries:",
+ " iHarder Base64 Encoder/Decoder ",
+ " released into the public domain",
+ " Apache Ant bzip2 library ",
+ " released under The Apache Software License Version 2.0",
+ "",
+ "This program is distributed under the GNU General Public License version 3 or",
+ "later. See for the details.",
+ ""
+ };
+
+ /**
+ * Contains settings variables for a DMGExtractor session.
+ */
+ private static class Session {
+ public String parseArgsErrorMessage = null;
+ public boolean useSaxParser = false;
+ public boolean verbose = false;
+ public boolean debug = false;
+ public boolean graphical = false;
+ public String startupCommand = "java DMGExtractor";
+ public File dmgFile = null;
+ public File isoFile = null;
+ }
+
+ public static void main(String[] args) throws Exception {
+ Session ses = null;
+ try {
+ ses = parseArgs(args);
+
+ if(ses.debug)
+ ses.verbose = true;
+
+ final UserInterface ui;
+ if(ses.graphical)
+ ui = new SwingUI(ses.verbose);
+ else
+ ui = new TextModeUI(ses.verbose);
+
+ ui.displayMessage(COPYRIGHT_MESSAGE);
+ if(ses.parseArgsErrorMessage == null) {
+ if(ses.graphical) {
+ if(ses.dmgFile == null) {
+ ses.dmgFile = ui.getInputFileFromUser();
+ }
+ if(ses.dmgFile != null && ses.isoFile == null) {
+ if(ui.getOutputConfirmationFromUser()) {
+ ses.isoFile = ui.getOutputFileFromUser(ses.dmgFile);
+ if(ses.isoFile == null)
+ System.exit(0);
+ }
+ }
+ }
+
+ if(ses.dmgFile != null) {
+ String dmgFilename = null;
+ String isoFilename = null;
+
+ if(ses.dmgFile != null)
+ dmgFilename = ses.dmgFile.getName();
+ if(ses.isoFile != null)
+ isoFilename = ses.isoFile.getName();
+
+ ui.setProgressFilenames(dmgFilename, isoFilename);
+ extractProcedure(ses, ui);
+ }
+ else if(!ses.graphical)
+ printUsageInstructions(ui, ses.startupCommand,
+ "Error: No input file specified.");
+ }
+ else
+ printUsageInstructions(ui, ses.startupCommand, ses.parseArgsErrorMessage);
+
+ } catch(Exception e) {
+ if(ses != null && ses.graphical) {
+ String stackTrace = e.toString() + "\n";
+ for(StackTraceElement ste : e.getStackTrace())
+ stackTrace += " " + ste.toString() + "\n";
+ JOptionPane.showMessageDialog(null, "The program encountered an uncaught exception:\n" + stackTrace +
+ "\nCan not recover. Exiting...", "Error", JOptionPane.ERROR_MESSAGE);
+ }
+ throw e;
+ }
+ }
+
+ private static void extractProcedure(Session ses, UserInterface ui) throws Exception {
+
+ ui.displayMessageVerbose("Processing: \"" + ses.dmgFile + "\"");
+
+ ReadableRandomAccessStream dmgRaf = new ReadableFileStream(new RandomAccessFile(ses.dmgFile, "r"));
+
+ final boolean encrypted;
+ if(ReadableCEncryptedEncodingStream.isCEncryptedEncoding(dmgRaf)) {
+ encrypted = true;
+ char[] password;
+ while(true) {
+ password = ui.getPasswordFromUser();
+ if(password == null) {
+ ui.displayMessage("No password specified. Can not continue...");
+ System.exit(0);
+ }
+ try {
+ ReadableCEncryptedEncodingStream encryptionFilter =
+ new ReadableCEncryptedEncodingStream(dmgRaf, password);
+ dmgRaf = encryptionFilter;
+ break;
+ } catch(Exception e) {
+ ui.displayMessage("Incorrect password!");
+ }
+ }
+ }
+ else
+ encrypted = false;
+
+ TruncatableRandomAccessStream isoRaf = null;
+ final boolean testOnly;
+ if(ses.isoFile != null) {
+ testOnly = false;
+ isoRaf = new FileStream(ses.isoFile);
+ isoRaf.setLength(0);
+ ui.displayMessageVerbose("Extracting to: \"" + ses.isoFile + "\"");
+ }
+ else {
+ testOnly = true;
+ ui.displayMessageVerbose("Simulating extraction...");
+ }
+
+ if(!UDIFDetector.isUDIFEncoded(dmgRaf)) {
+ if(!encrypted) {
+ if(!ui.warning("The image you selected does not seem to be UDIF encoded or encrypted.",
+ "Its contents will be copied unchanged to the destination.")) {
+ System.exit(1);
+ }
+ }
+ copyData(dmgRaf, isoRaf, ui);
+ System.exit(0);
+ }
+
+ Koly koly;
+ {
+ dmgRaf.seek(dmgRaf.length() - Koly.length());
+ byte[] kolyData = new byte[512];
+ int kolyDataRead = dmgRaf.read(kolyData);
+ if(kolyDataRead != kolyData.length)
+ throw new RuntimeException("Could not read koly completely. Read " + kolyDataRead + "/" +
+ kolyData.length + " bytes.");
+ else
+ koly = new Koly(kolyData, 0);
+ }
+
+ if(ses.debug) {
+ ui.displayMessage("plist addresses:",
+ " " + koly.getPlistBegin1(),
+ " " + koly.getPlistBegin2());
+ }
+ if(koly.getPlistBegin1() != koly.getPlistBegin2()) {
+ ui.displayMessage("WARNING: Addresses not equal! Assumption false.",
+ koly.getPlistBegin1() + " != " + koly.getPlistBegin2());
+ //System.exit(0);
+ }
+ // if(false && plistSize != (plistEnd-plistBegin1)) { // This assumption is proven false. plistEnd means something else
+ // println("NOTE: plistSize field does not match plistEnd marker. Assumption false.",
+ // "plistSize=" + plistSize + " plistBegin1=" + plistBegin1 + " plistEnd=" + plistEnd + " plistEnd-plistBegin1=" + (plistEnd-plistBegin1));
+ // }
+ ui.displayMessageVerbose("Jumping to address...");
+ dmgRaf.seek(koly.getPlistBegin1());
+ final long plistSize = koly.getPlistSize();
+ if(plistSize > Integer.MAX_VALUE)
+ throw new RuntimeException("getPlistSize() way too large (" + plistSize + ")!");
+ else if(plistSize < 0)
+ throw new RuntimeException("getPlistSize() way too small (" + plistSize + ")!");
+ byte[] buffer = new byte[(int) koly.getPlistSize()];
+ dmgRaf.read(buffer);
+
+ Plist plist = new Plist(buffer, ses.useSaxParser);
+ PlistPartition[] partitions = plist.getPartitions();
+
+ long totalOutSize = 0;
+ for(PlistPartition p : partitions) {
+ Iterator blockIt = p.getBlockIterator();
+ while(blockIt.hasNext())
+ totalOutSize += blockIt.next().getOutSize();
+ }
+ ui.displayMessageVerbose("Target size: " + totalOutSize + " bytes");
+ ui.setTotalProgressLength(totalOutSize);
+
+ byte[] zeroblock = new byte[4096];
+ Util.zero(zeroblock);
+
+ int partitionNumber = 0;
+ int errorsReported = 0;
+ int warningsReported = 0;
+ long totalSize = 0;
+ ui.reportProgress(0);
+ for(PlistPartition dpp : partitions) {
+ long partitionSize = dpp.getPartitionSize();
+ totalSize += partitionSize;
+
+ ui.displayMessageVerbose(" " + dpp.getName(),
+ " ID: " + dpp.getID(),
+ " Attributes: " + dpp.getAttributes(),
+ " Partition map block count: " + dpp.getBlockCount(),
+ " Partition size: " + partitionSize + " bytes");
+
+ int blockCount = 0;
+ Iterator blockIterator = dpp.getBlockIterator();
+ while(blockIterator.hasNext()) {
+ if(ui.cancelSignaled())
+ System.exit(0);
+ UDIFBlock currentBlock = blockIterator.next();
+
+ /* Offset of the input data for the current block in the input file */
+ final int blockType = currentBlock.getBlockType();
+ /* Offset of the input data for the current block in the input file */
+ final long inOffset = currentBlock.getTrueInOffset();
+ /* Size of the input data for the current block */
+ final long inSize = currentBlock.getInSize();
+ /* Offset of the output data for the current block in the output file */
+ final long outOffset = currentBlock.getTrueOutOffset();
+ /* Size of the output data (possibly larger than inSize because of
+ decompression, zero expansion...) */
+ final long outSize = currentBlock.getOutSize();
+
+ final long trueOutOffset = currentBlock.getTrueOutOffset();
+ final long trueInOffset = currentBlock.getTrueInOffset();
+ final String blockTypeString = currentBlock.getBlockTypeAsString();
+
+ /*
+ String[] variableStatus = {"outOffset=" + outOffset + " outSize=" + outSize,
+ "inOffset=" + inOffset + " inSize=" + inSize,
+ "trueOutOffset=" + trueOutOffset + " trueInOffset=" + trueInOffset};
+ */
+
+ if(ses.debug) {
+ ui.displayMessage(
+ " " + partitionNumber + ":" + blockCount + ". " + blockTypeString + " processing...",
+ " outOffset=" + outOffset + " outSize=" + outSize,
+ " inOffset=" + inOffset + " inSize=" + inSize,
+ " trueOutOffset=" + trueOutOffset + " trueInOffset=" + trueInOffset);
+ }
+ else
+ ui.displayMessageVerbose(" Processing " + blockTypeString +
+ " block. In: " + inSize +
+ " bytes. Out: " + outSize + " bytes.");
+
+ if(!testOnly && isoRaf.getFilePointer() != trueOutOffset) {
+ ++warningsReported;
+ boolean proceed = ui.warning(blockTypeString +
+ " FP != trueOutOffset (" + isoRaf.getFilePointer() +
+ " != " + trueOutOffset + ")");
+
+ if(!proceed)
+ System.exit(0);
+ }
+
+
+ if(blockType == UDIFBlock.BT_ADC) {
+ ++errorsReported;
+
+ if(extractionError(ui, testOnly, "BT_ADC not supported."))
+ break;
+ else
+ System.exit(0);
+ }
+ else if(blockType == UDIFBlock.BT_ZLIB) {
+ try {
+ DMGBlockHandlers.processBlock(currentBlock, dmgRaf, isoRaf, testOnly, ui);
+ } catch(DmgException de) {
+ de.printStackTrace();
+ String[] message = { "BT_ZLIB Could not decode..." };
+
+ ++errorsReported;
+ if(!ses.debug) {
+ String[] appended = { "outOffset=" + outOffset + " outSize=" + outSize,
+ "inOffset=" + inOffset + " inSize=" + inSize,
+ "trueOutOffset=" + trueOutOffset + " trueInOffset=" + trueInOffset };
+ message = Util.concatenate(message, appended);
+ }
+
+ if(extractionError(ui, testOnly, message))
+ break;
+ else
+ System.exit(0);
+ }
+ }
+ else if(blockType == UDIFBlock.BT_BZIP2) {
+ DMGBlockHandlers.processBlock(currentBlock, dmgRaf, isoRaf, testOnly, ui);
+ }
+ else if(blockType == UDIFBlock.BT_COPY) {
+ DMGBlockHandlers.processBlock(currentBlock, dmgRaf, isoRaf, testOnly, ui);
+ }
+ else if(blockType == UDIFBlock.BT_ZERO) {
+ DMGBlockHandlers.processBlock(currentBlock, dmgRaf, isoRaf, testOnly, ui);
+ }
+ else if(blockType == UDIFBlock.BT_ZERO2) {
+ DMGBlockHandlers.processBlock(currentBlock, dmgRaf, isoRaf, testOnly, ui);
+ }
+ else if(blockType == UDIFBlock.BT_UNKNOWN) {
+ /* I have no idea what this blocktype is... but it's common, and usually
+ doesn't appear more than 2-3 times in a dmg. As long as its input and
+ output sizes are 0, there's no reason to complain... is there? */
+ if(!(inSize == 0 && outSize == 0)) {
+ String[] message = { "Blocktype BT_UNKNOWN had non-zero sizes...",
+ " inSize=" + inSize + ", outSize=" + outSize,
+ " Please contact the author of the program to report this bug!" };
+
+ ++errorsReported;
+ if(!ses.debug) {
+ String[] appended = { "outOffset=" + outOffset + " outSize=" + outSize,
+ "inOffset=" + inOffset + " inSize=" + inSize,
+ "trueOutOffset=" + trueOutOffset + " trueInOffset=" + trueInOffset };
+ message = Util.concatenate(message, appended);
+ }
+
+ if(extractionError(ui, testOnly, message))
+ break;
+ else
+ System.exit(0);
+ }
+ }
+ else if(blockType == UDIFBlock.BT_END) {
+ // Nothing needs to be done in this pass.
+ }
+ else {
+ if(inSize == 0 && outSize == 0) {
+ ui.warning("previously unseen blocktype " + blockType + " [0x" + Integer.toHexString(blockType) + "]",
+ ("outOffset=" + outOffset + " outSize=" + outSize +
+ " inOffset=" + inOffset + " inSize=" + inSize),
+ "As inSize and outSize is 0 (block is a marker?), we can try to continue the operation...");
+ ++warningsReported;
+ }
+ else {
+ String[] message = { "previously unseen blocktype " + blockType + " [0x" + Integer.toHexString(blockType) + "]",
+ "outOffset=" + outOffset + " outSize=" + outSize + " inOffset=" + inOffset + " inSize=" + inSize,
+ "CRITICAL. inSize and/or outSize are not 0!" };
+ //errorMessage("previously unseen blocktype " + blockType + " [0x" + Integer.toHexString(blockType) + "]",
+ // (" outOffset=" + outOffset + " outSize=" + outSize +
+ // " inOffset=" + inOffset + " inSize=" + inSize),
+ // " CRITICAL. inSize and/or outSize are not 0!");
+ ++errorsReported;
+ if(!ses.debug) {
+ String[] appended = { "outOffset=" + outOffset + " outSize=" + outSize,
+ "inOffset=" + inOffset + " inSize=" + inSize,
+ "trueOutOffset=" + trueOutOffset + " trueInOffset=" + trueInOffset };
+ message = Util.concatenate(message, appended);
+ }
+
+ if(extractionError(ui, testOnly, message))
+ break;
+ else
+ System.exit(0);
+ }
+
+ }
+
+ ++blockCount;
+ }
+ ++partitionNumber;
+ }
+
+ ui.reportProgress(100);
+ ui.reportFinished(isoRaf == null, errorsReported, warningsReported, totalSize);
+
+ if(!ses.debug) {
+ if(isoRaf != null)
+ isoRaf.close();
+ dmgRaf.close();
+ }
+ else {
+ if(isoRaf != null)
+ isoRaf.close();
+ ConcatenatedIterator cit = new ConcatenatedIterator();
+ for(PlistPartition dpp : partitions)
+ cit.add(dpp.getBlockIterator());
+
+ LinkedList blocks = new LinkedList();
+ while(cit.hasNext()) {
+ UDIFBlock b = cit.next();
+ if(b.getInSize() == 0)
+ continue; // Not relevant to the calculation
+ else if(b.getInSize() > 0)
+ blocks.add(b);
+ else
+ throw new RuntimeException("Negative inSize! inSize=" + b.getInSize());
+ }
+ Collections.sort(blocks);
+
+ LinkedList merged = mergeBlocks(blocks.iterator());
+
+ String[] mergedRegions = new String[] { "Merged regions (size: " + merged.size() + "):" };
+ for(UDIFBlock b : merged)
+ Util.concatenate(mergedRegions, " " + b.getTrueInOffset() + " - " +
+ (b.getTrueInOffset() + b.getInSize()));
+ Util.concatenate(mergedRegions, "", "Extracting the regions not " +
+ "containing block data from source file...");
+ ui.displayMessage(mergedRegions);
+ int i = 1;
+ Iterator mergedIt = merged.iterator();
+ UDIFBlock previous = null;
+ if(merged.size() > 0 && merged.getFirst().getTrueInOffset() == 0)
+ previous = mergedIt.next();
+
+ while(mergedIt.hasNext() || previous != null) {
+ UDIFBlock b = null;
+ if(mergedIt.hasNext())
+ b = mergedIt.next();
+ //else
+ // b =
+ if(b == null || b.getTrueInOffset() > 0) {
+ long offset;
+ int size;
+ if(previous == null) {
+ offset = 0;
+ size = (int) (b.getInOffset());
+ if(size == 0)
+ continue; // First part may be empty, then we just continue
+ }
+ else if(b == null) {
+ offset = previous.getTrueInOffset() + previous.getInSize();
+ size = (int) (dmgRaf.length() - offset);
+ if(size == 0)
+ break; // Last part may be empty (in theory, though not in practice with true UDIF files)
+ }
+ else {
+ offset = previous.getTrueInOffset() + previous.getInSize();
+ size = (int) (b.getInOffset() - offset);
+ }
+
+ String filename = "[" + ses.dmgFile.getName() + "]-" + i++ + "-[" + offset + "-" + (offset + size) + "].region";
+ ui.displayMessage(" " + new File(filename).getCanonicalPath() + " (" + size + " bytes)...");
+ FileOutputStream curFos = new FileOutputStream(new File(filename));
+
+ if(size < 0) {
+ ui.error("ERROR: Negative array size (" + size + ")...",
+ " current:",
+ " " + b.toString(),
+ " previous:",
+ " " + previous.toString());
+ }
+
+ byte[] data = new byte[size];
+ dmgRaf.seek(offset);
+ dmgRaf.read(data);
+ curFos.write(data);
+ curFos.close();
+ }
+ previous = b;
+ }
+ dmgRaf.close();
+ ui.displayMessage("Done!");
+ }
+ }
+
+ private static void copyData(ReadableRandomAccessStream inStream,
+ TruncatableRandomAccessStream outStream, UserInterface ui) {
+ byte[] buffer = new byte[64*1024];
+
+ ui.setTotalProgressLength(inStream.length());
+ long totalBytesCopied = 0;
+ inStream.seek(0);
+ int bytesRead = inStream.read(buffer);
+ while(bytesRead > 0 && !ui.cancelSignaled()) {
+ if(outStream != null)
+ outStream.write(buffer, 0, bytesRead);
+ ui.addProgressRaw(bytesRead);
+ totalBytesCopied += bytesRead;
+
+ bytesRead = inStream.read(buffer);
+ }
+
+ ui.reportProgress(100);
+ ui.reportFinished(outStream == null, 0, 0, totalBytesCopied);
+ }
+
+
+ /**
+ *
+ * @param message
+ * @param ui
+ * @param testOnly
+ * @return true if the extraction should proceed, false if it should not.
+ */
+ private static boolean extractionError(UserInterface ui, boolean testOnly,
+ String... message) {
+ if(!testOnly) {
+ ui.error(message);
+ return false;
+ }
+ else {
+ message = Util.concatenate(message, "The program is " +
+ "run in testing mode, so we can continue...");
+ return ui.warning(message);
+ }
+ }
+
+ private static Session parseArgs(String[] args) {
+ Session ses = new Session();
+ try {
+ /* Take care of the options... */
+ int i;
+ for(i = 0; i < args.length; ++i) {
+ String cur = args[i];
+ //System.err.println("Parsing argument: \"" + cur + "\"");
+ if(!cur.startsWith("-"))
+ break;
+ else if(cur.equals("-gui"))
+ ses.graphical = true;
+ else if(cur.equals("-saxparser"))
+ ses.useSaxParser = true;
+ else if(cur.equals("-v"))
+ ses.verbose = true;
+ else if(cur.equals("-debug")) {
+ Debug.debug = true;
+ ses.debug = true;
+ }
+ else if(cur.equals("-startupcommand")) {
+ ses.startupCommand = args[i + 1];
+ ++i;
+ }
+ else {
+ ses.parseArgsErrorMessage = "Invalid argument: " + cur;
+ return ses;
+ }
+ }
+
+ /*
+ * This isn't a very good hack... clearly the invoker is doing
+ * something wrong when this situation comes up.
+ */
+ int emptyTrailingEntries = 0;
+ for(int j = i; j < args.length; ++j) {
+ if(args[i].equals(""))
+ ++emptyTrailingEntries;
+ }
+ //System.err.println("empty: " + emptyTrailingEntries);
+
+ if(i != args.length && (args.length - i) != emptyTrailingEntries) {
+ ses.dmgFile = new File(args[i++]);
+ if(ses.dmgFile.exists()) {
+
+ if(i <= args.length - 1 && !args[i].trim().equals(""))
+ ses.isoFile = new File(args[i++]);
+
+ if(i != args.length) {
+ if(!args[i].trim().equals(""))
+ ses.parseArgsErrorMessage = "Invalid argument: " +
+ args[i];
+ }
+ }
+ else {
+ ses.parseArgsErrorMessage =
+ "Input file \"" + ses.dmgFile + "\" not found!";
+ }
+ }
+ else if(!ses.graphical) {
+ ses.parseArgsErrorMessage = "Error: No input file specified.";
+ }
+ } catch(Exception e) {
+ e.printStackTrace();
+ ses.parseArgsErrorMessage = "Unhandled exception: " + e.toString() +
+ " (see console for stacktrace)";
+ }
+ return ses;
+ }
+
+ private static void printUsageInstructions(UserInterface ui, String startupCommand) {
+ printUsageInstructions(ui, startupCommand, null);
+ }
+
+ private static void printUsageInstructions(UserInterface ui, String startupCommand, String errorMessage) {
+ String[] prefixMessage = new String[0];
+
+ if(errorMessage != null)
+ prefixMessage = Util.concatenate(prefixMessage, errorMessage);
+
+ // 80 char ruler:
+ // <-------------------------------------------------------------------------------->
+ String[] mainMessage = new String[] {
+ " usage: " + startupCommand + " [options] []",
+ "",
+ " If an output file is not supplied, the program will simulate an extraction",
+ " (useful for detecting errors in dmg-files).",
+ "",
+ " options:",
+ " -v verbose operation... for finding out what went wrong",
+ " -saxparser use the standard SAX parser for XML processing instead of",
+ " the APX parser (will connect to Apple's website for DTD",
+ " validation)",
+ " -gui starts the program in graphical mode",
+ " -debug performs unspecified debug operations (only intended for",
+ " development use)",
+ ""
+ };
+
+
+ ui.displayMessage(Util.concatenate(prefixMessage, mainMessage));
+
+ }
+
+ private static void printSAXParserInfo(XMLReader saxParser, PrintStream ps, String prefix) throws Exception {
+ ps.println(prefix + "Features:");
+ ps.println(prefix + " external-general-entities: " + saxParser.getFeature("http://xml.org/sax/features/external-general-entities"));
+ ps.println(prefix + " external-parameter-entities: " + saxParser.getFeature("http://xml.org/sax/features/external-parameter-entities"));
+ ps.println(prefix + " is-standalone: " + saxParser.getFeature("http://xml.org/sax/features/is-standalone"));
+ ps.println(prefix + " lexical-handler/parameter-entities: " + saxParser.getFeature("http://xml.org/sax/features/lexical-handler/parameter-entities"));
+ //ps.println(prefix + " parameter-entities: " + saxParser.getFeature("http://xml.org/sax/features/parameter-entities"));
+ ps.println(prefix + " namespaces: " + saxParser.getFeature("http://xml.org/sax/features/namespaces"));
+ ps.println(prefix + " namespace-prefixes: " + saxParser.getFeature("http://xml.org/sax/features/namespace-prefixes"));
+ ps.println(prefix + " resolve-dtd-uris: " + saxParser.getFeature("http://xml.org/sax/features/resolve-dtd-uris"));
+ ps.println(prefix + " string-interning: " + saxParser.getFeature("http://xml.org/sax/features/string-interning"));
+ ps.println(prefix + " unicode-normalization-checking: " + saxParser.getFeature("http://xml.org/sax/features/unicode-normalization-checking"));
+ ps.println(prefix + " use-attributes2: " + saxParser.getFeature("http://xml.org/sax/features/use-attributes2"));
+ ps.println(prefix + " use-locator2: " + saxParser.getFeature("http://xml.org/sax/features/use-locator2"));
+ ps.println(prefix + " use-entity-resolver2: " + saxParser.getFeature("http://xml.org/sax/features/use-entity-resolver2"));
+ ps.println(prefix + " validation: " + saxParser.getFeature("http://xml.org/sax/features/validation"));
+ ps.println(prefix + " xmlns-uris: " + saxParser.getFeature("http://xml.org/sax/features/xmlns-uris"));
+ ps.println(prefix + " xml-1.1: " + saxParser.getFeature("http://xml.org/sax/features/xml-1.1"));
+
+ ps.println("Properties: ");
+ ps.println(prefix + " declaration-handler: " + saxParser.getProperty("http://xml.org/sax/properties/declaration-handler"));
+ ps.println(prefix + " document-xml-version: " + saxParser.getProperty("http://xml.org/sax/properties/document-xml-version"));
+ ps.println(prefix + " dom-node: " + saxParser.getProperty("http://xml.org/sax/properties/dom-node"));
+ ps.println(prefix + " lexical-handler: " + saxParser.getProperty("http://xml.org/sax/properties/lexical-handler"));
+ ps.println(prefix + " xml-string: " + saxParser.getProperty("http://xml.org/sax/properties/xml-string"));
+
+ //ps.println("isValidating: " + saxParser.isValidating());
+ }
+
+ private static LinkedList mergeBlocks(LinkedList blockList) {
+ Iterator it = blockList.iterator();
+ return mergeBlocks(it);
+ }
+
+ private static LinkedList mergeBlocks(Iterator it) {
+ LinkedList result = new LinkedList();
+ UDIFBlock previous = it.next();
+ while(previous.getInSize() == 0 && it.hasNext()) {
+ //System.err.println("Skipping: " + previous.toString());
+ previous = it.next();
+ }
+ //System.err.println("First block in merge sequence: " + previous.toString());
+
+ UDIFBlock current;
+ while(it.hasNext()) {
+ current = it.next();
+ if(current.getInSize() != 0) {
+ if(current.getTrueInOffset() == previous.getTrueInOffset() + previous.getInSize()) {
+ UDIFBlock mergedBlock = new UDIFBlock(
+ previous.getBlockType(),
+ previous.getReserved(),
+ previous.getOutOffset(),
+ previous.getOutSize() + current.getOutSize(),
+ previous.getInOffset(),
+ previous.getInSize() + current.getInSize(),
+ previous.getOutOffsetCompensation(),
+ previous.getInOffsetCompensation());
+ previous = mergedBlock;
+ }
+ else {
+ result.addLast(previous);
+ previous = current;
+ }
+ }
+ }
+ result.addLast(previous);
+ return result;
+ }
+
+}
+
diff --git a/src/DMGExtractorGraphical.java b/src/org/catacombae/dmgextractor/DMGExtractorGraphical.java
similarity index 52%
rename from src/DMGExtractorGraphical.java
rename to src/org/catacombae/dmgextractor/DMGExtractorGraphical.java
index 3f37ee7..8e131aa 100644
--- a/src/DMGExtractorGraphical.java
+++ b/src/org/catacombae/dmgextractor/DMGExtractorGraphical.java
@@ -1,27 +1,27 @@
/*-
* Copyright (C) 2006 Erik Larsson
*
- * All rights reserved.
- *
- * 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 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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ * along with this program. If not, see .
*/
+package org.catacombae.dmgextractor;
+
public class DMGExtractorGraphical {
public static void main(String[] args) throws Exception {
- String[] newargs = new String[1];
+ String[] newargs = new String[args.length+1];
newargs[0] = "-gui";
+ System.arraycopy(args, 0, newargs, 1, args.length);
DMGExtractor.main(newargs);
}
}
diff --git a/src/org/catacombae/dmgextractor/DmgException.java b/src/org/catacombae/dmgextractor/DmgException.java
new file mode 100644
index 0000000..358d767
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/DmgException.java
@@ -0,0 +1,27 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor;
+
+public class DmgException extends RuntimeException {
+ public DmgException() {
+ super();
+ }
+ public DmgException(String message) {
+ super(message);
+ }
+}
diff --git a/src/SimpleFileFilter.java b/src/org/catacombae/dmgextractor/SimpleFileFilter.java
similarity index 74%
rename from src/SimpleFileFilter.java
rename to src/org/catacombae/dmgextractor/SimpleFileFilter.java
index 3cbf88b..f52b056 100644
--- a/src/SimpleFileFilter.java
+++ b/src/org/catacombae/dmgextractor/SimpleFileFilter.java
@@ -1,23 +1,22 @@
/*-
* Copyright (C) 2006 Erik Larsson
*
- * All rights reserved.
- *
- * 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 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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ * along with this program. If not, see .
*/
+package org.catacombae.dmgextractor;
+
import java.util.*;
import java.io.*;
diff --git a/src/org/catacombae/dmgextractor/SimplerFileFilter.java b/src/org/catacombae/dmgextractor/SimplerFileFilter.java
new file mode 100644
index 0000000..8b33310
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/SimplerFileFilter.java
@@ -0,0 +1,60 @@
+/*-
+ * Copyright (C) 2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor;
+
+import java.io.File;
+import javax.swing.filechooser.FileFilter;
+
+
+/**
+ * Even simpler file filter as it only allows one extension.
+ * Directories are always accepted.
+ *
+ * @author Erik Larsson
+ */
+public class SimplerFileFilter extends FileFilter {
+
+ private String extension;
+ private String description;
+
+ public SimplerFileFilter(String extension, String description) {
+ this.extension = extension;
+ this.description = description;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean accept(File f) {
+ if(f.isDirectory())
+ return true;
+ else if(f.getName().endsWith(extension))
+ return true;
+ else
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String getDescription() { return description; }
+
+ /**
+ * Returns the extension that this file filter matches.
+ * @return the extension that this file filter matches.
+ */
+ public String getExtension() { return extension; }
+}
diff --git a/src/org/catacombae/dmgextractor/SwingUI.java b/src/org/catacombae/dmgextractor/SwingUI.java
new file mode 100644
index 0000000..2661459
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/SwingUI.java
@@ -0,0 +1,257 @@
+/*-
+ * Copyright (C) 2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor;
+
+import java.io.File;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.ProgressMonitor;
+import javax.swing.filechooser.FileFilter;
+import org.catacombae.dmgextractor.ui.PasswordDialog;
+
+/**
+ * User interface implementation using Java Swing.
+ *
+ * @author Erik Larsson
+ */
+class SwingUI extends BasicUI implements UserInterface {
+
+ private ProgressMonitor progmon = null;
+ private String inputFilename = null;
+ private String outputFilename = null;
+
+ public SwingUI(boolean verbose) {
+ super(verbose);
+
+ //System.setProperty("swing.aatext", "true"); //Antialiased text
+ try {
+ javax.swing.UIManager.setLookAndFeel(
+ javax.swing.UIManager.getSystemLookAndFeelClassName());
+ } catch(Exception e) {
+ // No big deal. Go with default.
+ }
+ }
+
+ /** {@inheritDoc} */
+ public boolean warning(String... messageLines) {
+ StringBuilder sb = new StringBuilder();
+
+ boolean firstLine = true;
+ for(String s : messageLines) {
+ if(!firstLine)
+ sb.append("\n");
+ else
+ firstLine = false;
+ sb.append(s);
+ }
+
+ sb.append("\n\nDo you want to continue?");
+
+ int res = JOptionPane.showConfirmDialog(null, sb.toString(),
+ DMGExtractor.APPNAME + ": Warning", JOptionPane.YES_NO_OPTION,
+ JOptionPane.WARNING_MESSAGE);
+ return res == JOptionPane.YES_OPTION;
+ }
+
+ /** {@inheritDoc} */
+ public void error(String... messageLines) {
+ StringBuilder sb = new StringBuilder();
+
+ boolean firstLine = true;
+ for(String s : messageLines) {
+ if(!firstLine)
+ sb.append("\n");
+ else
+ firstLine = false;
+ sb.append(s);
+ }
+
+ JOptionPane.showMessageDialog(null, sb.toString(),
+ DMGExtractor.APPNAME + ": Error", JOptionPane.ERROR_MESSAGE);
+ }
+
+ /** {@inheritDoc} */
+ public void reportProgress(int progressPercentage) {
+ if(progressPercentage != previousPercentage) {
+ if(progmon == null) {
+ final String progmonText;
+ if(outputFilename != null) {
+ progmonText = "Extracting \"" +
+ inputFilename + "\" to\n \"" + outputFilename + "\"...";
+ }
+ else {
+ progmonText = "Simulating extraction of \"" +
+ inputFilename + "\"...";
+ }
+ progmon = new ProgressMonitor(null, progmonText, "0%", 0, 100);
+ progmon.setProgress(0);
+ progmon.setMillisToPopup(0);
+ }
+ progmon.setProgress((int) progressPercentage);
+ progmon.setNote(progressPercentage + "%");
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void reportFinished(boolean simulation, int errorsReported, int warningsReported, long totalExtractedSize) {
+ StringBuilder message = new StringBuilder();
+ if(simulation)
+ message.append("Simulation");
+ else
+ message.append("Extraction");
+ message.append(" complete! ");
+ if(errorsReported != 0)
+ message.append(errorsReported).append(" errors reported");
+ else
+ message.append("No errors reported");
+
+ if(warningsReported != 0)
+ message.append(" (").append(warningsReported).append(" warnings emitted)");
+
+ message.append(".\nSize of extracted data: ").append(totalExtractedSize);
+ message.append(" bytes");
+
+ progmon.close();
+ JOptionPane.showMessageDialog(null, message.toString(),
+ DMGExtractor.APPNAME, JOptionPane.INFORMATION_MESSAGE);
+ System.exit(0); // TODO check this
+ }
+
+ /** {@inheritDoc} */
+ public boolean cancelSignaled() {
+ return progmon != null && progmon.isCanceled();
+ }
+
+ /** {@inheritDoc} */
+ public void displayMessage(String... messageLines) {
+ StringBuilder resultString = new StringBuilder();
+ boolean firstIteration = true;
+ for(String s : messageLines) {
+ if(!firstIteration)
+ resultString.append("\n");
+ else
+ firstIteration = false;
+
+ resultString.append(s);
+ }
+
+ JOptionPane.showMessageDialog(null, resultString.toString(),
+ DMGExtractor.APPNAME, JOptionPane.INFORMATION_MESSAGE);
+ }
+
+ /** {@inheritDoc} */
+ public File getInputFileFromUser() {
+ SimpleFileFilter sff = new SimpleFileFilter();
+ sff.addExtension("dmg");
+ sff.setDescription("Mac OS X disk images (*.dmg)");
+ JFileChooser jfc = new JFileChooser();
+ jfc.setFileFilter(sff);
+ jfc.setMultiSelectionEnabled(false);
+ jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+ jfc.setDialogTitle("Choose the .dmg file to read...");
+ while(true) {
+ if(jfc.showDialog(null, "Open") == JFileChooser.APPROVE_OPTION) {
+ File f = jfc.getSelectedFile();
+ if(f.exists()) {
+ return f;
+ }
+ else
+ JOptionPane.showMessageDialog(null, "The file does not exist! Choose again...",
+ "Error", JOptionPane.ERROR_MESSAGE);
+ }
+ else
+ return null;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public boolean getOutputConfirmationFromUser() {
+ return JOptionPane.showConfirmDialog(null, "Do you want to specify an output file?\n" +
+ "(Choosing \"No\" means the extraction will only be simulated,\n" +
+ "which can be useful for detecting errors in .dmg files...)",
+ "Confirmation", JOptionPane.YES_NO_OPTION,
+ JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;
+ }
+
+ /** {@inheritDoc} */
+ public File getOutputFileFromUser(File inputFile) {
+ final String msgFileExists = "The file already exists. Do you want to overwrite?";
+
+ JFileChooser jfc = new JFileChooser();
+ jfc.setMultiSelectionEnabled(false);
+ jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
+
+ SimplerFileFilter defaultFileFilter = new SimplerFileFilter(".iso", "CD/DVD image (*.iso)");
+ jfc.addChoosableFileFilter(defaultFileFilter);
+ jfc.addChoosableFileFilter(new SimplerFileFilter(".img", "Raw image (*.img)"));
+ jfc.addChoosableFileFilter(new SimplerFileFilter(".bin", "Binary file (*.bin)"));
+ jfc.addChoosableFileFilter(new SimplerFileFilter(".dmg", "Mac OS X read/write disk image (*.dmg)"));
+ jfc.setFileFilter(defaultFileFilter);
+ jfc.setDialogTitle("Select your output file");
+
+ if(inputFile != null) {
+ String name = inputFile.getName();
+ String defaultOutName = name;
+ int lastDotIndex = defaultOutName.lastIndexOf(".");
+ if(lastDotIndex >= 0) {
+ defaultOutName = defaultOutName.substring(0, lastDotIndex);
+ }
+ jfc.setSelectedFile(new File(inputFile.getParentFile(),
+ defaultOutName));
+ }
+
+ while(true) {
+ if(jfc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
+ File selectedFile = jfc.getSelectedFile();
+ final File saveFile;
+ FileFilter selectedFileFilter = jfc.getFileFilter();
+ if(selectedFileFilter instanceof SimplerFileFilter) {
+ SimplerFileFilter sff = (SimplerFileFilter) selectedFileFilter;
+ if(!selectedFile.getName().endsWith(sff.getExtension()))
+ saveFile = new File(selectedFile.getParentFile(), selectedFile.getName() + sff.getExtension());
+ else
+ saveFile = selectedFile;
+ }
+ else {
+ saveFile = selectedFile;
+ }
+
+ if(!saveFile.exists())
+ return saveFile;
+ else if(JOptionPane.showConfirmDialog(null, msgFileExists,
+ "Confirmation", JOptionPane.YES_NO_OPTION,
+ JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) {
+ return saveFile;
+ }
+ }
+ else
+ return null;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public char[] getPasswordFromUser() {
+ return PasswordDialog.showDialog(null, "Reading encrypted disk image...",
+ "You need to enter a password to unlock this disk image:");
+ }
+
+ public void setProgressFilenames(String inputFilename, String outputFilename) {
+ this.inputFilename = inputFilename;
+ this.outputFilename = outputFilename;
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/TextModeUI.java b/src/org/catacombae/dmgextractor/TextModeUI.java
new file mode 100644
index 0000000..0924e26
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/TextModeUI.java
@@ -0,0 +1,199 @@
+/*-
+ * Copyright (C) 2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+
+/**
+ * User interface implementation using plain old System.in and System.out for a
+ * text-based UI.
+ *
+ * @author Erik Larsson
+ */
+class TextModeUI extends BasicUI implements UserInterface {
+
+ /** A string containing 79 backspace characters. */
+ public static final String BACKSPACE79 =
+ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" +
+ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" +
+ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" +
+ "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";
+ private final PrintStream ps = System.out;
+ private final BufferedReader stdin =
+ new BufferedReader(new InputStreamReader(System.in));
+
+ public TextModeUI(boolean verbose) {
+ super(verbose);
+ }
+
+ /** {@inheritDoc} */
+ public boolean warning(String... messageLines) {
+ if(messageLines.length > 0) {
+ ps.println("WARNING: " + messageLines[0]);
+ for(int i = 1; i < messageLines.length; ++i)
+ ps.println(" " + messageLines[i]);
+ }
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ public void error(String... messageLines) {
+ if(messageLines.length > 0) {
+ ps.println("!------>ERROR: " + messageLines[0]);
+ for(int i = 1; i < messageLines.length; ++i)
+ ps.println(" " + messageLines[i]);
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void reportProgress(int progressPercentage) {
+ if(progressPercentage != previousPercentage) {
+ previousPercentage = progressPercentage;
+ ps.println("--->Progress: " + progressPercentage + "%");
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void reportFinished(boolean simulation, int errorsReported, int warningsReported, long totalExtractedSize) {
+ StringBuilder summary = new StringBuilder();
+ if(errorsReported != 0)
+ summary.append(errorsReported).append(" errors reported");
+ else
+ summary.append("No errors reported");
+
+ if(warningsReported != 0)
+ summary.append(" (").append(warningsReported).append(" warnings emitted)");
+
+ summary.append(".");
+
+ ps.println();
+ ps.println(summary.toString());
+ if(verbose)
+ ps.println("Size of extracted data: " + totalExtractedSize + " bytes");
+ }
+
+ /** {@inheritDoc} */
+ public boolean cancelSignaled() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public void displayMessage(String... messageLines) {
+ //ps.print(BACKSPACE79);
+ for(String s : messageLines)
+ ps.println(s);
+ if(messageLines.length < 1)
+ ps.println();
+ }
+
+ /** {@inheritDoc} */
+ public File getInputFileFromUser() {
+ /*
+ //String s = "";
+ while(true) {
+ printCurrentLine("Please specify the path to the dmg file to extract from: ");
+ File f = new File(stdin.readLine().trim());
+ while(!f.exists()) {
+ println("File does not exist!");
+ printCurrentLine("Please specify the path to the dmg file to extract from: ");
+ f = new File(stdin.readLine().trim());
+ }
+ return f;
+ }
+ */
+
+ // Text mode operation is not interactive anymore.
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public boolean getOutputConfirmationFromUser() {
+ /*
+ String s = "";
+ while(true) {
+ printCurrentLine("Do you want to specify an output file (y/n)? ");
+ s = stdin.readLine().trim();
+ if(s.equalsIgnoreCase("y"))
+ return true;
+ else if(s.equalsIgnoreCase("n"))
+ return false;
+ }
+ */
+
+ // Text mode operation is not interactive anymore.
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public File getOutputFileFromUser(File inputFile) {
+ /*
+ final String msg1 = "Please specify the path of the iso file to extract to: ";
+ final String msg2 = "The file already exists. Do you want to overwrite?";
+ while(true) {
+ printCurrentLine(msg1);
+ File f = new File(stdin.readLine().trim());
+ while(f.exists()) {
+ while(true) {
+ printCurrentLine(msg2 + " (y/n)? ");
+ String s = stdin.readLine().trim();
+ if(s.equalsIgnoreCase("y"))
+ return f;
+ else if(s.equalsIgnoreCase("n"))
+ break;
+ }
+ printCurrentLine(msg1);
+ f = new File(stdin.readLine().trim());
+ }
+ return f;
+ }
+ */
+
+ // Text mode operation is not interactive anymore.
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public char[] getPasswordFromUser() {
+ displayMessage("The disk image you are trying to extract is encrypted.");
+ try {
+ String reply = prompt("Please enter password: ");
+ if(reply != null)
+ return reply.toCharArray();
+ else
+ return null;
+ } catch(IOException ioe) {
+ ioe.printStackTrace();
+ return null;
+ }
+ }
+
+ private String prompt(String s) throws IOException {
+ ps.print(s);
+ return stdin.readLine();
+ }
+
+ public void setProgressFilenames(String inputFilename, String outputFilename) {
+ /*
+ * We currently don't act on this.
+ */
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/UserInterface.java b/src/org/catacombae/dmgextractor/UserInterface.java
new file mode 100644
index 0000000..829d5ba
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/UserInterface.java
@@ -0,0 +1,161 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor;
+
+import java.io.File;
+
+interface UserInterface {
+
+ public boolean cancelSignaled();
+
+ public void displayMessageVerbose(String... messageLines);
+
+ public File getInputFileFromUser();
+
+ public boolean getOutputConfirmationFromUser();
+
+ public File getOutputFileFromUser(File inputFile);
+
+ public char[] getPasswordFromUser();
+
+ /**
+ * If outputFilename is null, is would mean that a simulation is in progress.
+ * @param inputFilename
+ * @param outputFilename
+ */
+ public void setProgressFilenames(String inputFilename, String outputFilename);
+
+ /**
+ * Unconditionally displays a message to the user, to inform about certain
+ * things that happen in a program.
+ * This method may block execution, so use it sparsely.
+ *
+ * @param messageLines the message, line by line.
+ */
+ public void displayMessage(String... messageLines);
+
+ /**
+ * Issues a warning message to the user. Returns true if the process should
+ * proceed despite the warning, and false if the process should be
+ * aborted.
+ * This method may block execution, so use it sparsely.
+ *
+ * @param messageLines the message, line by line.
+ * @return true if the process should proceed despite the warning, and
+ * false if the process should be aborted.
+ */
+ public boolean warning(String... messageLines);
+
+ /**
+ * Issues an error message to the user.
+ * This method may block execution, so use it sparsely.
+ *
+ * @param messageLines the message, line by line.
+ */
+ public void error(String... messageLines);
+
+ /**
+ * This method should be called to bring up a summary after a finished
+ * extraction/simulation process.
+ *
+ * @param simulation set to true when the extraction was only simulated,
+ * false for a real extraction.
+ * @param errorsReported the number of errors encountered during the
+ * extraction.
+ * @param warningsReported the number of warnings encountered during the
+ * extraction.
+ * @param totalExtractedSize the outgoing data size, i.e. the data that was
+ * written (or should have been written, in the case of a simulation).
+ */
+ public void reportFinished(boolean simulation, int errorsReported, int warningsReported, long totalExtractedSize);
+
+ /**
+ * Sets the current progress value to a specified percentage. This method
+ * should not be used except for when the progress meter is to be reset or
+ * set forcibly to 100% when the process has completed.
+ *
+ * @param progressPercentage the percentage to set the progress to (range
+ * 0-100).
+ */
+ public void reportProgress(int progressPercentage);
+
+ /**
+ * Used in conjunction with addProgressRaw(...), and
+ * denotes the total length of the data on which we monitor progress.
+ *
+ * @param len the number of bytes of data that is the maximum value for
+ * raw progress.
+ */
+ public void setTotalProgressLength(long len);
+
+ /**
+ * Adds progress as raw bytes, instead of setting it as percentage. The
+ * percentage will automatically be calculated from the value previously
+ * set through setTotalProgressLength().
+ *
+ * @param value the byte value to add to the current progress.
+ */
+ public void addProgressRaw(long value);
+
+ static class NullUI extends BasicUI {
+
+ public NullUI() {
+ super(false);
+ }
+
+ public void reportProgress(int progressPercentage) {
+ }
+
+ public void displayMessage(String... messageLines) {
+ }
+
+ public boolean warning(String... messageLines) {
+ return true;
+ }
+
+ public void error(String... messageLines) {
+ }
+
+ public void reportFinished(boolean simulation, int errorsReported, int warningsReported, long totalExtractedSize) {
+ }
+
+ public boolean cancelSignaled() {
+ return false;
+ }
+
+ public File getInputFileFromUser() {
+ return null;
+ }
+
+ public boolean getOutputConfirmationFromUser() {
+ return false;
+ }
+
+ public File getOutputFileFromUser(File inputFile) {
+ return null;
+ }
+
+ public char[] getPasswordFromUser() {
+ return null;
+ }
+
+ public void setProgressFilenames(String inputFilename, String outputFilename) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/Util.java b/src/org/catacombae/dmgextractor/Util.java
new file mode 100644
index 0000000..6e261b9
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/Util.java
@@ -0,0 +1,22 @@
+/*-
+ * Copyright (C) 2006-2007 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor;
+
+public class Util extends org.catacombae.util.Util {
+ //public static int sectorSize = 0x800;
+}
diff --git a/src/org/catacombae/dmgextractor/io/.cvsignore b/src/org/catacombae/dmgextractor/io/.cvsignore
new file mode 100644
index 0000000..cd5dc09
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/io/.cvsignore
@@ -0,0 +1,5 @@
+*~
+*#
+*.class
+.DS_Store
+Thumbs.db
diff --git a/src/org/catacombae/dmgextractor/io/ByteCountInputStream.java b/src/org/catacombae/dmgextractor/io/ByteCountInputStream.java
new file mode 100644
index 0000000..edf4d80
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/io/ByteCountInputStream.java
@@ -0,0 +1,107 @@
+/*-
+ * Copyright (C) 2007-2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Filter stream that records information about how many bytes have been
+ * processed (either read or skipped).
+ */
+public class ByteCountInputStream extends InputStream {
+ private long bytePos = 0;
+ private InputStream is;
+
+ /**
+ * Creates a new ByteCountInputStream wrapping is.
+ *
+ * @param is the underlying InputStream.
+ */
+ public ByteCountInputStream(InputStream is) {
+ this.is = is;
+ }
+
+ /**
+ * Returns the number of bytes that have been read since the creation
+ * of this filter stream.
+ *
+ * @return the number of bytes that have been read.
+ */
+ public long getBytesRead() { return bytePos; }
+
+ /** {@inheritDoc} */
+ @Override
+ public int available() throws IOException { return is.available(); }
+
+ /** {@inheritDoc} */
+ @Override
+ public void close() throws IOException { is.close(); }
+
+ /** {@inheritDoc} */
+ @Override
+ public void mark(int readLimit) { throw new UnsupportedOperationException("Mark/reset not supported"); }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean markSupported() { return false; }
+
+ /** {@inheritDoc} */
+ @Override
+ public int read() throws IOException {
+ //System.out.println("read();");
+ int res = is.read();
+ if(res > 0)
+ ++bytePos;
+ return res;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int read(byte[] b) throws IOException {
+ //System.out.println("read(b.length=" + b.length + ");");
+ int res = is.read(b);
+ if(res > 0)
+ bytePos += res;
+ return res;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ //System.out.println("read(b.length=" + b.length + ", " + off + ", " + len + ");");
+ int res = is.read(b, off, len);
+ if(res > 0)
+ bytePos += res;
+ return res;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void reset() throws IOException { throw new UnsupportedOperationException("Mark/reset not supported"); }
+
+ /** {@inheritDoc} */
+ @Override
+ public long skip(long n) throws IOException {
+ System.out.println("skip(" + n + ");");
+ long res = is.skip(n);
+ if(res > 0)
+ bytePos += res;
+ return res;
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/io/CharArrayBuilder.java b/src/org/catacombae/dmgextractor/io/CharArrayBuilder.java
new file mode 100644
index 0000000..44750bc
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/io/CharArrayBuilder.java
@@ -0,0 +1,54 @@
+/*-
+ * Copyright (C) 2007 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor.io;
+// /* unused... remnants of old ideas */
+public class CharArrayBuilder {
+ /*
+ private int growConstant;
+ private char[] backingArray;
+ private int pos;
+
+ public CharArrayBuilder() { this(512); }
+ public CharArrayBuilder(int capacity) {
+ growConstant = capacity;
+ backingArray = new char[capacity];
+ pos = 0;
+ }
+
+ public void put(char c) {
+ if(pos == backingArray.length)
+ growArray();
+ backingArray[pos++] = c;
+ }
+
+ public byte[] clearBuffer() {
+ byte[] result = new byte[pos];
+ System.arraycopy(backingArray, 0, result, 0, result.length);
+ backingArray = new byte[growConstant];
+ pos = 0;
+ return result;
+ }
+
+ private void growArray() {
+ byte[] oldArray = backingArray;
+ byte[] newArray = new byte[backingArray.length+growConstant];
+ System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
+ backingArray = newArray;
+ }
+ */
+}
diff --git a/src/org/catacombae/dmgextractor/io/CharByCharReader.java b/src/org/catacombae/dmgextractor/io/CharByCharReader.java
new file mode 100644
index 0000000..662ceca
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/io/CharByCharReader.java
@@ -0,0 +1,72 @@
+/*-
+ * Copyright (C) 2007 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor.io;
+import java.io.*;
+import java.nio.*;
+import java.nio.charset.*;
+
+public class CharByCharReader extends Reader {
+ private InputStream is;
+ private Charset cs;
+ private CharsetDecoder cdec;
+ private byte[] tempBuffer;
+ private int tempBufferPtr = 0;
+
+ //private CharArrayBuilder cab = new CharArrayBuilder();
+
+ /* The assumption we make here is that a number of bytes define a Unicode character. */
+ public CharByCharReader(InputStream is, Charset cs) {
+ this.is = is;
+ this.cs = cs;
+ this.cdec = cs.newDecoder();
+ tempBuffer = new byte[(int)Math.ceil(cdec.maxCharsPerByte())];
+ }
+
+ public void close() throws IOException {}
+ public int read(char[] cbuf, int off, int len) throws IOException {
+ int curByte;
+ int charsRead = 0;
+
+ while(charsRead < len) {
+ while(true) {
+ curByte = is.read();
+ if(curByte >= 0) {
+ tempBuffer[tempBufferPtr++] = (byte)curByte;
+ ByteBuffer bb = ByteBuffer.wrap(tempBuffer, 0, tempBufferPtr);
+ CharBuffer out = CharBuffer.allocate(1);
+
+ CoderResult res = cdec.decode(bb, out, true);
+ if(!res.isError()) {
+ cbuf[off+charsRead] = out.get(0);//cab.put(out.get(0));
+ break;
+ }
+ else if(tempBufferPtr == tempBuffer.length) {
+ System.err.println(res.toString());
+ throw new RuntimeException("error while decoding");
+ }
+ }
+ else
+ return charsRead;
+ }
+ ++charsRead;
+ tempBufferPtr = 0;
+ }
+
+ return charsRead;
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/io/ConcatenatedReader.java b/src/org/catacombae/dmgextractor/io/ConcatenatedReader.java
new file mode 100644
index 0000000..a12fefa
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/io/ConcatenatedReader.java
@@ -0,0 +1,51 @@
+/*-
+ * Copyright (C) 2007 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor.io;
+import java.io.*;
+
+public class ConcatenatedReader extends Reader {
+ private final Reader[] sources;
+ private int currentSource;
+ private long charPos = 0;
+ public ConcatenatedReader(Reader[] sources) {
+ this.sources = sources;
+ this.currentSource = 0;
+ }
+ public void close() throws IOException {
+ for(Reader r : sources)
+ r.close();
+ }
+ public int read(char[] cbuf, int off, int len) throws IOException {
+ int bytesRead = 0;
+ while(bytesRead < len) {
+ Reader currentReader = sources[currentSource];
+ int currentRead = currentReader.read(cbuf, off+bytesRead, len-bytesRead);
+ while(currentRead != -1 && bytesRead < len) {
+ bytesRead += currentRead;
+ currentRead = currentReader.read(cbuf, off+bytesRead, len-bytesRead);
+ }
+ if(currentRead == -1) {
+ if(currentSource+1 < sources.length)
+ ++currentSource;
+ else
+ break; // There wasn't enough data
+ }
+ }
+ return bytesRead;
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/io/RandomAccessInputStream.java b/src/org/catacombae/dmgextractor/io/RandomAccessInputStream.java
new file mode 100644
index 0000000..fef1136
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/io/RandomAccessInputStream.java
@@ -0,0 +1,141 @@
+/*-
+ * Copyright (C) 2007-2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor.io;
+
+import java.io.*;
+import org.catacombae.io.RuntimeIOException;
+
+/**
+ * This class subclasses java.io.InputStream to transform a part of a RandomAccessStream
+ * into an ordinary InputStream.
+ */
+public class RandomAccessInputStream extends InputStream {
+ private final SynchronizedRandomAccessStream ras;
+ private long streamPos;
+ private final long endPos;
+
+ /** length == -1 means length == ras.length() */
+ public RandomAccessInputStream(SynchronizedRandomAccessStream ras, long offset, long length) {
+ try {
+ long rasLength = ras.length();
+ if(length == -1)
+ length = rasLength;
+ if(offset > rasLength || offset < 0)
+ throw new IllegalArgumentException("offset out of bounds (offset=" +
+ offset + " length=" + length + ")");
+ if(length > rasLength-offset || length < 0)
+ throw new IllegalArgumentException("length out of bounds (offset=" +
+ offset + " length=" + length + ")");
+ this.ras = ras;
+ this.streamPos = offset;
+ this.endPos = offset+length;
+ } catch(Exception e) { throw new RuntimeException(e); }
+ }
+
+ /**
+ * Constructs an InputStream that covers the data contained in the underlying
+ * RandomAccessStream, from the beginning, to the end. */
+ public RandomAccessInputStream(SynchronizedRandomAccessStream ras) {
+ this(ras, 0, -1);
+ }
+
+ @Override
+ public int available() throws IOException {
+ long remaining = endPos - streamPos;
+ if(remaining > Integer.MAX_VALUE)
+ return Integer.MAX_VALUE;
+ else if(remaining < Integer.MIN_VALUE)
+ return Integer.MIN_VALUE;
+ else
+ return (int)remaining;
+ }
+
+ /** Does not do anything. The underlying SynchronizedRandomAccessStream might be in use by others. */
+ @Override
+ public void close() throws IOException {}
+
+ /** Not supported, not implemented (not needed). */
+ @Override
+ public void mark(int readlimit) {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ /** Not supported, not implemented (not needed). */
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+
+ @Override
+ public int read() throws IOException {
+ final byte[] tmp = new byte[1];
+ int res = read(tmp, 0, 1);
+ if(res == 1)
+ return tmp[0] & 0xFF;
+ else
+ return -1;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ try {
+ int bytesToRead = (int)((streamPos+len > endPos)?endPos-streamPos:len);
+ if(bytesToRead == 0)
+ return -1;
+ int res = ras.readFrom(streamPos, b, off, bytesToRead);
+ if(res > 0) streamPos += res;
+ return res;
+ } catch(RuntimeIOException ex) {
+ IOException ioe = ex.getIOCause();
+ if(ioe != null) {
+ ex.printStackTrace();
+ throw ioe;
+ }
+ else
+ throw ex;
+ }
+ }
+
+ /** Not supported, not implemented (not needed). */
+ @Override
+ public void reset() throws IOException {
+ throw new UnsupportedOperationException("Not supported");
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ try {
+ long res = ras.skipFrom(streamPos, n);
+ if(res > 0) streamPos += res;
+ return res;
+ } catch(RuntimeIOException ex) {
+ IOException ioe = ex.getIOCause();
+ if(ioe != null) {
+ ex.printStackTrace();
+ throw ioe;
+ }
+ else
+ throw ex;
+ }
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/io/ReaderInputStream.java b/src/org/catacombae/dmgextractor/io/ReaderInputStream.java
new file mode 100644
index 0000000..ee8c310
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/io/ReaderInputStream.java
@@ -0,0 +1,182 @@
+/*-
+ * Copyright (C) 2007-2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor.io;
+
+import java.io.*;
+import java.nio.charset.*;
+
+public class ReaderInputStream extends InputStream {
+ private Reader r;
+ private CharsetEncoder encoder;
+ private byte[] chardata;
+ private int remainingChardata = 0;
+ private LousyByteArrayStream lbas;
+ private OutputStreamWriter osw;
+
+ private static class LousyByteArrayStream extends OutputStream {
+ private final byte[] buffer;
+ private int bufpos = 0;
+ public LousyByteArrayStream(int buflen) {
+ //System.err.println("Creating a LousyByteArrayStream with length " + buflen);
+ buffer = new byte[buflen];
+ }
+ public void write(int b) {
+ buffer[bufpos++] = (byte)b;
+ }
+ public int reset(byte[] chardata) {
+ int length = bufpos;
+ System.arraycopy(buffer, 0, chardata, 0, length);
+ bufpos = 0;
+ return length;
+ }
+ }
+
+ public ReaderInputStream(Reader r, Charset c) {
+ this.r = r;
+ this.encoder = c.newEncoder();
+ //System.err.println("Creating a ReaderInputStream. encoder.maxBytesPerChar() == " + encoder.maxBytesPerChar());
+ this.chardata = new byte[(int)Math.ceil(encoder.maxBytesPerChar())];
+
+ lbas = new LousyByteArrayStream(chardata.length);
+ osw = new OutputStreamWriter(lbas, encoder);
+
+ }
+
+ public int read() throws IOException {
+ byte[] b = new byte[1];
+ int res = read(b, 0, 1);
+ if(res == 1)
+ return b[0] & 0xFF;
+ else
+ return -1;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException { return read(b, 0, b.length); }
+
+ /* Säg då att vi skippar 204 bytes. Vad händer? När vi går in i read(3) är remainingChardata = 0,
+ och off = 0, len = 204. b.length = 4096. Alltså kommer ingen av de 4 första if-satserna vara
+ giltiga...
+ I vilken situation kan vi ha läst in mindre data i b än returvärdet?
+ */
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+// System.err.println("ReaderInputStream.read(b.length=" + b.length + ", " + off + ", " + len + ")");
+ if(len < 0) throw new IllegalArgumentException();
+ if(len == 0) return 0;
+
+ int originalOffset = off;
+ int endPos = off+len;
+
+ if(remainingChardata > 0) {
+// System.err.println("Remaining chardata! length=" + remainingChardata);
+ int bytesToCopy = remainingChardata>len?len:remainingChardata;
+// System.err.println("bytesToCopy=" + bytesToCopy);
+ System.arraycopy(chardata, 0, b, off, bytesToCopy);
+ off += bytesToCopy;
+ remainingChardata -= bytesToCopy;
+ }
+ if(off == endPos) {
+// System.err.println("(1)returning with " + (off-originalOffset) + " from ReaderInputStream.read");
+ return off-originalOffset;
+ }
+
+// int baba = 3;
+ while(off < endPos) {
+// if(baba > 0) {
+// System.err.println(" looping... off==" + off + " endPos=" + endPos);
+// --baba;
+// }
+// else
+// baba = Integer.MIN_VALUE;
+ int cur = r.read();
+ if(cur < 0)
+ break;
+
+ if(Character.isHighSurrogate((char)cur)) {
+ int lowSurrogate = r.read(); // UTF-16 is a crap encoding for a programming language
+
+ if(lowSurrogate < 0)
+ throw new IOException("Too lazy to handle this error...");
+ else if(!Character.isSurrogatePair((char)cur, (char)lowSurrogate))
+ throw new IOException("Encountered a high surrogate without a matching low surrogate... oh crap.");
+
+ cur = Character.toCodePoint((char)cur, (char)lowSurrogate);
+ }
+ char[] charArray = Character.toChars(cur);
+ String charString = new String(charArray, 0, charArray.length);
+
+ // Now we need to write
+ //System.out.println("Writing codepoint: 0x" + Util.toHexStringBE(cur));
+
+ osw.write(charString);
+ osw.flush();
+
+ //System.out.println("Resetting...");
+ int chardataLength = lbas.reset(chardata);
+ int remainingLength = endPos-off;
+ int bytesToCopy = (chardataLength > remainingLength)?remainingLength:chardataLength;
+ System.arraycopy(chardata, 0, b, off, bytesToCopy);
+ off += bytesToCopy;
+
+ if(chardataLength > remainingLength) {
+ remainingChardata = chardataLength-remainingLength;
+ System.arraycopy(chardata, bytesToCopy, chardata, 0, remainingChardata);
+ }
+// if(baba >= 0) {
+// System.err.println(" chardataLength=" + chardataLength + " remainingLength=" + remainingLength);
+// System.err.println(" bytesToCopy=" + bytesToCopy + " off=" + off);
+// }
+ }
+ int bytesRead = off-originalOffset;
+ if(off < endPos && bytesRead == 0) { // We have a break due to end of stream
+ //System.err.println("(3)returning -1 due to end of stream");
+ return -1;
+ }
+ else {
+ //System.err.println("(2)returning with " + bytesRead + " from ReaderInputStream.read");
+ return bytesRead;
+ }
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ System.err.println("ReaderInputStream.skip(" + n + ")");
+ byte[] skipBuffer = new byte[4096];
+ long bytesSkipped = 0;
+ while(bytesSkipped < n) {
+ //System.out.println(" Looping...");
+ long remainingBytes = n-bytesSkipped;
+ int bytesToSkip = (int)(skipBuffer.length 0) {
+ //System.out.println(" Actually skipped " + res + " bytes.");
+ //System.out.println(" This is the data skipped: " + Util.byteArrayToHexString(skipBuffer, 0, res));
+ bytesSkipped += res;
+ }
+ else {
+ //System.out.println("encountered EOF!");
+ break; // Seems we can't skip all bytes
+ }
+ }
+ //debug
+ //System.out.println(" bytesSkipped=" + bytesSkipped + " n=" + n);
+ return bytesSkipped; //super.skip(n);
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/io/SynchronizedRandomAccessStream.java b/src/org/catacombae/dmgextractor/io/SynchronizedRandomAccessStream.java
new file mode 100644
index 0000000..1c9ad6a
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/io/SynchronizedRandomAccessStream.java
@@ -0,0 +1,100 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor.io;
+
+import java.io.*;
+import org.catacombae.io.BasicReadableRandomAccessStream;
+import org.catacombae.io.ReadableRandomAccessStream;
+import org.catacombae.io.RuntimeIOException;
+
+/**
+ * This class adds concurrency safety to a random access stream. It includes a seek+read
+ * atomic operation. All operations on this object is synchronized on its own monitor.
+ */
+public class SynchronizedRandomAccessStream extends BasicReadableRandomAccessStream {
+ /** The underlying stream. */
+ private ReadableRandomAccessStream ras;
+
+ public SynchronizedRandomAccessStream(ReadableRandomAccessStream ras) {
+ this.ras = ras;
+ }
+
+ /** Atomic seek+read. */
+ public synchronized int readFrom(long pos, byte[] b, int off, int len) throws RuntimeIOException {
+ if(getFilePointer() != pos)
+ seek(pos);
+ return read(b, off, len);
+ }
+
+ /** Atomic seek+skip. */
+ public synchronized long skipFrom(final long pos, final long length) throws RuntimeIOException {
+ long streamLength = length();
+ long newPos = pos+length;
+
+ if(newPos > streamLength) {
+ seek(streamLength);
+ return streamLength-pos;
+ }
+ else {
+ seek(newPos);
+ return length;
+ }
+ }
+
+ /** Atomic length() - getFilePointer(). */
+ public synchronized long remainingLength() throws RuntimeIOException {
+ return length()-getFilePointer();
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public synchronized void close() throws RuntimeIOException {
+ ras.close();
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public synchronized long getFilePointer() throws RuntimeIOException {
+ return ras.getFilePointer();
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public synchronized long length() throws RuntimeIOException {
+ return ras.length();
+ }
+
+ /** @see java.io.RandomAccessFile */
+ @Override
+ public synchronized int read() throws RuntimeIOException {
+ return ras.read();
+ }
+
+ /** @see java.io.RandomAccessFile */
+ @Override
+ public synchronized int read(byte[] b) throws RuntimeIOException {
+ return ras.read(b);
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public synchronized int read(byte[] b, int off, int len) throws RuntimeIOException {
+ return ras.read(b, off, len);
+ }
+
+ /** @see java.io.RandomAccessFile */
+ public synchronized void seek(long pos) throws RuntimeIOException {
+ ras.seek(pos);
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/ui/PasswordDialog.java b/src/org/catacombae/dmgextractor/ui/PasswordDialog.java
new file mode 100644
index 0000000..005d422
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/ui/PasswordDialog.java
@@ -0,0 +1,101 @@
+/*-
+ * Copyright (C) 2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor.ui;
+
+import java.awt.Component;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.JDialog;
+import javax.swing.JOptionPane;
+
+/**
+ *
+ * @author Erik Larsson
+ */
+public class PasswordDialog extends JDialog {
+ private final PasswordPanel passwordPanel;
+ private char[] password = null;
+
+ private PasswordDialog(Frame owner, boolean modal, String dialogTitle, String messageLine) {
+ super(owner, dialogTitle, modal);
+
+ ActionListener okButtonListener = new ActionListener() {
+
+ public void actionPerformed(ActionEvent e) {
+ actionOkButtonClicked();
+ }
+
+ };
+ ActionListener cancelButtonListener = new ActionListener() {
+
+ public void actionPerformed(ActionEvent e) {
+ actionCancelButtonClicked();
+ }
+
+ };
+ this.passwordPanel = new PasswordPanel(messageLine, okButtonListener, cancelButtonListener);
+ add(passwordPanel);
+ }
+ private void actionOkButtonClicked() {
+ password = passwordPanel.getPassword();
+ /* It is important to call dispose() to dispose of AWT resources, allowing the calling
+ * thread to exit the program at will. Otherwise, an active AWT thread will block. */
+ dispose();
+ }
+
+ private void actionCancelButtonClicked() {
+ password = null;
+ dispose();
+ }
+ private char[] getPassword() {
+ return password;
+ }
+
+ /**
+ * Shows a dialog, modal to owner which requests a password from the user, halts
+ * execution until the password has been entered, and then returns the entered password, or
+ * null depending on whether the user clicked the "Ok" or the "Cancel" button.
+ *
+ * @param parentComponent this dialog's parent component.
+ * @param dialogTitle the title of the dialog, printed in the window header.
+ * @param messageLine the one line message to display above the password field, for example
+ * "Please enter password:" or anything similar.
+ * @return the entered password, or null if the user canceled the dialog.
+ */
+ public static char[] showDialog(Component parentComponent, String dialogTitle, String messageLine) {
+ PasswordDialog pd = new PasswordDialog(JOptionPane.getFrameForComponent(parentComponent), true, dialogTitle, messageLine);
+ pd.pack();
+ pd.setResizable(false);
+ pd.setLocationRelativeTo(null);
+ pd.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+ pd.setVisible(true);
+ return pd.getPassword();
+ }
+
+ /*
+ public static void main(String[] args) {
+ char[] pwd = showDialog(null, "hej", "apa");
+ //String pwd = JOptionPane.showInputDialog(null, "apa", "hej");
+ if(pwd != null)
+ System.out.println("Password: \"" + new String(pwd) + "\"");
+ else
+ System.out.println("User canceled dialog.");
+ }
+ */
+}
diff --git a/src/org/catacombae/dmgextractor/ui/PasswordPanel.form b/src/org/catacombae/dmgextractor/ui/PasswordPanel.form
new file mode 100644
index 0000000..c987d92
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/ui/PasswordPanel.form
@@ -0,0 +1,71 @@
+
+
+
diff --git a/src/org/catacombae/dmgextractor/ui/PasswordPanel.java b/src/org/catacombae/dmgextractor/ui/PasswordPanel.java
new file mode 100644
index 0000000..4a1d822
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/ui/PasswordPanel.java
@@ -0,0 +1,118 @@
+/*-
+ * Copyright (C) 2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor.ui;
+
+import java.awt.event.ActionListener;
+//import javax.swing.JFrame;
+
+/**
+ * The UI design component for PasswordDialog.
+ *
+ * @author Erik Larsson
+ */
+class PasswordPanel extends javax.swing.JPanel {
+
+ /** Creates new form PasswordPanel */
+ public PasswordPanel(String messageLine, ActionListener okButtonListener, ActionListener cancelButtonListener) {
+ this();
+ instructionsLabel.setText(messageLine);
+ passwordField.addActionListener(okButtonListener);
+ okButton.addActionListener(okButtonListener);
+ cancelButton.addActionListener(cancelButtonListener);
+ }
+
+ private PasswordPanel() {
+ initComponents();
+ }
+
+ char[] getPassword() {
+ return passwordField.getPassword();
+ }
+
+ /*
+ public static void main(String[] args) {
+ JFrame jp = new JFrame();
+ PasswordPanel pp = new PasswordPanel();
+ pp.instructionsLabel.setText("tada\nYADA\nBADA");
+ jp.add(pp);
+ jp.pack();
+ jp.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ jp.setVisible(true);
+ }
+ */
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ passwordField = new javax.swing.JPasswordField();
+ instructionsLabel = new javax.swing.JLabel();
+ buttonLayoutHelper = new javax.swing.JPanel();
+ okButton = new javax.swing.JButton();
+ cancelButton = new javax.swing.JButton();
+
+ instructionsLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
+ instructionsLabel.setText("You need to enter a password to unlock this disk image:");
+
+ buttonLayoutHelper.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 5, 0));
+
+ okButton.setText("OK");
+ buttonLayoutHelper.add(okButton);
+
+ cancelButton.setText("Cancel");
+ buttonLayoutHelper.add(cancelButton);
+
+ org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(layout.createSequentialGroup()
+ .addContainerGap()
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(passwordField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 306, Short.MAX_VALUE)
+ .add(instructionsLabel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 306, Short.MAX_VALUE)
+ .add(buttonLayoutHelper, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 306, Short.MAX_VALUE))
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(layout.createSequentialGroup()
+ .addContainerGap()
+ .add(instructionsLabel)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(passwordField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.UNRELATED)
+ .add(buttonLayoutHelper, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
+ .addContainerGap(org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+ }// //GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JPanel buttonLayoutHelper;
+ private javax.swing.JButton cancelButton;
+ private javax.swing.JLabel instructionsLabel;
+ private javax.swing.JButton okButton;
+ private javax.swing.JPasswordField passwordField;
+ // End of variables declaration//GEN-END:variables
+ }
diff --git a/src/org/catacombae/dmgextractor/utils/.cvsignore b/src/org/catacombae/dmgextractor/utils/.cvsignore
new file mode 100644
index 0000000..cd5dc09
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/.cvsignore
@@ -0,0 +1,5 @@
+*~
+*#
+*.class
+.DS_Store
+Thumbs.db
diff --git a/src/org/catacombae/dmgextractor/utils/DMGInfo.java b/src/org/catacombae/dmgextractor/utils/DMGInfo.java
new file mode 100644
index 0000000..92dd240
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/DMGInfo.java
@@ -0,0 +1,175 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+package org.catacombae.dmgextractor.utils;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+
+public class DMGInfo {
+
+ public static void main(String[] args) throws IOException {
+ RandomAccessFile inRaf = new RandomAccessFile(args[0], "r");
+
+ // Check opening signature "koly"
+ inRaf.seek(inRaf.length() - 512);
+ byte[] koly = new byte[4];
+ inRaf.readFully(koly);
+ String kolySignature = new String(koly, "US-ASCII");
+ if(!kolySignature.equals("koly"))
+ System.out.println("ERROR: Signature incorrect. Found \"" + kolySignature + "\" instead of \"koly\".");
+ else
+ System.out.println("\"koly\" signature OK.");
+
+ // Read partition list start location 1 and end location
+ inRaf.seek(inRaf.length() - 0x1E0);
+
+ // -0x1E0: address to plist xml structure (8 bytes)
+ long plistAddress1 = inRaf.readLong();
+ System.out.println("Address to plist: 0x" + Long.toHexString(plistAddress1));
+
+
+ // -0x1D8: address to end of plist xml structure (8 bytes)
+ long plistEndAddress = inRaf.readLong();
+ System.out.println("Address to end of plist: 0x" + Long.toHexString(plistEndAddress));
+ System.out.println(" Implication: plist size = " + (plistEndAddress - plistAddress1) + " B");
+
+
+ long unknown_0x1D0 = inRaf.readLong();
+
+
+ long unknown_0x1C8 = inRaf.readLong();
+ if(unknown_0x1C8 != 0x0000000100000001L)
+ System.out.println("Assertion failed! unknown_0x1C8 == 0x" +
+ Long.toHexString(unknown_0x1C8) + " and not 0x0000000100000001");
+
+
+ long unknown_0x1C0 = inRaf.readLong();
+
+
+ long unknown_0x1B8 = inRaf.readLong();
+ System.out.println("Some kind of signature? Value: 0x" + Long.toHexString(unknown_0x1B8));
+
+
+ long unknown_0x1B0 = inRaf.readLong();
+ if(unknown_0x1B0 != 0x0000000200000020L)
+ System.out.println("Assertion failed! unknown_0x1B0 == 0x" +
+ Long.toHexString(unknown_0x1B0) + " and not 0x0000000200000020");
+
+
+ int unknown_0x1A8 = inRaf.readInt();
+
+
+ int unknown_0x1A4 = inRaf.readInt();
+ System.out.println("Some kind of unit size? Value: 0x" +
+ Integer.toHexString(unknown_0x1A4) + " / " + unknown_0x1A4);
+
+
+ // Unknown chunk of data (120 bytes)
+ byte[] unknown_0x1A0 = new byte[120];
+ inRaf.readFully(unknown_0x1A0);
+
+
+ // -0x128: address to beginning of plist xml structure (second occurrence) (8 bytes)
+ long plistAddress2 = inRaf.readLong();
+ System.out.println("Address to plist (2): 0x" + Long.toHexString(plistAddress2));
+
+
+ // -0x120: size of plist xml structure (8 bytes)
+ long plistSize = inRaf.readLong();
+ System.out.println("plist size: " + plistSize + " B");
+
+
+ // Unknown chunk of data (120 bytes)
+ byte[] unknown_0x118 = new byte[120];
+ inRaf.readFully(unknown_0x118);
+
+
+ // -0x0A0: Checksum type identifier (4 bytes)
+ System.out.print("Checksum type");
+ int cs_type = inRaf.readInt();
+ if(cs_type == 0x00000002)
+ System.out.println(": CRC-32");
+ else if(cs_type == 0x00000004)
+ System.out.println(": MD5");
+ else
+ System.out.println(" unknown! Data: 0x" + Integer.toHexString(cs_type));
+
+
+ // -0x09C: Length of checksum in bits (4 bytes)
+ int cs_length = inRaf.readInt();
+ System.out.println("Checksum length: " + cs_length + " bits");
+
+
+ // -0x098: Checksum ((cs_length/8) bytes)
+ byte[] checksum = new byte[cs_length / 8];
+ inRaf.readFully(checksum);
+ System.out.println("Checksum: 0x" + byteArrayToHexString(checksum).toUpperCase());
+
+ /*
+ if(unknown_0x != 0xL)
+ System.out.println("Assertion failed! unknown_0x == 0x" + Long.toHexString(unknown_0x) + " and not 0xL");
+ */
+ }
+
+ public static String byteArrayToHexString(byte[] array) {
+ StringBuilder result = new StringBuilder();
+ for(byte b : array) {
+ String s = Integer.toHexString(b & 0xFF);
+ if(s.length() == 1)
+ s = "0" + s;
+ result.append(s);
+ }
+ return result.toString();
+ }
+}
+
+class DMGInfoFrame extends JFrame {
+
+ private JTabbedPane mainPane;
+
+ public DMGInfoFrame() {
+ super("DMGInfo");
+
+ mainPane = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
+
+ StatisticsPanel statisticsPanel;
+ statisticsPanel = new StatisticsPanel();
+ //mainPane.addTab(statisticsPanel, "Statistics");
+ }
+}
+
+class StatisticsPanel extends JPanel {
+
+ JPanel blocktypeCountPanel;
+
+ public StatisticsPanel(/*DMGFile dmgFile*/) {
+ }
+}
+
+/*
+class DMGFile extends RandomAccessFile {
+public DMGFile(File file, String mode) {
+super(file, mode);
+}
+public DMGFile(String name, String mode) {
+super(name, mode);
+}
+}
+ */
diff --git a/src/org/catacombae/dmgextractor/utils/DMGInfoWindow.java b/src/org/catacombae/dmgextractor/utils/DMGInfoWindow.java
new file mode 100644
index 0000000..7cb97e1
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/DMGInfoWindow.java
@@ -0,0 +1,48 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor.utils;
+
+import java.awt.BorderLayout;
+import javax.swing.JFrame;
+import net.iharder.dnd.FileDrop;
+import org.catacombae.dmgextractor.utils.gui.DMGInfoPanel;
+
+public class DMGInfoWindow extends JFrame {
+ private DMGInfoPanel infoPanel;
+
+ public DMGInfoWindow() {
+ infoPanel = new DMGInfoPanel();
+ add(infoPanel, BorderLayout.CENTER);
+
+ // Register handler for file drag&drop events
+ new FileDrop(this, new FileDrop.Listener() {
+
+ public void filesDropped(java.io.File[] files) {
+ if(files.length > 0);//loadFile(files[0]);
+ }
+ });
+
+ pack();
+ setLocationRelativeTo(null);
+
+ }
+
+ public static void main(String[] args) {
+ new DMGInfoWindow().setVisible(true);
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/utils/DMGMetadata.java b/src/org/catacombae/dmgextractor/utils/DMGMetadata.java
new file mode 100644
index 0000000..7f610d4
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/DMGMetadata.java
@@ -0,0 +1,397 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+package org.catacombae.dmgextractor.utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.util.LinkedList;
+
+public class DMGMetadata {
+
+ public static final long PLIST_ADDRESS_1 = 0x1E0;
+ public static final long PLIST_ADDRESS_2 = 0x128;
+ public final byte[] rawData;
+ public final byte[] plistXmlData;
+ public final byte[] unknown1_256;
+ public PartitionBlockList[] blockLists;
+ public final byte[] unknown2_12;
+ public APMPartition[] partitions;
+ public final byte[] unknown3_unknown;
+ public final byte[] koly;
+
+ public DMGMetadata(RandomAccessFile dmgFile) throws IOException {
+ dmgFile.seek(dmgFile.length() - PLIST_ADDRESS_1);
+ long plistBegin1 = dmgFile.readLong();
+ long plistEnd = dmgFile.readLong();
+ dmgFile.seek(dmgFile.length() - PLIST_ADDRESS_2);
+ long plistBegin2 = dmgFile.readLong();
+ long plistSize = dmgFile.readLong();
+
+ rawData = new byte[(int) (dmgFile.length() - plistBegin1)];
+ dmgFile.seek(plistBegin1);
+ dmgFile.readFully(rawData);
+
+ plistXmlData = new byte[(int) plistSize];
+ dmgFile.seek(plistBegin1);
+ dmgFile.readFully(plistXmlData);
+
+ unknown1_256 = new byte[256];
+ dmgFile.readFully(unknown1_256);
+
+ LinkedList blockListList = new LinkedList();
+ int length = dmgFile.readInt();
+ byte[] fourcc = new byte[4];
+ dmgFile.readFully(fourcc);
+ String fourccString = new String(fourcc, "US-ASCII");
+ dmgFile.seek(dmgFile.getFilePointer() - 4);
+ while(fourccString.equals("mish")) {
+ blockListList.add(new PartitionBlockList(dmgFile, length));
+ length = dmgFile.readInt();
+ dmgFile.readFully(fourcc);
+ fourccString = new String(fourcc, "US-ASCII");
+ dmgFile.seek(dmgFile.getFilePointer() - 4);
+ }
+ blockLists = blockListList.toArray(new PartitionBlockList[blockListList.size()]);
+
+ unknown2_12 = new byte[12];
+ dmgFile.readFully(unknown2_12);
+
+ LinkedList partitionList = new LinkedList();
+ byte[] currentPartitionEntry = new byte[0x200];
+ dmgFile.readFully(currentPartitionEntry);
+ byte[] pmSig = new byte[2];
+ pmSig[0] = currentPartitionEntry[0];
+ pmSig[1] = currentPartitionEntry[1];
+ while(new String(pmSig, "US-ASCII").equals("PM")) {
+ partitionList.addLast(new APMPartition(currentPartitionEntry));
+ dmgFile.readFully(currentPartitionEntry);
+ pmSig[0] = currentPartitionEntry[0];
+ pmSig[1] = currentPartitionEntry[1];
+ }
+ while(onlyZeros(currentPartitionEntry))
+ dmgFile.readFully(currentPartitionEntry);
+ partitions = partitionList.toArray(new APMPartition[partitionList.size()]);
+
+ unknown3_unknown = new byte[(int) (dmgFile.length() - dmgFile.getFilePointer() - 512)];
+ dmgFile.readFully(unknown3_unknown);
+
+ koly = new byte[512];
+ dmgFile.seek(dmgFile.length() - koly.length);
+ dmgFile.readFully(koly);
+
+ if(dmgFile.getFilePointer() != dmgFile.length())
+ System.out.println("MISCALCULATION! FP=" + dmgFile.getFilePointer() + " LENGTH=" + dmgFile.length());
+ }
+
+ public void printInfo(PrintStream ps) {
+ ps.println("block list:");
+ for(PartitionBlockList pbl : blockLists)
+ pbl.printInfo(ps);
+ ps.println("partitions:");
+ for(APMPartition ap : partitions)
+ ap.printPartitionInfo(ps);
+ }
+
+ private static boolean onlyZeros(byte[] array) {
+ for(int i = 0; i < array.length; ++i) {
+ if(array[i] != 0)
+ return false;
+ }
+ return true;
+ }
+
+ public static class PartitionBlockList {
+
+ public final byte[] header = new byte[0xCC];
+ public final BlockDescriptor[] descriptors;
+
+ public PartitionBlockList(byte[] entryData) throws IOException {
+ this(new DataInputStream(new ByteArrayInputStream(entryData)), entryData.length);
+ }
+
+ public PartitionBlockList(DataInput di, int length) throws IOException {
+ int position = 0;
+ di.readFully(header);
+ position += header.length;
+ LinkedList descs = new LinkedList();
+ while(position < length) {
+ descs.addLast(new BlockDescriptor(di));
+ position += 0x28;
+ }
+ descriptors = descs.toArray(new BlockDescriptor[descs.size()]);
+ }
+
+ public void printInfo(PrintStream ps) {
+ for(BlockDescriptor bd : descriptors)
+ ps.println(bd.toString());
+ }
+ }
+
+ public static class BlockDescriptor {
+ // Known block types
+
+ public static final int BT_COPY = 0x00000001;
+ public static final int BT_ZERO = 0x00000002;
+ public static final int BT_ZLIB = 0x80000005;
+ public static final int BT_END = 0xffffffff;
+ public static final int BT_UNKNOWN1 = 0x7ffffffe;
+ private static final int[] KNOWN_BLOCK_TYPES = { BT_COPY,
+ BT_ZERO,
+ BT_ZLIB,
+ BT_END,
+ BT_UNKNOWN1 };
+ private static final String[] KNOWN_BLOCK_TYPE_NAMES = { "BT_COPY",
+ "BT_ZERO",
+ "BT_ZLIB",
+ "BT_END",
+ "BT_UNKNOWN1" };
+ private int blockType;
+ private int unknown;
+ private long outOffset;
+ private long outSize;
+ private long inOffset;
+ private long inSize;
+
+ public BlockDescriptor() {
+ }
+
+ public BlockDescriptor(byte[] entryData) throws IOException {
+ this(new DataInputStream(new ByteArrayInputStream(entryData)));
+ }
+
+ public BlockDescriptor(DataInput dataIn) throws IOException {
+ blockType = dataIn.readInt();
+ unknown = dataIn.readInt();
+ outOffset = dataIn.readLong() * 0x200;
+ outSize = dataIn.readLong() * 0x200;
+ inOffset = dataIn.readLong();
+ inSize = dataIn.readLong();
+ }
+
+ public byte[] toBytes() throws IOException {
+ ByteArrayOutputStream result = new ByteArrayOutputStream(0x28);
+ DataOutputStream dataOut = new DataOutputStream(result);
+
+ dataOut.writeInt(blockType); // 4 bytes
+ dataOut.writeInt(unknown); // 4 bytes
+ if((outOffset % 0x200) != 0)
+ throw new RuntimeException("Out offset must be aligned to 0x200 block size!");
+ dataOut.writeLong(outOffset / 0x200); // 8 bytes
+ if((outSize % 0x200) != 0)
+ throw new RuntimeException("Out size must be aligned to 0x200 block size!");
+ dataOut.writeLong(outSize / 0x200); // 8 bytes
+ dataOut.writeLong(inOffset); // 8 bytes
+ dataOut.writeLong(inSize); // 8 bytes
+ // sum = 4 + 4 + 8 + 8 + 8 + 8 = 40 = 0x28
+
+ dataOut.flush();
+ dataOut.close();
+ return result.toByteArray();
+ }
+
+ public int getBlockType() {
+ return blockType;
+ }
+
+ public int getUnknown() {
+ return unknown;
+ }
+
+ public String getUnknownAsString() throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(4);
+ DataOutputStream dos = new DataOutputStream(baos);
+ dos.write(unknown);
+ dos.close();
+ return new String(baos.toByteArray(), "US-ASCII");
+ }
+
+ public long getOutOffset() {
+ return outOffset;
+ }
+
+ public long getOutSize() {
+ return outSize;
+ }
+
+ public long getInOffset() {
+ return inOffset;
+ }
+
+ public long getInSize() {
+ return inSize;
+ }
+
+ public void setBlockType(int blockType) {
+ this.blockType = blockType;
+ }
+
+ public void setUnknown(int unknown) {
+ this.unknown = unknown;
+ }
+
+ public void setOutOffset(long outOffset) {
+ if((outOffset % 0x200) != 0)
+ throw new RuntimeException("Out offset must be aligned to 0x200 block size!");
+ this.outOffset = outOffset;
+ }
+
+ public void setOutSize(long outSize) {
+ if((outSize % 0x200) != 0)
+ throw new RuntimeException("Out size must be aligned to 0x200 block size!");
+ this.outSize = outSize;
+ }
+
+ public void setInOffset(long inOffset) {
+ this.inOffset = inOffset;
+ }
+
+ public void setInSize(long inSize) {
+ this.inSize = inSize;
+ }
+
+ public boolean hasKnownBlockType() {
+ for(int current : KNOWN_BLOCK_TYPES) {
+ if(blockType == current)
+ return true;
+ }
+ return false;
+ }
+
+ public String getBlockTypeName() {
+ for(int i = 0; i < KNOWN_BLOCK_TYPES.length; ++i) {
+ int current = KNOWN_BLOCK_TYPES[i];
+ if(blockType == current)
+ return KNOWN_BLOCK_TYPE_NAMES[i];
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder("[BlockDescriptor");
+
+ String blockTypeString = "\"" + getBlockTypeName() + "\"";
+ if(blockTypeString == null)
+ blockTypeString = "0x" + Integer.toHexString(blockType) + " (unknown type)";
+
+ result.append(" blockType=" + blockTypeString);
+ result.append(" unknown=" + Integer.toHexString(unknown));
+ result.append(" outOffset=" + outOffset);
+ result.append(" outSize=" + outSize);
+ result.append(" inOffset=" + inOffset);
+ result.append(" inSize=" + inSize);
+ result.append("]");
+
+ return result.toString();
+ }
+ }
+
+ // Saxat från HFSExplorer.java
+ public static class APMPartition {
+
+ public int pmSig; // {partition signature}
+ public int pmSigPad; // {reserved}
+ public long pmMapBlkCnt; // {number of blocks in partition map}
+ public long pmPyPartStart; // {first physical block of partition}
+ public long pmPartBlkCnt; // {number of blocks in partition}
+ public final byte[] pmPartName = new byte[32]; // {partition name}
+ public final byte[] pmParType = new byte[32]; // {partition type}
+ public long pmLgDataStart; // {first logical block of data area}
+ public long pmDataCnt; // {number of blocks in data area}
+ public long pmPartStatus; // {partition status information}
+ public long pmLgBootStart; // {first logical block of boot code}
+ public long pmBootSize; // {size of boot code, in bytes}
+ public long pmBootAddr; // {boot code load address}
+ public long pmBootAddr2; // {reserved}
+ public long pmBootEntry; // {boot code entry point}
+ public long pmBootEntry2; // {reserved}
+ public long pmBootCksum; // {boot code checksum}
+ public final byte[] pmProcessor = new byte[16]; // {processor type}
+ public final int[] pmPad = new int[188]; // {reserved}
+
+ public APMPartition(byte[] entryData) throws IOException {
+ this(new DataInputStream(new ByteArrayInputStream(entryData)));
+ }
+
+ public APMPartition(DataInput di) throws IOException {
+ // 2*2 + 4*3 + 32*2 + 10*4 + 16 + 188*2 = 512
+ pmSig = di.readShort() & 0xffff;
+ pmSigPad = di.readShort() & 0xffff;
+ pmMapBlkCnt = di.readInt() & 0xffffffffL;
+ pmPyPartStart = di.readInt() & 0xffffffffL;
+ pmPartBlkCnt = di.readInt() & 0xffffffffL;
+ di.readFully(pmPartName);
+ di.readFully(pmParType);
+ pmLgDataStart = di.readInt() & 0xffffffffL;
+ pmDataCnt = di.readInt() & 0xffffffffL;
+ pmPartStatus = di.readInt() & 0xffffffffL;
+ pmLgBootStart = di.readInt() & 0xffffffffL;
+ pmBootSize = di.readInt() & 0xffffffffL;
+ pmBootAddr = di.readInt() & 0xffffffffL;
+ pmBootAddr2 = di.readInt() & 0xffffffffL;
+ pmBootEntry = di.readInt() & 0xffffffffL;
+ pmBootEntry2 = di.readInt() & 0xffffffffL;
+ pmBootCksum = di.readInt() & 0xffffffffL;
+ di.readFully(pmProcessor);
+ for(int i = 0; i < pmPad.length; ++i)
+ pmPad[i] = di.readShort() & 0xffff;
+ }
+
+ public void printPartitionInfo(PrintStream ps) {
+// String result = "";
+// result += "Partition name: \"" + new String(pmPartName) + "\"\n";
+// result += "Partition type: \"" + new String(pmParType) + "\"\n";
+// result += "Processor type: \"" + new String(pmProcessor) + "\"\n";
+// return result;
+ try {
+ ps.println("pmSig: " + pmSig);
+ ps.println("pmSigPad: " + pmSigPad);
+ ps.println("pmMapBlkCnt: " + pmMapBlkCnt);
+ ps.println("pmPyPartStart: " + pmPyPartStart);
+ ps.println("pmPartBlkCnt: " + pmPartBlkCnt);
+ ps.println("pmPartName: \"" + new String(pmPartName, "US-ASCII") + "\"");
+ ps.println("pmParType: \"" + new String(pmParType, "US-ASCII") + "\"");
+ ps.println("pmLgDataStart: " + pmLgDataStart);
+ ps.println("pmDataCnt: " + pmDataCnt);
+ ps.println("pmPartStatus: " + pmPartStatus);
+ ps.println("pmLgBootStart: " + pmLgBootStart);
+ ps.println("pmBootSize: " + pmBootSize);
+ ps.println("pmBootAddr: " + pmBootAddr);
+ ps.println("pmBootAddr2: " + pmBootAddr2);
+ ps.println("pmBootEntry: " + pmBootEntry);
+ ps.println("pmBootEntry2: " + pmBootEntry2);
+ ps.println("pmBootCksum: " + pmBootCksum);
+ ps.println("pmProcessor: \"" + new String(pmProcessor, "US-ASCII") + "\"");
+ ps.println("pmPad: " + pmPad);
+ } catch(UnsupportedEncodingException uee) {
+ uee.printStackTrace();
+ } // Will never happen. Ever. Period.
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ DMGMetadata meta = new DMGMetadata(new RandomAccessFile(args[0], "r"));
+ meta.printInfo(System.out);
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/utils/ExtractPlist.java b/src/org/catacombae/dmgextractor/utils/ExtractPlist.java
new file mode 100644
index 0000000..4d56d26
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/ExtractPlist.java
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor.utils;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import org.catacombae.io.ReadableFileStream;
+import org.catacombae.dmg.udif.UDIFFileView;
+
+public class ExtractPlist {
+
+ public static void main(String[] args) throws IOException {
+ RandomAccessFile inFile;
+ OutputStream outStream;
+ try {
+ inFile = new RandomAccessFile(args[0], "r");
+ outStream = new FileOutputStream(args[1]);
+ } catch(ArrayIndexOutOfBoundsException aioobe) {
+ System.out.println("Usage: ExtractPlist ");
+ return;//System.exit(0);
+ }
+
+ UDIFFileView dfw = new UDIFFileView(new ReadableFileStream(inFile));
+ byte[] plistData = dfw.getPlistData();
+ /*int bytesWritten = */ outStream.write(plistData);
+// if(bytesWritten != plistData.length)
+// System.out.println("ERROR: Could not write all data to output file. " + bytesWritten + " of " + plistData.length + " bytes written.");
+
+ inFile.close();
+ outStream.close();
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/utils/ValidateDmg.java b/src/org/catacombae/dmgextractor/utils/ValidateDmg.java
new file mode 100644
index 0000000..fdcbf54
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/ValidateDmg.java
@@ -0,0 +1,145 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+package org.catacombae.dmgextractor.utils;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import org.catacombae.dmgextractor.Util;
+import org.catacombae.dmg.udif.Koly;
+import org.catacombae.dmg.udif.UDIFFileView;
+import org.catacombae.io.ReadableFileStream;
+
+/**
+ * This is a console application that validates a set of dmg files
+ * according to the currently known rules (implemented in Koly.validate).
+ */
+public class ValidateDmg {
+
+ public static void main(String[] filenames) throws IOException {
+ if(filenames.length == 0)
+ System.out.println("No files to validate.");
+ for(String fn : filenames) {
+ System.out.println("Processing \"" + fn + "\"...");
+ try {
+ RandomAccessFile raf = new RandomAccessFile(fn, "r");
+ UDIFFileView dfw = new UDIFFileView(new ReadableFileStream(raf));
+ Koly koly = dfw.getKoly();
+ ValidateResult vr = validateKoly(raf, koly);
+ String[] errors = vr.getErrors();
+ String[] warnings = vr.getWarnings();
+ for(int i = 0; i < errors.length; ++i) {
+ if(i == 0)
+ System.out.println(" " + errors.length + " errors");
+ System.out.println(" " + errors[i].toString());
+ }
+ for(int i = 0; i < warnings.length; ++i) {
+ if(i == 0)
+ System.out.println(" " + warnings.length + " warnings:");
+ System.out.println(" " + warnings[i].toString());
+ }
+ dfw.getPlist(); // Creates a new Plist, which automatically parses the XML data (and validates it).
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+ System.out.println();
+ }
+ }
+
+ public static ValidateResult validateKoly(RandomAccessFile sourceFile, Koly koly) throws IOException {
+ /* Validates the data in the koly block, as much as we can validate it
+ * given what we know about it.. */
+
+ ValidateResult vr = new ValidateResult();
+
+ // Check that the fourcc reads "koly"
+ try {
+ String fourCCString = new String(Util.toByteArrayBE(koly.getFourCC()), "US-ASCII");
+ if(!fourCCString.equals("koly"))
+ vr.addError("Invalid fourCC: \"" + fourCCString + "\" (should be \"koly\")");
+ } catch(UnsupportedEncodingException uee) {
+ throw new RuntimeException(uee); // This should never happen
+ }
+
+ // unknown1 has always been a certain byte-sequence in examples. checkit
+ // 0000 0004 0000 0200 0000 0001 0000 0000 0000 0000 0000 0000 0000 0000
+ byte[] previouslySeenString = { 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x20, 0x0,
+ 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0 };
+ if(!Util.arraysEqual(previouslySeenString, koly.getUnknown1()))
+ vr.addWarning("unknown1 deviates from earlier observations: 0x" + Util.byteArrayToHexString(koly.getUnknown1()));
+
+ long plistBegin1 = koly.getPlistBegin1();
+ long plistBegin2 = koly.getPlistBegin2();
+ long plistSize = koly.getPlistSize();
+
+ // Plist addresses must be equal
+ if(plistBegin1 != plistBegin2)
+ vr.addError("The two plist addresses don't match (" + plistBegin1 + "!=" + plistBegin2 + ")");
+
+ // Address to start of plist must be within file bounds.
+ if(plistBegin1 > sourceFile.length())
+ vr.addError("plistBegin1 out of bounds (pistBegin1: " + plistBegin1 + " file size: " + sourceFile.length() + ")");
+ else {
+ // There must be a plist at that address
+ sourceFile.seek(plistBegin1);
+ byte[] xmlIdentifier = new byte[5];
+ sourceFile.read(xmlIdentifier);
+ try {
+ String xmlIdentifierString = new String(xmlIdentifier, "US-ASCII");
+ if(!xmlIdentifierString.equals(" sourceFile.length())
+ vr.addError("plistBegin2 out of bounds (pistBegin1: " + plistBegin2 + " file size: " + sourceFile.length() + ")");
+ else {
+ // There must be a plist at that address
+ sourceFile.seek(plistBegin2);
+ byte[] xmlIdentifier = new byte[5];
+ sourceFile.read(xmlIdentifier);
+ try {
+ String xmlIdentifierString = new String(xmlIdentifier, "US-ASCII");
+ if(!xmlIdentifierString.equals(" 0) {
+ if(plistSize + plistBegin1 > sourceFile.length() - 512)
+ vr.addError("plist dimensions outside file bounds! (plistSize: " + plistSize + " plistBegin1: " + plistBegin1 + " sourceFile.length()-512: " + (sourceFile.length() - 512));
+ if(plistSize + plistBegin2 > sourceFile.length() - 512)
+ vr.addError("plist dimensions outside file bounds! (plistSize: " + plistSize + " plistBegin2: " + plistBegin2 + " sourceFile.length()-512: " + (sourceFile.length() - 512));
+ }
+ else
+ vr.addError("plist dimensions outside file bounds! (plistSize: " + plistSize + " sourceFile.length-512: " + (sourceFile.length() - 512));
+ return vr;
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/utils/ValidateDmgs.java b/src/org/catacombae/dmgextractor/utils/ValidateDmgs.java
new file mode 100644
index 0000000..1246fc3
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/ValidateDmgs.java
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor.utils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.LinkedList;
+
+/**
+ * Wrapper to allow processing of a list of dmg files. (I was too lazy
+ * to write a shell script for this..)
+ */
+public class ValidateDmgs {
+
+ public static void main(String[] args) throws IOException {
+ LinkedList fileList = new LinkedList();
+ for(String currentList : args) {
+ try {
+ BufferedReader listIn = new BufferedReader(new InputStreamReader(new FileInputStream(new File(currentList))));
+ String currentDmg = listIn.readLine();
+ while(currentDmg != null) {
+ fileList.add(currentDmg);
+ currentDmg = listIn.readLine();
+ }
+ } catch(IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+ ValidateDmg.main(fileList.toArray(new String[fileList.size()]));
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/utils/ValidateResult.java b/src/org/catacombae/dmgextractor/utils/ValidateResult.java
new file mode 100644
index 0000000..9d5f6d4
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/ValidateResult.java
@@ -0,0 +1,45 @@
+/*-
+ * Copyright (C) 2006-2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.dmgextractor.utils;
+
+import java.util.LinkedList;
+
+public class ValidateResult {
+
+ private final LinkedList errors = new LinkedList();
+ private final LinkedList warnings = new LinkedList();
+
+ public ValidateResult() {
+ }
+
+ public void addError(String message) {
+ errors.addLast(message);
+ }
+
+ public void addWarning(String message) {
+ warnings.addLast(message);
+ }
+
+ public String[] getErrors() {
+ return errors.toArray(new String[errors.size()]);
+ }
+
+ public String[] getWarnings() {
+ return warnings.toArray(new String[warnings.size()]);
+ }
+}
diff --git a/src/org/catacombae/dmgextractor/utils/gui/.cvsignore b/src/org/catacombae/dmgextractor/utils/gui/.cvsignore
new file mode 100644
index 0000000..cd5dc09
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/gui/.cvsignore
@@ -0,0 +1,5 @@
+*~
+*#
+*.class
+.DS_Store
+Thumbs.db
diff --git a/src/org/catacombae/dmgextractor/utils/gui/DMGInfoPanel.form b/src/org/catacombae/dmgextractor/utils/gui/DMGInfoPanel.form
new file mode 100644
index 0000000..3e5ed5e
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/gui/DMGInfoPanel.form
@@ -0,0 +1,71 @@
+
+
+
diff --git a/src/org/catacombae/dmgextractor/utils/gui/DMGInfoPanel.java b/src/org/catacombae/dmgextractor/utils/gui/DMGInfoPanel.java
new file mode 100644
index 0000000..e801fc9
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/gui/DMGInfoPanel.java
@@ -0,0 +1,133 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+/*
+ * DMGInfoPanel.java
+ *
+ * Created on den 7 november 2006, 09:35
+ */
+
+package org.catacombae.dmgextractor.utils.gui;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.event.*;
+
+/**
+ *
+ * @author erik
+ */
+public class DMGInfoPanel extends javax.swing.JPanel {
+ private CardLayout contentsCardLayout;
+ private String[] contentTags = {
+ "General info",
+ "plist",
+ "Unknown (256 bytes)",
+ "Block map",
+ "Unknown (12 bytes)",
+ "Apple Partition Map",
+ "Unknown (X bytes)",
+ "koly"};
+
+ /** Creates new form DMGInfoPanel */
+ public DMGInfoPanel() {
+ initComponents();
+
+ ListModel listModel = new javax.swing.AbstractListModel() {
+ public int getSize() { return contentTags.length; }
+ public Object getElementAt(int i) { return contentTags[i]; }
+ };
+
+ contentsList.setModel(listModel);
+
+ contentsCardLayout = new CardLayout();
+ contentsPane.setLayout(contentsCardLayout);
+
+
+ // Now, let's add all components to contentsPane
+ Component[] cmp = new Component[contentTags.length];
+ cmp[0] = new GeneralInfoPanel();
+ cmp[1] = new PlistPanel();
+ cmp[2] = new UnknownDataViewPanel();
+ cmp[3] = new JPanel(); // N/I
+ cmp[4] = new UnknownDataViewPanel();
+ cmp[5] = new JPanel(); // N/I
+ cmp[6] = new UnknownDataViewPanel();
+ cmp[7] = new KolyPanel();
+
+ for(int i = 0; i < contentTags.length; ++i)
+ contentsPane.add(cmp[i], contentTags[i]);
+
+ contentsList.addListSelectionListener(new ListSelectionListener() {
+ public void valueChanged(ListSelectionEvent lse) {
+ //System.out.println(lse);
+ if(!lse.getValueIsAdjusting()) {
+ int index = contentsList.getSelectedIndex();
+ //System.out.println("Switching to " + index + "...");
+ contentsCardLayout.show(contentsPane, contentTags[index]);
+ }
+ }
+ });
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+ listContentsSplitter = new javax.swing.JSplitPane();
+ contentsListScroller = new javax.swing.JScrollPane();
+ contentsList = new javax.swing.JList();
+ contentsPane = new javax.swing.JPanel();
+
+ contentsList.setModel(new javax.swing.AbstractListModel() {
+ String[] strings = { "General info", "plist", "Unknown (256 bytes)", "Block map", "Unknown (12 bytes)", "Apple Partition Map", "Unknown (X bytes)", "koly" };
+ public int getSize() { return strings.length; }
+ public Object getElementAt(int i) { return strings[i]; }
+ });
+ contentsList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
+ contentsListScroller.setViewportView(contentsList);
+
+ listContentsSplitter.setLeftComponent(contentsListScroller);
+
+ contentsPane.setLayout(new java.awt.CardLayout());
+
+ listContentsSplitter.setRightComponent(contentsPane);
+
+ org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(listContentsSplitter, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 549, Short.MAX_VALUE)
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(listContentsSplitter, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 369, Short.MAX_VALUE)
+ );
+ }// //GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JList contentsList;
+ private javax.swing.JScrollPane contentsListScroller;
+ private javax.swing.JPanel contentsPane;
+ private javax.swing.JSplitPane listContentsSplitter;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/src/org/catacombae/dmgextractor/utils/gui/GeneralInfoPanel.form b/src/org/catacombae/dmgextractor/utils/gui/GeneralInfoPanel.form
new file mode 100644
index 0000000..63665fa
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/gui/GeneralInfoPanel.form
@@ -0,0 +1,56 @@
+
+
+
diff --git a/src/org/catacombae/dmgextractor/utils/gui/GeneralInfoPanel.java b/src/org/catacombae/dmgextractor/utils/gui/GeneralInfoPanel.java
new file mode 100644
index 0000000..61cbadf
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/gui/GeneralInfoPanel.java
@@ -0,0 +1,86 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+/*
+ * GeneralInfoPanel.java
+ *
+ * Created on den 7 november 2006, 09:52
+ */
+
+package org.catacombae.dmgextractor.utils.gui;
+
+/**
+ *
+ * @author erik
+ */
+public class GeneralInfoPanel extends javax.swing.JPanel {
+
+ /** Creates new form GeneralInfoPanel */
+ public GeneralInfoPanel() {
+ initComponents();
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+ jLabel1 = new javax.swing.JLabel();
+ jLabel2 = new javax.swing.JLabel();
+ jLabel3 = new javax.swing.JLabel();
+
+ jLabel1.setText("File name:");
+
+ jLabel2.setText("Size:");
+
+ jLabel3.setText("Number of partitions:");
+
+ org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(layout.createSequentialGroup()
+ .addContainerGap()
+ .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jLabel1)
+ .add(jLabel2)
+ .add(jLabel3))
+ .addContainerGap(244, Short.MAX_VALUE))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(layout.createSequentialGroup()
+ .addContainerGap()
+ .add(jLabel1)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(jLabel2)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(jLabel3)
+ .addContainerGap(222, Short.MAX_VALUE))
+ );
+ }// //GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel jLabel1;
+ private javax.swing.JLabel jLabel2;
+ private javax.swing.JLabel jLabel3;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/src/org/catacombae/dmgextractor/utils/gui/KolyPanel.form b/src/org/catacombae/dmgextractor/utils/gui/KolyPanel.form
new file mode 100644
index 0000000..6aaf191
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/gui/KolyPanel.form
@@ -0,0 +1,36 @@
+
+
+
diff --git a/src/org/catacombae/dmgextractor/utils/gui/KolyPanel.java b/src/org/catacombae/dmgextractor/utils/gui/KolyPanel.java
new file mode 100644
index 0000000..b078214
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/gui/KolyPanel.java
@@ -0,0 +1,69 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+/*
+ * KolyPanel.java
+ *
+ * Created on den 7 november 2006, 16:43
+ */
+
+package org.catacombae.dmgextractor.utils.gui;
+
+/**
+ *
+ * @author erik
+ */
+public class KolyPanel extends javax.swing.JPanel {
+
+ /** Creates new form KolyPanel */
+ public KolyPanel() {
+ initComponents();
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+ jLabel1 = new javax.swing.JLabel();
+
+ jLabel1.setText("Koly:");
+
+ org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(layout.createSequentialGroup()
+ .add(jLabel1)
+ .addContainerGap(369, Short.MAX_VALUE))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(layout.createSequentialGroup()
+ .add(jLabel1)
+ .addContainerGap(284, Short.MAX_VALUE))
+ );
+ }// //GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JLabel jLabel1;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/src/org/catacombae/dmgextractor/utils/gui/PlistPanel.form b/src/org/catacombae/dmgextractor/utils/gui/PlistPanel.form
new file mode 100644
index 0000000..6c320e7
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/gui/PlistPanel.form
@@ -0,0 +1,123 @@
+
+
+
diff --git a/src/org/catacombae/dmgextractor/utils/gui/PlistPanel.java b/src/org/catacombae/dmgextractor/utils/gui/PlistPanel.java
new file mode 100644
index 0000000..f08e332
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/gui/PlistPanel.java
@@ -0,0 +1,109 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+/*
+ * PlistPanel.java
+ *
+ * Created on den 7 november 2006, 09:58
+ */
+
+package org.catacombae.dmgextractor.utils.gui;
+
+/**
+ *
+ * @author erik
+ */
+public class PlistPanel extends javax.swing.JPanel {
+
+ /** Creates new form PlistPanel */
+ public PlistPanel() {
+ initComponents();
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+ plistTabs = new javax.swing.JTabbedPane();
+ textViewScroller = new javax.swing.JScrollPane();
+ textViewArea = new javax.swing.JTextArea();
+ hierarchicalViewPanel = new javax.swing.JPanel();
+ hierarchicalViewSplitter = new javax.swing.JSplitPane();
+ xmlTreeScroller = new javax.swing.JScrollPane();
+ xmlTree = new javax.swing.JTree();
+ keyDataScroller = new javax.swing.JScrollPane();
+ keyDataView = new javax.swing.JTextArea();
+
+ textViewArea.setColumns(20);
+ textViewArea.setRows(5);
+ textViewArea.setText("yo\n");
+ textViewScroller.setViewportView(textViewArea);
+
+ plistTabs.addTab("Text view", textViewScroller);
+
+ hierarchicalViewSplitter.setDividerLocation(120);
+ xmlTreeScroller.setViewportView(xmlTree);
+
+ hierarchicalViewSplitter.setLeftComponent(xmlTreeScroller);
+
+ keyDataView.setColumns(20);
+ keyDataView.setRows(5);
+ keyDataScroller.setViewportView(keyDataView);
+
+ hierarchicalViewSplitter.setRightComponent(keyDataScroller);
+
+ org.jdesktop.layout.GroupLayout hierarchicalViewPanelLayout = new org.jdesktop.layout.GroupLayout(hierarchicalViewPanel);
+ hierarchicalViewPanel.setLayout(hierarchicalViewPanelLayout);
+ hierarchicalViewPanelLayout.setHorizontalGroup(
+ hierarchicalViewPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(hierarchicalViewSplitter, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 379, Short.MAX_VALUE)
+ );
+ hierarchicalViewPanelLayout.setVerticalGroup(
+ hierarchicalViewPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(hierarchicalViewSplitter, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 330, Short.MAX_VALUE)
+ );
+ plistTabs.addTab("Hierarchical view", hierarchicalViewPanel);
+
+ org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(plistTabs, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(plistTabs, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 376, Short.MAX_VALUE)
+ );
+ }// //GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JPanel hierarchicalViewPanel;
+ private javax.swing.JSplitPane hierarchicalViewSplitter;
+ private javax.swing.JScrollPane keyDataScroller;
+ private javax.swing.JTextArea keyDataView;
+ private javax.swing.JTabbedPane plistTabs;
+ private javax.swing.JTextArea textViewArea;
+ private javax.swing.JScrollPane textViewScroller;
+ private javax.swing.JTree xmlTree;
+ private javax.swing.JScrollPane xmlTreeScroller;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/src/org/catacombae/dmgextractor/utils/gui/UnknownDataViewPanel.form b/src/org/catacombae/dmgextractor/utils/gui/UnknownDataViewPanel.form
new file mode 100644
index 0000000..238894a
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/gui/UnknownDataViewPanel.form
@@ -0,0 +1,119 @@
+
+
+
diff --git a/src/org/catacombae/dmgextractor/utils/gui/UnknownDataViewPanel.java b/src/org/catacombae/dmgextractor/utils/gui/UnknownDataViewPanel.java
new file mode 100644
index 0000000..164cafe
--- /dev/null
+++ b/src/org/catacombae/dmgextractor/utils/gui/UnknownDataViewPanel.java
@@ -0,0 +1,111 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+/*
+ * UnknownDataViewPanel.java
+ *
+ * Created on den 7 november 2006, 11:10
+ */
+
+package org.catacombae.dmgextractor.utils.gui;
+
+/**
+ *
+ * @author erik
+ */
+public class UnknownDataViewPanel extends javax.swing.JPanel {
+
+ /** Creates new form UnknownDataViewPanel */
+ public UnknownDataViewPanel() {
+ initComponents();
+ }
+
+ /** This method is called from within the constructor to
+ * initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is
+ * always regenerated by the Form Editor.
+ */
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+ jTabbedPane1 = new javax.swing.JTabbedPane();
+ jScrollPane2 = new javax.swing.JScrollPane();
+ jTextArea2 = new javax.swing.JTextArea();
+ jPanel1 = new javax.swing.JPanel();
+ jPanel2 = new javax.swing.JPanel();
+ jLabel1 = new javax.swing.JLabel();
+ jComboBox1 = new javax.swing.JComboBox();
+ jScrollPane1 = new javax.swing.JScrollPane();
+ jTextArea1 = new javax.swing.JTextArea();
+
+ jTextArea2.setColumns(20);
+ jTextArea2.setRows(5);
+ jScrollPane2.setViewportView(jTextArea2);
+
+ jTabbedPane1.addTab("Hex view", jScrollPane2);
+
+ jLabel1.setText("Encoding:");
+ jPanel2.add(jLabel1);
+
+ jComboBox1.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "US-ASCII", "UTF-8", "UTF-16BE" }));
+ jPanel2.add(jComboBox1);
+
+ jTextArea1.setColumns(20);
+ jTextArea1.setRows(5);
+ jScrollPane1.setViewportView(jTextArea1);
+
+ org.jdesktop.layout.GroupLayout jPanel1Layout = new org.jdesktop.layout.GroupLayout(jPanel1);
+ jPanel1.setLayout(jPanel1Layout);
+ jPanel1Layout.setHorizontalGroup(
+ jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(org.jdesktop.layout.GroupLayout.TRAILING, jPanel2, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 379, Short.MAX_VALUE)
+ .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 379, Short.MAX_VALUE)
+ );
+ jPanel1Layout.setVerticalGroup(
+ jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jPanel1Layout.createSequentialGroup()
+ .add(jPanel2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
+ .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 209, Short.MAX_VALUE))
+ );
+ jTabbedPane1.addTab("Text view", jPanel1);
+
+ org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jTabbedPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
+ .add(jTabbedPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)
+ );
+ }// //GEN-END:initComponents
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JComboBox jComboBox1;
+ private javax.swing.JLabel jLabel1;
+ private javax.swing.JPanel jPanel1;
+ private javax.swing.JPanel jPanel2;
+ private javax.swing.JScrollPane jScrollPane1;
+ private javax.swing.JScrollPane jScrollPane2;
+ private javax.swing.JTabbedPane jTabbedPane1;
+ private javax.swing.JTextArea jTextArea1;
+ private javax.swing.JTextArea jTextArea2;
+ // End of variables declaration//GEN-END:variables
+
+}
diff --git a/src/org/catacombae/dmgx/Attribute.java b/src/org/catacombae/dmgx/Attribute.java
deleted file mode 100644
index 5b000bb..0000000
--- a/src/org/catacombae/dmgx/Attribute.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*-
- * Copyright (C) 2006 Erik Larsson
- *
- * All rights reserved.
- *
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-package org.catacombae.dmgx;
-
-class Attribute {
- public final String localName;
- public final String qName;
- public final String type;
- public final String URI;
- public final String value;
- public Attribute(String localName, String qName, String type, String URI, String value) {
- this.localName = localName;
- this.qName = qName;
- this.type = type;
- this.URI = URI;
- this.value = value;
- }
-}
diff --git a/src/org/catacombae/dmgx/BuildNumber.java b/src/org/catacombae/dmgx/BuildNumber.java
deleted file mode 100644
index 5ba865b..0000000
--- a/src/org/catacombae/dmgx/BuildNumber.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*-
- * Copyright (C) 2006 Erik Larsson
- *
- * All rights reserved.
- *
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-package org.catacombae.dmgx;
-
-public class BuildNumber {
- //[BuildEnumerator:Opening] WARNING: The following lines are managed by an external program. Do NOT change.
- public static final long BUILD_NUMBER = 144L;
- //[BuildEnumerator:Closing] The lines managed by an external program end here.
-}
-
diff --git a/src/org/catacombae/dmgx/DMGExtractor.java b/src/org/catacombae/dmgx/DMGExtractor.java
deleted file mode 100644
index a9cc09e..0000000
--- a/src/org/catacombae/dmgx/DMGExtractor.java
+++ /dev/null
@@ -1,829 +0,0 @@
-/*-
- * Copyright (C) 2006 Erik Larsson
- * (C) 2004 vu1tur (not the actual code but...)
- *
- * All rights reserved.
- *
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-package org.catacombae.dmgx;
-
-import net.iharder.Base64;
-import java.io.*;
-import java.util.LinkedList;
-import java.util.Iterator;
-import java.util.zip.Inflater;
-import java.util.zip.DataFormatException;
-import javax.xml.parsers.SAXParserFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.parsers.SAXParser;
-import javax.swing.JOptionPane;
-import javax.swing.JFileChooser;
-import javax.swing.ProgressMonitor;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
-public class DMGExtractor {
- public static final String APPNAME = "DMGExtractor 0.51pre";
- public static final String BUILDSTRING = "(Build #" + BuildNumber.BUILD_NUMBER + ")";
- public static final boolean DEBUG = false;
- // Constants defining block types in the dmg file
- public static final int BT_ADC = 0x80000004;
- public static final int BT_ZLIB = 0x80000005;
- public static final int BT_BZIP2 = 0x80000006;
- public static final int BT_COPY = 0x00000001;
- public static final int BT_ZERO = 0x00000002;
- public static final int BT_END = 0xffffffff;
- public static final int BT_UNKNOWN = 0x7ffffffe;
- public static final long PLIST_ADDRESS_1 = 0x1E0;
- public static final long PLIST_ADDRESS_2 = 0x128;
- public static final String BACKSPACE79 = "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";
-// public static PrintStream stdout = System.out;
-// public static PrintStream stderr = System.err;
- public static BufferedReader stdin =
- new BufferedReader(new InputStreamReader(System.in));
-
- public static boolean verbose = false;
- public static boolean graphical = false;
- public static String startupCommand = "java DMGExtractor";
- public static File dmgFile = null;
- public static File isoFile = null;
-
- public static ProgressMonitor progmon;
-
- public static void main(String[] args) throws Exception {
- try {
- notmain(args);
- } catch(Exception e) {
- if(graphical)
- JOptionPane.showMessageDialog(null, "The program encountered an unexpected error: " + e.toString() +
- "\nClosing...", "Error", JOptionPane.ERROR_MESSAGE);
- throw e;
- }
- }
-
- public static void notmain(String[] args) throws Exception {
- System.setProperty("swing.aatext", "true"); //Antialiased text
- try { javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager.getSystemLookAndFeelClassName()); }
- catch(Exception e) {}
-
- if(DEBUG) verbose = true;
-
- parseArgs(args);
-
- printlnVerbose("Processing: " + dmgFile);
- RandomAccessFile dmgRaf = new RandomAccessFile(dmgFile, "r");
- RandomAccessFile isoRaf = null;
- boolean testOnly = false;
- if(isoFile != null) {
- isoRaf = new RandomAccessFile(isoFile, "rw");
- isoRaf.setLength(0);
- printlnVerbose("Extracting to: " + isoFile);
- }
- else {
- testOnly = true;
- printlnVerbose("Simulating extraction...");
- }
-
- dmgRaf.seek(dmgRaf.length()-PLIST_ADDRESS_1);
- long plistBegin1 = dmgRaf.readLong();
- long plistEnd = dmgRaf.readLong();
- dmgRaf.seek(dmgRaf.length()-PLIST_ADDRESS_2);
- long plistBegin2 = dmgRaf.readLong();
- long plistSize = dmgRaf.readLong();
-
- if(DEBUG) {
- println("Read addresses:",
- " " + plistBegin1,
- " " + plistBegin2);
- }
- if(plistBegin1 != plistBegin2) {
- println("Addresses not equal! Assumption broken... =/",
- plistBegin1 + " != " + plistBegin2);
- System.exit(0);
- }
- if(plistSize != (plistEnd-plistBegin1)) {
- println("plistSize field does not match plistEnd marker!",
- "plistSize=" + plistSize + " plistBegin1=" + plistBegin1 + " plistEnd=" + plistEnd + " plistEnd-plistBegin1=" + (plistEnd-plistBegin1));
- }
- printlnVerbose("Jumping to address...");
- dmgRaf.seek(plistBegin1);
- byte[] buffer = new byte[(int)plistSize];
- dmgRaf.read(buffer);
-
- InputStream is = new ByteArrayInputStream(buffer);
-
- NodeBuilder handler = new NodeBuilder();
- SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
- try {
-// System.out.println("validation: " + saxParser.getProperty("validation"));
-// System.out.println("external-general-entities: " + saxParser.getProperty("external-general-entities"));
-// System.out.println("external-parameter-entities: " + saxParser.getProperty("external-parameter-entities"));
-// System.out.println("is-standalone: " + saxParser.getProperty("is-standalone"));
-// System.out.println("lexical-handler: " + saxParser.getProperty("lexical-handler"));
-// System.out.println("parameter-entities: " + saxParser.getProperty("parameter-entities"));
-// System.out.println("namespaces: " + saxParser.getProperty("namespaces"));
-// System.out.println("namespace-prefixes: " + saxParser.getProperty("namespace-prefixes"));
-// System.out.println(": " + saxParser.getProperty(""));
-// System.out.println(": " + saxParser.getProperty(""));
-// System.out.println(": " + saxParser.getProperty(""));
-// System.out.println(": " + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
-// System.out.println("" + saxParser.getProperty(""));
- System.out.println("isValidating: " + saxParser.isValidating());
- saxParser.parse(is, handler);
- } catch(SAXException se) {
- se.printStackTrace();
- System.err.println("Could not read the partition list... exiting.");
- System.exit(1);
- }
-
- XMLNode[] rootNodes = handler.getRoots();
- if(rootNodes.length != 1) {
- println("Could not parse DMG-file!");
- System.exit(0);
- }
-
- /* Ok, now we have a tree built from the XML-document. Let's walk to the right place. */
- /* cd plist
- cd dict
- cdkey resource-fork (type:dict)
- cdkey blkx (type:array) */
- XMLNode current;
- XMLElement[] children;
- boolean keyFound;
- current = rootNodes[0]; //We are at plist... probably (there should be only one root node)
-
- current = current.cd("dict");
- current = current.cdkey("resource-fork");
- current = current.cdkey("blkx");
- printlnVerbose("Found " + current.getChildren().length + " partitions:");
-
- byte[] tmp = new byte[0x40000];
- byte[] otmp = new byte[0x40000];
-
- byte[] zeroblock = new byte[4096];
- /* I think java always zeroes its arrays on creation...
- but let's play safe. */
- for(int y = 0; y < zeroblock.length; ++y)
- zeroblock[y] = 0;
-
- LinkedList blocks = new LinkedList();
-
- //long lastOffs = 0;
- long lastOutOffset = 0;
- long lastInOffset = 0;
- long totalSize = 0;
- boolean errorsFound = false;
- reportProgress(0);
- for(XMLElement xe : current.getChildren()) {
- if(progmon != null && progmon.isCanceled()) System.exit(0);
- if(xe instanceof XMLNode) {
- XMLNode xn = (XMLNode)xe;
- byte[] data = Base64.decode(xn.getKeyValue("Data"));
-
- long partitionSize = calculatePartitionSize(data);
- totalSize += partitionSize;
-
- printlnVerbose(" " + xn.getKeyValue("Name"));
- printlnVerbose(" ID: " + xn.getKeyValue("ID"));
- printlnVerbose(" Attributes: " + xn.getKeyValue("Attributes"));
- printlnVerbose(" Partition map data length: " + data.length + " bytes");
- printlnVerbose(" Partition size: " + partitionSize + " bytes");
- if(verbose) {
- printlnVerbose(" Dumping blkx...");
- FileOutputStream fos = new FileOutputStream(xn.getKeyValue("ID") + ".blkx");
- fos.write(data);
- fos.close();
- }
-
- if(DEBUG) {
- File dumpFile = new File("data " + xn.getKeyValue("ID") + ".bin");
- println(" Dumping partition map to file: " + dumpFile);
-
- FileOutputStream dump = new FileOutputStream(dumpFile);
- dump.write(data);
- dump.close();
- }
-
- int offset = 0xCC;
- int blockType = 0;
-
- /* Offset of the input data for the current block in the input file */
- long inOffset = 0;
- /* Size of the input data for the current block */
- long inSize = 0;
- /* Offset of the output data for the current block in the output file */
- long outOffset = 0;
- /* Size of the output data (possibly larger than inSize because of
- decompression, zero expansion...) */
- long outSize = 0;
-
- long lastByteReadInBlock = -1;
-
- boolean addInOffset = false;
-
- //, lastInOffs = 0;
- int blockCount = 0;
- long previousPercentage = -1;
- while(blockType != BT_END) {
- if(progmon != null && progmon.isCanceled()) System.exit(0);
- DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
- int bytesSkipped = 0;
- while(bytesSkipped < offset)
- bytesSkipped += dis.skipBytes(offset-bytesSkipped);
-
- blockType = dis.readInt();
- int skipped = dis.readInt(); //Skip 4 bytes forward
- outOffset = dis.readLong()*0x200;//(dis.readInt() & 0xffffffffL)*0x200; //unsigned int -> long
- //dis.readInt(); //Skip 4 bytes forward
- outSize = dis.readLong()*0x200;//(dis.readInt() & 0xffffffffL)*0x200; //unsigned int -> long
- inOffset = dis.readLong();// & 0xffffffffL; //unsigned int -> long
- //dis.readInt(); //Skip 4 bytes forward
- inSize = dis.readLong();//dis.readInt() & 0xffffffffL; //unsigned int -> long
-
- blocks.add(new DMGBlock(blockType, skipped, outOffset, outSize, inOffset, inSize));
-
- if(lastByteReadInBlock == -1)
- lastByteReadInBlock = inOffset;
- lastByteReadInBlock += inSize;
-
- /* The lines below are a "hack" that I had to do to make dmgx work with
- certain dmg-files. I don't understand the issue at all, which is why
- this hack is here, but sometimes inOffset == 0 means that it is 0
- relative to the previous partition's last inOffset. And sometimes it
- doesn't (meaning the actual position 0 in the dmg file). */
- if(addInOffset)
- inOffset += lastInOffset;
- else if(inOffset == 0) {
- addInOffset = true;
- inOffset += lastInOffset;
- }
- outOffset += lastOutOffset;
-
- if(DEBUG) {
- println("outOffset=" + outOffset + " outSize=" + outSize +
- " inOffset=" + inOffset + " inSize=" + inSize +
- " lastOutOffset=" + lastOutOffset + " lastInOffset=" + lastInOffset
- /*+ " lastInOffs=" + lastInOffs + " lastOffs=" + lastOffs*/);
- }
-
- if(blockType == BT_ADC) {
- println(" " + blockCount + ". BT_ADC not supported.");
- if(!testOnly)
- System.exit(0);
- }
- else if(blockType == BT_ZLIB) {
- if(DEBUG)
- println(" " + blockCount + ". BT_ZLIB processing...");
-
- if(!testOnly && isoRaf.getFilePointer() != outOffset)
- println(" " + blockCount + ". BT_ZLIB FP != outOffset (" +
- isoRaf.getFilePointer() + " != " + outOffset + ")");
-
- dmgRaf.seek(/*lastOffs+*/inOffset);
-
- if(tmp.length < inSize)
- tmp = new byte[(int)inSize];
-
- long totalBytesRead = 0;
- while(totalBytesRead < inSize) {
- totalBytesRead += dmgRaf.read(tmp, (int)totalBytesRead, Math.min((int)(inSize-totalBytesRead), tmp.length));
- }
- long progressPercentage = dmgRaf.getFilePointer()*100/dmgRaf.length();
- if(progressPercentage != previousPercentage) {
- reportProgress(progressPercentage);
- previousPercentage = progressPercentage;
- }
-
- Inflater inflater = new Inflater();
- inflater.setInput(tmp, 0, (int)totalBytesRead);
-
- if(otmp.length < outSize)
- otmp = new byte[(int)outSize];
-
- int bytesInflated = 0;
- while(true) {
- try {
- int counter = 0;
- while(bytesInflated < outSize && counter++ < 10) {
- int old = bytesInflated;
- bytesInflated += inflater.inflate(otmp, bytesInflated, (int)(outSize-bytesInflated));
- if(old == bytesInflated)
- println("Nothing new! finished()=" + inflater.finished() + " needsInput()=" + inflater.needsInput() + " needsDictionary()=" + inflater.needsDictionary() + " getAdler()=" + inflater.getAdler() + " getBytesRead()=" + inflater.getBytesRead() + " getBytesWritten()=" + inflater.getBytesWritten() + " getRemaining()=" + inflater.getRemaining());
- }
- //System.out.println(" Inflated " + bytesInflated + " bytes. Left in buffer: " + inflater.getRemaining() + " bytes inSize="+inSize+" outSize="+outSize);
-
- if(inflater.getRemaining() == 0) {
- //System.out.println(" done!");
- break;
- }
- else {
- println(" " + blockCount + ". BT_ZLIB ERROR: otmp contents lost! (should not happen...)",
- " outSize=" + outSize + " inSize=" + inSize + " tmp.length=" + tmp.length + " otmp.length=" + otmp.length + " bytesInflated=" + bytesInflated + " inflater.getRemaining()=" + inflater.getRemaining());
-// if(bytesInflated == 0)
- throw new RuntimeException("WTF");
- }
- }
- catch(DataFormatException dfe) {
- println(" " + blockCount + ". BT_ZLIB Could not decode...");
- if(!DEBUG) {
- println("outOffset=" + outOffset + " outSize=" + outSize +
- " inOffset=" + inOffset + " inSize=" + inSize +
- " lastOutOffset=" + lastOutOffset + " lastInOffset=" + lastInOffset);
- }
- dfe.printStackTrace();
- if(!testOnly)
- System.exit(0);
- else {
- println(" Testing mode, so continuing...");
- //System.exit(0);
- errorsFound = true;
- break;
- }
- }
-
- }
- inflater.end();
-
- if(!testOnly)
- isoRaf.write(otmp, 0, (int)outSize);
-
- //lastInOffs = inOffset+inSize;
- }
- else if(blockType == BT_BZIP2) {
- println(" " + blockCount + ". BT_BZIP2 not currently supported.");
- if(!testOnly)
- System.exit(0);
- }
- else if(blockType == BT_COPY) {
- if(DEBUG)
- println(" " + blockCount + ". BT_COPY processing...");
-
- if(!testOnly && isoRaf.getFilePointer() != outOffset)
- println(" " + blockCount + ". BT_COPY FP != outOffset (" + isoRaf.getFilePointer() + " != " + outOffset + ")");
- dmgRaf.seek(/*lastOffs+*/inOffset);
-
- int bytesRead = dmgRaf.read(tmp, 0, Math.min((int)inSize, tmp.length));
- long totalBytesRead = bytesRead;
- while(bytesRead != -1) {
- long progressPercentage = dmgRaf.getFilePointer()*100/dmgRaf.length();
- if(progressPercentage != previousPercentage) {
- reportProgress(progressPercentage);
- previousPercentage = progressPercentage;
- }
-
-
- if(!testOnly)
- isoRaf.write(tmp, 0, bytesRead);
- if(totalBytesRead >= inSize)
- break;
- bytesRead = dmgRaf.read(tmp, 0, Math.min((int)(inSize-totalBytesRead), tmp.length));
- if(bytesRead > 0)
- totalBytesRead += bytesRead;
- }
-
- //lastInOffs = inOffset+inSize;
- }
- else if(blockType == BT_ZERO) {
- if(DEBUG)
- println(" " + blockCount + ". BT_ZERO processing...");
- if(!testOnly && isoRaf.getFilePointer() != outOffset)
- println(" " + blockCount + ". BT_ZERO FP != outOffset (" +
- isoRaf.getFilePointer() + " != " + outOffset + ")");
-
- long progressPercentage = dmgRaf.getFilePointer()*100/dmgRaf.length();
- if(progressPercentage != previousPercentage) {
- reportProgress(progressPercentage);
- previousPercentage = progressPercentage;
- }
-
- long numberOfZeroBlocks = outSize/zeroblock.length;
- int numberOfRemainingBytes = (int)(outSize%zeroblock.length);
- for(int j = 0; j < numberOfZeroBlocks; ++j) {
- if(!testOnly)
- isoRaf.write(zeroblock);
- }
- if(!testOnly)
- isoRaf.write(zeroblock, 0, numberOfRemainingBytes);
-
- //lastInOffs = inOffset+inSize;
- }
- else if(blockType == BT_UNKNOWN) {
- /* I have no idea what this blocktype is... but it's common, and usually
- doesn't appear more than 2-3 times in a dmg. As long as its input and
- output sizes are 0, there's no reason to complain... is there? */
- if(DEBUG)
- println(" " + blockCount + ". BT_UNKNOWN processing...");
- if(!(inSize == 0 && outSize == 0)) {
- println(" " + blockCount + ". WARNING! Blocktype BT_UNKNOWN had non-zero sizes...",
- " inSize=" + inSize + ", outSize=" + outSize);
- //println(" The author of the program would be pleased if you contacted him about this.");
- // ...or would I?
- }
- }
- else if(blockType == BT_END) {
- if(DEBUG)
- println(" " + blockCount + ". BT_END processing...");
- if(!testOnly && isoRaf.getFilePointer() != outOffset)
- println(" " + blockCount + ". BT_END FP != outOffset (" +
- isoRaf.getFilePointer() + " != " + outOffset + ")");
-
- //lastOffs += lastInOffs;
- lastOutOffset = outOffset;
- lastInOffset += lastByteReadInBlock;
- }
- else {
- println(" " + blockCount + ". WARNING: previously unseen blocktype " + blockType + " [0x" + Integer.toHexString(blockType) + "]",
- " " + blockCount + ". outOffset=" + outOffset + " outSize=" + outSize + " inOffset=" + inOffset + " inSize=" + inSize);
-
- if(!testOnly && isoRaf.getFilePointer() != outOffset)
- println(" " + blockCount + ". unknown blocktype FP != outOffset (" +
- isoRaf.getFilePointer() + " != " + outOffset + ")");
-
- }
-
- offset += 0x28;
- ++blockCount;
- }
- }
- }
- //printlnVerbose("Progress: 100% Done!");
- reportProgress(100);
- String errors = errorsFound?"There were errors...":"No errors reported.";
- if(!graphical) {
- newline();
- println(errors);
- printlnVerbose("Total extracted bytes: " + totalSize + " B");
- }
- else {
- progmon.close();
- JOptionPane.showMessageDialog(null, "Extraction complete! " + errors + "\n" +
- "Total extracted bytes: " + totalSize + " B",
- "Information", JOptionPane.INFORMATION_MESSAGE);
- System.exit(0);
- }
-// System.out.println("blocks.size()=" + blocks.size());
-// for(DMGBlock b : blocks)
-// System.out.println(" " + b.toString());
- LinkedList merged = mergeBlocks(blocks);
-// System.out.println("merged.size()=" + merged.size());
-// for(DMGBlock b : merged)
-// System.out.println(" " + b.toString());
- System.out.println("Extracting all the parts not containing block data from source file:");
- int i = 1;
- DMGBlock previous = null;
- for(DMGBlock b : merged) {
- if(previous == null && b.inOffset > 0) {
- String filename = i++ + ".block";
- System.out.print(" " + filename + "...");
- FileOutputStream curFos = new FileOutputStream(new File(filename));
- dmgRaf.seek(0);
- byte[] data = new byte[(int)(b.inOffset)];
- dmgRaf.read(data);
- curFos.write(data);
- curFos.close();
- }
- else if(previous != null) {
- String filename = i++ + ".block";
- System.out.print(" " + filename + "...");
- FileOutputStream curFos = new FileOutputStream(new File(filename));
- dmgRaf.seek(previous.inOffset+previous.inSize);
- byte[] data = new byte[(int)(b.inOffset-(previous.inOffset+previous.inSize))];
- dmgRaf.read(data);
- curFos.write(data);
- curFos.close();
- }
- previous = b;
- }
- if(previous.inOffset+previous.inSize != dmgRaf.length()) {
- String filename = i++ + ".block";
- System.out.print(" " + filename + "...");
- FileOutputStream curFos = new FileOutputStream(new File(filename));
- dmgRaf.seek(previous.inOffset+previous.inSize);
- byte[] data = new byte[(int)(dmgRaf.length()-(previous.inOffset+previous.inSize))];
- dmgRaf.read(data);
- curFos.write(data);
- curFos.close();
- }
- dmgRaf.close();
- System.out.println("done!");
- }
-
- public static void parseArgs(String[] args) {
- boolean parseSuccessful = false;
- try {
- /* Take care of the options... */
- int i;
- for(i = 0; i < args.length; ++i) {
- String cur = args[i];
- if(!cur.startsWith("-"))
- break;
- else if(cur.equals("-gui"))
- graphical = true;
- else if(cur.equals("-v"))
- verbose = true;
- else if(cur.equals("-startupcommand")) {
- startupCommand = args[i+1];
- ++i;
- }
- }
-
- println(APPNAME + " " + BUILDSTRING,
- "Copyright (c) 2006 Erik Larsson ",
- " written from the source code to the original dmg2iso program",
- " Copyright (c) 2004 vu1tur ",
- " also using the iharder Base64 Encoder/Decoder ",
- "",
- "This program is distributed under the GNU General Public License version 2 or",
- "later.",
- "See for the details.",
- "");
-
- if(i == args.length) {
- dmgFile = getInputFileFromUser();
- if(dmgFile == null)
- System.exit(0);
- if(getOutputConfirmationFromUser()) {
- isoFile = getOutputFileFromUser();
- if(isoFile == null)
- System.exit(0);
- }
- }
- else {
- dmgFile = new File(args[i++]);
- if(!dmgFile.exists()) {
- println("File \"" + dmgFile + "\" could not be found!");
- System.exit(0);
- }
-
- if(i == args.length-1)
- isoFile = new File(args[i]);
- else if(i != args.length)
- throw new Exception();
- }
-
- parseSuccessful = true;
- } catch(Exception e) {
- println();
- println(" usage: " + startupCommand + " [options] []");
- println(" if an iso-file is not supplied, the program will simulate an extraction");
- println(" (useful for detecting errors in dmg-files)");
- println();
- System.exit(0);
- }
- }
-
- public static long calculatePartitionSize(byte[] data) throws IOException {
- long partitionSize = 0;
- DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));
- long totalBytesRead;
- totalBytesRead = 0;
- while(totalBytesRead < 0xCC)
- totalBytesRead += dis.skip(0xCC);
-
- while(totalBytesRead < data.length) {
- int bytesRead = 0;
- while(bytesRead < 0x10)
- bytesRead += dis.skip(0x10-bytesRead);
-
- partitionSize += dis.readLong()*0x200;
- bytesRead += 0x8;
-
- while(bytesRead < 0x28)
- bytesRead += dis.skip(0x28-bytesRead);
- totalBytesRead += bytesRead;
- }
- return partitionSize;
- }
-
- /** Never used. Java is big-endian. */
- public static int swapEndian(int i) {
- return
- ((i & 0xff000000) >> 24) |
- ((i & 0x00ff0000) >> 8 ) |
- ((i & 0x0000ff00) << 8 ) |
- ((i & 0x000000ff) << 24);
- }
-
- public static void printCurrentLine(String s) {
- System.out.print(BACKSPACE79);
- System.out.print(s);
- }
- public static void println() {
- System.out.print(BACKSPACE79);
- System.out.println();
- }
- public static void println(String... lines) {
- if(!graphical) {
- System.out.print(BACKSPACE79);
- for(String s : lines)
- System.out.println(s);
- }
- else {
- String resultString = null;
- for(String s : lines) {
- if(resultString == null)
- resultString = s;
- else
- resultString += "\n" + s;
- }
- JOptionPane.showMessageDialog(null, resultString,
- APPNAME, JOptionPane.INFORMATION_MESSAGE);
- }
- }
- public static void printlnVerbose() {
- if(verbose) {
- System.out.print(BACKSPACE79);
- System.out.println();
- }
- }
- public static void printlnVerbose(String... lines) {
- if(verbose) {
- System.out.print(BACKSPACE79);
- for(String s : lines)
- System.out.println(s);
- }
- }
-
- public static void newline() {
- System.out.println();
- }
-
- public static void reportProgress(long progressPercentage) {
- if(!graphical) {
- printCurrentLine("--->Progress: " + progressPercentage + "%");
- }
- else {
- if(progmon == null) {
- progmon = new ProgressMonitor(null, "Extracting dmg to iso...", "0%", 0, 100);
- progmon.setProgress(0);
- progmon.setMillisToPopup(0);
- }
- progmon.setProgress((int)progressPercentage);
- progmon.setNote(progressPercentage + "%");
- }
- }
-
- public static File getInputFileFromUser() throws IOException {
- if(!graphical) {
- //String s = "";
- while(true) {
- printCurrentLine("Please specify the path to the dmg file to extract from: ");
- File f = new File(stdin.readLine().trim());
- while(!f.exists()) {
- println("File does not exist!");
- printCurrentLine("Please specify the path to the dmg file to extract from: ");
- f = new File(stdin.readLine().trim());
- }
- return f;
- }
- }
- else {
- SimpleFileFilter sff = new SimpleFileFilter();
- sff.addExtension("dmg");
- sff.setDescription("DMG disk image files");
- JFileChooser jfc = new JFileChooser();
- jfc.setFileFilter(sff);
- jfc.setMultiSelectionEnabled(false);
- jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
- jfc.setDialogTitle("Choose the dmg-file to read...");
- while(true) {
- if(jfc.showDialog(null, "Open") == JFileChooser.APPROVE_OPTION) {
- File f = jfc.getSelectedFile();
- if(f.exists())
- return f;
- else
- JOptionPane.showMessageDialog(null, "The file does not exist! Choose again...",
- "Error", JOptionPane.ERROR_MESSAGE);
- }
- else
- return null;
- }
- }
- }
- public static boolean getOutputConfirmationFromUser() throws IOException {
- if(!graphical) {
- String s = "";
- while(true) {
- printCurrentLine("Do you want to specify an output file (y/n)? ");
- s = stdin.readLine().trim();
- if(s.equalsIgnoreCase("y"))
- return true;
- else if(s.equalsIgnoreCase("n"))
- return false;
- }
- }
- else {
- return JOptionPane.showConfirmDialog(null, "Do you want to specify an output file?",
- "Confirmation", JOptionPane.YES_NO_OPTION,
- JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION;
- }
- }
- public static File getOutputFileFromUser() throws IOException {
- final String msg1 = "Please specify the path of the iso file to extract to: ";
- final String msg2 = "The file already exists. Do you want to overwrite?";
- if(!graphical) {
- while(true) {
- printCurrentLine(msg1);
- File f = new File(stdin.readLine().trim());
- while(f.exists()) {
- while(true) {
- printCurrentLine(msg2 + " (y/n)? ");
- String s = stdin.readLine().trim();
- if(s.equalsIgnoreCase("y"))
- return f;
- else if(s.equalsIgnoreCase("n"))
- break;
- }
- printCurrentLine(msg1);
- f = new File(stdin.readLine().trim());
- }
- return f;
- }
- }
- else {
- JFileChooser jfc = new JFileChooser();
- jfc.setMultiSelectionEnabled(false);
- jfc.setFileSelectionMode(JFileChooser.FILES_ONLY);
- jfc.setDialogTitle("Choose the output iso-file...");
- while(true) {
- if(jfc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
- File f = jfc.getSelectedFile();
- if(!f.exists())
- return f;
- else if(JOptionPane.showConfirmDialog(null, msg2, "Confirmation", JOptionPane.YES_NO_OPTION,
- JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) {
- return f;
- }
- }
- else
- return null;
- }
- }
- }
-
- public static LinkedList mergeBlocks(LinkedList blockList) {
- LinkedList result = new LinkedList();
- Iterator it = blockList.iterator();
- DMGBlock previous = it.next();
- DMGBlock current;
- while(it.hasNext()) {
- current = it.next();
- if(current.inSize != 0) {
- if(current.inOffset == previous.inOffset+previous.inSize) {
- DMGBlock mergedBlock = new DMGBlock(previous.blockType, previous.skipped, previous.outOffset, previous.outSize+current.outSize, previous.inOffset, previous.inSize+current.inSize);
- previous = mergedBlock;
- }
- else {
- result.addLast(previous);
- previous = current;
- }
- }
- }
- result.addLast(previous);
- return result;
- }
-
- public static class DMGBlock {
- public int blockType;
- public int skipped;
- public long outOffset;
- public long outSize;
- public long inOffset;
- public long inSize;
-
- public DMGBlock(int blockType, int skipped, long outOffset, long outSize, long inOffset, long inSize) {
- this.blockType = blockType;
- this.skipped = skipped;
- this.outOffset = outOffset;
- this.outSize = outSize;
- this.inOffset = inOffset;
- this.inSize = inSize;
- }
-
- public String toString() {
- return "[type: 0x" + Integer.toHexString(blockType) + " skipped: 0x" + Integer.toHexString(skipped) + " outOffset: " + outOffset + " outSize: " + outSize + " inOffset: " + inOffset + " inSize: " + inSize + "]";
- }
- }
-}
-
diff --git a/src/org/catacombae/dmgx/DMGExtractorGraphical.java b/src/org/catacombae/dmgx/DMGExtractorGraphical.java
deleted file mode 100644
index 7aa80e3..0000000
--- a/src/org/catacombae/dmgx/DMGExtractorGraphical.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*-
- * Copyright (C) 2006 Erik Larsson
- *
- * All rights reserved.
- *
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-package org.catacombae.dmgx;
-
-public class DMGExtractorGraphical {
- public static void main(String[] args) throws Exception {
- String[] newargs = new String[1];
- newargs[0] = "-gui";
- DMGExtractor.main(newargs);
- }
-}
diff --git a/src/org/catacombae/dmgx/DMGInfo.java b/src/org/catacombae/dmgx/DMGInfo.java
deleted file mode 100644
index 479cd50..0000000
--- a/src/org/catacombae/dmgx/DMGInfo.java
+++ /dev/null
@@ -1,152 +0,0 @@
-package org.catacombae.dmgx;
-
-import java.io.*;
-import javax.swing.*;
-
-public class DMGInfo {
- public static void main(String[] args) throws IOException {
- RandomAccessFile inRaf = new RandomAccessFile(args[0], "r");
-
- // Check opening signature "koly"
- inRaf.seek(inRaf.length()-512);
- byte[] koly = new byte[4];
- inRaf.readFully(koly);
- String kolySignature = new String(koly, "US-ASCII");
- if(!kolySignature.equals("koly"))
- System.out.println("ERROR: Signature incorrect. Found \"" + kolySignature + "\" instead of \"koly\".");
- else
- System.out.println("\"koly\" signature OK.");
-
- // Read partition list start location 1 and end location
- inRaf.seek(inRaf.length()-0x1E0);
-
- // -0x1E0: address to plist xml structure (8 bytes)
- long plistAddress1 = inRaf.readLong();
- System.out.println("Address to plist: 0x" + Long.toHexString(plistAddress1));
-
-
- // -0x1D8: address to end of plist xml structure (8 bytes)
- long plistEndAddress = inRaf.readLong();
- System.out.println("Address to end of plist: 0x" + Long.toHexString(plistEndAddress));
- System.out.println(" Implication: plist size = " + (plistEndAddress-plistAddress1) + " B");
-
-
- long unknown_0x1D0 = inRaf.readLong();
-
-
- long unknown_0x1C8 = inRaf.readLong();
- if(unknown_0x1C8 != 0x0000000100000001L)
- System.out.println("Assertion failed! unknown_0x1C8 == 0x" +
- Long.toHexString(unknown_0x1C8) + " and not 0x0000000100000001");
-
-
- long unknown_0x1C0 = inRaf.readLong();
-
-
- long unknown_0x1B8 = inRaf.readLong();
- System.out.println("Some kind of signature? Value: 0x" + Long.toHexString(unknown_0x1B8));
-
-
- long unknown_0x1B0 = inRaf.readLong();
- if(unknown_0x1B0 != 0x0000000200000020L)
- System.out.println("Assertion failed! unknown_0x1B0 == 0x" +
- Long.toHexString(unknown_0x1B0) + " and not 0x0000000200000020");
-
-
- int unknown_0x1A8 = inRaf.readInt();
-
-
- int unknown_0x1A4 = inRaf.readInt();
- System.out.println("Some kind of unit size? Value: 0x" +
- Integer.toHexString(unknown_0x1A4) + " / " + unknown_0x1A4);
-
-
- // Unknown chunk of data (120 bytes)
- byte[] unknown_0x1A0 = new byte[120];
- inRaf.readFully(unknown_0x1A0);
-
-
- // -0x128: address to beginning of plist xml structure (second occurrence) (8 bytes)
- long plistAddress2 = inRaf.readLong();
- System.out.println("Address to plist (2): 0x" + Long.toHexString(plistAddress2));
-
-
- // -0x120: size of plist xml structure (8 bytes)
- long plistSize = inRaf.readLong();
- System.out.println("plist size: " + plistSize + " B");
-
-
- // Unknown chunk of data (120 bytes)
- byte[] unknown_0x118 = new byte[120];
- inRaf.readFully(unknown_0x118);
-
-
- // -0x0A0: Checksum type identifier (4 bytes)
- System.out.print("Checksum type");
- int cs_type = inRaf.readInt();
- if(cs_type == 0x00000002)
- System.out.println(": CRC-32");
- else if(cs_type == 0x00000004)
- System.out.println(": MD5");
- else
- System.out.println(" unknown! Data: 0x" + Integer.toHexString(cs_type));
-
-
- // -0x09C: Length of checksum in bits (4 bytes)
- int cs_length = inRaf.readInt();
- System.out.println("Checksum length: " + cs_length + " bits");
-
-
- // -0x098: Checksum ((cs_length/8) bytes)
- byte[] checksum = new byte[cs_length/8];
- inRaf.readFully(checksum);
- System.out.println("Checksum: 0x" + byteArrayToHexString(checksum).toUpperCase());
-
- /*
- if(unknown_0x != 0xL)
- System.out.println("Assertion failed! unknown_0x == 0x" + Long.toHexString(unknown_0x) + " and not 0xL");
- */
- }
-
- public static String byteArrayToHexString(byte[] array) {
- StringBuilder result = new StringBuilder();
- for(byte b : array) {
- String s = Integer.toHexString(b & 0xFF);
- if(s.length() == 1)
- s = "0" + s;
- result.append(s);
- }
- return result.toString();
- }
-}
-
-class DMGInfoFrame extends JFrame {
- private JTabbedPane mainPane;
-
- public DMGInfoFrame() {
- super("DMGInfo");
-
- mainPane = new JTabbedPane(JTabbedPane.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
-
- StatisticsPanel statisticsPanel;
- statisticsPanel = new StatisticsPanel();
- //mainPane.addTab(statisticsPanel, "Statistics");
- }
-}
-
-class StatisticsPanel extends JPanel {
- JPanel blocktypeCountPanel;
-
- public StatisticsPanel(/*DMGFile dmgFile*/) {}
-}
-
-/*
-class DMGFile extends RandomAccessFile {
- public DMGFile(File file, String mode) {
- super(file, mode);
- }
- public DMGFile(String name, String mode) {
- super(name, mode);
- }
-}
-*/
diff --git a/src/org/catacombae/dmgx/DMGInfoWindow.java b/src/org/catacombae/dmgx/DMGInfoWindow.java
deleted file mode 100644
index 03230dd..0000000
--- a/src/org/catacombae/dmgx/DMGInfoWindow.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package org.catacombae.dmgx;
-
-import net.iharder.dnd.FileDrop;
-import org.catacombae.dmgx.gui.*;
-import java.awt.*;
-import javax.swing.*;
-
-public class DMGInfoWindow extends JFrame {
- private DMGInfoPanel infoPanel;
-
- public DMGInfoWindow() {
- infoPanel = new DMGInfoPanel();
- add(infoPanel, BorderLayout.CENTER);
-
- // Register handler for file drag&drop events
- new FileDrop(this, new FileDrop.Listener() {
- public void filesDropped(java.io.File[] files) {
- if(files.length > 0)
- ;//loadFile(files[0]);
- }
- });
-
- pack();
- setLocationRelativeTo(null);
-
- }
- public static void main(String[] args) {
- new DMGInfoWindow().setVisible(true);
- }
-}
\ No newline at end of file
diff --git a/src/org/catacombae/dmgx/DMGMetadata.java b/src/org/catacombae/dmgx/DMGMetadata.java
deleted file mode 100644
index 725194c..0000000
--- a/src/org/catacombae/dmgx/DMGMetadata.java
+++ /dev/null
@@ -1,348 +0,0 @@
-package org.catacombae.dmgx;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.DataInput;
-import java.io.DataInputStream;
-import java.io.DataOutput;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.io.RandomAccessFile;
-import java.io.UnsupportedEncodingException;
-import java.util.LinkedList;
-
-public class DMGMetadata {
- public static final long PLIST_ADDRESS_1 = 0x1E0;
- public static final long PLIST_ADDRESS_2 = 0x128;
-
- public final byte[] rawData;
-
- public final byte[] plistXmlData;
- public final byte[] unknown1_256;
- public PartitionBlockList[] blockLists;
- public final byte[] unknown2_12;
- public APMPartition[] partitions;
- public final byte[] unknown3_unknown;
- public final byte[] koly;
-
- public DMGMetadata(RandomAccessFile dmgFile) throws IOException {
- dmgFile.seek(dmgFile.length()-PLIST_ADDRESS_1);
- long plistBegin1 = dmgFile.readLong();
- long plistEnd = dmgFile.readLong();
- dmgFile.seek(dmgFile.length()-PLIST_ADDRESS_2);
- long plistBegin2 = dmgFile.readLong();
- long plistSize = dmgFile.readLong();
-
- rawData = new byte[(int)(dmgFile.length()-plistBegin1)];
- dmgFile.seek(plistBegin1);
- dmgFile.readFully(rawData);
-
- plistXmlData = new byte[(int)plistSize];
- dmgFile.seek(plistBegin1);
- dmgFile.readFully(plistXmlData);
-
- unknown1_256 = new byte[256];
- dmgFile.readFully(unknown1_256);
-
- LinkedList blockListList = new LinkedList();
- int length = dmgFile.readInt();
- byte[] fourcc = new byte[4];
- dmgFile.readFully(fourcc);
- String fourccString = new String(fourcc, "US-ASCII");
- dmgFile.seek(dmgFile.getFilePointer()-4);
- while(fourccString.equals("mish")) {
- blockListList.add(new PartitionBlockList(dmgFile, length));
- length = dmgFile.readInt();
- dmgFile.readFully(fourcc);
- fourccString = new String(fourcc, "US-ASCII");
- dmgFile.seek(dmgFile.getFilePointer()-4);
- }
- blockLists = blockListList.toArray(new PartitionBlockList[blockListList.size()]);
-
- unknown2_12 = new byte[12];
- dmgFile.readFully(unknown2_12);
-
- LinkedList partitionList = new LinkedList();
- byte[] currentPartitionEntry = new byte[0x200];
- dmgFile.readFully(currentPartitionEntry);
- byte[] pmSig = new byte[2];
- pmSig[0] = currentPartitionEntry[0];
- pmSig[1] = currentPartitionEntry[1];
- while(new String(pmSig, "US-ASCII").equals("PM")) {
- partitionList.addLast(new APMPartition(currentPartitionEntry));
- dmgFile.readFully(currentPartitionEntry);
- pmSig[0] = currentPartitionEntry[0];
- pmSig[1] = currentPartitionEntry[1];
- }
- while(onlyZeros(currentPartitionEntry))
- dmgFile.readFully(currentPartitionEntry);
- partitions = partitionList.toArray(new APMPartition[partitionList.size()]);
-
- unknown3_unknown = new byte[(int)(dmgFile.length()-dmgFile.getFilePointer()-512)];
- dmgFile.readFully(unknown3_unknown);
-
- koly = new byte[512];
- dmgFile.seek(dmgFile.length()-koly.length);
- dmgFile.readFully(koly);
-
- if(dmgFile.getFilePointer() != dmgFile.length())
- System.out.println("MISCALCULATION! FP=" + dmgFile.getFilePointer() + " LENGTH=" + dmgFile.length());
- }
-
- public void printInfo(PrintStream ps) {
- ps.println("block list:");
- for(PartitionBlockList pbl : blockLists)
- pbl.printInfo(ps);
- ps.println("partitions:");
- for(APMPartition ap : partitions)
- ap.printPartitionInfo(ps);
- }
-
- private static boolean onlyZeros(byte[] array) {
- for(int i = 0; i < array.length; ++i) {
- if(array[i] != 0)
- return false;
- }
- return true;
- }
-
- public static class PartitionBlockList {
- public final byte[] header = new byte[0xCC];
- public final BlockDescriptor[] descriptors;
-
- public PartitionBlockList(byte[] entryData) throws IOException {
- this(new DataInputStream(new ByteArrayInputStream(entryData)), entryData.length);
- }
-
- public PartitionBlockList(DataInput di, int length) throws IOException {
- int position = 0;
- di.readFully(header);
- position += header.length;
- LinkedList descs = new LinkedList();
- while(position < length) {
- descs.addLast(new BlockDescriptor(di));
- position += 0x28;
- }
- descriptors = descs.toArray(new BlockDescriptor[descs.size()]);
- }
-
- public void printInfo(PrintStream ps) {
- for(BlockDescriptor bd : descriptors)
- ps.println(bd.toString());
- }
- }
-
- public static class BlockDescriptor {
- // Known block types
- public static final int BT_COPY = 0x00000001;
- public static final int BT_ZERO = 0x00000002;
- public static final int BT_ZLIB = 0x80000005;
- public static final int BT_END = 0xffffffff;
- public static final int BT_UNKNOWN1 = 0x7ffffffe;
- private static final int[] KNOWN_BLOCK_TYPES = { BT_COPY,
- BT_ZERO,
- BT_ZLIB,
- BT_END,
- BT_UNKNOWN1 };
- private static final String[] KNOWN_BLOCK_TYPE_NAMES = { "BT_COPY",
- "BT_ZERO",
- "BT_ZLIB",
- "BT_END",
- "BT_UNKNOWN1" };
-
- private int blockType;
- private int unknown;
- private long outOffset;
- private long outSize;
- private long inOffset;
- private long inSize;
-
- public BlockDescriptor() {}
-
- public BlockDescriptor(byte[] entryData) throws IOException {
- this(new DataInputStream(new ByteArrayInputStream(entryData)));
- }
-
- public BlockDescriptor(DataInput dataIn) throws IOException {
- blockType = dataIn.readInt();
- unknown = dataIn.readInt();
- outOffset = dataIn.readLong()*0x200;
- outSize = dataIn.readLong()*0x200;
- inOffset = dataIn.readLong();
- inSize = dataIn.readLong();
- }
-
- public byte[] toBytes() throws IOException {
- ByteArrayOutputStream result = new ByteArrayOutputStream(0x28);
- DataOutputStream dataOut = new DataOutputStream(result);
-
- dataOut.writeInt(blockType); // 4 bytes
- dataOut.writeInt(unknown); // 4 bytes
- if((outOffset % 0x200) != 0)
- throw new RuntimeException("Out offset must be aligned to 0x200 block size!");
- dataOut.writeLong(outOffset/0x200); // 8 bytes
- if((outSize % 0x200) != 0)
- throw new RuntimeException("Out size must be aligned to 0x200 block size!");
- dataOut.writeLong(outSize/0x200); // 8 bytes
- dataOut.writeLong(inOffset); // 8 bytes
- dataOut.writeLong(inSize); // 8 bytes
- // sum = 4 + 4 + 8 + 8 + 8 + 8 = 40 = 0x28
-
- dataOut.flush();
- dataOut.close();
- return result.toByteArray();
- }
-
- public int getBlockType() { return blockType; }
- public int getUnknown() { return unknown; }
- public String getUnknownAsString() throws IOException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream(4);
- DataOutputStream dos = new DataOutputStream(baos);
- dos.write(unknown);
- dos.close();
- return new String(baos.toByteArray(), "US-ASCII");
- }
- public long getOutOffset() { return outOffset; }
- public long getOutSize() { return outSize; }
- public long getInOffset() { return inOffset; }
- public long getInSize() { return inSize; }
-
- public void setBlockType(int blockType) { this.blockType = blockType; }
- public void setUnknown(int unknown) { this.unknown = unknown; }
- public void setOutOffset(long outOffset) {
- if((outOffset % 0x200) != 0)
- throw new RuntimeException("Out offset must be aligned to 0x200 block size!");
- this.outOffset = outOffset;
- }
- public void setOutSize(long outSize) {
- if((outSize % 0x200) != 0)
- throw new RuntimeException("Out size must be aligned to 0x200 block size!");
- this.outSize = outSize;
- }
- public void setInOffset(long inOffset) { this.inOffset = inOffset; }
- public void setInSize(long inSize) { this.inSize = inSize; }
-
- public boolean hasKnownBlockType() {
- for(int current : KNOWN_BLOCK_TYPES) {
- if(blockType == current)
- return true;
- }
- return false;
- }
-
- public String getBlockTypeName() {
- for(int i = 0; i < KNOWN_BLOCK_TYPES.length; ++i) {
- int current = KNOWN_BLOCK_TYPES[i];
- if(blockType == current)
- return KNOWN_BLOCK_TYPE_NAMES[i];
- }
- return null;
- }
-
- public String toString() {
- StringBuilder result = new StringBuilder("[BlockDescriptor");
-
- String blockTypeString = "\"" + getBlockTypeName() + "\"";
- if(blockTypeString == null)
- blockTypeString = "0x" + Integer.toHexString(blockType) + " (unknown type)";
-
- result.append(" blockType=" + blockTypeString);
- result.append(" unknown=" + Integer.toHexString(unknown));
- result.append(" outOffset=" + outOffset);
- result.append(" outSize=" + outSize);
- result.append(" inOffset=" + inOffset);
- result.append(" inSize=" + inSize);
- result.append("]");
-
- return result.toString();
- }
- }
-
- // Saxat från HFSExplorer.java
- public static class APMPartition {
- public int pmSig; // {partition signature}
- public int pmSigPad; // {reserved}
- public long pmMapBlkCnt; // {number of blocks in partition map}
- public long pmPyPartStart; // {first physical block of partition}
- public long pmPartBlkCnt; // {number of blocks in partition}
- public final byte[] pmPartName = new byte[32]; // {partition name}
- public final byte[] pmParType = new byte[32]; // {partition type}
- public long pmLgDataStart; // {first logical block of data area}
- public long pmDataCnt; // {number of blocks in data area}
- public long pmPartStatus; // {partition status information}
- public long pmLgBootStart; // {first logical block of boot code}
- public long pmBootSize; // {size of boot code, in bytes}
- public long pmBootAddr; // {boot code load address}
- public long pmBootAddr2; // {reserved}
- public long pmBootEntry; // {boot code entry point}
- public long pmBootEntry2; // {reserved}
- public long pmBootCksum; // {boot code checksum}
- public final byte[] pmProcessor = new byte[16]; // {processor type}
- public final int[] pmPad = new int[188]; // {reserved}
-
- public APMPartition(byte[] entryData) throws IOException {
- this(new DataInputStream(new ByteArrayInputStream(entryData)));
- }
-
- public APMPartition(DataInput di) throws IOException {
- // 2*2 + 4*3 + 32*2 + 10*4 + 16 + 188*2 = 512
- pmSig = di.readShort() & 0xffff;
- pmSigPad = di.readShort() & 0xffff;
- pmMapBlkCnt = di.readInt() & 0xffffffffL;
- pmPyPartStart = di.readInt() & 0xffffffffL;
- pmPartBlkCnt = di.readInt() & 0xffffffffL;
- di.readFully(pmPartName);
- di.readFully(pmParType);
- pmLgDataStart = di.readInt() & 0xffffffffL;
- pmDataCnt = di.readInt() & 0xffffffffL;
- pmPartStatus = di.readInt() & 0xffffffffL;
- pmLgBootStart = di.readInt() & 0xffffffffL;
- pmBootSize = di.readInt() & 0xffffffffL;
- pmBootAddr = di.readInt() & 0xffffffffL;
- pmBootAddr2 = di.readInt() & 0xffffffffL;
- pmBootEntry = di.readInt() & 0xffffffffL;
- pmBootEntry2 = di.readInt() & 0xffffffffL;
- pmBootCksum = di.readInt() & 0xffffffffL;
- di.readFully(pmProcessor);
- for(int i = 0; i < pmPad.length; ++i)
- pmPad[i] = di.readShort() & 0xffff;
- }
-
- public void printPartitionInfo(PrintStream ps) {
-// String result = "";
-// result += "Partition name: \"" + new String(pmPartName) + "\"\n";
-// result += "Partition type: \"" + new String(pmParType) + "\"\n";
-// result += "Processor type: \"" + new String(pmProcessor) + "\"\n";
-// return result;
- try {
- ps.println("pmSig: " + pmSig);
- ps.println("pmSigPad: " + pmSigPad);
- ps.println("pmMapBlkCnt: " + pmMapBlkCnt);
- ps.println("pmPyPartStart: " + pmPyPartStart);
- ps.println("pmPartBlkCnt: " + pmPartBlkCnt);
- ps.println("pmPartName: \"" + new String(pmPartName, "US-ASCII") + "\"");
- ps.println("pmParType: \"" + new String(pmParType, "US-ASCII") + "\"");
- ps.println("pmLgDataStart: " + pmLgDataStart);
- ps.println("pmDataCnt: " + pmDataCnt);
- ps.println("pmPartStatus: " + pmPartStatus);
- ps.println("pmLgBootStart: " + pmLgBootStart);
- ps.println("pmBootSize: " + pmBootSize);
- ps.println("pmBootAddr: " + pmBootAddr);
- ps.println("pmBootAddr2: " + pmBootAddr2);
- ps.println("pmBootEntry: " + pmBootEntry);
- ps.println("pmBootEntry2: " + pmBootEntry2);
- ps.println("pmBootCksum: " + pmBootCksum);
- ps.println("pmProcessor: \"" + new String(pmProcessor, "US-ASCII") + "\"");
- ps.println("pmPad: " + pmPad);
- } catch(UnsupportedEncodingException uee) {
- uee.printStackTrace();
- } // Will never happen. Ever. Period.
- }
- }
-
- public static void main(String[] args) throws IOException {
- DMGMetadata meta = new DMGMetadata(new RandomAccessFile(args[0], "r"));
- meta.printInfo(System.out);
- }
-}
diff --git a/src/org/catacombae/dmgx/NodeBuilder.java b/src/org/catacombae/dmgx/NodeBuilder.java
deleted file mode 100644
index da3c3cc..0000000
--- a/src/org/catacombae/dmgx/NodeBuilder.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*-
- * Copyright (C) 2006 Erik Larsson
- *
- * All rights reserved.
- *
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-package org.catacombae.dmgx;
-
-import org.xml.sax.helpers.DefaultHandler;
-import org.xml.sax.SAXException;
-import org.xml.sax.Attributes;
-
-/**
- * Plugs into a SAXParser to build a tree of XMLElements representing the document.
- */
-class NodeBuilder extends DefaultHandler {
- /** NEVER try to obtain anything from this except its children. */
- private XMLNode artificialRoot;
-
- private XMLNode currentNode;
-
- public NodeBuilder() {
- artificialRoot = new XMLNode(null, null, null, null, null);
- currentNode = artificialRoot;
- }
- public void startElement(String namespaceURI, String sName, String qName,
- Attributes attrs) throws SAXException {
- //System.out.println("SE");
- Attribute[] attributes = new Attribute[attrs.getLength()];
- for(int i = 0; i < attributes.length; ++i) {
- attributes[i] = new Attribute(attrs.getLocalName(i), attrs.getQName(i),
- attrs.getType(i), attrs.getURI(i), attrs.getValue(i));
- }
-
- XMLNode newNode = new XMLNode(namespaceURI, sName, qName,
- attributes, currentNode);
- currentNode.addChild(newNode);
- currentNode = newNode;
- }
- public void endElement(String namespaceURI, String sName,
- String qName) throws SAXException {
- //System.out.println("EE");
- currentNode = currentNode.parent;
- }
- public void characters(char[] buf, int offset, int len)
- throws SAXException {
- //System.out.println("CH");
- String s = new String(buf, offset, len).trim();
- if(s.length() != 0)
- currentNode.addChild(new XMLText(s));
- }
- public void notationDecl(String name, String publicId,
- String systemId) throws SAXException {
- System.out.println("notationDecl(" + name + ", " +
- publicId + ", " + systemId + ");");
- }
- public XMLNode[] getRoots() throws RuntimeException {
- if(artificialRoot != currentNode)
- throw new RuntimeException("Tree was not closed!");
-
- int numberOfNodes = 0;
- for(XMLElement xe : artificialRoot.getChildren()) {
- if(xe instanceof XMLNode)
- ++numberOfNodes;
- }
- XMLNode[] result = new XMLNode[numberOfNodes];
- int i = 0;
- for(XMLElement xe : artificialRoot.getChildren()) {
- if(xe instanceof XMLNode)
- result[i++] = (XMLNode)xe;
- }
- return result;
- }
-}
diff --git a/src/org/catacombae/dmgx/SimpleFileFilter.java b/src/org/catacombae/dmgx/SimpleFileFilter.java
deleted file mode 100644
index 145a5cd..0000000
--- a/src/org/catacombae/dmgx/SimpleFileFilter.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*-
- * Copyright (C) 2006 Erik Larsson
- *
- * All rights reserved.
- *
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-package org.catacombae.dmgx;
-
-import java.util.*;
-import java.io.*;
-
-public class SimpleFileFilter extends javax.swing.filechooser.FileFilter {
-
- private Vector extensions;
- private String description;
-
- public SimpleFileFilter() {
- extensions = new Vector();
- description = "";
- }
- public void addExtension(String extension) { extensions.add(extension); }
- public void setDescription(String idescription) { description = idescription; }
- public void removeExtension(String iextension) {
- for(int i = 0; i < extensions.size(); i++) {
- if(extensions.get(i).equals(iextension))
- extensions.remove(i);
- }
- }
- public boolean accept(File f) {
-
- if(f.isDirectory()) return true;
-
- for(int i = 0; i < extensions.size(); i++) {
- if(f.getName().endsWith(extensions.get(i)))
- return true;
- }
- return false;
- }
- public String getDescription() { return description; }
-}
diff --git a/src/org/catacombae/dmgx/XMLElement.java b/src/org/catacombae/dmgx/XMLElement.java
deleted file mode 100644
index 64e2433..0000000
--- a/src/org/catacombae/dmgx/XMLElement.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*-
- * Copyright (C) 2006 Erik Larsson
- *
- * All rights reserved.
- *
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-package org.catacombae.dmgx;
-
-import java.io.PrintStream;
-
-abstract class XMLElement {
- protected abstract void _printTree(PrintStream pw, int level);
-}
diff --git a/src/org/catacombae/dmgx/XMLNode.java b/src/org/catacombae/dmgx/XMLNode.java
deleted file mode 100644
index 8cb227d..0000000
--- a/src/org/catacombae/dmgx/XMLNode.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*-
- * Copyright (C) 2006 Erik Larsson
- *
- * All rights reserved.
- *
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-package org.catacombae.dmgx;
-
-import java.util.LinkedList;
-import java.io.PrintStream;
-
-class XMLNode extends XMLElement {
- public final String namespaceURI;
- public final String sName;
- public final String qName;
- public final Attribute[] attrs;
- public final XMLNode parent;
- private final LinkedList children;
-
- public XMLNode(String namespaceURI, String sName,
- String qName, Attribute[] attrs, XMLNode parent) {
- this.namespaceURI = namespaceURI;
- this.sName = sName;
- this.qName = qName;
- this.attrs = attrs;
- this.parent = parent;
- this.children = new LinkedList();
- }
-
- public void addChild(XMLElement x) {
- children.addLast(x);
- }
-
- public void printTree(PrintStream pw) {
- _printTree(pw, 0);
- }
-
- protected void _printTree(PrintStream pw, int level) {
- for(int i = 0; i < level; ++i)
- pw.print(" ");
- pw.print("<");
- pw.print(qName);
- for(Attribute a : attrs)
- pw.print(" " + a.qName + "=" + a.value);
- pw.println(">");
- for(XMLElement xe : children)
- xe._printTree(pw, level+1);
-
- for(int i = 0; i < level; ++i)
- pw.print(" ");
- pw.println("" + qName + ">");
- }
- public XMLElement[] getChildren() {
- return children.toArray(new XMLElement[children.size()]);
- }
-
- /**
- * The concept of "changing directory" in a tree is perhabs not a
- * perfect way to describe things. But this method will look up the
- * first subnode of our node that is of the type type
- * and return it.
- * If you have more than one of the same type, tough luck. You only
- * get the first.
- */
- public XMLNode cd(String type) {
- for(XMLElement xn : getChildren()) {
- if(xn instanceof XMLNode && ((XMLNode)xn).qName.equals(type))
- return (XMLNode)xn;
- }
- return null;
- }
- /**
- * This is different from the cd method in that it
- * searches for a node of the type "key", and looks up the
- * XMLText within. It then compares the text with the String
- * key. If they match, it returns the node coming
- * after the key node. Else it continues to search. If no match is
- * found, null is returned.
- */
- public XMLNode cdkey(String key) {
- boolean keyFound = false;
- for(XMLElement xn : getChildren()) {
- if(xn instanceof XMLNode) {
- if(keyFound)
- return (XMLNode)xn;
-
- else if(((XMLNode)xn).qName.equals("key")) {
- for(XMLElement xn2 : ((XMLNode)xn).getChildren()) {
- if(xn2 instanceof XMLText && ((XMLText)xn2).text.equals(key))
- keyFound = true;
- }
- }
- }
- }
- return null;
- }
- public String getKeyValue(String key) {
- XMLNode keyNode = cdkey(key);
- StringBuilder returnString = new StringBuilder();
- for(XMLElement xe : keyNode.getChildren()) {
- if(xe instanceof XMLText)
- returnString.append(((XMLText)xe).text);
- }
- if(returnString.length() == 0)
- return null;
- else
- return returnString.toString();
- }
-}
diff --git a/src/org/catacombae/dmgx/XMLParse.java b/src/org/catacombae/dmgx/XMLParse.java
deleted file mode 100644
index d8d2826..0000000
--- a/src/org/catacombae/dmgx/XMLParse.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
-public class XMLParse {
- public String encoding = "US-ASCII";
-
- public XMLElement parse(InputStream is) throws IOException {
- readElement(is);
- }
-
- public void readElement(InputStream is) {
- LinkedList buf = new LinkedList();
- int currentByte = is.read();
- if(currentByte != '<')
- throw new RuntimeException();
-
- while(currentByte != '>' && currentByte != -1) {
- buf.add(currentByte);
- currentByte = is.read();
- }
- if(currentByte == -1)
- throw new RuntimeException();
- else {
- buf.add(currentByte);
- byte[] bytes = new byte[buf.size()];
- int i = 0;
- for(int cur : buf)
- bytes[i++] = cur;
- String s = new String(bytes, encoding);
- }
- }
-}
-*/
diff --git a/src/org/catacombae/dmgx/XMLText.java b/src/org/catacombae/dmgx/XMLText.java
deleted file mode 100644
index b087598..0000000
--- a/src/org/catacombae/dmgx/XMLText.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*-
- * Copyright (C) 2006 Erik Larsson
- *
- * All rights reserved.
- *
- * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-package org.catacombae.dmgx;
-
-import java.io.PrintStream;
-
-class XMLText extends XMLElement {
- public final String text;
- public XMLText(String text) {
- this.text = text;
- }
- protected void _printTree(PrintStream pw, int level) {
- for(int i = 0; i < level; ++i)
- pw.print(" ");
- pw.println(text);
- }
-
- public static void main(String[] args) {
- System.out.println(args[0] + " " + args[1]);
- }
-}
diff --git a/src/org/catacombae/plist/PlistNode.java b/src/org/catacombae/plist/PlistNode.java
new file mode 100644
index 0000000..5166c5e
--- /dev/null
+++ b/src/org/catacombae/plist/PlistNode.java
@@ -0,0 +1,31 @@
+/*-
+ * Copyright (C) 2006-2011 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.plist;
+
+import java.io.Reader;
+
+/**
+ *
+ * @author erik
+ */
+public abstract class PlistNode {
+ public abstract PlistNode[] getChildren();
+ public abstract PlistNode cd(String type);
+ public abstract PlistNode cdkey(String key);
+ public abstract Reader getKeyValue(String key);
+}
diff --git a/src/org/catacombae/plist/XmlPlist.java b/src/org/catacombae/plist/XmlPlist.java
new file mode 100644
index 0000000..f2c48f3
--- /dev/null
+++ b/src/org/catacombae/plist/XmlPlist.java
@@ -0,0 +1,178 @@
+/*-
+ * Copyright (C) 2006-2011 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.plist;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import org.catacombae.dmgextractor.io.RandomAccessInputStream;
+import org.catacombae.dmgextractor.io.SynchronizedRandomAccessStream;
+import org.catacombae.io.ReadableByteArrayStream;
+import org.catacombae.xml.DebugXMLContentHandler;
+import org.catacombae.xml.NodeBuilder;
+import org.catacombae.xml.NodeBuilderContentHandler;
+import org.catacombae.xml.NullXMLContentHandler;
+import org.catacombae.xml.XMLNode;
+import org.catacombae.xml.apx.APXParser;
+import org.catacombae.xml.apx.ParseException;
+import org.xml.sax.SAXException;
+
+/**
+ *
+ * @author erik
+ */
+public class XmlPlist {
+
+ private final XMLNode rootNode;
+
+ public XmlPlist(byte[] data) {
+ this(data, 0, data.length);
+ }
+
+ public XmlPlist(byte[] data, boolean useSAXParser) {
+ this(data, 0, data.length, useSAXParser);
+ }
+
+ public XmlPlist(byte[] data, int offset, int length) {
+ this(data, offset, length, false);
+ }
+
+ public XmlPlist(byte[] data, int offset, int length, boolean useSAXParser) {
+ //plistData = new byte[length];
+ //System.arraycopy(data, offset, plistData, 0, length);
+ rootNode = parseXMLData(data, useSAXParser);
+ }
+
+ public PlistNode getRootNode() {
+ return new XmlPlistNode(rootNode);
+ }
+
+ private XMLNode parseXMLData(byte[] plistData, boolean defaultToSAX) {
+ //InputStream is = new ByteArrayInputStream(plistData);
+ NodeBuilder handler = new NodeBuilder();
+
+ if(defaultToSAX) {
+ parseXMLDataSAX(plistData, handler);
+ }
+ else {
+ /* First try to parse with the internal homebrew parser, and if it
+ * doesn't succeed, go for the SAX parser. */
+ //System.err.println("Trying to parse xml data...");
+ try {
+ parseXMLDataAPX(plistData, handler);
+ //System.err.println("xml data parsed...");
+ } catch(Exception e) {
+ e.printStackTrace();
+ System.err.println("APX parser threw exception... falling back to SAX parser. Report this error!");
+ handler = new NodeBuilder();
+ parseXMLDataSAX(plistData, handler);
+ }
+ }
+
+ XMLNode[] rootNodes = handler.getRoots();
+ if(rootNodes.length != 1)
+ throw new RuntimeException("Could not parse DMG-file!");
+ else
+ return rootNodes[0];
+ }
+
+ private void parseXMLDataAPX(byte[] buffer, NodeBuilder handler) {
+ try {
+ ReadableByteArrayStream ya = new ReadableByteArrayStream(buffer);
+ SynchronizedRandomAccessStream bufferStream =
+ new SynchronizedRandomAccessStream(ya);//new ReadableByteArrayStream(buffer));
+
+ // First we parse the xml declaration using a US-ASCII charset just to extract the charset description
+ //System.err.println("parsing encoding");
+ InputStream is = new RandomAccessInputStream(bufferStream);
+ APXParser encodingParser = APXParser.create(new InputStreamReader(is, "US-ASCII"),
+ new NullXMLContentHandler(Charset.forName("US-ASCII")));
+ String encodingName = encodingParser.xmlDecl();
+ //System.err.println("encodingName=" + encodingName);
+ if(encodingName == null)
+ encodingName = "US-ASCII";
+
+ Charset encoding = Charset.forName(encodingName);
+
+ // Then we proceed to parse the entire document
+ is = new RandomAccessInputStream(bufferStream);
+ Reader usedReader = new BufferedReader(new InputStreamReader(is, encoding));
+ //System.err.println("parsing document");
+ //try { FileOutputStream dump = new FileOutputStream("dump.xml"); dump.write(buffer); dump.close(); }
+ //catch(Exception e) { e.printStackTrace(); }
+
+ if(false) { //
+ APXParser documentParser = APXParser.create(usedReader, new DebugXMLContentHandler(encoding));
+ documentParser.xmlDocument();
+ System.exit(0);
+ }
+ else {
+ APXParser documentParser = APXParser.create(usedReader, new NodeBuilderContentHandler(handler, bufferStream, encoding));
+ documentParser.xmlDocument();
+ }
+
+ } catch(ParseException pe) {
+ //System.err.println("Could not read the partition list...");
+ throw new RuntimeException(pe);
+ } catch(UnsupportedEncodingException uee) {
+ throw new RuntimeException(uee);
+ }
+ }
+
+ private void parseXMLDataSAX(byte[] buffer, NodeBuilder handler) {
+ try {
+ InputStream is = new ByteArrayInputStream(buffer);
+ SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
+// System.out.println("validation: " + saxParser.getProperty("validation"));
+// System.out.println("external-general-entities: " + saxParser.getProperty("external-general-entities"));
+// System.out.println("external-parameter-entities: " + saxParser.getProperty("external-parameter-entities"));
+// System.out.println("is-standalone: " + saxParser.getProperty("is-standalone"));
+// System.out.println("lexical-handler: " + saxParser.getProperty("lexical-handler"));
+// System.out.println("parameter-entities: " + saxParser.getProperty("parameter-entities"));
+// System.out.println("namespaces: " + saxParser.getProperty("namespaces"));
+// System.out.println("namespace-prefixes: " + saxParser.getProperty("namespace-prefixes"));
+// System.out.println(": " + saxParser.getProperty(""));
+// System.out.println(": " + saxParser.getProperty(""));
+// System.out.println(": " + saxParser.getProperty(""));
+// System.out.println(": " + saxParser.getProperty(""));
+// System.out.println("" + saxParser.getProperty(""));
+// System.out.println("" + saxParser.getProperty(""));
+// System.out.println("" + saxParser.getProperty(""));
+// System.out.println("" + saxParser.getProperty(""));
+// System.out.println("" + saxParser.getProperty(""));
+// System.out.println("" + saxParser.getProperty(""));
+// System.out.println("" + saxParser.getProperty(""));
+// System.out.println("" + saxParser.getProperty(""));
+
+ //System.out.println("isValidating: " + saxParser.isValidating());
+ saxParser.parse(is, handler);
+ } catch(SAXException se) {
+ se.printStackTrace();
+ //System.err.println("Could not read the partition list... exiting.");
+ throw new RuntimeException(se);
+ } catch(Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/org/catacombae/plist/XmlPlistNode.java b/src/org/catacombae/plist/XmlPlistNode.java
new file mode 100644
index 0000000..db81b7c
--- /dev/null
+++ b/src/org/catacombae/plist/XmlPlistNode.java
@@ -0,0 +1,178 @@
+/*-
+ * Copyright (C) 2006-2011 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.plist;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.LinkedList;
+import org.catacombae.dmgextractor.io.ConcatenatedReader;
+import org.catacombae.io.RuntimeIOException;
+import org.catacombae.util.Util;
+import org.catacombae.xml.XMLElement;
+import org.catacombae.xml.XMLNode;
+import org.catacombae.xml.XMLText;
+
+/**
+ *
+ * @author erik
+ */
+public class XmlPlistNode extends PlistNode {
+ private final XMLNode xmlNode;
+
+ public XmlPlistNode(XMLNode xmlNode) {
+ this.xmlNode = xmlNode;
+ }
+
+ private XMLNode getXMLNode() {
+ return xmlNode;
+ }
+
+ private String[] getKeys() throws RuntimeIOException {
+ final LinkedList keyList = new LinkedList();
+
+ for(XMLElement xe : xmlNode.getChildren()) {
+ if(xe instanceof XMLNode) {
+ XMLNode xn = (XMLNode) xe;
+ if(xn.qName.equals("key")) {
+ for(XMLElement xeChild : xn.getChildren()) {
+ if(xeChild instanceof XMLText) {
+ final XMLText xtChild = (XMLText) xeChild;
+ String key;
+
+ try {
+ key = Util.readFully(xtChild.getText());
+ } catch(IOException e) {
+ throw new RuntimeIOException(e);
+ }
+
+ keyList.addLast(key);
+ }
+ }
+ }
+ }
+ }
+
+ return keyList.toArray(new String[keyList.size()]);
+ }
+
+ public PlistNode[] getChildren() {
+ final LinkedList children = new LinkedList();
+
+ for(String key : getKeys()) {
+ children.add(cdkey(key));
+ }
+
+ return children.toArray(new PlistNode[children.size()]);
+ }
+
+ /**
+ * The concept of "changing directory" in a tree is perhaps not a
+ * perfect way to describe things. But this method will look up the
+ * first subnode of our node that is of the type type
+ * and return it.
+ * If you have more than one of the same type, tough luck. You only
+ * get the first.
+ */
+ public PlistNode cd(String type) {
+ for(XMLElement xn : xmlNode.getChildren()) {
+ if(xn instanceof XMLNode && ((XMLNode)xn).qName.equals(type))
+ return new XmlPlistNode((XMLNode)xn);
+ }
+ return null;
+ }
+
+ /**
+ * This is different from the cd method in that it
+ * searches for a node of the type "key", and looks up the
+ * XMLText within. It then compares the text with the String
+ * key. If they match, it returns the node coming
+ * after the key node. Else it continues to search. If no match is
+ * found, null is returned.
+ */
+ public PlistNode cdkey(String key) {
+ return cdkeyXml(key);
+ }
+
+ private XmlPlistNode cdkeyXml(String key) {
+ boolean keyFound = false;
+ for(XMLElement xn : xmlNode.getChildren()) {
+ if(xn instanceof XMLNode) {
+ if(keyFound)
+ return new XmlPlistNode((XMLNode)xn);
+
+ else if(((XMLNode)xn).qName.equals("key")) {
+ for(XMLElement xn2 : ((XMLNode)xn).getChildren()) {
+ try {
+ if(xn2 instanceof XMLText) {
+ String s = Util.readFully(((XMLText)xn2).getText());
+ //System.err.println("cdkey searching: \"" + s + "\"");
+ if(s.equals(key))
+ keyFound = true;
+ }
+ } catch(Exception e) { throw new RuntimeException(e); }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public Reader getKeyValue(String key) {
+ //System.out.println("XMLNode.getKeyValue(\"" + key + "\")");
+ XmlPlistNode keyNode = cdkeyXml(key);
+ if(keyNode == null)
+ return null;
+
+ XMLElement[] nodeChildren = keyNode.getXMLNode().getChildren();
+ if(nodeChildren.length != 1) {
+ //System.out.println(" nodeChildren.length == " + nodeChildren.length);
+
+ LinkedList collectedReaders = new LinkedList();
+ for(XMLElement xe : keyNode.getXMLNode().getChildren()) {
+ if(xe instanceof XMLText) {
+ try {
+ Reader xt = ((XMLText)xe).getText();
+ collectedReaders.addLast(xt);
+ } catch(Exception e) { throw new RuntimeException(e); }
+ //System.out.print("\"");
+ //for(int i = 0; i < xt.length(); ++i) System.out.print(xt.charAt(i));
+ //System.out.println("\"");
+ //System.out.println("free memory: " + Runtime.getRuntime().freeMemory() + " total memory: " + Runtime.getRuntime().totalMemory());
+ }
+ }
+ ConcatenatedReader result;
+ if(collectedReaders.size() == 0)
+ result = null;
+ else {
+ //System.out.println("doing a toString... free memory: " + Runtime.getRuntime().freeMemory() + " total memory: " + Runtime.getRuntime().totalMemory());
+ //result = returnString.toString();
+ //System.out.println("done.free memory: " + Runtime.getRuntime().freeMemory() + " total memory: " + Runtime.getRuntime().totalMemory());
+ result = new ConcatenatedReader(collectedReaders.toArray(new Reader[collectedReaders.size()]));
+ }
+ return result;
+ }
+ else if(nodeChildren[0] instanceof XMLText) {
+ //System.err.println("Special case!");
+ try {
+ return ((XMLText)nodeChildren[0]).getText();
+ } catch(Exception e) { throw new RuntimeException(e); }
+ }
+ else
+ return null;
+ }
+}
diff --git a/src/org/catacombae/xml/.cvsignore b/src/org/catacombae/xml/.cvsignore
new file mode 100644
index 0000000..cd5dc09
--- /dev/null
+++ b/src/org/catacombae/xml/.cvsignore
@@ -0,0 +1,5 @@
+*~
+*#
+*.class
+.DS_Store
+Thumbs.db
diff --git a/src/org/catacombae/xml/Attribute.java b/src/org/catacombae/xml/Attribute.java
new file mode 100644
index 0000000..3bde8b8
--- /dev/null
+++ b/src/org/catacombae/xml/Attribute.java
@@ -0,0 +1,58 @@
+/*-
+ * Copyright (C) 2007-2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.xml;
+
+import java.util.List;
+
+public class Attribute {
+ public static abstract class ValueComponent {
+ @Override
+ public abstract String toString();
+ }
+ public static class StringComponent extends ValueComponent {
+ public String content;
+ public StringComponent(String content) { this.content = content; }
+ public String toString() { return content; }
+ }
+ public static class ReferenceComponent extends ValueComponent {
+ public String content;
+ public ReferenceComponent(String content) { this.content = content; }
+ public String toString() { return content; } // should resolve the reference here
+ }
+ public static class Value {
+ public List components;
+ public Value(List components) {
+ this.components = components;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ for(ValueComponent vc : components)
+ result.append(vc.toString());
+ return result.toString();
+ }
+ }
+ public String identifier;
+ public Value value;
+
+ public Attribute(String identifier, Value value) {
+ this.identifier = identifier;
+ this.value = value;
+ }
+}
\ No newline at end of file
diff --git a/src/Attribute.java b/src/org/catacombae/xml/Attribute2.java
similarity index 50%
rename from src/Attribute.java
rename to src/org/catacombae/xml/Attribute2.java
index fa62722..0411939 100644
--- a/src/Attribute.java
+++ b/src/org/catacombae/xml/Attribute2.java
@@ -1,30 +1,29 @@
/*-
- * Copyright (C) 2006 Erik Larsson
- *
- * All rights reserved.
- *
- * 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.
+ * Copyright (C) 2006-2008 Erik Larsson
*
+ * 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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ * along with this program. If not, see .
*/
-class Attribute {
+package org.catacombae.xml;
+
+public class Attribute2 {
public final String localName;
public final String qName;
public final String type;
public final String URI;
public final String value;
- public Attribute(String localName, String qName, String type, String URI, String value) {
+ public Attribute2(String localName, String qName, String type, String URI, String value) {
this.localName = localName;
this.qName = qName;
this.type = type;
diff --git a/src/org/catacombae/xml/DebugXMLContentHandler.java b/src/org/catacombae/xml/DebugXMLContentHandler.java
new file mode 100644
index 0000000..ad970e5
--- /dev/null
+++ b/src/org/catacombae/xml/DebugXMLContentHandler.java
@@ -0,0 +1,108 @@
+/*-
+ * Copyright (C) 2007-2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.xml;
+
+import java.util.List;
+import java.nio.charset.Charset;
+
+public class DebugXMLContentHandler extends XMLContentHandler {
+ public DebugXMLContentHandler(Charset encoding) {
+ super(encoding);
+ }
+ //public void doctype(String
+ public void xmlDecl(String version, String encoding, Boolean standalone) {
+ print("xmlDecl: ");
+ }
+ public void pi(String id, String content) {
+ print("pi: " + id);
+ if(content != null)
+ print(" " + content);
+ println("?>");
+ }
+
+ public void comment(String content) {
+ println("comment: ");
+ }
+
+ public void doctype(String name, ExternalID eid) {
+ print("doctype: ");
+ }
+
+ public void cdata(String cdata) {
+ println("cdata: ");
+ }
+
+ public void emptyElement(String name, List attributes) {
+ print("emptyElement: <" + name);
+ for(Attribute attr : attributes)
+ print(" " + attr.identifier + "=\"" + attr.value + "\"");
+ println("/>");
+ }
+
+ public void startElement(String name, List attributes) {
+ print("startElement: <" + name);
+ for(Attribute attr : attributes)
+ print(" " + attr.identifier + "=\"" + attr.value + "\"");
+ println(">");
+ }
+
+ public void endElement(String name) {
+ println("endElement: " + name + ">");
+ }
+
+// public void chardata(CharSequence data) {
+// print("chardata: \"");
+// for(int i = 0; i < data.length(); ++i)
+// print(data.charAt(i)+"");
+// println("\"");
+
+// }
+ public void chardata(int beginLine, int beginColumn, int endLine, int endColumn) {
+ println("chardata: starting at (" + beginLine + "," + beginColumn + ") and ending at (" + endLine + "," + endColumn + ")");
+ }
+
+ public void reference(String ref) {
+ println("reference: \"" + ref + "\"");
+ }
+
+ private static void print(String s) {
+ System.out.print(s);
+ }
+ private static void println(String s) {
+ System.out.println(s);
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/catacombae/xml/ExternalID.java b/src/org/catacombae/xml/ExternalID.java
new file mode 100644
index 0000000..58078dc
--- /dev/null
+++ b/src/org/catacombae/xml/ExternalID.java
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (C) 2007 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.xml;
+
+public class ExternalID {
+ public static final int SYSTEM = 0;
+ public static final int PUBLIC = 1;
+ public int type;
+ public String pubidLiteral;
+ public String systemLiteral;
+
+ public ExternalID(String pubidLiteral, String systemLiteral) {
+ this.pubidLiteral = pubidLiteral;
+ this.systemLiteral = systemLiteral;
+ this.type = PUBLIC;
+ }
+ public ExternalID(String systemLiteral) {
+ this.systemLiteral = systemLiteral;
+ this.type = SYSTEM;
+ }
+}
\ No newline at end of file
diff --git a/src/org/catacombae/xml/MutableInputStreamReader.java b/src/org/catacombae/xml/MutableInputStreamReader.java
new file mode 100644
index 0000000..c10b8c9
--- /dev/null
+++ b/src/org/catacombae/xml/MutableInputStreamReader.java
@@ -0,0 +1,146 @@
+/*-
+ * Copyright (C) 2007-2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.xml;
+
+import java.io.*;
+import java.nio.*;
+
+public class MutableInputStreamReader extends Reader {
+ private static final boolean DEBUG = true;
+ private static final String PREFIX = "---->MutableInputStreamReader: ";
+ private static final PrintStream os = System.out;
+ private InputStream iStream;
+ private InputStreamReader isReader;
+
+ public MutableInputStreamReader(InputStream iStream, String charset) throws UnsupportedEncodingException {
+ this.iStream = iStream;
+ this.isReader = new InputStreamReader(iStream, charset);
+ }
+ public void close() throws IOException {
+ try {
+ if(DEBUG) os.println(PREFIX + "isReader.close()");
+ isReader.close();
+ }
+ catch(IOException ioe) { if(DEBUG) ioe.printStackTrace(); throw ioe; }
+ catch(RuntimeException e) { if(DEBUG) e.printStackTrace(); throw e; }
+ }
+
+ @Override
+ public void mark(int readAheadLimit) throws IOException {
+ try {
+ if(DEBUG) os.println(PREFIX + "isReader.mark(" + readAheadLimit + ")");
+ isReader.mark(readAheadLimit);
+ }
+ catch(IOException ioe) { if(DEBUG) ioe.printStackTrace(); throw ioe; }
+ catch(RuntimeException e) { if(DEBUG) e.printStackTrace(); throw e; }
+ }
+
+ @Override
+ public boolean markSupported() {
+ try {
+ boolean result = isReader.markSupported();
+ if(DEBUG) os.println(PREFIX + "isReader.markSupported() == " + result);
+ return result;
+ }
+ catch(RuntimeException e) { if(DEBUG) e.printStackTrace(); throw e; }
+ }
+
+ @Override
+ public int read() throws IOException {
+ try {
+ int result = isReader.read();
+ if(DEBUG) os.println(PREFIX + "isReader.read() == " + result);
+ return result;
+ }
+ catch(IOException ioe) { if(DEBUG) ioe.printStackTrace(); throw ioe; }
+ catch(RuntimeException e) { if(DEBUG) e.printStackTrace(); throw e; }
+ }
+
+ @Override
+ public int read(char[] cbuf) throws IOException {
+ try {
+ int result = isReader.read(cbuf);
+ if(DEBUG) os.println(PREFIX + "isReader.read(" + cbuf.length + " bytes...) == " + result);
+ return result;
+ }
+ catch(IOException ioe) { if(DEBUG) ioe.printStackTrace(); throw ioe; }
+ catch(RuntimeException e) { if(DEBUG) e.printStackTrace(); throw e; }
+ }
+
+ @Override
+ public int read(char[] cbuf, int off, int len) throws IOException {
+ try {
+ int result = isReader.read(cbuf, off, len);
+ if(DEBUG) os.println(PREFIX + "isReader.read(" + cbuf.length + " bytes..., " + off + ", " + len + ") == " + result);
+ return result;
+ }
+ catch(IOException ioe) { if(DEBUG) ioe.printStackTrace(); throw ioe; }
+ catch(RuntimeException e) { if(DEBUG) e.printStackTrace(); throw e; }
+ }
+
+ @Override
+ public int read(CharBuffer target) throws IOException {
+ try {
+ int result = isReader.read(target);
+ if(DEBUG) os.println(PREFIX + "isReader.read(CharBuffer with length " + target.length() + ") == " + result);
+ return result;
+ }
+ catch(IOException ioe) { if(DEBUG) ioe.printStackTrace(); throw ioe; }
+ catch(RuntimeException e) { if(DEBUG) e.printStackTrace(); throw e; }
+ }
+
+ @Override
+ public boolean ready() throws IOException {
+ try {
+ boolean result = isReader.ready();
+ if(DEBUG) os.println(PREFIX + "isReader.ready() == " + result);
+ return result;
+ }
+ catch(IOException ioe) { if(DEBUG) ioe.printStackTrace(); throw ioe; }
+ catch(RuntimeException e) { if(DEBUG) e.printStackTrace(); throw e; }
+ }
+
+ @Override
+ public void reset() throws IOException {
+ try {
+ if(DEBUG) os.println(PREFIX + "isReader.reset()");
+ isReader.reset();
+ }
+ catch(IOException ioe) { if(DEBUG) ioe.printStackTrace(); throw ioe; }
+ catch(RuntimeException e) { if(DEBUG) e.printStackTrace(); throw e; }
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ try {
+ long result = isReader.skip(n);
+ if(DEBUG) os.println(PREFIX + "isReader.skip(" + n + ") == " + result);
+ return result;
+ }
+ catch(IOException ioe) { if(DEBUG) ioe.printStackTrace(); throw ioe; }
+ catch(RuntimeException e) { if(DEBUG) e.printStackTrace(); throw e; }
+ }
+
+ public void changeEncoding(String charset) throws UnsupportedEncodingException {
+ try {
+ if(DEBUG) os.println(PREFIX + "changeEncoding(\"" + charset + "\")");
+ isReader = new InputStreamReader(iStream, charset);
+ }
+ catch(RuntimeException e) { if(DEBUG) e.printStackTrace(); throw e; }
+ }
+}
\ No newline at end of file
diff --git a/src/NodeBuilder.java b/src/org/catacombae/xml/NodeBuilder.java
similarity index 61%
rename from src/NodeBuilder.java
rename to src/org/catacombae/xml/NodeBuilder.java
index 722713a..c983faf 100644
--- a/src/NodeBuilder.java
+++ b/src/org/catacombae/xml/NodeBuilder.java
@@ -1,31 +1,32 @@
/*-
- * Copyright (C) 2006 Erik Larsson
- *
- * All rights reserved.
- *
- * 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.
+ * Copyright (C) 2006-2008 Erik Larsson
*
+ * 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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ * along with this program. If not, see .
*/
+package org.catacombae.xml;
+
+import org.catacombae.dmgextractor.io.*;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.SAXException;
import org.xml.sax.Attributes;
+import java.nio.charset.Charset;
/**
* Plugs into a SAXParser to build a tree of XMLElements representing the document.
*/
-class NodeBuilder extends DefaultHandler {
+public class NodeBuilder extends DefaultHandler {
/** NEVER try to obtain anything from this except its children. */
private XMLNode artificialRoot;
@@ -35,25 +36,40 @@ public NodeBuilder() {
artificialRoot = new XMLNode(null, null, null, null, null);
currentNode = artificialRoot;
}
+
+ @Override
public void startElement(String namespaceURI, String sName, String qName,
Attributes attrs) throws SAXException {
//System.out.println("SE");
- Attribute[] attributes = new Attribute[attrs.getLength()];
+ Attribute2[] attributes = new Attribute2[attrs.getLength()];
for(int i = 0; i < attributes.length; ++i) {
- attributes[i] = new Attribute(attrs.getLocalName(i), attrs.getQName(i),
- attrs.getType(i), attrs.getURI(i), attrs.getValue(i));
+ attributes[i] = new Attribute2(attrs.getLocalName(i), attrs.getQName(i),
+ attrs.getType(i), attrs.getURI(i), attrs.getValue(i));
}
-
+ startElementInternal(namespaceURI, sName, qName, attributes);
+ }
+
+ void startElementInternal(String namespaceURI, String sName, String qName,
+ Attribute2[] attributes) throws SAXException {
XMLNode newNode = new XMLNode(namespaceURI, sName, qName,
attributes, currentNode);
currentNode.addChild(newNode);
currentNode = newNode;
}
+
+ @Override
public void endElement(String namespaceURI, String sName,
String qName) throws SAXException {
//System.out.println("EE");
currentNode = currentNode.parent;
}
+
+ public void characters(SynchronizedRandomAccessStream file, Charset encoding,
+ int startLine, int startColumn, int endLine, int endColumn) {
+ currentNode.addChild(new XMLText(file, encoding, startLine, startColumn, endLine, endColumn));
+ }
+
+ @Override
public void characters(char[] buf, int offset, int len)
throws SAXException {
//System.out.println("CH");
@@ -61,11 +77,13 @@ public void characters(char[] buf, int offset, int len)
if(s.length() != 0)
currentNode.addChild(new XMLText(s));
}
+
+ @Override
public void notationDecl(String name, String publicId,
String systemId) throws SAXException {
- System.out.println("notationDecl(" + name + ", " +
- publicId + ", " + systemId + ");");
+ //System.out.println("notationDecl(" + name + ", " + publicId + ", " + systemId + ");");
}
+
public XMLNode[] getRoots() throws RuntimeException {
if(artificialRoot != currentNode)
throw new RuntimeException("Tree was not closed!");
diff --git a/src/org/catacombae/xml/NodeBuilderContentHandler.java b/src/org/catacombae/xml/NodeBuilderContentHandler.java
new file mode 100644
index 0000000..7e6981e
--- /dev/null
+++ b/src/org/catacombae/xml/NodeBuilderContentHandler.java
@@ -0,0 +1,99 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.xml;
+
+//import org.catacombae.xml.*;
+import org.catacombae.dmgextractor.io.*;
+import java.util.List;
+import java.nio.charset.Charset;
+
+public class NodeBuilderContentHandler extends XMLContentHandler {
+ private NodeBuilder nodeBuilder;
+ private SynchronizedRandomAccessStream sras;
+ private Charset encoding;
+
+ public NodeBuilderContentHandler(NodeBuilder nodeBuilder, SynchronizedRandomAccessStream sras, Charset encoding) {
+ super(encoding);
+ this.nodeBuilder = nodeBuilder;
+ this.sras = sras;
+ this.encoding = encoding;
+ }
+ public void xmlDecl(String version, String encoding, Boolean standalone) {}
+ public void pi(String id, String content) {}
+ public void comment(String comment) {}
+ public void doctype(String name, ExternalID eid) {} // Needs a DTD description also
+ public void cdata(String cdata) {
+ try {
+ nodeBuilder.characters(cdata.toCharArray(), 0, cdata.length());
+ } catch(Exception e) { throw new RuntimeException(e); }
+ }
+ public void emptyElement(String name, List attributes) {
+ try {
+ startElement(name, attributes);
+ endElement(name);
+ } catch(Exception e) { throw new RuntimeException(e); }
+ }
+ public void startElement(String name, List attributes) {
+ try {
+ Attribute2[] attrs = new Attribute2[attributes.size()];
+ //for(int i = 0; i < attributes.length; ++i) {
+ int i = 0;
+ for(org.catacombae.xml.Attribute a : attributes) {
+ attrs[i++] = new Attribute2("", a.identifier,
+ "CDATA", "", a.value.toString());
+ }
+// org.xml.sax.ext.Attributes2Impl a2i = new org.xml.sax.ext.Attributes2Impl();
+// for(org.catacombae.xml.Attribute a : attributes) {
+// System.err.println("id: " + a.identifier + " value: " + a.value.toString());
+// a2i.addAttribute("", a.identifier, a.identifier, "CDATA", a.value.toString());
+// }
+ nodeBuilder.startElementInternal(null, null, name, attrs);
+ } catch(Exception e) { throw new RuntimeException(e); }
+ }
+ public void endElement(String name) {
+ try {
+ nodeBuilder.endElement(null, null, name);
+ } catch(Exception e) { throw new RuntimeException(e); }
+ }
+ public void chardata(int beginLine, int beginColumn, int endLine, int endColumn) {
+ nodeBuilder.characters(sras, encoding, beginLine, beginColumn, endLine, endColumn);
+ }
+// public void chardata(CharSequence data) {
+// try {
+// //char[] ca = data.toCharArray();
+// //nodeBuilder.characters(ca, 0, ca.length);
+// nodeBuilder.characters(data);
+// } catch(Exception e) { throw new RuntimeException(e); }
+// }
+ public void reference(String ref) {
+ try {
+ if(ref.startsWith("")) {
+ // CharRef
+ int[] codePoints = new int[1];
+ if(ref.startsWith(""))
+ codePoints[0] = Integer.parseInt(ref.substring(3), 16);
+ else
+ codePoints[0] = Integer.parseInt(ref.substring(2), 10);
+ char[] cp_ca = Character.toChars(codePoints[0]);
+ nodeBuilder.characters(cp_ca, 0, cp_ca.length);
+ }
+ else
+ System.out.println("WARNING: Encountered external references, which cannot be resolved with this version of the parser.");
+ } catch(Exception e) { throw new RuntimeException(e); }
+ }
+}
diff --git a/src/org/catacombae/xml/NullXMLContentHandler.java b/src/org/catacombae/xml/NullXMLContentHandler.java
new file mode 100644
index 0000000..40dc955
--- /dev/null
+++ b/src/org/catacombae/xml/NullXMLContentHandler.java
@@ -0,0 +1,37 @@
+/*-
+ * Copyright (C) 2007 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.xml;
+
+import java.util.List;
+import java.nio.charset.Charset;
+
+public class NullXMLContentHandler extends XMLContentHandler {
+ public NullXMLContentHandler(Charset encoding) {
+ super(encoding);
+ }
+ public void xmlDecl(String version, String encoding, Boolean standalone) {}
+ public void pi(String id, String content) {}
+ public void comment(String comment) {}
+ public void doctype(String name, ExternalID eid) {} // Needs a DTD description also
+ public void cdata(String cdata) {}
+ public void emptyElement(String name, List attributes) {}
+ public void startElement(String name, List attributes) {}
+ public void endElement(String name) {}
+ public void chardata(int beginLine, int beginColumn, int endLine, int endColumn) {}
+ public void reference(String ref) {}
+}
\ No newline at end of file
diff --git a/src/org/catacombae/xml/UTF16BEInputStream.java b/src/org/catacombae/xml/UTF16BEInputStream.java
new file mode 100644
index 0000000..c079edb
--- /dev/null
+++ b/src/org/catacombae/xml/UTF16BEInputStream.java
@@ -0,0 +1,141 @@
+/*-
+ * Copyright (C) 2007-2008 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.xml;
+
+import java.io.*;
+import java.nio.*;
+import java.nio.charset.*;
+
+/**
+ * This class always outputs UTF-16BE encoded data. The input encoding
+ * can be changed on the fly.
+ */
+public class UTF16BEInputStream extends InputStream {
+ private InputStream underlyingStream;
+ private Reader inReader;
+ private CharsetEncoder utf16beEncoder;
+ private int overflow = -1;
+ private char[] tempArray = new char[4096];
+
+ public UTF16BEInputStream(InputStream underlyingStream, String encodingName) throws UnsupportedEncodingException {
+ this.underlyingStream = underlyingStream;
+ this.inReader = new InputStreamReader(underlyingStream, encodingName);
+ this.utf16beEncoder = Charset.forName("UTF-16BE").newEncoder();
+ }
+
+ public void changeEncoding(String encodingName) throws UnsupportedEncodingException {
+ this.inReader = new InputStreamReader(underlyingStream, encodingName);
+ }
+
+ public int read() throws IOException {
+ if(overflow != -1) {
+ int result = overflow;
+ overflow = -1;
+ return result;
+ }
+ else {
+ inReader.read(tempArray, 0, 1);
+ ByteBuffer bb = utf16beEncoder.encode(CharBuffer.wrap(tempArray, 0, 1));
+ int result = bb.get() & 0xFF;
+ overflow = bb.get() & 0xFF;
+ return result;
+ }
+ }
+
+ @Override
+ public int read(byte[] ba) throws IOException { return read(ba, 0, ba.length); }
+
+ @Override
+ public int read(byte[] ba, int offset, int length) throws IOException {
+ if(length == 0) return 0;
+
+ int curOffset = offset;
+ int curLength = length;
+
+ // Take care of the overflow, if any
+ if(overflow != -1) {
+ ba[curOffset++] = (byte)overflow;
+ --length;
+ overflow = -1;
+ }
+
+ // Do the dance...
+ int numberOfCharsToRead = length/2 + length%2;
+ int charsRead = 0;
+ while(numberOfCharsToRead > charsRead) {
+ int charsRemaining = numberOfCharsToRead - charsRead;
+ int curCharsRead = inReader.read(tempArray, 0, (charsRemaining < tempArray.length?charsRemaining:tempArray.length));
+ if(curCharsRead == -1)
+ break;
+ else {
+ CharBuffer cb = CharBuffer.wrap(tempArray, 0, curCharsRead);
+ ByteBuffer bb = utf16beEncoder.encode(cb);
+ int bytesToWrite = curCharsRead*2 > curLength?curLength:curCharsRead*2;
+ bb.get(ba, curOffset, bytesToWrite);
+ curOffset += bytesToWrite;
+ curLength -= bytesToWrite;
+ if(bytesToWrite != curCharsRead*2)
+ overflow = bb.get() & 0xFF;
+
+ charsRead += curCharsRead;
+ if(bytesToWrite != curCharsRead*2 && numberOfCharsToRead > charsRead)
+ throw new RuntimeException("Mind meltdown!");
+ }
+ }
+
+ if(numberOfCharsToRead*2 != length) {
+ if(numberOfCharsToRead*2-1 != length)
+ throw new RuntimeException("wtf?!");
+ if(charsRead*2-1 == length)
+ return length;
+ else
+ return charsRead*2;
+ }
+ else
+ return charsRead*2;
+ }
+
+ @Override
+ public long skip(long bytesToSkip) throws IOException {
+ byte[] garbage = new byte[4096];
+ long bytesRead = 0;
+ while(bytesRead < bytesToSkip) {
+ int curBytesRead = read(garbage);
+ if(curBytesRead == -1)
+ break;
+ else
+ bytesRead += curBytesRead;
+ }
+ return bytesRead;
+ }
+
+ @Override
+ public int available() throws IOException { return 0; }
+
+ @Override
+ public void close() throws IOException { underlyingStream.close(); }
+
+ @Override
+ public void mark(int readLimit) {}
+
+ @Override
+ public void reset() throws IOException { throw new IOException("Not supported"); }
+
+ @Override
+ public boolean markSupported() { return false; }
+}
\ No newline at end of file
diff --git a/src/org/catacombae/xml/XMLContentHandler.java b/src/org/catacombae/xml/XMLContentHandler.java
new file mode 100644
index 0000000..5274e85
--- /dev/null
+++ b/src/org/catacombae/xml/XMLContentHandler.java
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (C) 2007 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.xml;
+
+import java.util.List;
+import java.nio.charset.Charset;
+
+public abstract class XMLContentHandler {
+ protected Charset encoding;
+ public XMLContentHandler(Charset encoding) {
+ this.encoding = encoding;
+ }
+ //public void doctype(String
+ public abstract void xmlDecl(String version, String encoding, Boolean standalone);
+ public abstract void pi(String id, String content);
+ public abstract void comment(String comment);
+ public abstract void doctype(String name, ExternalID eid); // Needs a DTD description also
+ public abstract void cdata(String cdata);
+ public abstract void emptyElement(String name, List attributes);
+ public abstract void startElement(String name, List attributes);
+ public abstract void endElement(String name);
+ public abstract void chardata(int beginLine, int beginColumn, int endLine, int endColumn);
+ public abstract void reference(String ref);
+}
\ No newline at end of file
diff --git a/src/XMLElement.java b/src/org/catacombae/xml/XMLElement.java
similarity index 50%
rename from src/XMLElement.java
rename to src/org/catacombae/xml/XMLElement.java
index 4a8658d..ac347e3 100644
--- a/src/XMLElement.java
+++ b/src/org/catacombae/xml/XMLElement.java
@@ -1,25 +1,24 @@
/*-
* Copyright (C) 2006 Erik Larsson
*
- * All rights reserved.
- *
- * 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 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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ * along with this program. If not, see .
*/
+package org.catacombae.xml;
+
import java.io.PrintStream;
-abstract class XMLElement {
+public abstract class XMLElement {
protected abstract void _printTree(PrintStream pw, int level);
}
diff --git a/src/org/catacombae/xml/XMLNode.java b/src/org/catacombae/xml/XMLNode.java
new file mode 100644
index 0000000..98bf02d
--- /dev/null
+++ b/src/org/catacombae/xml/XMLNode.java
@@ -0,0 +1,68 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.xml;
+
+import java.util.LinkedList;
+import java.io.*;
+
+public class XMLNode extends XMLElement {
+ public final String namespaceURI;
+ public final String sName;
+ public final String qName;
+ public final Attribute2[] attrs;
+ public final XMLNode parent;
+ private final LinkedList children;
+
+ public XMLNode(String namespaceURI, String sName,
+ String qName, Attribute2[] attrs, XMLNode parent) {
+ this.namespaceURI = namespaceURI;
+ this.sName = sName;
+ this.qName = qName;
+ this.attrs = attrs;
+ this.parent = parent;
+ this.children = new LinkedList();
+ }
+
+ public void addChild(XMLElement x) {
+ children.addLast(x);
+ }
+
+ public void printTree(PrintStream pw) {
+ _printTree(pw, 0);
+ }
+
+ protected void _printTree(PrintStream pw, int level) {
+ for(int i = 0; i < level; ++i)
+ pw.print(" ");
+ pw.print("<");
+ pw.print(qName);
+ for(Attribute2 a : attrs)
+ pw.print(" " + a.qName + "=" + a.value);
+ pw.println(">");
+ for(XMLElement xe : children)
+ xe._printTree(pw, level+1);
+
+ for(int i = 0; i < level; ++i)
+ pw.print(" ");
+ pw.println("" + qName + ">");
+ }
+ public XMLElement[] getChildren() {
+ return children.toArray(new XMLElement[children.size()]);
+ }
+
+}
diff --git a/src/org/catacombae/xml/XMLText.java b/src/org/catacombae/xml/XMLText.java
new file mode 100644
index 0000000..9fcf4b8
--- /dev/null
+++ b/src/org/catacombae/xml/XMLText.java
@@ -0,0 +1,140 @@
+/*-
+ * Copyright (C) 2006 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.xml;
+
+import org.catacombae.dmgextractor.io.*;
+import java.io.*;
+import java.nio.charset.Charset;
+
+public class XMLText extends XMLElement {
+ private static final char CR = '\r';
+ private static final char LF = '\n';
+ private static final char TAB = '\t';
+ private final String text;
+ private int beginLine = -1, beginColumn = -1, endLine = -1, endColumn = -1;
+ private long beginOffset, endOffset;
+ private final SynchronizedRandomAccessStream xmlFile;
+ private Charset encoding;
+
+ public XMLText(String text) {
+ this.text = text;
+ this.xmlFile = null;
+ }
+ public XMLText(SynchronizedRandomAccessStream xmlFile, Charset encoding,
+ long beginOffset, long endOffset) {
+ this.text = null;
+ this.xmlFile = xmlFile;
+ this.encoding = encoding;
+ this.beginOffset = beginOffset;
+ this.endOffset = endOffset;
+ //SynchronizedRandomAccessStream sras = new SynchronizedRandomAccessStream(new RandomAccessFileStream(raf));
+ }
+
+ /** This way of dealing with the issue of lines and columns is very heavy and fragile as it tries to
+ read through the entire xmlFile to seek forward to the desired lines and columns (there's no
+ algorithmic way to do this, so we have toseek exhaustively). The calculation will not be performed
+ in the constructor, but on the first call to getText()... (in order to reduce workload) */
+ public XMLText(SynchronizedRandomAccessStream xmlFile, Charset encoding,
+ int beginLine, int beginColumn, int endLine, int endColumn) {
+ this(xmlFile, encoding, -1, -1); // we set the -1 fields later, ...
+ if(endLine < beginLine || (endLine == beginLine && endColumn < beginColumn))
+ throw new IllegalArgumentException("negative interval length");
+
+ this.beginLine = beginLine;
+ this.beginColumn = beginColumn;
+ this.endLine = endLine;
+ this.endColumn = endColumn;
+ }
+
+ public Reader getText() throws IOException {
+ if(text == null) {
+ if(beginOffset == -1 && endOffset == -1)
+ calculateOffsets();
+ return new InputStreamReader(new RandomAccessInputStream(xmlFile, beginOffset, endOffset-beginOffset), encoding);
+ }
+ else
+ return new StringReader(text);
+ }
+
+ private void calculateOffsets() throws IOException {
+ ByteCountInputStream bcis = new ByteCountInputStream(new BufferedInputStream(new RandomAccessInputStream(xmlFile)));
+ Reader lnr = new CharByCharReader(bcis, encoding);
+ //Vi har xmlFile
+ //CharsetDecoder decoder = Charset.newDecoder();
+
+ boolean previousCR = false;
+ long lineNumber = 1, colNumber = -1;
+ //int beginOffset = -1, endOffset = -1;
+
+ int currentChar = 0;
+ while(currentChar >= 0) {
+ char c = (char)currentChar;
+
+ boolean lfskip = false;
+ if(c == CR) {
+ ++lineNumber;
+ previousCR = true;
+ }
+ else if(c == LF) {
+ if(!previousCR) {
+ ++lineNumber;
+ colNumber = 0;
+ }
+ else {
+ previousCR = false;
+ lfskip = true; // We haven't changed the col or line number in this iteration, as in the other cases
+ }
+ }
+ else if(c == TAB) {
+ colNumber += 8;
+ previousCR = false;
+ }
+ else {
+ ++colNumber;
+ previousCR = false;
+ }
+
+// System.err.println("Trying to read... lineNumber=" + lineNumber + " colNumber=" + colNumber);
+
+ if(!lfskip) {
+ if(lineNumber == beginLine && colNumber == beginColumn)
+ beginOffset = bcis.getBytesRead()-1; // We have already passed the position.
+ if(lineNumber == endLine && colNumber == endColumn) {
+ endOffset = bcis.getBytesRead();
+ break;
+ }
+ }
+ currentChar = lnr.read();
+ }
+
+ if(beginOffset == -1 || endOffset == -1)
+ throw new RuntimeException("Could not find the requested interval! (begin: (" + beginLine + "," + beginColumn + ") end: (" + endLine + "," + endColumn + "))");
+// else
+// System.out.println("Terminating with beginOffset=" + beginOffset + " endOffset=" + endOffset);
+ }
+
+ protected void _printTree(PrintStream pw, int level) {
+ for(int i = 0; i < level; ++i)
+ pw.print(" ");
+ pw.println(text.toString());
+ }
+
+// public static void main(String[] args) {
+// System.out.println(args[0] + " " + args[1]);
+// }
+}
diff --git a/src/org/catacombae/xml/apx/.cvsignore b/src/org/catacombae/xml/apx/.cvsignore
new file mode 100644
index 0000000..cd5dc09
--- /dev/null
+++ b/src/org/catacombae/xml/apx/.cvsignore
@@ -0,0 +1,5 @@
+*~
+*#
+*.class
+.DS_Store
+Thumbs.db
diff --git a/src/org/catacombae/xml/apx/APXParser.java b/src/org/catacombae/xml/apx/APXParser.java
new file mode 100644
index 0000000..c8809c9
--- /dev/null
+++ b/src/org/catacombae/xml/apx/APXParser.java
@@ -0,0 +1,804 @@
+/* Generated By:JavaCC: Do not edit this line. APXParser.java */
+/*-
+ * Copyright (C) 2007 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.xml.apx;
+import org.catacombae.xml.*;
+
+import java.util.*;
+import java.io.*;
+import java.nio.charset.Charset;
+
+/** Early version of the APX Parser for XML. Currently does not parse DTDs and so doesn't check
+ for any validity constraints. It just breaks up the syntactic structure of an XML file and
+ reports it to an org.catacombae.xml.XMLContentHandler. This is sufficient for my needs so it
+ remains to be seen if the parser will be extended in the future. */
+public class APXParser implements APXParserConstants {
+ public static final String DEFAULT_ENCODING = "US-ASCII";
+ //private Reader usedReader;
+ private XMLContentHandler contentHandler;
+
+ /** Test main method. The first and only argument denotes a file containing an XML document. */
+ public static void main(String[] args) throws Exception {
+ InputStream is;
+ if(args.length != 1)
+ throw new RuntimeException("The only valid argument is the name of the input file!");
+
+ // First, we read the encoding from the xml declaration (if it exists)
+ APXParser encodingParser = create(new InputStreamReader(new FileInputStream(args[0]), DEFAULT_ENCODING),
+ new NullXMLContentHandler(Charset.forName(DEFAULT_ENCODING)));
+ String encoding = null;
+ try { encoding = encodingParser.xmlDecl();} catch(ParseException pe) {}
+ if(encoding == null) encoding = DEFAULT_ENCODING;
+
+ encodingParser = null; // GC candy
+
+ // Then we create a new stream, and parse the entire document using the appropriate encoding
+ InputStreamReader usedReader;
+ usedReader = new InputStreamReader(new FileInputStream(args[0]), encoding);
+ APXParser a = create(usedReader, new DebugXMLContentHandler(Charset.forName(encoding))); //new NullXMLContentHandler(encoding));//
+ a.xmlDocument();
+ }
+
+ /** This is the way to create an APXParser. Don't use the constructor even if it would be possible.
+ @param misr the reader supplying the input data for the parser. Can not be null.
+ @param xch the content handler that takes care of the contents of the parsed XML document. Can
+ not be null. Use NullXMLContentHandler if you are not intrested in the contents of
+ the document.
+ @return the parser (parser.xmlDocument() will start the parsing) */
+ public static APXParser create(Reader misr, XMLContentHandler xch) {
+ APXParser a = new APXParser(misr);
+ //a.usedReader = misr;
+ a.contentHandler = xch;
+ return a;
+ }
+
+ final public void xmlDocument() throws ParseException {
+ prolog();
+ element();
+ label_1:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STARTPI:
+ case STARTCOMMENT:
+ ;
+ break;
+ default:
+ jj_la1[0] = jj_gen;
+ break label_1;
+ }
+ misc();
+ }
+ jj_consume_token(0);
+ }
+
+ final public void prolog() throws ParseException {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STARTXMLDECL:
+ xmlDecl();
+ break;
+ default:
+ jj_la1[1] = jj_gen;
+ ;
+ }
+ label_2:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STARTPI:
+ case STARTCOMMENT:
+ ;
+ break;
+ default:
+ jj_la1[2] = jj_gen;
+ break label_2;
+ }
+ misc();
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STARTDOCTYPEDECL:
+ doctypeDecl();
+ label_3:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STARTPI:
+ case STARTCOMMENT:
+ ;
+ break;
+ default:
+ jj_la1[3] = jj_gen;
+ break label_3;
+ }
+ misc();
+ }
+ break;
+ default:
+ jj_la1[4] = jj_gen;
+ ;
+ }
+ }
+
+/* For convenience, this method returns the encoding. We need to determine that before anything else. */
+ final public String xmlDecl() throws ParseException {
+ String version, encoding = null;
+ Boolean standalone = null;
+ jj_consume_token(STARTXMLDECL);
+ // Takes us to state WithinXMLDecl
+ version = versionInfo();
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ENCODING:
+ /*LOOKAHEAD(2)*/ encoding = encodingDecl();
+ break;
+ default:
+ jj_la1[5] = jj_gen;
+ ;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STANDALONE:
+ /*LOOKAHEAD(2)*/ standalone = sdDecl();
+ break;
+ default:
+ jj_la1[6] = jj_gen;
+ ;
+ }
+ jj_consume_token(ENDXMLDECL);
+ contentHandler.xmlDecl(version, encoding, standalone); {if (true) return encoding;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public String versionInfo() throws ParseException {
+ Token versionString;
+ jj_consume_token(VERSION);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case VERSION_DOPEN:
+ jj_consume_token(VERSION_DOPEN);
+ versionString = jj_consume_token(VER);
+ jj_consume_token(VERSION_DCLOSE);
+ break;
+ case VERSION_SOPEN:
+ jj_consume_token(VERSION_SOPEN);
+ versionString = jj_consume_token(VER);
+ jj_consume_token(VERSION_SCLOSE);
+ break;
+ default:
+ jj_la1[7] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return versionString.image;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public String encodingDecl() throws ParseException {
+ Token encoding;
+ jj_consume_token(ENCODING);
+ jj_consume_token(XMLDECL_EQ);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case XD_SQUOTE_OPEN:
+ jj_consume_token(XD_SQUOTE_OPEN);
+ encoding = jj_consume_token(XD_SQUOTE_STRING);
+ jj_consume_token(XD_SQUOTE_CLOSE);
+ break;
+ case XD_DQUOTE_OPEN:
+ jj_consume_token(XD_DQUOTE_OPEN);
+ encoding = jj_consume_token(XD_DQUOTE_STRING);
+ jj_consume_token(XD_DQUOTE_CLOSE);
+ break;
+ default:
+ jj_la1[8] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return encoding.image;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public boolean sdDecl() throws ParseException {
+ boolean b;
+ jj_consume_token(STANDALONE);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STANDALONE_DOPEN:
+ jj_consume_token(STANDALONE_DOPEN);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STANDALONE_YES:
+ jj_consume_token(STANDALONE_YES);
+ b = true;
+ break;
+ case STANDALONE_NO:
+ jj_consume_token(STANDALONE_NO);
+ b = false;
+ break;
+ default:
+ jj_la1[9] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ jj_consume_token(STANDALONE_DCLOSE);
+ break;
+ case STANDALONE_SOPEN:
+ jj_consume_token(STANDALONE_SOPEN);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STANDALONE_YES:
+ jj_consume_token(STANDALONE_YES);
+ b = true;
+ break;
+ case STANDALONE_NO:
+ jj_consume_token(STANDALONE_NO);
+ b = false;
+ break;
+ default:
+ jj_la1[10] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ jj_consume_token(STANDALONE_SCLOSE);
+ break;
+ default:
+ jj_la1[11] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return b;}
+ throw new Error("Missing return statement in function");
+ }
+
+// Needs more work to support inline DTD declarations
+ final public void doctypeDecl() throws ParseException {
+ Token name;
+ jj_consume_token(STARTDOCTYPEDECL);
+ // DEFAULT -> WithinDoctypeDecl
+
+ // Lexical state: WithinDoctypeDecl
+ /**/ name = jj_consume_token(WDD_NAME);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case EXTERNALID:
+ jj_consume_token(EXTERNALID);
+ break;
+ default:
+ jj_la1[12] = jj_gen;
+ ;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case BEGIN_INTSUBSET:
+ jj_consume_token(BEGIN_INTSUBSET);
+ intSubset();
+ break;
+ default:
+ jj_la1[13] = jj_gen;
+ ;
+ }
+ jj_consume_token(ENDDOCTYPEDECL);
+ contentHandler.doctype(name.image, null);
+ }
+
+ final public void intSubset() throws ParseException {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHATEVER:
+ jj_consume_token(WHATEVER);
+ break;
+ default:
+ jj_la1[14] = jj_gen;
+ ;
+ }
+ jj_consume_token(END_INTSUBSET);
+ }
+
+//void markupDecl() :
+//{}
+//{
+//
+//}
+ final public void misc() throws ParseException {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STARTCOMMENT:
+ comment();
+ break;
+ case STARTPI:
+ pi();
+ break;
+ default:
+ jj_la1[15] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+
+ final public void comment() throws ParseException {
+ Token t;
+ StringBuilder sb = new StringBuilder();
+ jj_consume_token(STARTCOMMENT);
+ label_4:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMENT_CHAR:
+ ;
+ break;
+ default:
+ jj_la1[16] = jj_gen;
+ break label_4;
+ }
+ t = jj_consume_token(COMMENT_CHAR);
+ sb.append(t.image);
+ }
+ jj_consume_token(ENDCOMMENT);
+ contentHandler.comment(sb.toString());
+ }
+
+ final public void pi() throws ParseException {
+ Token target;
+ StringBuilder content = null;
+ jj_consume_token(STARTPI);
+ target = jj_consume_token(PITARGET);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WITHINPI_S:
+ jj_consume_token(WITHINPI_S);
+ content = piContent();
+ break;
+ case ENDPI:
+ jj_consume_token(ENDPI);
+ break;
+ default:
+ jj_la1[17] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ contentHandler.pi(target.image, content.toString());
+ }
+
+// returns the content of the processor instruction, minus the trailing "?>"
+ final public StringBuilder piContent() throws ParseException {
+ Token t;
+ StringBuilder sb = new StringBuilder();
+ label_5:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ENDPI:
+ jj_consume_token(ENDPI);
+ break;
+ case PC_CHAR:
+ t = jj_consume_token(PC_CHAR);
+ sb.append(t.image);
+ break;
+ default:
+ jj_la1[18] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ENDPI:
+ case PC_CHAR:
+ ;
+ break;
+ default:
+ jj_la1[19] = jj_gen;
+ break label_5;
+ }
+ }
+ {if (true) return sb;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public void element() throws ParseException {
+ String name;
+ Attribute currentAttribute;
+ LinkedList attributes = new LinkedList();
+ jj_consume_token(STARTTAG);
+ // DEFAULT -> WithinTag
+ name = elementname();
+ label_6:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WT_NAME:
+ ;
+ break;
+ default:
+ jj_la1[20] = jj_gen;
+ break label_6;
+ }
+ currentAttribute = attribute();
+ attributes.add(currentAttribute);
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case EMPTY_ENDTAG:
+ jj_consume_token(EMPTY_ENDTAG);
+ contentHandler.emptyElement(name, attributes);
+ break;
+ case ENDTAG:
+ jj_consume_token(ENDTAG);
+ contentHandler.startElement(name, attributes);
+ content();
+ etag(name);
+ break;
+ default:
+ jj_la1[21] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+
+ final public String elementname() throws ParseException {
+ Token t;
+ t = jj_consume_token(WT_NAME);
+ {if (true) return t.image;}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public Attribute attribute() throws ParseException {
+ Token t;
+ String name;
+ LinkedList value = new LinkedList();
+ name = elementname();
+ jj_consume_token(WT_EQ);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WT_DQUOTE:
+ jj_consume_token(WT_DQUOTE);
+ label_7:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ATTR_DQUOTE_STRING:
+ case ATTR_REFERENCE:
+ ;
+ break;
+ default:
+ jj_la1[22] = jj_gen;
+ break label_7;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ATTR_DQUOTE_STRING:
+ t = jj_consume_token(ATTR_DQUOTE_STRING);
+ value.add(new Attribute.StringComponent(t.image));
+ break;
+ case ATTR_REFERENCE:
+ t = jj_consume_token(ATTR_REFERENCE);
+ value.add(new Attribute.ReferenceComponent(t.image));
+ break;
+ default:
+ jj_la1[23] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ jj_consume_token(ATTR_DQUOTE);
+ break;
+ case WT_SQUOTE:
+ jj_consume_token(WT_SQUOTE);
+ label_8:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ATTR_SQUOTE_STRING:
+ case ATTR_REFERENCE:
+ ;
+ break;
+ default:
+ jj_la1[24] = jj_gen;
+ break label_8;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ATTR_SQUOTE_STRING:
+ t = jj_consume_token(ATTR_SQUOTE_STRING);
+ value.add(new Attribute.StringComponent(t.image));
+ break;
+ case ATTR_REFERENCE:
+ t = jj_consume_token(ATTR_REFERENCE);
+ value.add(new Attribute.ReferenceComponent(t.image));
+ break;
+ default:
+ jj_la1[25] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ jj_consume_token(ATTR_SQUOTE);
+ break;
+ default:
+ jj_la1[26] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ {if (true) return new Attribute(name, new Attribute.Value(value));}
+ throw new Error("Missing return statement in function");
+ }
+
+ final public void etag(String startName) throws ParseException {
+ String name;
+ jj_consume_token(STARTCLOSINGTAG);
+ // DEFAULT -> WithinTag
+ name = elementname();
+ jj_consume_token(ENDTAG);
+ if(startName.equals(name))
+ contentHandler.endElement(name);
+ else
+ {if (true) throw new ParseException("Expected \"" + startName + "\" but got \"" + name + "\".");}
+ }
+
+ final public void content() throws ParseException {
+ //StringBuilder sb = new StringBuilder();
+ Token t;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case CHARDATA:
+ charData();
+ break;
+ default:
+ jj_la1[27] = jj_gen;
+ ;
+ }
+ label_9:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STARTTAG:
+ case STARTPI:
+ case STARTCOMMENT:
+ case STARTCDATA:
+ case DEFAULT_REFERENCE:
+ ;
+ break;
+ default:
+ jj_la1[28] = jj_gen;
+ break label_9;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STARTTAG:
+ element();
+ break;
+ case DEFAULT_REFERENCE:
+ t = jj_consume_token(DEFAULT_REFERENCE);
+ break;
+ case STARTCDATA:
+ cdSect();
+ break;
+ case STARTPI:
+ pi();
+ break;
+ case STARTCOMMENT:
+ comment();
+ break;
+ default:
+ jj_la1[29] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case CHARDATA:
+ charData();
+ break;
+ default:
+ jj_la1[30] = jj_gen;
+ ;
+ }
+ }
+ }
+
+ final public void charData() throws ParseException {
+ Token t;
+ int beginLine = -1, beginColumn = -1;
+ label_10:
+ while (true) {
+ t = jj_consume_token(CHARDATA);
+ if(beginLine == -1) { beginLine = t.beginLine; beginColumn = t.beginColumn; }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case CHARDATA:
+ ;
+ break;
+ default:
+ jj_la1[31] = jj_gen;
+ break label_10;
+ }
+ }
+ contentHandler.chardata(beginLine, beginColumn, t.endLine, t.endColumn);
+ }
+
+ final public void cdSect() throws ParseException {
+ Token t;
+ StringBuilder cdata = new StringBuilder();
+ jj_consume_token(STARTCDATA);
+ label_11:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ENDCDATA:
+ case WCD_CHAR:
+ ;
+ break;
+ default:
+ jj_la1[32] = jj_gen;
+ break label_11;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ENDCDATA:
+ jj_consume_token(ENDCDATA);
+ break;
+ case WCD_CHAR:
+ t = jj_consume_token(WCD_CHAR);
+ cdata.append(t.image);
+ break;
+ default:
+ jj_la1[33] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ contentHandler.cdata(cdata.toString());
+ }
+
+ public APXParserTokenManager token_source;
+ SimpleCharStream jj_input_stream;
+ public Token token, jj_nt;
+ private int jj_ntk;
+ private int jj_gen;
+ final private int[] jj_la1 = new int[34];
+ static private int[] jj_la1_0;
+ static private int[] jj_la1_1;
+ static private int[] jj_la1_2;
+ static {
+ jj_la1_0();
+ jj_la1_1();
+ jj_la1_2();
+ }
+ private static void jj_la1_0() {
+ jj_la1_0 = new int[] {0x240000,0x80000,0x240000,0x240000,0x100000,0x40000000,0x80000000,0x0,0x18000000,0x0,0x0,0x0,0x0,0x0,0x0,0x240000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x1650000,0x1650000,0x800000,0x800000,0x0,0x0,};
+ }
+ private static void jj_la1_1() {
+ jj_la1_1 = new int[] {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xa0,0x0,0xc000,0xc000,0x1400,0x40000,0x400000,0x2000000,0x0,0x10000000,0x20000000,0x20000000,0x20000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,};
+ }
+ private static void jj_la1_2() {
+ jj_la1_2 = new int[] {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x2,0x2,0x8,0xc0,0x2800,0x2800,0x3000,0x3000,0x30,0x0,0x0,0x0,0x0,0x0,0xc000,0xc000,};
+ }
+
+ public APXParser(java.io.InputStream stream) {
+ this(stream, null);
+ }
+ public APXParser(java.io.InputStream stream, String encoding) {
+ try { jj_input_stream = new SimpleCharStream(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
+ token_source = new APXParserTokenManager(jj_input_stream);
+ token = new Token();
+ jj_ntk = -1;
+ jj_gen = 0;
+ for (int i = 0; i < 34; i++) jj_la1[i] = -1;
+ }
+
+ public void ReInit(java.io.InputStream stream) {
+ ReInit(stream, null);
+ }
+ public void ReInit(java.io.InputStream stream, String encoding) {
+ try { jj_input_stream.ReInit(stream, encoding, 1, 1); } catch(java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); }
+ token_source.ReInit(jj_input_stream);
+ token = new Token();
+ jj_ntk = -1;
+ jj_gen = 0;
+ for (int i = 0; i < 34; i++) jj_la1[i] = -1;
+ }
+
+ public APXParser(java.io.Reader stream) {
+ jj_input_stream = new SimpleCharStream(stream, 1, 1);
+ token_source = new APXParserTokenManager(jj_input_stream);
+ token = new Token();
+ jj_ntk = -1;
+ jj_gen = 0;
+ for (int i = 0; i < 34; i++) jj_la1[i] = -1;
+ }
+
+ public void ReInit(java.io.Reader stream) {
+ jj_input_stream.ReInit(stream, 1, 1);
+ token_source.ReInit(jj_input_stream);
+ token = new Token();
+ jj_ntk = -1;
+ jj_gen = 0;
+ for (int i = 0; i < 34; i++) jj_la1[i] = -1;
+ }
+
+ public APXParser(APXParserTokenManager tm) {
+ token_source = tm;
+ token = new Token();
+ jj_ntk = -1;
+ jj_gen = 0;
+ for (int i = 0; i < 34; i++) jj_la1[i] = -1;
+ }
+
+ public void ReInit(APXParserTokenManager tm) {
+ token_source = tm;
+ token = new Token();
+ jj_ntk = -1;
+ jj_gen = 0;
+ for (int i = 0; i < 34; i++) jj_la1[i] = -1;
+ }
+
+ final private Token jj_consume_token(int kind) throws ParseException {
+ Token oldToken;
+ if ((oldToken = token).next != null) token = token.next;
+ else token = token.next = token_source.getNextToken();
+ jj_ntk = -1;
+ if (token.kind == kind) {
+ jj_gen++;
+ return token;
+ }
+ token = oldToken;
+ jj_kind = kind;
+ throw generateParseException();
+ }
+
+ final public Token getNextToken() {
+ if (token.next != null) token = token.next;
+ else token = token.next = token_source.getNextToken();
+ jj_ntk = -1;
+ jj_gen++;
+ return token;
+ }
+
+ final public Token getToken(int index) {
+ Token t = token;
+ for (int i = 0; i < index; i++) {
+ if (t.next != null) t = t.next;
+ else t = t.next = token_source.getNextToken();
+ }
+ return t;
+ }
+
+ final private int jj_ntk() {
+ if ((jj_nt=token.next) == null)
+ return (jj_ntk = (token.next=token_source.getNextToken()).kind);
+ else
+ return (jj_ntk = jj_nt.kind);
+ }
+
+ private java.util.Vector jj_expentries = new java.util.Vector();
+ private int[] jj_expentry;
+ private int jj_kind = -1;
+
+ public ParseException generateParseException() {
+ jj_expentries.removeAllElements();
+ boolean[] la1tokens = new boolean[80];
+ for (int i = 0; i < 80; i++) {
+ la1tokens[i] = false;
+ }
+ if (jj_kind >= 0) {
+ la1tokens[jj_kind] = true;
+ jj_kind = -1;
+ }
+ for (int i = 0; i < 34; i++) {
+ if (jj_la1[i] == jj_gen) {
+ for (int j = 0; j < 32; j++) {
+ if ((jj_la1_0[i] & (1<.
+ */
+
+// Name explanation: APX = APX Parser for XML (at least officially)
+
+options {
+ STATIC = false;
+ UNICODE_INPUT = true;
+}
+
+PARSER_BEGIN(APXParser)
+/*-
+ * Copyright (C) 2007 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.xml.apx;
+import org.catacombae.xml.*;
+
+import java.util.*;
+import java.io.*;
+import java.nio.charset.Charset;
+
+/** Early version of the APX Parser for XML. Currently does not parse DTDs and so doesn't check
+ for any validity constraints. It just breaks up the syntactic structure of an XML file and
+ reports it to an org.catacombae.xml.XMLContentHandler. This is sufficient for my needs so it
+ remains to be seen if the parser will be extended in the future. */
+public class APXParser {
+ public static final String DEFAULT_ENCODING = "US-ASCII";
+ //private Reader usedReader;
+ private XMLContentHandler contentHandler;
+
+ /** Test main method. The first and only argument denotes a file containing an XML document. */
+ public static void main(String[] args) throws Exception {
+ InputStream is;
+ if(args.length != 1)
+ throw new RuntimeException("The only valid argument is the name of the input file!");
+
+ // First, we read the encoding from the xml declaration (if it exists)
+ APXParser encodingParser = create(new InputStreamReader(new FileInputStream(args[0]), DEFAULT_ENCODING),
+ new NullXMLContentHandler(Charset.forName(DEFAULT_ENCODING)));
+ String encoding = null;
+ try { encoding = encodingParser.xmlDecl();} catch(ParseException pe) {}
+ if(encoding == null) encoding = DEFAULT_ENCODING;
+
+ encodingParser = null; // GC candy
+
+ // Then we create a new stream, and parse the entire document using the appropriate encoding
+ InputStreamReader usedReader;
+ usedReader = new InputStreamReader(new FileInputStream(args[0]), encoding);
+ APXParser a = create(usedReader, new DebugXMLContentHandler(Charset.forName(encoding))); //new NullXMLContentHandler(encoding));//
+ a.xmlDocument();
+ }
+
+ /** This is the way to create an APXParser. Don't use the constructor even if it would be possible.
+ @param misr the reader supplying the input data for the parser. Can not be null.
+ @param xch the content handler that takes care of the contents of the parsed XML document. Can
+ not be null. Use NullXMLContentHandler if you are not intrested in the contents of
+ the document.
+ @return the parser (parser.xmlDocument() will start the parsing) */
+ public static APXParser create(Reader misr, XMLContentHandler xch) {
+ APXParser a = new APXParser(misr);
+ //a.usedReader = misr;
+ a.contentHandler = xch;
+ return a;
+ }
+}
+PARSER_END(APXParser)
+
+/* Tokens common to all states. */
+/* PROBLEMS: The class CHAR should be able to match the 10000-10FFFF range, but java's Unicode
+ escape does not allow it, since you have to convert it to UTF-16BE first. This conversion
+ might not be easy to do as part of a token, and it's really not urgent, considering this
+ parser is only used to parse the Plist. This is non-urgent TODO. */
+<*> TOKEN: {
+ < #SINGLE_S: (" " | "\t" | "\n" | "\r") >
+ //| < #S: ( )+ >
+ | < #CHAR: ["\u0009", "\n", "\r", "\u0020"-"\uD7FF", "\uE000"-"\uFFFD"/*, "\u10000"-"\u10FFFF"*/] >
+ | < #NAME: ( )* >
+ | < #NAMESTART: ( | "_" | ":" ) >
+ | < #NAMECHAR: | | "." | "-" | "_" | ":" | | >
+ | < #LETTER: ( | ) >
+ | < #BASECHAR: ["\u0041"-"\u005A","\u0061"-"\u007A","\u00C0"-"\u00D6","\u00D8"-"\u00F6",
+ "\u00F8"-"\u00FF","\u0100"-"\u0131","\u0134"-"\u013E","\u0141"-"\u0148",
+ "\u014A"-"\u017E","\u0180"-"\u01C3","\u01CD"-"\u01F0","\u01F4"-"\u01F5",
+ "\u01FA"-"\u0217","\u0250"-"\u02A8","\u02BB"-"\u02C1","\u0386",
+ "\u0388"-"\u038A","\u038C","\u038E"-"\u03A1","\u03A3"-"\u03CE",
+ "\u03D0"-"\u03D6","\u03DA","\u03DC","\u03DE","\u03E0","\u03E2"-"\u03F3",
+ "\u0401"-"\u040C","\u040E"-"\u044F","\u0451"-"\u045C","\u045E"-"\u0481",
+ "\u0490"-"\u04C4","\u04C7"-"\u04C8","\u04CB"-"\u04CC","\u04D0"-"\u04EB",
+ "\u04EE"-"\u04F5","\u04F8"-"\u04F9","\u0531"-"\u0556","\u0559",
+ "\u0561"-"\u0586","\u05D0"-"\u05EA","\u05F0"-"\u05F2","\u0621"-"\u063A",
+ "\u0641"-"\u064A","\u0671"-"\u06B7","\u06BA"-"\u06BE","\u06C0"-"\u06CE",
+ "\u06D0"-"\u06D3","\u06D5","\u06E5"-"\u06E6","\u0905"-"\u0939","\u093D",
+ "\u0958"-"\u0961","\u0985"-"\u098C","\u098F"-"\u0990","\u0993"-"\u09A8",
+ "\u09AA"-"\u09B0","\u09B2","\u09B6"-"\u09B9","\u09DC"-"\u09DD",
+ "\u09DF"-"\u09E1","\u09F0"-"\u09F1","\u0A05"-"\u0A0A","\u0A0F"-"\u0A10",
+ "\u0A13"-"\u0A28","\u0A2A"-"\u0A30","\u0A32"-"\u0A33","\u0A35"-"\u0A36",
+ "\u0A38"-"\u0A39","\u0A59"-"\u0A5C","\u0A5E","\u0A72"-"\u0A74",
+ "\u0A85"-"\u0A8B","\u0A8D","\u0A8F"-"\u0A91","\u0A93"-"\u0AA8",
+ "\u0AAA"-"\u0AB0","\u0AB2"-"\u0AB3","\u0AB5"-"\u0AB9","\u0ABD","\u0AE0",
+ "\u0B05"-"\u0B0C","\u0B0F"-"\u0B10","\u0B13"-"\u0B28","\u0B2A"-"\u0B30",
+ "\u0B32"-"\u0B33","\u0B36"-"\u0B39","\u0B3D","\u0B5C"-"\u0B5D",
+ "\u0B5F"-"\u0B61","\u0B85"-"\u0B8A","\u0B8E"-"\u0B90","\u0B92"-"\u0B95",
+ "\u0B99"-"\u0B9A","\u0B9C","\u0B9E"-"\u0B9F","\u0BA3"-"\u0BA4",
+ "\u0BA8"-"\u0BAA","\u0BAE"-"\u0BB5","\u0BB7"-"\u0BB9","\u0C05"-"\u0C0C",
+ "\u0C0E"-"\u0C10","\u0C12"-"\u0C28","\u0C2A"-"\u0C33","\u0C35"-"\u0C39",
+ "\u0C60"-"\u0C61","\u0C85"-"\u0C8C","\u0C8E"-"\u0C90","\u0C92"-"\u0CA8",
+ "\u0CAA"-"\u0CB3","\u0CB5"-"\u0CB9","\u0CDE","\u0CE0"-"\u0CE1",
+ "\u0D05"-"\u0D0C","\u0D0E"-"\u0D10","\u0D12"-"\u0D28","\u0D2A"-"\u0D39",
+ "\u0D60"-"\u0D61","\u0E01"-"\u0E2E","\u0E30","\u0E32"-"\u0E33",
+ "\u0E40"-"\u0E45","\u0E81"-"\u0E82","\u0E84","\u0E87"-"\u0E88","\u0E8A",
+ "\u0E8D","\u0E94"-"\u0E97","\u0E99"-"\u0E9F","\u0EA1"-"\u0EA3","\u0EA5",
+ "\u0EA7","\u0EAA"-"\u0EAB","\u0EAD"-"\u0EAE","\u0EB0","\u0EB2"-"\u0EB3",
+ "\u0EBD","\u0EC0"-"\u0EC4","\u0F40"-"\u0F47","\u0F49"-"\u0F69",
+ "\u10A0"-"\u10C5","\u10D0"-"\u10F6","\u1100","\u1102"-"\u1103",
+ "\u1105"-"\u1107","\u1109","\u110B"-"\u110C","\u110E"-"\u1112","\u113C",
+ "\u113E","\u1140","\u114C","\u114E","\u1150","\u1154"-"\u1155","\u1159",
+ "\u115F"-"\u1161","\u1163","\u1165","\u1167","\u1169","\u116D"-"\u116E",
+ "\u1172"-"\u1173","\u1175","\u119E","\u11A8","\u11AB","\u11AE"-"\u11AF",
+ "\u11B7"-"\u11B8","\u11BA","\u11BC"-"\u11C2","\u11EB","\u11F0","\u11F9",
+ "\u1E00"-"\u1E9B","\u1EA0"-"\u1EF9","\u1F00"-"\u1F15","\u1F18"-"\u1F1D",
+ "\u1F20"-"\u1F45","\u1F48"-"\u1F4D","\u1F50"-"\u1F57","\u1F59","\u1F5B",
+ "\u1F5D","\u1F5F"-"\u1F7D","\u1F80"-"\u1FB4","\u1FB6"-"\u1FBC","\u1FBE",
+ "\u1FC2"-"\u1FC4","\u1FC6"-"\u1FCC","\u1FD0"-"\u1FD3","\u1FD6"-"\u1FDB",
+ "\u1FE0"-"\u1FEC","\u1FF2"-"\u1FF4","\u1FF6"-"\u1FFC","\u2126",
+ "\u212A"-"\u212B","\u212E","\u2180"-"\u2182","\u3041"-"\u3094",
+ "\u30A1"-"\u30FA","\u3105"-"\u312C","\uAC00"-"\uD7A3"] >
+ | < #IDEOGRAPHIC: ["\u4E00"-"\u9FA5","\u3007","\u3021"-"\u3029"] >
+ | < #COMBININGCHAR: ["\u0300"-"\u0345", "\u0360"-"\u0361", "\u0483"-"\u0486",
+ "\u0591"-"\u05A1", "\u05A3"-"\u05B9", "\u05BB"-"\u05BD", "\u05BF",
+ "\u05C1"-"\u05C2", "\u05C4", "\u064B"-"\u0652", "\u0670",
+ "\u06D6"-"\u06DC", "\u06DD"-"\u06DF", "\u06E0"-"\u06E4",
+ "\u06E7"-"\u06E8", "\u06EA"-"\u06ED", "\u0901"-"\u0903", "\u093C",
+ "\u093E"-"\u094C", "\u094D", "\u0951"-"\u0954", "\u0962"-"\u0963",
+ "\u0981"-"\u0983", "\u09BC", "\u09BE", "\u09BF", "\u09C0"-"\u09C4",
+ "\u09C7"-"\u09C8", "\u09CB"-"\u09CD", "\u09D7", "\u09E2"-"\u09E3",
+ "\u0A02", "\u0A3C", "\u0A3E", "\u0A3F", "\u0A40"-"\u0A42",
+ "\u0A47"-"\u0A48", "\u0A4B"-"\u0A4D", "\u0A70"-"\u0A71",
+ "\u0A81"-"\u0A83", "\u0ABC", "\u0ABE"-"\u0AC5", "\u0AC7"-"\u0AC9",
+ "\u0ACB"-"\u0ACD", "\u0B01"-"\u0B03", "\u0B3C", "\u0B3E"-"\u0B43",
+ "\u0B47"-"\u0B48", "\u0B4B"-"\u0B4D", "\u0B56"-"\u0B57",
+ "\u0B82"-"\u0B83", "\u0BBE"-"\u0BC2", "\u0BC6"-"\u0BC8",
+ "\u0BCA"-"\u0BCD", "\u0BD7", "\u0C01"-"\u0C03", "\u0C3E"-"\u0C44",
+ "\u0C46"-"\u0C48", "\u0C4A"-"\u0C4D", "\u0C55"-"\u0C56",
+ "\u0C82"-"\u0C83", "\u0CBE"-"\u0CC4", "\u0CC6"-"\u0CC8",
+ "\u0CCA"-"\u0CCD", "\u0CD5"-"\u0CD6", "\u0D02"-"\u0D03",
+ "\u0D3E"-"\u0D43", "\u0D46"-"\u0D48", "\u0D4A"-"\u0D4D",
+ "\u0D57", "\u0E31", "\u0E34"-"\u0E3A", "\u0E47"-"\u0E4E", "\u0EB1",
+ "\u0EB4"-"\u0EB9", "\u0EBB"-"\u0EBC", "\u0EC8"-"\u0ECD",
+ "\u0F18"-"\u0F19", "\u0F35", "\u0F37", "\u0F39", "\u0F3E", "\u0F3F",
+ "\u0F71"-"\u0F84", "\u0F86"-"\u0F8B", "\u0F90"-"\u0F95", "\u0F97",
+ "\u0F99"-"\u0FAD", "\u0FB1"-"\u0FB7", "\u0FB9", "\u20D0"-"\u20DC",
+ "\u20E1", "\u302A"-"\u302F", "\u3099", "\u309A"] >
+ | < #DIGIT: ["\u0030"-"\u0039", "\u0660"-"\u0669", "\u06F0"-"\u06F9", "\u0966"-"\u096F",
+ "\u09E6"-"\u09EF", "\u0A66"-"\u0A6F", "\u0AE6"-"\u0AEF", "\u0B66"-"\u0B6F",
+ "\u0BE7"-"\u0BEF", "\u0C66"-"\u0C6F", "\u0CE6"-"\u0CEF", "\u0D66"-"\u0D6F",
+ "\u0E50"-"\u0E59", "\u0ED0"-"\u0ED9", "\u0F20"-"\u0F29"] >
+ | < #EXTENDER: ["\u00B7", "\u02D0", "\u02D1", "\u0387", "\u0640", "\u0E46", "\u0EC6",
+ "\u3005", "\u3031"-"\u3035", "\u309D"-"\u309E", "\u30FC"-"\u30FE"] >
+ | < #REFERENCE: | >
+ | < #ENTITYREF: "&" ()* ";" >
+ | < #CHARREF: "" (["0"-"9"])+ ";" | "" (["0"-"9", "a"-"f", "A"-"F"])+ ";" >
+}
+
+
+SKIP: // Whitespace is skipped in lexical state DEFAULT
+{
+ < >
+}
+
+TOKEN : {
+ < STARTTAG: "<" > : WithinTag
+ | < STARTCLOSINGTAG: "" > : WithinTag
+ | < STARTPI: "" > : WithinPI
+ | < STARTXMLDECL: " : WithinXMLDecl
+ | < STARTDOCTYPEDECL: " : WithinDoctypeDecl
+ | < STARTCOMMENT: "" > : DEFAULT
+ | < COMMENT_ILLEGAL: "--" >
+ | < COMMENT_CHAR: >
+}
+void comment() :
+{
+ Token t;
+ StringBuilder sb = new StringBuilder();
+}
+{
+ // DEFAULT -> Comment
+ ( t = {sb.append(t.image);} )*
+ // Comment -> DEFAULT
+ { contentHandler.comment(sb.toString()); }
+}
+
+/*********************************************************
+ * Here starts the parsing of the processor instructions *
+ *********************************************************/
+
+/* There are two states within a PI, first the one recognizing the identifier of the target
+ of the processor instruction, represented by . Then the parser switches to a
+ state where everything is accepted except for the token.
+ The token of state DEFAULT takes us to the first PI state, labeled WithinPi,
+ the token (representing whitespace) takes us to the second PI state (where
+ everything goes except for ) and the token takes us back to DEFAULT. */
+
+
+ TOKEN: {
+ < ENDPI: "?>" > : DEFAULT
+}
+
+ TOKEN: {
+ < ILLEGALTARGET: ( "X" | "x") ("M" | "m") ("L" | "l") ()* >
+ | < PITARGET: >
+ | < WITHINPI_S: > : WithinPIContent
+}
+void pi() :
+{
+ Token target;
+ StringBuilder content = null;
+}
+{
+
+ target = //{ System.out.println("target: " + target.image); }
+ ( content = piContent() | )
+ { contentHandler.pi(target.image, content.toString()); }
+}
+
+ TOKEN: {
+ //< ENDPI: "?>" > : WithinXMLDecl
+ < PC_CHAR: >
+ //< NONCONTENT: ()* "?>" ()* >
+ //< CONTENT: ("?" (">" | ) | >
+}
+
+// returns the content of the processor instruction, minus the trailing "?>"
+StringBuilder piContent() :
+{
+ Token t;
+ StringBuilder sb = new StringBuilder();
+}
+{
+ (
+ |
+ t = { sb.append(t.image); } )+
+ { return sb; }
+}
+
+/**************************************************
+ * Here starts the parsing of the actual elements *
+ **************************************************/
+ TOKEN: {
+ < WT_EQ: "=" >
+ | < WT_NAME: >
+ //| < WT_S: >
+ | < WT_DQUOTE: "\"" > : Attribute
+ | < WT_SQUOTE: "'" > : Attribute
+ | < EMPTY_ENDTAG: "/>" > : DEFAULT
+ | < ENDTAG: ">" > : DEFAULT
+}
+
+ SKIP: {
+ < >
+}
+
+void element() :
+{
+ String name;
+ Attribute currentAttribute;
+ LinkedList attributes = new LinkedList();
+}
+{
+ // DEFAULT -> WithinTag
+ name = elementname() //name =
+ ( currentAttribute = attribute() {attributes.add(currentAttribute);} )*
+ ( // WithinTag -> DEFAULT
+ { contentHandler.emptyElement(name, attributes); }
+ |
+ // WithinTag -> DEFAULT
+ { contentHandler.startElement(name, attributes); }
+ content()
+ etag(name) )
+}
+
+String elementname() :
+{
+ Token t;
+}
+{
+ t =
+ { return t.image; }
+}
+
+ TOKEN: {
+ < ATTR_DQUOTE: "\"" > : WithinTag
+ | < ATTR_SQUOTE: "'" > : WithinTag
+ | < ATTR_DQUOTE_STRING: ~["<", "&", "\""] >
+ | < ATTR_SQUOTE_STRING: ~["<", "&", "'"] >
+ | < ATTR_REFERENCE: >
+}
+
+Attribute attribute() :
+{
+ Token t;
+ String name;
+ LinkedList value = new LinkedList();
+}
+{
+ name = elementname()
+ (
+ (
+ // WithinTag -> Attribute
+ ( t = { value.add(new Attribute.StringComponent(t.image)); } |
+ t = { value.add(new Attribute.ReferenceComponent(t.image)); } )*
+ // Attribute -> WithinTag
+ )
+ |
+ (
+ // WithinTag -> Attribute
+ ( t = { value.add(new Attribute.StringComponent(t.image)); } |
+ t = { value.add(new Attribute.ReferenceComponent(t.image)); } )*
+ // Attribute -> WithinTag
+ )
+ )
+ { return new Attribute(name, new Attribute.Value(value)); }
+}
+
+void etag(String startName) :
+{
+ String name;
+}
+{
+ // DEFAULT -> WithinTag
+ name = elementname() /*()? */
+ // WithinTag -> DEFAULT
+ {
+ if(startName.equals(name))
+ contentHandler.endElement(name);
+ else
+ throw new ParseException("Expected \"" + startName + "\" but got \"" + name + "\".");
+ }
+}
+
+void content() :
+{
+ //StringBuilder sb = new StringBuilder();
+ Token t;
+}
+{
+ // Lexical state: DEFAULT
+ // Original: CharData? ((element | Reference | CDSect | PI | Comment) CharData?)*
+ // Ekvivalent(?): (element | Reference | CDSect | PI | Comment | CharData)*
+ ( charData() )?
+ ( ( element() | t = | cdSect() | pi() | comment() ) ( charData() )? )*
+
+ //( element() | | cdSect() | pi() | comment() | charData() )*
+}
+
+void charData() :
+{
+ Token t;
+ int beginLine = -1, beginColumn = -1;
+ //StringBuilder sb = new StringBuilder();
+}
+{
+ ( t =
+ { if(beginLine == -1) { beginLine = t.beginLine; beginColumn = t.beginColumn; } } )+
+ { contentHandler.chardata(beginLine, beginColumn, t.endLine, t.endColumn); }
+
+ //( t = { System.out.println("chardata1: \"" + t.image + "\""); })+
+}
+
+
+/******************************************
+ * Here starts the parsing CDATA sections *
+ ******************************************/
+
+/* To remove from a string of s we use a recursive method, cdSect_prime() */
+ TOKEN: {
+ < ENDCDATA: "]]>" > : DEFAULT
+ | < WCD_CHAR: >
+}
+void cdSect() :
+{
+ Token t;
+ StringBuilder cdata = new StringBuilder();
+}
+{
+ // DEFAULT -> WithinCData
+ ( // WithinCData -> DEFAULT
+ |
+ t = {cdata.append(t.image);} )*
+
+ { contentHandler.cdata(cdata.toString()); }
+}
+
+/*
+// Old but interesting code
+
+void xmlDocument() :
+{
+ Token tagName, optionName, optionValue;
+ LinkedList params = new LinkedList();
+}
+{
+
+ tagName =
+ (optionName =
+
+ (
+ optionValue =
+ |
+
+ optionValue =
+ )
+ { params.add(new Param(optionName, optionValue)); }
+ )*
+
+ {
+ System.out.println("Collected the following data:");
+ System.out.println("Tag name: \"" + tagName.image + "\"");
+ System.out.println("Params:");
+ for(Param p : params)
+ System.out.println(" \"" + p.id.image + "\" = \"" + p.string.image + "\"");
+ }
+
+
+
+}
+*/
\ No newline at end of file
diff --git a/src/org/catacombae/xml/apx/APXParserConstants.java b/src/org/catacombae/xml/apx/APXParserConstants.java
new file mode 100644
index 0000000..dfe4b81
--- /dev/null
+++ b/src/org/catacombae/xml/apx/APXParserConstants.java
@@ -0,0 +1,198 @@
+/* Generated By:JavaCC: Do not edit this line. APXParserConstants.java */
+/*-
+ * Copyright (C) 2007 Erik Larsson
+ *
+ * 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 3 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, see .
+ */
+
+package org.catacombae.xml.apx;
+
+public interface APXParserConstants {
+
+ int EOF = 0;
+ int SINGLE_S = 1;
+ int CHAR = 2;
+ int NAME = 3;
+ int NAMESTART = 4;
+ int NAMECHAR = 5;
+ int LETTER = 6;
+ int BASECHAR = 7;
+ int IDEOGRAPHIC = 8;
+ int COMBININGCHAR = 9;
+ int DIGIT = 10;
+ int EXTENDER = 11;
+ int REFERENCE = 12;
+ int ENTITYREF = 13;
+ int CHARREF = 14;
+ int STARTTAG = 16;
+ int STARTCLOSINGTAG = 17;
+ int STARTPI = 18;
+ int STARTXMLDECL = 19;
+ int STARTDOCTYPEDECL = 20;
+ int STARTCOMMENT = 21;
+ int STARTCDATA = 22;
+ int CHARDATA = 23;
+ int DEFAULT_REFERENCE = 24;
+ int ENDXMLDECL = 25;
+ int XMLDECL_EQ = 26;
+ int XD_DQUOTE_OPEN = 27;
+ int XD_SQUOTE_OPEN = 28;
+ int VERSION = 29;
+ int ENCODING = 30;
+ int STANDALONE = 31;
+ int XD_DQUOTE_STRING = 33;
+ int XD_DQUOTE_CLOSE = 34;
+ int XD_SQUOTE_STRING = 35;
+ int XD_SQUOTE_CLOSE = 36;
+ int VERSION_DOPEN = 37;
+ int VERSION_DCLOSE = 38;
+ int VERSION_SOPEN = 39;
+ int VERSION_SCLOSE = 40;
+ int VER = 41;
+ int STANDALONE_DOPEN = 42;
+ int STANDALONE_DCLOSE = 43;
+ int STANDALONE_SOPEN = 44;
+ int STANDALONE_SCLOSE = 45;
+ int STANDALONE_YES = 46;
+ int STANDALONE_NO = 47;
+ int WDD_NAME = 48;
+ int ENDDOCTYPEDECL = 49;
+ int EXTERNALID = 50;
+ int SYSTEMLITERAL = 51;
+ int PUBIDLITERAL = 52;
+ int PUBIDCHAR = 53;
+ int BEGIN_INTSUBSET = 54;
+ int END_INTSUBSET = 56;
+ int WHATEVER = 57;
+ int ENDCOMMENT = 58;
+ int COMMENT_ILLEGAL = 59;
+ int COMMENT_CHAR = 60;
+ int ENDPI = 61;
+ int ILLEGALTARGET = 62;
+ int PITARGET = 63;
+ int WITHINPI_S = 64;
+ int PC_CHAR = 65;
+ int WT_EQ = 66;
+ int WT_NAME = 67;
+ int WT_DQUOTE = 68;
+ int WT_SQUOTE = 69;
+ int EMPTY_ENDTAG = 70;
+ int ENDTAG = 71;
+ int ATTR_DQUOTE = 73;
+ int ATTR_SQUOTE = 74;
+ int ATTR_DQUOTE_STRING = 75;
+ int ATTR_SQUOTE_STRING = 76;
+ int ATTR_REFERENCE = 77;
+ int ENDCDATA = 78;
+ int WCD_CHAR = 79;
+
+ int WithinCData = 0;
+ int Attribute = 1;
+ int WithinTag = 2;
+ int WithinPIContent = 3;
+ int WithinPI = 4;
+ int Comment = 5;
+ int WithinIntSubset = 6;
+ int WithinDoctypeDecl = 7;
+ int WithinXMLDecl_Standalone = 8;
+ int WithinXMLDecl_Version = 9;
+ int WithinXMLDecl_SquoteString = 10;
+ int WithinXMLDecl_DquoteString = 11;
+ int WithinXMLDecl = 12;
+ int DEFAULT = 13;
+
+ String[] tokenImage = {
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "\"<\"",
+ "\"\"",
+ "\"\"",
+ "\"",
+ "",
+ "\"?>\"",
+ "\"=\"",
+ "\"\\\"\"",
+ "\"\\\'\"",
+ "\"version\"",
+ "\"encoding\"",
+ "\"standalone\"",
+ "",
+ "