Add a small stand-alone utility for managing binary patches.
							parent
							
								
									ab90e3eefe
								
							
						
					
					
						commit
						408f0cb06e
					
				
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								| @ -0,0 +1,308 @@ | |||||||
|  | /*
 | ||||||
|  | https://github.com/peterix/dfhack
 | ||||||
|  | Copyright (c) 2011 Petr Mrázek <peterix@gmail.com> | ||||||
|  | 
 | ||||||
|  | A thread-safe logging console with a line editor for windows. | ||||||
|  | 
 | ||||||
|  | Based on linenoise win32 port, | ||||||
|  | copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>. | ||||||
|  | All rights reserved. | ||||||
|  | Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>. | ||||||
|  | The original linenoise can be found at: http://github.com/antirez/linenoise
 | ||||||
|  | 
 | ||||||
|  | Redistribution and use in source and binary forms, with or without | ||||||
|  | modification, are permitted provided that the following conditions are met: | ||||||
|  | 
 | ||||||
|  |   * Redistributions of source code must retain the above copyright notice, | ||||||
|  |     this list of conditions and the following disclaimer. | ||||||
|  |   * Redistributions in binary form must reproduce the above copyright | ||||||
|  |     notice, this list of conditions and the following disclaimer in the | ||||||
|  |     documentation and/or other materials provided with the distribution. | ||||||
|  |   * Neither the name of Redis nor the names of its contributors may be used | ||||||
|  |     to endorse or promote products derived from this software without | ||||||
|  |     specific prior written permission. | ||||||
|  | 
 | ||||||
|  | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||||
|  | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||||
|  | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||||||
|  | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | ||||||
|  | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | ||||||
|  | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | ||||||
|  | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | ||||||
|  | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | ||||||
|  | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||||||
|  | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | ||||||
|  | POSSIBILITY OF SUCH DAMAGE. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #include <stdarg.h> | ||||||
|  | #include <errno.h> | ||||||
|  | #include <stdio.h> | ||||||
|  | #include <assert.h> | ||||||
|  | #include <iostream> | ||||||
|  | #include <fstream> | ||||||
|  | #include <istream> | ||||||
|  | #include <string> | ||||||
|  | #include <stdint.h> | ||||||
|  | 
 | ||||||
|  | #include <cstdio> | ||||||
|  | #include <cstdlib> | ||||||
|  | #include <sstream> | ||||||
|  | #include <vector> | ||||||
|  | 
 | ||||||
|  | #include <memory> | ||||||
|  | 
 | ||||||
|  | using std::cout; | ||||||
|  | using std::cerr; | ||||||
|  | using std::endl; | ||||||
|  | 
 | ||||||
|  | typedef unsigned char patch_byte; | ||||||
|  | 
 | ||||||
|  | struct BinaryPatch { | ||||||
|  |     struct Byte { | ||||||
|  |         unsigned offset; | ||||||
|  |         patch_byte old_val, new_val; | ||||||
|  |     }; | ||||||
|  |     enum State { | ||||||
|  |         Conflict = 0, | ||||||
|  |         Unapplied = 1, | ||||||
|  |         Applied = 2, | ||||||
|  |         Partial = 3 | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     std::vector<Byte> entries; | ||||||
|  | 
 | ||||||
|  |     bool loadDIF(std::string name); | ||||||
|  |     State checkState(const patch_byte *ptr, size_t len); | ||||||
|  | 
 | ||||||
|  |     void apply(patch_byte *ptr, size_t len, bool newv); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | inline bool is_hex(char c) | ||||||
|  | { | ||||||
|  |     return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool BinaryPatch::loadDIF(std::string name) | ||||||
|  | { | ||||||
|  |     entries.clear(); | ||||||
|  | 
 | ||||||
|  |     std::ifstream infile(name); | ||||||
|  |     if(infile.bad()) | ||||||
|  |     { | ||||||
|  |         cerr << "Cannot open file: " << name << endl; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::string s; | ||||||
|  |     while(std::getline(infile, s)) | ||||||
|  |     { | ||||||
|  |         // Parse lines that begin with "[0-9a-f]+:"
 | ||||||
|  |         size_t idx = s.find(':'); | ||||||
|  |         if (idx == std::string::npos || idx == 0 || idx > 8) | ||||||
|  |             continue; | ||||||
|  | 
 | ||||||
|  |         bool ok = true; | ||||||
|  |         for (size_t i = 0; i < idx; i++) | ||||||
|  |             if (!is_hex(s[i])) | ||||||
|  |                 ok = false; | ||||||
|  |         if (!ok) | ||||||
|  |             continue; | ||||||
|  | 
 | ||||||
|  |         unsigned off, oval, nval; | ||||||
|  |         int nchar = 0; | ||||||
|  |         int cnt = sscanf(s.c_str(), "%x: %x %x%n", &off, &oval, &nval, &nchar); | ||||||
|  | 
 | ||||||
|  |         if (cnt < 3) | ||||||
|  |         { | ||||||
|  |             cerr << "Could not parse: " << s << endl; | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         for (size_t i = nchar; i < s.size(); i++) | ||||||
|  |         { | ||||||
|  |             if (!isspace(s[i])) | ||||||
|  |             { | ||||||
|  |                 cerr << "Garbage at end of line: " << s << endl; | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (oval >= 256 || nval >= 256) | ||||||
|  |         { | ||||||
|  |             cerr << "Invalid byte values: " << s << endl; | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Byte bv = { off, patch_byte(oval), patch_byte(nval) }; | ||||||
|  |         entries.push_back(bv); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (entries.empty()) | ||||||
|  |     { | ||||||
|  |         cerr << "No lines recognized." << endl; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | BinaryPatch::State BinaryPatch::checkState(const patch_byte *ptr, size_t len) | ||||||
|  | { | ||||||
|  |     int state = 0; | ||||||
|  | 
 | ||||||
|  |     for (size_t i = 0; i < entries.size(); i++) | ||||||
|  |     { | ||||||
|  |         Byte &bv = entries[i]; | ||||||
|  | 
 | ||||||
|  |         if (bv.offset >= len) | ||||||
|  |         { | ||||||
|  |             cerr << "Offset out of range: 0x" << std::hex << bv.offset << std::dec << endl; | ||||||
|  |             return Conflict; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         patch_byte cv = ptr[bv.offset]; | ||||||
|  |         if (bv.old_val == cv) | ||||||
|  |             state |= Unapplied; | ||||||
|  |         else if (bv.new_val == cv) | ||||||
|  |             state |= Applied; | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             cerr << std::hex << bv.offset << ": " << bv.old_val << " " << bv.new_val | ||||||
|  |                  << ", but currently " << cv << std::dec << endl; | ||||||
|  |             return Conflict; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return State(state); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void BinaryPatch::apply(patch_byte *ptr, size_t len, bool newv) | ||||||
|  | { | ||||||
|  |     for (size_t i = 0; i < entries.size(); i++) | ||||||
|  |     { | ||||||
|  |         Byte &bv = entries[i]; | ||||||
|  |         assert (bv.offset < len); | ||||||
|  | 
 | ||||||
|  |         ptr[bv.offset] = (newv ? bv.new_val : bv.old_val); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool load_file(std::vector<patch_byte> *pvec, std::string fname) | ||||||
|  | { | ||||||
|  |     FILE *f = fopen(fname.c_str(), "rb"); | ||||||
|  |     if (!f) | ||||||
|  |     { | ||||||
|  |         cerr << "Cannot open file: " << fname << endl; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fseek(f, 0, SEEK_END); | ||||||
|  |     pvec->resize(ftell(f)); | ||||||
|  |     fseek(f, 0, SEEK_SET); | ||||||
|  |     size_t cnt = fread(pvec->data(), 1, pvec->size(), f); | ||||||
|  |     fclose(f); | ||||||
|  | 
 | ||||||
|  |     return cnt == pvec->size(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool save_file(const std::vector<patch_byte> &pvec, std::string fname) | ||||||
|  | { | ||||||
|  |     FILE *f = fopen(fname.c_str(), "wb"); | ||||||
|  |     if (!f) | ||||||
|  |     { | ||||||
|  |         cerr << "Cannot open file: " << fname << endl; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     size_t cnt = fwrite(pvec.data(), 1, pvec.size(), f); | ||||||
|  |     fclose(f); | ||||||
|  | 
 | ||||||
|  |     return cnt == pvec.size(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int main (int argc, char *argv[]) | ||||||
|  | { | ||||||
|  |     if (argc <= 3) | ||||||
|  |     { | ||||||
|  |         cerr << "Usage: binpatch check|apply|remove <exe> <patch>" << endl; | ||||||
|  |         return 2; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::string cmd = argv[1]; | ||||||
|  | 
 | ||||||
|  |     if (cmd != "check" && cmd != "apply" && cmd != "remove") | ||||||
|  |     { | ||||||
|  |         cerr << "Invalid command: " << cmd << endl; | ||||||
|  |         return 2; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::string exe_file = argv[2]; | ||||||
|  |     std::vector<patch_byte> bindata; | ||||||
|  |     if (!load_file(&bindata, exe_file)) | ||||||
|  |         return 2; | ||||||
|  | 
 | ||||||
|  |     BinaryPatch patch; | ||||||
|  |     if (!patch.loadDIF(argv[3])) | ||||||
|  |         return 2; | ||||||
|  | 
 | ||||||
|  |     BinaryPatch::State state = patch.checkState(bindata.data(), bindata.size()); | ||||||
|  |     if (state == BinaryPatch::Conflict) | ||||||
|  |         return 1; | ||||||
|  | 
 | ||||||
|  |     if (cmd == "check") | ||||||
|  |     { | ||||||
|  |         switch (state) | ||||||
|  |         { | ||||||
|  |         case BinaryPatch::Unapplied: | ||||||
|  |             cout << "Currently not applied." << endl; | ||||||
|  |             break; | ||||||
|  |         case BinaryPatch::Applied: | ||||||
|  |             cout << "Currently applied." << endl; | ||||||
|  |             break; | ||||||
|  |         case BinaryPatch::Partial: | ||||||
|  |             cout << "Currently partially applied." << endl; | ||||||
|  |             break; | ||||||
|  |         default: | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  |     else if (cmd == "apply") | ||||||
|  |     { | ||||||
|  |         if (state == BinaryPatch::Applied) | ||||||
|  |         { | ||||||
|  |             cout << "Already applied." << endl; | ||||||
|  |             return 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         patch.apply(bindata.data(), bindata.size(), true); | ||||||
|  |     } | ||||||
|  |     else if (cmd == "remove") | ||||||
|  |     { | ||||||
|  |         if (state == BinaryPatch::Unapplied) | ||||||
|  |         { | ||||||
|  |             cout << "Already removed." << endl; | ||||||
|  |             return 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         patch.apply(bindata.data(), bindata.size(), false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     std::string bak_file = exe_file + ".bak"; | ||||||
|  |     remove(bak_file.c_str()); | ||||||
|  | 
 | ||||||
|  |     if (rename(exe_file.c_str(), bak_file.c_str()) != 0) | ||||||
|  |     { | ||||||
|  |         cerr << "Could not create backup." << endl; | ||||||
|  |         return 1; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!save_file(bindata, exe_file)) | ||||||
|  |         return 1; | ||||||
|  | 
 | ||||||
|  |     cout << "Patched " << patch.entries.size() << " bytes." << endl; | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue