Restore applypatch/updater/edify for non-A/B OTA support
Google deleted this source from bootable/recovery outright (commit c7ebad5f, "rm -rf non-AB code", 2024-04-05) — confirmed present at android-14, absent at android-16.0.0_r4. The runtime side (install.cpp's SetUpNonAbUpdateCommands) and build-side packaging (ota_from_target_files.py's GenerateNonAbOtaPackage) both survived; only the source that builds the update-binary executable itself was removed. Sourced from TeamWin's android_bootable_recovery (android-14.1), the only living copy found, with install/ZipUtil.cpp, get_args.cpp, and set_metadata.cpp intentionally left out (only wired into TeamWin's modified install.cpp, which PawletOS isn't adopting — twrpinstall carries its own independent copies). Kept as its own repo, not folded into a recovery fork, so it's buildable independent of whichever recovery UI ends up in use.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _APPLYPATCH_H
|
||||
#define _APPLYPATCH_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <openssl/sha.h>
|
||||
|
||||
// Forward declaration to avoid including "edify/expr.h" in the header.
|
||||
struct Value;
|
||||
|
||||
struct FileContents {
|
||||
uint8_t sha1[SHA_DIGEST_LENGTH];
|
||||
std::vector<unsigned char> data;
|
||||
};
|
||||
|
||||
using SinkFn = std::function<size_t(const unsigned char*, size_t)>;
|
||||
|
||||
// applypatch.cpp
|
||||
|
||||
int ShowLicenses();
|
||||
|
||||
// Parses a given string of 40 hex digits into 20-byte array 'digest'. 'str' may contain only the
|
||||
// digest or be of the form "<digest>:<anything>". Returns 0 on success, or -1 on any error.
|
||||
int ParseSha1(const std::string& str, uint8_t* digest);
|
||||
|
||||
struct Partition {
|
||||
Partition() = default;
|
||||
|
||||
Partition(const std::string& name, size_t size, const std::string& hash)
|
||||
: name(name), size(size), hash(hash) {}
|
||||
|
||||
// Parses and returns the given string into a Partition object. The input string is of the form
|
||||
// "EMMC:<device>:<size>:<hash>". Returns the parsed Partition, or an empty object on error.
|
||||
static Partition Parse(const std::string& partition, std::string* err);
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
// Returns whether the current Partition object is valid.
|
||||
explicit operator bool() const {
|
||||
return !name.empty();
|
||||
}
|
||||
|
||||
std::string name;
|
||||
size_t size;
|
||||
std::string hash;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Partition& partition);
|
||||
|
||||
// Applies the given 'patch' to the 'source' Partition, verifies then writes the patching result to
|
||||
// the 'target' Partition. While patching, it will backup the data on the source partition to
|
||||
// /cache, so that the patching could be resumed on interruption even if both of the source and
|
||||
// target partitions refer to the same device. The function is idempotent if called multiple times.
|
||||
// 'bonus' can be provided if the patch was generated with a bonus output, or nullptr.
|
||||
// 'backup_source' indicates whether the source partition should be backed up prior to the update
|
||||
// (e.g. when doing in-place update). Returns the patching result.
|
||||
bool PatchPartition(const Partition& target, const Partition& source, const Value& patch,
|
||||
const Value* bonus, bool backup_source);
|
||||
|
||||
// Returns whether the contents of the eMMC target or the cached file match the embedded hash.
|
||||
// It will look for the backup on /cache if the given partition doesn't match the checksum.
|
||||
bool PatchPartitionCheck(const Partition& target, const Partition& source);
|
||||
|
||||
// Checks whether the contents of the given partition has the desired hash. It will NOT look for
|
||||
// the backup on /cache if the given partition doesn't have the expected checksum.
|
||||
bool CheckPartition(const Partition& target);
|
||||
|
||||
// Flashes a given image in 'source_filename' to the eMMC target partition. It verifies the target
|
||||
// checksum first, and will return if target already has the desired hash. Otherwise it checks the
|
||||
// checksum of the given source image, flashes, and verifies the target partition afterwards. The
|
||||
// function is idempotent. Returns the flashing result.
|
||||
bool FlashPartition(const Partition& target, const std::string& source_filename);
|
||||
|
||||
// Reads a file into memory; stores the file contents and associated metadata in *file.
|
||||
bool LoadFileContents(const std::string& filename, FileContents* file);
|
||||
|
||||
// Saves the given FileContents object to the given filename.
|
||||
bool SaveFileContents(const std::string& filename, const FileContents* file);
|
||||
|
||||
// bspatch.cpp
|
||||
|
||||
void ShowBSDiffLicense();
|
||||
|
||||
// Applies the bsdiff-patch given in 'patch' (from offset 'patch_offset' to the end) to the source
|
||||
// data given by (old_data, old_size). Writes the patched output through the given 'sink'. Returns
|
||||
// 0 on success.
|
||||
int ApplyBSDiffPatch(const unsigned char* old_data, size_t old_size, const Value& patch,
|
||||
size_t patch_offset, SinkFn sink);
|
||||
|
||||
// imgpatch.cpp
|
||||
|
||||
// Applies the imgdiff-patch given in 'patch' to the source data given by (old_data, old_size), with
|
||||
// the optional bonus data. Writes the patched output through the given 'sink'. Returns 0 on
|
||||
// success.
|
||||
int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const Value& patch, SinkFn sink,
|
||||
const Value* bonus_data);
|
||||
|
||||
// freecache.cpp
|
||||
|
||||
// Checks whether /cache partition has at least 'bytes'-byte free space. Returns true immediately
|
||||
// if so. Otherwise, it will try to free some space by removing older logs, checks again and
|
||||
// returns the checking result.
|
||||
bool CheckAndFreeSpaceOnCache(size_t bytes);
|
||||
|
||||
// Removes the files in |dirname| until we have at least |bytes_needed| bytes of free space on the
|
||||
// partition. |space_checker| should return the size of the free space, or -1 on error.
|
||||
bool RemoveFilesInDirectory(size_t bytes_needed, const std::string& dirname,
|
||||
const std::function<int64_t(const std::string&)>& space_checker);
|
||||
#endif
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _APPLYPATCH_IMGDIFF_H
|
||||
#define _APPLYPATCH_IMGDIFF_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
// Image patch chunk types
|
||||
#define CHUNK_NORMAL 0
|
||||
#define CHUNK_GZIP 1 // version 1 only
|
||||
#define CHUNK_DEFLATE 2 // version 2 only
|
||||
#define CHUNK_RAW 3 // version 2 only
|
||||
|
||||
// The gzip header size is actually variable, but we currently don't
|
||||
// support gzipped data with any of the optional fields, so for now it
|
||||
// will always be ten bytes. See RFC 1952 for the definition of the
|
||||
// gzip format.
|
||||
static constexpr size_t GZIP_HEADER_LEN = 10;
|
||||
|
||||
// The gzip footer size really is fixed.
|
||||
static constexpr size_t GZIP_FOOTER_LEN = 8;
|
||||
|
||||
int imgdiff(int argc, const char** argv);
|
||||
|
||||
#endif // _APPLYPATCH_IMGDIFF_H
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _APPLYPATCH_IMGDIFF_IMAGE_H
|
||||
#define _APPLYPATCH_IMGDIFF_IMAGE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <bsdiff/bsdiff.h>
|
||||
#include <ziparchive/zip_archive.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#include "imgdiff.h"
|
||||
#include "otautil/rangeset.h"
|
||||
|
||||
class ImageChunk {
|
||||
public:
|
||||
static constexpr auto WINDOWBITS = -15; // 32kb window; negative to indicate a raw stream.
|
||||
static constexpr auto MEMLEVEL = 8; // the default value.
|
||||
static constexpr auto METHOD = Z_DEFLATED;
|
||||
static constexpr auto STRATEGY = Z_DEFAULT_STRATEGY;
|
||||
|
||||
ImageChunk(int type, size_t start, const std::vector<uint8_t>* file_content, size_t raw_data_len,
|
||||
std::string entry_name = {});
|
||||
|
||||
int GetType() const {
|
||||
return type_;
|
||||
}
|
||||
|
||||
const uint8_t* GetRawData() const;
|
||||
size_t GetRawDataLength() const {
|
||||
return raw_data_len_;
|
||||
}
|
||||
const std::string& GetEntryName() const {
|
||||
return entry_name_;
|
||||
}
|
||||
size_t GetStartOffset() const {
|
||||
return start_;
|
||||
}
|
||||
int GetCompressLevel() const {
|
||||
return compress_level_;
|
||||
}
|
||||
|
||||
// CHUNK_DEFLATE will return the uncompressed data for diff, while other types will simply return
|
||||
// the raw data.
|
||||
const uint8_t* DataForPatch() const;
|
||||
size_t DataLengthForPatch() const;
|
||||
|
||||
void Dump(size_t index) const;
|
||||
|
||||
void SetUncompressedData(std::vector<uint8_t> data);
|
||||
bool SetBonusData(const std::vector<uint8_t>& bonus_data);
|
||||
|
||||
bool operator==(const ImageChunk& other) const;
|
||||
bool operator!=(const ImageChunk& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
/*
|
||||
* Cause a gzip chunk to be treated as a normal chunk (ie, as a blob of uninterpreted data).
|
||||
* The resulting patch will likely be about as big as the target file, but it lets us handle
|
||||
* the case of images where some gzip chunks are reconstructible but others aren't (by treating
|
||||
* the ones that aren't as normal chunks).
|
||||
*/
|
||||
void ChangeDeflateChunkToNormal();
|
||||
|
||||
/*
|
||||
* Verify that we can reproduce exactly the same compressed data that we started with. Sets the
|
||||
* level, method, windowBits, memLevel, and strategy fields in the chunk to the encoding
|
||||
* parameters needed to produce the right output.
|
||||
*/
|
||||
bool ReconstructDeflateChunk();
|
||||
bool IsAdjacentNormal(const ImageChunk& other) const;
|
||||
void MergeAdjacentNormal(const ImageChunk& other);
|
||||
|
||||
/*
|
||||
* Compute a bsdiff patch between |src| and |tgt|; Store the result in the patch_data.
|
||||
* |bsdiff_cache| can be used to cache the suffix array if the same |src| chunk is used
|
||||
* repeatedly, pass nullptr if not needed.
|
||||
*/
|
||||
static bool MakePatch(const ImageChunk& tgt, const ImageChunk& src,
|
||||
std::vector<uint8_t>* patch_data,
|
||||
bsdiff::SuffixArrayIndexInterface** bsdiff_cache);
|
||||
|
||||
private:
|
||||
bool TryReconstruction(int level);
|
||||
|
||||
int type_; // CHUNK_NORMAL, CHUNK_DEFLATE, CHUNK_RAW
|
||||
size_t start_; // offset of chunk in the original input file
|
||||
const std::vector<uint8_t>* input_file_ptr_; // ptr to the full content of original input file
|
||||
size_t raw_data_len_;
|
||||
|
||||
// deflate encoder parameters
|
||||
int compress_level_;
|
||||
|
||||
// --- for CHUNK_DEFLATE chunks only: ---
|
||||
std::vector<uint8_t> uncompressed_data_;
|
||||
std::string entry_name_; // used for zip entries
|
||||
};
|
||||
|
||||
// PatchChunk stores the patch data between a source chunk and a target chunk. It also keeps track
|
||||
// of the metadata of src&tgt chunks (e.g. offset, raw data length, uncompressed data length).
|
||||
class PatchChunk {
|
||||
public:
|
||||
PatchChunk(const ImageChunk& tgt, const ImageChunk& src, std::vector<uint8_t> data);
|
||||
|
||||
// Construct a CHUNK_RAW patch from the target data directly.
|
||||
explicit PatchChunk(const ImageChunk& tgt);
|
||||
|
||||
// Return true if raw data size is smaller than the patch size.
|
||||
static bool RawDataIsSmaller(const ImageChunk& tgt, size_t patch_size);
|
||||
|
||||
// Update the source start with the new offset within the source range.
|
||||
void UpdateSourceOffset(const SortedRangeSet& src_range);
|
||||
|
||||
// Return the total size (header + data) of the patch.
|
||||
size_t PatchSize() const;
|
||||
|
||||
static bool WritePatchDataToFd(const std::vector<PatchChunk>& patch_chunks, int patch_fd);
|
||||
|
||||
private:
|
||||
size_t GetHeaderSize() const;
|
||||
size_t WriteHeaderToFd(int fd, size_t offset, size_t index) const;
|
||||
|
||||
// The patch chunk type is the same as the target chunk type. The only exception is we change
|
||||
// the |type_| to CHUNK_RAW if target length is smaller than the patch size.
|
||||
int type_;
|
||||
|
||||
size_t source_start_;
|
||||
size_t source_len_;
|
||||
size_t source_uncompressed_len_;
|
||||
|
||||
size_t target_start_; // offset of the target chunk within the target file
|
||||
size_t target_len_;
|
||||
size_t target_uncompressed_len_;
|
||||
size_t target_compress_level_; // the deflate compression level of the target chunk.
|
||||
|
||||
std::vector<uint8_t> data_; // storage for the patch data
|
||||
};
|
||||
|
||||
// Interface for zip_mode and image_mode images. We initialize the image from an input file and
|
||||
// split the file content into a list of image chunks.
|
||||
class Image {
|
||||
public:
|
||||
explicit Image(bool is_source) : is_source_(is_source) {}
|
||||
|
||||
virtual ~Image() {}
|
||||
|
||||
// Create a list of image chunks from input file.
|
||||
virtual bool Initialize(const std::string& filename) = 0;
|
||||
|
||||
// Look for runs of adjacent normal chunks and compress them down into a single chunk. (Such
|
||||
// runs can be produced when deflate chunks are changed to normal chunks.)
|
||||
void MergeAdjacentNormalChunks();
|
||||
|
||||
void DumpChunks() const;
|
||||
|
||||
// Non const iterators to access the stored ImageChunks.
|
||||
std::vector<ImageChunk>::iterator begin() {
|
||||
return chunks_.begin();
|
||||
}
|
||||
|
||||
std::vector<ImageChunk>::iterator end() {
|
||||
return chunks_.end();
|
||||
}
|
||||
|
||||
std::vector<ImageChunk>::const_iterator cbegin() const {
|
||||
return chunks_.cbegin();
|
||||
}
|
||||
|
||||
std::vector<ImageChunk>::const_iterator cend() const {
|
||||
return chunks_.cend();
|
||||
}
|
||||
|
||||
ImageChunk& operator[](size_t i);
|
||||
const ImageChunk& operator[](size_t i) const;
|
||||
|
||||
size_t NumOfChunks() const {
|
||||
return chunks_.size();
|
||||
}
|
||||
|
||||
protected:
|
||||
bool ReadFile(const std::string& filename, std::vector<uint8_t>* file_content);
|
||||
|
||||
bool is_source_; // True if it's for source chunks.
|
||||
std::vector<ImageChunk> chunks_; // Internal storage of ImageChunk.
|
||||
std::vector<uint8_t> file_content_; // Store the whole input file in memory.
|
||||
};
|
||||
|
||||
class ZipModeImage : public Image {
|
||||
public:
|
||||
explicit ZipModeImage(bool is_source, size_t limit = 0) : Image(is_source), limit_(limit) {}
|
||||
|
||||
bool Initialize(const std::string& filename) override;
|
||||
|
||||
// Initialize a fake ZipModeImage from an existing ImageChunk vector. For src img pieces, we
|
||||
// reconstruct a new file_content based on the source ranges; but it's not needed for the tgt img
|
||||
// pieces; because for each chunk both the data and their offset within the file are unchanged.
|
||||
void Initialize(const std::vector<ImageChunk>& chunks, const std::vector<uint8_t>& file_content) {
|
||||
chunks_ = chunks;
|
||||
file_content_ = file_content;
|
||||
}
|
||||
|
||||
// The pesudo source chunk for bsdiff if there's no match for the given target chunk. It's in
|
||||
// fact the whole source file.
|
||||
ImageChunk PseudoSource() const;
|
||||
|
||||
// Find the matching deflate source chunk by entry name. Search for normal chunks also if
|
||||
// |find_normal| is true.
|
||||
ImageChunk* FindChunkByName(const std::string& name, bool find_normal = false);
|
||||
|
||||
const ImageChunk* FindChunkByName(const std::string& name, bool find_normal = false) const;
|
||||
|
||||
// Verify that we can reconstruct the deflate chunks; also change the type to CHUNK_NORMAL if
|
||||
// src and tgt are identical.
|
||||
static bool CheckAndProcessChunks(ZipModeImage* tgt_image, ZipModeImage* src_image);
|
||||
|
||||
// Compute the patch between tgt & src images, and write the data into |patch_name|.
|
||||
static bool GeneratePatches(const ZipModeImage& tgt_image, const ZipModeImage& src_image,
|
||||
const std::string& patch_name);
|
||||
|
||||
// Compute the patch based on the lists of split src and tgt images. Generate patches for each
|
||||
// pair of split pieces and write the data to |patch_name|. If |debug_dir| is specified, write
|
||||
// each split src data and patch data into that directory.
|
||||
static bool GeneratePatches(const std::vector<ZipModeImage>& split_tgt_images,
|
||||
const std::vector<ZipModeImage>& split_src_images,
|
||||
const std::vector<SortedRangeSet>& split_src_ranges,
|
||||
const std::string& patch_name, const std::string& split_info_file,
|
||||
const std::string& debug_dir);
|
||||
|
||||
// Split the tgt chunks and src chunks based on the size limit.
|
||||
static bool SplitZipModeImageWithLimit(const ZipModeImage& tgt_image,
|
||||
const ZipModeImage& src_image,
|
||||
std::vector<ZipModeImage>* split_tgt_images,
|
||||
std::vector<ZipModeImage>* split_src_images,
|
||||
std::vector<SortedRangeSet>* split_src_ranges);
|
||||
|
||||
private:
|
||||
// Initialize image chunks based on the zip entries.
|
||||
bool InitializeChunks(const std::string& filename, ZipArchiveHandle handle);
|
||||
// Add the a zip entry to the list.
|
||||
bool AddZipEntryToChunks(ZipArchiveHandle handle, const std::string& entry_name,
|
||||
ZipEntry64* entry);
|
||||
// Return the real size of the zip file. (omit the trailing zeros that used for alignment)
|
||||
bool GetZipFileSize(size_t* input_file_size);
|
||||
|
||||
static void ValidateSplitImages(const std::vector<ZipModeImage>& split_tgt_images,
|
||||
const std::vector<ZipModeImage>& split_src_images,
|
||||
std::vector<SortedRangeSet>& split_src_ranges,
|
||||
size_t total_tgt_size);
|
||||
// Construct the fake split images based on the chunks info and source ranges; and move them into
|
||||
// the given vectors. Return true if we add a new split image into |split_tgt_images|, and
|
||||
// false otherwise.
|
||||
static bool AddSplitImageFromChunkList(const ZipModeImage& tgt_image,
|
||||
const ZipModeImage& src_image,
|
||||
const SortedRangeSet& split_src_ranges,
|
||||
const std::vector<ImageChunk>& split_tgt_chunks,
|
||||
const std::vector<ImageChunk>& split_src_chunks,
|
||||
std::vector<ZipModeImage>* split_tgt_images,
|
||||
std::vector<ZipModeImage>* split_src_images);
|
||||
|
||||
// Function that actually iterates the tgt_chunks and makes patches.
|
||||
static bool GeneratePatchesInternal(const ZipModeImage& tgt_image, const ZipModeImage& src_image,
|
||||
std::vector<PatchChunk>* patch_chunks);
|
||||
|
||||
// size limit in bytes of each chunk. Also, if the length of one zip_entry exceeds the limit,
|
||||
// we'll split that entry into several smaller chunks in advance.
|
||||
size_t limit_;
|
||||
};
|
||||
|
||||
class ImageModeImage : public Image {
|
||||
public:
|
||||
explicit ImageModeImage(bool is_source) : Image(is_source) {}
|
||||
|
||||
// Initialize the image chunks list by searching the magic numbers in an image file.
|
||||
bool Initialize(const std::string& filename) override;
|
||||
|
||||
bool SetBonusData(const std::vector<uint8_t>& bonus_data);
|
||||
|
||||
// In Image Mode, verify that the source and target images have the same chunk structure (ie, the
|
||||
// same sequence of deflate and normal chunks).
|
||||
static bool CheckAndProcessChunks(ImageModeImage* tgt_image, ImageModeImage* src_image);
|
||||
|
||||
// In image mode, generate patches against the given source chunks and bonus_data; write the
|
||||
// result to |patch_name|.
|
||||
static bool GeneratePatches(const ImageModeImage& tgt_image, const ImageModeImage& src_image,
|
||||
const std::string& patch_name);
|
||||
};
|
||||
|
||||
#endif // _APPLYPATCH_IMGDIFF_IMAGE_H
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef _APPLYPATCH_IMGPATCH_H
|
||||
#define _APPLYPATCH_IMGPATCH_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
using SinkFn = std::function<size_t(const unsigned char*, size_t)>;
|
||||
|
||||
int ApplyImagePatch(const unsigned char* old_data, size_t old_size, const unsigned char* patch_data,
|
||||
size_t patch_size, SinkFn sink);
|
||||
|
||||
#endif // _APPLYPATCH_IMGPATCH_H
|
||||
Reference in New Issue
Block a user