int updateAttribution()

in dmg/attribution.c [209:354]


int updateAttribution(AbstractFile* abstractIn, AbstractFile* abstractOut, const char* anchor, const char* data, size_t dataLen)
{
  // In an `attributable` DMG file:
  // - read `attribution` resource
  // - update bytes in BZ_RAW block in place
  // - update <blkx> checksum: there's a UDIF checksum (34 bytes?) in
  //   each <blkx> dict, which is part of a Base64 encoded struct.  We
  //   can Base64 decode to bytes, swizzle the 4 bytes of the
  //   checksum, and then Base64 encode back to the same number of
  //   bytes.
  // - update data fork checksum (compressed)
  // - update master checksum (uncompressed)

  off_t fileLength;
  UDIFResourceFile resourceFile;

  ResourceData* curData;

  fileLength = abstractIn->getLength(abstractIn);
  abstractIn->seek(abstractIn, fileLength - sizeof(UDIFResourceFile));
  readUDIFResourceFile(abstractIn, &resourceFile);

  char* resourceXML;

  resourceXML = malloc(resourceFile.fUDIFXMLLength + 1);
  ASSERT( abstractIn->seek(abstractIn, (off_t)(resourceFile.fUDIFXMLOffset)) == 0, "fseeko" );
  ASSERT( abstractIn->read(abstractIn, resourceXML, (size_t)resourceFile.fUDIFXMLLength) == (size_t)resourceFile.fUDIFXMLLength, "fread" );
  resourceXML[resourceFile.fUDIFXMLLength] = 0;

  ResourceKey* resources;
  resources = readResources(resourceXML, resourceFile.fUDIFXMLLength, true);
  ResourceKey* resource = getResourceByKey(resources, "plst");
  unsigned char* mine = (unsigned char*)(resource->data->name);
  AttributionResource* attributionResource = (AttributionResource*)(resource->data->name);

  ASSERT(attributionResource->signature == ATTR_SIGNATURE, "bad attr signature!");
  ASSERT(attributionResource->version == 1, "only version 1 recognized");

  printf("attribution: at 0x%llx, 0x%llx bytes\n",
         attributionResource->rawPos,
         attributionResource->rawLength);

  // Step 1.  Replace bytes at anchor.
  ASSERT(abstractIn->seek(abstractIn, 0) == 0, "seek in");
  size_t inLength = abstractIn->getLength(abstractIn);
  while (1) {
    unsigned char buffer[8192];
    size_t readLength = abstractIn->read(abstractIn, buffer, 8192);
    ASSERT(readLength == abstractOut->write(abstractOut, buffer, readLength), "write copy");
    if (readLength < 8192) {
      break;
    }
  }

  ASSERT(abstractIn->seek(abstractIn, attributionResource->rawPos) == 0, "seek in");
  char* rawBuffer = malloc(attributionResource->rawLength);
  ASSERT(rawBuffer, "malloc rawBuffer");
  ASSERT(abstractIn->read(abstractIn, rawBuffer, attributionResource->rawLength) == attributionResource->rawLength, "read raw in");

  printf("Looking for anchor: '%s'\n", anchor);

  const char* rawAnchor = FindStrInBuf((const char*)rawBuffer, attributionResource->rawLength, anchor);
  ASSERT(rawAnchor, "anchor position");

  int64_t anchorOffset = rawAnchor - rawBuffer;
  printf("anchorOffset: 0x%llx\n", anchorOffset);

  ASSERT(rawAnchor + dataLen <= rawBuffer + attributionResource->rawLength, "data too long!");
  // Zero out the anchor area, in case the data is shorter than the anchor
  // Note that we're taking the strlen of `rawAnchor` and not the `anchor`
  // passed in. This ensures that the entire anchor is zero'ed, even if only
  // a prefix of it was provided.
  memset((void *)rawAnchor, 0, strlen(rawAnchor));
  // Copy the new data into the same spot the anchor was in
  memcpy((void *)rawAnchor, data, dataLen);

  // Write the new block.
  ASSERT(abstractOut->seek(abstractOut, attributionResource->rawPos) == 0, "seek out");
  ASSERT(abstractOut->write(abstractOut, rawBuffer, attributionResource->rawLength) == attributionResource->rawLength, "write data");

  // New block checksum.
  ChecksumToken newRawToken;
  memset(&newRawToken, 0, sizeof(ChecksumToken));
  CRCProxy(&newRawToken, (unsigned char*)rawBuffer, attributionResource->rawLength);

  free(rawBuffer);

  // Step 2: update "attribution" resource.
  attributionResource->rawChecksum = newRawToken.crc;

  // Step 3.  Update blkx checksum.

  // TODO: check ranges of all the partitions and runs to be sure we're identifying the correct file system.
  int partNum = -1;
  ResourceData* blkxData = NULL;
  if(partNum < 0) {
    blkxData = getResourceByKey(resources, "blkx")->data;
    while(blkxData != NULL) {
      if(strstr(blkxData->name, "Apple_HFS") != NULL) {
        break;
      }
      blkxData = blkxData->next;
    }
  } else {
    blkxData = getDataByID(getResourceByKey(resources, "blkx"), partNum);
  }

  ASSERT(blkxData, "blkxData");

  BLKXTable* blkx = (BLKXTable*)(blkxData->data);
  blkx->checksum.data[0] =
    crc32_combine(crc32_combine(attributionResource->beforeUncompressedChecksum, newRawToken.crc, attributionResource->rawLength),
                  attributionResource->afterUncompressedChecksum, attributionResource->afterUncompressedLength);

  ASSERT( abstractOut->seek(abstractOut, (off_t)(resourceFile.fUDIFXMLOffset)) == 0, "fseeko" );
  writeResources(abstractOut, resources, true);

  // Step 4.  Update koly block checksums.

  resourceFile.fUDIFDataForkChecksum.type = CHECKSUM_UDIF_CRC32;
  resourceFile.fUDIFDataForkChecksum.bitness = checksumBitness(CHECKSUM_UDIF_CRC32);
  resourceFile.fUDIFDataForkChecksum.data[0] =
    crc32_combine(crc32_combine(attributionResource->beforeCompressedChecksum, newRawToken.crc, attributionResource->rawLength),
                  attributionResource->afterCompressedChecksum, attributionResource->afterCompressedLength);

  resourceFile.fUDIFMasterChecksum.type = CHECKSUM_UDIF_CRC32;
  resourceFile.fUDIFMasterChecksum.bitness = checksumBitness(CHECKSUM_UDIF_CRC32);
  resourceFile.fUDIFMasterChecksum.data[0] = calculateMasterChecksum(resources);
  printf("Master checksum: %x\n", resourceFile.fUDIFMasterChecksum.data[0]); fflush(stdout);

  printf("Writing out UDIF resource file...\n"); fflush(stdout);

  writeUDIFResourceFile(abstractOut, &resourceFile);

  printf("Cleaning up...\n"); fflush(stdout);

  releaseResources(resources);

  abstractIn->close(abstractIn);

  printf("Done\n"); fflush(stdout);

  abstractOut->close(abstractOut);

  return TRUE;
}