From 6cd6b35c737e0e4042a8fd79af1decc9f10ed84b Mon Sep 17 00:00:00 2001 From: Benjamin Dobell Date: Mon, 1 Oct 2012 12:43:05 +1000 Subject: Heimdall 1.4 RC1: - Massive refactoring. - Support for Qualcomm based devices. - Print PIT from file. - Use partition names as arguments e.g. --HIDDEN, --KERNEL, --MOVINAND etc. - Heimdall Frontend UI improvements. - And much more... --- heimdall/source/FlashAction.cpp | 620 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 620 insertions(+) create mode 100644 heimdall/source/FlashAction.cpp (limited to 'heimdall/source/FlashAction.cpp') diff --git a/heimdall/source/FlashAction.cpp b/heimdall/source/FlashAction.cpp new file mode 100644 index 0000000..d79235e --- /dev/null +++ b/heimdall/source/FlashAction.cpp @@ -0,0 +1,620 @@ +/* Copyright (c) 2010-2012 Benjamin Dobell, Glass Echidna + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE.*/ + +// C Standard Library +#include + +// Heimdall +#include "Arguments.h" +#include "BridgeManager.h" +#include "EndModemFileTransferPacket.h" +#include "EndPhoneFileTransferPacket.h" +#include "FlashAction.h" +#include "Heimdall.h" +#include "Interface.h" +#include "SessionSetupResponse.h" +#include "TotalBytesPacket.h" +#include "Utility.h" + +using namespace Heimdall; + +const char *FlashAction::usage = "Action: flash\n\ +Arguments:\n\ + --repartition --pit [--factoryfs ]\n\ + [--cache ] [--dbdata ] [--primary-boot ]\n\ + [--secondary-boot ] [--param ] [--kernel ]\n\ + [--modem ] [--radio ] [--normal-boot ]\n\ + [--system ] [--user-data ] [--fota ]\n\ + [--hidden ] [--movinand ] [--data ]\n\ + [--ums ] [--emmc ]\n\ + [-- ]\n\ + [-- ]\n\ + [--verbose] [--no-reboot] [--stdout-errors] [--delay ]\n\ + or:\n\ + [--factoryfs ] [--cache ] [--dbdata ]\n\ + [--primary-boot ] [--secondary-boot ]\n\ + [--secondary-boot-backup ] [--param ]\n\ + [--kernel ] [--recovery ] [--efs ]\n\ + [--modem ] [--radio ] [--normal-boot ]\n\ + [--system ] [--user-data ] [--fota ]\n\ + [--hidden ] [--movinand ] [--data ]\n\ + [--ums ] [--emmc ] [--pit ]\n\ + [-- ]\n\ + [-- ]\n\ + [--verbose] [--no-reboot] [--stdout-errors] [--delay ]\n\ +Description: Flashes firmware files to your phone. Partition identifiers are\n\ + integer values, they can be obtained by executing the print-pit action.\n\ +WARNING: If you're repartitioning it's strongly recommended you specify\n\ + all files at your disposal, including bootloaders.\n"; + +struct PartitionFlashInfo +{ + const PitEntry *pitEntry; + FILE *file; + + PartitionFlashInfo(const PitEntry *pitEntry, FILE *file) + { + this->pitEntry = pitEntry; + this->file = file; + } +}; + +static void buildArgumentPartitionNamesMap(map< string, vector >& argumentPartitionNamesMap, map& shortArgumentAliases) +{ + argumentPartitionNamesMap["pit"].push_back("PIT"); + argumentPartitionNamesMap["factoryfs"].push_back("FACTORYFS"); + argumentPartitionNamesMap["cache"].push_back("CACHE"); + argumentPartitionNamesMap["dbdata"].push_back("DBDATAFS"); + + argumentPartitionNamesMap["primary-boot"].push_back("IBL+PBL"); + argumentPartitionNamesMap["primary-boot"].push_back("BOOT"); + + argumentPartitionNamesMap["secondary-boot"].push_back("SBL"); + argumentPartitionNamesMap["secondary-boot"].push_back("SBL1"); + + argumentPartitionNamesMap["secondary-boot-backup"].push_back("SBL2"); + argumentPartitionNamesMap["param"].push_back("PARAM"); + argumentPartitionNamesMap["kernel"].push_back("KERNEL"); + argumentPartitionNamesMap["recovery"].push_back("RECOVERY"); + argumentPartitionNamesMap["efs"].push_back("EFS"); + argumentPartitionNamesMap["modem"].push_back("MODEM"); + argumentPartitionNamesMap["radio"].push_back("RADIO"); + argumentPartitionNamesMap["normal-boot"].push_back("NORMALBOOT"); + argumentPartitionNamesMap["system"].push_back("SYSTEM"); + argumentPartitionNamesMap["user-data"].push_back("USERDATA"); + argumentPartitionNamesMap["fota"].push_back("FOTA"); + argumentPartitionNamesMap["hidden"].push_back("HIDDEN"); + argumentPartitionNamesMap["movinand"].push_back("MOVINAND"); + argumentPartitionNamesMap["data"].push_back("DATAFS"); + argumentPartitionNamesMap["ums"].push_back("UMS.EN"); + argumentPartitionNamesMap["emmc"].push_back("GANG"); + + shortArgumentAliases["pit"] = "pit"; + shortArgumentAliases["fs"] = "factoryfs"; + shortArgumentAliases["cache"] = "cache"; + shortArgumentAliases["db"] = "dbdata"; + shortArgumentAliases["boot"] = "primary-boot"; + shortArgumentAliases["sbl"] = "secondary-boot"; + shortArgumentAliases["sbl2"] = "secondary-boot-backup"; + shortArgumentAliases["param"] = "param"; + shortArgumentAliases["z"] = "kernel"; + shortArgumentAliases["rec"] = "recovery"; + shortArgumentAliases["efs"] = "efs"; + shortArgumentAliases["m"] = "modem"; + shortArgumentAliases["rdio"] = "radio"; + shortArgumentAliases["norm"] = "normal-boot"; + shortArgumentAliases["sys"] = "system"; + shortArgumentAliases["udata"] = "user-data"; + shortArgumentAliases["fota"] = "fota"; + shortArgumentAliases["hide"] = "hidden"; + shortArgumentAliases["nand"] = "movinand"; + shortArgumentAliases["data"] = "data"; + shortArgumentAliases["ums"] = "ums"; + shortArgumentAliases["emmc"] = "emmc"; +} + +static bool openFiles(Arguments& arguments, const map< string, vector >& argumentPartitionNamesMap, + map& argumentFileMap) +{ + for (map::const_iterator it = arguments.GetArguments().begin(); it != arguments.GetArguments().end(); it++) + { + bool isPartitionArgument = false; + const string& argumentName = it->first; + + if (arguments.GetArgumentTypes().find(argumentName) == arguments.GetArgumentTypes().end()) + { + // The only way an argument could exist without being in the argument types map is if it's a wild-card. + // The "%d" wild-card refers to a partition by identifier, where as the "%s" wild-card refers to a + // partition by name. + isPartitionArgument = true; + } + else + { + // The argument wasn't a wild-card, check if it's a known partition name. + if (argumentPartitionNamesMap.find(argumentName) != argumentPartitionNamesMap.end()) + isPartitionArgument = true; + } + + if (isPartitionArgument) + { + const StringArgument *stringArgument = static_cast(it->second); + FILE *file = fopen(stringArgument->GetValue().c_str(), "rb"); + + if (!file) + { + Interface::PrintError("Failed to open file \"%s\"\n", stringArgument->GetValue().c_str()); + return (false); + } + + argumentFileMap[it->first] = file; + } + } + + return (true); +} + +static void closeFiles(map argumentfileMap) +{ + for (map::iterator it = argumentfileMap.begin(); it != argumentfileMap.end(); it++) + fclose(it->second); + + argumentfileMap.clear(); +} + +static bool sendTotalTransferSize(BridgeManager *bridgeManager, const map& argumentFileMap, bool repartition) +{ + int totalBytes = 0; + for (map::const_iterator it = argumentFileMap.begin(); it != argumentFileMap.end(); it++) + { + if (repartition || it->first != "pit") + { + fseek(it->second, 0, SEEK_END); + totalBytes += ftell(it->second); + rewind(it->second); + } + } + + bool success; + + TotalBytesPacket *totalBytesPacket = new TotalBytesPacket(totalBytes); + success = bridgeManager->SendPacket(totalBytesPacket); + delete totalBytesPacket; + + if (!success) + { + Interface::PrintError("Failed to send total bytes device info packet!\n"); + return (false); + } + + SessionSetupResponse *totalBytesResponse = new SessionSetupResponse(); + success = bridgeManager->ReceivePacket(totalBytesResponse); + int totalBytesResult = totalBytesResponse->GetResult(); + delete totalBytesResponse; + + if (!success) + { + Interface::PrintError("Failed to receive device info response!\n"); + return (false); + } + + if (totalBytesResult != 0) + { + Interface::PrintError("Unexpected device info response!\nExpected: 0\nReceived:%d\n", totalBytesResponse); + return (false); + } + + return (true); +} + +static bool setupPartitionFlashInfo(const map& argumentFileMap, const map< string, vector >& argumentPartitionNamesMap, + const PitData *pitData, vector& partitionFlashInfos) +{ + for (map::const_iterator it = argumentFileMap.begin(); it != argumentFileMap.end(); it++) + { + const string& argumentName = it->first; + FILE *partitionFile = it->second; + + const PitEntry *pitEntry = nullptr; + + // Was the argument a partition identifier? + unsigned int partitionIdentifier; + + if (Utility::ParseUnsignedInt(partitionIdentifier, argumentName.c_str()) == kNumberParsingStatusSuccess) + { + pitEntry = pitData->FindEntry(partitionIdentifier); + + if (!pitEntry) + { + Interface::PrintError("No partition with identifier \"%s\" exists in the specified PIT.\n", argumentName.c_str()); + return (false); + } + } + else + { + // The argument wasn't a partition identifier. Was it a known human-readable partition name? + map< string, vector >::const_iterator argumentPartitionNamesIt = argumentPartitionNamesMap.find(argumentName); + + if (argumentPartitionNamesIt != argumentPartitionNamesMap.end()) + { + const vector& partitionNames = argumentPartitionNamesIt->second; + + // Check for the partition in the PIT file using all known names. + for (vector::const_iterator nameIt = partitionNames.begin(); nameIt != partitionNames.end(); nameIt++) + { + pitEntry = pitData->FindEntry(nameIt->c_str()); + + if (pitEntry) + break; + } + + if (!pitEntry) + { + Interface::PrintError("Partition name for \"%s\" could not be located\n", argumentName.c_str()); + return (false); + } + } + else + { + // The argument must be an actual partition name. e.g. "ZIMAGE", instead of human-readable "kernel". + pitEntry = pitData->FindEntry(argumentName.c_str()); + + if (!pitEntry) + { + Interface::PrintError("Partition \"%s\" does not exist in the specified PIT.\n", argumentName.c_str()); + return (false); + } + } + } + + partitionFlashInfos.push_back(PartitionFlashInfo(pitEntry, partitionFile)); + } + + return (true); +} + +static bool isKnownPartition(const map >& argumentPartitionNamesMap, const string& argumentName, const string& partitionName) +{ + const vector& partitionNames = argumentPartitionNamesMap.find(argumentName)->second; + + for (vector::const_iterator it = partitionNames.begin(); it != partitionNames.end(); it++) + { + if (partitionName == *it) + return (true); + } + + return (false); +} + +static bool isKnownBootPartition(const map >& argumentPartitionNamesMap, const char *partitionName) +{ + return (isKnownPartition(argumentPartitionNamesMap, "primary-boot", partitionName) + || isKnownPartition(argumentPartitionNamesMap, "secondary-boot", partitionName) + || isKnownPartition(argumentPartitionNamesMap, "secondary-boot-backup", partitionName) + || isKnownPartition(argumentPartitionNamesMap, "param", partitionName) + || isKnownPartition(argumentPartitionNamesMap, "normal-boot", partitionName) + || strcmp(partitionName, "SBL3") == 0 + || strcmp(partitionName, "ABOOT") == 0 + || strcmp(partitionName, "RPM") == 0 + || strcmp(partitionName, "TZ") == 0); +} + +static bool flashFile(BridgeManager *bridgeManager, const map< string, vector >& argumentPartitionNamesMap, + const PartitionFlashInfo& partitionFlashInfo) +{ + // PIT files need to be handled differently, try determine if the partition we're flashing to is a PIT partition. + + if (isKnownPartition(argumentPartitionNamesMap, "pit", partitionFlashInfo.pitEntry->GetPartitionName())) + { + Interface::Print("Uploading %s\n", partitionFlashInfo.pitEntry->GetPartitionName()); + + if (bridgeManager->SendPitFile(partitionFlashInfo.file)) + { + Interface::Print("%s upload successful\n\n", partitionFlashInfo.pitEntry->GetPartitionName()); + return (true); + } + else + { + Interface::PrintError("%s upload failed!\n\n", partitionFlashInfo.pitEntry->GetPartitionName()); + return (false); + } + } + else + { + if (partitionFlashInfo.pitEntry->GetBinaryType() == PitEntry::kBinaryTypeCommunicationProcessor) // Modem + { + Interface::Print("Uploading %s\n", partitionFlashInfo.pitEntry->GetPartitionName()); + + if (bridgeManager->SendFile(partitionFlashInfo.file, EndModemFileTransferPacket::kDestinationModem, + partitionFlashInfo.pitEntry->GetDeviceType())) // <-- Odin method + { + Interface::Print("%s upload successful\n\n", partitionFlashInfo.pitEntry->GetPartitionName()); + return (true); + } + else + { + Interface::PrintError("%s upload failed!\n\n", partitionFlashInfo.pitEntry->GetPartitionName()); + return (false); + } + } + else // partitionFlashInfo.pitEntry->GetBinaryType() == PitEntry::kBinaryTypeApplicationProcessor + { + Interface::Print("Uploading %s\n", partitionFlashInfo.pitEntry->GetPartitionName()); + + if (bridgeManager->SendFile(partitionFlashInfo.file, EndPhoneFileTransferPacket::kDestinationPhone, + partitionFlashInfo.pitEntry->GetDeviceType(), partitionFlashInfo.pitEntry->GetIdentifier())) + { + Interface::Print("%s upload successful\n\n", partitionFlashInfo.pitEntry->GetPartitionName()); + return (true); + } + else + { + Interface::PrintError("%s upload failed!\n\n", partitionFlashInfo.pitEntry->GetPartitionName()); + return (false); + } + } + } + + return (true); +} + +static bool flashPartitions(const map& argumentFileMap, const map< string, vector >& argumentPartitionNamesMap, + const PitData *pitData, BridgeManager *bridgeManager, bool repartition) +{ + vector partitionFlashInfos; + + // Map the files being flashed to partitions stored in the PIT file. + if (!setupPartitionFlashInfo(argumentFileMap, argumentPartitionNamesMap, pitData, partitionFlashInfos)) + return (false); + + // If we're repartitioning then we need to flash the PIT file first. + if (repartition) + { + vector::const_iterator it; + + for (it = partitionFlashInfos.begin(); it != partitionFlashInfos.end(); it++) + { + if (isKnownPartition(argumentPartitionNamesMap, "pit", it->pitEntry->GetPartitionName())) + { + if (!flashFile(bridgeManager, argumentPartitionNamesMap, *it)) + return (false); + + break; + } + } + + if (it == partitionFlashInfos.end()) + { + Interface::PrintError("Could not identify the PIT partition within the specified PIT file.\n\n"); + return (false); + } + } + + // Flash partitions not involved in the boot process second. + for (vector::const_iterator it = partitionFlashInfos.begin(); it != partitionFlashInfos.end(); it++) + { + if (!isKnownPartition(argumentPartitionNamesMap, "pit", it->pitEntry->GetPartitionName()) + && !isKnownBootPartition(argumentPartitionNamesMap, it->pitEntry->GetPartitionName())) + { + if (!flashFile(bridgeManager, argumentPartitionNamesMap, *it)) + return (false); + } + } + + // Flash boot partitions last. + for (vector::const_iterator it = partitionFlashInfos.begin(); it != partitionFlashInfos.end(); it++) + { + if (isKnownBootPartition(argumentPartitionNamesMap, it->pitEntry->GetPartitionName())) + { + if (!flashFile(bridgeManager, argumentPartitionNamesMap, *it)) + return (false); + } + } + + return (true); +} + +static PitData *getPitData(const map& argumentFileMap, BridgeManager *bridgeManager, bool repartition) +{ + PitData *pitData; + PitData *localPitData = nullptr; + + // If a PIT file was passed as an argument then we must unpack it. + + map::const_iterator localPitFileIt = argumentFileMap.find("pit"); + + if (localPitFileIt != argumentFileMap.end()) + { + FILE *localPitFile = localPitFileIt->second; + + // Load the local pit file into memory. + unsigned char *pitFileBuffer = new unsigned char[4096]; + memset(pitFileBuffer, 0, 4096); + + fseek(localPitFile, 0, SEEK_END); + long localPitFileSize = ftell(localPitFile); + rewind(localPitFile); + + // dataRead is discarded, it's here to remove warnings. + int dataRead = fread(pitFileBuffer, 1, localPitFileSize, localPitFile); + rewind(localPitFile); + + localPitData = new PitData(); + localPitData->Unpack(pitFileBuffer); + + delete [] pitFileBuffer; + } + + if (repartition) + { + // Use the local PIT file data. + pitData = localPitData; + } + else + { + // If we're not repartitioning then we need to retrieve the device's PIT file and unpack it. + unsigned char *pitFileBuffer; + + if (bridgeManager->DownloadPitFile(&pitFileBuffer) == 0) + return (nullptr); + + pitData = new PitData(); + pitData->Unpack(pitFileBuffer); + + delete [] pitFileBuffer; + + if (localPitData != nullptr) + { + // The user has specified a PIT without repartitioning, we should verify the local and device PIT data match! + bool pitsMatch = pitData->Matches(localPitData); + delete localPitData; + + if (!pitsMatch) + { + Interface::Print("Local and device PIT files don't match and repartition wasn't specified!\n"); + Interface::PrintError("Flash aborted!\n"); + return (nullptr); + } + } + } + + return (pitData); +} + +int FlashAction::Execute(int argc, char **argv) +{ + // Setup argument types + + map argumentTypes; + + argumentTypes["repartition"] = kArgumentTypeString; + + argumentTypes["no-reboot"] = kArgumentTypeFlag; + argumentTypes["delay"] = kArgumentTypeUnsignedInteger; + argumentTypes["verbose"] = kArgumentTypeFlag; + argumentTypes["stdout-errors"] = kArgumentTypeFlag; + + map< string, vector > argumentPartitionNamesMap; + map shortArgumentAliases; + + buildArgumentPartitionNamesMap(argumentPartitionNamesMap, shortArgumentAliases); + + for (map< string, vector >::const_iterator it = argumentPartitionNamesMap.begin(); it != argumentPartitionNamesMap.end(); it++) + argumentTypes[it->first] = kArgumentTypeString; + + // Add wild-cards "%d" and "%s", for partition identifiers and partition names respectively. + argumentTypes["%d"] = kArgumentTypeString; + shortArgumentAliases["%d"] = "%d"; + + argumentTypes["%s"] = kArgumentTypeString; + shortArgumentAliases["%s"] = "%s"; + + map argumentAliases; + argumentAliases["PIT"] = "pit"; // Map upper-case PIT argument (i.e. partition name) to known lower-case pit argument. + + // Handle arguments + + Arguments arguments(argumentTypes, shortArgumentAliases, argumentAliases); + + if (!arguments.ParseArguments(argc, argv, 2)) + { + Interface::Print(FlashAction::usage); + return (0); + } + + const UnsignedIntegerArgument *communicationDelayArgument = static_cast(arguments.GetArgument("delay")); + + bool reboot = arguments.GetArgument("no-reboot") == nullptr; + bool verbose = arguments.GetArgument("verbose") != nullptr; + + if (arguments.GetArgument("stdout-errors") != nullptr) + Interface::SetStdoutErrors(true); + + const StringArgument *pitArgument = static_cast(arguments.GetArgument("pit")); + + bool repartition = arguments.GetArgument("repartition") != nullptr; + + if (repartition && !pitArgument) + { + Interface::Print("If you wish to repartition then a PIT file must be specified.\n\n"); + Interface::Print(FlashAction::usage); + return (0); + } + + // Open files + + map argumentFileMap; + + if (!openFiles(arguments, argumentPartitionNamesMap, argumentFileMap)) + { + closeFiles(argumentFileMap); + return (1); + } + + if (argumentFileMap.size() == 0) + { + Interface::Print(FlashAction::usage); + return (0); + } + + // Info + + Interface::PrintReleaseInfo(); + Sleep(1000); + + // Perform flash + + int communicationDelay = BridgeManager::kCommunicationDelayDefault; + + if (communicationDelayArgument) + communicationDelay = communicationDelayArgument->GetValue(); + + BridgeManager *bridgeManager = new BridgeManager(verbose, communicationDelay); + + if (bridgeManager->Initialise() != BridgeManager::kInitialiseSucceeded || !bridgeManager->BeginSession()) + { + closeFiles(argumentFileMap); + delete bridgeManager; + + return (1); + } + + bool success = sendTotalTransferSize(bridgeManager, argumentFileMap, repartition); + + if (success) + { + PitData *pitData = getPitData(argumentFileMap, bridgeManager, repartition); + + if (pitData) + success = flashPartitions(argumentFileMap, argumentPartitionNamesMap, pitData, bridgeManager, repartition); + else + success = false; + + delete pitData; + } + + closeFiles(argumentFileMap); + + if (!bridgeManager->EndSession(reboot)) + success = false; + + delete bridgeManager; + + return (success ? 0 : 1); +} -- cgit v1.1