├── .gitignore ├── LICENSE.txt ├── Project.txt ├── README.md ├── README_LGKDZ.txt ├── README_ozip.md ├── README_simg2img.txt ├── README_unpayload.md ├── about_pycrypto.md ├── clean_cache.py ├── dz.py ├── gpt.py ├── image2chunks.py ├── install_requirements.py ├── kdz.py ├── main.py ├── make.py ├── ofp_libextract.py ├── ozipdecrypt.py ├── payload_dumper.py ├── pic ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── README.txt └── home.png ├── requirements.txt ├── rimg2sdat.py ├── sdat2img.py ├── simg2img.py ├── undz.py ├── unkdz.py └── update_metadata_pb2.py /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。 3 | ################################################################################ 4 | __pycache__ 5 | .git 6 | .vs 7 | META-INF 8 | output 9 | rom 10 | Imgextractor.exe 11 | build 12 | dist 13 | main.spec 14 | testcode.py 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /Project.txt: -------------------------------------------------------------------------------- 1 | oppoozip:https://github.com/tahirtaous/ozip2zip 2 | extract_android_ota_payload:https://github.com/cyxx/extract_android_ota_payload 3 | sdat2img:https://github.com/xpirt/sdat2img 4 | rimg2sdat:https://github.com/jazchen/rimg2sdat 5 | LGKDZ:https://github.com/randomstuffpaul/kdztools 6 | PayloadDumperOnDocker:https://github.com/matze19999/PayloadDumperOnDocker 7 | simg2img:https://code.google.com/p/simg2img -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rom处理工具(Python) 2 | 3 | 此项目使用Python语言,一键解包安卓ROM的system.img 4 | 5 | 由于各大手机厂商基本上已经封闭了ROM的下载和获取,后继对于ROM解包支持可能会难以维护 6 | 7 | 目前主要支持安卓10及以下的ROM解包(以及安卓10及以上标准的动态分区/AB分区解包支持) 8 | 9 | 10 | 支持格式: 11 | 12 | > .new.dat. .new.dat.br .img .tar.md5 .ozip .kdz .dz .bin .zip .tar 13 | 14 | 其中,ozip,new.dat(.br),img,payload.bin,以及部分zip可以一键解包出system. 15 | 16 | 同时,还可以嗅探某些ROM的底层(魅族 魅蓝note5/6 嗅探底层成功) 17 | 18 | 支持几乎除了安卓10动态分区外的所有卡刷包,以及三星,LG线刷包,ozip解密等 19 | 20 | 特点:将众多开源项目涵盖在了一个项目中,方便ROM的解包操作及寻找开源项目 21 | 22 | (若main处理不了,你还可以用其它的,不过会略微麻烦就是了) 23 | 24 | 25 | 26 | 测试结果:→[前往观赏测试图](pic) 27 | 28 | 可以正常识别目前我见到的卡刷包 29 | 30 | oppo ozip解密解包功能正常(仅部分机型)(含.new.dat.br) 31 | 32 | 三星官方tar.md5解包system正常 33 | 34 | MIUI线刷包卡刷包解包正常 35 | 36 | 魅族 new.dat解包正常 37 | 38 | 360 普通打包方式解包正常 39 | 40 | Google AB payload.bin解包正常 41 | 42 | LG KDZ解包正常 43 | 44 | (暂未添加DZ解包) 45 | 46 | 食用步骤: 47 | 48 | Clone该项目: 49 | 50 | ``` 51 | git clone --depth=1 https://hub.fastgit.org/AEnjoy/unpackandroidrom.git 52 | ``` 53 | 54 | 安装依赖: 55 | 56 | ```shell 57 | python3 install_requirements.py 58 | # or 59 | pip install -r requirements.txt 60 | ``` 61 | 62 | 运行: 63 | 64 | ```shell 65 | python3 main.py 66 | ``` 67 | 68 | (提交bug -_-||,无情嘲讽) 69 | 70 | 可选操作: 71 | 72 | 定期执行-清理缓存 73 | 74 | ```shell 75 | python clean_cache.py 76 | ``` 77 | 78 | 定期执行-项目同步 79 | 80 | ```shell 81 | git pull 82 | ``` 83 | 84 | ![img](pic/home.png) 85 | 86 | 本项目引用的项目(文件)列表及来源: 87 | 88 | oppoozip:https://github.com/tahirtaous/ozip2zip 89 | ~~extract_android_ota_payload:https://github.com/cyxx/extract_android_ota_payload~~ 90 | sdat2img:https://github.com/xpirt/sdat2img 91 | rimg2sdat:https://github.com/jazchen/rimg2sdat 92 | LGKDZ:https://github.com/randomstuffpaul/kdztools 93 | PayloadDumperOnDocker:https://github.com/matze19999/PayloadDumperOnDocker 94 | simg2img:https://code.google.com/p/simg2img 95 | 96 | 运行环境需求: 97 | 98 | Python2.7/Python3.6+ 99 | 100 | 运行依赖包含在requirements.txt文件中,你可以运行install_requirements.py 一键安装依赖 101 | 102 | Changes: 103 | 104 | ``` 105 | 2.2.4→2.2.5:2024-7-7 17:35:59 106 | 1.Upgrade Docs. 107 | 2.Fix TypeError thanks to @WangBoee 108 | 109 | 2.2.3→2.2.4:2021-8-22 23:09:49 110 | 1.旧版本号忘记更新了,现在更新回来 111 | 2.修复三星线刷包解包失败问题(Thanks to CoolApkUser:名字嘛随机取一个) 112 | 3.修复三星线刷包识别可能失败的问题 113 | 4.清理代码中的垃圾部分 114 | 5.make.py Update:减小打包后的文件体积 115 | 6.make.py Update:根据系统,系统架构自动重命名压缩包文件 116 | 7.修复MIUI线刷包解包后可能会出现错误的问题 117 | 8.解决识别部分ROM后产生的垃圾文件未自动清理的问题 118 | 9.默认自动转换动态分区super.img文件 119 | 120 | 2.2.2→2.2.3:2021-8-22 19:09:49 121 | 1.修复MIUI线刷包解包后可能会出现错误的问题 122 | 2.解决识别部分ROM后产生的垃圾文件未自动清理的问题 123 | 3.默认自动转换动态分区super.img文件 124 | 125 | 2.2.2→2.2.3:2021-8-20 01:25:48 126 | 1.旧版本号忘记更新了,现在更新回来 127 | 2.修复三星线刷包解包失败问题(Thanks to CoolApkUser:名字嘛随机取一个) 128 | 3.修复三星线刷包识别可能失败的问题 129 | 4.清理代码中的垃圾部分 130 | 5.make.py Update:减小打包后的文件体积 131 | 6.make.py Update:根据系统,系统架构自动重命名压缩包文件 132 | 133 | 2.2.1→2.2.2:2021-8-19 134 | 1.Bug Fixed. 135 | 136 | 2.1→2.2.1:on 1 Jul 137 | 1.增加了Linux arm64的编译 138 | 2.增加了有关pycrypto的readme 139 | 3.现在没有pycrypto依赖也可以运行项目 140 | 4.make.py支持手动编译,方便跨平台 141 | 142 | 2→2.1:2020-8-16 13:19:14 143 | 1.修复了一个小bug 144 | 145 | 1→2:2020-8-15 21:58:28 146 | 1.更新lz4,brotli解包代码 147 | 2.更新依赖(可能需要重新运行install_requirements.py) 148 | 3.LG DZ解包支持(之前只支持查看) 149 | 4.MIUI线刷包解包 150 | ``` 151 | 152 | 根据上游开源,本项目开源许可协议为GNU/GPL3 -------------------------------------------------------------------------------- /README_LGKDZ.txt: -------------------------------------------------------------------------------- 1 | ------------------------------------------- 2 | 3 | LGE KDZ Utilities 4 | originally 5 | Copyright 2013 "IOMonster" (thecubed on XDA and GitHub) 6 | Copyright 2016 Elliott Mitchell 7 | 8 | ------------------------------------------- 9 | 10 | These two scripts will allow you to extract both KDZ files and DZ files 11 | 12 | Run unkdz.py or undz.py with the -h option to get more options. 13 | 14 | -l or --list 15 | Lists all files contained in the archive 16 | 17 | -x or --extract 18 | Extract all portions or as chunk-files. See -h or below for 19 | more detail. 20 | 21 | -c or --chunks 22 | (undz-only) Extract archive as chunks, unless a specific 23 | chunk number is give, all chunks will be extracted. 24 | 25 | -s ID or --single ID 26 | Extract a single slice/partition by ID (can be found with 27 | --list). With undz multiple IDs can be given and the whole 28 | slice/partition will be extracted 29 | 30 | -i or --image 31 | (undz-only) Extract who archive as a disk image 32 | 33 | -d DIR or --dir DIR 34 | Set directory instead of the default "[kdz|dz]extracted" 35 | directory in the current path 36 | 37 | -f FILE or --file FILE 38 | File to operate on 39 | 40 | A sample workflow can look like: 41 | 42 | $ unkdz -f H90120j_00_0712.kdz -l 43 | [+] KDZ Partition List (format v2) 44 | ========================================= 45 | 0 : H90120j_00.dz (1988019978 bytes) 46 | 1 : LGUP_c.dll (2875560 bytes) 47 | 2 : LGUP_c.dylib (1170000 bytes) 48 | $ unkdz -f H90120j_00_0712.kdz -x 49 | [+] Extracting all partitions from v2 file! 50 | 51 | [+] Extracting H90120e_00.dz to kdzextracted/H90120j_00.dz 52 | [+] Extracting LGUP_c.dll to kdzextracted/LGUP_c.dll 53 | [+] Extracting LGUP_c.dylib to kdzextracted/LGUP_c.dylib 54 | $ undz -f kdzextracted/H90120j_00.dz -l 55 | [!] Warning: Chunk is part of "BackupGPT", but starts in front of slice?! 56 | [+] DZ Partition List 57 | ========================================= 58 | 0/ 1 : PrimaryGPT_0.bin (5199 bytes) 59 | -1/?? : _unallocated_0_8371200 () 60 | 1/ 2 : modem_16384.bin (42902300 bytes) 61 | 2/?? : spare1 () 62 | 3/ 3 : pmic_196608.bin (11065 bytes) 63 | 4/ 4 : sbl1_197632.bin (290373 bytes) 64 | 5/ 5 : tz_199680.bin (258129 bytes) 65 | 6/ 6 : sdi_201728.bin (14684 bytes) 66 | 7/ 7 : hyp_202752.bin (23677 bytes) 67 | 8/ 8 : rpm_203776.bin (111265 bytes) 68 | 9/ 9 : aboot_204800.bin (407367 bytes) 69 | 10/10 : sbl1bak_208896.bin (290373 bytes) 70 | 11/11 : pmicbak_210944.bin (11065 bytes) 71 | 12/12 : hypbak_211968.bin (23677 bytes) 72 | 13/13 : tzbak_212992.bin (258129 bytes) 73 | 14/14 : rpmbak_215040.bin (111265 bytes) 74 | 15/15 : abootbak_216064.bin (407367 bytes) 75 | 16/16 : sdibak_220160.bin (14684 bytes) 76 | 17/?? : limits () 77 | 18/?? : apdp () 78 | 19/?? : msadp () 79 | 20/?? : dpo () 80 | 21/?? : spare2 () 81 | 22/?? : persistent () 82 | 23/?? : devinfo () 83 | 24/?? : spare3 () 84 | 25/?? : misc () 85 | 26/17 : persist_278528.bin (23385 bytes) 86 | 27/?? : modemst1 () 87 | 28/?? : modemst2 () 88 | 29/?? : fsg () 89 | 30/?? : fsc () 90 | 31/?? : ssd () 91 | 32/?? : keystore () 92 | 33/?? : DDR () 93 | 34/18 : sec_360448.bin (2603 bytes) 94 | 35/?? : encrypt () 95 | 36/?? : eksst () 96 | 37/19 : rct_363520.bin (2317 bytes) 97 | 38/?? : spare4 () 98 | 39/20 : laf_376832.bin (19533292 bytes) 99 | 40/21 : boot_475136.bin (16694149 bytes) 100 | 41/22 : recovery_557056.bin (17891387 bytes) 101 | 42/?? : drm () 102 | 43/?? : sns () 103 | 44/?? : mpt () 104 | 45/23 : raw_resources_737280.bin (888299 bytes) 105 | 46/24 : raw_resourcesbak_745472.bin (888299 bytes) 106 | 47/25 : factory_753664.bin (2317 bytes) 107 | 48/?? : spare5 () 108 | 49/?? : fota () 109 | 50/?? : fau () 110 | 51/26 : system_901120.bin (25371453 bytes) 111 | 51/27 : system_1165448.bin (60652649 bytes) 112 | 51/28 : system_1429456.bin (65098099 bytes) 113 | 51/29 : system_1689736.bin (2313 bytes) 114 | 51/30 : system_1693784.bin (52573309 bytes) 115 | 51/31 : system_1953744.bin (64109275 bytes) 116 | 51/32 : system_2214024.bin (2313 bytes) 117 | 51/33 : system_2218072.bin (75157338 bytes) 118 | 51/34 : system_2478032.bin (56945821 bytes) 119 | 51/35 : system_2738312.bin (2313 bytes) 120 | 51/36 : system_2742360.bin (68310797 bytes) 121 | 51/37 : system_3002320.bin (54648076 bytes) 122 | 51/38 : system_3262600.bin (2313 bytes) 123 | 51/39 : system_3266648.bin (59937725 bytes) 124 | 51/40 : system_3526608.bin (66799033 bytes) 125 | 51/41 : system_3788752.bin (61032997 bytes) 126 | 51/42 : system_4050896.bin (88105175 bytes) 127 | 51/43 : system_4313040.bin (57305742 bytes) 128 | 51/44 : system_4575184.bin (59805583 bytes) 129 | 51/45 : system_4837328.bin (53946948 bytes) 130 | 51/46 : system_5099472.bin (62892749 bytes) 131 | 51/47 : system_5361616.bin (82165315 bytes) 132 | 51/48 : system_5623760.bin (68205296 bytes) 133 | 51/49 : system_5885904.bin (60609918 bytes) 134 | 51/50 : system_6148048.bin (80451673 bytes) 135 | 51/51 : system_6410192.bin (73034607 bytes) 136 | 51/52 : system_6672336.bin (59097445 bytes) 137 | 51/53 : system_6934480.bin (61310637 bytes) 138 | 51/54 : system_7196624.bin (58278602 bytes) 139 | 51/55 : system_7456904.bin (2313 bytes) 140 | 51/56 : system_7460952.bin (55951601 bytes) 141 | 51/57 : system_7720912.bin (64319230 bytes) 142 | 51/58 : system_7981192.bin (2313 bytes) 143 | 51/59 : system_7985240.bin (66376379 bytes) 144 | 51/60 : system_8245200.bin (62336405 bytes) 145 | 51/61 : system_8507344.bin (26426046 bytes) 146 | 51/62 : system_8765440.bin (2314 bytes) 147 | 51/63 : system_9027584.bin (2314 bytes) 148 | 51/64 : system_9289728.bin (2314 bytes) 149 | 51/65 : system_9551872.bin (2317 bytes) 150 | 51/66 : system_9554936.bin (35634261 bytes) 151 | 52/?? : cache () 152 | 53/?? : userdata () 153 | 54/?? : grow () 154 | 55/67 : BackupGPT_122141696.bin (5174 bytes) 155 | $ undz -f kdzextracted/H90120j_00.dz -x 156 | [!] Warning: Chunk is part of "BackupGPT", but starts in front of slice?! 157 | [+] Extracting all chunkfiles! 158 | 159 | [+] Extracting PrimaryGPT_0.bin to PrimaryGPT_0.bin.chunk 160 | [+] Extracting modem_16384.bin to modem_16384.bin.chunk 161 | [+] Extracting pmic_196608.bin to pmic_196608.bin.chunk 162 | [+] Extracting sbl1_197632.bin to sbl1_197632.bin.chunk 163 | [+] Extracting tz_199680.bin to tz_199680.bin.chunk 164 | [+] Extracting sdi_201728.bin to sdi_201728.bin.chunk 165 | [+] Extracting hyp_202752.bin to hyp_202752.bin.chunk 166 | [+] Extracting rpm_203776.bin to rpm_203776.bin.chunk 167 | [+] Extracting aboot_204800.bin to aboot_204800.bin.chunk 168 | [+] Extracting sbl1bak_208896.bin to sbl1bak_208896.bin.chunk 169 | [+] Extracting pmicbak_210944.bin to pmicbak_210944.bin.chunk 170 | [+] Extracting hypbak_211968.bin to hypbak_211968.bin.chunk 171 | [+] Extracting tzbak_212992.bin to tzbak_212992.bin.chunk 172 | [+] Extracting rpmbak_215040.bin to rpmbak_215040.bin.chunk 173 | [+] Extracting abootbak_216064.bin to abootbak_216064.bin.chunk 174 | [+] Extracting sdibak_220160.bin to sdibak_220160.bin.chunk 175 | [+] Extracting persist_278528.bin to persist_278528.bin.chunk 176 | [+] Extracting sec_360448.bin to sec_360448.bin.chunk 177 | [+] Extracting rct_363520.bin to rct_363520.bin.chunk 178 | [+] Extracting laf_376832.bin to laf_376832.bin.chunk 179 | [+] Extracting boot_475136.bin to boot_475136.bin.chunk 180 | [+] Extracting recovery_557056.bin to recovery_557056.bin.chunk 181 | [+] Extracting raw_resources_737280.bin to raw_resources_737280.bin.chunk 182 | [+] Extracting raw_resourcesbak_745472.bin to raw_resourcesbak_745472.bin.chunk 183 | [+] Extracting factory_753664.bin to factory_753664.bin.chunk 184 | [+] Extracting system_901120.bin to system_901120.bin.chunk 185 | [+] Extracting system_1165448.bin to system_1165448.bin.chunk 186 | [+] Extracting system_1429456.bin to system_1429456.bin.chunk 187 | [+] Extracting system_1689736.bin to system_1689736.bin.chunk 188 | [+] Extracting system_1693784.bin to system_1693784.bin.chunk 189 | [+] Extracting system_1953744.bin to system_1953744.bin.chunk 190 | [+] Extracting system_2214024.bin to system_2214024.bin.chunk 191 | [+] Extracting system_2218072.bin to system_2218072.bin.chunk 192 | [+] Extracting system_2478032.bin to system_2478032.bin.chunk 193 | [+] Extracting system_2738312.bin to system_2738312.bin.chunk 194 | [+] Extracting system_2742360.bin to system_2742360.bin.chunk 195 | [+] Extracting system_3002320.bin to system_3002320.bin.chunk 196 | [+] Extracting system_3262600.bin to system_3262600.bin.chunk 197 | [+] Extracting system_3266648.bin to system_3266648.bin.chunk 198 | [+] Extracting system_3526608.bin to system_3526608.bin.chunk 199 | [+] Extracting system_3788752.bin to system_3788752.bin.chunk 200 | [+] Extracting system_4050896.bin to system_4050896.bin.chunk 201 | [+] Extracting system_4313040.bin to system_4313040.bin.chunk 202 | [+] Extracting system_4575184.bin to system_4575184.bin.chunk 203 | [+] Extracting system_4837328.bin to system_4837328.bin.chunk 204 | [+] Extracting system_5099472.bin to system_5099472.bin.chunk 205 | [+] Extracting system_5361616.bin to system_5361616.bin.chunk 206 | [+] Extracting system_5623760.bin to system_5623760.bin.chunk 207 | [+] Extracting system_5885904.bin to system_5885904.bin.chunk 208 | [+] Extracting system_6148048.bin to system_6148048.bin.chunk 209 | [+] Extracting system_6410192.bin to system_6410192.bin.chunk 210 | [+] Extracting system_6672336.bin to system_6672336.bin.chunk 211 | [+] Extracting system_6934480.bin to system_6934480.bin.chunk 212 | [+] Extracting system_7196624.bin to system_7196624.bin.chunk 213 | [+] Extracting system_7456904.bin to system_7456904.bin.chunk 214 | [+] Extracting system_7460952.bin to system_7460952.bin.chunk 215 | [+] Extracting system_7720912.bin to system_7720912.bin.chunk 216 | [+] Extracting system_7981192.bin to system_7981192.bin.chunk 217 | [+] Extracting system_7985240.bin to system_7985240.bin.chunk 218 | [+] Extracting system_8245200.bin to system_8245200.bin.chunk 219 | [+] Extracting system_8507344.bin to system_8507344.bin.chunk 220 | [+] Extracting system_8765440.bin to system_8765440.bin.chunk 221 | [+] Extracting system_9027584.bin to system_9027584.bin.chunk 222 | [+] Extracting system_9289728.bin to system_9289728.bin.chunk 223 | [+] Extracting system_9551872.bin to system_9551872.bin.chunk 224 | [+] Extracting system_9554936.bin to system_9554936.bin.chunk 225 | [+] Extracting BackupGPT_122141696.bin to BackupGPT_122141696.bin.chunk 226 | $ rm dzextracted/system_[0-9]*[0-9].bin.chunk 227 | $ undz -f kdzextracted/H90120j_00.dz -s 51 228 | [!] Warning: Chunk is part of "BackupGPT", but starts in front of slice?! 229 | [+] Extracting single slice^Wpartition! 230 | 231 | [+] Extracting system_901120.bin to system.image 232 | [+] Extracting system_1165448.bin to system.image 233 | [+] Extracting system_1429456.bin to system.image 234 | [+] Extracting system_1689736.bin to system.image 235 | [+] Extracting system_1693784.bin to system.image 236 | [+] Extracting system_1953744.bin to system.image 237 | [+] Extracting system_2214024.bin to system.image 238 | [+] Extracting system_2218072.bin to system.image 239 | [+] Extracting system_2478032.bin to system.image 240 | [+] Extracting system_2738312.bin to system.image 241 | [+] Extracting system_2742360.bin to system.image 242 | [+] Extracting system_3002320.bin to system.image 243 | [+] Extracting system_3262600.bin to system.image 244 | [+] Extracting system_3266648.bin to system.image 245 | [+] Extracting system_3526608.bin to system.image 246 | [+] Extracting system_3788752.bin to system.image 247 | [+] Extracting system_4050896.bin to system.image 248 | [+] Extracting system_4313040.bin to system.image 249 | [+] Extracting system_4575184.bin to system.image 250 | [+] Extracting system_4837328.bin to system.image 251 | [+] Extracting system_5099472.bin to system.image 252 | [+] Extracting system_5361616.bin to system.image 253 | [+] Extracting system_5623760.bin to system.image 254 | [+] Extracting system_5885904.bin to system.image 255 | [+] Extracting system_6148048.bin to system.image 256 | [+] Extracting system_6410192.bin to system.image 257 | [+] Extracting system_6672336.bin to system.image 258 | [+] Extracting system_6934480.bin to system.image 259 | [+] Extracting system_7196624.bin to system.image 260 | [+] Extracting system_7456904.bin to system.image 261 | [+] Extracting system_7460952.bin to system.image 262 | [+] Extracting system_7720912.bin to system.image 263 | [+] Extracting system_7981192.bin to system.image 264 | [+] Extracting system_7985240.bin to system.image 265 | [+] Extracting system_8245200.bin to system.image 266 | [+] Extracting system_8507344.bin to system.image 267 | [+] Extracting system_8765440.bin to system.image 268 | [+] Extracting system_9027584.bin to system.image 269 | [+] Extracting system_9289728.bin to system.image 270 | [+] Extracting system_9551872.bin to system.image 271 | [+] Extracting system_9554936.bin to system.image 272 | $ 273 | 274 | The theory was at this point you can modify out/system.image to your liking. 275 | For this particular device (LG H901), system.image is an EXT4 filesystem. Many 276 | tools exist for modifying EXT4 filesystems and these should work fine. 277 | 278 | The next step would be reconstructing the file. There are three steps, turning 279 | the files into chunks, merging them together into a DZ file, and then merging 280 | everything back into a KDZ file. The first step has some quirks. 281 | 282 | "image2chunks.py" currently has 3 strategies for breaking image files into 283 | chunks. At of this writing the prefered strategy is to make use of ext2simg 284 | from the Android image utilities. This produces results that differ somewhat 285 | from LG's images, but is believed to produce sane results. The differences 286 | have me wondering whether LG's images are either unsafe, or else relying on 287 | special knowledge of the hardware (is the eMMC certain to give back zero blocks 288 | for TRIMmed areas?). 289 | 290 | The next two strategies are utilizing support for SEEK_DATA and SEEK_HOLE, or 291 | probing for the presence of holes. Operating System support for 292 | SEEK_DATA/SEEK_HOLE is decent, though to my knowledge no versions of Windows 293 | include this. This seems a bit of a cheat since it is relying on knowledge of 294 | which areas of the image haven't been written to. Probing marks an awful lot 295 | of areas as holes, which leaves me uncomfortable believing the results to be 296 | sane. As such I reccommend the first if available (ext2simg is known to work 297 | for Linux, but I'm unsure Windows binaries are available). 298 | 299 | WARNING: It has been found there is some additional unknown verification 300 | mechanism in LGE's tools. Due to this mechanism currently the generated KDZ 301 | files haven't been shown to work. There are some guesses as to where the 302 | mechanism is, but as of now not enough is known to work around it. 303 | 304 | $ image2chunks --ext4 dzextracted/system.image 305 | [+] Compressing system.image to system_901120.bin (0 empty blocks) 306 | [+] Compressing system.image to system_1163264.bin (8 empty blocks) 307 | [+] Compressing system.image to system_1425408.bin (8 empty blocks) 308 | [+] Compressing system.image to system_1687552.bin (8 empty blocks) 309 | [+] Compressing system.image to system_1949696.bin (8 empty blocks) 310 | [+] Compressing system.image to system_2211840.bin (8 empty blocks) 311 | [+] Compressing system.image to system_2473984.bin (8 empty blocks) 312 | [+] Compressing system.image to system_2736128.bin (8 empty blocks) 313 | [+] Compressing system.image to system_2998272.bin (8 empty blocks) 314 | [+] Compressing system.image to system_3260416.bin (8 empty blocks) 315 | [+] Compressing system.image to system_3522560.bin (8 empty blocks) 316 | [+] Compressing system.image to system_3784704.bin (8 empty blocks) 317 | [+] Compressing system.image to system_4046848.bin (8 empty blocks) 318 | [+] Compressing system.image to system_4308992.bin (8 empty blocks) 319 | [+] Compressing system.image to system_4571136.bin (8 empty blocks) 320 | [+] Compressing system.image to system_4833280.bin (8 empty blocks) 321 | [+] Compressing system.image to system_5095424.bin (8 empty blocks) 322 | [+] Compressing system.image to system_5357568.bin (8 empty blocks) 323 | [+] Compressing system.image to system_5619712.bin (8 empty blocks) 324 | [+] Compressing system.image to system_5881856.bin (8 empty blocks) 325 | [+] Compressing system.image to system_6144000.bin (8 empty blocks) 326 | [+] Compressing system.image to system_6406144.bin (8 empty blocks) 327 | [+] Compressing system.image to system_6668288.bin (8 empty blocks) 328 | [+] Compressing system.image to system_6930432.bin (8 empty blocks) 329 | [+] Compressing system.image to system_7192576.bin (8 empty blocks) 330 | [+] Compressing system.image to system_7454720.bin (8 empty blocks) 331 | [+] Compressing system.image to system_7716864.bin (8 empty blocks) 332 | [+] Compressing system.image to system_7979008.bin (8 empty blocks) 333 | [+] Compressing system.image to system_8241152.bin (8 empty blocks) 334 | [+] Compressing system.image to system_8503296.bin (150056 empty blocks) 335 | [+] Compressing system.image to system_8765440.bin (258096 empty blocks) 336 | [+] Compressing system.image to system_9027584.bin (258096 empty blocks) 337 | [+] Compressing system.image to system_9289728.bin (258096 empty blocks) 338 | [+] Compressing system.image to system_9551872.bin (8 empty blocks) 339 | [+] done 340 | 341 | $ mkdz -f kdzextracted/H90120j_00.dz -m 342 | [+] Writing 60 chunks to H90120j_00.dz: 343 | 344 | [+] Writing PrimaryGPT_0.bin.chunk to H90120j_00.dz (5711 bytes) 345 | [+] Writing modem_16384.bin.chunk to H90120j_00.dz (42902812 bytes) 346 | [+] Writing pmic_196608.bin.chunk to H90120j_00.dz (11577 bytes) 347 | [+] Writing sbl1_197632.bin.chunk to H90120j_00.dz (290885 bytes) 348 | [+] Writing tz_199680.bin.chunk to H90120j_00.dz (258641 bytes) 349 | [+] Writing sdi_201728.bin.chunk to H90120j_00.dz (15196 bytes) 350 | [+] Writing hyp_202752.bin.chunk to H90120j_00.dz (24189 bytes) 351 | [+] Writing rpm_203776.bin.chunk to H90120j_00.dz (111777 bytes) 352 | [+] Writing aboot_204800.bin.chunk to H90120j_00.dz (407879 bytes) 353 | [+] Writing sbl1bak_208896.bin.chunk to H90120j_00.dz (290885 bytes) 354 | [+] Writing pmicbak_210944.bin.chunk to H90120j_00.dz (11577 bytes) 355 | [+] Writing hypbak_211968.bin.chunk to H90120j_00.dz (24189 bytes) 356 | [+] Writing tzbak_212992.bin.chunk to H90120j_00.dz (258641 bytes) 357 | [+] Writing rpmbak_215040.bin.chunk to H90120j_00.dz (111777 bytes) 358 | [+] Writing abootbak_216064.bin.chunk to H90120j_00.dz (407879 bytes) 359 | [+] Writing sdibak_220160.bin.chunk to H90120j_00.dz (15196 bytes) 360 | [+] Writing persist_278528.bin.chunk to H90120j_00.dz (23897 bytes) 361 | [+] Writing sec_360448.bin.chunk to H90120j_00.dz (3115 bytes) 362 | [+] Writing rct_363520.bin.chunk to H90120j_00.dz (2829 bytes) 363 | [+] Writing laf_376832.bin.chunk to H90120j_00.dz (19533804 bytes) 364 | [+] Writing boot_475136.bin.chunk to H90120j_00.dz (16694661 bytes) 365 | [+] Writing recovery_557056.bin.chunk to H90120j_00.dz (17891899 bytes) 366 | [+] Writing raw_resources_737280.bin.chunk to H90120j_00.dz (888811 bytes) 367 | [+] Writing raw_resourcesbak_745472.bin.chunk to H90120j_00.dz (888811 bytes) 368 | [+] Writing factory_753664.bin.chunk to H90120j_00.dz (2829 bytes) 369 | [+] Writing system_901120.bin.chunk to H90120j_00.dz (25368238 bytes) 370 | [+] Writing system_1163264.bin.chunk to H90120j_00.dz (60662497 bytes) 371 | [+] Writing system_1425408.bin.chunk to H90120j_00.dz (65117233 bytes) 372 | [+] Writing system_1687552.bin.chunk to H90120j_00.dz (52590332 bytes) 373 | [+] Writing system_1949696.bin.chunk to H90120j_00.dz (64116886 bytes) 374 | [+] Writing system_2211840.bin.chunk to H90120j_00.dz (75176824 bytes) 375 | [+] Writing system_2473984.bin.chunk to H90120j_00.dz (56952617 bytes) 376 | [+] Writing system_2736128.bin.chunk to H90120j_00.dz (68318938 bytes) 377 | [+] Writing system_2998272.bin.chunk to H90120j_00.dz (54652864 bytes) 378 | [+] Writing system_3260416.bin.chunk to H90120j_00.dz (59959919 bytes) 379 | [+] Writing system_3522560.bin.chunk to H90120j_00.dz (66803716 bytes) 380 | [+] Writing system_3784704.bin.chunk to H90120j_00.dz (61048842 bytes) 381 | [+] Writing system_4046848.bin.chunk to H90120j_00.dz (88113814 bytes) 382 | [+] Writing system_4308992.bin.chunk to H90120j_00.dz (57314201 bytes) 383 | [+] Writing system_4571136.bin.chunk to H90120j_00.dz (59818074 bytes) 384 | [+] Writing system_4833280.bin.chunk to H90120j_00.dz (53953971 bytes) 385 | [+] Writing system_5095424.bin.chunk to H90120j_00.dz (62896913 bytes) 386 | [+] Writing system_5357568.bin.chunk to H90120j_00.dz (82167175 bytes) 387 | [+] Writing system_5619712.bin.chunk to H90120j_00.dz (68191722 bytes) 388 | [+] Writing system_5881856.bin.chunk to H90120j_00.dz (60612219 bytes) 389 | [+] Writing system_6144000.bin.chunk to H90120j_00.dz (80457934 bytes) 390 | [+] Writing system_6406144.bin.chunk to H90120j_00.dz (73042775 bytes) 391 | [+] Writing system_6668288.bin.chunk to H90120j_00.dz (59107610 bytes) 392 | [+] Writing system_6930432.bin.chunk to H90120j_00.dz (61329475 bytes) 393 | [+] Writing system_7192576.bin.chunk to H90120j_00.dz (58290368 bytes) 394 | [+] Writing system_7454720.bin.chunk to H90120j_00.dz (55970470 bytes) 395 | [+] Writing system_7716864.bin.chunk to H90120j_00.dz (64312923 bytes) 396 | [+] Writing system_7979008.bin.chunk to H90120j_00.dz (66396113 bytes) 397 | [+] Writing system_8241152.bin.chunk to H90120j_00.dz (62351646 bytes) 398 | [+] Writing system_8503296.bin.chunk to H90120j_00.dz (26438033 bytes) 399 | [+] Writing system_8765440.bin.chunk to H90120j_00.dz (9577 bytes) 400 | [+] Writing system_9027584.bin.chunk to H90120j_00.dz (9577 bytes) 401 | [+] Writing system_9289728.bin.chunk to H90120j_00.dz (9577 bytes) 402 | [+] Writing system_9551872.bin.chunk to H90120j_00.dz (35643326 bytes) 403 | [+] Writing BackupGPT_122141696.bin.chunk to H90120j_00.dz (5686 bytes) 404 | $ mkkdz -f myH901_20j.kdz -m 405 | [+] Writing LGUP_c.dll to output file myH901_20j.kdz 406 | [+] Writing LGUP_c.dylib to output file myH901_20j.kdz 407 | [+] Writing H90120j_00.dz to output file myH901_20j.kdz 408 | 409 | [+] Writing headers to myH901_20j.kdz 410 | [+] Done! 411 | $ 412 | 413 | At this point myH901_20j.kdz should be installable using LG's tools. If the 414 | image was unmodified, and image2chunks.py was used with its --sparse option, 415 | it should be identical to the original file. This second statement relies on 416 | behavior of the OS and may not work precisely on all systems. Exactly 417 | identical files have only been shown on Linux with system.image being unpacked 418 | onto a EXT4 FS (dzextracted being on EXT4). EXT2/3 will also work if mounted 419 | using the EXT4's backwards compatibility mode, rather than the native 420 | implementation of EXT2/3. Most other flavors of Unix should get sane output, 421 | but not as likely to be identical. 422 | 423 | There is a value in the chunk headers referred to as "trimCount" in the code, 424 | as well as in the .params files (these are simply text files) generated for 425 | extracted slices. My suspicion is this is this is a count of blocks to be 426 | TRIMed prior to writing the data from the DZ file. There are some oddities 427 | though, notably several slices/partitions are marked as being wiped by a 428 | large value on the prior chunk. I'm unsure what this means, perhaps LG's tools 429 | ignore super large wipes. 430 | 431 | There is also the quirk of the backup GPT's chunk extending near the front of 432 | the "grow" slice/partition. My suspicion is this is a weakness of the tools LG 433 | is using to generate the files. Perhaps they don't understand the concept of 434 | chunks which don't start at a slice/partition boundary, they also may not 435 | correctly handle the case of chunks which *begin* with a hole. 436 | 437 | Lastly, I'm concerned about the number of unknowns in the DZ header. Several 438 | look to be harmless to simply copy the value from an existing file, but others 439 | are totally unknown to me at this time. Two fields look to be date codes of 440 | some flavor (easy, simply copy). I worry more of these need to be regenerated, 441 | but I've got no idea what to put in new files. 442 | 443 | WARNING: As of this writing it appears the fear of the unknowns was valid. 444 | According to reports it appears there is an additional verification mechanism 445 | which needs to be worked around. At the moment I'm guessing "unknown1" (saved 446 | in dzextracted/.dz.params) is a MD5 of some portion of the image. 447 | Unfortunately without knowing which portion, it cannot be adjusted. Similarly 448 | "unknown3" could be a CRC-32 of the same area. My fear is "unknown1" could be 449 | a keyed hash at which point fixing is impossible unless we discover what/where 450 | the key is. 451 | 452 | One piece of good news is I'm pretty confident unpacking mostly works 453 | correctly. The one quirk is newer LGE devices (the G5) the DZ format is 454 | somewhat modified and unpacking doesn't yet meet my expectations. 455 | 456 | 457 | ------------------------------------------- 458 | 459 | Permission was granted to rerelease this software as long as attribution was 460 | given. This version is distributed under the terms of the GNU Public License 461 | version 3. 462 | 463 | ------------------------------------------- 464 | 465 | -------------------------------------------------------------------------------- /README_ozip.md: -------------------------------------------------------------------------------- 1 | - ozip2zip 2 | Convert Oppo ozip firmware file to zip files 3 | 4 | 5 | Welcome to Androidiya - Please Subscribe if you haven't 6 | 7 | Please subscribe for more https://www.youtube.com/channel/UC0dMbs2KSS5GvBMRoyQZ4Ow/featured?disable_polymer=1 8 | 9 | I am Tahir, I just found this code online which worked for me. I spend several housr to find a solution to convert Oppo firmware from ozip ro zip file b ut only this code worked for me on Linux. I didn't write this code and I don't know how wrote this code. I am just sharing it because it works. 10 | 11 | I was able to convert Oppo F5 firmware to zip file with this. 12 | 13 | **How to use it** 14 | 15 | Open terminal and use thse commands to Install Python 16 | ``` 17 | sudo apt install python3 18 | sudo apt install python3-pip 19 | pip3 install pycrypto 20 | ``` 21 | Now create a new directory and move .ozip firmware file, ozipdecrypt.py, and ofp_libextract.py in this same folder. 22 | 23 | Run 24 | ``` 25 | ./ozipdecrypt.py *.ozip 26 | ``` 27 | or 28 | ``` 29 | ./ozipdecrypt.py firmwarefilename.ozip 30 | ``` 31 | It will take a minute ot two to convert, depends on the file size. 32 | 33 | **With this code you can convert** 34 | 35 | Oppo,Realme 36 | 37 | [ stock recovery ozip --> custom recovery flashable to zip ] 38 | 39 | Supported devices list for .ozip to .zip 40 | 41 | OPPO:- 42 | - A77 43 | - R11 44 | - R11s 45 | - R11s Plus 46 | - R9s 47 | - R9s Plus 48 | - FindX 49 | - FindX 50 | - K1 51 | - Reno 52 | - K3 53 | - A9 54 | - Reno 10x zoom PCCM00 55 | - A1 56 | - A83t 57 | - R17 Pro 58 | 59 | REALME:- 60 | - Realme 2 = Rename the .ozip file to .zip and flash via recovery 61 | - Realme C1 = Rename the .ozip file to .zip and flash via recovery 62 | - Realme 1 63 | - Realme C2 64 | - Realme 2 pro 65 | - Realme U1 RMX1831 66 | - Realme 3 RMX1825EX 67 | - Realme 3 Pro 68 | - Realme X 69 | - Realme X2 70 | - Realme 5 71 | - Realme 5 = Rename the .ozip file to .zip and flash via recovery 72 | -------------------------------------------------------------------------------- /README_simg2img.txt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # FILE: simg2img.py 5 | # 6 | # USAGE: ./simg2img.py system.img 7 | # 8 | # DESCRIPTION: 9 | # 10 | # AUTHOR: Karl Zheng 11 | # COMPANY: Meizu 12 | # CREATED: 2011年10月18日 15时25分15秒 CST 13 | # REVISION: --- 14 | # MODIFY: AEnjoy 15 | #=============================================================================== 16 | GNU GENERAL PUBLIC LICENSE 17 | Version 2, June 1991 18 | 19 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 20 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 21 | Everyone is permitted to copy and distribute verbatim copies 22 | of this license document, but changing it is not allowed. 23 | 24 | Preamble 25 | 26 | The licenses for most software are designed to take away your 27 | freedom to share and change it. By contrast, the GNU General Public 28 | License is intended to guarantee your freedom to share and change free 29 | software--to make sure the software is free for all its users. This 30 | General Public License applies to most of the Free Software 31 | Foundation's software and to any other program whose authors commit to 32 | using it. (Some other Free Software Foundation software is covered by 33 | the GNU Lesser General Public License instead.) You can apply it to 34 | your programs, too. 35 | 36 | When we speak of free software, we are referring to freedom, not 37 | price. Our General Public Licenses are designed to make sure that you 38 | have the freedom to distribute copies of free software (and charge for 39 | this service if you wish), that you receive source code or can get it 40 | if you want it, that you can change the software or use pieces of it 41 | in new free programs; and that you know you can do these things. 42 | 43 | To protect your rights, we need to make restrictions that forbid 44 | anyone to deny you these rights or to ask you to surrender the rights. 45 | These restrictions translate to certain responsibilities for you if you 46 | distribute copies of the software, or if you modify it. 47 | 48 | For example, if you distribute copies of such a program, whether 49 | gratis or for a fee, you must give the recipients all the rights that 50 | you have. You must make sure that they, too, receive or can get the 51 | source code. And you must show them these terms so they know their 52 | rights. 53 | 54 | We protect your rights with two steps: (1) copyright the software, and 55 | (2) offer you this license which gives you legal permission to copy, 56 | distribute and/or modify the software. 57 | 58 | Also, for each author's protection and ours, we want to make certain 59 | that everyone understands that there is no warranty for this free 60 | software. If the software is modified by someone else and passed on, we 61 | want its recipients to know that what they have is not the original, so 62 | that any problems introduced by others will not reflect on the original 63 | authors' reputations. 64 | 65 | Finally, any free program is threatened constantly by software 66 | patents. We wish to avoid the danger that redistributors of a free 67 | program will individually obtain patent licenses, in effect making the 68 | program proprietary. To prevent this, we have made it clear that any 69 | patent must be licensed for everyone's free use or not licensed at all. 70 | 71 | The precise terms and conditions for copying, distribution and 72 | modification follow. 73 | 74 | GNU GENERAL PUBLIC LICENSE 75 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 76 | 77 | 0. This License applies to any program or other work which contains 78 | a notice placed by the copyright holder saying it may be distributed 79 | under the terms of this General Public License. The "Program", below, 80 | refers to any such program or work, and a "work based on the Program" 81 | means either the Program or any derivative work under copyright law: 82 | that is to say, a work containing the Program or a portion of it, 83 | either verbatim or with modifications and/or translated into another 84 | language. (Hereinafter, translation is included without limitation in 85 | the term "modification".) Each licensee is addressed as "you". 86 | 87 | Activities other than copying, distribution and modification are not 88 | covered by this License; they are outside its scope. The act of 89 | running the Program is not restricted, and the output from the Program 90 | is covered only if its contents constitute a work based on the 91 | Program (independent of having been made by running the Program). 92 | Whether that is true depends on what the Program does. 93 | 94 | 1. You may copy and distribute verbatim copies of the Program's 95 | source code as you receive it, in any medium, provided that you 96 | conspicuously and appropriately publish on each copy an appropriate 97 | copyright notice and disclaimer of warranty; keep intact all the 98 | notices that refer to this License and to the absence of any warranty; 99 | and give any other recipients of the Program a copy of this License 100 | along with the Program. 101 | 102 | You may charge a fee for the physical act of transferring a copy, and 103 | you may at your option offer warranty protection in exchange for a fee. 104 | 105 | 2. You may modify your copy or copies of the Program or any portion 106 | of it, thus forming a work based on the Program, and copy and 107 | distribute such modifications or work under the terms of Section 1 108 | above, provided that you also meet all of these conditions: 109 | 110 | a) You must cause the modified files to carry prominent notices 111 | stating that you changed the files and the date of any change. 112 | 113 | b) You must cause any work that you distribute or publish, that in 114 | whole or in part contains or is derived from the Program or any 115 | part thereof, to be licensed as a whole at no charge to all third 116 | parties under the terms of this License. 117 | 118 | c) If the modified program normally reads commands interactively 119 | when run, you must cause it, when started running for such 120 | interactive use in the most ordinary way, to print or display an 121 | announcement including an appropriate copyright notice and a 122 | notice that there is no warranty (or else, saying that you provide 123 | a warranty) and that users may redistribute the program under 124 | these conditions, and telling the user how to view a copy of this 125 | License. (Exception: if the Program itself is interactive but 126 | does not normally print such an announcement, your work based on 127 | the Program is not required to print an announcement.) 128 | 129 | These requirements apply to the modified work as a whole. If 130 | identifiable sections of that work are not derived from the Program, 131 | and can be reasonably considered independent and separate works in 132 | themselves, then this License, and its terms, do not apply to those 133 | sections when you distribute them as separate works. But when you 134 | distribute the same sections as part of a whole which is a work based 135 | on the Program, the distribution of the whole must be on the terms of 136 | this License, whose permissions for other licensees extend to the 137 | entire whole, and thus to each and every part regardless of who wrote it. 138 | 139 | Thus, it is not the intent of this section to claim rights or contest 140 | your rights to work written entirely by you; rather, the intent is to 141 | exercise the right to control the distribution of derivative or 142 | collective works based on the Program. 143 | 144 | In addition, mere aggregation of another work not based on the Program 145 | with the Program (or with a work based on the Program) on a volume of 146 | a storage or distribution medium does not bring the other work under 147 | the scope of this License. 148 | 149 | 3. You may copy and distribute the Program (or a work based on it, 150 | under Section 2) in object code or executable form under the terms of 151 | Sections 1 and 2 above provided that you also do one of the following: 152 | 153 | a) Accompany it with the complete corresponding machine-readable 154 | source code, which must be distributed under the terms of Sections 155 | 1 and 2 above on a medium customarily used for software interchange; or, 156 | 157 | b) Accompany it with a written offer, valid for at least three 158 | years, to give any third party, for a charge no more than your 159 | cost of physically performing source distribution, a complete 160 | machine-readable copy of the corresponding source code, to be 161 | distributed under the terms of Sections 1 and 2 above on a medium 162 | customarily used for software interchange; or, 163 | 164 | c) Accompany it with the information you received as to the offer 165 | to distribute corresponding source code. (This alternative is 166 | allowed only for noncommercial distribution and only if you 167 | received the program in object code or executable form with such 168 | an offer, in accord with Subsection b above.) 169 | 170 | The source code for a work means the preferred form of the work for 171 | making modifications to it. For an executable work, complete source 172 | code means all the source code for all modules it contains, plus any 173 | associated interface definition files, plus the scripts used to 174 | control compilation and installation of the executable. However, as a 175 | special exception, the source code distributed need not include 176 | anything that is normally distributed (in either source or binary 177 | form) with the major components (compiler, kernel, and so on) of the 178 | operating system on which the executable runs, unless that component 179 | itself accompanies the executable. 180 | 181 | If distribution of executable or object code is made by offering 182 | access to copy from a designated place, then offering equivalent 183 | access to copy the source code from the same place counts as 184 | distribution of the source code, even though third parties are not 185 | compelled to copy the source along with the object code. 186 | 187 | 4. You may not copy, modify, sublicense, or distribute the Program 188 | except as expressly provided under this License. Any attempt 189 | otherwise to copy, modify, sublicense or distribute the Program is 190 | void, and will automatically terminate your rights under this License. 191 | However, parties who have received copies, or rights, from you under 192 | this License will not have their licenses terminated so long as such 193 | parties remain in full compliance. 194 | 195 | 5. You are not required to accept this License, since you have not 196 | signed it. However, nothing else grants you permission to modify or 197 | distribute the Program or its derivative works. These actions are 198 | prohibited by law if you do not accept this License. Therefore, by 199 | modifying or distributing the Program (or any work based on the 200 | Program), you indicate your acceptance of this License to do so, and 201 | all its terms and conditions for copying, distributing or modifying 202 | the Program or works based on it. 203 | 204 | 6. Each time you redistribute the Program (or any work based on the 205 | Program), the recipient automatically receives a license from the 206 | original licensor to copy, distribute or modify the Program subject to 207 | these terms and conditions. You may not impose any further 208 | restrictions on the recipients' exercise of the rights granted herein. 209 | You are not responsible for enforcing compliance by third parties to 210 | this License. 211 | 212 | 7. If, as a consequence of a court judgment or allegation of patent 213 | infringement or for any other reason (not limited to patent issues), 214 | conditions are imposed on you (whether by court order, agreement or 215 | otherwise) that contradict the conditions of this License, they do not 216 | excuse you from the conditions of this License. If you cannot 217 | distribute so as to satisfy simultaneously your obligations under this 218 | License and any other pertinent obligations, then as a consequence you 219 | may not distribute the Program at all. For example, if a patent 220 | license would not permit royalty-free redistribution of the Program by 221 | all those who receive copies directly or indirectly through you, then 222 | the only way you could satisfy both it and this License would be to 223 | refrain entirely from distribution of the Program. 224 | 225 | If any portion of this section is held invalid or unenforceable under 226 | any particular circumstance, the balance of the section is intended to 227 | apply and the section as a whole is intended to apply in other 228 | circumstances. 229 | 230 | It is not the purpose of this section to induce you to infringe any 231 | patents or other property right claims or to contest validity of any 232 | such claims; this section has the sole purpose of protecting the 233 | integrity of the free software distribution system, which is 234 | implemented by public license practices. Many people have made 235 | generous contributions to the wide range of software distributed 236 | through that system in reliance on consistent application of that 237 | system; it is up to the author/donor to decide if he or she is willing 238 | to distribute software through any other system and a licensee cannot 239 | impose that choice. 240 | 241 | This section is intended to make thoroughly clear what is believed to 242 | be a consequence of the rest of this License. 243 | 244 | 8. If the distribution and/or use of the Program is restricted in 245 | certain countries either by patents or by copyrighted interfaces, the 246 | original copyright holder who places the Program under this License 247 | may add an explicit geographical distribution limitation excluding 248 | those countries, so that distribution is permitted only in or among 249 | countries not thus excluded. In such case, this License incorporates 250 | the limitation as if written in the body of this License. 251 | 252 | 9. The Free Software Foundation may publish revised and/or new versions 253 | of the General Public License from time to time. Such new versions will 254 | be similar in spirit to the present version, but may differ in detail to 255 | address new problems or concerns. 256 | 257 | Each version is given a distinguishing version number. If the Program 258 | specifies a version number of this License which applies to it and "any 259 | later version", you have the option of following the terms and conditions 260 | either of that version or of any later version published by the Free 261 | Software Foundation. If the Program does not specify a version number of 262 | this License, you may choose any version ever published by the Free Software 263 | Foundation. 264 | 265 | 10. If you wish to incorporate parts of the Program into other free 266 | programs whose distribution conditions are different, write to the author 267 | to ask for permission. For software which is copyrighted by the Free 268 | Software Foundation, write to the Free Software Foundation; we sometimes 269 | make exceptions for this. Our decision will be guided by the two goals 270 | of preserving the free status of all derivatives of our free software and 271 | of promoting the sharing and reuse of software generally. 272 | 273 | NO WARRANTY 274 | 275 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 276 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 277 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 278 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 279 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 280 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 281 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 282 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 283 | REPAIR OR CORRECTION. 284 | 285 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 286 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 287 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 288 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 289 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 290 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 291 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 292 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 293 | POSSIBILITY OF SUCH DAMAGES. 294 | 295 | END OF TERMS AND CONDITIONS 296 | 297 | How to Apply These Terms to Your New Programs 298 | 299 | If you develop a new program, and you want it to be of the greatest 300 | possible use to the public, the best way to achieve this is to make it 301 | free software which everyone can redistribute and change under these terms. 302 | 303 | To do so, attach the following notices to the program. It is safest 304 | to attach them to the start of each source file to most effectively 305 | convey the exclusion of warranty; and each file should have at least 306 | the "copyright" line and a pointer to where the full notice is found. 307 | 308 | 309 | Copyright (C) 310 | 311 | This program is free software; you can redistribute it and/or modify 312 | it under the terms of the GNU General Public License as published by 313 | the Free Software Foundation; either version 2 of the License, or 314 | (at your option) any later version. 315 | 316 | This program is distributed in the hope that it will be useful, 317 | but WITHOUT ANY WARRANTY; without even the implied warranty of 318 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 319 | GNU General Public License for more details. 320 | 321 | You should have received a copy of the GNU General Public License along 322 | with this program; if not, write to the Free Software Foundation, Inc., 323 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 324 | 325 | Also add information on how to contact you by electronic and paper mail. 326 | 327 | If the program is interactive, make it output a short notice like this 328 | when it starts in an interactive mode: 329 | 330 | Gnomovision version 69, Copyright (C) year name of author 331 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 332 | This is free software, and you are welcome to redistribute it 333 | under certain conditions; type `show c' for details. 334 | 335 | The hypothetical commands `show w' and `show c' should show the appropriate 336 | parts of the General Public License. Of course, the commands you use may 337 | be called something other than `show w' and `show c'; they could even be 338 | mouse-clicks or menu items--whatever suits your program. 339 | 340 | You should also get your employer (if you work as a programmer) or your 341 | school, if any, to sign a "copyright disclaimer" for the program, if 342 | necessary. Here is a sample; alter the names: 343 | 344 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 345 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 346 | 347 | , 1 April 1989 348 | Ty Coon, President of Vice 349 | 350 | This General Public License does not permit incorporating your program into 351 | proprietary programs. If your program is a subroutine library, you may 352 | consider it more useful to permit linking proprietary applications with the 353 | library. If this is what you want to do, use the GNU Lesser General 354 | Public License instead of this License. -------------------------------------------------------------------------------- /README_unpayload.md: -------------------------------------------------------------------------------- 1 | # extract_android_ota_payload.py 2 | 3 | Extract Android firmware images from an OTA payload.bin file. 4 | 5 | With the introduction of the A/B system update, the OTA file format changed. 6 | This tool allows to extract and decompress the firmware images packed using the 'brillo' toolset. 7 | 8 | Incremental firmware images are not supported (source_copy, source_bsdiff operations). 9 | 10 | ## Usage 11 | 12 | ``` 13 | $ extract_android_ota_payload.py [target_dir] 14 | : file extracted from the OTA zip file or the OTA zip file 15 | : output directory for the extracted file 16 | ``` 17 | 18 | ## Example 19 | 20 | ``` 21 | $ python extract_android_ota_payload.py marlin-ota-opm4.171019.021.d1-fd6998a5.zip /tmp/ 22 | Extracting 'boot.img' 23 | Extracting 'system.img' 24 | Extracting 'vendor.img' 25 | ... 26 | Extracting 'modem.img' 27 | ``` 28 | 29 | ## Dependencies 30 | 31 | ``` 32 | python-protobuf 33 | ``` 34 | -------------------------------------------------------------------------------- /about_pycrypto.md: -------------------------------------------------------------------------------- 1 | # pycrypto依赖可能遇到的安装问题 2 | 3 | ## 1.error: Microsoft Visual C++ 14.0 is required. Get it with "Build Tools for Visual Studio" 4 | 5 | 下载Build Tools for Visual Studio: 6 | 7 | https://visualstudio.microsoft.com/downloads/ 8 | 9 | ## 2.winrand.c ... ..\pyconfig.h(59): fatal error C1083: 无法打开包括文件: “io.h”: No such file or directory 10 | 11 | 安装完整Visual Studio,选择c/c++桌面开发 12 | 13 | ## 3.其它问题 14 | 15 | ### 1.提交issue给我 16 | 17 | ### 2.安装pycryptodome 18 | 19 | ### 3.不能定义Crypto?No Module named Crypto 20 | 21 | > #### 找到你的python包的安装目录, 22 | > 23 | > #### lib\site-packages把crypto改为Crypto 24 | 25 | # 替代方案 26 | 27 | 安装pycryptodome 28 | 29 | 打开requirements.txt,将pycrypto更改为pycryptodome 30 | 31 | -------------------------------------------------------------------------------- /clean_cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os,platform 4 | print('开始清理缓存') 5 | os.rmdir('__pycache__') 6 | if platform.system()=='Windows': 7 | os.system('rd /s /q META-INF output rom system system_') 8 | os.system('del /f /s /q *.img payload.bin system.new.dat.br system.transfer.list system.new.dat') 9 | if platform.system()=='Linux': 10 | os.system('rm -rf META-INF output rom system system_') 11 | os.system('rm -rf *.img ') 12 | os.system('rm payload.bin system.new.dat.br system.transfer.list system.new.dat') -------------------------------------------------------------------------------- /dz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Copyright (C) 2016 Elliott Mitchell 5 | Copyright (C) 2013 IOMonster (thecubed on XDA) 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | """ 20 | 21 | from __future__ import print_function 22 | import sys 23 | from struct import Struct 24 | from collections import OrderedDict 25 | 26 | 27 | class DZStruct(object): 28 | """ 29 | Common class for DZ file structures 30 | """ 31 | 32 | # Length of the headers in DZ files 33 | _dz_length = 512 34 | 35 | def __init__(self, classy): 36 | """ 37 | Common initializations for all DZ structures 38 | """ 39 | 40 | 41 | # Generate the struct for .unpack() 42 | try: 43 | classy._dzstruct 44 | 45 | except AttributeError: 46 | classy._dz_struct = Struct("<" + "".join([x[0] for x in classy._dz_format_dict.values()])) 47 | 48 | # Sanity check 49 | if self._dz_struct.size != self._dz_length: 50 | print("[!] Internal error! Chunk format wrong! (computed={:d}, specified={:d})".format(self._dz_struct.size, self._dz_length), file=sys.stderr) 51 | sys.exit(-1) 52 | 53 | # Generate list of items that can be collapsed (truncated) 54 | try: 55 | classy._dz_collapsibles 56 | 57 | except AttributeError: 58 | classy._dz_collapsibles = [n for n, (y, p) in classy._dz_format_dict.items() if p] 59 | 60 | 61 | def packdict(self, din): 62 | """ 63 | Pack all the fields from the dict into a returned buffer 64 | """ 65 | 66 | dout = dict() 67 | 68 | # pad any string keys that need padding 69 | for k in self._dz_format_dict.keys(): 70 | if self._dz_format_dict[k][0][-1] == 's': 71 | l = int(self._dz_format_dict[k][0][:-1]) 72 | dout[k] = (din[k] if k in din else b"").ljust(l, b'\x00') 73 | elif not k in din and k in self._dz_collapsibles: 74 | dout[k] = 0 75 | else: 76 | dout[k] = din[k] 77 | 78 | dout['header'] = self._dz_header 79 | 80 | values = [dout[k] for k in self._dz_format_dict.keys()] 81 | buffer = self._dz_struct.pack(*values) 82 | 83 | return buffer 84 | 85 | 86 | def unpackdict(self, buffer): 87 | """ 88 | Unpack data in buffer into a returned dictionary, return None 89 | if magic number/header is absent 90 | """ 91 | 92 | d = dict(zip( 93 | self._dz_format_dict.keys(), 94 | self._dz_struct.unpack(buffer) 95 | )) 96 | 97 | if d['header'] != self._dz_header: 98 | return None 99 | 100 | return d 101 | 102 | 103 | 104 | class DZChunk(DZStruct): 105 | """ 106 | Representation of an individual file chunk from a LGE DZ file 107 | """ 108 | 109 | _dz_area = "chunk" 110 | _dz_header = b"\x30\x12\x95\x78" 111 | 112 | # Format string dict 113 | # itemName is the new dict key for the data to be stored under 114 | # formatString is the Python formatstring for struct.unpack() 115 | # collapse: boolean that controls whether extra \x00 's should be stripped 116 | # for integer types collapse set to True means that the value should always be zero 117 | # Example: 118 | # ('itemName', ('formatString', collapse)) 119 | _dz_format_dict = OrderedDict([ 120 | ('header', ('4s', False)), # magic number 121 | ('sliceName', ('32s', True)), # name of our slice 122 | ('chunkName', ('64s', True)), # name of our chunk 123 | ('targetSize', ('I', False)), # bytes of target area 124 | ('dataSize', ('I', False)), # amount of compressed 125 | ('md5', ('16s', False)), # MD5 of target image 126 | ('targetAddr', ('I', False)), # first block to write 127 | ('trimCount', ('I', False)), # blocks to TRIM before 128 | ('dev', ('I', False)), # flash device Id 129 | ('crc32', ('I', False)), # CRC32 of target image 130 | ('pad', ('372s', True)), # currently always zero 131 | ]) 132 | 133 | def __init__(self): 134 | """ 135 | Initializer for DZChunk, gets DZStruct to fill remaining values 136 | """ 137 | super(DZChunk, self).__init__(DZChunk) 138 | 139 | 140 | 141 | class DZFile(DZStruct): 142 | """ 143 | Representation of the data parsed from a LGE DZ file 144 | """ 145 | 146 | _dz_area = "file" 147 | _dz_header = b"\x32\x96\x18\x74" 148 | 149 | # Format string dict 150 | # itemName is the new dict key for the data to be stored under 151 | # formatString is the Python formatstring for struct.unpack() 152 | # collapse: boolean that controls whether extra \x00 's should be stripped 153 | # for integer types collapse set to True means that the value should always be zero 154 | # Example: 155 | # ('itemName', ('formatString', collapse)) 156 | _dz_format_dict = OrderedDict([ 157 | ('header', ('4s', False)), # magic number 158 | ('formatMajor', ('I', False)), # always 2 in LE 159 | ('formatMinor', ('I', False)), # always 1 in LE 160 | ('reserved0', ('I', True)), # format patchlevel? 161 | ('device', ('32s', True)), 162 | ('version', ('144s', True)), # "factoryversion" 163 | ('chunkCount', ('I', False)), 164 | ('md5', ('16s', False)), # MD5 of chunk headers 165 | ('unknown0', ('I', False)), # 256? 166 | ('reserved1', ('I', True)), # currently always zero 167 | ('reserved4', ('H', True)), # currently always zero 168 | ('unknown1', ('16s', False)), # unknown, MD5 of thing? 169 | ('unknown2', ('50s', True)), # A##-M##-C##-U##-0 ? 170 | ('buildType', ('20s', True)), # "user"??? 171 | ('unknown3', ('4s', False)), # version code? CRC? 172 | ('androidVer', ('10s', True)), # Android ver, optional 173 | #anti-rollback minimum date? absent from Lollipop, "122142720" on all other V10 174 | ('oldDateCode', ('10s', True)), # prior firmware date? 175 | ('reserved5', ('I', False)), # currently always zero 176 | ('unknown4', ('I', False)), # sometimes 256? 177 | ('unknown5', ('I', False)), # ??? 178 | ('unknown6', ('64s', False)), # ??? 179 | ('unknown7', ('32s', False)), # ??? 180 | ('unknown8', ('8s', False)), # ??? 181 | ('pad', ('64s', True)), # currently always zero 182 | ]) 183 | 184 | def __init__(self): 185 | """ 186 | Initializer for DZFile, gets DZStruct to fill remaining values 187 | """ 188 | super(DZFile, self).__init__(DZFile) 189 | 190 | 191 | 192 | if __name__ == "__main__": 193 | print("Sorry, this file is an internal library and doesn't do anything interesting by", file=sys.stderr) 194 | print("itself.", file=sys.stderr) 195 | sys.exit(1) 196 | 197 | -------------------------------------------------------------------------------- /gpt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Copyright (C) 2016 Elliott Mitchell 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from __future__ import print_function 21 | import os 22 | import sys 23 | import io 24 | from collections import OrderedDict 25 | from struct import Struct 26 | from uuid import UUID 27 | from binascii import crc32 28 | 29 | 30 | verbose = lambda msg: None 31 | 32 | 33 | class NoGPT(Exception): 34 | def __init__(self, errmsg): 35 | self.errmsg = errmsg 36 | def __str__(self): 37 | return self.errmsg 38 | 39 | 40 | class GPTSlice(object): 41 | """ 42 | Class for handling invidual slices^Wpartitions of a GUID table 43 | """ 44 | 45 | _gpt_slice_length = 128 # this is *default*! 46 | 47 | # Format string dict 48 | # itemName is the new dict key for the data to be stored under 49 | # formatString is the Python formatstring for struct.unpack() 50 | # Example: 51 | # ('itemName', ('formatString')) 52 | _gpt_slice_fmt = OrderedDict([ 53 | ('type', ('16s')), 54 | ('uuid', ('16s')), 55 | ('startLBA', ('Q')), 56 | ('endLBA', ('Q')), 57 | ('flags', ('Q')), 58 | ('name', ('72s')) 59 | ]) 60 | 61 | # Generate the formatstring for struct.unpack() 62 | _gpt_struct = Struct("<" + "".join([x for x in _gpt_slice_fmt.values()])) 63 | 64 | def display(self, idx): 65 | """ 66 | Display the data for this slice of a GPT 67 | """ 68 | 69 | if self.type == UUID(int=0): 70 | verbose("Name: ") 71 | return None 72 | 73 | verbose("Name({:d}): \"{:s}\" start={:d} end={:d} count={:d}".format(idx, self.name, self.startLBA, self.endLBA, self.endLBA-self.startLBA+1)) 74 | verbose("typ={:s} id={:s}".format(str(self.type), str(self.uuid))) 75 | 76 | def __init__(self, buf): 77 | """ 78 | Initialize the GPTSlice class 79 | """ 80 | 81 | data = dict(zip( 82 | self._gpt_slice_fmt.keys(), 83 | self._gpt_struct.unpack(buf) 84 | )) 85 | 86 | self.type = UUID(bytes=data['type']) 87 | self.uuid = UUID(bytes=data['uuid']) 88 | self.startLBA = data['startLBA'] 89 | self.endLBA = data['endLBA'] 90 | self.flags = data['flags'] 91 | self.name = data['name'].decode("utf16").rstrip('\x00') 92 | 93 | 94 | 95 | class GPT(object): 96 | """ 97 | Class for handling of GUID Partition Tables 98 | (https://en.wikipedia.org/wiki/GUID_Partition_Table) 99 | """ 100 | 101 | _gpt_header = b"EFI PART" 102 | _gpt_size = 0x5C # default, can be overridden by headerSize 103 | 104 | # Format string dict 105 | # itemName is the new dict key for the data to be stored under 106 | # formatString is the Python formatstring for struct.unpack() 107 | # Example: 108 | # ('itemName', ('formatString')) 109 | _gpt_head_fmt = OrderedDict([ 110 | ('header', ('8s')), # magic number 111 | ('revision', ('I')), # actually 2 shorts, Struct... 112 | ('headerSize', ('I')), 113 | ('crc32', ('I')), 114 | ('reserved', ('I')), 115 | ('myLBA', ('Q')), 116 | ('altLBA', ('Q')), 117 | ('dataStartLBA',('Q')), 118 | ('dataEndLBA', ('Q')), 119 | ('uuid', ('16s')), 120 | ('entryStart', ('Q')), 121 | ('entryCount', ('I')), 122 | ('entrySize', ('I')), 123 | ('entryCrc32', ('I')), 124 | ]) 125 | 126 | # Generate the formatstring for struct.unpack() 127 | _gpt_struct = Struct("<" + "".join([x for x in _gpt_head_fmt.values()])) 128 | 129 | 130 | 131 | def display(self): 132 | """ 133 | Display the data in the particular GPT 134 | """ 135 | 136 | verbose("") 137 | 138 | verbose("block size is {:d} bytes (shift {:d})".format(1<>self.shiftLBA) 148 | if endEntry < self.dataStartLBA: 149 | verbose("Note: {:d} unused slice entry blocks before first usable block".format(self.dataStartLBA - endEntry)) 150 | else: 151 | if self.entryStart != self.dataEndLBA+1: 152 | verbose("Note: {:d} unused slice entry blocks after last usable block".format(self.entryStart-self.dataEndLBA-1)) 153 | endEntry = self.entryStart + ((self.entrySize * self.entryCount + (1<>self.shiftLBA) 154 | if endEntry < self.myLBA-1: 155 | verbose("Note: {:d} unused blocks between GPT header and entry table".format(self.myLBA-endEntry+1)) 156 | 157 | current = self.dataStartLBA 158 | idx = 1 159 | for slice in self.slices: 160 | if slice.type != UUID(int=0): 161 | if slice.startLBA != current: 162 | verbose("Note: non-contiguous ({:d} unused)".format(slice.startLBA-current)) 163 | current = slice.endLBA + 1 164 | slice.display(idx) 165 | idx += 1 166 | current-=1 167 | if self.dataEndLBA != current: 168 | verbose("Note: empty LBAs at end ({:d} unused)".format(self.dataEndLBA-current)) 169 | 170 | 171 | def tryParseHeader(self, buf): 172 | """ 173 | Try to parse a buffer as a GPT header, return None on failure 174 | """ 175 | 176 | if len(buf) < self._gpt_size: 177 | raise NoGPT("Failed to locate GPT") 178 | 179 | data = dict(zip( 180 | self._gpt_head_fmt.keys(), 181 | self._gpt_struct.unpack(buf[0:self._gpt_size]) 182 | )) 183 | 184 | if data['header'] != self._gpt_header: 185 | return None 186 | 187 | tmp = data['crc32'] 188 | data['crc32'] = 0 189 | 190 | crc = crc32(self._gpt_struct.pack(*[data[k] for k in self._gpt_head_fmt.keys()])) 191 | 192 | data['crc32'] = tmp 193 | 194 | # just in case future ones are larger 195 | crc = crc32(buf[self._gpt_size:data['headerSize']], crc) 196 | crc &= 0xFFFFFFFF 197 | 198 | if crc != data['crc32']: 199 | verbose("Warning: Found GPT candidate with bad CRC") 200 | return None 201 | 202 | return data 203 | 204 | 205 | 206 | def __init__(self, buf, type=None, lbaMinShift=9, lbaMaxShift=16): 207 | """ 208 | Initialize the GPT class 209 | """ 210 | 211 | # sanity checking 212 | if self._gpt_struct.size != self._gpt_size: 213 | raise NoGPT("GPT format string wrong!") 214 | 215 | # we assume we're searching, start with the bottom end 216 | shiftLBA = lbaMinShift 217 | lbaSize = 1<>16 != 1: 268 | raise NoGPT("Error: GPT major version isn't 1") 269 | elif self.revision&0xFFFF != 0: 270 | verbose("Warning: Newer GPT revision") 271 | 272 | # these tests were against our version and may well fail 273 | elif self.reserved != 0: 274 | verbose("Warning: Reserved area non-zero") 275 | 276 | # this is an error according to the specs 277 | if (self.myLBA != 1) and (self.altLBA != 1): 278 | raise NoGPT("Error: No GPT at LBA 1 ?!") 279 | 280 | # this is an error according to the specs 281 | if self.entrySize & (self.entrySize-1): 282 | raise NoGPT("Error: entry size is not a power of 2") 283 | 284 | if self.myLBA == 1: 285 | sliceAddr = self.entryStart< 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from __future__ import print_function 21 | import os 22 | import sys 23 | import subprocess 24 | import io 25 | import zlib 26 | import argparse 27 | import hashlib 28 | from collections import OrderedDict 29 | from binascii import crc32 30 | 31 | # our tools are in "libexec" 32 | sys.path.append(os.path.join(sys.path[0], "libexec")) 33 | 34 | import dz 35 | 36 | # compatibility, Python 3 has SEEK_HOLE/SEEK_DATA, Python 2 does not 37 | SEEK_HOLE = io.SEEK_HOLE if hasattr(io, "SEEK_HOLE") else 4 38 | SEEK_DATA = io.SEEK_DATA if hasattr(io, "SEEK_DATA") else 3 39 | 40 | class EXT4SparseChunk(dz.DZStruct): 41 | """ 42 | Class for handling chunk from Android sparse image format file 43 | """ 44 | 45 | # Known length of chunk 46 | _dz_length = 12 47 | 48 | # Doesn't include magic number, this should match output, I hope 49 | _dz_header = b"" 50 | 51 | # Format dictionary 52 | _dz_format_dict = OrderedDict([ 53 | ('header', ('0s', False)), # magic number (none) 54 | ('type', ('H', False)), # type value 55 | ('reserved1', ('H', True)), # reserved 56 | ('chunkCount', ('I', False)), # blocks in output 57 | ('totalSize', ('I', False)), # bytes of chunk input 58 | ]) 59 | 60 | # Chunk types 61 | typeRaw = 0xCAC1 62 | typeFill = 0xCAC2 63 | typeDontCare = 0xCAC3 64 | typeCrc32 = 0xCAC4 65 | 66 | def __init__(self, head, buf, pipe, blockShift, readSize): 67 | """ 68 | Initializer for EXT4SparseChunk, gets DZStruct to fill values 69 | """ 70 | super(EXT4SparseChunk, self).__init__(EXT4SparseChunk) 71 | 72 | values = self.unpackdict(buf[:self._dz_length]) 73 | 74 | self.type = values['type'] 75 | self.blocks = values['chunkCount'] 76 | 77 | self.remaining = self.blocks << blockShift 78 | 79 | self.readSize = readSize 80 | 81 | self.pipe = pipe 82 | 83 | self.head = head 84 | 85 | if self.type == self.typeRaw: 86 | # would such a case suggest zero-padding? 87 | if self.remaining != values['totalSize'] - len(buf): 88 | print("[!] Error encountered raw chunk with incorrect amount of payload", file=sys.stderr) 89 | sys.exit(64) 90 | elif self.type == self.typeFill: 91 | buf = self.pipe.read(values['totalSize'] - len(buf)) 92 | self.buffer = b"" 93 | while len(self.buffer) < readSize: 94 | self.buffer += buf 95 | 96 | def __del__(self): 97 | """ 98 | Destructor for EXT4SparseChunk, notably read remaining data, 99 | if any is left behind 100 | """ 101 | 102 | if self.type == self.typeRaw or self.type == self.typeFill: 103 | while self.remaining > 0: 104 | if self.remaining < (1< 0: 211 | print("[!] Warning: Output format from ext2simg more recent than this utility", file=sys.stderr) 212 | 213 | # Extra fields to ignore 214 | if values['headerSize'] > self._dz_length: 215 | self.child.stdout.read(values['headerSize'] - self._dz_length) 216 | 217 | # How large the chunks are 218 | self.chunkHSize = values['chunkHSize'] 219 | self.chunkCount = values['totalChunks'] 220 | 221 | # CRC32 of image 222 | self.origCrc = values['imageCRC32'] 223 | self.crc = crc32(b"") 224 | 225 | # Block size of the device, power of 2, minimum of 4K 226 | size = values['blockSize'] 227 | self.blockSize = size 228 | 229 | if size & (size-1): 230 | print("[!] Error: Block size specified in sparse header is not a power of 2", file=sys.stderr) 231 | sys.exit(1) 232 | 233 | result = 0 234 | shift = 32 235 | while shift > 0: 236 | if (size>>shift)>0: 237 | size>>=shift 238 | result+=shift 239 | shift>>=1 240 | self.blockShift = result 241 | 242 | def __del__(self): 243 | """ 244 | Destructor for Image2Chunks, notably kills child if needed 245 | """ 246 | 247 | if hasattr(self, 'child') and self.child != None: 248 | # normal exit 249 | if self.chunkCount <= 0: 250 | if self.child.poll() != None: 251 | if self.child.returncode > 0: 252 | print("[!] Warning: Return code of {:d} from ext2simg!".format(self.child.returncode), file=sys.stderr) 253 | elif self.child.returncode < 0: 254 | print("[!] Warning ext2simg killed by signal {:d}!".format(-self.child.returncode), file=sys.stderr) 255 | # otherwise 0 exit, no problem 256 | 257 | # hasn't terminated, trouble... 258 | else: 259 | self.child.terminate() 260 | if self.child.wait(10) == None: 261 | self.child.kill() 262 | print("[!] Warning forced to terminate ext2simg", file=sys.stderr) 263 | # abnormal condition of some sort 264 | else: 265 | self.child.terminate() 266 | if self.child.wait(10) == None: 267 | self.child.kill() 268 | 269 | def __iter__(self): 270 | """ 271 | The __iter__ method for doing loops 272 | """ 273 | return self 274 | 275 | def __next__(self): 276 | """ 277 | Retrieve the next parsed chunk 278 | """ 279 | 280 | try: 281 | # The previous chunk object *MUST* be destroyed *NOW* 282 | self.last.__del__() 283 | except AttributeError: 284 | # doesn't exist yet 285 | pass 286 | 287 | if self.chunkCount <= 0: 288 | self.crc = self.crc & 0xFFFFFFFF 289 | # apparently 0 is used for none computed 290 | if self.crc != self.origCrc and self.origCrc != 0: 291 | print("[!] CRC mismatch, computed={:08X}, original={:08X}".format(self.crc, self.origCrc)) 292 | sys.exit(4) 293 | 294 | raise StopIteration 295 | 296 | self.chunkCount -= 1 297 | 298 | buf = self.child.stdout.read(self.chunkHSize) 299 | 300 | if len(buf) != self.chunkHSize: 301 | print("[!] Attempting to read chunk header got short read", file=sys.stderr) 302 | sys.exit(2) 303 | 304 | self.last = EXT4SparseChunk(self, buf, self.child.stdout, self.blockShift, self.readSize) 305 | return self.last 306 | 307 | # Python 2 compatibility 308 | next = __next__ 309 | 310 | 311 | class Image2Chunks(dz.DZChunk): 312 | """ 313 | Class for transforming a single file from a raw image into chunk files 314 | """ 315 | 316 | def openFiles(self, name): 317 | """ 318 | Opens the files, provide an error message if one doesn't exist 319 | """ 320 | 321 | try: 322 | role = "parameter" 323 | self.paramsFile = io.open(name + ".params", "rt") 324 | role = "image" 325 | self.file = io.FileIO(name, "rb") 326 | 327 | except IOError: 328 | print("[!] Failed opening {:s} file for {:s}".format(role, name), file=sys.stderr) 329 | sys.exit(1) 330 | 331 | 332 | 333 | 334 | def loadParams(self, name): 335 | """ 336 | Loads the .params file for image, saves off key values 337 | """ 338 | 339 | 340 | params = dict() 341 | line = self.paramsFile.readline() 342 | while len(line) > 0: 343 | line.lstrip() 344 | line = line.partition("#")[0] 345 | if len(line) == 0: 346 | line = self.paramsFile.readline() 347 | continue 348 | parts = line.partition("=") 349 | if len(parts[1]) == 0: 350 | print("[!] Bad line in {:s}'s parameter file".format(name), file=sys.stderr) 351 | var = parts[0].rstrip() 352 | # currently we only have integers in the file 353 | val = int(parts[2].strip()) 354 | params[var] = val 355 | line = self.paramsFile.readline() 356 | 357 | self.paramsFile.close() 358 | del self.paramsFile 359 | 360 | if 'phantom' in params and params['phantom']: 361 | print("[!] {:s} is a phantom slice, skipping!".format(name)) 362 | return False 363 | 364 | for k in 'blockShift', 'startLBA', 'endLBA', 'lastWipe', 'dev': 365 | if k not in params: 366 | print("Parameter value \"{:s}\" is missing, unable to continue".format(k)) 367 | sys.exit(1) 368 | 369 | self.blockShift = params['blockShift'] 370 | self.blockSize = 1 << self.blockShift 371 | self.startLBA = params['startLBA'] 372 | self.endLBA = params['endLBA'] 373 | self.lastWipe = params['lastWipe'] 374 | self.dev = params['dev'] 375 | 376 | return True 377 | 378 | 379 | def makeChunksHoles(self, name): 380 | """ 381 | Generate one or more .chunks files for the named file 382 | """ 383 | 384 | os.chdir(os.path.dirname(name)) 385 | name = os.path.basename(name) 386 | baseName = name.rpartition(".")[0] + "_" 387 | sliceName = name.rpartition(".")[0].encode("utf8") 388 | 389 | current = 0 390 | targetAddr = self.startLBA 391 | eof = self.file.seek(0, io.SEEK_END) 392 | self.file.seek(0, io.SEEK_SET) 393 | 394 | while current < eof: 395 | hole = (self.file.seek(current, SEEK_HOLE) + self.blockSize-1) & ~(self.blockSize-1) 396 | # Python's handling of this condition is suboptimal 397 | try: 398 | next = self.file.seek(hole, SEEK_DATA) & ~(self.blockSize-1) 399 | trimCount = (next - current) >> self.blockShift 400 | except IOError: 401 | next = eof 402 | trimCount = self.lastWipe - targetAddr 403 | 404 | # Watch out for chunks >4GB (too big!) 405 | # Also, try not to test the limits of LG's tools... 406 | if (hole - current) >= 1<<27: 407 | hole = current + (1<<27) 408 | next = hole 409 | trimCount = (next - current) >> self.blockShift 410 | 411 | md5 = hashlib.md5() 412 | crc = crc32(b"") 413 | zobj = zlib.compressobj(1) 414 | self.file.seek(current, io.SEEK_SET) 415 | 416 | chunkName = baseName + str(targetAddr) + ".bin" 417 | out = io.FileIO(chunkName + ".chunk", "wb") 418 | 419 | print("[+] Compressing {:s} to {:s} ({:d} empty blocks)".format(name, chunkName, (next - hole) >> self.blockShift)) 420 | 421 | chunkName = chunkName.encode("utf8") 422 | out.seek(self._dz_length, io.SEEK_SET) 423 | zlen = 0 424 | 425 | for b in range((hole - current) >> self.blockShift): 426 | buf = self.file.read(self.blockSize) 427 | md5.update(buf) 428 | crc = crc32(buf, crc) 429 | zdata = zobj.compress(buf) 430 | zlen += len(zdata) 431 | out.write(zdata) 432 | 433 | zdata = zobj.flush(zlib.Z_FINISH) 434 | zlen += len(zdata) 435 | out.write(zdata) 436 | md5 = md5.digest() 437 | 438 | 439 | out.seek(0, io.SEEK_SET) 440 | 441 | values = { 442 | 'sliceName': sliceName, 443 | 'chunkName': chunkName, 444 | 'targetSize': hole - current, 445 | 'dataSize': zlen, 446 | 'md5': md5, 447 | 'targetAddr': targetAddr, 448 | 'trimCount': trimCount, 449 | 'crc32': crc & 0xFFFFFFFF, 450 | 'dev': self.dev, 451 | } 452 | 453 | header = self.packdict(values) 454 | out.write(header) 455 | out.close() 456 | 457 | current = next 458 | targetAddr = self.startLBA + (current >> self.blockShift) 459 | 460 | print("[+] done\n") 461 | 462 | 463 | def makeChunksEXT4FS(self, name): 464 | """ 465 | Generate one or more .chunks files assuming an EXT4 FS 466 | """ 467 | 468 | os.chdir(os.path.dirname(name)) 469 | name = os.path.basename(name) 470 | baseName = name.rpartition(".")[0] + "_" 471 | sliceName = name.rpartition(".")[0].encode("utf8") 472 | 473 | # Check length before closing 474 | eof = self.file.seek(0, io.SEEK_END) 475 | 476 | # Alas, ext2simg can't take the image as stdin 477 | self.file.close() 478 | 479 | sparse = EXT4SparseFile(name, 1<> self.blockShift) 517 | 518 | print("({:d} empty blocks)".format(nl.trimCount - dataBlocks)) 519 | 520 | nl.trimCount = 0 521 | 522 | for chunk in sparse: 523 | if chunk.type == EXT4SparseChunk.typeRaw or chunk.type == EXT4SparseChunk.typeFill: 524 | if nl.trimCount: 525 | complete() 526 | dataBlocks = chunk.remaining >> self.blockShift 527 | nl.trimCount += dataBlocks 528 | 529 | nl.md5 = hashlib.md5() 530 | crc = crc32(b"") 531 | zobj = zlib.compressobj(1) 532 | 533 | chunkName = baseName + str(nl.targetAddr) + ".bin" 534 | out = io.FileIO(chunkName + ".chunk", "wb") 535 | 536 | sys.stdout.write("[+] Compressing {:s} to {:s} ".format(name, chunkName)) 537 | 538 | chunkName = chunkName.encode("utf8") 539 | out.seek(self._dz_length, io.SEEK_SET) 540 | nl.zlen = 0 541 | 542 | for buf in chunk: 543 | nl.md5.update(buf) 544 | crc = crc32(buf, crc) 545 | zdata = zobj.compress(buf) 546 | nl.zlen += len(zdata) 547 | out.write(zdata) 548 | 549 | elif chunk.type == EXT4SparseChunk.typeDontCare: 550 | # check for EOF, lastWipe overrides 551 | if sparse.chunkCount == 0: 552 | nl.trimCount = self.lastWipe - nl.targetAddr 553 | else: 554 | nl.trimCount += chunk.remaining>>self.blockShift 555 | complete() 556 | 557 | elif chunk.type == EXT4SparseChunk.typeCrc32: 558 | pass 559 | else: 560 | print("[!] Error: unknown chunk, type=0x{:04X}".format(chunk.type), file=sys.stderr) 561 | sys.exit(64) 562 | 563 | if nl.trimCount: 564 | nl.trimCount = self.lastWipe - nl.targetAddr 565 | complete() 566 | 567 | print("[+] done\n") 568 | 569 | 570 | def makeChunksProbe(self, name): 571 | """ 572 | Generate one or more .chunks files for the named file 573 | """ 574 | 575 | os.chdir(os.path.dirname(name)) 576 | name = os.path.basename(name) 577 | baseName = name.rpartition(".")[0] + "_" 578 | sliceName = name.rpartition(".")[0].encode("utf8") 579 | 580 | current = 0 581 | targetAddr = self.startLBA 582 | eof = self.file.seek(0, io.SEEK_END) 583 | self.file.seek(0, io.SEEK_SET) 584 | 585 | 586 | 587 | # emulate characteristics of LG's tool, always find a block at start 588 | readSize = self.blockSize << 10 589 | 590 | md5 = hashlib.md5() 591 | crc = crc32(b"") 592 | zobj = zlib.compressobj(1) 593 | self.file.seek(current, io.SEEK_SET) 594 | 595 | chunkName = baseName + str(targetAddr) + ".bin" 596 | out = io.FileIO(chunkName + ".chunk", "wb") 597 | 598 | sys.stdout.write("[+] Compressing {:s} to {:s} ".format(name, chunkName)) 599 | 600 | chunkName = chunkName.encode("utf8") 601 | out.seek(self._dz_length, io.SEEK_SET) 602 | zlen = 0 603 | 604 | wipeData = readSize 605 | dataCount = readSize 606 | 607 | 608 | 609 | buf = self.file.read(readSize) 610 | 611 | while len(buf.lstrip(b'\x00')) == 0 and current < eof: 612 | md5.update(buf) 613 | crc = crc32(buf, crc) 614 | zdata = zobj.compress(buf) 615 | zlen += len(zdata) 616 | out.write(zdata) 617 | wipeData += readSize 618 | dataCount += readSize 619 | current += readSize 620 | 621 | buf = self.file.read(readSize) 622 | 623 | 624 | 625 | while current < eof: 626 | 627 | 628 | 629 | 630 | while len(buf.lstrip(b'\x00')) != 0 and current < eof: 631 | md5.update(buf) 632 | crc = crc32(buf, crc) 633 | zdata = zobj.compress(buf) 634 | zlen += len(zdata) 635 | out.write(zdata) 636 | wipeData += readSize 637 | dataCount += readSize 638 | current += readSize 639 | 640 | buf = self.file.read(readSize) 641 | 642 | zdata = zobj.flush(zlib.Z_FINISH) 643 | zlen += len(zdata) 644 | out.write(zdata) 645 | md5 = md5.digest() 646 | 647 | 648 | 649 | while len(buf.lstrip(b'\x00')) == 0 and current < eof: 650 | wipeData += readSize 651 | current += readSize 652 | buf = self.file.read(readSize) 653 | 654 | 655 | print("({:d} empty blocks)".format((wipeData - dataCount) >> self.blockShift)) 656 | 657 | 658 | 659 | 660 | 661 | out.seek(0, io.SEEK_SET) 662 | 663 | values = { 664 | 'sliceName': sliceName, 665 | 'chunkName': chunkName, 666 | 'targetSize': dataCount, 667 | 'dataSize': zlen, 668 | 'md5': md5, 669 | 'targetAddr': targetAddr, 670 | 'trimCount': wipeData >> self.blockShift, 671 | 'crc32': crc & 0xFFFFFFFF, 672 | 'dev': self.dev, 673 | } 674 | 675 | header = self.packdict(values) 676 | out.write(header) 677 | out.close() 678 | 679 | 680 | 681 | 682 | targetAddr = self.startLBA + (current >> self.blockShift) 683 | 684 | 685 | if current < eof: 686 | 687 | 688 | md5 = hashlib.md5() 689 | crc = crc32(b"") 690 | zobj = zlib.compressobj(1) 691 | 692 | chunkName = baseName + str(targetAddr) + ".bin" 693 | out = io.FileIO(chunkName + ".chunk", "wb") 694 | 695 | sys.stdout.write("[+] Compressing {:s} to {:s} ".format(name, chunkName)) 696 | 697 | chunkName = chunkName.encode("utf8") 698 | out.seek(self._dz_length, io.SEEK_SET) 699 | zlen = 0 700 | 701 | wipeData = readSize 702 | dataCount = readSize 703 | 704 | 705 | 706 | 707 | 708 | 709 | print("[+] done\n") 710 | 711 | 712 | def __init__(self, name, strategy): 713 | """ 714 | Initializer for Image2Chunks class, takes filename as arg 715 | """ 716 | 717 | super(Image2Chunks, self).__init__() 718 | 719 | self.openFiles(name) 720 | 721 | if self.loadParams(name): 722 | if strategy == 0: 723 | self.makeChunksEXT4FS(name) 724 | elif strategy == 1: 725 | self.makeChunksHoles(name) 726 | elif strategy == 2: 727 | self.makeChunksProbe(name) 728 | elif strategy == None: 729 | print("[!] No strategy specified, one *must* be specified before filename(s)", file=sys.stderr) 730 | sys.exit(1) 731 | else: 732 | print("[!] Internal error!", file=sys.stderr) 733 | sys.exit(127) 734 | 735 | 736 | def help(progname): 737 | print("usage: {:s} [-h | --help] [-e | --ext4 | -s | --sparse | -p | --probe] \n".format(progname)) 738 | print("DZ Chunking program by Elliott Mitchell\n") 739 | print("optional arguments:") 740 | print(" -h | --help show this help message and exit") 741 | print(" -e | --ext4 use Android's sparse EXT4 dump utility (recommended)") 742 | print(" -s | --sparse use SEEK_DATA/SEEK_HOLE (not available on all OSes)") 743 | print(" -p | --probe probe for holes (safe)") 744 | sys.exit(0) 745 | 746 | 747 | if __name__ == "__main__": 748 | 749 | progname = sys.argv[0] 750 | del sys.argv[0] 751 | 752 | basedir = os.open(".", os.O_DIRECTORY) 753 | 754 | # no default strategy, ext2simg is reasonable, but worrisome if non-FS 755 | strategy = None 756 | 757 | if len(sys.argv) <= 0: 758 | help(progname) 759 | 760 | for arg in sys.argv: 761 | 762 | if arg[0] == "-": 763 | if arg == "-e" or arg == "--ext4": 764 | strategy = 0 765 | elif arg == "-s" or arg == "--sparse": 766 | strategy = 1 767 | elif arg == "-p" or arg == "--probe": 768 | strategy = 2 769 | elif arg == "-h" or arg == "--help": 770 | help(progname) 771 | 772 | elif arg[0] == "-": 773 | print('[!] Unknown option "{:s}"'.format(arg)) 774 | sys.exit(1) 775 | 776 | continue 777 | 778 | Image2Chunks(arg, strategy) 779 | 780 | os.fchdir(basedir) 781 | 782 | -------------------------------------------------------------------------------- /install_requirements.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | ''' 4 | 执行此文件将自动安装requirements.txt中的依赖 5 | ''' 6 | import os,platform 7 | f=open('requirements.txt') 8 | for i in f: 9 | if i[0]=='#': 10 | pass 11 | else: 12 | os.system('pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple '+i) 13 | f.close() 14 | if platform.system()=='Linux':print('请手动安装brotli') 15 | 16 | -------------------------------------------------------------------------------- /kdz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Copyright (C) 2016 Elliott Mitchell 5 | Copyright (C) 2013 IOMonster (thecubed on XDA) 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | """ 20 | 21 | from __future__ import print_function 22 | import sys 23 | from struct import Struct 24 | from collections import OrderedDict 25 | import dz 26 | 27 | 28 | class KDZFile(dz.DZStruct): 29 | """ 30 | LGE KDZ File tools 31 | """ 32 | 33 | 34 | _dz_length = 272 35 | _dz_header = b"\x28\x05\x00\x00"b"\x24\x38\x22\x25" 36 | 37 | # Format string dict 38 | # itemName is the new dict key for the data to be stored under 39 | # formatString is the Python formatstring for struct.unpack() 40 | # collapse is boolean that controls whether extra \x00 's should be stripped 41 | # Example: 42 | # ('itemName', ('formatString', collapse)) 43 | _dz_format_dict = OrderedDict([ 44 | ('name', ('256s', True)), 45 | ('length', ('Q', False)), 46 | ('offset', ('Q', False)), 47 | ]) 48 | 49 | 50 | def __init__(self): 51 | """ 52 | Initializer for KDZFile, gets DZStruct to fill remaining values 53 | """ 54 | super(KDZFile, self).__init__(KDZFile) 55 | 56 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # adbshellpy_libunpakrom 4 | # By : AEnjoy 5 | # Version : 2.2.5 6 | # last Update: 2024-7-7 17:38:01 7 | try:import sys,os,zipfile,urllib.request,tarfile,argparse,platform,lz4.frame,glob2,undz,unkdz,sdat2img,payload_dumper,simg2img 8 | except ImportError: 9 | print('E:请执行install_requirements.py后再执行main!') 10 | input('按Enter键退出') 11 | exit(1) 12 | ozipmodelerror=0 13 | try:import ozipdecrypt 14 | except ImportError: 15 | print('W:pycrypto依赖未安装,oppo ozip解包将不可用') 16 | ozipmodelerror=1 17 | 18 | quiet=0 #0询问 1安静 19 | 20 | def get_saminfo(filename='AP_G9650ZCS6CSK2_CL17051143_QB26966166_REV01_user_low_ship_MULTI_CERT_meta_OS9.tar.md5',l=-1): 21 | """ 22 | get samsumg rom info 23 | :return: last line or None for empty file 24 | """ 25 | try: 26 | filesize = os.path.getsize(filename) 27 | if filesize == 0: 28 | return None 29 | else: 30 | with open(filename, 'rb') as fp: 31 | offset = -8 32 | while -offset < filesize: 33 | fp.seek(offset, 2) 34 | lines = fp.readlines() 35 | if len(lines) >= 2: 36 | return lines[l] 37 | else: 38 | offset *= 2 39 | fp.seek(0) 40 | lines = fp.readlines() 41 | print('b') 42 | return lines[l] 43 | except FileNotFoundError: 44 | print(filename + ' 未找到!') 45 | return None 46 | class rominformation: 47 | ''' 48 | brotli=None 49 | newdat=None 50 | olnyimg=None 51 | onlyfolder=None 52 | ozip=None 53 | androidVersion= 54 | flag: 55 | 1.无效文件路径 56 | 2.不支持格式 57 | 3.线刷包找到 58 | 4.卡刷包找到 ROM 59 | 5.卡刷包找到 flashable 60 | ''' 61 | type=None #1功能性卡刷包(如opengapps) 2ROM卡刷包 3线刷包 62 | flag=0 63 | olnyimg,onlyfolder,brotil,newdat,samsumgodinfile,ozip,lgkd,lgkdz,abflag,miuitar=False,False,False,False,False,False,False,False,False,False 64 | def __init__(self,file=''): 65 | print('正在处理ROM信息...该过程需要1s-2min分钟不等') 66 | '''获取ROM信息 输入的文件可以是线刷包,也可以是卡刷包''' 67 | size=os.path.getsize(file) 68 | if os.path.exists(file)==False or size==0: 69 | print('E:请选择一个正确的文件!!!') 70 | self.flag=1#无效文件路径 71 | return 72 | self.file=file 73 | if file.find('payload.bin')>-1: 74 | self.abflag=True 75 | self.flag=3 76 | print('发现A/B(System As Root)更新文件(安卓10动态分区)') 77 | return 78 | if file.find('.ozip') > -1 and zipfile.is_zipfile(file)==True: 79 | with open(file,'rb') as fr: 80 | magic=fr.read(12) 81 | if magic==b"OPPOENCRYPT!" or magic[:2]==b"PK": 82 | self.ozip=True 83 | print('发现OPPO OZIP! 需要解密后才能读取ROM信息') 84 | fr.close() 85 | return 86 | print('这个ROM可能不是OPPO OZIP?!') 87 | if file.find('.tar.md5') > -1 and tarfile.is_tarfile(file): 88 | self.samsumgodinfile=True 89 | a=str(get_saminfo(file)) 90 | if a: 91 | a=a.replace("b'",'') 92 | a=a.replace(".tar\\n'",'') 93 | li=a.split(' ') 94 | a=li[2].split('_') 95 | print('ROM类型:'+a[0]+'\n版本:'+a[1]+'\n发行标志:'+a[5]+'\n固件类型:offical') 96 | print('发现三星odin线刷文件!') 97 | print('W:只有ROM类型为AP才支持解包出系统镜像') 98 | return 99 | print('Maybe:发现三星odin线刷文件?!') 100 | if file.find('.tgz') > -1 and tarfile.is_tarfile(file): 101 | #MIUI 102 | tar = tarfile.open(file, "r:gz") 103 | l=tar.getnames() 104 | for a in l: 105 | if a.find('system.img')>-1 : 106 | self.flag=3 107 | self.miuitar=True 108 | print('Maybe:MIUI 线刷包找到') 109 | return 110 | elif a.find('super.img')>-1: 111 | self.flag=3 112 | self.miuitar=True 113 | self.super=True 114 | print('Maybe:MIUI 线刷包找到') 115 | return 116 | if zipfile.is_zipfile(file)==False: 117 | print('E:不支持的格式!!!!') 118 | self.flag=2 119 | return 120 | self.file=file 121 | z=zipfile.ZipFile(file) 122 | self.l=z.namelist() 123 | self.flag=4 124 | #z.close() 125 | if 'system.img' in self.l: 126 | self.olnyimg=True 127 | 128 | if 'system/framework/framework.jar' in self.l: 129 | self.onlyfolder=True 130 | 131 | if 'system.new.dat.br' in self.l and 'system.transfer.list' in self.l: 132 | self.brotil=True 133 | 134 | if 'system.new.dat' in self.l and 'system.transfer.list' in self.l: 135 | self.newdat=True 136 | 137 | if 'system.transfer.list' in self.l: 138 | z.extract('system.transfer.list') 139 | f = open('system.transfer.list', 'r') 140 | v = int(f.readline()) 141 | f.close() 142 | if v == 1: 143 | print('Android Lollipop 5.0 检测到!\n') 144 | self.androidVersion='Lollipop 5.0 API 21' 145 | elif v == 2: 146 | print('Android Lollipop 5.1 检测到!\n') 147 | self.androidVersion='Lollipop 5.1 API 22' 148 | elif v == 3: 149 | print('Android Marshmallow 6.x 检测到!\n') 150 | self.androidVersion='Marshmallow 6.x API 23' 151 | elif v == 4: 152 | print('Android Nougat 7.x / Oreo 8.x 或更高版本检测到!\n') 153 | self.androidVersion='Nougat 7.x or higher API 24+' 154 | os.remove('system.transfer.list') 155 | if 'payload.bin' in self.l: 156 | self.abflag=True 157 | self.flag=4 158 | print('发现A/B(System As Root)更新文件(安卓10动态分区)') 159 | 160 | if 'META-INF/com/android/metadata' in self.l: 161 | z.extract('META-INF/com/android/metadata') 162 | f=open('META-INF/com/android/metadata', encoding='UTF-8') 163 | l=[] 164 | for i in f:l.append(i.strip()) 165 | f.close() 166 | os.remove('META-INF/com/android/metadata') 167 | for i in l: 168 | x=i.split('=') 169 | if x[0]=='post-build': 170 | text=x[1] 171 | self.info=text.split('/') 172 | if len(self.info)==6: 173 | print('ROM制造商:'+self.info[0]+'\n手机代号:'+self.info[1]+'\n版本:'+self.info[2]+'\nAndroid开发版本:'+self.info[3]+'\n固件版本:'+self.info[4]+'\n发行标志:'+self.info[5]) 174 | z.close() 175 | return 176 | else: 177 | print('您的设备指纹可能已经被修改,无法获取ROM信息!!!') 178 | else: 179 | print('metadata文件不存在?!') 180 | z.close() 181 | 182 | 183 | for names in self.l:#prop获取Android版本 184 | if names.find('build.prop') > -1: 185 | try:z.extract(names) 186 | except:pass 187 | if os.path.exists(names): 188 | f=open(names, encoding='UTF-8') 189 | l=[] 190 | for i in f:l.append(i.strip()) 191 | f.close() 192 | os.remove(names) 193 | for i in l: 194 | x=i.split('=') 195 | if x[0]=='ro.build.fingerprint':#Android 指纹库 196 | text=x[1] 197 | self.info=text.split('/') 198 | if len(self.info)==6: 199 | print('ROM制造商:'+self.info[0]+'\n手机代号:'+self.info[1]+'\n版本:'+self.info[2]+'\nAndroid开发版本:'+self.info[3]+'\n固件版本:'+self.info[4]+'\n发行标志:'+self.info[5]) 200 | z.close() 201 | return 202 | else: 203 | print('您的设备指纹可能已经被修改,无法获取ROM信息!!!') 204 | 205 | if 'META-INF/com/google/android/updater-script' in self.l: 206 | z.extract('META-INF/com/google/android/updater-script') 207 | f=open('META-INF/com/google/android/updater-script', encoding='UTF-8') 208 | l=[] 209 | for i in f:l.append(i.strip()) 210 | f.close() 211 | os.remove('META-INF/com/google/android/updater-script') 212 | for i in l: 213 | if 'ui_print("Target:' in i: 214 | i=i.replace('ui_print("Target:','') 215 | i=i.replace('");','') 216 | i=i.replace(' ','') 217 | self.info=i.split('/') 218 | if len(self.info)==6: 219 | print('ROM制造商:'+self.info[0]+'\n手机代号:'+self.info[1]+'\n版本:'+self.info[2]+'\nAndroid开发版本:'+self.info[3]+'\n固件版本:'+self.info[4]+'\n发行标志:'+self.info[5]) 220 | z.close() 221 | return 222 | if (i.find('update-binary') > -1 and i.find('ummy') > -1) or i.find('#MAGISK') > -1: 223 | self.flag=5 224 | print('发现该压缩包为功能性卡刷包!(Magisk/oepngapps/ak2/ak3/etc.') 225 | z.close() 226 | return 227 | print('W:无法从updater-script获取ROM信息!!') 228 | if zipfile.is_zipfile(file)==False: 229 | if file.find('.kdz') > -1: 230 | print('Maybe:发现LG .kdz文件!\n正在测试是否为 .kdz文件...') 231 | if lg_kd_kdz(file).islgkdzfile(): 232 | self.lgkdz=True 233 | self.flag=3 234 | self.type=3 235 | print('发现LG .kdz文件!') 236 | return 237 | else: 238 | print('这个文件可能不是LG .kdz文件?') 239 | self.flag=2 240 | return 241 | if file.find('.dz') > -1: 242 | print('Maybe:发现LG .dz文件!\n正在测试是否为 .dz文件...') 243 | if lg_kd_kdz(file).islgdzfile(): 244 | self.lgkd=True 245 | self.flag=3 246 | self.type=3 247 | print('发现LG .dz文件!') 248 | else: 249 | print('这个文件可能不是LG .dz文件?') 250 | self.flag=2 251 | return 252 | print('无效不可读格式?') 253 | self.flag=2 254 | return 255 | z.close() 256 | 257 | class lg_kd_kdz(): 258 | def __init__(self,file): 259 | """ 260 | GitHub Paper:https://github.com/randomstuffpaul/kdztools 261 | Copyright (C) 2016 Elliott Mitchell 262 | Copyright (C) 2013 IOMonster (thecubed on XDA) 263 | This program is free software: you can redistribute it and/or modify 264 | it under the terms of the GNU General Public License as published by 265 | the Free Software Foundation, either version 3 of the License, or 266 | (at your option) any later version. 267 | This program is distributed in the hope that it will be useful, 268 | but WITHOUT ANY WARRANTY; without even the implied warranty of 269 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 270 | GNU General Public License for more details. 271 | You should have received a copy of the GNU General Public License 272 | along with this program. If not, see . 273 | file 输入的文件(kdz/kd) 274 | """ 275 | if os.path.exists(file)==False: 276 | print('E:无效文件路径!') 277 | return 278 | self.file=file 279 | def islgkdzfile(self): 280 | kdz=unkdz.KDZFileTools() 281 | kdz.kdzfile=self.file 282 | try: 283 | kdz.openFile(self.file) 284 | l=kdz.getPartitions() 285 | if len(l) !=0:return True 286 | else:return False 287 | except:return False 288 | 289 | def islgdzfile(self): 290 | dz=undz.DZFileTools() 291 | try: 292 | dz.dz_file=undz.UNDZFile(self.file) 293 | dz.cmdListPartitions() 294 | return True 295 | except:return False 296 | #dz. 297 | def unpackkdz(self):#all 298 | kdz=unkdz.KDZFileTools() 299 | kdz.kdzfile=self.file 300 | kdz.openFile(self.file) 301 | kdz.outdir=os.getcwd() 302 | kdz.partList=kdz.getPartitions() 303 | kdz.cmdExtractAll() 304 | def listkdz(self):#all 305 | kdz=unkdz.KDZFileTools() 306 | kdz.kdzfile=self.file 307 | kdz.openFile(self.file) 308 | kdz.cmdListPartitions() 309 | def unpackdz(self,mode=1): 310 | ''' 311 | mode: 312 | 1 USE COMMAND TO EXTRACT 313 | 2 USE PYTHON FUNCTION 314 | ''' 315 | if mode==2: 316 | dz=undz.DZFileTools() 317 | dz.mainbyclass(self.file,'rom') 318 | elif mode==1: 319 | os.system('python undz.py -f %s -i -d rom'%self.file) 320 | 321 | class unpackrom(): 322 | global quiet 323 | file='' 324 | unpacktodir=1 325 | def __init__(self,file,rominfo,unpacktodir=1,check=0): 326 | '''file:inputfile unpacktodir 0/1 0:Only run onec ;1 only to system dir check:lib 0/1''' 327 | self.rominfo=rominfo 328 | if file!='':self.file=rominfo.file 329 | else:self.file=file 330 | self.unpacktodir=unpacktodir 331 | if check==1: 332 | print('正在安装依赖...') 333 | import install_requirements 334 | a=input('ROM解析完成.是否解包?y/n>>>') 335 | if a=='y': 336 | if rominfo.abflag==True and zipfile.is_zipfile(self.file)==True:self.unzip() 337 | if rominfo.miuitar==True:self.miui_tar() 338 | if rominfo.samsumgodinfile==True:self.samsumg_tar() 339 | if rominfo.lgkdz==True:self.lg_kdz() 340 | if rominfo.lgkd==True:self.lg_dz() 341 | if rominfo.flag==5 or rominfo.flag==4:self.unzip() 342 | if rominfo.ozip==True:self.oppo_ozip() 343 | if rominfo.abflag==True:self.abunpack()#.bin 344 | elif a=='n':print('操作已取消') 345 | def miui_tar(self): 346 | if quiet==0: 347 | if input('是否解包XiaomiTarFile?y/n>>>')=='n':return 348 | tar=tarfile.open(self.file) 349 | tar.extractall(path='rom') 350 | tar.close() 351 | print('Done.') 352 | items = os.listdir('rom') 353 | dir='rom/' 354 | if len(items)==1: 355 | dir=dir+items[0]+'/' 356 | items = os.listdir(dir) 357 | for i in items: 358 | if i.find('images')>-1: 359 | dir=dir+i+'/' 360 | if os.path.exists(dir+'super.img'): 361 | print('MIUI:发现线刷包动态分区镜像.') 362 | imgfile = dir+'super.img' 363 | if os.path.exists(dir+'system.img'): 364 | imgfile = dir+'system.img' 365 | self.file=imgfile 366 | self.imgunpack() 367 | 368 | def samsumg_tar(self): 369 | if quiet==0: 370 | if input('是否解包SamsungTarFile?y/n>>>')=='n':return 371 | tar=tarfile.open(self.file) 372 | tar.extractall(path='rom') 373 | tar.close() 374 | #lz4File 375 | lz4file = glob2.glob('rom/*.lz4') 376 | for a in lz4file: 377 | #a:input_file b:output_file char 378 | b=a.replace('.img.lz4','.img') 379 | with open(a, 'rb') as infile: 380 | data=infile.read() 381 | newdata = lz4.frame.decompress(data) 382 | outfile = open(b, 'wb') 383 | outfile.write(newdata) 384 | outfile.close() 385 | infile.close() 386 | if os.path.exists('rom/super.img') and self.unpacktodir==1: 387 | print('W:动态分区解包有待测试') 388 | self.file='rom/super.img' 389 | self.imgunpack() 390 | if os.path.exists('rom/system.img') and self.unpacktodir==1: 391 | self.file='rom/system.img' 392 | self.imgunpack() 393 | if os.path.exists('rom/system.img.ext4') and self.unpacktodir==1: 394 | self.file='rom/system.img.ext4' 395 | self.imgunpack() 396 | def lg_kdz(self): 397 | if quiet==0: 398 | if input('是否解包LG KDZ?y/n>>>')=='n':return 399 | print('当a,b同时为y时,将列出kdz文件分区表并解包,否则当a=y,b=n时,将仅列出kdz内文件列表') 400 | a=input('a:是否仅列出.kdz分区列表?(默认n)y/n') 401 | b=input('b:是否解包kdz全部文件(默认y)?y/n') 402 | c=input('c:欲解包的分区序号:(默认全部Enter)[暂不可用]') 403 | elif quiet==1: 404 | a,b='y','y' 405 | if a=='y' and b=='y':lg_kd_kdz(self.file).unpackkdz() 406 | elif a=='y' and b=='n':lg_kd_kdz(self.file).listkdz() 407 | def lg_dz(self): 408 | if quiet==0: 409 | if input('是否解包LG DZ?y/n>>>')=='n':return 410 | a=int(input('请选择解包模式:1.command 2.func:')) 411 | elif quiet==1: 412 | a=1 413 | lg_kd_kdz(self.file).unpackdz(a) 414 | 415 | def oppo_ozip(self): 416 | global ozipmodelerror 417 | if quiet==0: 418 | if input('是否解密oppo ozip?y/n>>>')=='n':return 419 | if ozipmodelerror==1: 420 | print(''' 421 | E:pycrypto模块错误!不支持oppo ozip解包! 422 | 有关pycrypto安装信息,请浏览: 423 | https://github.com/AEnjoy/unpackandroidrom/blob/master/about_pycrypto.md 424 | https://hub.fastgit.org/AEnjoy/unpackandroidrom/blob/master/about_pycrypto.md 425 | ''') 426 | return 427 | ozipdecrypt.main(self.file) 428 | self.file=self.file.replace('.ozip','.zip') 429 | z=zipfile.ZipFile(self.file) 430 | if 'system.new.dat.br' in z.namelist() and 'system.transfer.list' in z.namelist():self.rominfo.brotil=True 431 | else:self.rominfo.newdat=True 432 | rominformation(self.file) 433 | self.unzip() 434 | def unzip(self):#system.img 435 | if self.rominfo.flag==1: 436 | print('无效格式!!!') 437 | sys.exit(1) 438 | if quiet==0: 439 | if input('是否解包卡刷包zip文件?y/n>>>')=='n': 440 | print('取消.') 441 | sys.exit(0) 442 | if self.rominfo.flag==5: 443 | z=zipfile.ZipFile(self.file) 444 | z.extractall(path='flashable') 445 | z.close() 446 | print('功能性卡刷包解包完成.输出目录:flashable') 447 | print('Done.') 448 | sys.exit(0) 449 | if self.rominfo.abflag==True and zipfile.is_zipfile(self.file)==True: 450 | z=zipfile.ZipFile(self.file) 451 | z.extract('payload.bin') 452 | z.close() 453 | self.file='payload.bin' 454 | self.abunpack() 455 | print('Done.') 456 | sys.exit(0) 457 | if self.rominfo.onlyfolder==True: 458 | z=zipfile.ZipFile(self.file) 459 | for name in z.namelist() : 460 | if name.find('system')==0: 461 | z.extract(name) 462 | z.close() 463 | print('Done.') 464 | sys.exit(0) 465 | if self.rominfo.olnyimg==True: 466 | z=zipfile.ZipFile(self.file) 467 | z.extract('system.img') 468 | z.close() 469 | if self.unpacktodir==1: 470 | self.file='system.img' 471 | self.imgunpack() 472 | print('Done.') 473 | sys.exit(0) 474 | if self.rominfo.brotil==True: 475 | z=zipfile.ZipFile(self.file) 476 | z.extract('system.transfer.list') 477 | z.extract('system.new.dat.br') 478 | z.close() 479 | self.brotli() 480 | self.newdatunpack() 481 | if self.unpacktodir==1: 482 | self.file='system.img' 483 | self.imgunpack() 484 | print('Done.') 485 | sys.exit(0) 486 | if self.rominfo.newdat==True: 487 | z=zipfile.ZipFile(self.file) 488 | z.extract('system.transfer.list') 489 | z.extract('system.new.dat') 490 | z.close() 491 | self.newdatunpack() 492 | if self.unpacktodir==1: 493 | self.file='system.img' 494 | self.imgunpack() 495 | print('Done.') 496 | sys.exit(0) 497 | if self.unpacktodir==0: 498 | print('Done! 输出的到的目录: /') 499 | return 500 | else:pass 501 | 502 | def abunpack(self): 503 | if quiet==0: 504 | if input('是否解密payload.bin?y/n>>>')=='n':return 505 | if os.path.exists('output')==False:os.mkdir('output') 506 | payload_dumper.main(self.file) 507 | try:os.remove(self.file) 508 | except:pass 509 | if self.unpacktodir==1: 510 | if os.path.exists('output/system.img'): 511 | self.file='output/system.img' 512 | self.imgunpack() 513 | if os.path.exists('output/system_a.img'): 514 | self.file='output/system_a.img' 515 | self.imgunpack() 516 | if os.path.exists('output/surper.img'): 517 | self.file='output/surper.img' 518 | print('W:动态分区有待测试!') 519 | self.imgunpack() 520 | def imgunpack(self,flag=1): 521 | '''flag: 1mount 2unmount Linux''' 522 | if quiet==0: 523 | if input('是否解包.img?y/n>>>')=='n':return 524 | if self.file.find('super.img')>-1: 525 | print('''W:暂不支持解包动态分区super.img文件! 526 | 正在转换super.img...以便手动解包 527 | ''') 528 | a='' 529 | simg2img.main(self.file,a) 530 | print('输出的文件:'+a) 531 | return 532 | if platform.system()=='Linux': 533 | if flag==1: 534 | os.system('mkdir android-system-img') 535 | os.system('sudo mount %s android-system-img'%self.file) 536 | print('Done!: 挂载镜像到文件夹 android-system-img') 537 | if flag==2: 538 | os.system('sudo umount android-system-img') 539 | os.system('e2fsck -p -f '+self.file) 540 | os.system('resize2fs -M '+self.file) 541 | print('Done!: 保存的镜像 '+self.file) 542 | if platform.system()=='Windows': 543 | url='https://hub.fastgit.org/AEnjoy/adbshellpy/raw/master/Imgextractor.exe' 544 | if os.path.exists('Imgextractor.exe')==False: 545 | try:urllib.request.urlretrieve(url,'Imgextractor.exe') 546 | except: 547 | print('E:下载失败!') 548 | return 549 | os.system('Imgextractor '+self.file) 550 | print('Done!') 551 | 552 | def newdatunpack(self,TRANSFER_LIST_FILE='system.transfer.list', NEW_DATA_FILE='system.new.dat', OUTPUT_IMAGE_FILE='system.img'): 553 | #==================================================== 554 | # FILE: sdat2img.py 555 | # AUTHORS: xpirt - luxi78 - howellzhu 556 | # DATE: 2018-10-27 10:33:21 CEST 557 | # Chinese: 神郭 558 | #==================================================== 559 | if quiet==0: 560 | if input('是否转换.new.dat?y/n>>>')=='n':return 561 | sdat2img.main(TRANSFER_LIST_FILE,NEW_DATA_FILE,OUTPUT_IMAGE_FILE,0) 562 | 563 | def brotli(self,INPUT_FILE='system.new.dat.br',OUTPUT_FILE='system.new.dat',flag=1): 564 | import brotli as b 565 | if quiet==0: 566 | if input('是否转换.new.dat.br?y/n>>>')=='n':return 567 | if flag==1: 568 | with open(INPUT_FILE, 'rb') as infile: 569 | data = infile.read() 570 | outfile = open(OUTPUT_FILE, 'wb') 571 | data = b.decompress(data) 572 | outfile.write(data) 573 | outfile.close() 574 | infile.close() 575 | if flag==2: 576 | with open(INPUT_FILE, 'rb') as infile: 577 | data = infile.read() 578 | outfile = open(OUTPUT_FILE, 'wb') 579 | data = b.compress(data) 580 | outfile.write(data) 581 | outfile.close() 582 | infile.close() 583 | print('参数无效!') 584 | 585 | def parseArgs(): 586 | parser = argparse.ArgumentParser(description='System一键解包工具') 587 | parser.add_argument('-f', '--file', help='欲解包的ROM文件', action='store', required=False, dest='file') 588 | parser.add_argument("-t", "--type", type=str, choices=['kdz', 'dz', 'samsumgodin','abota','flashable','ozip','miuitar','newdat','newdatbr'], help="强制指定输入的文件ROM的类型", required=False) 589 | parser.add_argument('-q', '--quiet', help='安静模式 不询问yes', action='store_true', required=False, dest='quit') 590 | parser.add_argument('-v', '--version', help='显示程序版本信息', action='store_true', required=False, dest='version') 591 | return parser.parse_args() 592 | def main(args=None): 593 | global quiet 594 | if os.path.exists('rom')==False:os.mkdir('rom') 595 | if args.file:rom=rominformation(args.file) 596 | if args.version: 597 | print('Android ROM Unpack Tool \r\n 安卓ROM解包工具 \r\n Version:2.2.4 \r\n BuildDate: 2021-8-22 19:09:49') 598 | sys.exit(0) 599 | else: 600 | file=input('请选择一个处理的ROM>>>') 601 | if file=='': 602 | print('E:请选择一个ROM文件!') 603 | exit(1) 604 | elif os.path.exists(file):rom=rominformation(file) 605 | else: 606 | print('E:请选择一个ROM文件!') 607 | exit(1) 608 | if args.type=='kdz':rom.lgkdz=True 609 | elif args.type=='dz':rom.lgkd=True 610 | elif args.type=='samsumgodin':rom.samsumgodinfile=True 611 | elif args.type=='abota':rom.abflag=True 612 | elif args.type=='ozip':rom.ozip=True 613 | elif args.type=='flashable':rom.flag=5 614 | elif args.type=='miuitar': 615 | rom.miuitar=True 616 | rom.flag=3 617 | elif args.type=='newdat':rom.newdat=True 618 | elif args.type=='newdatbr':rom.brotil=True 619 | if args.quit:quiet=1 620 | unpackrom(args.file,rom) 621 | if __name__ == '__main__': 622 | print(''' 623 | **********************************libunpakrom***************************************** 624 | * Android ROM 智能解包工具箱 版本2.2.4 * 625 | * 支持市面上绝大部分Android手机的ROM解包,未来更新后还将支持ROM打包等操作 * 626 | * 功能: * 627 | * ①OPPO OZIP解密 * 628 | * ②Android O+ A/B分区(System As Root) payload.bin 解包 * 629 | * ③Android Q+ 真(虚拟)动态分区payload.bin解包 * 630 | * ④Android L+ .new.dat, .new.dat.br 转换img * 631 | * ⑤Android L+ 分区.img解包 * 632 | * ⑥常规解包,卡刷包解包. * 633 | * ⑦部分ROM卡刷包支持直接读取ROM信息 * 634 | * ⑧Samsung odin .tar.md5 文件解包/获取ROM信息 * 635 | * (仅官方.tar.md5文件支持) 解包.lz4→.img * 636 | * ⑨LG KDZ / DZ 文件解包 * 637 | * ⑩.tar线刷包解包 * 638 | * 支持文件格式: * 639 | * .img/.zip/.tar/.tar.gz/.tar.md5/.new.dat/.new.dat.br/ * 640 | * .kdz/.dz/.ozip/payload.bin/.tgz * 641 | * 项目地址: https://github.com/AEnjoy/unpackandroidrom * 642 | * * 643 | **********************************libunpakrom***************************************** 644 | ''') 645 | args=parseArgs() 646 | main(args) 647 | -------------------------------------------------------------------------------- /make.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # make.py 4 | # By : 神郭 5 | import os,zipfile,platform 6 | from shutil import copy,copytree 7 | def main(): 8 | print('''make: 9 | [1]:下载Python库Lib依赖 [2]:安装pyinstaller [3]:同步源代码(需要先安装git) 10 | [4]:编译可执行文件 [5]:清理工作目录 [6]:Exit 11 | ''') 12 | a=int(input('>>>')) 13 | if a==1: 14 | l=['lz4','ConfigParser','protobuf','brotli','pycryptodome', 15 | 'docopt','Crypto','zstandard','google','checker','glob2'] 16 | for i in l:os.system('pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple '+i) 17 | main() 18 | return 19 | if a==2: 20 | os.system('pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple pyinstaller') 21 | main() 22 | return 23 | if a==3: 24 | os.system('git pull') 25 | main() 26 | return 27 | if a==4: 28 | os.system('pyinstaller -F main.py') 29 | copy('Project.txt', 'dist'+os.sep+'Project.txt') 30 | copy('about_pycrypto.md', 'dist'+os.sep+'about_pycrypto.md') 31 | copy('README.md', 'dist'+os.sep+'README.md') 32 | copy('requirements.txt', 'dist'+os.sep+'requirements.txt') 33 | copy('README_LGKDZ.txt', 'dist'+os.sep+'README_LGKDZ.txt') 34 | copy('README_ozip.md', 'dist'+os.sep+'README_ozip.md') 35 | copy('README_unpayload.md', 'dist'+os.sep+'README_unpayload.md') 36 | copy('README_simg2img.txt', 'dist'+os.sep+'README_simg2img.txt') 37 | copytree('pic', 'dist'+os.sep+'pic') 38 | z = zipfile.ZipFile(platform.system()+'_'+platform.machine()+'.zip', 'w',compression=zipfile.ZIP_DEFLATED,allowZip64=True) 39 | for d in os.listdir('dist'): 40 | z.write('dist'+os.sep+d) 41 | for c in os.listdir('dist'+os.sep+'pic'): 42 | z.write('dist'+os.sep+'pic'+os.sep+c) 43 | z.close() 44 | main() 45 | return 46 | if a==5: 47 | l1=['main.spec','main.zip'] 48 | l2=['__pycache__','build','dist','logs','temp'] 49 | for i in l1: 50 | try:os.remove(i) 51 | except:pass 52 | for i in l2: 53 | try:os.rmdir(i) 54 | except:pass 55 | main() 56 | return 57 | if a==6:exit() 58 | if __name__ == '__main__': 59 | main() -------------------------------------------------------------------------------- /ofp_libextract.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # (c) B.Kerler, MIT license 4 | import os 5 | import sys 6 | from struct import unpack 7 | from struct import unpack 8 | 9 | if len(sys.argv)<2: 10 | print("Usage: ./ofp_libextract.py [Filename.ofp]") 11 | exit(0) 12 | 13 | filename=sys.argv[1] 14 | filesize=os.stat(filename).st_size 15 | 16 | def elfcalcsize(pos): 17 | rf.seek(pos) 18 | data=rf.read(0x200) 19 | if data[:4]!=b"\x7FELF": 20 | print("没有 ELF 发现.") 21 | return 0 22 | sectionheaderoffset=unpack("0): 32 | rf.seek(pos) 33 | data=rf.read(0x20004) 34 | idx=data.find(b"\x7FELF") 35 | if idx!=-1: 36 | length=elfcalcsize(pos+idx) 37 | if length>0x50000 and length<0x200000: 38 | return [(pos+idx),length] 39 | if length>0x200000: 40 | return [] 41 | pos-=0x20000 42 | return [] 43 | 44 | def seekfordecryptstring(rf): 45 | pos=0 46 | area=[] 47 | old=0 48 | i=0 49 | while(posold: 53 | old=percent 54 | print(f"{percent}% scanned.") 55 | data=rf.read(0x20004) 56 | idx=data.find(b"OPPOENCRYPT!\x00Decrypt failed!") 57 | if idx!=-1: 58 | print(f"Found a possible candidate at: {hex(dinfo[0])}") 59 | dinfo=reverseseekforelf(rf,pos+idx) 60 | if dinfo!=[]: 61 | i += 1 62 | print(f"Extracting possible candidate: {hex(dinfo[0])}, Length:{hex(dinfo[1])} as {str(i)}.elf") 63 | rf.seek(dinfo[0]) 64 | data=rf.read(dinfo[1]) 65 | with open(f"{str(i)}.elf","wb") as wf: 66 | wf.write(data) 67 | pos+=0x20000 68 | return area 69 | 70 | 71 | def seekforrecovery(rf): 72 | pos=0 73 | area=[] 74 | old=0 75 | i=0 76 | found=0 77 | while(posold: 81 | old=percent 82 | print(f"{percent}% scanned.") 83 | data=rf.read(0x20004) 84 | idx=0 85 | idx=data.find(b"\x1F\x8B\x08\x00\x00\x00\x00\x00\x00",idx) 86 | if idx!=-1: 87 | found+=1 88 | if found>1: 89 | print(f"Found a possible candidate at: {hex(pos+idx)}") 90 | pos=pos+idx 91 | pos2=pos 92 | while(pos2old: 96 | old=percent 97 | print(f"{percent}% scanned.") 98 | data=rf.read(0x20004) 99 | idx2=data.find(b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") 100 | if idx2!=-1: 101 | print(f"Extracting recovery: {hex(pos)}, Length:{hex(pos2+idx2-pos)} as recovery.cpio.gz") 102 | rf.seek(pos) 103 | data=rf.read(pos2+idx2-pos) 104 | with open(f"recovery{str(found-1)}.cpio.gz","wb") as wf: 105 | wf.write(data) 106 | break; 107 | pos2+=0x20000 108 | pos+=0x20000 109 | return area 110 | 111 | with open(filename,'rb') as rf: 112 | #info=seekfordecryptstring(rf) 113 | seekforrecovery(rf) 114 | print("完成.") 115 | -------------------------------------------------------------------------------- /ozipdecrypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | #(c) B. Kerler 2017-2019, licensed under MIT license 4 | 5 | import os 6 | import sys, stat 7 | import shutil 8 | import binascii 9 | from Crypto.Cipher import AES 10 | from zipfile import ZipFile 11 | 12 | keys=[ 13 | "2143DCCB21513E39E1DCAFD41ACEDBD7", 14 | "2D23CCBBA1563519CE23C1C4AA1E3412", #A77 15 | "1E38C1B72D522E29E0D4ACD50ACFDCD6", 16 | "D7DBCE1AD4AFDCE1393E5121CBDC4321", #R11s, Plus 17 | "D6DCCF0AD5ACD4E0292E522DB7C1381E", #R9s, Plus, R11 18 | "12341EAAC4C123CE193556A1BBCC232D", 19 | "D4D2CD61D4AFDCE13B5E01221BD14D20", #FindX 20 | "261CC7131D7C1481294E532DB752381E", #FindX 21 | "172B3E14E46F3CE13E2B5121CBDC4321", #Realme 1 22 | "ACAC1E13A72531AE4A1B22BB31C1CC22", #Realme C2 23 | "2132321EA2CA86621A11241ABA512722", #Realme C2 24 | "1CA21E12271335AE33AB81B2A7B14622", #Realme 2 pro 25 | "acaa1e12a71431ce4a1b21bba1c1c6a2", #Realme U1 RMX1831 26 | "acac1e13a72531ae4a1b22bb31c1cc22", #Realme 3 RMX1825EX 27 | "1c4c1ea3a12531ae491b21bb31613c11", #Realme 3 Pro, Realme X 28 | "1c4a11a3a12589ae441a23bb31517733", #Realme X2 29 | "1C4A11A3A22513AE541B53BB31513121", #Realme 5 30 | "D4D2CE11D4AFDCE13B3E0121CBD14D20", #K1 31 | "ACAC1E13A12531AE4A1B21BB31C13C21", #Reno, K3 32 | "ACAC1E13A72431AE4A1B22BBA1C1C6A2", #A9 33 | "1c4c1ea3a12531ae4a1b21bb31c13c21", #Reno 10x zoom PCCM00 34 | "D6EECF0AE5ACD4E0E9FE522DE7CE381E", #mnkey 35 | "D6ECCF0AE5ACD4E0E92E522DE7C1381E", #mkey 36 | "D6DCCF0AD5ACD4E0292E522DB7C1381E", #realkey 37 | "D7DCCE1AD4AFDCE2393E5161CBDC4321", #testkey 38 | "D7DBCE2AD4ADDCE1393E5521CBDC4321", #utilkey 39 | "12cac11211aac3aea2658690122c1e81", #A1,A83t 40 | "2442CE821A4F352E33AE81B22BC1462E", #R17 Pro 41 | ] 42 | 43 | 44 | def keytest(data): 45 | for key in keys: 46 | ctx=AES.new(binascii.unhexlify(key),AES.MODE_ECB) 47 | dat=ctx.decrypt(data) 48 | if (dat[0:4]==b'\x50\x4B\x03\x04'): 49 | print ("找到正确的 AES key: "+key) 50 | return binascii.unhexlify(key) 51 | elif (dat[0:4]==b'\x41\x4E\x44\x52'): 52 | print ("找到正确的 AES key: "+key) 53 | return binascii.unhexlify(key) 54 | return -1 55 | 56 | def del_rw(action, name, exc): 57 | os.chmod(name, stat.S_IWRITE) 58 | os.remove(name) 59 | 60 | def rmrf(path): 61 | if os.path.exists(path): 62 | if os.path.isfile(path): 63 | del_rw("",path,"") 64 | else: 65 | shutil.rmtree(path, onerror=del_rw) 66 | 67 | def main(file='None'): 68 | print ("ozipdecrypt 0.5 (c) B.Kerler 2017-2019") 69 | if (len(sys.argv)!=2) and os.path.exists(file)==False: 70 | print ("usage: ozipdecrypt.py [*.ozip]") 71 | exit(1) 72 | try:file_input=sys.argv[1] 73 | except IndexError:file_input=file 74 | 75 | with open(file_input,'rb') as fr: 76 | magic=fr.read(12) 77 | if (magic==b"OPPOENCRYPT!"): 78 | pk=False 79 | elif magic[:2]==b"PK": 80 | pk=True 81 | else: 82 | print ("ozip has unknown magic, OPPOENCRYPT! expected !") 83 | exit(1) 84 | 85 | if pk==False: 86 | fr.seek(0x1050) 87 | data=fr.read(16) 88 | key=keytest(data) 89 | if (key==-1): 90 | print("未知的AES密钥,请先从Recovery获取解密密钥!!") 91 | exit(1) 92 | ctx=AES.new(key,AES.MODE_ECB) 93 | filename=file_input[:-4]+"zip" 94 | with open(filename,'wb') as wf: 95 | fr.seek(0x1050) 96 | print("Decrypting...") 97 | while (True): 98 | data=fr.read(16) 99 | if len(data)==0: 100 | break 101 | wf.write(ctx.decrypt(data)) 102 | data = fr.read(0x4000) 103 | if len(data)==0: 104 | break 105 | wf.write(data) 106 | print("DONE!!") 107 | else: 108 | testkey=True 109 | with ZipFile(file_input,'r') as zipObj: 110 | if os.path.exists('temp'): 111 | rmrf('temp') 112 | os.mkdir('temp') 113 | if os.path.exists('out'): 114 | rmrf('out') 115 | os.mkdir('out') 116 | print("Extracting "+file_input) 117 | zipObj.extractall('temp') 118 | for r, d, f in os.walk('temp'): 119 | for file in f: 120 | rfilename=os.path.join(r, file) 121 | wfilename = os.path.join("out", rfilename[rfilename.rfind('/') + 1:]) 122 | with open(rfilename,'rb') as rr: 123 | magic = rr.read(12) 124 | if (magic == b"OPPOENCRYPT!"): 125 | if testkey==True: 126 | with open(os.path.join("temp","boot.img"),"rb") as rt: 127 | rt.seek(0x1050) 128 | data=rt.read(16) 129 | key=keytest(data) 130 | if (key==-1): 131 | print("未知的AES密钥,请先从Recovery获取反向密钥!!") 132 | exit(1) 133 | testkey=False 134 | with open(wfilename,'wb') as wf: 135 | rr.seek(0x10) 136 | dsize=int(rr.read(0x10).replace(b"\x00",b"").decode('utf-8'),10) 137 | rr.seek(0x1050) 138 | print("Decrypting "+rfilename) 139 | flen=os.stat(rfilename).st_size-0x1050 140 | 141 | ctx = AES.new(key, AES.MODE_ECB) 142 | while (dsize>0): 143 | if flen>0x4000: 144 | size=0x4000 145 | else: 146 | size=flen 147 | data = rr.read(size) 148 | if dsizeI', x)[0] 18 | 19 | def u64(x): 20 | return struct.unpack('>Q', x)[0] 21 | 22 | def verify_contiguous(exts): 23 | blocks = 0 24 | 25 | for ext in exts: 26 | if ext.start_block != blocks: 27 | return False 28 | 29 | blocks += ext.num_blocks 30 | 31 | return True 32 | 33 | def data_for_op(op): 34 | global data_offset,p 35 | p.seek(data_offset + op.data_offset) 36 | data = p.read(op.data_length) 37 | 38 | # assert hashlib.sha256(data).digest() == op.data_sha256_hash, 'operation data hash mismatch' 39 | 40 | if op.type == op.REPLACE_XZ: 41 | dec = lzma.LZMADecompressor() 42 | data = dec.decompress(data) 43 | elif op.type == op.REPLACE_BZ: 44 | dec = bz2.BZ2Decompressor() 45 | data = dec.decompress(data) 46 | 47 | return data 48 | 49 | def dump_part(part): 50 | print(part.partition_name) 51 | 52 | out_file = open('output/%s.img' % part.partition_name, 'wb') 53 | h = hashlib.sha256() 54 | 55 | for op in part.operations: 56 | data = data_for_op(op) 57 | h.update(data) 58 | out_file.write(data) 59 | 60 | # assert h.digest() == part.new_partition_info.hash, 'partition hash mismatch' 61 | def parse(p): 62 | global data_offset 63 | magic = p.read(4) 64 | assert magic == b'CrAU' 65 | 66 | file_format_version = u64(p.read(8)) 67 | assert file_format_version == 2 68 | 69 | manifest_size = u64(p.read(8)) 70 | 71 | metadata_signature_size = 0 72 | 73 | if file_format_version > 1: 74 | metadata_signature_size = u32(p.read(4)) 75 | 76 | manifest = p.read(manifest_size) 77 | metadata_signature = p.read(metadata_signature_size) 78 | 79 | data_offset = p.tell() 80 | 81 | dam = um.DeltaArchiveManifest() 82 | dam.ParseFromString(manifest) 83 | 84 | for part in dam.partitions: 85 | # for op in part.operations: 86 | # assert op.type in (op.REPLACE, op.REPLACE_BZ, op.REPLACE_XZ), \ 87 | # 'unsupported op' 88 | 89 | # extents = flatten([op.dst_extents for op in part.operations]) 90 | # assert verify_contiguous(extents), 'operations do not span full image' 91 | 92 | dump_part(part) 93 | if __name__ == "__main__": 94 | p = open(sys.argv[1], 'rb') 95 | parse(p) 96 | 97 | def main(file): 98 | global p 99 | p = open(file, 'rb') 100 | parse(p) 101 | -------------------------------------------------------------------------------- /pic/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AEnjoy/unpackandroidrom/473f292d813136700550fe657f7d6a41a10077f0/pic/1.png -------------------------------------------------------------------------------- /pic/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AEnjoy/unpackandroidrom/473f292d813136700550fe657f7d6a41a10077f0/pic/2.png -------------------------------------------------------------------------------- /pic/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AEnjoy/unpackandroidrom/473f292d813136700550fe657f7d6a41a10077f0/pic/3.png -------------------------------------------------------------------------------- /pic/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AEnjoy/unpackandroidrom/473f292d813136700550fe657f7d6a41a10077f0/pic/4.png -------------------------------------------------------------------------------- /pic/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AEnjoy/unpackandroidrom/473f292d813136700550fe657f7d6a41a10077f0/pic/5.png -------------------------------------------------------------------------------- /pic/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AEnjoy/unpackandroidrom/473f292d813136700550fe657f7d6a41a10077f0/pic/6.png -------------------------------------------------------------------------------- /pic/README.txt: -------------------------------------------------------------------------------- 1 | 测试图 -------------------------------------------------------------------------------- /pic/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AEnjoy/unpackandroidrom/473f292d813136700550fe657f7d6a41a10077f0/pic/home.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | #该文件用于记录此项目所需依赖. 2 | ConfigParser 3 | protobuf 4 | brotli 5 | pycryptodome 6 | docopt 7 | #stat 已弃用 8 | Crypto 9 | zstandard 10 | google 11 | lz4 12 | glob2 13 | #pyinstaller 14 | -------------------------------------------------------------------------------- /rimg2sdat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | #=================================== 5 | # File: rimg2sdat.py 6 | # Authors: jazchen 7 | # Date: 2018.07.05 17:39 CST 8 | #=================================== 9 | 10 | """ 11 | version == 1: Android Lollipop 5.0 12 | version == 2: Android Lollipop 5.1 13 | version == 3: Android Marshmallow 6.x 14 | version == 4: Android Nougat 7.x / Oreo 8.x 15 | """ 16 | 17 | from __future__ import print_function 18 | 19 | import os 20 | import sys 21 | import struct 22 | import time 23 | 24 | 25 | def GetRawImageFileHandle(filepath, image_size): 26 | image_name = os.path.basename(filepath) 27 | file_handle = open(filepath, 'rb') 28 | 29 | file_handle.seek(0x438) 30 | magic = struct.unpack(" 0 : 63 | streams = [(range_list[i], range_list[i+1]) for i in range(0, len(range_list), 2)] 64 | commands = [] 65 | cmd_range = [] 66 | 67 | counter = limit 68 | for begin, end in streams: 69 | length = end - begin 70 | 71 | while (length > 0) : 72 | if length > counter : 73 | cmd_range.append(begin) 74 | cmd_range.append(begin + counter) 75 | begin = begin + counter 76 | length -= counter 77 | counter = 0 78 | else: 79 | cmd_range.append(begin) 80 | cmd_range.append(end) 81 | counter -= length 82 | length = 0 83 | 84 | if counter == 0 : 85 | commands.append(tuple(cmd_range)) 86 | cmd_range = [] 87 | counter = limit 88 | else: 89 | if len(cmd_range) > 0: 90 | commands.append(tuple(cmd_range)) 91 | 92 | return commands 93 | else: 94 | return [tuple(range_list)] 95 | 96 | 97 | def WriteTransfers(prefix, version, rangeSet, write_blocks, total_blocks, nolimit=False): 98 | 99 | rangeSet['erase'] = [] 100 | 101 | if version <= 2 : 102 | rangeSet['erase'].append(0) 103 | rangeSet['erase'].append(total_blocks) 104 | 105 | elif version >= 3 : 106 | range_zero = tuple(rangeSet['zero']) 107 | del rangeSet['zero'][:] 108 | 109 | for i in range(0, len(range_zero), 2): 110 | if (range_zero[i+1] - range_zero[i]) > 1024 : 111 | rangeSet['erase'].append(range_zero[i]) 112 | rangeSet['erase'].append(range_zero[i+1]) 113 | else: 114 | rangeSet['zero'].append(range_zero[i]) 115 | rangeSet['zero'].append(range_zero[i+1]) 116 | else: 117 | del range_zero 118 | 119 | 120 | if version == 4 and nolimit == False : 121 | rangeSet['new'] = SplitRangeSet(rangeSet['new'], 1024) 122 | rangeSet['zero'] = SplitRangeSet(rangeSet['zero'], 1024) 123 | else: 124 | rangeSet['new'] = SplitRangeSet(rangeSet['new']) 125 | rangeSet['zero'] = SplitRangeSet(rangeSet['zero']) 126 | 127 | 128 | with open(prefix + '.transfer.list', "w") as f: 129 | f.write(str(version) +'\n') 130 | f.write(str(write_blocks) +'\n') 131 | 132 | if version >= 2: 133 | f.write('0\n0\n') 134 | 135 | if len(rangeSet['erase']) > 0: 136 | f.write('erase ' + str(len(rangeSet['erase'])) + ',' + ','.join(map(str,rangeSet['erase'])) +'\n') 137 | 138 | for cmd_range in rangeSet['new']: 139 | f.write('new ' + str(len(cmd_range)) + ',' + ','.join(map(str, cmd_range)) +'\n') 140 | 141 | for cmd_range in rangeSet['zero']: 142 | f.write('zero ' + str(len(cmd_range)) + ',' + ','.join(map(str, cmd_range)) +'\n') 143 | 144 | 145 | def CompressDatFile(in_name, out_name): 146 | import imp 147 | 148 | read_size = 2 ** 24 149 | bc = brotli.Compressor(quality=6, lgwin=24) 150 | 151 | # check which one installed 152 | if imp.find_module('brotli')[0] == None : 153 | # brotlipy package 154 | def compress(uncompress): 155 | return bc.compress(uncompress) 156 | else: 157 | # brotli module 158 | def compress(uncompress): 159 | return bc.process(uncompress) 160 | 161 | with open(out_name, 'wb') as out_file: 162 | with open(in_name, 'rb') as in_file: 163 | while True: 164 | data = in_file.read(read_size) 165 | if not data: 166 | break 167 | 168 | out_file.write(compress(data)) 169 | 170 | out_file.write(bc.finish()) 171 | 172 | 173 | def Compute(args): 174 | sTime = time.time() 175 | 176 | if args.sha1 == True: 177 | sha1sum = sha1() 178 | 179 | block_size = 4096 180 | image_file = args.image 181 | image_size = os.path.getsize(image_file) 182 | 183 | prefix = args.outdir + os.sep + args.prefix 184 | 185 | fh = GetRawImageFileHandle(image_file, image_size) 186 | fh.seek(0, 0) 187 | 188 | block_count = int(image_size / block_size) 189 | ref_zero = '\0'.encode('utf-8') * block_size 190 | 191 | rangeSet = {'new': [], 'zero': []} 192 | zero_range = 0 193 | write_blocks = 0 194 | last_command = '' 195 | 196 | dat = open(prefix + '.new.dat', 'wb') 197 | 198 | for bindex in range(block_count): 199 | data = fh.read(block_size) 200 | 201 | if data == ref_zero: 202 | command = 'zero' 203 | zero_range += 1 204 | else: 205 | command = 'new' 206 | 207 | # if zero_block range < 128, merge to 'new' command 208 | if 0 < zero_range and zero_range < 128 : 209 | for r in range(zero_range): 210 | if args.sha1 == True: 211 | sha1sum.update(ref_zero) 212 | dat.write(ref_zero) 213 | write_blocks += zero_range 214 | last_command = 'new' 215 | rangeSet['new'].pop() 216 | rangeSet['zero'].pop() 217 | 218 | if args.sha1 == True: 219 | sha1sum.update(data) 220 | dat.write(data) 221 | zero_range = 0 222 | write_blocks += 1 223 | 224 | if command != last_command : 225 | last_command = command 226 | 227 | if bindex == 0: 228 | rangeSet['new'].append(bindex) 229 | else: 230 | rangeSet['new'].append(bindex) 231 | rangeSet['zero'].append(bindex) 232 | else: 233 | rangeSet[command].append(bindex+1) 234 | 235 | dat.close() 236 | fh.close() 237 | 238 | WriteTransfers(prefix, args.version, rangeSet, write_blocks, block_count, args.nolimit) 239 | 240 | tTime = time.time() - sTime 241 | print('') 242 | print(' 转换为分卷的Android数据镜像已完成, 使用 %d seconds' % tTime) 243 | 244 | if args.sha1 == True: 245 | print('\n Get sha1 checksums: ' + sha1sum.hexdigest()) 246 | 247 | if args.compress == True: 248 | sTime = time.time() 249 | CompressDatFile(prefix + '.new.dat', prefix + '.new.dat.br') 250 | tTime = time.time() - sTime 251 | print('\n 压缩完成, 使用 %d seconds' % tTime) 252 | 253 | print('') 254 | 255 | 256 | def main(args): 257 | if not os.path.isfile(args.image): 258 | print('raw_image_file 不存在: %s' % args.image) 259 | sys.exit(2) 260 | 261 | if not os.path.isdir(args.outdir): 262 | print('outdir 不存在: %s' % args.outdir) 263 | sys.exit(2) 264 | 265 | if args.sha1 == True: 266 | global sha1 267 | from hashlib import sha1 268 | 269 | if args.compress == True: 270 | if args.version == 4: 271 | global brotli 272 | try: 273 | import brotli 274 | except ImportError: 275 | print('Please install brotli (brotli or brotlipy), use:') 276 | print(' pip install brotli\n Or\n pip%d.%d install brotli' % (sys.version_info.major, sys.version_info.minor)) 277 | sys.exit(1) 278 | else: 279 | print('仅版本4支持压缩选项') 280 | sys.exit(1) 281 | 282 | Compute(args) 283 | 284 | 285 | from argparse import ArgumentParser 286 | 287 | parser = ArgumentParser(usage='%(prog)s [OPTION] ') 288 | parser.add_argument('-o', dest='outdir', metavar='outdir', default=".", help='输出目录,默认:当前目录') 289 | parser.add_argument('-p', dest='prefix', metavar='prefix', default="system", help='分卷数据映像的名称(prefix.new.dat),默认: system') 290 | parser.add_argument('-v', dest='version', metavar='version', default=4, type=int, choices=range(1,5), help='transfer 列出版本号, default: 4') 291 | parser.add_argument('-c', dest='compress', action='store_true', default=False, help='使用brotli将new.dat文件压缩为new.dat.br文件。 (警告!仅支持Android 8.1)') 292 | parser.add_argument('-nl', dest='nolimit', action='store_true', default=False, help='当版本为4时,单个命令中没有限制1024个块') 293 | parser.add_argument('-sha1', dest='sha1', action='store_true', default=False, help='获取分卷数据镜像的sha1校验和') 294 | parser.add_argument('image', metavar='raw_image_file', help='输入 system 原镜像文件') 295 | 296 | 297 | if __name__ == '__main__': 298 | # as cli 299 | if len(sys.argv) == 1: 300 | parser.print_help() 301 | sys.exit() 302 | else: 303 | args = parser.parse_args() 304 | 305 | main(args) 306 | 307 | else: 308 | # as module 309 | def process(image, option=''): 310 | options = str(option).split() 311 | options.append(image) 312 | 313 | args = parser.parse_args(options) 314 | main(args) 315 | -------------------------------------------------------------------------------- /sdat2img.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | #==================================================== 4 | # FILE: sdat2img.py 5 | # AUTHORS: xpirt - luxi78 - howellzhu 6 | # DATE: 2018-10-27 10:33:21 CEST 7 | # Chinese: 神郭 8 | #==================================================== 9 | ''' 10 | 相对于原版的改进: 11 | 1.中文 12 | 2.Python3 13 | 3.如若当前路径下存在system.new.dat和system.transfer.list,则不需要输入命令直接运行 14 | 例: 15 | ./sdat2img.py 16 | ./sdat2img.py system.transfer.list system.new.dat 17 | ./sdat2img.py system.transfer.list system.new.dat system.img 18 | ''' 19 | from __future__ import print_function 20 | import sys, os, errno 21 | 22 | def main(TRANSFER_LIST_FILE, NEW_DATA_FILE, OUTPUT_IMAGE_FILE,quiet=1):#quiet:0:enable 1:disable 23 | __version__ = '1.2' 24 | 25 | if sys.hexversion < 0x02070000: 26 | print >> sys.stderr, "需要Python 2.7或更新的版本." 27 | try: 28 | input = raw_input 29 | except NameError: pass 30 | input('按 ENTER 退出...') 31 | sys.exit(1) 32 | else: 33 | if quiet==1: 34 | print('sdat2img Version: {}\n'.format(__version__)) 35 | 36 | def rangeset(src): 37 | src_set = src.split(',') 38 | num_set = [int(item) for item in src_set] 39 | if len(num_set) != num_set[0]+1: 40 | print('以下数据分析到RangeSet时出错:\n {}'.format(src), file=sys.stderr) 41 | sys.exit(1) 42 | 43 | return tuple ([ (num_set[i], num_set[i+1]) for i in range(1, len(num_set), 2) ]) 44 | 45 | def parse_transfer_list_file(path): 46 | trans_list = open(TRANSFER_LIST_FILE, 'r') 47 | 48 | # First line in transfer list is the version number 49 | version = int(trans_list.readline()) 50 | 51 | # Second line in transfer list is the total number of blocks we expect to write 52 | new_blocks = int(trans_list.readline()) 53 | 54 | if version >= 2: 55 | # Third line is how many stash entries are needed simultaneously 56 | trans_list.readline() 57 | # Fourth line is the maximum number of blocks that will be stashed simultaneously 58 | trans_list.readline() 59 | 60 | # Subsequent lines are all individual transfer commands 61 | commands = [] 62 | for line in trans_list: 63 | line = line.split(' ') 64 | cmd = line[0] 65 | if cmd in ['erase', 'new', 'zero']: 66 | commands.append([cmd, rangeset(line[1])]) 67 | else: 68 | # Skip lines starting with numbers, they are not commands anyway 69 | if not cmd[0].isdigit(): 70 | print('命令 "{}" 无效.'.format(cmd), file=sys.stderr) 71 | trans_list.close() 72 | sys.exit(1) 73 | 74 | trans_list.close() 75 | return version, new_blocks, commands 76 | 77 | BLOCK_SIZE = 4096 78 | 79 | version, new_blocks, commands = parse_transfer_list_file(TRANSFER_LIST_FILE) 80 | if quiet==1: 81 | if version == 1: 82 | print('Android Lollipop 5.0 检测到!\n') 83 | elif version == 2: 84 | print('Android Lollipop 5.1 检测到!\n') 85 | elif version == 3: 86 | print('Android Marshmallow 6.x 检测到!\n') 87 | elif version == 4: 88 | print('Android Nougat 7.x / Oreo 8.x 检测到!\n') 89 | else: 90 | print('Unknown Android version!\n') 91 | 92 | # Don't clobber existing files to avoid accidental data loss 93 | try: 94 | output_img = open(OUTPUT_IMAGE_FILE, 'wb') 95 | except IOError as e: 96 | if e.errno == errno.EEXIST: 97 | print('错误: 输出的文件 "{}" 已经存在'.format(e.filename), file=sys.stderr) 98 | print('移动它, 重命名它, 或选择一个不同的文件名.', file=sys.stderr) 99 | sys.exit(e.errno) 100 | else: 101 | raise 102 | 103 | new_data_file = open(NEW_DATA_FILE, 'rb') 104 | all_block_sets = [i for command in commands for i in command[1]] 105 | max_file_size = max(pair[1] for pair in all_block_sets)*BLOCK_SIZE 106 | 107 | for command in commands: 108 | if command[0] == 'new': 109 | for block in command[1]: 110 | begin = block[0] 111 | end = block[1] 112 | block_count = end - begin 113 | if quiet==1: 114 | print('复制 {} 区段到位置 {}...'.format(block_count, begin)) 115 | # Position output file 116 | output_img.seek(begin*BLOCK_SIZE) 117 | 118 | # Copy one block at a time 119 | while(block_count > 0): 120 | output_img.write(new_data_file.read(BLOCK_SIZE)) 121 | block_count -= 1 122 | else: 123 | if quiet==1: 124 | print('跳过命令 {}...'.format(command[0])) 125 | 126 | # Make file larger if necessary 127 | if(output_img.tell() < max_file_size): 128 | output_img.truncate(max_file_size) 129 | 130 | output_img.close() 131 | new_data_file.close() 132 | print('完成! 输出的镜像文件: {}'.format(os.path.realpath(output_img.name))) 133 | 134 | if __name__ == '__main__': 135 | try: 136 | TRANSFER_LIST_FILE = str(sys.argv[1]) 137 | NEW_DATA_FILE = str(sys.argv[2]) 138 | except IndexError: 139 | print('\n用法: sdat2img.py [ ] [system_img]\n') 140 | print(' : transfer list 文件') 141 | print(' : system new dat 文件') 142 | print(' [system_img]: 输出 system 镜像\n\n') 143 | print('Visit xda thread for more information.\n') 144 | try: 145 | input = raw_input 146 | except NameError: pass 147 | if os.path.exists('system.new.dat') and os.path.exists('system.transfer.list'): 148 | NEW_DATA_FILE='system.new.dat' 149 | TRANSFER_LIST_FILE='system.transfer.list' 150 | else: 151 | input('按 ENTER 退出...') 152 | sys.exit() 153 | try: 154 | OUTPUT_IMAGE_FILE = str(sys.argv[3]) 155 | except IndexError: 156 | OUTPUT_IMAGE_FILE = 'system.img' 157 | if sys.hexversion < 0x03000000: 158 | import time 159 | print('Warning:For better Chinese support, please use Python 3 or above.\n') 160 | print('The program will continue after 3s.\n') 161 | time.sleep(3) 162 | main(TRANSFER_LIST_FILE, NEW_DATA_FILE, OUTPUT_IMAGE_FILE) 163 | -------------------------------------------------------------------------------- /simg2img.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # FILE: simg2img.py 5 | # 6 | # USAGE: ./simg2img.py system.img 7 | # 8 | # DESCRIPTION: 9 | # 10 | # AUTHOR: Karl Zheng 11 | # COMPANY: Meizu 12 | # CREATED: 2011年10月18日 15时25分15秒 CST 13 | # REVISION: --- 14 | # MODIFY: AEnjoy 15 | #=============================================================================== 16 | 17 | from __future__ import print_function 18 | import os 19 | import sys 20 | import struct 21 | 22 | EXT4_FILE_HEADER_MAGIC = 0xED26FF3A 23 | EXT4_CHUNK_HEADER_SIZE = 12 24 | 25 | class ext4_file_header(object): 26 | def __init__(self, buf): 27 | (self.magic, 28 | self.major, 29 | self.minor, 30 | self.file_header_size, 31 | self.chunk_header_size, 32 | self.block_size, 33 | self.total_blocks, 34 | self.total_chunks, 35 | self.crc32) = struct.unpack(' 0: 65 | chunk_header = ext4_chunk_header(ifd.read(EXT4_CHUNK_HEADER_SIZE)) 66 | sector_size = (chunk_header.chunk_size * file_header.block_size) >> 9 67 | #print("ct:%X, cs:%X, ts:%X, ss:%X"%(chunk_header.type, chunk_header.chunk_size, chunk_header.total_size, sector_size)) 68 | 69 | if chunk_header.type == 0xCAC1: # raw type 70 | data = ifd.read(chunk_header.total_size - EXT4_CHUNK_HEADER_SIZE) 71 | len_data = len(data) 72 | if len_data != (sector_size << 9): 73 | print("len data:%d, sector_size:%d" % (len_data, sector_size << 9)) 74 | sys.exit(4) 75 | else: 76 | print("len data:%d, sector_size:%d" % (len_data, sector_size << 9)) 77 | ofd.write(data) 78 | print("raw_chunk ") 79 | print("write raw data in %d size %d \n" % (sector_base, sector_size)) 80 | print("output len:%x" % (output_len + len_data)) 81 | else: 82 | len_data = sector_size << 9 83 | if chunk_header.type == 0xCAC2: # TYPE_FILL 84 | print("fill_chunk \n") 85 | print("chunk_size:%x" % chunk_header.chunk_size) 86 | print("output len:%x" % (output_len + len_data)) 87 | else: 88 | if chunk_header.type == 0xCAC3: # TYPE_DONT_CARE 89 | print("none chunk at chunk:%d" % (file_header.total_chunks - total_chunks)) 90 | print("data_size:0x%x, chunk_size:%d, block_size:%d" % (len_data, 91 | chunk_header.chunk_size, file_header.block_size)) 92 | else: 93 | print("unknown type!!") 94 | print("output len:%x" % (output_len + len_data)) 95 | 96 | ofd.write(struct.pack("B", 0) * len_data) 97 | output_len += len_data 98 | sector_base += sector_size 99 | 100 | total_chunks -= 1 101 | print("remain chunks = %d "% total_chunks) 102 | 103 | print("write done") 104 | return 0 105 | 106 | if __name__ == "__main__": 107 | if len(sys.argv) == 2 and os.path.exists(sys.argv[1]): 108 | main(sys.argv[1]) 109 | else: 110 | print("required existing input file") 111 | sys.exit(1) 112 | 113 | -------------------------------------------------------------------------------- /unkdz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Copyright (C) 2016 Elliott Mitchell 6 | Copyright (C) 2013 IOMonster (thecubed on XDA) 7 | 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | 18 | You should have received a copy of the GNU General Public License 19 | along with this program. If not, see . 20 | """ 21 | 22 | from __future__ import print_function 23 | import os 24 | import argparse 25 | import sys 26 | from binascii import b2a_hex 27 | 28 | 29 | import kdz 30 | 31 | 32 | class KDZFileTools(kdz.KDZFile): 33 | """ 34 | LGE KDZ File tools 35 | """ 36 | 37 | # Setup variables 38 | partitions = [] 39 | outdir = "kdzextracted" 40 | infile = None 41 | 42 | kdz_header = { 43 | b"\x28\x05\x00\x00"b"\x34\x31\x25\x80": 0, 44 | b"\x18\x05\x00\x00"b"\x32\x79\x44\x50": 1, 45 | kdz.KDZFile._dz_header: 2, 46 | } 47 | 48 | 49 | def readKDZHeader(self): 50 | """ 51 | Reads the KDZ header, and returns a single kdz_item 52 | in the form as defined by self._dz_format_dict 53 | """ 54 | 55 | # Read a whole DZ header 56 | buf = self.infile.read(self._dz_length) 57 | 58 | # "Make the item" 59 | # Create a new dict using the keys from the format string 60 | # and the format string itself 61 | # and apply the format to the buffer 62 | kdz_item = dict(zip( 63 | self._dz_format_dict.keys(), 64 | self._dz_struct.unpack(buf) 65 | )) 66 | 67 | # Collapse (truncate) each key's value if it's listed as collapsible 68 | for key in self._dz_collapsibles: 69 | if type(kdz_item[key]) is str or type(kdz_item[key]) is bytes: 70 | kdz_item[key] = kdz_item[key].rstrip(b'\x00') 71 | if b'\x00' in kdz_item[key]: 72 | print("[!] Warning:发现多余的数据"+key, file=sys.stderr) 73 | #sys.exit(1) 74 | elif type(kdz_item[key]) is int: 75 | if kdz_item[key] != 0: 76 | print('[!] Error: 空间 "'+key+'" is 没有0 ('+b2a_hex(kdz_item[key])+')', file=sys.stderr) 77 | sys.exit(1) 78 | else: 79 | print("[!] Error: 内部错误", file=sys.stderr) 80 | sys.exit(-1) 81 | 82 | return kdz_item 83 | 84 | def getPartitions(self): 85 | """ 86 | Returns the list of partitions from a KDZ file containing multiple segments 87 | """ 88 | 89 | # Setup initial values 90 | last = False 91 | cont = not last 92 | self.dataStart = 1<<63 93 | 94 | while cont: 95 | 96 | # Read the current KDZ header 97 | kdz_sub = self.readKDZHeader() 98 | 99 | # Add it to our list 100 | self.partitions.append(kdz_sub) 101 | 102 | # Update start of data, if needed 103 | if kdz_sub['offset'] < self.dataStart: 104 | self.dataStart = kdz_sub['offset'] 105 | 106 | # Was it the last one? 107 | cont = not last 108 | 109 | # Check for end of headers 110 | nextchar = self.infile.read(1) 111 | # Is this the last KDZ header? (ctrl-C, how appropos) 112 | if nextchar == b'\x03': 113 | last = True 114 | # Alternative, immediate end 115 | elif nextchar == b'\x00': 116 | cont = False 117 | # Rewind file pointer 1 byte 118 | else: 119 | self.infile.seek(-1, os.SEEK_CUR) 120 | 121 | # Record where headers end 122 | self.headerEnd = self.infile.tell() 123 | 124 | # Paranoia check for an updated file format 125 | buf = self.infile.read(self.dataStart - self.headerEnd - 1) 126 | if len(buf.lstrip(b'\x00')) > 0: 127 | print("[!] Warning: 标头和有效负载之间的数据! (offsets {:d} to {:d})".format(self.headerEnd, self.dataStart), file=sys.stderr) 128 | self.hasExtra = True 129 | 130 | # Make partition list 131 | return [(x['name'],x['length']) for x in self.partitions] 132 | 133 | def extractPartition(self,index): 134 | """ 135 | Extracts a partition from a KDZ file 136 | """ 137 | 138 | currentPartition = self.partitions[index] 139 | 140 | # Seek to the beginning of the compressed data in the specified partition 141 | self.infile.seek(currentPartition['offset'], os.SEEK_SET) 142 | 143 | # Ensure that the output directory exists 144 | if not os.path.exists(self.outdir): 145 | os.makedirs(self.outdir) 146 | 147 | # Open the new file for writing 148 | outfile = open(os.path.join(self.outdir,currentPartition['name'].decode("utf8")), 'wb') 149 | 150 | # Use 1024 byte chunks 151 | chunkSize = 1024 152 | 153 | # uncomment to prevent runaways 154 | #for x in xrange(10): 155 | 156 | while True: 157 | 158 | # Read file in 1024 byte chunks 159 | outfile.write(self.infile.read(chunkSize)) 160 | 161 | # If the output file + chunkSize would be larger than the input data 162 | if outfile.tell() + chunkSize >= currentPartition['length']: 163 | # Change the chunk size to be the difference between the length of the input and the current length of the output 164 | outfile.write(self.infile.read(currentPartition['length'] - outfile.tell() )) 165 | # Prevent runaways! 166 | break 167 | 168 | # Close the file 169 | outfile.close() 170 | 171 | def saveExtra(self): 172 | """ 173 | Save the extra data that has appeared between headers&files 174 | """ 175 | 176 | try: 177 | if not self.hasExtra: 178 | return 179 | except AttributeError: 180 | return 181 | 182 | filename = os.path.join(self.outdir, "kdz_extras.bin") 183 | 184 | extra = open(filename, "wb") 185 | 186 | print("[+] 提取额外的数据到" + filename) 187 | 188 | self.infile.seek(self.headerEnd, os.SEEK_SET) 189 | 190 | total = self.dataStart - self.headerEnd 191 | while total > 0: 192 | count = 4096 if 4096 < total else total 193 | 194 | buf = self.infile.read(count) 195 | extra.write(buf) 196 | 197 | total -= count 198 | 199 | extra.close() 200 | 201 | def saveParams(self): 202 | """ 203 | Save the parameters for creating a compatible file 204 | """ 205 | 206 | params = open(os.path.join(self.outdir, ".kdz.params"), "wt") 207 | params.write('# saved parameters from the file "{:s}"\n'.format(self.kdzfile)) 208 | params.write("version={:d}\n".format(self.header_type)) 209 | params.write("# note, this is actually quite fluid, dataStart just needs to be large enough\n") 210 | params.write("# for headers not to overwrite data; roughly 16 bytes for overhead plus 272\n") 211 | params.write("# bytes per file should be sufficient (but not match original)\n") 212 | params.write("dataStart={:d}\n".format(self.dataStart)) 213 | params.write("# embedded files\n") 214 | 215 | out = [] 216 | i = 0 217 | for p in self.partitions: 218 | out.append({'name': p['name'], 'data': p['offset'], 'header': i}) 219 | i += 1 220 | 221 | out.sort(key=lambda p: p['data']) 222 | 223 | i = 0 224 | for p in out: 225 | params.write("payload{:d}={:s}\n".format(i, p['name'].decode("utf8"))) 226 | params.write("payload{:d}head={:d}\n".format(i, p['header'])) 227 | i += 1 228 | 229 | params.close() 230 | 231 | def parseArgs(self): 232 | # Parse arguments 233 | parser = argparse.ArgumentParser(description='LG KDZ File Extractor最初由IOMonster提供') 234 | parser.add_argument('-f', '--file', help='要读的KDZ文件', action='store', required=True, dest='kdzfile') 235 | group = parser.add_mutually_exclusive_group(required=True) 236 | group.add_argument('-l', '--list', help='列出分区', action='store_true', dest='listOnly') 237 | group.add_argument('-x', '--extract', help='解压所有分区', action='store_true', dest='extractAll') 238 | group.add_argument('-s', '--single', help='通过ID解压单个分区', action='store', dest='extractID', type=int) 239 | parser.add_argument('-d', '--dir', '-o', '--out', help='输出目录', action='store', dest='outdir') 240 | #1.kdzfile 2.listOnly 3.extractAll 4.extractID(int) 5.outdir 241 | return parser.parse_args() 242 | 243 | def openFile(self, kdzfile): 244 | # Open the file 245 | try: 246 | self.infile = open(kdzfile, "rb") 247 | except IOError as err: 248 | print(err, file=sys.stderr) 249 | sys.exit(1) 250 | 251 | # Get length of whole file 252 | self.infile.seek(0, os.SEEK_END) 253 | # os.seek() doesn't return current position?! 254 | self.kdz_length = self.infile.tell() 255 | self.infile.seek(0, os.SEEK_SET) 256 | 257 | # Verify KDZ header 258 | verify_header = self.infile.read(8) 259 | 260 | if verify_header not in self.kdz_header: 261 | print("[!] Error: 不支持的KDZ文件格式.") 262 | print('[ ] Received header "{:s}".'.format(" ".join(b2a_hex(n) for n in verify_header))) 263 | sys.exit(1) 264 | 265 | self.header_type = self.kdz_header[verify_header] 266 | 267 | 268 | def cmdExtractSingle(self, partID): 269 | print("[+] 从中提取单个分区 v{:d} file!\n".format(self.header_type)) 270 | print("[+] 提取 " + str(self.partList[partID][0]) + " to " + os.path.join(self.outdir,self.partList[partID][0].decode("utf8"))) 271 | self.extractPartition(partID) 272 | 273 | def cmdExtractAll(self): 274 | print("[+] 从 v{:d} 提取所有分区文件!\n".format(self.header_type)) 275 | for part in enumerate(self.partList): 276 | print("[+] 提取 " + part[1][0].decode("utf8") + " to " + os.path.join(self.outdir,part[1][0].decode("utf8"))) 277 | self.extractPartition(part[0]) 278 | self.saveExtra() 279 | self.saveParams() 280 | 281 | def cmdListPartitions(self): 282 | print("[+] KDZ 分区列表 (format v{:d})\n=========================================".format(self.header_type)) 283 | for part in enumerate(self.partList): 284 | print("{:2d} : {:s} ({:d} bytes)".format(part[0], part[1][0].decode("utf8"), part[1][1])) 285 | 286 | def main(self): 287 | args = self.parseArgs() 288 | self.kdzfile = args.kdzfile 289 | self.openFile(args.kdzfile) 290 | self.partList = self.getPartitions() 291 | 292 | if args.outdir: 293 | self.outdir = args.outdir 294 | 295 | if args.listOnly: 296 | self.cmdListPartitions() 297 | 298 | elif args.extractID != None: 299 | if args.extractID >= 0 and args.extractID < len(self.partList): 300 | self.cmdExtractSingle(args.extractID) 301 | else: 302 | print("[!] 片段 {:d} 超出范围!".format(args.extractID), file=sys.stderr) 303 | 304 | elif args.extractAll: 305 | self.cmdExtractAll() 306 | 307 | def mainbyclass(self,kdzfile,outdir=os.getcwd(),extractID=None,listOnly=False,extractAll=True):#extractID(int) import 初始化 308 | #if init==False:return #通过命令行自动运行的使用main()初始化 309 | self.kdzfile=kdzfile 310 | self.openFile(kdzfile) 311 | self.partList = self.getPartitions() 312 | self.outdir = outdir 313 | if listOnly:self.cmdListPartitions() 314 | elif extractID != None: 315 | if extractID >= 0 and extractID < len(self.partList): 316 | self.cmdExtractSingle(extractID) 317 | else: 318 | print("[!] 片段 {:d} 超出范围!".format(extractID), file=sys.stderr) 319 | elif extractAll: 320 | self.cmdExtractAll() 321 | 322 | 323 | 324 | if __name__ == "__main__": 325 | kdztools = KDZFileTools() 326 | kdztools.main() #命令行执行初始化 327 | 328 | --------------------------------------------------------------------------------