/*-
* 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 <http://www.gnu.org/licenses/>.
*/
package org.catacombae.hfsexplorer.testcode;
import java.io.BufferedReader;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import org.catacombae.storage.ps.gpt.types.GPTEntry;
import org.catacombae.storage.ps.gpt.types.GPTHeader;
import org.catacombae.storage.ps.gpt.types.GUIDPartitionTable;
import org.catacombae.storage.ps.gpt.types.MutableGPTEntry;
import org.catacombae.storage.ps.gpt.types.MutableGPTHeader;
import org.catacombae.storage.ps.gpt.types.MutableGUIDPartitionTable;
import org.catacombae.storage.io.win32.Win32FileStream;
import org.catacombae.io.FileStream;
import org.catacombae.io.RandomAccessStream;
import org.catacombae.storage.ps.gpt.GPTPartitionType;
import org.catacombae.util.Util;
/**
* This class was specifically written to repair my hard disk GPT table, which had become inconsistent with
* the MBR table.
* <pre>
* When I connected my drive to Windows and reformatted my HFS+ partition as NTFS, only the MBR entry was
* changed by Windows (due to 32-bit Windows XP being incapable of dealing with GPT drives). So I had an
* inconsistent partition table, meaning Windows XP could recognize the NTFS partition, but OS X saw it as a
* faulty HFS partition.
* Instead of deleting the partition in OS X and recreate it as NTFS, which I couldn't do at the time as I had
* managed to put some important data on that partition, I decided to write a program to change the "partition
* type" entry in the GPT entry for my partition. This was easier said than done, since GPT has two similar
* headers on the disk, but with different contents, and both with different checksums.
*
* Finally though, I figured it all out, and at the first attempt of running the complete version of this
* program, it did its job! (If it hadn't, I might have been left with a corrupt GPT table, so it was very
* importand that it worked out of the box, which is why I do so many checks during the process, and backs up
* a lot of data)
*
* So what this program does is:
* - Read the current GPT data into memory.
* - Change the partition type for the second partition (index 1) to "Microsoft basic data", which is the type
* for Windows partitions (NTFS, FAT32(?)).
* - Update all checksums so that the GPT table is valid.
* - Write the modified GPT data back to disk.
*
* I ran it on a Windows XP SP2 (x86) system, with the partitions mounted. Since I knew Windows wouldn't bother
* about the GPT data, I thought it would be safe, and it was. (A little strange that Windows actually allows
* one to write individual sectors of data to the raw disk while it is being used, but it's nice that it
* worked...)
*
* Command line: test.bat testcode.RepairMyGPTPlease \\?\GLOBALROOT\Device\Harddisk2\Partition0
* (test.bat just says java -cp lib\hfsx.jar org.catacombae.hfsexplorer.%1 %2 %3 etc.)
*
* </pre>
*/
public class RepairMyGPTPlease4 {
private static BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws Exception {
long runTimeStamp = System.currentTimeMillis();
RandomAccessStream llf;
if(System.getProperty("os.name").toLowerCase().startsWith("windows"))
llf = new Win32FileStream(args[0]);
else
llf = new FileStream(args[0]);
final GUIDPartitionTable originalGpt = new GUIDPartitionTable(llf, 0);
MutableGUIDPartitionTable gpt = new MutableGUIDPartitionTable(originalGpt);
if(originalGpt.isValid() && gpt.isValid()) {
final int blockSize = 512;
GPTHeader hdr = gpt.getHeader();
// Backup the entire partition table part of the disk, in case something goes wrong
// First the MBR and GPT tables at the beginning of the disk.
final byte[] mbr = new byte[blockSize];
byte[] backup1 = new byte[blockSize + hdr.getNumberOfPartitionEntries()*hdr.getSizeOfPartitionEntry()];
llf.seek(0);
llf.readFully(mbr);
llf.readFully(backup1);
String backupFilename1 = "gpt_mbr_tables-" + runTimeStamp + ".backup";
System.out.print("Backing up MBR and GPT primary header and table to \"" + backupFilename1 + "\"...");
FileOutputStream backupFile1 = new FileOutputStream(backupFilename1);
backupFile1.write(mbr);
backupFile1.write(backup1);
backupFile1.close();
System.out.println("done!");
// Then the backup GPT table at the end of the disk.
byte[] backup2 = new byte[hdr.getNumberOfPartitionEntries()*hdr.getSizeOfPartitionEntry() + blockSize];
llf.seek(hdr.getBackupLBA()*blockSize - hdr.getNumberOfPartitionEntries()*hdr.getSizeOfPartitionEntry());
llf.read(backup2);
String backupFilename2 = "gpt_backup_table-" + runTimeStamp + ".backup";
System.out.print("Backing up GPT backup header and table to \"" + backupFilename2 + "\"...");
FileOutputStream backupFile2 = new FileOutputStream(backupFilename2);
backupFile2.write(backup2);
backupFile2.close();
System.out.println("done!");
/* Now we want to change the partition type for the second partition from:
* Hierarchical File System (HFS+) partition {48465300-0000-11AA-AA11-00306543ECAC}
* to:
* Basic Data Partition {EBD0A0A2-B9E5-4433-87C0-68B6B72699C7}
*/
byte[] efiSystemPartitionType = GPTPartitionType.PARTITION_TYPE_EFI_SYSTEM.getBytes();
byte[] microsoftBasicDataType = GPTPartitionType.PARTITION_TYPE_PRIMARY_PARTITION.getBytes();
byte[] appleHfsType = GPTPartitionType.PARTITION_TYPE_APPLE_HFS.getBytes();
//byte[] microsoftReservedType = GPTEntry.GPTPartitionType.PARTITION_TYPE_MICROSOFT_RESERVED.getBytes();
// First we verify that the current data is as we expect it to be.
System.out.print("Checking if the first partition has type \"EFI System Partition\"...");
byte[] currentType1 = gpt.getEntry(0).getPartitionTypeGUID().getBytes();
if(!Util.arraysEqual(currentType1, efiSystemPartitionType)) {
System.out.println("failed! Halting program.");
System.exit(0);
}
System.out.println("yes.");
System.out.print("Checking if the second partition has type \"Apple HFS\"...");
byte[] currentType2 = gpt.getEntry(1).getPartitionTypeGUID().getBytes();
if(!Util.arraysEqual(currentType2, appleHfsType)) {
System.out.println("failed! Halting program.");
System.exit(0);
}
System.out.println("yes.");
System.out.print("Checking if the third partition has type \"Microsoft Basic Data\"...");
byte[] currentType3 = gpt.getEntry(2).getPartitionTypeGUID().getBytes();
if(!Util.arraysEqual(currentType3, microsoftBasicDataType)) {
System.out.println("failed! Halting program.");
System.exit(0);
}
System.out.println("yes.");
System.out.print("Checking if the fourth partition has type \"Microsoft Basic Data\"...");
byte[] currentType4 = gpt.getEntry(3).getPartitionTypeGUID().getBytes();
if(!Util.arraysEqual(currentType4, microsoftBasicDataType)) {
System.out.println("failed! Halting program.");
System.exit(0);
}
System.out.println("yes.");
System.out.println("All seems to be as expected.");
// Now let's modify the table in memory
System.out.println("Modifying GPT data in memory:");
System.out.print(" - Swapping third and fourth partition...");
MutableGPTEntry modifiedPrimaryEntry3 = gpt.getMutablePrimaryEntry(2);
MutableGPTEntry modifiedBackupEntry3 = gpt.getMutableBackupEntry(2);
MutableGPTEntry modifiedPrimaryEntry4 = gpt.getMutablePrimaryEntry(3);
MutableGPTEntry modifiedBackupEntry4 = gpt.getMutableBackupEntry(3);
GPTEntry tempPrimaryEntry = new GPTEntry(modifiedPrimaryEntry3);
GPTEntry tempBackupEntry = new GPTEntry(modifiedBackupEntry3);
modifiedPrimaryEntry3.setFields(modifiedPrimaryEntry4);
modifiedBackupEntry3.setFields(modifiedBackupEntry4);
modifiedPrimaryEntry4.setFields(tempPrimaryEntry);
modifiedBackupEntry4.setFields(tempBackupEntry);
System.out.println("done.");
MutableGPTHeader primaryHeader = gpt.getMutablePrimaryHeader();
MutableGPTHeader backupHeader = gpt.getMutableBackupHeader();
System.out.print(" - Checking if calculated entries checksums match...");
int entriesChecksum1 = gpt.calculatePrimaryEntriesChecksum();
int entriesChecksum2 = gpt.calculateBackupEntriesChecksum();
if(entriesChecksum1 != entriesChecksum2) {
System.out.println("failed! Halting program.");
System.exit(0);
}
System.out.println("yes.");
primaryHeader.setPartitionEntryArrayCRC32(entriesChecksum1);
backupHeader.setPartitionEntryArrayCRC32(entriesChecksum1);
System.out.print(" - Checking if gpt.isValid() == false as it should be...");
if(gpt.isValid()) {
System.out.println("failed! Halting program.");
System.exit(0);
}
System.out.println("yes.");
System.out.print(" - Calculating header checksums...");
primaryHeader.setCRC32Checksum(gpt.calculatePrimaryHeaderChecksum());
backupHeader.setCRC32Checksum(gpt.calculateBackupHeaderChecksum());
System.out.println("done.");
System.out.print(" - Checking if gpt.isValid() == true as it now should be...");
if(!gpt.isValid()) {
System.out.println("failed! Halting program.");
System.exit(0);
}
System.out.println("yes.");
// If we have got to this point, the table should be valid and ready to be written to disk!
System.out.println("The table is now ready to be written down to disk.");
System.out.print("Press enter to view the original table:");
stdin.readLine();
originalGpt.print(System.out, "");
System.out.print("Press enter to view the modified table:");
stdin.readLine();
gpt.print(System.out, "");
System.out.print("If you want to write this table to disk, type \"yes\" here: ");
String answer = stdin.readLine();
if(answer.equals("yes")) {
System.out.print("Getting binary data for primary and backup tables...");
byte[] newPrimaryGPT = gpt.getPrimaryTableBytes();
byte[] newBackupGPT = gpt.getBackupTableBytes();
System.out.println("done.");
// Write the MBR + the new primary GPT data to a file.
String newdataFilename1 = "gpt_mbr_tables-" + runTimeStamp + ".new";
System.out.print("Writing old MBR and new GPT primary header and table to \"" + newdataFilename1 + "\"...");
FileOutputStream newdataFile1 = new FileOutputStream(newdataFilename1);
newdataFile1.write(mbr);
newdataFile1.write(newPrimaryGPT);
newdataFile1.close();
System.out.println("done!");
// Write the new backup GPT data to a file.
String newdataFilename2 = "gpt_backup_table-" + runTimeStamp + ".new";
System.out.print("Writing new GPT backup header and table to \"" + newdataFilename2 + "\"...");
FileOutputStream newdataFile2 = new FileOutputStream(newdataFilename2);
newdataFile2.write(newBackupGPT);
newdataFile2.close();
System.out.println("done!");
// Write to disk! Dangerous stuff...
System.out.print("Writing primary table...");
llf.seek(gpt.getPrimaryTableBytesOffset());
llf.write(newPrimaryGPT);
System.out.println("done!");
System.out.print("Writing backup table...");
llf.seek(gpt.getBackupTableBytesOffset());
llf.write(newBackupGPT);
System.out.println("done!");
// Check to see if we have succeeded.
System.out.println();
System.out.println("Checking the newly written GPT...");
GUIDPartitionTable newGpt = new GUIDPartitionTable(llf, 0);
newGpt.print(System.out, "");
if(newGpt.isValid())
System.out.println("The GPT on disk is valid!");
else {
System.out.println("INVALID GPT ON DISK! FATAL ERROR!");
System.out.println("Try to restore the original GPT tables from the backups files:");
System.out.println(" " + backupFilename1);
System.out.println(" " + backupFilename2);
System.out.println("(dd in linux might do the job)");
}
}
else
System.out.println("Exiting program without modifying anything.");
}
else
System.out.println("Could not proceed! Detected an invalid GUID Partition Table on disk.");
llf.close();
}
}