pax_global_header00006660000000000000000000000064136206425730014522gustar00rootroot0000000000000052 comment=71f03f49aa53262847372a1427b40913d866c6aa sachesi-2.0.4+ds/000077500000000000000000000000001362064257300135465ustar00rootroot00000000000000sachesi-2.0.4+ds/.gitignore000066400000000000000000000012461362064257300155410ustar00rootroot00000000000000# Compiled Object files *.slo *.lo *.o *.obj # Compiled Dynamic libraries *.so *.dylib *.dll # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app *.bar # Qt *.user *.qm # Compiler Stuff /Makefile /Sachesi /moc_apps.cpp /moc_apps.cpp /moc_autoloaderwriter.cpp /moc_backupinfo.cpp /moc_blitzinfo.cpp /moc_boot.cpp /moc_carrierinfo.cpp /moc_deviceinfo.cpp /moc_discoveredrelease.cpp /moc_downloadinfo.cpp /moc_fs.cpp /moc_ifs.cpp /moc_installer.cpp /moc_mainnet.cpp /moc_qnx6.cpp /moc_quagzipfile.cpp /moc_quaziodevice.cpp /moc_quazipfile.cpp /moc_rcfs.cpp /moc_scanner.cpp /moc_splitter.cpp /moc_translator.cpp /qrc_UI.cpp /qrc_translations.cpp sachesi-2.0.4+ds/LICENSE000066400000000000000000001044601362064257300145600ustar00rootroot00000000000000GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read .sachesi-2.0.4+ds/QML.qrc000066400000000000000000000012651362064257300147120ustar00rootroot00000000000000 C:\Qt54\5.4\mingw491_32\qml\Qt\labs\settings\qmldir C:\Qt54\5.4\mingw491_32\qml\QtQuick\Controls\qmldir C:\Qt54\5.4\mingw491_32\qml\QtQuick\Dialogs\qmldir C:\Qt54\5.4\mingw491_32\qml\QtQuick\Layouts\qmldir C:\Qt54\5.4\mingw491_32\qml\QtQuick\Window.2\qmldir C:\Qt54\5.4\mingw491_32\qml\QtQuick.2\qmldir sachesi-2.0.4+ds/README.md000066400000000000000000000060531362064257300150310ustar00rootroot00000000000000# Sachesi Introducing Sachesi. The results of my continued work on firmware tools for Blackberry 10 and Playbook. Sachesi allows you to extract, search for and (un)install Blackberry firmware. It also allows you to backup, restore, wipe, reboot and nuke. This is a continued evolution of the original Sachup and Sachibar applications. None of its activities require development mode. That is, you can sideload and uninstall applications without developer mode. The application mimics communications performed by official Blackberry tools and allows modification of the typically fixed commands that are sent from the computer. This allows increased control and flexibility over firmware related activies on your device. Developed by Sacha Refshauge. Project originally known as Dingleberry. Public release of source code on May 26, 2014. ## Build Instructions Technically should work on all operating systems that support Qt. This project works with both dynamic and static builds of Qt4.8+ and Qt5.0+. It is known to build and has built binaries available for desktop platforms: Windows XP+, Linux, Mac OSX 10.5+. It can also build and run a restricted subset of activities on mobile platforms: Symbian, Android and Blackberry 10. To upgrade firmware via a mobile device requires USB low-level access (host device must support network usb drivers), such as Symbian and possibly rooted Android devices. This project requires miniLZO, zlib-1.2.8, QuaZIP and OpenSSL. For Linux and Mac, the project optionally uses libusb-1.0 for bootloader activities. For your convenience, a snapshot of the important files from libusb-1.0, miniLZO, zlib-1.2.8 and QuaZIP have been provided. OpenSSL for Android is also provided. ## Windows Build Instructions (MinGW) 1. Install latest QT with MinGW from [qt-project.org](https://qt-project.org). 2. Install OpenSSL to `C:\OpenSSL` from [http://www.wittfella.com/openssl](http://www.wittfella.com/openssl). 3. Open Sachesi.pro in QtCreator. 4. Adjust the hardcoded paths set in `QML.qrc` to point to the correct qml files. 5. Run. **Notes for Visual Studio**: - Use [this precompiled OpenSSL](http://www.npcglib.org/~stathis/blog/precompiled-openssl) version. ## Ubuntu Build Instructions 1. Install the required dependencies: ```bash sudo apt-get install qt5-qmake libqt5network5 libqt5qml5 qtdeclarative5-settings-plugin qtdeclarative5-controls-plugin qtdeclarative5-dialogs-plugin qtdeclarative5-quicklayouts-plugin qtdeclarative5-window-plugin qtdeclarative5-qtquick2-plugin qttools5-dev-tools libssl-dev qtdeclarative5-dev libusb-1.0-0-dev ``` 2. Compile Sachesi: ```bash cd /path/to/Sachesi; qmake; make -j4; ``` **Common Errors** On some systems there might be some `Cannot find file translations/...` errors. This can be fixed by executing `make compiler_lang_make_all` and executing the above steps again. ## OSX Build Instructions 1. Install latest XCode. 2. Install latest QT from [qt-project.org](https://qt-project.org). 3. Install `brew` from [brew.sh](https://brew.sh). 4. Run `brew install libusb`. 5. Open Sachesi.pro in QtCreator. 6. Run. sachesi-2.0.4+ds/Sachesi.pro000066400000000000000000000103351362064257300156510ustar00rootroot00000000000000QT += network gui widgets quick qml TARGET="Sachesi" win32: RC_ICONS += assets/sachesi.ico else:mac: ICON = assets/sachesi.icns else: ICON = assets/sachesi.png VERSION = 2.0.4 # Global specific CONFIG += c++11 INCLUDEPATH += ext src P = $$_PRO_FILE_PWD_ win32 { SOURCES += $$P/ext/zlib-win/*.c HEADERS += $$P/ext/zlib-win/*.h INCLUDEPATH += ext/zlib-win # Where is your OpenSSL Install? Hardcoded for Win32 OPENSSL_PATH = C:\\OpenSSL INCLUDEPATH += $$OPENSSL_PATH\\include # Is all-in-one binary? #CONFIG += static static: DEFINES += STATIC STATIC_BUILD !contains(QT_CONFIG, openssl-linked) { mingw: LIBS += -L$$OPENSSL_PATH -llibssl -llibcrypto -lgdi32 else:static: LIBS += -L$$OPENSSL_PATH\\lib -llibeay32MT -lssleay32MT -lGDI32 -lAdvapi32 else: LIBS += -L$$OPENSSL_PATH\\lib -llibeay32MD -lGDI32 } !mingw { DEFINES += NOMINMAX _CRT_SECURE_NO_WARNINGS # Hardcoded lib folder for winsocks LIBS+= -L"C:\\Program Files (x86)\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\x86" -lWSock32 -lUser32 -lCrypt32 } } else:blackberry { DEFINES += BLACKBERRY LIBS += -lz -lcrypto } else:mac { INCLUDEPATH += /opt/local/include LIBS+= -lcrypto -lssl -lz -framework CoreFoundation -framework IOKit -lobjc /opt/local/lib/libusb-1.0.a DEFINES += BOOTLOADER_ACCESS } else:freebsd-*|openbsd-* { isEmpty(PREFIX): PREFIX = /usr/local/ LIBS += -lz -lcrypto -lusb } else:android { LIBS += -L $$P/Android -lcrypto -lssl -lusb1.0 INCLUDEPATH += $$P/Android/include/ ANDROID_EXTRA_LIBS += $$P/Android/libusb1.0.so ANDROID_PACKAGE_SOURCE_DIR = $$P/Android DEFINES += BOOTLOADER_ACCESS } else { shared_quazip: LIBS += -lquazip shared_lzo2 { LIBS += -llzo2 DEFINES += _LZO2_SHARED } LIBS += -lz -ldl -ludev # These below should be static for it to be fully portable (changing ABIs) LIBS += -lcrypto -lusb-1.0 DEFINES += BOOTLOADER_ACCESS } SOURCES += \ src/sachesi.cpp \ src/search/mainnet.cpp \ src/search/scanner.cpp \ src/splitter.cpp \ src/ports.cpp \ src/apps.cpp \ src/fs/ifs.cpp \ src/fs/fs.cpp \ src/fs/rcfs.cpp \ src/fs/qnx6.cpp HEADERS += \ src/search/mainnet.h \ src/search/scanner.h \ src/splitter.h \ src/ports.h \ src/downloadinfo.h \ src/apps.h \ src/fs/ifs.h \ src/fs/fs.h \ src/fs/rcfs.h \ src/fs/qnx6.h \ src/carrierinfo.h \ src/search/discoveredrelease.h \ src/autoloaderwriter.h \ src/deviceinfo.h \ src/translator.h # Welcome to the only OS that won't give network access to USB device !blackberry { SOURCES += \ src/installer.cpp \ src/installer_qml.cpp \ src/installer_establish.cpp \ src/installer_auth.cpp \ src/backupinfo.cpp \ src/blitzinfo.cpp HEADERS += \ src/installer.h \ src/backupinfo.h \ src/blitzinfo.h } # This requires libusb to be linked contains(DEFINES, BOOTLOADER_ACCESS) { SOURCES += src/boot.cpp HEADERS += src/boot.h } !shared_quazip { DEFINES += QUAZIP_STATIC include(ext/quazip/quazip.pri) } !shared_lzo2 { SOURCES += src/lzo.cpp HEADERS += src/lzo.h } RESOURCES += UI.qrc \ translations.qrc # The qmldir is built in for dynamic libs but not static. static: RESOURCES += QML.qrc OTHER_FILES += \ qml/generic/*.qml \ qml/generic/UI/*.qml \ Android/AndroidManifest.xml # Qt Workaround for having install.cpp file phony.depends = install uninstall phony.target = .PHONY QMAKE_EXTRA_TARGETS += phony # Translations LREL_TOOL = lrelease # Grab all possible directories (win32/unix) win32: PATHS = $$split($$(PATH), ;) else: PATHS = $$split($$(PATH), :) # Maybe it has -qt5 extension? for(bin, PATHS): exists($${bin}/$${LREL_TOOL}-qt5): LREL_TOOL=$${bin}/$${LREL_TOOL}-qt5 TRANSLATIONS = $$files($$P/translations/*.ts) lang.name = $$LREL_TOOL ${QMAKE_FILE_IN} lang.input = TRANSLATIONS lang.output = ${QMAKE_FILE_PATH}/${QMAKE_FILE_BASE}.qm lang.commands = $$LREL_TOOL ${QMAKE_FILE_IN} lang.CONFIG = no_link QMAKE_EXTRA_COMPILERS += lang PRE_TARGETDEPS += compiler_lang_make_all lupdate_only{ SOURCES = \ qml/generic/mcc.js \ qml/generic/*.qml \ qml/generic/UI/*.qml } sachesi-2.0.4+ds/UI.qrc000066400000000000000000000015261362064257300145760ustar00rootroot00000000000000 qml/generic/Search.qml qml/generic/UI/CircleProgress.qml qml/generic/UI/TextCouple.qml qml/generic/UI/TextCoupleSelect.qml qml/generic/Title.qml qml/generic/USBConnect.qml qml/generic/Installer.qml qml/generic/Backup.qml qml/generic/Downloader.qml qml/generic/Boot.qml qml/generic/Extract.qml qml/generic/VersionLookup.qml qml/generic/showpass.png qml/generic/text.png qml/generic/trash.png qml/generic/Device.qml qml/generic/DeviceSub.qml sachesi-2.0.4+ds/assets/000077500000000000000000000000001362064257300150505ustar00rootroot00000000000000sachesi-2.0.4+ds/assets/sachesi.icns000066400000000000000000006451311362064257300173570ustar00rootroot00000000000000icnsJYis32pupnnpupifvmji jmvfi gesjhhi hjscgqvjjijijijikupmkbkajbhceedfotjS0J;K9DBCE>Chvok03&'`sngCUI3Ci/TD19]rln'C^rW+Wg]9`G[rnk1,"0*$( asrjM);,:.93580;gvlk_l`kahbee cdomsijikijijijsmaZpihi hiqZadTqkilsUdmx}pmllnq|n$EIOHJQMH          1 k     x ""   %7!).c0R D/>`+N>-8  BVjP(Q_U4XD  &/$/ *&)&  C    P      N  a'( )-&.,Ʊ űz { ʰ~DlMjOcW]_V_5  @& 7$) %" &5   p8Y>VASGLNFP¬ ˷s8mk UUHHppMM [[Ǩcc\[_]sػpil32 Zo7|zz|5o _ayn{solj los|ly`^yzojgghhiihhggjp{y uzrighihgis{um(pmnghihgomp$m~mrs~mgi gmssn{ngign|rgihhiihihihiihhiihigriizijkiikjiilikiijjiikihizhiogimPMljFXmeAcmZAjmJMmjAZmgoqzjhfm?;lj/Mn`(^oQ-hm;?mh-Rkgi{qrgkL<5/==/6>:.678/<=23==-4=[jrngkJ *  &\jmkhkK i#=t'N_jkyjhlA7 z)N\2] l@WM1"^kizwihkISG=tu?X^}>5}`kiywihkK =*}Bk@]BRq}uA'r_kixxjhkI\mE{2EdISeS7 Fv`kiz}khlE"~=hul,?.:]{] O_jkmgkK (]jmpglH.(*..'+-.&,.+)..)*,.),3Zjpkwiiae?;fc2Kg[+XgN/bf;?fb/OceixkmgjpHDol:Tod4bpV6koBFok6Wogn\[uhiiggiighihghigeiiffiiegihwY[ogijijijiijjiijjgpvss|khi gluuzxe\wihigjz]f{YvkgigkyLitym=$i  !! *VfwAs/}@^DN^N7Bm #) ''t~ 9ame);+6WrWH$ ,9,p-=*7(*11',0/&.0-)00)*.0),;   $&% % ! 59 >:       +'hmmg%@pjpQ\okl/5oko\Qpkjhi~hhsgij_SXI DYUX6&SVWR+%7YUYC JXVV+/WUXJ CXSajftmgilN364066561/5.1525404512541673Qlfnm}jhhkQ0        7Slhj~nwhihkQ-*m6SlhhxsgihkR.LJ36SligtpgihkS#+X1*[ @L-7O@P 6RligqogihkS&(KIB06͊3Wc} 06SligongihkR/u<l[H~:e46SligongihkQ-VA51GtmgmNYlhj+1lhlYNmhignvgi-jcMajhjYPhiihOZjhk^FbjijMPjhjbF^kiigx}mgimjiihkmimkhihjmihllhimjhignyhhihihhihhihihhihiihh{)irfiftp|y{nfifo}{{h`~lgifmfmLykgifl~2^6Z|mfhihfn&beUpggiggral u|ukggi gglxy |ϫ~rkgghi hgglt} ^c{q}smihghg hjnurqr qhxspnlk lnquzlw }sNՑ| TuH9dJGGJd;J E"%5jl@-$ $-Apm7&"G ;@C%L+   ,N&DB= I 30L(   )N15 !L M2/ 14N J-CM#   #OD/NS(,?A.*UW)! 9;!"+X9+*0>A2+.<HF8JKNL:JL4K" #O6E .   0 H;6DI  LG9>W'8      )\7BF ( $ [% ) " ) JE9)  +''* " * *im &"%+%!&$#&uq29 #A<9/:=<=4/<.4<9=8/:< 23=<=:0;=A <4)  #5    < + #48D:(e= !s $5GE/w= |J %+& S.'U ;G*3J;J= N< %-%FD>,2/Q]t -= @8 $6m8eUCu6_0= <8 #4P=2}~-Cl{8?2= <8 #4kQ.BlzQ6/3,&= =B $3 s[`DnOi= H^ %++-|525fy;tfD&= f $4$921 8  :< = $ #3C<'&0  $>,,'*++,*(,++,((,,+*'*+,-)(,--*'*.@ 5+GJ 10/).1,*0110),1.*/110+,1/*./0UR" )$ %( ! ($*.6,, (( -%" - ;3.F-        !O0(-5 :2,0;O"      &YBH4GGPQ;X9'"%2 9(&-B+ ) 00@R" *eQ=AE(-OIC62=+4I?? 438&9   D0$E@= 9GI(u*   0.d_C C'@R(   -bT!,F 1=? nt3! %7!MJ8A(46J4+&''(.:SA?4= kɁ ҉i о ̿ § ¸ ú ֺ ¶ ½ֶúڰßĻ#]20a}:F}W'$\8E˳zg_|D+&{y$G|\j~08}j\zͻl(20/10//0./0/010110113(tr,   3xxr(5$ 1wt̳r(3@8<, 1ws%G  @ 1wýr% I6FD6H1G>71#@,? 1wr))J&4: <0,?B8$< 1wr(M/>0@<)/B61HG= 1wr((9D02@<).BF= 1wr'+5F"5:$=)0=3( ? 1wŲs%>9D49@/D=:&-E<,'7DB : 1wr(     1ws) 2xm)&$'%#&'$##$'%#%($##$'&#$#$($)+tN[K$G[WZ9*UXYT):ZW[F%LZXX/3YWZL%F[MÝW*'\}8E}*}Q!Vx/=x-t{zgv|g!jvľĽ~Ƥ^ ɶ ż ͱ Ⱥ wh8mk .11. B۷B --++**JJ[[NN0/ 66::RQ,+CBFFFFEE43gd$$XVZX('b\le4/smyrFʌ> EdvwwvdCit32Zn }nqp qm}ɱ kso pskow~ke̪gjzq0;y멙{xtrux{yA4l=|vqmkihiknrv}Cmx}uokihhihhikpv}x |tlihih ilt} ̙umihih imvІ ~qjhih jq~…֦ Й}qjhih iq}⏔ fW~qihih iq~hm qihih ir :]Twkhih kwW]9 oiihih iio wkihihjw[pihih iiq^ "lihihm.zjhihjzwihihiwtjhihiuqihihirqihihirѬtihihit虗vihihiwtWyihihiySs}jhihj~lhihlphihpeuhihv[}jhihj~nhihn`!uhihvBi͑~jhihjԡƈphihp񠗈{ihihihihihihihihi|nhihghihghihghihiihihiihihiihihnzihihkihhihkihhihkihhi hhihddgihhi hhigddhihhi hhigddhihhih{nhi hhiH67?eihi hhiH67?eihi hhiH67?eihih<*,2ajhihja2,*$N5Mk՗?7vHb8<ڪ'J׏yO*ֳ* ,Ih55:98\jgi hiimliihihhjF7:5; 'ۦ1ڛ#& ]Oi65:98\jgi hhilliihihhjF7:5tL߻ p=dJ h8i65:98\jgi hhilliihihhjF7:5 $D`<]u!Qni65:98\jgi hhilliihi'hhjF7:5 * ᫄67hVNr/Di65:98\jgi hhilliihihhjF7:5Mڻ X 66ic5Zy i65:98\jgi hhilliihihhjF7:51h 6ih7V޺.i65:98\jgi hhilliihihhjF7:5bبZԲO6C 6ie`,7568  i65:98\jgi hhilmiihihhjF7:5ؿ #᫄6iWFv߉i65:98\jgi hiimmiihi hhjF7:5 6i=k ݹi65:98\jgi hiimR^phihi hhjF7:55,ٱ ~;6iݣi65:98\jgi hihqnfksrhihi+hhjF7:5ރݾBٝ6iN ݵi65:98\jgihhihrum{uhihhiehhjF7:5`sC7J(sԑ ֙?6rU޿ 5hBv<8Y7|'h55:98\jgihhihvxhihhiehhjF7:6P ,9otLo95:98\jgihhihz|iihhiehhjF7:5<Ǚ/̺)xs (NTȢ-F͠'N(5:98\jgihhii~jihhihhjF7:5-5/-4-41$52335:98\jgihhikхmiihi hhjF7:5 5:98\jgihiin堙gnqhihihhjF7:55:98\jgihhihsslviihhihhjF7:55:98\jgihhiix|iihhihhjF7:55:98\jgihhijmihhihhjF79:   :998\jgihhimbirhihhjF799<=<=<=<98\jgih srlxihihhjG79/*+-89/*+-89/*+-893//191//391//39:9\jgiih zkhihhjE5876'%3876'%3876'%387($%%48784%%$(784%%$(76\jhiihhik+GqhihhjYEFG@&$9HFG@&$9HFG@&$9HFG@&$9HFH9$&@GFH9$&@GFEKfihiihr1xihihhj ijb*%'$Ulhj ijb*%'$Ulhj ijb*%'$Ulhj ijb*%'$Ulhj hlU$'%*bjij hlU$'%*bjijihhihzlhihic,%'$Wkghic,%'$Wkghic,%'$Wkghic,%'$WkghgkW$'%,cihgkW$'%,cihihmptthihic,%'$Wkghic,%'$Wkghic,%'$Wkghic,%'$WkghgkW$'%,cihgkW$'%,cihihu~z}jhi hid,%'$Wkgi hid,%'$Wkgi hid,%'$Wkgi hid,%'$Wkgi gkW$'%,dihi gkW$'%,dihihjphi hid-&(%Xkgi hid-&(%Xkgi hid-&(%Xkgi hid,%'%Xkgi gkX%'%,dihi gkX%'%,dihihq{jhi hid)"$!Wkgi hid)"$!Wkgi hid)"$!Wkgi hic*$&"Vlgi glV"&$*cihi glV"&$*cihihi}phi hhiH67?eihi hhiH67?eihi hhiH67?eihih<*,2ajhihja2,*=CHGVYtj ,MXg{\F#oWJA:2,%!!%,2:BKXq#H]|[lwM3 wWH=2(  (3=HXy 4N{p^XflE!<0ySC6(  )7DUzAM!FniZ P\_A/^F7( (8G_0B_\P XglE6W@. .BY7GoiZ kB3P=*  +?Q6Em XFHLFDZV<(  )>XIOLI[ ]DXA+  +C[ Ea @ETCglH/  1JpjFVFB `OU9  ;XQd anpIr{F, -H|zLtrd ZW_44]=  ?`87bZ]MJVP4 5RZMP gDCYH, -J\HJk T?=LsE$   $HzP@CYV>9H|u?  AzKp{[W\TPRt>@{WUYMaaCFcbQJ6zG"  #J7PL9CJP*  +SKD9NSLMZ[13^^PPX]X{n9;s[bMCGMbXCHO*  +TMESaVKQid79kpUO\_CGwa]ISU,  -YVLbMaΟ;=cR]G?FO%'SJBJbMZ{9<^R^D=DM$%RG@Id9[z9       &%#   #%&  #%&<_C`;BM$ ')'+ ')'+ ')'+ '(&* *&(' *&('%RF>fhZ: %'%( %'%( %'%( %'%) )%'% )%'%=^kSGOT(  %'%) %'%) %'%) %'%) )%'% )%'% *WSJX^nA%'%(%'%(%'%(%'%((%'%(%'%DqdRNad1  %'%(  %'%(  %'%(  %'%(   (%'%  (%'% 3hgRYV7>J  %&%(  %&%(  %&%(  %&%(   (%&%  (%&%!NB:\fV:/--,-,'&+,,-,'&+,,-,'&+,,-,'&+,,-,,+&',-,,+&',-.'  >]lrLZ_- /@;<:'%%$6=<:'%%$6=<:'%%$6=<;'%6=<=6%';<=6%';<;@ /c`Quv;AK!,;78/*+-78/*+-78/*+-7893//181//3981//3987; "OE?}m>-<89<=<=<=<998<BrxXux3 -<8:   :98< 6}\|HQW( -<955:8< *[VL18I-<955M 5:8< M=6o>-<95 .5:8<BsX6-<95.VaU2Ѳ  zӻ(5:8<9]Tdf/ -<95*Ȯ2ҵ  f5:8< 1ljYENT'-<95,ŵ,ҵ  zӻ(5:8<)YTJ?EJ!-<95Ȑ(Gsҵ  .5:8<"OJD!)E-<953ǧ ҵ  5:8<I0'>-<95Yo.1//Ҵ!3( ,/,05:8< C9-<95ad 5ȿȽ{оŮD d;iI&5:8< ;7-<95QˌN>Ө ߗ9|Ͼ֙xŴh55:8< ;gj1-<95$:$yI2Icƍ;3nCн\48˟&EɆ qJ'ǧ' )Da25:8< 5wshk1-<95ý7 %͛-̑#ҵ #д ӌWJ b35:8< 5xthk1-<95lKЯ h9ҵ]Eр԰ a5b35:8< 5xthk1-<95ҡҵ Ҵ$ҵ @Z8WmҿLg b35:8< 5xthk1(-<95 {ͻ' ҵ Ҡ5ҵ 4aPIj,?b35:8< 5xthk1-<95Hˮ Sʷ Ҕ5ҵ 2b]1Tq b35:8< 5xthk1-<95.aƸ ґҵ 2baр4QϮ+b35:8< 5xthk1-<95\ɝTŦJ2?Ͷ ҕҵ 2b^Z*424 ̽b35:8< 5yugj1-<95ɲ μ ҵ ҟҵ 2bQAnЀb35:8< 5xt7 -<95˴ їҵ Ѳҵ 2b9dέb35:8< <6H-<95)ʥ ғҵ v7ҵ 2bΘҴ b35:8< ; ?(-<95zβҵ >ʒҵ 2bH ϩb35:8<E&-Cd-<95Zk?3E%kƇγ ȏ;2jPг 2a=n84S3t%w{a25:8<G70@EL!d-<96J õ)ޥ޿ 5hlGxŶѺh55:8<#RKEENS'd-<958ƺ,|ʾ}&ȸpl &INȻ*A̿%I&5:8<*YVKUfh. -<95*2,*1*0."1//05:8< 2rpZ]6 -<95 5:8<;cW<-<955:8<Cb39H-<955:8<"NC=lIRW( -<955:8< ,^ZN}X}2-<8:   :98< 8^p=-<89<=<=<=<998< C {;AG!,;78/*+-78/*+-78/*+-7893//181//3981//3987; %PKDmCST, /@;<:'%%$6=<:'%%$6=<:'%%$6=<;'%6=<=6%';<=6%';<;@ 2ddPsM6/--,-,'&+,,-,'&+,,-,'&+,,-,'&+,,-,,+&',-,,+&',-.' ?^N:@G! %&%(  %&%(  %&%(  %&%(   (%&%  (%&%%QHA_WQll1 %'%(  %'%(  %'%(  %'%(   (%'%  (%'%5rtR`TzA%'%(%'%(%'%(%'%((%'%(%'%F*#hTISW)  %'%) %'%) %'%) %'%) )%'% )%'% -\XLThf9 %'%( %'%( %'%( %'%) )%'% )%'%@aqe=BK%  ')'+ ')'+ ')'+ '(&* *&(' *&(' )TMEY]U7       &%#   #%&  #%&>aZ]UcW]S>EK&  +WQG^fZk;?tW\Q@JM*  1^\OQfX"A"G53#]1Kl4EK)  /WSGSPD @  #G56BS\=Kr4>f!e]PBNU/6ekRSLD86:K(  /WVMUZW<"C"  )LGECOxm<  %E{5F]bU9  !CZdJ>AIh/ =YTS<3:PO+  ;lKGI=6@ST4  "BiRNR T-+D9  &A KBE R-(C>! *HIBEMJS@& 2J(_KMRNQ*+E,   8T,-VMJ JVV=XV6  &@s_(   1IGh E<9@%'M1 %;Z1&P89z ?FN.>*   3GAXXL K:,K8(  .Ahb>C r 9@C9W4#   +>[)7_[J u=#B8&   ,=[6PKIJ [A*Q4'   /=X3*,D(Y>_4Z<0"  )4El.IYZALP:-"  )4?U6,D8-PH-EI&N;2)"  &/7BX7No|0JeAwM@70-(% !(+27=GSe7 .GpnT{.Ib@+ Ƶ`IC@678FESn6GlfS fO?4 GLKH3 4GKKG.7NHAJLJA%ELMH1'ELNI:;95::5 ˻X3;5NM5 UNMNLP? EONNLT97NJPMNMLN$/QNHFMQ9-QMJDLJ'SP5::5 ϻX3;5KIK -@JP%6NK5)7NG#3KM<'OL2*KOLK?%NK5::5 λX3;53OIG HM:KM77OD:MJ 9ODBP5!NM%OL5::5 λX3;5CMJL)4O<67ODLO%NM(OKHKI%OL5::5 λX3;5IJMB:CCCO77OD,NK7ODLO% GN97OD%OL5::5 X3;51MN.>NC7ODML77ODLO%9MLAN@%OL5::5eX3;5"H)7LJK(PK3 ;ND=MK6(6NCKN%OJI)5 ,,KO.%NK5::5eX3;5>SONMNNML>PNJDHKNIINMNLT>:THPS')PLOMMOV-XJEHMOF 'SP5::5X3;55EKLKF6 /EKNLHA/8HKKF* );39;#@ILKG9;HNMH8;95::5X3;5      5::5ƺ X3;55::5X3;55::5X3;55::5X3;55::5ºX3::   :9:5X3:9<=<=<=<99:5 Y4;:/*+-8:/*+-8:/*+-8:3//19:91//3:91//3:;7¿¹U-43'%143'%143'%1434($%%232%%$(432%%$(434/TYXZN&&'#C[WXZN&&'#C[WXZN&&'#C[WXZN&&'#C[WXW[C#'&&NZXW[C#'&&NZXYVd .$( .$( .$( .$( ($. ($.º 1$(! 1$(! 1$(! 1$(! !($1 !($1 0$(! 0$(! 0$(! 0$(! !($0 !($0 1$(! 1$(! 1$(! 1$(! !($1 !($1à 2&*# 2&*# 2&*# 2%)# #)%2 #)%2m +! +! +! ,"& &", &",ÿ˻ kFHX kFHX kFHXQ-0<<0-Q<0-Q ¿̻¿ݮۮۮŻٮö׮ſծ޾Ӯۿ¿ҮЮ€ȸͮĻˮľ–ɮ¼ǮŮӀԿ߸ ¾ ſ Ż ½ ƽ ¹ K ¾ù ѷ  Ʒ žt8mk@ $>f||e>$ !X๊X!UϓT\\:9\\\Z\\>=][<;nm////(& wvMKkk%%[Z |{~~|| WU*)54ywLJ 0.NL{z wqHB+$ vE;lh'(okK@^^``]\@<FE 4+^Ov -* xaN9%n:,ve =$<<=)skA{, :xީj*=q™i1  % Ȏ;꜋-AXpW/;Ӯ.I`9gD"!&-JA@sm.hy$v~Ql-T<9lg:?!y@ƒ0D߆'IaF_\8eF:[½?/)PpZfX\I(tqy?ui~@JE'e&xhi4H\,df ~ r‹r1YC2~$힖$i@^ai{Φ;/*ɀ 5i~UY];DZA+6aF֯Z6AL!+)Q碙HwÏrQd~#6ӄCӿnZ~s?l/gN@j}#yd%boMM0gaHGr/4bܜ _E31r|6;?J_O /S{vLçwmX LneQuf;O/tRߙd7sPNq_j'#)fK(88r~$A=;~u9-: H?#}/uHhmYp$-Al }F6hʹ0m B^BaK]I7xI_%E2XkMϡt_%`˳*t}J՟'qV}I+"Dlb>-$ C89Kj Bm5X) hѽU=`&W .'UȪ_,M.4;dVЧrd҉\QGhd̡"]A8rŤVg^nwv~F2bx]~~\—wp.DyV`k]|߆FVX%Ƶy[QKzomNPD*0%Y*t?~gk}}1j4^օJ8~!m9JꠎR83 ՠ<6چ?i pZz /Q@N±W7i CAMtYvB֧F3eUpͨ2|,vvn+mmgj20]\hWp659@qjo"C= ZgZǮpo#8ٰ!9@U c#L .U8!ts!.-FCNRsɩeN[^򶉗̰}Z kx{=R(-_S0ʆ'?>'lo)bFr׼⬷LLQ8ifBwK\DJVlfprT hAsw_L:E|6pQ (C7_4!?.g})B%[B7#Pp2k<++ <tNSH+Vu_zw@1Q$@qL{1Kk"+䋗> y>"Dk,l=1 !K uߵC@Y E9 $娹k&)%]"dsb%e@Ӱoys#,/unl:GsT?z\,~DS` tugL1pq%kEDy-Rg;D~t:F[͛RԢ I2B+p€ԑ *c D7@ksǯ`:8:3ڶ?`Gl?B%x'ֶ fL Dͫg*7tx Ё"dwuh˓БJ(ʕ_@&Z,=ߚz}U)06/ߝ~t,~8Ub  ̰v[!F,SE׾S 6!M:pϏr l2yap4 53@T\`O*2hL}2z!9}:f=;hCS:bAk|q[}aA߰eA"•Y'RElN!p|n7xk?C&ICEp;qῆHĉ[Ta! (U);v:dճ /Prš8ũFf,7u7? kAl<ۖ7 8Vwu.6YR6= +å;Z?!)A^HvXRׄDnES_5 L--#Jm-- 9Xd"vVRM6ܧɦR# BY?7[4,?¿>].ZŮ7׽MuoG{،eZ *u4q x"R:9d##3Vz }՞3<͗ڤ21iQ} q@DEby tk=CA1pِ'0)5BLL=&#$lty{SDpqAM g4KS`TVc ]3\&T&ӽo8rI0 bcDyM}ebuo(;eScץȧN$sb'AP De%#&ug<Mn`L\tjruujdv_Ep!Jyʣ"`o^0tTS!cBIZs*PK3L̈́{{K.'}>^օ}4u:WH>Azn4/as쌠(q~h#IHo& H9P>ڭO&YDdծPbvQ!8MF?:w`I*Kx}ϢW[=uñ Pԫ٤-f)4{3Qj0 'L2F3XpIm3MUj;NOQ黷G&6Eb}YE$NDiqN1 1U!7,tK)Cr)nLEoT:JqYسyM2ZGٳA&;-JvN ?UN8= tSB{<lR[TV z*;Nhn^Z>5 0O{ћ}P2r@?PKeDSaZ^n`7;0T1XUar)}ͭP猏s0H{a+R'˚Ǽ{Φs$ۗi/(5s ųΉwEFnsE8aqn-LFTA;ʱt9 sT9+<` $`m?/F-L;GZ!37ֆbbI:ZIyM i_bϘBӟrXx\ev(8OgkIN ƣyξۯڬvbGj[0_6[|E@&եr ٪,d20(Џ9j9#HP "Pf%ח.5 @d>~(Wg:R Y݅yCC9uL(!CVLjuPs\[Y (F!肖w"&x2kK=%0R_^ kT*!ݑcJUh%ʝEA(VN']Ǽڶ5aM)H*rXy{4΄#FS5 ܕK@ã ?Vtχ+%bk 19XFneoCX8갎bwᰘ 0b[:Uɍ 4ɦ Pd{ʿ=%e% ]Ve7F5hQsˎ!km/&A m7P.~l@1q ͗ Yơï)i:K6#l]|l-`\ .6|L^m yzcH sB1/ ʲxtR,F҈[^л$O;{!TRYМ&ANA:|+$RS~~N51SbI2# s( HݬzjʭG@+/%1V24)e%^wwe+9xfLnm}Ph_rq=RO9_Jh/RAG1e̕iF5ZT.|/EI ~(AakY!?TΧ$g FZ>O&l2u@kCsqfH{; =s%sB@@K,$r;&XdAX7) Qr{}J8H+R i:t) 0mG.&@Lh߆˝:G Rʡ=vsS!_((]rHƁ^wN5q`MS k-s6V:s *ex`7Z ®Ǻ)ȋw$XD}FyVUOo+nH泝X0 ^ܙw/<}gk=(?BtvR:NSずt"uVta?^81q}hD  G.aUg.ETT#>/'  ܴ%VUBVtp^S2bp1 D}~ Af{ 'w4-߰ς7u:z(bllklz,?_  $V-[޸ށ1`O޶럮X׉6u6d.֢9l;.l.mne_$|mט#"E0~D\el ,Q bDI}IE!x!dgȪ0aʜp2]ǥTzΐuڌ B`ooT(1케2f0b!âh%:OGR'+sбppƔp])%aS$!5:A՟$^(ilF@yiި"-k5&@3_a%8o߫|SbHߺKQY N&X?G/g5}irGkK&h:S4>9ז!OsL } KBƜN9$_̠ 2Y8t ;$[y/,_=0H84H 6,v01d 9}ub7O‹yF6_[lkK C|/; JJ÷[q5kpäitoD` ;p j{ (=F| #W &rfR/MjG,9_JaΆ)\Hx#]7mbl Z&'ф0 ͥu ]s8ns@⒒Es$ dus29NL ם(S+jv-++kc4yɀgxOd84"ESä_&0Wڊ7[9Y ڄw<- 0Rzd@ͩ_S1 ڭNaaTDy&UB_!U:вiɛd8]J@{&T H;4J}U4*FfB6vFf`Օ)7jUf;A?;Tmc~!r@߱p V}¹^9gI^V]GGMfr0!Mujc"; 3uTEbQޣYDv`*Y$2ԞV';OC{,n'qxy[LJ\J6`Gpz ;Q݌IWkQ꼸L"XtO=9ʵ_ x.YSq6C{*2}2&Ϣ|Q´RF~CzdsjE범턐a_D+a/;X/E5N엤D_޾)3q R}؊ka+NP |/&)SfSv޵sBTfbp% ;ըݕ^y UkYjYnO^Ђ!,lbR8("qXz!3,*Ff?uĘr"a7&T/PQaN#M7QO+""e9 tݑܺ)_ " \`$mڗdQw.桰3+UX>H]?SqlLt>5FjWQn z2\yK|u鈶t3)0a(B 5ED80,. W#ЧԮ^,*DGX\Z'cdEȟպYc%-Dw3yYs6c% |][8zkfӾEPTܖ%8(qYp7Qɧ`LSȪ7ON;8?} (@?)&Q[[R. p!Ɵ7#>o319J6 fbaUR+WX߶WhH&k1 z-9]\_A݂ V;2UKMĞ eFf51 ȽY6n6!5)nMQ1.2s5D\HSQgjI%m 8y7oțFqKvc!k4Pg_CR{Pe:To K?,"/¾ zpnBΟi(ߞߞGᢀJ ON3Ҵd̔) j٫ѰQ=zl2 }qrd.uݶnImW MރߞA-dp'KIʙնA$4<ɍ& 1EqK{R{@zb{\T4UB䲗;.QfG AE+tYDtb+ѓaք{ʙsPL'(tyBOQuwa##-kx+;;]1Ϙ#݊V@}?j>d-i`p6? r8waPy^M/3jfU^ `"`UܱҼecJXKZqF k:Gr-DvBiupL H3Һpի1~nv9dnZ\о]s+*]F lF= ) r.>ͬp5 [`>`O]ű68]jU۫-YZtuEGV9nes:X-Xl?7vLߌ,0 ̉v̯-:xNR4LS+}ie>VFc]oʑF=1KkjTM;?vRo5qCo.Bdw Ge<ٖLWHl˜1 s7_!u7 i^e|^'v)ڃ|O'\J.xkI(\PnT?=[ArcyG" J ~8&6hTĬ.54׉"D?^Ȩ"-/-@?XÛc!= Ipe(rinK48| ^r0߂~ ͑&1UFjW7f:./ļCw湵c;/k_&w[t֪lIM$}Q:ĦI40F[}^@o3ϙlbuŚ?ׂL t٧ϳ d8 c{m'2ϣr^eO c48Y[j'>g<;Ov%8J>*r+"4{ bGKȾWץ62Az%јlΗoMd}Bu=iP2 -'M%ְ6gkdAPj67N4cCO[u pE0Mp#ffXR'Uo/>{ZfnR)|(2 6#$FZJO6=@V;!J]uUT3Á*hY֩yë-DN9Dì&S~XȚ|>9p7lyF1޵}+T=+ ,$QJS asZ&d]~^bbo{n |@D{If ӍիnݺMzfi߯,!oU,iUu*e;9jf(aT5XZH&9fmNwS ks5K[>[&sľeabՕvzC?\mo5–^Ny i d tbW2-h`%,Q:9'fsa&$$:|I,l18SDu:04$sc?  hm-9]%FGm3[o*ˆ3̟l3Uwv(\WfcՕR;9 A oˬH_$J-WR=\vn:moig |\C)X$5`|c=Do静 KZtpqIw0f["{]5od(/1tbM1/.xh -F:7%- A w6߼x0Ƽ]FsL)]Q?N[ѨCK!&Ν{Q~gD8Ew4&̋ˡ (>^̤vrbwZ bL\sWp[>c\Fo9_+Oׁ/Aˑ0%> ,%)M/{/gl0%nԔلi_R0rdu)y#vW'rAx`v!L>qT"3.m3>>c˄b֒($gJʵ㯧KMk}eECY/'>{JHhUb& e:Rq!mEk-Pt`gCC/ˏWu^~zk3!h=f#ſPj$iw׹ bRt?gxW {ɴl$s )zF~KY *?] 'A4F+ Y/X*jޥsH>o+;# fP4 EDJ)Ѫu(_IX(%0NUF(A[B7mMK;)xt)05âdY0rybpƗ\fn&8 "Z$cS}+}m?XldfBVFBYyo TߝI <}`j{/\Ans`:/99.}Gh=J.u.}:CRg/+/^jė !- $.+讳[q;lZ?V `ą:~Y -jsRgEJl cmtee5Hd:I9Z v@4B.>SHmHY*iCjz>̱谾^Z;R̲¡db{*TqMvQєYf x1DUA aGAZd4q*beU\ TEum/6+kD> XYhpzˬ*I$Z<3<*ܭ|$>JnׅW a //#B}-t绽r~^yk|9}L*gNĂH§C 3Ry5Un:ץ[C@(xiG9>IoKZB}yS<Vf8x|~OWpvYN˽nB@#[v~aH럆 ?"XMNjB~|91,gh/Ix#BZ$rJc.I_A7Ȝ:߫H!=+螄*|X7 r+ߤl$()C+ettKZ<>'0`F>8_*wmW* 2'|A64qR VS4d\3j>V ouXbwќ $Q:$QgWB Ո7)_EmpB|]bԲ9{5wy7C/#Y|DjJTHt1IffI8 # Z~Z5NxBѳ,f8R~G< L|վWRQrbl4t;Y;V;;\%!a/>3Hgj`qmvP$0㠏dIl RC &KĈk^i򁄡O,|ڗ& QMwrX3AMRv2g i\#>Kx//甐O_߱qw&бnX'kF4|ƶ;rYÙ`gRq4r\rs!fB!\.4]^3: ]m0{nC΍閮z3q:d2jt&3G{l̜Nn7i:퐤YMfMz(wN ƖN}Gފ*Hr4=tCV>%F>e^\QP6CɈSd X.Nt>KXL7hGi.U\:@쮏4S~&y~R-}+: 3Wa t0E~cN%U9`( 11VhCMfYeփA1ؒ Y-sZ`h&PIhց3PIM^r (%ekYQĆre\Umcf>vLԁ^3v]R{\zr=ț]|2Cn4Z `@ VY;>uQ.ۚ)՜]޷Jqҵʄn!{qk3 5$E!u .I>OsKI-k fBuA5kI-ia;t◎ܜݙpIb,YWXZwhg36 sqa}}"*9HN*0[w$/c'j чN1Hīq2COƮ Ui%k4]GEC:hJR.S; (JVkpy{#&$Vq]|FȷlE=xnݰ[,Hվ`^2xyvx@ F#A6 ` }-BMdЦukkNI+Pd>\&5W"cB<8u[(*s|W.^qX0k^#|]^p*GSxdןmͶ~-)S[|w.aa6>8og+ 6mPFe"U)e_-lQ¾"<ܹ6ŜlZJȃ1!K:G@grG{} U:Lcx{?, rDrTI&ɡ6\=ZC.mSt۩3RQ\֗!~ q /"hnT( bs//Asd|[}fGx~s8h52 QJ̥#A(J> ]T`},9{YH rm[S"b$ %4҃)X{E?b^b:WoQ!K,f}}Pe؆K$&ǂW*ShDQ4.C;)ɗ^*[ӇHsy V6#n<H׼ƍoCCr?HR+- %Sj( D9ֆ~zj L 5R +["?I XCUߠTaU; Y9WL8 evptH{_aYﴚ!YUgJC5rٸD-x6"4;)GeU!ك2 Y0 @H|Q!}w*KL*+L{ND KOis~;W?iM@ɣb_R^|*K _K|֡m$m ύ?:zFnPu*.p{pF*9DE+,ak` hm1Gn^Yx.XoOƜ se}TNoǬcS`k`5=S~c2z[²}5 e|?VJ=BKٻ[7ֲ5>p„֋-sSosa<zI8XYuϠ3cJGCwؾćͶzQ26F4MX;#7Kw^[#>8\s? LO)S)8d :u":#8lLJ4|+R'K6S(M#0scw|*(LnAFj`5> j -Nf#Y6ݩL ҚƶȷF}wxMV7ZGxaN֓br>o M-dz1ruP"c. yQN\V#N^jY UiDǃ=wX#:aMJȼ#u.8EaZZ Dig6.־CPIͧr=<*s~wZMV,)7ivz>|A%rf8wP228v:P`7ɴ}֌rR5U/Hȝzqg,d6SLCPn=#$i2=qtm޽2yIN0'ǭ^S?8Sț h+iΗV`/ݡo,rC"?  ^.G1MDSD̚,dǰ艻.H= =kyQ1cXVsk,E![s; [0+"?BYh/ֶpO*IY4ҶG8Lc7 .eAl(frPEaj^Tʖ- 91* q^*߫'$-? B)ښ8"W6ӉA9zV.)dB"Nӭ ur' o_!'0aof惁EoD7ހ^ǜ?l)nuK@"'F VNȹV|hTv'2'csV 8xFXr=pP8RBY9~,UAC#/\rau dEa2QPV*GtWwLR\ ;9a7z+PLF%WךmlWF"/Bj\QdGBaj Q@bMD Gm; H )*qf%+"2__REy͆gVc̓ʚIn74ע徼 /) ДZO+Ͼ@:"(m' UiQ?̆2iG1h;][7YM hlk?bIH Xn3cS< Wb\6V:A=8P zKxD;DǀWPLzQd( ΚᝩZ!bxyBlG|+M)Uy[N! MlȾRD>V O02t|ujWCMVF)ޜ%phnBWvOD7E_*Ȃr*wD˽ER(9ѯó%3yȝ% y*UÈj&us%Q8n<(HLA!q|6Zb#k"^8:K&¡mn.9C-֣m"n>B<'9|(~t8lDϹ17T4\ DٴJJRZxbC蟣 =IB848I4IFLZ yV `ƚ4YŻ!KG (ˆMa\q򓜲avє FNOD3p-T+qUA/1it.+l L1#[ =ەߦnW8t9@+ 9cIW*šA*BqY˘O -p|Eq66f[3ROlgol ٖ)o*kH. 2(Rmwb"˧FI=ņ،d{E7zJwGE_m}Y! rZi4VU_a{6}~.Q,ܚ$ݵ*>vn>vIUbMkD9tgJ\ؼ$E\"lȖhO̧Y ̑;1M,q'ĵ)1mYԶlr!)l +@?b*VT=7>{>L} js$U7eF3l_Lo͛>@CoXm8t}m c>Y!l)UN=aCIHM?\zec jՂE<EhLnCjPOd*ecR6WϮqpMpP%.ߊۍHG~bLM7S(:GGN`xѶaM(+t1D3o+u ԯr($wS"N%~=H7;T9 x塹GqZpP`GmhIe#w`'F Ag1IOmLÇG5e*8;fc8#l7Lt-Ѥy,J;na@J[@E(s6sȬ;wESvQiȽ^aE(y&c*4aD3xW\iV+#X7t'2CX4_=Dp"P=7<%TDԆ@Y`צU#`nfٯt4f*$]uH<6fKlPܾg6`33}=TLR/jTb5W}VmrlH9 l'Z7 k.yuIohC6r.*tA7WOsoh|t) CF(Qt)_[E_cbNs|9 f c=)"…a\o;K::3a[ItHVIF}z$fz vLGaLb AVB{E&/'OKNDPNo00Nu7X Z2iS9FiB@#ԩv +$דͭFpUR0ي.o\ IKZ`~D%Bd|-GCơ'w(ul.:i9(FR[9 /jS4T9^"w#."<lCc}hd&l+bxZгq~a9q6*ho6xq!{7f~T4W3V_d?M.5ǁoI٨h$p5:*0rAqQ#1ā~D)GN/v;g[v;zcu:\17{&˶7TABc4֩R/h>0Tq\ʕ8paR04'ܫC+6˻>C}JKjnH; Oװs+h֕IޠE k?ok$ bp^"1I^z9$ d8S iN‹57喳q0+6Kю@283SCrO:2|`ay3}##iVR#tVPW)o^-D/,enF0m:V[<sQ/-ԡ  H+^rv|e i@~h z̰?xTg@CȥCWj2Um6tdr=#YlYJwB|*t5*9L'Q҉36#nDpO'gMX֔`6U~BaY>6Ǜny K#&@h%x:͉VMWu>Jb {Kq3$߻%J5ֆ,iM4E:^z~ܦ2C,^{=ɖXg #SmWVC!: |Rڧjk~ kG֖Ͱ}L eխ8I`uT:'y[ i"Bbo_eῼ NӾ{e q`EΫGE^2yN gylF:R=4`xY ^&f%M(ly+0;x U]A@c*v0qXYڍ4I{"Nx' O'ؔ={jL?R;%nb<࠱T`:XCɂ>'@ =NP,2|r%m|qc!FMzҶ9E,ea?)vj!&FSvkX;)O".s{^l}\4SFOCa3Dٮty}EhxKfQ~?~mCPrgd lTiiKk d:&*J `hAYᙝh<\`b _p? =y" dsg}ߘpe&i7U!q5kߒn&VյST5D`Y[~g)p~ }xϹ[0X1SΌM*DA UOzX(_*^@<א{uqgo!Ea9n _ <b*X𨃇٫]N2"H8&YN)A:]ҙ*Ǹ2\җI~%&'"ΩС ㍀M+HM]I{;~q GY?bD|:%2U+Ө73YvP4Cpm8K">ge;vE^4=nJw)-)[w+_̛o[`5RUD3@=3Cü:Z1n0P\+) J8Y6R.IE[hbΪet[&7F ;޲pC> 1CFGweg , 3` OJNk%rRr27!XhNnr[ A^yŠ|YoZUZ*G{iw,>~ǢE TL)ucљKf8}C`=,@wVɝaB{ fǹG"cE7cl'\B )bpVNΊ/-%teP{-#2pk*]m?7pu9`XDeO1n-K_ҍih'Pt%yޫ(+WHyLsjn9EoH|x@gC^dd NL4ɇpho$gA3+r"$hb]*;>!Vѵ Yg_BC"Δ wfܞs' gx.F:#Y#A4GZj.n0,SpY3_0\ӾbV^3Weg菁c^?VAGB8(Pw[;jmo=wI~/`\SCH^+RU9K^]`JȿxA c{:Ŋynʥ}_u&qW; TautOL!go]`|4gA~3gZH%g/!(0hlDW5L< R_eSJ<Ε ZYDC2#J6_ܺ wI},*ɀ8?s{F-V)~8#[nq^ T1bXD֖kj*7_N URu,tx]=:ߨB/ AAH eyS T֥EA;㹺F֙@GOnB=T\ϲ-adM^R3ԣQ _ܢ`xntIہuChk`Bg5?>f8?5J޳Dz5Fͥ޴hB@H9B4˜qm(av`cEmVGWXg/bq0;r# 7`f8ph ! ֺu샛G߫1i¶]gymӷm*qXMiZ1[$bn_5v,hOt4]\rڒp㥶ՠr3;ێh_:^}.ӞWGcPIJ#rjhnbņ,z1u4tSjiiaD`X։cPI߽~i_\]oMctGzеeQHTvQR dt)Mz籮s=&Ӄ4;BT1vU%%u9?&$v.5҈˽x>E:ˏ""E !ɜZQ erA5k7jx.zAnN- mp3$'w]n"s%,9f R3I ~O"7mеZzA|O?V4 wppc< Nm2 ݉L VДj('Pڃ$Lh.Ul T²fsH$\I+.LΰGGxohp`.*,R# '4CǸIU*Q[&{\-#6 >eBF##pό%M!Ъ[I+<CL*ef :k- 5{Q?;y Ateq܃;*<47߀Ad#Q农P 0|&_Y2F,㉠8 Sw1@ϳG={_7Ƣէ˓>Bc !Jx[(Rh#C?)(LP4Ŀ.1C[k*yҼ+㪀"]ĻanavYẄFMGP`I5̥9Toj'+Qu[|XD@XQ _{>`a $&cH@|_%m#o+ڣ/JdDLiٓ=-'*c0 a{k_"\;2K78g2:@桀;K^X#C S52v$MX/aH+`#2e$>τq?UXӖO"jA@=`t1(*Gbp| HD*}&Z߶,EJղ=ɪNC s5H^j82y@Q9,͚[1ghyb JdraůZ9jZv`wÌb/U"bG9{+A6Yy 1~+'l̵Jm_LCǺ't1ݮ|lcqa'#ϴ> nbHZ֢@J62 /rVdPS֕"̂%#EQ1#:9^"& /,V+T59^8 |ۊڻӊ|:E1>m(O~(HgЩT{cm3Qk Viҭ'@&<]Y1=bLYtJCˈ5IM"'fx٩Fe^y<H?$:iO4oA< 3(:TE7=ձO0|pZGa({_WT+m2PCckUJgPL^ASx_ddL+r+eEmU0r&;3WS0ꠖQ7y+rٵltǿ` \H0Z*HM'їÌ lM3'`#߬w+jUCuSW[3/z7߮B`ifZRo9J젘YUٞSg2$>/)lتԴc].Y E:aA6h04Ύ135, &_HƵqә$ȍ] U2̖?IL('뫏)|7XYX,bvPߡ)6Q(``Bՠ >UgS" QHXnFlO.s(:]id`g 0F=x-i˶ ' ХZ#Dӡ Ԑ_߳? ՘y5kX>3A@o2BX+${‡{P s#K jHd4(PDzRB >S`(U~uf.дGժ ’wqg 3(}Z )%?,EFQ2 ]"Ba.$/Y\`F$8C?3#;l9y|ax_]?)yz.q'%coxX8oޑj| *|4sJ(x-[f B&6nvTZY;pUX3%4h?h1^Qy;;ѳTw*+fr"ߖ:8X:Pv*tKSe@2q 8clA./dYv6+R30xE ԝJrmv=l3RbyLaU8KSi'lۨ"涫#e 2YƲTlP3U7 z']G tVwu/ƶH5pme*@k`(Uzڸ])B>'4'Kb9Ja6r? Z3A.p5K'Ra Fh̑!)7n6ҕ:ORWGPocKgCn-0DzXtEɾZЉŒjZvJ<0*2y]@K|w3 dȃAVt391FmPyކIYT]6Z0y\D Re7^77єK:jn}$qp՛99&au 6CC-0Uw0mZHZEEo= ?-Zh<8 c{2%5Pya *s S2#:EBBJ͆c!G bb52*q/m}Ickq@vJ'0Y@#7H|C ȉ h&M 덹 jᦅš"`泹=7K3UA n^t%Z *kRٮo%[,[ D4aW{A]_l:\V@)CIu,.R8)E*]^#콬L/XEahIF9+otp;2.l?=eGCHb:Q-ɎOpytQGqtl(fA}H.g_#tskgy<X|C@M#O,-)p`SQjkvd;4s[?9(37/aByXN]̅WE 6xd,B˕ r6&&G[-lI9 VPyM)_>B Y`PKv% A40`UQCELk0nKXũ(QfCT 7q;;8,ub( %2vK*J%O$yݹi"\W8P<<g/1TheAC~Na5nҗ U6EsH$ۻ]+Q@ 鞄*}}Gt !kIfQhႲ<'nηVɇHzeZ#|G-nGU`c|:&.@8 M%:|USmVZpQu%]?Έ+-TDMȪ+ p |=bQj e.kP<4T3v;Cv;zu}|>Ŗj3zekoPmjyI˩| Ψgk#ed@S o#9Jq̝}x{ N@۴@x-p oz? wd1^~n困_VZ`xs-AD( nn0T '՛w1EAڗ 7QX@*sŇ&L67>ْq3|#z|y]X}IF:QOb TwӉPgo뗡EyԪ+p|3W(BC3lGf= ya'/$G-֜CQӱ Dpa;'ȇ`>7-U >t{0n?mӲԍ_:-BTP7`l >XZ|EdgI\ ʟU4 e,1u6Ʋ> G_B:@Dhkv f}K 9{$&/?FÊ:nX~hPqJ:я~~8.C/T'77vؐug8aXˉыdZ,'T7R_)uM143FA dKwbT/A\Defqbk2QF'͊c wlNJeEAe2ֱwuNrLKo}7e݀Gi3dzĤG:̅1^^}=xgp +ftZMŊ.f$ iGb}0GQ3E$29Fb=aKBp_苠j8WZL\/A8,ٯ`e2T|MDe.Hj/=͏XWDB!"}#ʝ&8IltSe,xDwk`]pF"}{}kXQ9P  $3]˙9!@Uyo(`9MsireS-E;%#j Ǫ =3KUCUǍ]_^x wNvF-q|PpEl:v!\Yպ&b$OpsK~i؜ zǿbO@K -ڎYZd0m-Ѓz@^>@Y'ۊ?YnQdWJ~cyt\[!Y{juR$~x3[\h]cGۤ)qGGtϊNDSZ?[~IV>wj5 I#V#tldr*aŌ3{\yk{_/U(1V%_ɕ8QjZ(C[ݜ'k۳q4`@|wWގtʚv=EJF3E-|TYc$~DՕƩ[F`X_@_-9 J"0(9 [ߛKJ>Y/`C'(>`wV|EƛD$p TFIS4i$!hsO{VPc @ģeǾ:'ZI +wT7ΗaE/VTwdXtfFK uG#oь1)1b=9h7ږA-KDI%q`B,!>j#_0jϕ4yKf'+eAطLotNTNb~^}:fs%g 5Jf=+0;z' V3,΍Gq~^r/;!C*'5i26âK*=6hsXd끉0*K8'Ԝ Ecʝ;7!KVs]sMqhkЗlb:Q^63(K b7)ga8G6"-+:dzu 델0zGz9+XTዯNԠܸpF>́ZCsPe"r $rG 84CksE"?mM5l]0'5ԫdohPS zxCCF^) uxTV^NF.`):wy0 ԗUy쮴("ڴi\thn^9~ =3oF-RQ){S茙 ]z'9zbDRˀҥgy!N5oLjجcWE7 =PV'A{O(jhtq-gF cIUpo\}tZ떬m걗'[sh ̲x * PW?:/5uzBK <g5[w8ͼ2FVpL.QOWp/R\>>]Xw(9|x ߮Vyȕǽ`E߶=[ӄgrCoiޓ9Ɩ:mD%D*0]pGX8}~$LPDdREO{:ٞ mszTUwH@nIVg݄L^ipJz֕LRHcTfdZtp#re,kA<O8/԰#1C - tQ, fUyy=s:\5GY(ةcrU4Sg_*< K $:W/&56a4=-etpD㠋交m<rS"7bHr@G%Sxgۘʂa(@fl N ڭ: 0閂&z5URfϳ}{ZnEaHCҹrV|J1ubE+ G6Y6?Ƣf1ftr30ìc3tLZ>dYZnv:feG9ظtOɶV>ifab%G}]P?`m Zof#rjO ,Ɣ p*܆(sގyҿ|ʎYYj# -*LcvK{"}|3+MGF=Y툿Ƙx2׀8$jʸCy,Y{v b) <[9y s9ͩe;J6+ λ\QܡVvE_SSV5;]!/0ځu6 )'eCނǚzKM)ҡS sN!Y yEiCjکV:-eIzZ(JSv(8g6q/'F"'⹅Z._bbSilu^]-yY1Q(X  hT د#5T|U,C[mK-Z[ =eG*:MQTճ.oV>}Vr5WhRlHV~®8u{wmfQ"5+8`fёY 6P >Ǹ@gHM]t5TRɗw5/t9X?9ݘfWw5]=.G1՘m&#ʭN ej4xn.uOk< :f2GAqޫsԇYLc^*f5'm0,ʋ{C^`֔KMI.>,+࿇RMVkBaϼ!ozc`,;,~[=>l.C@( #]0K\DvWarII~{^`/<׏]2VFYW7[FբI[hy̙)Rkh#!k6ڵ\] q '$')bm#πeJM" N?Or=>ȧk3P[eJ=p¦ggm\Ga) ^ci l"jߪW(Јy*ĩg™moؕDiw.8jlE.WMVoNTNt9 GU.~\ʚ5~@[h% G1=*sҗ{afi96{'މٖ8p%H |Myqc-7/wVVėU+ѽ|K+[d?^#̛O!$ <\Ŝnt~KJ'FD+f$h*Pz=2ʷI\,6ΧJ_O$ELDEpsлGP+ )g$#]o8; ?GR,vI'Ouj8F c^I/FaLrW$ nN8f<*eR:륓Ә?Zw5uw+=#:QHT?0`#Ly] 'D;NCsaЫ RS] $t5} zt"YŪs3CCz^BүR1)FanϹ~AJuAMPu6fݛ_;ǽRnvEJqV@]ux?ܩ[6<Ԍk6#̲Ϳ! %12ztY(&NCM{sݓ1Owe: .L? .7߶aX#+(08CqiHQ=M9pkәj R f}AH%~);kNf Cߝs5<4M >U&wԮQNڲIg|ke*g%J~klU!gg6/ć~)*9E i]DP5-X)M!ctuVO/m)RܹH9=6 /&Y26 jh'I?)ܺ.4yyHG*aHw1 ݣzD7rѓApJ t lS_tsp7|\ qRaZ֙AFeNX{̠10͵#̃h*zJK stfStۘ8!&|oK,X"wѐo5 G\$FV~E|hjiq)Cr&xYnc~] _AJb̰Zt V̭ʗf b KhztI汛p<|:{p,ZvHQ50˄PHAԹT2u0#GoAb"g Sd(n #Z?9*q\:eQn2,neuٴhBR+tKLs_GBӪ\Q8Xl\L"2BE=1;-RL^-cos͛]')UX|1mknI+YbPeGF{H;;ftLs*J^QR!&@+ s4 @Y)gx~Mqeq^I*oE:3{QIMqkiAטiډMWc]̛qA=M tE.(c=nR^ǨK 6=ٗU53¸ш˪("lyO%%Qc)|ߊNj@yAJKf Hxe:؝MnZjRv4u.wed%!-Q_HѬ5.{kDٜqj >Pxo0aOXRL67Y[pH3BU疄N[6X+=F,R PMl)Cucj;BGN7ұz:A6N7Eh,]"r$3!vZYOƜ{¨2ᢂaj>_}]9tKLǔ1l1ɯe\Tnѫ/T AF x_ׄLZ/Xh^ lLoV#,)󠤽_#5lBv7 / pu>#e]0o zZ58dmvWĉ5QVv"x>l5"\6HCQ!ԓʹ> >?A뺼-BecQ]+ak GkcvNpzcUŮ/恅[I1Qu6E)VB q2NI¹ܷ>Vk89r, 8[2`+_ 3jlcNqHaZ׏5uGndV{% W97d=,I`OË[ RRR`Eւ 7*$$,!]0|I7'|NuM%/W2׹CUB HM@~zͺ']P1z$l/Lk1s4d *` b#Pb,dK\3#‹E޾f]8e-"ZPsA ,%vӄAQB! $UY0H*o?$&CX4L~MXm89RZ0y!͖YxCi՟,T,Q#M%$Xj0* ?IVarLwb&5/8t13hZIƚ/Fbm>U6Ո?on&.:g_޴lwyf&R قu0Ʌ@ vXZOk>ޕxc$Ä S*AY6R(Tdle$ѽ~:_01vÞ#d](5 Bip."pFFOzey G=;Hv:e)nRu0Nc:Ҩ+b]r-'1Yl?P#u1ɜ纏dOkfT儡 c)gDeo52% oV[Y3kU8xꋝJ1;PoNvneA㠉W{ƕ/x<)~IY<Lr/`|QG18QI-0f.8 |4ՙ8;]MA -<)h}7)zL7Kٞ6~{W>=hz? olû]!WZ!Wn?_S*ӕ_vX Z1'T~QX9<L!&QH(0#Mo2RY|4 |'/ja>#--cv3dkvprtmÚ4V썬xkBp7x\|Tb|E 1;)M47[L '8k:By`sNA$zn;Jܤw\Sb{r2]" m6 yuiFAMJ{ԂcC {%}lI_aX2K~y+'vm'ckQ ki,`v.:]C,/P")Gm5!+[H^T<mCr/T0wBE].w^^Za5yJ{0LC^.-s9@ͭ<{4̒tKCuO0Ia=5>cѠ/.d.x"f[ׇi&$ 9 TGvF'k6t5^! {p=✝ G7`4:?t 6c!Rzܘ9!ԱVM* 4TyڦЬ϶bwXs%<<ƃ>| vrO7Vil% a mhOTϑY;|!a}gZyҤlSvY;Sg#v;4ct:S/L?3Z#&@d,`LN"{ ϐ  3ZDq,+|TVv"v_: P"CWf}Hy")vŇ_5ټ#A&QYo[j,uڨiᩣPďjlw/s /y>yݑ!iqR}ao0{m [jM/l*.K7"98¿zRn\2dWljGP(QP+$h$Gs ֊OYEY9hiLT|S2Zw^8PwMC8XPXzh> VqΥMDd'Jn\$q(WB!gx#;:.6l2"vA)o!,#Ptn6!E"b?ivB]plrz0ÂUɇV 1U->rRIL9twq** pKOsSL%ƍU'<ɘ 3L _=0'A6~}}JM}I7L@)x4P־dҜΆXAFFY$NI]Gb']6rȚCxT|TQ`#L> ~f+y {F2$.wI9j3rI`aP%J/s'\%~o;\Kz-jTϜx,gևh%u|Zߡ4/Pr;4OaR<}U!gWWQy T)%3_ $? ftFv6v/0 b|7{%˿1k&eҗ J ZHC=4$m͓( ] wnF)|~E13A?s-,uޟc{f1X()y[L|gYtUn:_,q[) 3sѴ%Yl:6O츲 h^  t2qkٝnX8yQ::5guyP+.|u ؜LL!ހ錾2-YmZy*Q*93%M0IB*}2مQn?]pt?[iL$4 M^Dcʖ]AX2iTc,ӧyBb1 FbbO|\fר0= JWQy=>@b#Ky =uW[0L[_Q&Bߺ^P7kH :)GKE{4=cG5eidwl)]ᱭ7o8%xl&AbNdruA~1pxE^G|.o.>N7NcGC^0հ/Ƅ`d%Ll<&F%>m[.;m$ g*5,yW;Q:o1p 1]csDPߡhH@Z^lV{f^F` x;yw}ܲ@MH=uIB¼͗\IyO'l;W K&oBo F-v?ȥtlN쏺O\/޶=LY0yeGb8=d0xKQ~@͗Pc/^Hw{mF]Ip@X&Q5}mAp[Mg}/v8lk7qaCڹ{"abl3]:Zp;7jc"~9&*+ 4Hm1†MpjAt|_ ]Tz߈0 D_m \ =>"CjW^cAb]kRsL1d•,q*ާ'fGzM0蜣m53}[Wvuc/ b; b"uG&8IM=JK^ JtFr ȶlhExs hd6FY5%]X?>e P}y;FۻwO|/\2+L5ן9wY(:$_xW&Iu/fQ$tc<竏I6}Lwxo؉xF}Cnq3!&4v8I`fA$8g&ރ1j_B@_ ]l 4D -!x/,b2`~[q~*Yڨ>Oo UKvڼ_U3fm"a<@>k>FoD+$ΨxC8u$gxJX^GTLU2]͛ pěu 3aVrPap*ѷqHl^@97əsRDV!:G L`+2:ӗ{4oOЮK${G{]gM%oĶV=d*[qvAM}oPl}Y۪lk]Kpݼg&EB]ÒԄmG$$mY4ZIؚ^wt%F\kWҔzf &R=_TGggo܊ܬpU7tkk{POqN-UDoSͪfW'n, AÝiT{j*G+ZO(#DwgCyd\8"3(}>hp}"~l|mP@5+G0C۝`DmmuWYG`*g3Glƶ#}(U73i xy<&5)[߼1*6Y'NDfÈuaEEm+>;X'oE|]^DJ/ N_g.eYaIQe36{~D.t5vs j^چZJBl~odSg(<ʠyҘ&Ľ5_7 ep2 誘6JT:XCNX—PI n ۸piuf $ikm w6z.oBd OlMU(}ng4it]Ja`3x֌qgQXn\)O:ua?}HM$sWMWz[?'楜u:9-nf {צ`@کo XUzB5R<[ĶN$?Ĥ@s _uŨeSzNOZ!]+6ja*ϸ|Md7wھ%18zjKܓĎ,fxejmW>+ $!Vq45`d(fX|sP_oqԲAk eku~*:S)jGozD{sX ["IpI{4vݹoz[Ʒ75ѲF md;of{n eM3ELb-߅?(+:NxlBΜ <Ճ ^6 ߈,lUi[X4@xڡrnw,o8(r/|x6;g)jJ̴>DClQxO'RHa^TCXpL8 ~^ h Na=VY(.xPjM?lwv+Pۋ t Vk..}r<g @Z{_^vS~G-|݉nPm;E*jH<`Z 5_`0lZF単fވ&+Cxè l:FtE=0ӂbC2aE)"2#)&8Hxe^jua8F~CTJٛRլ@y0,@<5d>O_ g4GLA O],m.[?8P;ٮ~vo`uu _8抮*8;G QmOX6׺)Ž[_nnz&U&ko5 F[?K$j6ބ=e 9.TBT4-X8Ȗc Z1BM[_F)1:넺ˍZ-'1`oaqL8B]*:Nzͮ,Uf&f{Qu/ n2K+쏒%̙kQ$z^mf `e'J›>FwpF юUuS*MWXgTd5z0'N2 .jJӑp6a%Kk{.Sd`c=D\ A{MN}n/Y'܌]ݪ1h&.E,ҐP`& >(qDo6ƤI;*Ê7~ gO)}m>ݐ׆Ώ5^9Nò \.˒N>c(hwˎ>nj+x)N͖3n?n~é2FRzbK)GWy\\GϿx6+ SuOr$ X*neY=As Y;tݏSZAfg/]zˎڮDZpeϊL6KABrOz~ow:N3xB᜿ڂiK6V]jJLH%˴2X\(s7v2:IsĵM~-SLչ@RuV Gq (# UbN[Cy%k6lɢKUDe/"<@-pFbF*(VÃPj[qME1ЧG^; h~"[`h>`PLFrCS DL3Q0F(0N2p`/vgQ7B{UIxv@G8rID6m~j@5~SސMaN<viJdZoֽwG]pTR?.Rw tc#3^Q'#H4&:cOxjxԉ"UvƐcV澭:)dO\i/뇗X8d]\JbVtեa(rgMn 2,4囷/_H @tf{!,ZJKҥ8.pD|H}Sea<ǩTD)?m]Ïz6Ψ0 Zj`='Gh^5c#xM +wH\3,=ʊ8p7/?K $TbԔ V"l"FB`h^FJSAoPz;izZ,:*;:ÆUcT8Kp%Q$` f3HgRbLZ$v`̫˴}gJ5MnC]dV!Š{BShIl5juqyXyg{$ԑD&rڞ2,!.VHx/-@h|u6o )BdFi .)qJOQǭ+w*j!Cﴎq?iIFG}^ #Bu E1<%mšWA_6ݖoQ^ PK :Ɯh@HlEY1aK]H9?߈bi2ocBm6P-6;=fN$e"B%;Ük7 cRP VVkmZHnGhFDި3cVe6 xjZL6di ƛZ &gށaK(^3lkf9°H`1Eqh|1Si|u GP@r?x:pݽB}@=SfX0uAABzށg!H8b)6.l;H?cbNqwN![ߦ,v/{tݛִc|R8풣/6d1>jyqCаm:%pgp.!.!LҬ*@ XhJ>qНE%!4♖~9r ȀF qfidn^iWjIhŅG4'XIC/]FO9Yٮ'L瓟V#6I a25̞6 7A[c,Y(AVF||w=y3_GÕMW\[[bj/z i4`m?Ǘ[ )Ѽ,E`?̅WL;`Ӕד=)[\B'CZ6sMM|Z>KͽWtWjYTZ2\Yμ/7)ҀH!rH9&^omlZÌ'w@b]2pdȄd7r)9wE,OU/j DP,;ާ!Kx?WbVjK٪(5veGD1aYaב*U Ф}ZUڶ d&pq{.I x̏a,%T CO`R$wJL-XƆx@A^쯟&D)ˆa' Ehx2*U20bK7|< Wwt}uUD-4iT qYnǰ}ACPIBzaswjQ;⠊ĔuPܸneMә>ї0-T|qyϕߵ ({JJ6[AxoRIeiXlYgb0.nE!Nm8c($ 6Ub,{#2oVK}?t.f)uoAf*Q%?Ʌs ۪ݭJQBL06>)?ۦ[[t ?n~StlE`0 I뵸?%{Txs+S?$iHN! n64TZ (~?-hX?3:}'6>2E$RՈ{;2Q{7NdY^ľA,͗G&IP]`%:*>xkr?([_-/(AG e}dEZU{&x89NXoV|ߡI^) 0<`v 80g X4?<#ɘfa )U3'')tUm3t~' im2=H#QcFDNQ{ H/&0&$}m7~E﯒U`oaAlƄHS>>7R}޴G?!ݑB+,&f/ZPʛXH绰|/PdʒDh ¦i f ț}{(5XA3pTŪ&}Zgpfna6ʒbU؍2f,`C^jMJl'$LŴki ,^:E=dԵ\Ut{0N^FʾVH}5r%uUÕC[v: 8c}ly>V%&J(3l<}IGS lNS KCl5TTI3&=)=z g6FY~6AvNλ-1K~vxbI)Vg^,jGϵ%rmK7i Vpvek( er4_zhZ OzP"#8 Z`+ǥj?e;pױ#֡~UO;I `md`Dh [$ˢV53YhںY$%Ѱer۠AI7߶;$UG@> 4Q^Hɒ CK*nَ/3ZC/wu}[HHfrHc=` !N/~xp"3]y[[˟%d/5>B3ifb]?LGB[*T E3KB~-İ#Q{ 셁'W[XbHwepQh(-~}Jye0RM tdc4N byYb0A? ŀ/AZbI]O<ƑS5ֺ}-ŲD)VYU+C 5LyG]>18m)ƆAL.3=q0xo9V UYlQ& M]<$X<>bڋg+,7kt==I ;vJ7m,nE1p'}_T)vS2X{o|hLPz{K*{ڻԢ0zHevv|t+N)6A+"0y 'ܐcҲmQv`^ib /./\o37JRHg8 z7|df=܆-Z)PfO :iձ-0S*8"(Xt)^J'ņusOo_=/ -Ci8=0))|z܇ZZ_;f?Ėc`cQ>i#mv@Ȁ9P9>v '?ڮ Kͬ[n3Ø& =:iD{%Sa~pP*K"GHL(1etbi3;PKo݉' n+]l$~6qGs;OO@6dQ5}.>n1T+4lq+;*PPd UgLy_o@tT߃ET̟vnYT>1r Pٽ.yWTqUbH,wԕB0N2ZAyCNS5K͹tPn}djJ;w[@y2k> ?~ȏ;^gQ-I$Snu-3LH}C(Ad0Éƶ* ȺO4b!/&ca녙ަ/gBNKb_^\RYJ87xLlQRuΠ`\H)Y0p񴱗#m`_>Φ4_M`&CAw2nrd]XXclEMbɭ\ qMl"d#nE?V1heE*Ʋ,*FDA6t!ٹ꒕9:t7? PDɜi55mѾ<"ymx7esT*¬XOQ>v]H;A]}='K-CRG{KvG7jJ3ئYj6KR`L4n\l;BE"h U4f)YYtz#\XuO>5/L=2r kQz!Yhcs Q~P֩NC /1en<OM(!:qkFkK1Sr`AϐfS/ic09 jP ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cOQ2d#Creator: JasPer Version 1.900.1R \@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP]@@HHPHHPHHPHHPHHP c߅Nhv@MU 0cXjڝ=^ᨭ,τ_H̫E4)zh GƔ3nëYYLGHdWc>+sF~t"!soͻz2ƴCZBs%&sma-3-bE߅Nhyr%CXq))W* V ȦP:9+-9SSud8|u6QvW9'[Q\ ,%eG3OKs ɱᖤnQϸ{Ӆ RzAR0z)`r y۩bU&O7eH6q3?k f6/Y{Brg,Bn$ZᣖXk,AV{kO 1,M4#KG{*_1_'yA6PB+`j$ !D&`|*`+K2֏&X;`mOQv3 y]h$u|KU lHkB$ƞݾ:526N`{>(сۗif$g'|>sP-:Ȱ" $wZQxaJ>5:8\S>?v"=klN֝ypi@+<?D< dHD ~e&4_ c# )! ?$D9|Y3({D'Gܐg*.Xb^J9Y, 7"%*Xܫ ]PO`=lWaMupJ W+ũl=!F{qkF0ׇsY&8~Kbxltq%jpߋ9~]_K @>i˃[~^TksSZE_NyZ?\nTzO_8QcwӍ ”pΗ ߝ~tL~`Ub߇YY?<3n;kؔNP^4EQm67fbԹJ<Ƨ M6\Y_E ':z&x`f/2nqΘ'*Vp X3rs'{M3 >`[朔lp ,+QY!^k1aK`י3Jc( Ԑ*m}pʤ}=/@qn ˙kONGXFY7k~nJѥ$b|nE #xv[/A%aw)Khh k`"`<ݩTQck>Rks߬0#C̎9 ́$< Fp0-);A.jߦ=Yq>Jã?Ő,!x3A$(/%p?i)4Ż#]%.b3_:`'` (.>:KJtj}ڭNy_x2#dH2H:В< ?$I ESĢy@GA &y@'[HJ)4~4]\ۻ Utţ<^˻/`[U܌}'zr |'im8vcP<@ݱ1Y”};uIN5(0Wpuf jF̟ϔbw;l_)){#Mo5öYg\GeJ4r.v+%6#@f<5bln g vՍ.G=~5LL4#ay{g(+T%c8{]v Ѩu*N( ds!<0}fک)6UJ>RP'uwcHLT/Yo)EzȬ$6*#B,*i evk#!P/A]' qqN9KZG (X`óLuA6G*K{,]lK2 B{Oe]aGIs|8"sFGŪ$ {|wӔa>ubBrd>cwS!q9]?'pN E3ȩousw8!H.cIeֱk7%EvˣTJS֧Egb<'14RIMK'-2,8RS_fFV\Ё WŨ98y(M9ig~2Jt"6X|S>ٿ%U܁Χp;͘%;汓STp=1 Ub;k̛"xXdk~@LY |,GyOzL6>Fn3{!uj»q$C}mE!0(eE) \^Zac cG*qưM1;S ybn㒅"{?Vq V%n  PxN|3~= Gcw/irB!ʜ))DxzUdjxz^W\ qefZ)/<^$$ [p)2,)t2@ZjW-fn Hl%U#]sAdCJ&.t*'sVAxۜVòϥ0 vc[:ZγW@pP%MdN1迴, HHIl( 4 f7 I}uݗ ڈN# RVtlp q&0-?2 =4(9FkBpWu1Y~B`}Gܨg QMc9<ҎfI<ҡ s%o{Gߚ(Fv/fv:6ybb]pz9/[v54O)H!N? p#mfZFTe5_FXxd b)F bWDHlRD<)P/?!(kuxl5a'T4I4H _C<59{ȡF.dYK\0nJ9e̩a֖ OUDf_Zå|vZG^`Pj}.+~|""]_U:"= 4 ~ļ<:7iAw>7azF#e~Ș,HcEJB+o8ɃKrK㹹6 i4 3RS~ ų@msBӳ3B(=95{]Wh`D^ӽA9x\7@>1cUZװg$.>ұz _)*?zɏ}#0*cO"K b{Z[<2!EklD.Hm zcnƓIBc2X/ݠq]A=$Lj!A)K"AqoWrܑ:;NSgi~T}GݮE³;x|Ič \~8OOG^r{/H4;?]:y2ĺE %JkK]1Y[vsZ[Q XּK TLLMCh|˜L#S QX班w}F~#HzJ~ @U;xGailKLi\p^ _v W9կC\ʲYo gPEOCԦ-|AV :uDz7Z-FY'k=!7V%^ ;fqIyZ6?t ~D.W=pC̉ͱ%ӕ,lPEc5F]83N=9D6ꓷl]|}Dk ޿p Dfz`X8JsZƶ?jR)k&܂nI-u:iL@TRkl/D0GXQKP Z9"82Ac4;o}R80fFVNBsr:Д>–i[h;GO J=d=iWOpm5MLE~sB&(&:Y$}"~p9)Vw`RjjD [O+PclLJX.WNrP"=FE3vh~I%) Rs9ATbakts*nWR;I3xɣ`g-磚w BӰѴ`0ǟsVvƊc.t g#MsYLȹ5o8SŖȇT[<=YYl`:0~.(u؉}VzqQ${@9~ QW u3a8.!vUP}KT&do%אe /;q5'ݠ%;j˝kk$#GVj] {yh#4;^Zg;oi,SKr9=i,"F+#oFbs!qۊ#,u?u˃y:93&bozSl!JoA$o!Lu2Qa{J){V'S-D1Gk\lH^Qk=Hҡ6-\Wh1=k2}J3o'/O)#YAδF/z"*6{j:Wydk zK,iL"FX;Sͱ/X[J ݦBݖ)j~QzmC6Mj@|Rcv{?Ȉz^娚8-IvA-}]18Utۚﻘ.ּ)8H݅dbHfgUUl(jrS *S'&w4=(-cH1/;|<-t <ǣQZ{9yBjK),/B!իbyYq.o2:6І<@4>q@$+v!9dI%(_Eӏ7Vr$B롂S/ve-漟nle-L:sQ#zӃy# Nm+'&R!?D-cW,(g[?-]0 6^KG16vIW"w_`^6!9$\u7++% U5e.r2-wHNջqʱw/~c`pTQ8$aG[t1M8j%(JC8'U5ŠԨ6ZS떲ͩj <[˸ޯGX _቏KKa2v@6ReJ^%bs 2BX6F*qPLZi1ĂcF#%_˘Z0.whժRJw% `5f;k5c>p2xqlgoKnŠ'J-dT5X'!f/JvgɾTZe:_vu1+v=aD33y)gY}ϟT~k.zԟHnIt*qjɛ#x'c+ՇY*&+B./\5h ~=k7 Gb/ ZxqMĶTuj k!z\r 0ʱucٯE$BÚIG:UG@9]B3e2)h-^7W&eh7:E } 1Ue{Yw mPE>q8Iϩg&HɃe.W_k3z!o6=dզ٭hD S?Ҿb<mP}޿ #1 #p p>D MâZ:aMiߢNk^IN~;WCDzF7HR O ju,'u% 4&ٿveGFGR\ˡ!}3*rf~i$+JR_3?y&4Z KK7w.'{|lQd%(@=*5w=>$iAsSٷz\Yt_Eq}۲,SS5h$bNJU#x~^Tv7/ǿ)kV{M͜\W7Ҥ!*/휶YV-"ZSOYv5ǔG2@)"\%Tׁ'+=n1=2R;\B5\2 =][TwYV-E({y<0~4iKSb6xZG~߯Rl(jXh}Pm@HPa6\V<$kvlJ_%:ϣ Lbֿh5.CMρH}r6f[ ofB3;>ͻR^kg!܉Ov⭨Ny)}G!oq.I,d}=m'de)9኿Rws R69P?*Iq{%ԧPN]a2ʏQO+yL <M]~g8XPkM}AFQ5C+Ejn8ץЇ%3D .M{92=6Sǚg*7u%s #M8 Y-b~ <2~7酩YNkzVPL@E("am)D_wk-S82ȍRf-m_յ.i bӪƚ{Aƙ37$=t*}q B+a>ikC*sod<1gw0SHȦ,ߊc(M=YB0(lxK+ֽ2▇j 6VSlPIϖ,TU~K!{AKH߄^*ݯL4sX$V%a*$ ]^斖"_  +M8`D0]v` ]:4)1!͘ş-yୂ~<_Dx?#J)_ ^S9܃L7ĥOsf }֦bb Znl@g xkNTdV\Y -4w| $6k{$fƶ,n5\"Bmd'_`k(:WI42 < wɔ!y$(I: a#^)".ZB!7DPV. w01؛ҹ.%sb񮎼6Vᑎ2 2S2)X'_=C=w?F2yt 3O5Z˖EfHo94˱at&Km|ۗWO#ׇ f5 +Q-ߧNlv:OP| Z:}AV|_7nv_5PPC-3͸זVjz* .!OENizPE?ks8HK(JyQ,d5A'P"(aJ? tRZ%`=IMT`[*MO*~zSH6dbXtI_|UYcqq-* ݯ0E1]45yz іQUf>HU֙5y0తm%Hixj-:Cs&o82/1r5;Q`rk q^F&¹ gmRbϯĩpǓ(ƅML@uͱ<^;1UW۠$XcFuf ]&T01??=e.. ՋRf?e.?@bеe_젰jZO|‡gd̈́8J%c4 ?5 >ӖE]鳸[xhPbAi׻QD WJ t(yYJ^氃C>ՑCj̎s_\G}3mXJK"Б`g.imN mt ^{"Qa6t Ԁozߥ@o] kS@XؽR5u:ZE AF9IV rQZ,Z պU5z!ɑȖQ`&QA''v"$H1<_an= =K3wHb`$聦SaD2 !a{XHml@`f%~8Ap-^j0 ϥ;?|<9Q^'ju}QH'$״)kApej8kT\!&m^Ђ"uNY{e,|.|]&Iʀw/ǚ%aώ&2cchvHƪdScw^C@e gI h\XXur^J"8ym!҄մ6Uր̲&~S8캮i(r `G-xÁawOݵ&fKO2f7`G)l ۤC{WqaE";[Lb3@Q^~o0J6pczm\֘4oTh=>_vshF(9xUЁu(Igʉ!" aK-ȠߛMAuy?deЗ'J1`p g-vVm,W[&jj:#Rň5{a]jTw͢аyN Zq8$ԛ@Kx{@܈Ǘ oL]E|窕#&s4ֶhY[r"Lq2P\؁*)yAF6~55S27?ݨUh.Du8]= je#BܘŹկU`XoWIBZL ':zϓk,rC9`(B69 xGyB{]vi gsZа80`𡌀Xb`=S~j"j=Ao\^ij4)zVqg2Bq[jo< .S ;s{LU&y2݇U8C!4e!!"TfV〧Lc,v\vRiH,L ,n]^!|_3]D0E#fb},+Nfh Z[?FrԔ^53/o)!}&$d˅/P-SNE"}VfwSw-dAOqE9KKЄ`rCƎ͐<3GdďV%ҡ2%:K@c,fw&H0ضf'u$DF q5{:*uhH?v0 Z8"&?k #JcyD9oG?[pQV{^c 0d>6$Iv V/  gyS@5Z,p.ߝ70sTgR&K!t-q XGjٷy=IOohtNNd(5GUV$˖4=FPjNTxv/W0l.hQ̎RR$hZ=2,cғ~!6ڝ! $gX5x&jE/օPP$N'm+dEWma}Ιj)1HDB`eS xC{DH6,JR;t^o`Q^T0s>86}Yp5mx1=US[6W]`GL.>Npg=־MXu>\JjIÚbځZorٲM˃)dR Lo{A-# +4^Nl:ȿPD5t, $脈3p5BD)U=Ndn-0q(ޫ gIl 50B,w P Ҵ;w 0bz·TL왅;n~%}1z`´iQ$VhJXYHxB%m̓[{ݕbk`-|rDqk^KlK6K3BnNRst峻6bti-{1M %`=(*_fѸ2!':<'ĘVַZ*S0bvQ'k~J4Ϩ?%uo2c*j[WD|?.cܧIa0 W2b6|1$OqAwiG/k|]fsc|qQAvtvKo_ H4AP-Q$ZEi8گU_q3Ji”Ч",z1LrVx ]mz|pA )zIڿ>U ewئ{*v 9(%0X~`F@iN#W 8Vä0 :K$g}E%{qJ"j;񄗫ߝ>o"_S|iEO6\h)V}`WIށBhd-V)whLo4'SdI]N1zHW*>mk\LD,d1W+{ȬJ*o*?Ei5oaYBO'kshZ͋d>Ug=3pזӆ}J-9ΡAՂ]5% 8YvjGu7ҷ5krE*J] >m8b{܀CPO(Kree|~߸JjuR`5, C cQە@&tn0=o[Y DI4Zx2SyV9JK=5lN~A'=U$Vs;c7q/5!a,rǿǴRkiu)oQjb8?ײ]:  .Qu+sەVzihϽӿWz [l뚇 %[g1p3{W]X~g&lŦT!r񷟢;jW9Iw[P,#=ЃSa :l hאo@EK ido?l$ !QjЌDmh.Խ55,`V5GCiu>b_&O^V<֏sgWdq6@"O\hD& T_هZ!N?"I  2T~0P}ҡs t`uJU 0Q{<<Žk~9`xDs\i.5yytDa)srx4[dڐޗ}pd"x4& ?`B/bު{|΄"C:h_{qJEӽ?y9|klQ,m0=;{sF#B LiofJHM*x=AaY98 (ã/PJ2q|yó,|b̨PJĠHJ ?uޥLV#%WJ [=I5\:*!3|Ɩ^BI׿ld*SY*8*h6C\UlłiOO$ !49 A^_-qb2>i=,RijhNbx6xS)MMjceC2^0D'}$ -{8EPYaWW91~C}{!Jeb0Oy%Tz Z \o1j"ĵ#X5 SR3;<^zhfYBnoDlD}ΰ@~)B L,֮ΨVT}16d]B0`!+O_ijtb)OXM?.ؼNW`u&&#n}Ec{[j"a??Γ#P,e}LULedS]bt" ӱ '5.Ipv;fn=|31DL9^t]j\ },SMx$t VD1HZA[E?l|Khlg(8Ivn hA.G9ʿAn O K/"`^EE%zGV5ؑ*s qaULNk "KU'2HBE¸{Ul^o۬髩##MB,]~+Dα!ڿC_`qrRyD¹̬(_ 3YѱHq1Lg8T怆t*faM!~<4jVVf"rgIh*GEeDolF?E6οđOx˱lˑP*ЋcN3"Ev])̥ *;?aYݞ Bf(o[N9C55,F#!ԂR8Q&Y *"-1{kLZoA/vltыТVo5N쒗Ubazr۹HNEm覣2wg,))C`LB} %y7%9p+05eمl3s8\p+*=1v?8zM˺ҐLŠLQK03q1ê~ ]Rf3쥌Vd2XD^ Ut.R"t f]ԞM72\PbK#(ˡS4}]3\7ae>5]t{fcF!2IJ#u WPV,[۠&=A CK @z#T?.pŽ!5^P g΋ql;]N=X mӉ֍x/TX:Pd&lPFTL:#]_æK^?hGٙϑTxѵk9cu\t_ lC`Ob̞׆Ƈ=`̦M*B_5Yՠ\5M^cQ j)ŏӢ|BC)%QuN"9|&I,襧Q B<S1Iqjw aX_u=['bKnc M-탳CkPj seV:Z^ -Y?I H+?Tq<'gPvo}fQ$XNwKu%2?ihsVSAofض젵c@Q7HH j́Keuy3ԙrv&u[҃Ղ]G[i1bߞ3,K󰜖G7 &7|eCI Al&Y7#Lz2&Ja>;D8! prnG1q"Np k(Z e!fCvVڞa oU eT%KfWۻ`YCS}.`zf}:RipLi,muwl3D*qmAq6kv ~ O 1y&Qσه'F&n4Q iK2<10daC#|hgNET'vaK. ex<3xgbg d Hfr^a>& z WzA8 A^DVH q 9#qOx}K`CU!%"9Y\6.%M-fU8ߟL@F@?."3t%EJ%7Q7Mr^a2\Am,݋Mf83ʿd]^k.}[\rn Z;ti Rr;?cp9dD HT:r'_[X'aِ"1#!\80Qa~W|?k Tb nO4V%cPſ[T K2aG7Y@} `h.A zZf*; |1I%t'߶ 3`AC ǒ5}qGVk:NzȩOc,](qTܶ^&ҚU" }i/\sαxzMߟ G=x]@8A4!b亦njd*ۭjL@(,~$x09YUɘ"*ʗFJ1u3 o3 RNhF7Lc>:1-&#XVd|iBċU1nJbI5# lh=j&܋MdB[S: #΂ KeiYwI0k:kĺ=g"cC+Yú12jwu;Fgu:ru :}VzgN_ݭTjk/:`FT>n-`Ԕ85\oU4'T.F>4er"_[びݢU3 n=w~[W(B>Q»yϫ58`L$BɿC`Q*"1[fL0 {Hv %Sf2&d;l8FT.5:"F(o_9L}T[xWTbLtmtLS1֪V#`f,~ J^iBҷgg~nB2&zDl+#3Ȥ0u9?1O@] b;?Ymv`u?Yft#*O3n]wU D? Wfdo5C.4D7HvFwJ'Zۙ04Ċ@MT+”vuN IH!b2=^Z1dKI vK֚,h9w_\N_Z@%y8[uj7?$,rRW~'7(XHk?tά'e/܀JҨ=B4 kVy<* 0x.tܝ]zQ"^HYB7Txr֥]tB]cT*#[qf/,DMnzhxַӇ?9,o_ȣv/ 3?ÔqGaѢ,^K7@&}#$>!= UWUihJЋ}یć1/30Ͽ2EE|O*n{cy>ijZAnjɯ0W'DX7H6gP>Z0qN.~xTHgҸ;]}L$97>=o2uJj'H0 ((FYoc-~ (dغk7B~5,nqYFg/iЬ8zRK{Kԇf')-x~B4 }|~o@5G} P-EUx ҧL!X@=ЉįAa.X% ,{6n Hed<@+7*m/?2iRi"n^E֖ vh]|\&$pP*i@Z1K9BR?\p]Z2iG%dQocϹ_WTo4ޛOqoLC6/ 3 ⤐Jl6WdQLBf' 1ƁvM!%rLZ>ypB֔fnO1;|;iģelr,\|XBN!^0MBS t1x?}sM4!Yt8JΈ$zU> ٷj'b`;ŀ.2L~I0d%~ck|{Y@AG6*`2jȚ9ouyNܗO{uR^_ ""Ȁ`7}#jd>:6,6ذ}#!) &P2tiU3L+ۚ \ 2M씣V 7xq\<ڡbuQ~W^r 3*YG<"Ho`xVE~KS5bn{SC3:F!7UR9r!pgR5%#7F}0>{AF&B"&`]/fO_V.u70֕9h&zRs?$CpXh0/o3_Mܞ)Ht;ײ1 zW[e|6txvnAQ/ӒYE>]{.DqOa$otf&A:*)3ehoAJ236WtMN2HӢC/婎[|7b65Է`ҼS & d@xgz jP[=FӖO<*ESf]Q.r 1O g?g~.ܹ!]DOFh@>.w*; ?~r?xiZ Ҧm@د&k+m#nj'6'3k$8ոh~FQ( bg\>ЪI*"o# R|A.%)qgSA l@ꓸ׉o]§w_$Dsd7˫4 Erd1-C^rjd1\:DL %G=1F(H~V!YLl_HuuFj!47)$hֵAٞ>"q 9*<*XD#i(N+?.Xǫ32 Q3Ijk] Wو`;'m`{kzp_M堪SbWKhovt[N'E*b8FI ƖqGXk6RyI mHp5I}7x&}b ~ұy)ڒ|;}6OA#CG=_,!Lj(gf@.*Q X>Ÿ}Ƣ>~i ];ݮ(ǰ S) a̪t X4$81Zm>'\9hR oyj);2N*08)@og9ںL?mxv 7)u2oL aZdQć)3{ &{/t-Fı~:Qƹܤɵz\+ؑӄ|ѩǐO]cm&2*X;H_qptLfoƭe퟽E"ި<ݪ&1v13d)b.<@' ĢXU~2&rcE"h&]qJYCT`j$rIh $6X8r =ʠ*Kx 482 O=m*߃1'-H#%0X7M֝SzȎՎ5/pLcJn2\eUq+{3/X7KsFjoiyW8XoДpH1wGuoԀ,3@RpIa#|ub6YP˨ePΛR{`O }*GDz?0d,&D[bpǺɱf4:r+ CAE tm4/ >=jb "%_>Q|~;|0d\!-f'u>=Z$QoԦLv{]@,98b=_acL"~=ebKv.o,rXUU`Ւ+|iAcи;{R&7PyLMȲ 'b/@E+E#]RVm(U5%JwwlY -̞n~_rJB#PX?5 9 oLޥbV$/Fnt>ECS\R-:X .2ghb,,]ae,M9& Ov|iaxBE!kU>27_L~meɕʃ"6сlgȘ,jt':tGmγH CpL&rYRyg#t@9u .ꑳd |RO-/\edy}`&)!ø+vhoR]Ǵmd1NKx$+e s@;Kk= FLꚲC"FESr\|SGe[ Nؗ&wؽ#7@5cCv 4Seۍ( QvSA {hKEĩSsO,;Ns>1WȣY'a-]^P6Ut5q/CYYiD|cJ\|WBaOV>dTzY x" pvB-Xrz^0TsJrts"Ƹu_MV&Έ8=4؁lsfEs_^&G,UvG ֺh*4f]LBI.&9&#by* L۰[),b[9[N 6Z욿K'Ng0[aeVϞ^ sƿ0[OZ~tL{o5ܑ݂G@Ym-ED]>Ÿ/% yiD7c,*ӈnY]ВTsRw]TDLܯY yƎ1Fw+*´對M|ﻫԨw8ȫA|.A,r % MX^j F_ęרe?=,Od072*0 v:T5?6l `ŧ 7@MH ,X1O|(k\vņS4'w$4!`5o,0Dl A_uPH qeQ`72cmR< FMO~;H8ˑݣX/;}'0Xb2ofWG;@TSȼ 2E h@)' %ט]H#ڝCfZ$%̡q8 $wle=9}'ǯ=ȌYe''F)qHVd:/E5nqyhDb#Iů;=v&&vJ"TI`i+/4eͮRJB.6g-HttLTG̦|/Hx}7dei2bXk4(o@;JW']*  M<*%fU?߁Ϫ[U;`g.&^Kjs%’t ]PyZ+4E@!kDtQ qUb A:}Bjag L̋0N(o^/ :ErHL-xؼqyY$u7Ό0xUrh4 L޵E#2x!%ú(MvZw 'U= qs/P;Wllȗ=̚]ڋ =w:HI CcԾl Q5Yx:٬NlM\d4M {pF&N\E#6f7\mYبG bg"6Hxuf&@,1C+|X~ن[ k^i0ܫA\;5{Xm."ZrZCit;rz:qCkS4̕ZXܜ5̊F`:}k<|o:Ȭz,+X53=*G&׃17${zk[dș;8kV_-g,lgF$(0a\9.@c̥=|RY4+nͶyx]', _?erX".#ss_M1\w݆̟yUqRKeM,9aح ]Mv;;GϢ-V]٨<@2!8_!_f_ʁլ?#wIիWQtv<ݚ3f=l].52 tL@WSuǹ5QdL4;+ibO}KaUXb,"gGW̏)g1[Tv"עO`vŽD7 2"M^wD U-4f/a!wǽ% wt='jFc̎Jo秓0kcI;͙xMbOԐw{u)Ⱔ@pC(@Oe1s߆jFSV~C)iū&'}Zf-06%?6 6^w/5ְ6vnTׁ.@gZZhASPNAcҽ7ؠ*'M^ Ng№k,6w,]-7UGdUe>Ef yCxɝu"r?$oo۵nزܤi)5ZmN'OEn  d:\s/1ۄ(RLTO@)0xYr]'cT.dUKs1ɎOpڬ~-Ѯe4Z4-8Fx$z!dID?--.b < !fӊXBR9̠Gu+x[ӿVobGp87Ymż-[@yGzxb[Bdji"KIV4>P`Xv).{IHoI5qR|o?49y|zIWT<*~Fc´̭bJ{ӂnO:VʥyT|r!,wuͩA0}jw̹WƋ VqJ)WLS7,lؿ-$ṿ\|1HL;CFiKi̿Xr{K[AOlZ5aczkWI$d*U+u~cxiU1ȇ4${o_7_NL5[@U~f<FxȖ;uDyAM +훽^9Y~4YI>e L,#WW!YH2VhHțx2^5YjT7٧tE!oWK(%1!`5^fR١[8d,]`z ,}戲 Tҿ b-`55sFٓX瓔"yk(?oqm&}=QXm)}XJ;CD`t(h9=Pn y:Y4 +ˋǢ!ϭ$5IVi2OΤ$yyCG ?3toB%h]$nՒD]8WO vafA9ڦYr Od`/A|EcWm;xQ3{`}m0Lw^hoU4`r2۵kC#F5>61k ͋ىM 1s\Rb_jV[VT_Z,$+%6iΧل5+dSE8\UCjp÷qDd V$Αtqtn|c=_!w,<00`$JǨd[:V)4Hdo!M@ )]77.0F#a1EGٸ '8'p??oQE'Znx0QOL@`9 ]X);#׬6tm[\Ֆ&&}KP( 8i1cݔ먏hV_%>I\O*wHak]81/zG76+뷽MNecGMRPw $g}|$]#hΕCkVSJtSdx MjT-5}!Z ݅ R3)hE78pȉrEo/IX:YH'r8ek'I@ S8۱n sUhB{NAQ 5塎b6eXKNZmNJkΕV7,Z9w^m{+_oC3%2+B=v_#Qu2F~f[;eć`v[zIbvЋXl5=tY:-nHʓGkpr_%B+XtQ52tPe;<0vz^d:KC.K"]pOdH2L&+ s.6R-v95o>n-ڔἕa:9 ;0TSz2+Gܬ^dtJoV1RnPL?Щō0|p+[1\[U)V2?,[i-6$Ek pANWPG%<;21ʊeHփ{Ddq2Pq 큫ٷ]` /8H?VWj] BoEQ1uLu9wvMkCWci#d!6J3/t\EC"i$Ӛ jj̸#ɥf:lT, =-(CMSXkrŲߊytNj^oLFdVM)zCQh6J>1%-廛LR{mO5"է3¢($TK=싀58 _?MD!e\ԚXtaf͏URKE/hnC<[y-XNwnj]Y|1]`#/ths@Hͧ*Z9Y8Z*׀ LV9zU{" D /Cͧ g'Fy3ll+-[[gք8p" Oy[Q .IcW: b$7dCCcO2ijwAu G[ԋش~L]>N( P {>( ۚ-yӺ^D!fe8)p G\xB>DitX 0}O8Xs.̜=҃Z|ݗ[]xXDgŚF!{*As{CCBVhZAL9"6DF>f#l‹$;7Q@E#pA䣊 ﶻ+ 3wr4:vLa/TVw[וS1P+8Nե@`7b<4hj;{yA)![~lYq9^Bo`zD+hiy|[2OVB(4wֵ͟rSl[c(Lޱh!(Z1WE^?)EX2w%;n7vlCl&ѽ̞CJWm4v>Ɂ?fcr6k4UzG0䶀=h7CDW[i/Sc@`O''F0MM1{ax1Iǚ%=#1[+?v8 ̳>v7R~!R ׀qq?g$uOmsX.Ojvx4zB}-Yòpv$_]{o>7#XY~tIWK]"ۀEp5߅ah/uq Ygcl%Ő9(Yxusܼv8fg1ofv:~?[HuΥ_R%QTNeTD'٭ %O7~{r;R".#gӓ^(L2c2Z\'1+o_VI^Ќr@V@ [o̷"(Lw5|$Âm߮"jBrd^.D7ȂODAr$,?ԸKqܩץ1]6pY0T|-{N(2FcMjg+c%`3SȰai`%o@hxB>5L2)1 .C0r yM3'Ej&M17|.ǴSJ]*֤5lCo9Vq>4:?N,= DeN<ᒐ UNg:-ku+̢[[S]$1O84O㺅/!ByCu"?0١Y˓DR#)nùrfTW\`V20R6N $n8(UahjlgVEgӥݚJ?c ,i$L9{ߚ'dKn" r4aֹS(#vcx W̽E@tQ+c" \S8Gڶku05tKN,ݲҚVsѯvuYg?L.m+r)귖?{OG>m!܇>U2`#0I/#&|-`b_{rldމ"9iBh;:U?3F|^=H1-cGbW8e=pљ=wݘv- &pkz$4d](m}2ve5Hg>n NtgGE ||) ۇ!SCCc/3G{gKQ5K=켽\N9RFmKQα5/U )_r޲AWClw xu9k{|\v0H9wQ}PTi t+ 76q3WjRM0RedIIviy nZi+Խu%"QG}+ ʔӦg2Hqw P%lnu_‚tD0 CfAu_ X;ښMBT? ʆA1y'b>]([%47\V?*%ߗ9͐ HZ6 +Vdz!N ?Y&i>h_<37`=8aRQpӀ- G".4hعc0j~ L c֜C)[Fq3_d݈TI%@Nuy4/=I+t(gӔ2iD*C2[_8M/`>&7UqEħTv+0d`āʌ&^,ΆUX1Zm (b R"OJ*2Z|CU0F' j*(9B*RhfcJGY}r؎$G&-rCr+ x <[[K53JӯijrKYӢ*D)1粿8RVՄf1pSr!Y}K/9NB {ǣ~ZP܅D݉S 7ۈUY(* $0Jk2qt0GMeh]Ko2qQHqD[A806|/RڝF0˯[٫У [1i\s6{K@rZ76{.{f\W8n%Knt";lUrz]O%O;lg`c=QŸ;k@@4qT\ SKVS]hN؋98  ;Rd)[܊h&2ee4Z^H_NђʨA5 YBVi.#ǹ":&z%f B2w3RƂB%Ly~WBx((`{D5qOi1LV҃X^._B[ Wߙ*L|NנkPs'HE$#qk}C$`dhdڎ3!+fkP0#0Djy qB+r#`_15oʮ'z L{1.ȸx j +Q}Y߭F\Mb8%q#!US]tXb ĶvRT5ئ يvv)߰f:)W~Rdx:(2z5^@y4IO YX`$ E{kHº@&T >JTY-ivc"DZ}Gp% cByQLeUk`KT4zBɞm'IS: O UzLL2C>+A=pz_U"껴T`2zsu~0alȲeV?Ja U0@[yiײ-qe\LW-yj]J ,xlHg@M"=@Dnhfq@H[ ƈ]Z xDšZ*L5Zޤa:i廧= AQ0.ip*:b4ȷ3z>ﶗИ=b%s~Ѩ|&IvA3_Z;[boh/x8??l =D:i\[P"J5G^ S;uSO6ꚕBp n66P qdDk_Md$JW| #2/6d{Yc,!jCUnX|e/ U2B~4>X 5s7 ͉u[]bmHҷ_I<2!G5)'> >PE(fg"mH>JЌΰ z/i|-0_~|譴Mkm"D2`аH+x J92waؑzQ^&4G\.!xi*+,ޕY#"惁n pxʵ%1E Ue-w`1'XyƮ%p?X@6HYn;!OeZNCL6(K+6=/sFB; klq n9n c$s[,@"5 ,!6O2QBu/3|XN>Qf+(FV ;^e2: Ij/w^} ,QlYɴhY}2єdr']P`̕%X${LwN DžHN\!#m,7A6}p(õ2x]=LHuUןSbw&82R=ɝvnk Iee?Bm$_0y]J2o^A"`}I[RWGfw p:ظ\g2L [gDWȯ["N~aQ$^u\֕CJZL~ -vÌA-?JbR޾v(QQ2ܣD郤 8WW!\+³nW9^9蜽Y *1U}M| ^-=M34֓tV e[ŃڹܟpjP|?|vIJR`2Z #/e1Yxwq۝nI*t MwZ3{T\ M#HNT3S\=R%Vݮ\JVUikJؕt!"?$EJ)mwhHQz s,EWO'rF$ΣP7vJZYjA a ?%T{CVNV/E&cOaGfq r"$?<}?S)9?pZ^@wޞ2 5}/:, Va7%l ]MPv&KJF*O"VO4 kEkb/&|S7,'wсKۿ7 R;O(9" fTAM12000uoBe?s:F f~|;WM6P">x"̕SsV3? p)0gUf|:|3uU:kY-Nå`_֏r>f<|tPs#>HjRQzb|dQ ?iћrZw# \jQJя@tHF.\S<#tyBb,@j9*WZ  D.~ `s# ̻PT! eEK  OĂ&iG*yXދ۬$w:1kzeGӾ΋ G@`0bg|o8C ``hZ.'-u0IVr.t1t0vgͲw {̫Zʨ΍'vjᒂd88(A#y'~gL&p+~l"Acy>|Sj oܮϰíf>|Uh _vܳ~ڔ/,oKʉ킣67Rw{4*@)[m珁œ`vUBO@s°(¡ mc3$a1֖iԸ@W؁XMx>^5ڮeMjT{EzNvNrӫVvrp\#wAlS-uis'\C*;:Bݹq7+OX)ިQ'Cm~iϠPM;ylʔ'.jԨ8kIzr(2kq%j\d-B|P6w Pr(/rKV90k)'J ]Giwh-,q Ȼ@z=@M9S Z7 P5 $Euc5I ]/tx`HYP[<l՟{,ш`Nᐗ2[0/@7cj+fiwsE%VxinmgM!!OY xQ'%C~o@pwAv*yl,nEtܪ#{L|`Xzr!7SfVh,ʹ[N_&=`42<.;)0j|ioaJXuSu<*3yCy̓Kc?3<@xH]yyſ_RԜky>@M1 |=mtjHI w$Sh9oԲtu 9{NvK_ m%d%PE|+svYZVoUPyǝ'(fK"#q_dXi]Hn)^Hj<榾@m.bnQ2s@޹vtx*}~]8 vaNSxbjWGBj+W9$= N.{AS-6B  тmR jEHҷ;fnkj3 K5iP`Őz4DՀ_7]Yrvdu玬=CUo#EYT-K=Dē`(1[}Om8 PZfwA۝A^Wc 沉a9W{J#q2E`Bۇ)D2|HcѮ". o5({X!讹[BWX9Dȇfo*bXŲ5,ji-㩶wR5 Ƙ%g_M2M?䛗A6B= u9m  s ҚX: LdK#G)\DKD\V;6Y˞e7Oa |Զԗ@.{s[+yxG;"E)rE4E9gdw![EDa9 g'7K,%"W9Kku-Pe-74U6F{@qOYxr: >_റlϢ[Bso?rZY,NK 뜆$r6讓Bx04-خb.J %"{"QDѵTX₏;"F{|0_33ʏIQ5afQ'UIcj;V*.}z\0Pr6 z@DHE$Q{i㙯S~Ur%V|/׏&`r+˂7=y);\  mʹoy2%;vs}w`.>m &7-vr¡i"9? W!;Q`#}{v:yEj .z 1v8IR?qQ@ 7bd ڬT[]7`=)bPOq RA*HNzeJv8!4f5B﹅QK䋵'M"cN*,PBU,0x,HsoUUj2{#BvhNV2yCg4^h cG>/ ?MpP: *D_I !qІ(kp+_7p5oDVv$mZޟul`9}L *C` :.Olo[\ZՌ *>%iL˘r>DVן5Ħ#f <Ԓ1H ,%t7LYiadz[ȕŪp;534w9g yW+aw1fGcb:>_J~bx9p ]F;~Y=]e6o; ^{y:c=4]d?Fw-qܝ;=-s0TU"TygvE,JMғxp=l,6ڸ~T +k'N#Z7>uǠ! ͹ !JU-t6 ųwønx`~KPِhG:771KwwεP/܉Rj$s\4gAKe;gRRr[1y@2_.>NCn?9ݍTKR1Ԇp=ZvیjS5Ɨ<{J0  ]Mr5*"RW{RNP+HqAA/֋WD)5قO(R:ïߪ\J0iOg8hgXfc%3neȣ[p`^Gm9vw#W~@&_n Ch/|I EIo\JLlO [U)z6ܤ m Nk_F8EdJr`_gvA.F" wg\$ s9M} (M R QOm,lrDZL4T s"M?l]fyyѴFG:PJz-o2k/E ?*H3Ԃm~=L` f *d {NX7k(iФ~t(|:]ȱN|;|,:g"9'ԥ=OE=, | Iί>5ҳD8i Xզe%R@rPԘn v̞#S.fJlhAelwq$b/ v@_c_Cm?PХ;l DF% J tQ|PJڷ"6sm@:ċs' ǪLf}]"B9@ux' Xc^ޅȢZq~ыO$˩xx,`\q9Cϩ120yiL o&0Ը+,pٛXzїwkh  /JiS, uRfѡT`T%"1^'rRzFŲO^ ̎ZBц/:n}e?E)6'#XqG/kSGws$ljۻ9-Wdgؠ%6`3Eې@6nTkqBub,lshNOUJp%dഴ(0j6qJ7I0< X[~Ox?$HN%am},/{R$ }Zub lEpú\Yuub; fH6#o$xSA׭o9%q.Țfl]ʍT+T6ه$dDUF!白mn8H콨V!1π3pK-vLvfwu#="@q_lQێQ,q3+=XV Ӕ2Ԅ|:A&ڄ4jE'S>ݭ22?Jb3@Tg˦ݷEth2TP;r[BҽcYJL{ |vt9кs^}`܃ULo]j zKutWlA7%NX|c _r{FF؆|z%T$ₐ h$"b]R+;ү|5;XGғtR4x8,Ʀ|yϚSҦ`Ұ"rp2n@}ƶ>UUNR0~Q 1\ֈn]Sv\2Po\!Vsݹd0iGȊms}gQ%Ƨ aI"zLn, a GCM0S*5,Fu = }CkI8,ثs`. Uwqg$mζbgSv.W{^ͯAŬ+'j&yNs|>bh j|A^Քk9gz7]Γ'YUmyUld!Ff p wg-zzպ>NjM\* -ۏݎ+;Rmy]ކKN={X{I*zAM~r}c5E&x8J6 y 3y|޽<:zMb0r3EQF4Xu)rEV=J3ԜMcA7u56?7vz |zkۄQ绐Lcȏ [^-KIxO,gzq w_GzM8+, ;;ĂoRuV:g><;$>VWyq2 pG 1SFö5Uǚ.ڵe$i=>@ W rmS;_͍ݰjSl`iYOMvz5bMX+D{M `l3H҄JgPOP5gxE\֛!}򼀖ï-=\ N-  )x y[PS%L٨W{'Q0m!?&ylGL;rJ RZDxv|قRe;a.0Fr#H"Ǘ5 !Vmߤb"!JuFXD0)Xx*hJ, [[PwݕӨZEјIz 9QRRDi.P7Vp ݘj Ǫ=4 6^t;s7F!Mi;1Z!T?po?Pq4i`Gt/W喥|>X)k͖AatY":s'lvsv&T̼-;6QsDd#P?Ja6I&;~KEKtHꧯg!m-0^Í] ) h2'(O*OʔXKZg\˶  H]m쐆#@.! Mz {r{ƀaǞW\T%1ߔ@aK}.8qj|J`U$I4 "+ə*ykUX k SoOkiBw? GW77*JLT+۩a(ާ6a䃶0d0XsF]6ӴdJ~gSYm`$C?\}|ŎiѼЖy 8h&ewmT”1ӹ|eqT[QXUd)4XFrږ Lpb|zW3$vF?B7e-) R Ac5o}*^7(sXEb OrPi2tu~̞?w`6jY̶&'%څA,$L:X4kRPpH {ωZݹ+K|n1>dCNܵ1C]]Yp͖nXF]<;-JjKND'Є` P?)9]K/[dZ.M+< ƈ@H%o,yUc)Qms$>&>l⾏v\=Y*p,oQ.;+ItNVu5n˛VD!- ?lb2{t'6:?L<$F٦{eVb)#P/HAKp(m$ʃLY&P"ֳ/g=&5~$ۚ334;Z#o߇υv}AR CX'xxyCȍL~렪JEIH@ʡ.JnQ?lVxrRm"}:"U{sSbDf?>H!TO!Ex-LfB9ds_0۰tM6gU"+bT@-@a0fkyen!8FÄ ޲ /0E56* v!$fJ#z- uS+Y4Lup'Le۫)}P)I4[+_gMP o>)hK$FFrЅ0AP>41oE7zE0t"+yT*{9-g87|~'Nk䛛cAVA?Fq:Mn@BݶE[+`4+Ax0MO_˩$!jy<YPl>oq{K*8S!1ud5$zV~ծ\,_0h[FB`oU(Mt.adTBȹދELd /&G ǫRa$}D>JJ׶|AHXs8vGm8fڃj2'uy2q0`%E@صVY#IKޚ% azxХPr7gz#'2~ #0ޙ[-e?Zv^u_ϰ3?2oL]Џ%A WU< uQyC6ylH6p3.<N^SYvgU9Vt$i:0ͩ/D ×j-U+WӘEZiQnr(Aա(pci#xڊ1qnBg8ёϕY^ͭpޏdϱҍ遽:MR*yIc8 }e,4|$+F,iȯ ;<¯mʖ+-v(FR%a.YjECHg4z6`5?.ӭXj=n$oO7kd ݀G`4VaG+-_L ?GhtrՁЯA:yZqD!a[v.?˻ 346 ¿~ W)Abk(ڂS^Nʦ4s)|zSRF~!?Ը+,pٛXzїwZ(_$n2>]P%=)F;"W$aحuc{s3;aM(̷:dΉ;RLl.R9]V ]q Iφۅm'iHqr&(vomniXøqawD֨2U1 *SpQpo'G"/5vaoc@X2aM߸/_ "b5"g}& @Da|O'4ڎhz$Զ+^5F.'R[6]>^}.&_5ja (*E&w[z >I+84XUqKl/d fWd5$^6>L  ,̈́zd~&ǒ.Z ͪ1X3eqj#%Z_AЬ ?gޥqգypCFwk92dkQ(7j?kg׳@a)ᓩX+;Ȇf6I&z8cøM8,X?+ұ LE֚ |`NAjYIC53h:Py"T!@PHEW+^A Bq{g<0L~*ZmUL]ikR M\?C0B(:+Ud;xo~P ew7BM{كrf|颻"ĈE.2H<- :88UpSf-\(<7EBNM-壾A g>M[ %?GfN(ޮ*Q>?R_{{Ar WﷃֈjӚ-9Cv7!ttll}ވ?nݫ@ۿ{>tS^ wmwOo۲]5}e}S}4_۠\-[}}5C}n M/ov}}aAo*9_Mr=Q"qu I~r'2" CA"Q1]H3coU9a!*?Hc%'dIֲhhxtl<8jqi]i<#(dU2X3ZOA1W7 @-BCk%MY\Sj={[#K@ڡ/Ac6\?õSq_IuOnl Us\?uun9޳n01Nhlt]2}J&rRXU‹BIGn I$?Jyȷg]cCP:O"2< v.<<>(T #g1 \V ;o:ob=[Y] De6G{68z[.ڱĝb9 B[֙,jL.osZT#tK.B-I=hEf~{ 튠YnV*cK?+GiQ#vŜIgwR+s!}GE.qD$KdqKstNL|Ay,j5t՟ۈ8"0} NIY^M:)[5uK}blsvoаʶ`ٗ /` c$؈&;l]=w־cӍx L5M!h2@t $0+rj =ˣv!Di6+o5KVbKbC˵x*WiZAhQM݅HSt-~U̢^K8hƴ2_k7[ B&P'ꉓЍXRP4G3:8B5=Y7ZlmVh5A`liѢڤ'#:0` %ϻ7HwghUֆC޷op4 <i mT+(d ϓnQ'ګ]cA"B7Vtt+e+_ӳds7͓2{(J`Q.U"^f8%[K|a\F=,9vFn8{d1f&5 Ė(m(fBvrI*$+ %×l~:C^- A``0V̉r(WbD# ¼1"DATv8=w k2C\{|z*fM_ gf ǩVL]UhngeHBLR upG%$_w hQ·h| qiFJ/-(D2@ؚ1ڏ8,7cS!ELȭ7FL6QDqG冩GI ( >l ^<x8{\r",N`PApV؇>WIuЉ/nҭH cgb}$.+Sm_i mfv'ң;b $$gQItچ 1)0(J0+(҃h'iL>RI@]z[/m߀|r/ ﲙ|r)w3$UJ9J# @FNw D]:DbK6ϛ p#%BnbŨLћ] )ܑ ﱦdJqLb>˨3iO;.hGufO|/VA> +o0Pyk"s#M pmvo|$iP̐Uh*Œ. 9ˊBQ.}AWH7bӔI \ >u(Tn\ֲCV?rYꁪ/T$f.%>#L7#6lk6 ٔzrӫ ^qfk o: =ZxÎ3 2>es NlS l|bH jۛ ҖErٕxRxmKH:֪Yf Ԙ'fb0A-?IS`d!Im6l͛ZgHD( Ѹ-pN3U؃Vӈʄ"qHFZ WaNT~Vχ&86&*OpTk pͲv]CwdwEaD'{Vpd>yWț`ɘN_H_ 1ጢH"ٝw.V7f2ڛbZS-jc߷e [FQM9\gx6aX*EX+0󏌅淶s[LBhi>^BDGhp3f݄JM/xB{=4iSLrhqDv ` !1d;%H+t~5N\DT$L0D~8 I`C]b\UҘN- vnioZKjBMϲOPMgp`x;v8)DpP0'B?m l{_XUQsRʏ=TzZ5V \9Z0\ϳ5I&0RՓ)*>m>=HڎErџ^s,.Zg&uݎ "K4 ޝI^R]v5Qѫc C/*{ʯs_6v  ,I0jۅϟkj0δL>ǁc4&|!UW[vߟ?G~ 嘼\\l>%ib?I[ֹp'n.ldx{zN]xna9+,^vt4U?AF.ubAw$$grŨ|V= b@\Ae , ݼ&"WR(&hR^ `+a ["`W>p'uaz@k&6zRM-z:dfODN-㒩d+?Um$GӢCr=4QALHnOCq g=G0ξcddGuE˛Ҵeq݃qqܓcU Ĩv`bv5ws5 [,6Wzf$[]G vzt?_dTA]iDoOymB,;P+5H xQA i=B5߉t3חjC^tXW_?22&ߑtL}y?43ϺUDvt=T-c\kᷡ,4H2rj{|0{OpBBW "˞7DЫM^$QĂJHUJ~hO8[~X0"Gh?>0$5c9<}FMGsm nSOn3jEk4{L9Hu7YKGk,]܋)#1q\6f"}?Q[bQ)]ZmzNݣ~ڨ 4ړf0c([t*~/D+(,>c Lk ;ؼM7eR5=h|~f> **,m/#6m+9T $}jO $q\>GoLB_}翸Mqg)N"d_ IYcBF\n?GhYs˫1 xnDN"uLNU=ͯؼ>ȑ -:'*,% &gF^9I([{dnZE-rg}CF rzG1|¨ќt衴`=$A?Y]*,&u*|I,5G]gQsj)0.U7'<-Ic:0[ǝ}rJo#;!PlM=H/Gvϡkq 1Inv8GhXX,Pq]LKxoXim,؂C|ɷ]兣QRp/u_ F (NA:(5^r~8?g\fYp#<PƝP+.&oZ)pL3ɦBDY1jOv(Fu**0I:hIV&#xn=RDDqC L@լwnP qO{8ŝ 9 7F瞱Q+$UY$ggul)ʼn QŪ)bB`B$p)7sX 8#:ͪv^_XN&RnxH9-A&C#O<~vuM§#ses`͵uύzzr߁٦m:??ܢ oMUp+.{rw&Cd"y r)%_u(`gi@a`{_``qo842CxvP 8`'Ou&=g!7_gGPP~plEȀ&8|No[~JK r,c}%XxhW@WH8#6wJ' b9ʍG)2<#wOםc:N5 6 -@b"[j(!6g|EhO۪j_֕Wh.dLBR::+GP?_/sND;Wnf/_tj|/WT'Wx:(ml)3,hSnw$x CqfK./&TCguP>mqT!tS4)[g\KԽ-Jr\3{Kz~$0Ԍ:1+"+ҦhAT.rJ@~u)G&zgA= 3 Ƃq:x=Rfc0:XrtNӛ1l3^\ӫL?|֬+ɡ& {m~xH/+ى,]R9$ޯyN \,ߺkjhdm~.*Jp`wK܈"k.BpVYVc.-PS^oJk(nYW'X|8)W,c܍R@BM &'@KtF(42ס0(!C=4qҬ êonͫ 3$)SKfx W︇?P*ma v |;<ک3T(~Bۇ[GOx}T@Ishß%}F3͈3&u\;q!Dtaó^gOOZK`DΓ؞.K4 ̏bͨůH[-IYKP\AzZR`L0a]t7x!?7z9V/ @N9WU?"g24O9 k,ڽ@,h (_*蹦<-&a!*J08ڃSME)6{~ G0 KFf.|1H?ﰆ-"wڨP>S$E5/$fe 2r6AoGT qm&=`f [;^6CHԜn5awfS <ޯ=Էd/m,?&X[:.brӅdBK%&UZT~a^Ň,٬z ŕڄ7vw{~T$@bOܔ|ЯۯΣA!\ԳS@ 1@WƓA6#9יo%]NFњ:IoT:+] ƒhP?{SEtp!M 6Юwļx~THb8((e"`-o|]pn~9P>;i[AN" ؠ6drJae{QUY6Qfi>K+DD ҕ8F+o$Hj:WɆN)i89%ҀD Hd+-EywuјM~JЦMAG/gh$%H?@~-4”qhn'@\,;"낊cNN3%2a Sw b:QMδٶ.31>;2YvBRRF"LfeQ+Rq8oF_G}cG<M0MoGxڻ6&š m1ރGKq' q=HY.yR0Pz~4 l︦C/AMW "oȳ=( WT_HJ; |b?A$Aв^NqV#78fmE4/՘d▹*C;݅Xm7_9U ~nh_NHS_sǛXK_9(4n5[ o~!pv@FqTlͳYb2 HZ/;RG`ݭ}k>=y(8(7A Ѐ);<&u6;Mub5 @RNo6'hw[hPf~~w냝F<aaOF`_7<ҜzCTCK v 4MpW[wڱɓsG #q6.ӰĴR[Bq8yZO7gpM.^n\)ou=5M14H[$Wpgæ~`cYsYK [a*U^MvG^ydic&NDƾ>^ݍx6ڏh|{.e;"0]"CB+hk+IS9 gZӑǺ6O]xAČ\,`1+3\3+M g٨Y:n '֒/?if!Ki@2l8إ Gr'~Ү (+ *%,H4>n]jB!vhpSJvL!X.;# S`ˣ)}zFybBGc02:s,<"1$LO03Ԓ?嚺0َV"o _7]IwS>%59>C1(/ȽGؑ6y<92XR.}U'A 4\ gv($r'e_6ď +4~#u9c6lZqc0iV\豤Gd) >`e)Yto bE sН:Ayq@fï1WoR2.wa)y1Wȳv* ]ɕPYΞ 5Isl[-9H]͓"A W=xU02إe60B]7@%I{uLO/FըLWZvi;owp[+,{"T>IĿobO Sgܛ ľ_`?þcfӕ`S %@sk$c$ㄳm1M{$&xs{C*O6F&(@J"ԖV~nǙA+)= g }/[ ]?A=%r:%R%68L_)5oZ9B>g+o2iFtl> #oȒ ϣ$!ZLRy 5cPPCS}DIXNiӊ@ yesDf]k;2e3GO^]౱QC!flB.: iĬ!S!/d)pP қ#A[ K@)Kg*Ѯxc+Hݷt/G Oako=y~W#-tXF# cC| $)U{:FownscWUV% p:*;]Z߄Vi9}ˬs Ҩjg5c5ʇ(`f{H3F!$CoId+&ԈP2J vf\ lpQ<#"%;Lob#o~/#b@,Ć8wZ@N}ϵٕhw0ìd.x±FK{|KVhL7>,i$ZRҒ:F-o0+0<ư0YXq&9߱ \6|>f<听D9+~9(mrܦv8Ubcb ) fMq&xP5[ڕ`Э2 '0(A;m w$Uֽ\ZkA]>2s/VuB~c`$;U&I0 -6C`f5VɑHu0唖5lBo`>,{^T@>ͦRk| kJ_Si" k~8aIC2dS΋?BV `G;[ [J'ְO PЕAy2Յo9j~cc/fwt)܄(zfigМ[ك; cʹ]G9MYMCZ.pOu0D+74i/a wm>mc,DMNuۧPLw,+?Qftb:Q3CKf#wLW39N!ӹQO >CR,3d=` CxHA2ѸG+,xeS g" gh"%F8{還)f >ݹp5JK=oM:89_蝾̈́:,|EȮN|rkF|BQW`pf 0R ҀM^zp<[mqT0y%hW: Qp 2B(Zrg7^hm4q\u[R'gépY%w7, 9էu'Wn{]sRL?Iw~@`>u?4}fU2"7@\/Ur_ ײ-Z |+&!nk>hg<.ʟE#H[Ֆjcgθ9"Dz˫ _j6ة́ðOt|կb5(/iMj!PƲeKT׌6^evА6fsUcI[gNl\@`;[^=}fܷ{F!Fr5P]JeL% < {)4%50(WwߢSP iIYe+gX Pdz74Hљ[ O eiqe nwI1!S0ve6QW<-E-3io*d@3) cuK\|u_*k')H]Ϫ]a+hD@DplxFޮ_:Z<"&:=*E*׏;UrXzG.-l.l $ Kz]Ž#qM[nvmuOrhl-߂H.HaPr(A.&$y%o*?u(8akãV|oCqyf$ZR>6Vf`6?÷gԦ#(_JqӍ=6DؔPڎ8'wx+vQJ5# /?yMLOJ=YfrE-֔(%5Mc 3aԗp bSp{&^\bԠէ6 |bt܆MJcWեdf}OY(jÉ ˈvئ9꾲vYW3%$:Oc:'C+ф,"#T*˻b6gy=Nœ]pح^(;b)$ri'g:8=o+dBP wdܐ|j>sY<8`4880'CT5@2ilCo#8!@qtaSk0 _LJ<D3ԟvTή܂UD(DqH8 z?ʅXJœpNuX]~FJ ךz7)X4EkVΏI 9T7ٽG.<HԱX&rրZ]\X_ $H`^ ~)WU 9CZ:ZC@n=PBon݇}$Ժd[lZbTCd(t&*h^6#7V`j.ܭq \GJJ67Snl)g!< i[XhMvwgSgTEx'>lA87p2BKW9C{@3 e Yp %s.EXtFe?^Xig<5~s|WIF z%5 #OhN{u%`+ KsWХKvc]Ydژ<%K?{I^J㷭e6.5|Β,@->t=#BNj?С¸6zpqQ-OZ% eMYq`\ /T wb6dx״ ߝqvE/B3!kXz;fڪfv(1;{Pj%$F J?OR8m91Ad/3#Twk]D!k)ۋWݕ>(.Ul1=;ګ:"&e9*SU^x:pv l]o2sVL N/?Mj!%rXeeR8"P#B & >I}~ȺARU/%2gXNI-sB[YAͫ{2?QjH1!4@lhQY"ږl;m7ʷ*P[;B+K9ίb\^y.)V_}j9m gv͏\n{f6d 7 L(Y{.>ڍaFRs`[^_wlp Fay)?eh@CF=6NҴy'e5kIMeKyhvaK,7ӏTHyN],@sG"$aB~i1~178T7n!_*)P@&8#L+#صAQ_@11^i-PdKPnJ aNٙs9Hv _?1=,8MN¤nm!Nd㳁TPEp:.zD5ejs=q+l+ ùE nXMAIpAtFQ,E=4T33v5'~+hz4)++=e_lў 藽l^bD)~dm Aǎi"4#0vqOwƨ$\R'M$IT9`T5Zl,ƟKiHz2(3?F B\4E>K=;._*%mIwai) e9yVl#6 VY>dOc* >f82K[ ~r`HV}[wmz!}Wwބ/v{z nr=o{v}}nn_n ?BW[лO[tjWЯ| o}hn\} \=-[q+Cӆ ~J1$oѥ.>0vVr@"Vۏ#d/<^97BwJñŚ+.T/0|ckyV楿Fΐ*w^'06o rơ&U\&8U|:aAq9:6#ǘt}j里Y1}>keKdk&sgV6r1gr`"~^{yuc+H3]zz_8@0@i$+R4S xWXN'"IHv7*@r74mh{b xوMF\K1!KN_+aj$,] wMLv #f@\ffK-:tX,Iq!S磚1 [JFPf 5 'cjr5.$c\pbSF ʰx,&|8%ouS,Y2 XƷ1bkIk0X3O(}Ì8֯˨;Tn n;*%Y waXYkćPRmw6m~}@Ց/EǧuE.\&48$MS}`L;zEWX1CUνᶣ/n%{_h&mTcw>{@3ۚCe_#@9/堪K~Ob/4|v c[~adwDzv Լ35-]E=^Pk!RE_,6RjeL]KG!PphWJ!~1暆vVBVTQEQNnUJλ>L IuΪH76,5lv hºYh@55'iP} N}!`RPdV?ɪB0/ތgР 4{KCةi;V̅73kqWn#C C/]o9LH V up$S❇?AoFja'Ag* Q?OG"p)hcC:GP99muɛ:4[kst O[_o1ه.*f)w4ad|-d\Č^!RZ{jH/BMH)iW梣r=`^w6u>uw6M 9=m#r 5oy؛L A7VCa_RMhTk*z45W4:ƣx2TZkZ}Ga&D0ŁC ۚ~%:Ywώ|+ɕlZ0n_ hX|xV nz HRi&f/T杼 VÚe>~+V6ㆸyH@>C?BDp(2ԫ+1ϐ?7eЮb*4]&xt}>CFGzX7GR.\lkhA {*&X8@֊S,fm)0Eidl6>l )a"?(>m>Q=>YTl&t%x%Pa+U; \> h*I ,yVef:l^:ZHyՄy6 {V@ʖ_3/l6j1bb\$`ڬSD80Ԡ /P AY5L5tBO@5ɽ_EnҲmzmKʱH7{6<,_#W;fR(bh 24Oʵh\i~-_2؄;{`Դ"a ; ~ ?wD';\Ml-vm-_;I P_j6IٵPM7QZ2K.a5Rl8 V%SyqWmၹf! kKB_v.iP[tHTA_&.tS¡ĔoZ%FlO\P,ߗo>fY/>wv<7;<xA'oƨy<Ȥnm' s6YM7z=J-6)Ce͸0:(sr|w5vAl_<ɨyngc^kePhA$*l2* s̓`r7ceL=r{|g+ B6}m0:sG$#q͎z r`4 Rl&6XYԞ 95FO9=y:IaACA= (~ 2W6X*Ffiׄ3.^$B䣪##YGĀ̫,nz[F4I|;V0_0V `~͜x :Բ:>sïxo*و|{yAe A˾̹Esr!)X!ZL԰@T%r Eo.C) Tڞ6S\m8[)|G6y?jJgQ* -d s'f-9ByfG,-8Z- ? 56v)i:TV9%IT c @1vµ["q ۰KF-였p.,jz9hl}i\>1[kc]I:}0bz=v6!;Wyf38q*I1Rcf_1K`?tLd并O1&y6:x>k%J~ɻ>V@g>z4&o1^ pR1MGEhrB"} ߷ں4Nb!! ]:-Ti^Q$.yxϪYlTȮ/Ѵ`x|$ۯ/Zɣn;RE|s2 ~%&@ &;`9>mgyx&S_S[z1R}3ʒ5?"jnn&#Kyҗ(0Vx:+5YU)NzxIs|:V> YUv DMy#6T q$!z#I "Na},?u46l"8̏,9[>M+n׸jHjbrj s$ n4v:k0ͭ5&1Nawo ?/{mc)i\J,دUkW$5wXs?~8ؙTV™u.ApE8] @(D]E(0>5#6>lمcxe-_8rܐvDO "GD*Gx4XQg)I uiWH1&8$iߤx %XɇsoJP˭&4ȤyJ*0-Ep[.X2Zl@ OPϿN*/#8}_.롩̈nAbc&lɑ %Y蚆.Okb6)q˯Ka;Z%V^aEexϐ{M{%UW3DM'ʯMWqB'>j}^C-QߴAS , A]%m(U'E,+`6PbWWWFNl-^|d@[KQjbxLJ~ݖJona͡ٺDo+Λ3[cZ6X|U@@R_zԛCx⨭U-9*{l0P`֚Gw XíW@\mͻHvo<F; 3M%Gd>Ą8Y/_– "̠Xuv7&v =B?-M&ME-$mBr~ auwg_7GFѲ+JsPM'I6^ۨѳ4 ~֨+R0Q:R3)PAKqQYQ,`(J>X`4i;@[V.MbS ~Ig abUDa Y0mw,ICGv'4FR{ 9@P3iFj4]'>NCcvV2OtBXCF RAc3U (~%g D5e-.2x֣gZR`L0a^5%_\[ .b@ͬUs[Rmew fؚʉweʔSS dcogCN LLc/0W1f ؜˸ e:iBwOA{Ž)} \Z%&-.Xs\rU('й ݚ@RޘThGY_wr(k`5=puu : _UfX7@|NVqxL,6ɋh>^V{F w/Ę8\N̺߷D=''p~iM:1UKgvekxd|>4{u9a%ubSt0w\o Q2I|ZJ,V#Ø v~?oi6fV31f @M^|5 *g87lEy=4j^2L]zHu#y|+g+swz~±a^U5anA/B(|?][\8^/eNUIHX>:ּloMC1i}eOOs¯OLK^de+$آ&_üd6h?DO]/enR,as' Vօ3shI0`^frՕ =6oeJኟiZkXj2YODzĦHVA4BҺY1[)M=wVj\&ֲ0c[MחEIA]%< WQ7=;e+ [ s ^s55Տ|*6 )-.~ p\6 zBq&J7dH\hSA/ mE6O_GPNPEy Lo$+8R֤5!D k̑ 襽 ZRP1cc' ̳g1%2\}2gl3&Cqϲ)`uu *>.o-<;KZOvs qH/9r5 {:F1QFlzפ.c&yL? ]}0Ԭ^8՜zPix7ѓ|OHԋ0O_>AViml%kn`q,\++Ă0 ʉ72 þqOD#!xcP%O XT2 ai4~y賖iZ%@W%m} SYXu* 6~H3_GH6ŭorIP#6c#-yb\dtK3Ɖї'g$H{5+I`jE^uT˥/q(NHL"k(.ILyf.0:~UQ6G T;zKEAh252F͸ˇ 0Rlm-A4$ycIo(es6ԻSESٰxb 3-,>CIXr d'?}i̎RJ\ZCl`w\O鯻Ku_u [w2p&H.9o6ϲ`K$p?WL T# Ϻ]Yu׵*''jKHndhM5uhw2awO%uW V-`7`f.)9~)B҆>F٪6Td,T< iE:ﰁ )!s֓HuO#q' Mm\n}:ܚHP<9*Smzfzr|RNBxҚ Ik[ΞL!> 9\${%lut E( x|QĝS ]Vwr]Nd\0v9 ]ɕg*`$5 YxI1_>fԞ﬚J@K"naYtZ g|TɄ@TFlWJ+}/c~Ndai Ш#| O?}FkӌoIK3>ǧZN25r|}cZk_䢞?=̅Rj=7#3|/BIK{W 7ԻmSw3/}٭}Ťeꎗ)zKK9ӃcÍ`?9+9=5'_)EV>GEF+&o4!+m/Gi>]sma7-׍i'xQ8dt ^&o|Yw<\7X5P| 'V@˂*u% ×x9؛4n4s;4 -2ܯПK7|c9eք@*Їj/>E] ^m(}55}95mur+YW`Řį 8_28rXF+͓ӥ'5M_GyEPvP<ŋ!RB.3,QRRwkY܎*2"0a7G#02QLuirKv3&/;O9_@K&{Z+@|xXoLdc̟ٮk1 d XO' *ֽT@ _ LV6e\Zv9ے?BXZ&͚KK1;ۃ$N(WMK2H|K gNTگ> ; EccnߎZr|@c\6]x/cK mAt YST8ӄ3`K sݦHQ c:~FI?+'gxA\ mt} t'uh.;eY<(\(Ho Ytܛ`U /*GXj1hGwGm8:U{Mnt1U{a{}FYW+(il=pFa$W+3͈,k(~Q{{!.o<_4d, %*=?tJ&&+s?禭P=Ҹ'l h,딻7O%)ŵGxRcRoߥ`;bԇeɍY^s@)g?/#HJGj[d-eݴ ? -'މMzJQ2ŬK R챊{%aX -98O2ew!mTZ0#)An]lʂGȝ֬]Dv|7F-i3t\,&yso$w OiU0;ôT䰉paɾdd0{Ud?%W^%|Qe#ly狧(n(=t5N*eD2L-k\e) E eKF=G ~9S2&<$&-%5%x W1{`7= o0#Q[X> >vB"@.ݻ~Yo^ր<0Keqyߍ^1yR/Y 'F` "|<#4'TO,ڲgjY-t}v(-0hq&1\"89㎻c@ys1SOWmɂKe w er`xeůe.%F6xR'X4!2so.#x&اZv+؊j|0:|db@q/̙gF߃!kɚmao)acf샳nғG[X01&'GQ}d99=kP"ѐi(~pU( m k_,V`K;tkȠs# `bH!f)S'-)0N68(^cj' M|4U<*;W~3f>ekaQhW· '(dqq_5ܴ#5G"('!Q-#4jLF4&z-t4ĭ'ʒT-ŃeɡrBK'w?> Qc0ٛ~夁3> FٝΌY!  dHmQW,0Hug hut+|o0X1GǍ"mkf:SPF Bד7&R->>Or**6-άD@ԶE)2sX2@? y- ѩPsjײ[@9MUZMi9M mHʗspQLV]G\ٶ<3 tC\1d1<1u ;=TѷphPCՈPa9.(6["&G|]|gn TyᒝQ:]!\J,ozQm,!Vk͹]jqҘx*> l96 EH)<. /oH r-R^KUCp mv߱R;FOM.*4GNu.m:62HNzA $39^ۗrNﯶ T\v5He3{3|'O;*ȂU=K??l"+C30)emOi-ƫ9ܸhS[^W==SSf)b#?4LC3cRZ3 nt!5$:qBEawUÑM 2FlXBg`PjK2LwoT_ӌ!(1Rd[,'ؿ-kO/p9o.3AS!Q(]>^K?jxIGFNH4\}@`@nfp $PS*.تo9l So1A]?>&u/ C/l -3gi;)z_`;KXI7Zxh?[;M CnU:߅%_ɯwKΓa##a,yC0<0$t,rw<&e_1L?ǯI=H&7QZ^#e-w8cPY4]L}d6//8ȣg׹ ϼoI(Z< @~[A%" +gԦ#3v|*Sڬr=/` ,v\$=AQ'+=ct0ƔLs߅U Uvz?b3uͼ&,wOJř ,{/}`.|qxܰ[ay/)hxzYp`!^>pVhf@roZL9mw[0U_)@2t 7gNa?S]G=ƌ&p!&lyb5CE]ޓU oB%3Z1i=(e .0G1os2ciR=%{VTғ̉b޻'ӰkJW< D LJ<D3 dk0n`$ B;GևN0)?Xf62>h!?nll>UvPjW `H -rc <%QQЩУΏwdVO6BG-)-O0(`F-NZZ CI*nB+c z=-ô,|8[WrHs,s$lu:-|iޣG%4V1_q _. ":]G$:' #D&k!?p#!Õ-Zj;RISgTEx'>lԼ!&K{~$ ;$Meu` g |%s7M0g]!Z,I,)tT֊%w'/BR-}k } R8\ZU Hy1A N" ™G8Lx7rBx3И uzXQH<-;4<L!4;o/lKُioD;bxCnNh an{q|]Oqt75dB%H$gR!sPX1TzԆ5BWތvO"RYߤ>̼=y Ӌ0&vjZ)[ Bx?"Do|DA0"KH7 ggUꭾPS4gMba?V-G#'%Eg @\o0x5s;da 2m GO.bmo:bd.aH\^(Fy=].h [2ؒw8~\)wBS/QY">K-J1#ž Úl4LjYԩD!=TSf\w'̷{H}=^0k^-G>eK'"*xrdGɾ^J<6!D+sE_\NYG机}g]rG`IVmfu ڟ_z>yΆ~qLh!/WD GԦa hӨWiMz?P7Y W)ݵNUžZ!-I 8ILg! g*=tUmXvvJ] f5L֫-anF޴x\)@a'U0~~_yMD}bou~o]׃{hbuD/-XCG2q']ԙ3BXrrbMݞ{}{i:HYfՎ3Z3jO?E|ץ>PtMei@ַt6o)X_1.٬-FT0 ǜ?:X̩%mz8g*0T=j7$gYeQšl!([n_9ՠۦ;Y}44N}FöF73 )hJ5~K?-D-<75g+n׍+S00JF,Y5 WVMNG(l:u%^]@E w^ea] Oɲq6>rzVHe~k Ƒ)!.Jvi\yds}xv)/"Qkfo ]{*F‘jRӍ؛&Oڶ9B0;|ĞL<[fͿAЦ wv1*#b@!x-,6m5yu=)e%d:~@ȕ6-S-;hBAJ{>!w})tZ|?ðO N%GDCL_%2RY\ǒ!$z"odR9rϓ+SeF6HXm|:Jyȷg]c_ͭ[Q4LY,ݤf)l!t4L;XS}|اbjZ/y]E&zzy}rE6=POkstG[/R8DH]iH>v9.X{T)N9 õ_P0slYwxdwOhirV@ڈk1ﴂY!:a8f-bKt7A LyōD"T)4CDŽ#Wי6mq߬az_k@cFC1b)=Yuwb` .-bX`y$q 1l4'3뮎\EuPx}v ZSJ?==BFv7U" {2{h`w`6Sq^&-ޘݧ^n0R|2y,] xpy]D,ΖpySg)Ad%hL/C;uX{ ,N[:&`DQ qIy&˥Xa DJϺ7hT'ri=$Gx~c{  oTFf\U Lj_";bѴr(~y0N3zp% j Nw`N=bu]aLiP$W%(a*wI=IrQ¢_ل5yT*7/{tyn_ 7//V)'%MM t0ﬨz{K;^g'Kh0O6yflN%pɷ c;sʅȉvl>M[s%K!숰,T^7k ?h$)󠼢'G/:C1LvƼ~ZӁ[( U[Ɛ%[ /]1FzVdM*aS6&6tsޜ?/&O#OObJFL3ҹ]64{ 6LnKW.k|Mz8p9I?=fkf$YK\`ht3rڜs v!mח1o R`x+ .rkd:Y>0IsJڴ  `|n<5a欯l@ӱcl1QQI5ņÑ*-;Ӹs ǚKo6rzTmo"P{D-c&彇u$v/l͂q!NUGfB?ڤu2[lS: {KƦCx GѠ]Sdvhmb|dfל^Et_NO(fĮA'aDA!]SGޑw[egB}ndŅNTݞ!H`ؓP+:qHɅTv=d8⹌g5n3RUX߭7d\o\*ᚵgB)L!{ ʈP imD6O ;li$aEĩfv/5CE@v\I+=cgQfGNp{<&rwdLCuQ/&iߥ)NYW]IIΙPl! 򄼉ɑ]\-50؛qrcUة f/A@Dj9>^Q9fvQG(XN*SLـ4齒{ѝqoYwƓ.';eT"wFY_%AݩH8t`7Vph5: ;_Fۺ+8qɟ'=Gl?3&쯪*( ]zP^˄ܐ&T?XWcp EWbފvXioUc?ѝlZ1p C[頷;[C2|^ NOj;<^/@ b3A7+so.mC2SsK%vީGN!`wFQgU:V#${;&(Tqf*qin{B1DzBPxa$fDP"iC^i|V_o녢#q![CI–8 [kgԣ|*A̠oYY\Ys2S [0#@+r30!Nl`D)EJ{1$b:>JrouڥB%8Lt toP(yZgk"M x ؇]deŏ~.P#EF\-b,Yat)ji K>288-hҟ ~3َ1"T[h:W"yL޿f7nMW# Z'`9` U6mc(ҿқP[ ՍO!.BA;k;@ˎ2w]u}9|^PdS\=k㜘pc W8ԧ!̈́ӫ<՟7,Xh;SRDHisHSx5]As=o8,2Vm첩V'(K[Zo3ؖN]܀M[MHp^4T9"MB YclGQ*}ƫs4gt?DZduv3+?GQtNсo{ȃ)%Bdfd+磜KԼ`ü腕//{hXd8]W}qČDD e6S7 {@of-PYD#3J٘qzuT}`'akpQ)D((w/JUT"pDH~.J1AY)JjW[ ͤ-WvZ3Z5(R q5af_ϑ]`]D'y/Ke P J'b&-JLʁrPzw$Ykj&>GےF",WјikBRmffb0/ k* +RHЌTsMm~u֓{GDlx}t9rݰA<߾yep%ZΈW_u˜ػ.(?T Rra-=tz-$|  ![ 7,.~D1L6 OLTUS$͋UW弹cmJȣ[e)ہH_fcեE#P)ps[q%/}I'Ul!UygUs|aG0]rw0L `T7PDU}rj8u$1En9&- HN6,91 (QwyG5dcn\PP4_{%3*'1\1 >:p?DcKBa]jS=S0!]VVLA޿kQ҂;gX ?5zqݰd( G<|=FΥoX#ORSQЋ鲃XZL /Ó!Z.>o'*(qc|P9 <綥–/}ڵOJI/.zBM)b 8BN@?~,t<ٓ> ƺ bm 9b)1h@)$H>]1 |Sa!BٵOFK.XdM47;u٭kn{Wq|sJ: ޒ;#䄇[}|$ܱDIr{ p.n2ĺHGY_RE] pl*‹x,|H'[W:Rm5:{h+9x`^`,y GtYZ([`C.N1vRY*2P ޹LReuΕW`^RY٨Jd?r Jt Oߵk\|4KOF$? q$ )%'kStrZU_vݓ`N\&>y)pdhj@u9\s}wtḤYh! Qi M,Q]$83B2Ʊk|&ˇ.;U9'Ѳ6*S{}P \B(ʨ ޢ :{tF>dtǚf-FJI/f:Oi.jOdzoTI {텲s.gUTy?ASi{Hd#_371`J4̰l' n-}m¡-#يiZ-בjtq\Cy 8 {O2G )&׏D埂tu\ 5+,:ŪMkێܔL]dd4Fb.+P[Dvx֌;# ua+#ulkzt,b"mƱOj1^M6ز^.,nۨU7M{|n'WMK{V{J5 7vZQS߉H+d _hc$a#1gIJhn\ex0ee ,W GH0Ԩa=F y(sh+K92 m#k-\L( ^u_幒 \3Q*"E"Ibà 0]H("f._IN;qZZ ai^5 E<4K}Zmfɗ1Q I?&}=qfnq \0"_mNv*}#E7le7{JŜr+@4*g.֦f`/h9i1kdwyZ lbo>й"&J87u6RD B-%b!pl9ĮU8z e 9Y+:N{7?cŀ䘯-ԧ wzy[]9eIv{NPS.xl7›EGxp`a"حS>#\w1ş8բڌ ?9Xض&"k{QI&phαt}fF$X(iL#OX-}j1-E"IXYۃC4؞pK}f64i"Ov_{,SXp.#%*PWy=U[;3 "vAOi1ৱ4Y-, QnԈSmL =[ʮwWdg癬^RxC@KA91H?K`W%o AύU$3*Y"QyډhWY<'=ۭ%:(석?kvb;yiR-і/JR)L^%PD/ xOϾ~Ԅq@As.u=rSNl?p7􄃡آMY+k t 3,mk94#J {oՌRt&( *1[4'aԨYN:Z)rfA@qn`D7ʩkU_wQ~?u탣2 - l*Sxxe wG#ܾEIlHXF5҅O㓖<`ֳ,b9p!@_](kNI4[tw91nG- ۪.3^; ՔF/Ʋ U{e=uMLNѴ·.Ijm?j[ |RcڢaǧR XBO`u"yeIzX8vuo5Vؠ$vә]An*.k,$Y5|ˆ eJ+\S2P3N2/Aaa7=+Xg+w)2-Y6F%@́0%;XoCo2Ӯ)Z&ǷImfGRw=5 wp仫2,!`"6Ɩ<Ę#bM h;"=Gőz c"t兵Eqk /, qA~_jgGgrk7#8@EeM?ѕ[%QXCl^~|'3Džq~$+ju>S]v ^VpuƖ"C,<%+=[BuDg̯Z3ۚCݏg-*_z`iVeb> + JiYx1I pIpV}&bl`O䯴B;JOJ\[|F)̱p:Gd(K~2sEƟLЀR/-:ȼ_ъT1,| ёIkt<8ڹw*4i<Z].:-;"yDV ˖, `Ԓ//3$6`œÀ3Hq3;{ L>Gw.O9-Zw.[NP2u{r 0RuXIfjZ$`D *P],32Tmzl }|Ks|:G \CA]傜BвF`7f͊r-s5LWDЬu,ɫ8 u&*+S_?m"MK9@UMPKDAk|_'!8gKd =MJ4_ ;A Lans@7dM6.MZtd[MzvWϻs B%/QuK9pkvݚh#Xx_롙VPvշ&go϶X!jKs[Y^`HHE,Q$% 7eZw%ʄzlWN G#;ԨyN/" gNI] ^[N\yS 8%W`ļ=.nb^:}R1!}Ӏa/%mTKĺAd[t#350æ( wWo6"e㬠Lζy98>FpwEkmSkRZfZn,7# ,8{"vc˽>c,RmxqNπ}[[f J23 ^&R`ioU,Vt$<{ڑ=lwN ZhMMl<9k5K^#\Q~ F59in]ՅԖj[Z𱔢@ 5^Zx^p%D(5;񚘌`,'.js( 8%C"۰j[76CBӺK?z{|3HܫN|dhXة rDp <.a`2S:4V_?󴝭 ef8y 2I=$lRΠMQQ!5[wn8[_O,2)F+II6qNiHՓϭQVZ[䫺 tE 1=JV!O\2·1|NOtCTWqjb1VB &h^G XM,-#M&'Z6cbW&#K≬)[3IAo?!$#>X 'zўIE..)dzDd#aZ7{93r8u(=fetr%wTrmǖ W$b\NL;^#\V(R7e!Rj˩Z+Btʥ2}6Z[쒕6b@0I(DsjHPU 掙Q%¹Bה@_fQlfQ+>/eVQ',O Ϧ01(]TK7%#94PkJ>e XGs["/Fց.n~%jUemˍ)O}Ep5G iQM x!Aʕ`} qT$襁:/NZSh".Xxe!&}24E"]8N5*,M=MӞ @$/M.ܩY.673x[NlCTUQ 2C>Nv WaJ֤GN_n*yh98cBqY+ C Lk[e4yKkX321?"1I cԊ ׊VF=l^Y̗edqE8}f,F%0ϨC&|:?mK\bfVZ!E) =Fw(aE,gf@!ijT{b3Ҽ3pbMRj!ae!6C'~=)Ẁ2ש^h|1Tt"_%#_ $ -kB]Gf@]w \ Og 𳯶ˁvklHu?-/dYaV¨P*k -;}#гG=)W}GhCrF0 d?"T+< [.v}ԍ44IMW 4N|j26< Iޗ|.#kYw6D^VfMiC2z| 9H)XYu귐y<@Qdzk"h.hJgvi!TfpWҖsQAYz- ^:d=mV$щGd{\n]K:LBDãC.)Z2^Xdi@[@}X X#MX*D_v*$$G#C&@#^b%`~&n&$ 2 =^G[:Y/zf֦o7WGS?=[tJXQ# %32r,D HNӂG #Y x;R6hD8`3pyOMD6Dڧl[7_uhF:+f1o~OD`u# :.bWV ?x;` uLgOrz"U1!pK"ߦ!D n'[kq"?n~8KĨt,=wwA-vEBO`.Nda X|`F6dXl HDg Xn%UοY\!SM0^^z{>=I/CzI$݂P)%&a@Ew"Udf",dUC>5nWxM5:ZxiN^ x)R@09;!Ok{`' ʒzsVZF84,mSSx>=i?_t2oFnʧ`J qF_ȉ<YL_x nݾaFx%T5Ybfx׬T?1e7y{m |@Ȇ_e\.Z]-/>KNqQ~oJW)`rNEg{p;u/X -x"> OB~"8}й3YaX_=44)ڧ _uI qsdF!=wT~eQj҈` N6* []Ul6]DTѯ4O|Dwոɢ >8//eɶ~vu_p'IR`pg[$1<V@V^#{V>c$Lt g?T\s}AIޒD' >qosKBV鎾ƝezЊ'&?5D>DɷX;*lOrkpeь7eR~ [g֖jK Q*aUh4)rSEX/s͵mE〟ʔXphyn_ϤL~m|O$O6==FQuB۽' (t;a'o1/1"ұ#FmmMjq=37U9@z 53L##Y OلS1cR"Co3'=*/V"7e fIQAK,/ϼp[ 툲57qHޏYlb*?z|y|tx$6Myqil3ppz:\WGO3cU@AY{2jR*$K,l']$niꆢ}m,8R*oG;b$nʅYK&$NW<ƞ曓fԢSάpoS$y/,Mg~#8]{~jA%#4a =W ,B\cs&}uq<5u E_=BIa'pg6&`-M\| Q-1,&x.f{ța2PYziF,=Quu d `Q6Ǥe`h ύOT5!Q)ߺ7d4nsϤRI$NXIR6HU`Y[of(p!4lܷqw0Xau}[69`6H$WJHEeF:>9gmCSYV)Vs"ZPDHGn[rlz"0_k!>w1Vw=,!lmG7vo ;]ȭ p97Ϩ5ME:HNH^!}G#8OΙˎ U'/'vjvѥxZTDXm67Bׯ),qvW8Vzup2hյPzG0o9خA4ڀ-tE46SY\)R P$5%!2A@Ad=B3q~d BcbۉY>jS9" ;`|ÈȂI;kOKzP&l,ӘaL5^:rwĹma6HE>s+Ԫuo-*uHV֟PaE"G ͼ@JrDt"4}d)6N{<'dRϚǁ`p`AIŌ7U4'ep!=$˙ʀ(xCoz8]B4+B`ٸsP 8ou\aѯE} ә+{gldʞ#459itnt~( <2 &7zJ.h@c71nRm'h]ܷHdZ#YaRz/T5o-rϲoLHpmPXID9a s52YUDǛ 5&kBǤ^wv zYVQt/|t|jp![Ce8IWnw#̵/fjUӳxdKgQmÆSV+oL/345'YV91+zD޶cs%yˬSuֹq圢/OmSիagva?y&햟: Y)f5nf,8Za]I%"u7kv>f[L>'ȠbX3Y;#PÝ hH(f'n00=Yn h&(߿~acse񱴱FF̆PY?C32݀Oy)3$OAsrO)N%['H+-.`Gc}%\h]][]XQO{jS >N5%!2#[^V^ zM[Arq5[b~-Ќ|c݈֓K϶3OC?Q1,-!d7Q8Ocl(Ӫ6_ :V ҥC݈քn 6 I{,xoҕ_$0qm."@;xtc]c7mO2ҖG ȭҬ EZT*79iSy?Fd֕ry ?ٚʉLJRMt9<)hR]|moDEuhi3]AAJ>x_5%y㖈 06o(RNfLYn#Ёx.YUn &@Qk 1M6>tMO;?!\0627(7m< [px7["k\c^pSGozT]>V7测:Pgh1U;2%bX~?o_mx Nv(3J ?596 J}g3PRIEg1jVh,"K' >/2r~K.vK?0P! (?{7+P#8qSm? EOlV"PWV`R-+kurk-XC_kTݮ{/{$ElmP#ۭԅ֜}QD1 (lv_R8Ϙ( TG0{n5PLD  Uh( .L}z>f_1 2s5L:V/?뿞7S݋#໇p {-QYgH7.N^k]ۨi(,x6 rgp,+Ai#Me@>LzBJX-e|7~ZS$^\H,&hG,iNzX!kոNz.KĬ~HŨeՌV=enxi׮mqӗYJHTwjuS 4/XG-ABifw֔2#Wqnɰ':ϩʯad>/O{nr[z9- Bt֦^#.ǖ}cqˈhtC!FPK_X;6?;58Zl[< ܷާO6hdꄳ&K;)+8Gm</;uh4%}>z~{9~1S"ݼCIAjtH0{D/b8淼p8%"ے]Ƨvڧp`Mǥtg@ ӓ_99yCP!t#B,>w]ZYсcPmPathjӹQؤV-M>;io$TK5+0nmPa\`8wDWE"N9ssJn}qcYG=2K#\ͣEYc+kR)|c cd)}߿v R)uVij 8 _98c?qwTϿ4\鐌?%{2rBK`|}H7~&l˃ՇR\KmWDV%6P#u n|8r K5*q#/3th4>5rvkG$NYk+M+JhOUAUvhy )Ӄ v Cjk_zπz;"9l20WDztgN'}҅ X^G _ բ+_+H#vIo bܖwy޼C9پ><}-//!}i,VDjnDlPJU66dbN;偘[;1ⓑj/}] | bzjķ5"4-p2J>@ (z$%r!3C5s?|#Sm` 1vEBE|FE ՞E@N~/|+I."&F߅| ۵N\F?5# Y^~cv|Zt%XF-79 0^&?vz?~~dC0WuM5f :s rw9$qҊdHh' ]$Aފ/9ُ8dK#-lF+HF̷C)A?xLJwK4X" ^7OT GO+ȅ8Biz2.2$I@ENOY;2:q<6xF}A5soֹgFA~sy_D; N4&Av;k@$%å(3?#Z+U}"O+/}Zl I2VnE{ /: ٲ8nG^{VPv- <8{r*t'-^ڱAEֆdIn~)F21 Ac"k)%F="ڦYhCg %6ʅ>PGɥjÿ$ĠSsachesi-2.0.4+ds/assets/sachesi.ico000066400000000000000000013226261362064257300171770ustar00rootroot00000000000000 ( f ( @@ (B(00 %j   h.( vS?<;;;<<<<<<<<<<<<<<<?Hayt\>F`J=5356BYnBPos4bRC3 V N L K J LLLLLLLLLLLLL L L T l 0sIeHOn[GT@ \ bgnNxAz3qHA7ݼ6߽5666666666666666667߼<ݺIJ4qFy_zyUW 1J\<_W4Q5vv1V{8qFD7340,*~%z!y!x!x!x!x!x"y"y"y"y"y"y"y"y"y"y"y"y"y"y"y"y"y!y%{+/585@PJJugpj 0|F]8Lyx]< lXo>}Hθ71-(}"ywvvsrrqqpoonnoooooooooooooooooopqqrruw!x"y'|.34FGLy]C0Hj\V= kYoPc^=@ظ0-%{vurppoml l k j j j iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii i j j j l lmnoqquw%{/1=CKnu,E*@_ZTG`^F:,&{vurpoml l j j iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii i j j klmoqru x&{15BļHyn+n -BPVM2`[G9-"yusqpm l k j jiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii j j klmoqsv%{/5BȻHzn+dQMbZ@p+O:-!yurpnm l k iiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiii j j kmnqsw$z.6C¼LnhuTBXE[ YW73&{vspnm l j iiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhiiiiiiiiii i j kmnqsv&{/;NP:?|[ZMf>=5ظ.$zurom k j iiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhiiiiiiii i j kmnqt!x+~4FؼQv]&{GU<@n9C3(|wtpn l j iiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhhhhhhhiiiiii i j kmprv%z1Sc;uMYZO4_<:/&{uro l j iiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiii i kmpt"y,MziVq>at$J8&|urn l j iiiiihhhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhiiii i klpt$z3AOw@cE<c @u5(}vro l jiiiihhhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhiiii i kmqu&{3C`v QVqS^MC>Թ+ wro l j iiiihhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhiii i knqu&{8Eh8C{CY2G H2"ytpm j iiiihhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhiii j knrv-'|up l jiihhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhiii knr"x3CBr3BI6%{so l iiihhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhii jmqw/J]HXXrT1!xrn k iiihhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhii i lpu+~>HG._R"C-vqm j iihhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhii i knt'|<]r]g)H'|up l jiihhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhi i jmr$z7LĺjXzP>)}to k iiihhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhii imr"x6KǣYVVy5<*~uo k iiihhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhi j lp w5D9dX_:(}tp k iihhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhh j lq w0N޽lBV\ZJ0wsn k iihhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhi j kov,BTAIja]F)}tqm j iihhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhi i kou*}@TCIe~F'|so l iihhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhi i knt)}@TCV3I)}so k iiihhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhi jnt)}@TBFFZc0tp k iiihhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhii jnt*}Cj?JYs<:wq l iiihhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhi jnu-Nݿ Wg=)}rm jihhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhi kov0E hYY5{A-un jiihhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhi ko w6LBna E-vp kihhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhh i kq x7Naw[rE,vo kiihhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhi i lq"y9_3U]3vq l iihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhi i lr$z=NIC:SH5!xq l iihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhh jms)}BiDPos>@$zrm jhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhh jnt.O࿉ fYG̹(}tn jihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhi jnv1Fdq4xH,uo jiihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhi kp!x9_/GOu5vp kihhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhh i lq$z>PGGhV?6"yq l ihhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhi jms)}Fz6jF̹)}sm jihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhh jnu2J3vO/uo jhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhi kow:U{QKSC:"yq kihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhii lr'{AeB[z7~>+~sm jhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhh jmt2I.}JM{5un jihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhh ko w;V}RPQA8#zq kihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhi kr'{BeB[cF̹*~sm jhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhh jmt1I.3~P0uo jihhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhi jo w;VySVZA:#yq kihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhj lr(|Dy6ra/xB+~sm jhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhh jmt4SqDb_7wo jihhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhh kp#y?XERuH&{r liihhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhiils0H5VTx3vn jihhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhiiiiiiiiiiiiiihhhhhhhhhhhhhhhiiiiiiiiiiiiiiihhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhi jo w>doT\@&{q kiihhhhhiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhiiiiiiiiiiiiihhhhhhhhhhhhhhhhhiiiiiiiiiiiiihhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiihhhhhhj lr*~JeAP{3um ihhhhhhhiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiihhhhhhi jnv:Z}J[A&{q kihhhhhhiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhiiiiiiiiiiihhhhhhhhhhhhhhhhhhhiiiiiiiiiiihhhhhhhhhhhhhhhhhhhiiiiiiiiiiihhhhhhhhhhhhhhhhhhhiiiiiiiiiihhhhhhhhhhhhhhhhhhhiiiiiiiiiiihhhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhi kr*~JeAP|4um jhhhhhhhiiiiiiiiiiiiiiiiiihhhhhhgggggghhhhhhhiiiiiiiiiiihhhhhhgggggghhhhhhhiiiiiiiiiiihhhhhhgggggghhhhhhhiiiiiiiiiiihhhhhghiiiiihhhhhhhiiiiiiiiiihhhhhhhiiiiihghhhhhiiiiiiiiiiihhhhhhhiiiiihghhhhhiiiiiiiiiiiihhhhhhh jnv:Z~J\B&{q kihhhhhhiiiiiiiiiiiiiiiiiiihhhhhhlllllljghhhhhiiiiiiiiiiihhhhhhlllllljghhhhhiiiiiiiiiiihhhhhhlllllljghhhhhiiiiiiiiiiihhhhhjeccccciihhhhhiiiiiiiiiihhhhhiicccccejhhhhhiiiiiiiiiiihhhhhiicccccejhhhhhiiiiiiiiiiiiihhhhhhi kr*~JÏeBQ|4um jhhhhhhiiiiiiiiiiiiiiiiiiiihhhhigxNiGlHkHkHiG \khhhhhiiiiiiiiiiihhhhigxNiGlHkHkHiG \khhhhhiiiiiiiiiiihhhhigxNiGlHkHkHiG \khhhhhiiiiiiiiiiihhhgj Y:$10&,2&-1&-2&-.',_Cghhhhhiiiiiiiiiihhhhhg_C.',2&-1&-2&-0&,:$1 Yjghhhiiiiiiiiiiihhhhhg_C.',2&-1&-2&-0&,:$1 Yjghhhiiiiiiiiiiiiiihhhhhh jnv:Z~K\B&{q kiihhhhhiiiiiiiiiiiiiiiiiiiihhhhi_C,$+#+#+#+#,#0'- \jhhhhiiiiiiiiiiihhhhi_C,$+#+#+#+#,#0'- \jhhhhiiiiiiiiiiihhhhi_C,$+#+#+#+#,#0'- \jhhhhiiiiiiiiiiihhhhf;$2 *$&('%(&%(&%(&'(',$xNkghhhiiiiiiiiiihhhgkxN,$'('%(&%(&%(&&(' *$;$2fhhhhiiiiiiiiiiihhhgkxN,$'('%(&%(&%(&&(' *$;$2fhhhhiiiiiiiiiiiiiihhhhhhi kr*~JÏeBP{4um iihhhhhiiiiiiiiiiiiiiiiiiiiihhhic.','('*&')&')&')&',&)+#jGlghhhiiiiiiiiiiihhhic.','('*&')&')&')&',&)+#jGlghhhiiiiiiiiiiihhhic.','('*&')&')&')&',&)+#jGlghhhiiiiiiiiiihhhhic/',''''&&'&&'&&'&&*&(+$iGlghhhiiiiiiiiiihhhgliG+$*&('&&'&&'&&'&&'''/',cihhhhiiiiiiiiiihhhgliG+$*&('&&'&&'&&'&&'''/',cihhhhiiiiiiiiiiiiiihhhhhh jnw:Z}LfA&{q kiihhhhhiiiiiiiiiiiiiiiiiiiihhhhic1&-%(&&&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&&&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&&&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhid1&-%(&'&&&&&&&&&&&)&'+#lIlghhhiiiiiiiiiihhhgllI+#)&'&&&&&&&&&'&&%(&1&-dihhhhiiiiiiiiiihhhgllI+#)&'&&&&&&&&&'&&%(&1&-dihhhhiiiiiiiiiiiiiihhhhhii kr*}JÓfQRw4um iihhhhhiiiiiiiiiiiiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiiiiiiihhhhhi inv;[uNkF&{q kiihhhhhiiiiiiiiiiiiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiiiiiiihhhhhii lr,Pӿ aZaq9vn jihhhhhiiiiiiiiiiiiiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiiiiiiiihhhhhi jo"x=WDQ+N+r liihhhhhiiiiiiiiiiiiiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiiiiiiiihhhhhh i lt2WPez$@"yo jihhhhhiiiiiiiiiiiiiiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiiiiiiiihhhhhhi kp&{EoV;P~2tm jhhhhhhiiiiiiihhhhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhfkkH*#(%'&&&&&&&&&&&&%'&0&,chhhhhhhhhhhhhhhhhhfkkH*#(%'&&&&&&&&&&&&%'&0&,chhhhhhhhhhhhhhhhhhhhiihhhhhh jmv9SwGi J߹'|q kiihhhhhiiiiiiihhhhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhfkkH*#(%'&&&&&&&&&&&&%'&0&,chhhhhhhhhhhhhhhhhhfkkH*#(%'&&&&&&&&&&&&%'&0&,chhhhhhhhhhhhhhhhhhhhiihhhhhii kr,PcT[S7vn jihhhhhiiiiiiihhhhhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#jHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#jHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#jHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#jHkfhhhhhhhhhhhhhhhhfkjH*#(%'&&&&&&&&&&&&%'&0&,chhhhhhhhhhhhhhhhhhfkjH*#(%'&&&&&&&&&&&&%'&0&,chhhhhhhhhhhhhhhhhhhhhiihhhhhi jn"x=XDQL,r liihhhhhiiiiiiihhhhhhhgggggggggggggggc1%,$'&&&&&&&&&&&&&(%'*#lHkeggggggggggggggggggc1%,$'&&&&&&&&&&&&&(%'*#lHkeggggggggggggggggggc1%,$'&&&&&&&&&&&&&(%'*#lHkeggggggggggggggggggc1%,$'&&&&&&&&&&&&&(%'*#lHkeggggggggggggggggeklH*#(%'&&&&&&&&&&&&$'&1%,cggggggggggggggggggeklH*#(%'&&&&&&&&&&&&$'&1%,cgggggggggggggghhhhhhhiihhhhhh i lt2WSMb>#zp kiihhhhiiiiiiiihhhhhhikkkkkkkkkkjjjik^+&*&'&&&&&&&&&&&&&(%')#^Alijjjkkkkkkkkkkjjjik^+&*&'&&&&&&&&&&&&&(%')#^Alijjjkkkkkkkkkkjjjik^+&*&'&&&&&&&&&&&&&(%')#^Alijjjkkkkkkkkkkjjjik^+&*&'&&&&&&&&&&&&&(%')#^Alijjjkkkkkkkkkkjjjil^A)#(%'&&&&&&&&&&&&&'&+&*^kijjjkkkkkkkkkkjjjil^A)#(%'&&&&&&&&&&&&&'&+&*^kijjjkkkkkkkkkihhhhhhiihhhhhii kp&{Fx[GKq4um iihhhhhiiiiiiiihhhhhj_s!Pt Qt Qt Qt Qt Qt Qt Qt Qt QtQtQtQsPyTR#>"'$'&&&&&&&&&&&&&&&&&$&%0&,qOuRtQtQtQt Qt Qt Qt Qt Qt Qt Qt Qt Qt QtQtQtQsPyTR#>"'$'&&&&&&&&&&&&&&&&&$&%0&,qOuRtQtQtQt Qt Qt Qt Qt Qt Qt Qt Qt Qt QtQtQtQsPyTR#>"'$'&&&&&&&&&&&&&&&&&$&%0&,qOuRtQtQtQt Qt Qt Qt Qt Qt Qt Qt Qt Qt QtQtQtQsPyTR#>"'$'&&&&&&&&&&&&&&&&&$&%0&,qOuRtQtQtQt Qt Qt Qt Qt Qt Qt Qt Qt Qt QtQtQtQuRqO0&,$&%&&&&&&&&&&&&&&&'&&"'$R#>yTsPtQtQtQt Qt Qt Qt Qt Qt Qt Qt Qt Qt QtQtQtQuRqO0&,$&%&&&&&&&&&&&&&&&'&&"'$R#>yTsPtQtQtQt Qt Qt Qt Qt Qt Qt Qt Qs!P_jhhhhhiihhhhhhi imv<[rVB*~q kiihhhhhiiiiiiiihhhhi_B9>1@82?82?82?82?82?82?72?72?82?82?72?72?72>71?7295'''&&&&&&&&&&&&&&&&&&%%%-/.2?81>72?72?72?72?82?82?82?82?82?82?82?82?82?82?72?72?72>71?7295'''&&&&&&&&&&&&&&&&&&%%%-/.2?81>72?72?72?72?82?82?82?82?82?82?82?82?82?82?72?72?72>71?7295'''&&&&&&&&&&&&&&&&&&%%%-/.2?81>72?72?72?72?82?82?82?82?82?82?82?82?82?82?72?72?72>71?7295'''&&&&&&&&&&&&&&&&&&%%%-/.2?81>72?72?72?72?82?82?82?82?82?82?82?82?82?82?72?72?71>72?8-/.%%%&&&&&&&&&&&&&&&&&&'''2951?72>72?72?72?72?82?82?82?82?82?82?82?82?82?82?72?72?71>72?8-/.%%%&&&&&&&&&&&&&&&&&&'''2951?72>72?72?72?72?82?82?72?82?82?82?82?81@8B9>_ihhhhiiihhhhhi i lr0OF^;#yo jiihhhhiiiiiiiiihhhgks Q1@8>8;;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:989(((&&&&&&&&&&&&&&&&&&%%%101<:<;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:989(((&&&&&&&&&&&&&&&&&&%%%101<:<;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:989(((&&&&&&&&&&&&&&&&&&%%%101<:<;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:979'((&&&&&&&&&&&&&&&&&&%%%000=:<;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:=:<000%%%&&&&&&&&&&&&&&&&&&'((979;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:=:<000%%%&&&&&&&&&&&&&&&&&&'((979;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:>8;1@8s Qkghhhiiiihhhhii kp%{EvWHKp4tl iihhhhhiiiiiiiiihhhgkt Q3?8;8:999999999999999999999999999999999999999999888'''&&&&&&&&&&&&&&&&&&%%%000;;;999999999999999999999999999999999999999999999999999999999888'''&&&&&&&&&&&&&&&&&&%%%000;;;999999999999999999999999999999999999999999999999999999999888'''&&&&&&&&&&&&&&&&&&%%%000;;;999999999999999999999999999999999999999999999999999999999999)))%%%&&&&&&&&&&&&&&&%%%222:::999999999999999999999999999999999999999999999999999999:::222%%%&&&&&&&&&&&&&&&%%%)))999999999999999999999999999999999999999999999999999999999999:::222%%%&&&&&&&&&&&&&&&%%%)))999999999999999999999999999999999999999999;8:3?8t Qkghhhiiiihhhhhi imu<[qVK+r liihhhhhiiiiiiiiihhhgkt Q2?8;8:999999999999999999999999999999999999999999:::...%%%%%%%%%%%%%%%%%%'''666:::999999999999999999999999999999999999999999999999999999999:::...%%%%%%%%%%%%%%%%%%'''666:::999999999999999999999999999999999999999999999999999999999:::...%%%%%%%%%%%%%%%%%%'''666:::999999999999999999999999999999999999999999999999999999999:::555)))(((((((((((((((...999999999999999999999999999999999999999999999999999999999999999999...((((((((((((((()))555:::999999999999999999999999999999999999999999999999999999999999999...((((((((((((((()))555:::999999999999999999999999999999999999999;8:2?8t Qkghhhiiiihhhhhii ls0U)KiA$zp jiihhhhiiiiiiiiiihhhgkt Q2?8;8:999999999999999999999999999999999999999999999999222000000000000000666:::999999999999999999999999999999999999999999999999999999999999999999222000000000000000666:::999999999999999999999999999999999999999999999999999999999999999999222000000000000000666:::999999999999999999999999999999999999999999999999999999999999999:::999888888888888888999999999999999999999999999999999999999999999999999999999999999999999999888888888888888999:::999999999999999999999999999999999999999999999999999999999999999999999888888888888888999:::999999999999999999999999999999999999999999;8:2?8t Qkghhhiiiiihhhhi i kp(|IxT^gR9um jiihhhhiiiiiiiiiihhhgkt Q2?8;8:999999999999999999888888888888888888888888888888999999999999999999999888888888888888888888888888888888888888888888888888888888888888888888999999999999999999999888888888888888888888888888888888888888888888888888888888888888888888999999999999999999999888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888999999999999999999999;8:2?8t Qkghhhiiiiihhhhii jn x>fC]4I1sliiihhhhiiiiiiiiiihhhgkt Q2?8;8:999999999999999999===>>>>>>>>>>>>>>>>>>>>>>>>>>>==================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>==================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>==================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===999999999999999999999;8:2?8t Qkghhhiiiiihhhhiiilt7RG^ J޹'|q kiihhhhhiiiiiiiiiihhhgkt Q2?8;8:999999999999:::999""""""999:::999999999999999;8:2?8t Qkghhhiiiiihhhhhi i kq+Sмlf|$;!xo jiihhhhiiiiiiiiiiihhhgkt Q2?8;8:999999999999;;;;;;999999999999999;8:2?8t Qkghhhiiiiiihhhhii jo%{AjNY^y8tm jiihhhhiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666666:::999999999999;8:2?8t Qkghhhiiiiiihhhhii jnu>iefcK,r liiihhhhiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666666:::999999999999;8:2?8t Qkghhhiiiiiihhhhiiils4K@` K%{p kiiihhhhiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666666:::999999999999;8:2?8t Qkghhhiiiiiihhhhii i kq*~Spdy";!xo jiiihhhiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666666:::999999999999;8:2?8t Qkghhhiiiiiiihhhiii jp%{AjQ`ey:um iiiihhhiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666666:::999999999999;8:2?8t Qkghhhiiiiiiihhhhii jmv@sfi?Bx0s liiihhhhiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666666:::999999999999;8:2?8t Qkghhhiiiiiiihhhhiiilt7O|K|S(}q liiihhhhiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666666:::999999999999;8:2?8t Qkghhhiiiiiiihhhhiii lr/V+U~F$zp jiiihhhiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666666:::999999999999;8:2?8t Qkghhhiiiiiiihhhhiii kq(}PƐacy 9!xn jiiihhhiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666666:::999999999999;8:2?8t Qkghhhiiiiiiiihhhiii jo%z@e Lahy;tm iiiihhhiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666'*DI V[$_e$`g"Y`JO.1  #&DI!W^$ah#^d!V\IO48  @E V\$_f#^eLR)+ ,/JP"Z`$`g#]dPV9< ?C U[$_f$`f V[?C 666:::999999999999;8:2?8t Qkghhhiiiiiiiihhhiii jmvBtekEIy3sliiiihhhiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::66658-yBILNPPOMJD148'fn?ILOPPOMKHA4%bj37?C8HLNPPMJ>$`f15(jr$`g%bi&ck#]c15(jr$`g%bi&ck#]c !#+t|BJMOPONKF5IN !X_9HLNPPNLG7=B#]c&ck%bi$`g(jr15666:::999999999999;8:2?8t Qkghhhiiiiiiiihhhhii imt9T}PpK-r liiiihhhiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::6668<:LNLJIIIIIJJKNL5 EJEOLJIIHHIIJKLMQG  1NMJIIIIIJLQ;(jrWOPQL (jrWOPQL #^eHOKJJIIIIJKNO8<@GNMJIIHHIIJNN/  LQPOW(jr666:::999999999999;8:2?8t Qkghhhiiiiiiiihhhhiii ls3HwCm S'{q kiiihhhhiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666MIJILDGL BG1>BMS0NIJIN!X_ FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiihhhiii jn!xA1nW\9tm iiiihhhiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::66648@E3NIJJKF-xOIJJI),&dkPIJKF QWNIJIM< %biPIJKF %biPIJKF GLNIJJKC !#EKJIN3 FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiihhhiii imu@g|aBGx4tl iiiihhhiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666BLJJINPV:MIIM7%biPIJKF 7MIJJJ-1%biPIJKF %biPIJKF 5NIJJM?D1NIIL> FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiihhhiii ilt9PxKAE{2r liiiihhhiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666%bhOIJIO/}BLJIO*px%biPIJKF  GKJIN2%biPIJKF %biPIJKF FKJIM8'hoPIJKD  FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiihhhhiii ls6MzIrL-r liiiihhhiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666/2JJJIM; EKJIP&dk%biPIJKF AELJJIMGL%biPIJKF %biPIJKF =ALJJINOU&elPIJKE  FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiihhhhiii lr3JxCi Rݹ(|q kiiihhhhiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666GKJJLB DKJIP'gn%biPIJKF %biOIJKG%biPIJKF %biPIJKF $`gOIJKH 0NIJLA FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiihhhhiii lr,~ZɻyNl Dݸ'|p jiiihhhiiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666 FKJJKE >LIIO.|%biPIJKF .|OIIL>%biPIJKF %biPIJKF .{OIIL> DKJIN7 FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiihhhhiii jp)}L| YG^ @&{o jiiihhhiiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666 GKJJKD 4NIJL@ %cjPIJKF 6MIIN5%biPIJKF %biPIJKF 6MIIN4/~NIJIO%cj FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiiihhhiii jo(|Gj OG\ @%{o jiiihhhiiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666=BLJJIL?"]cOIJIN#]d%biPIJKF =MIIO/}%biPIJKF %biPIJKF =MIIO.y*ryNJJJKH  FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiiihhhiii jp'|Fj OG\ @%{o jiiihhhiiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::6660~NIJIN5 GKJJKJ!Y_&fmPIJKF ALJIO*ow%biPIJKF %biPIJKF BLJIP&el $'9NIJJIO'iq FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiiihhhiii jo'|Fj OG\@%{o jiiihhhiiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666,0IJJJIO&dl+r{OHJJJM@.{'fn&em,s|6FKJJKF  DKJIP'ip%biPIJKF %biPIJKF  DKJIM8"Za&dk%bi%bi%bi%bi%bi%bi%bi%bi%bi%bi%bi$ag'ip,0 &emINIJJHP7 FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiiihhhiii jo'|Fj OG\@%{o jiiihhhiiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666 =MIJJJJ*- >OHJJILOPPONKJJJKF  EKJIP&dk%biPIJKF %biPIJKF  EKJJJMPPPPPPPPPPPPPOV&em14>NJIJIJO6 FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiiihhhiii jo'|FiOG\@%{o jiiihhhiiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666 6NIJJIN6 =OJIIIIHHHHIJJJKF  EKJIP%cj%biPIJKF %biPIJKF  EKJJJIHHHHHHHHHHIIIHO%ah U[KMIJJIMK(iq FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiiihhhiii jo'|FiOG\@%{o jiiihhhiiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666  ;NIJJJJK15+s{HOMLKKLMOMJJJKF  CLJIP'gn%biPIJKF %biPIJKF  CLJJJJKKKKKKKKKKJJJIP%ah"Z`NJIJIJO;-1 FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiiihhhiii jo'|FiOG\@%{o jiiihhhiiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666HMFMIJJJIO(ks #]e4?DD@:,u}8MIJKF @LJIO*ow%biPIJKF %biPIJKF ALJJJHFFFFFFFFFFJJJIO#^e7;LJJJILI#]d FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiiihhhiii jo'|FiOG\@%{o jiiihhhiiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666 .zNKIJJJHP2  %biPIJKF ;MIIO.|%biPIJKF %cjPIJKF C/}OIJJL8= FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiiihhhiii jo'|FiOG\@%{o jiiihhhiiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666*oxNJIJJJINFFL&fmPIJKE RYNIJKH %biPIJKF 1NIIL=#^eOIJKD *.JJJJI),%'EKJIN3,s|OIJJJ,/%biPIJKF KQMIJIN U[5NIIM95NIIO+s{BLJIO+s{ FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiiihhhiii jo'|FiOG\ @%{o jiiihhhiiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::6660NIJJIN7 'gnNIJIO%bi15KJJIM: %biPIJKF  %biPIJJJKNOPPOKIJJIM>4OIJIMLGFJNJJILC-yOHJJJOJFGINP: FKJIP%bi666:::999999999999;8:2?8t Qkghhhiiiiiiiiiihhhiii jp)}Hl Qb O(|p kiiihhhhiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666>LIJJK9>/}NHIIIIIIJJIKO"[a/}PJIJJIIIIIHN)nv%biPIJJJIIIIIIJJJILI/2;PIJJIKKJIJILL;? :PIIJIJKKJHJG  EJIHO%`g666:::999999999999;8:2?8t Qkghhhiiiiiiiiihhhhiii kq+~UƟ iX+~r liiihhhhiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666 DKJJKG>QLJJIIIIJKOHRW&fmJNKJIIIIJKQ=%biPIJJJMKJIIIIJKOF;?2NMJJHHIJKOGDJ6OMJIIIIIJMQJO LQPOW(jr666:::999999999999;8:2?8t Qkghhhiiiiiiiiihhhhiii lr/WQAEy2s liiiihhhiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666 EKJJKF *-(ls%'IO"\b$_f U[AE 666:::999999999999;8:2?8t Qkghhhiiiiiiiiihhhiii ilt8OzINR{9tl iiiihhhiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666;MIJJJ15%biPIJKF 666:::999999999999;8:2?8t Qkghhhiiiiiiiiihhhiii ilu=Z}Uiqh?un jiiihhhiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666.{OIJIO'ho%biPIJKF 666:::999999999999;8:2?8t Qkghhhiiiiiiiiihhhiii jnvCu`lQq<#yo jiiihhhiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666GMMIJJKE !%biPIJKF 666:::999999999999;8:2?8t Qkghhhiiiiiiiiihhhiii jo$zAb IG] @%{o jiiihhhiiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666 ALIJIM> !NTCH%biPIJKF 666:::999999999999;8:2?8t Qkghhhiiiiiiiiihhhiii jo'|Eh NdP(|q kiiihhhhiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666 V\OIJJIMF)mv37/3"[b:Q:%biPIJKF  GM$aiGM 666:::999999999999;8:2?8t Qkghhhiiiiiiiihhhhiii kq*~UşjU-r liiihhhhiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::6667OIJJIKOKGFGJOMJI%'%biPIJKF   wn jiiihhhiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666NS?LOMLKKLMONE.{&)%biPIJKF  CKJJJKC 666:::999999999999;8:2?8t Qkghhhiiiiiiiihhhiii jn"xB0oI`A%{o jiiihhhiiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666b}\l|8> xn jiiihhhiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::66648PW)nv5< 666:::999999999999;8:2?8t Qkghhhiiiiiiihhhhii jn"yB2pTuG&{p kiiihhhiiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666666:::999999999999;8:2?8t Qkghhhiiiiiiihhhhii kp(}LZR-r liiihhhhiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666666:::999999999999;8:2?8t Qkghhhiiiiiihhhhiii lr1U<RS8tm jiihhhhiiiiiiiiiiihhhgkt Q2?8;8:999999999:::666666:::999999999999;8:2?8t Qkghhhiiiiiihhhhii imu;Z}Vhw;= wn jiihhhhiiiiiiiiiiihhhgkt Q2?8;8:999999999999;;;;;;999999999999999;8:2?8t Qkghhhiiiiiihhhhii jn"xA}3keuG&|p kiihhhhiiiiiiiiiiihhhgkt Q2?8;8:999999999999:::999""""""999:::999999999999999;8:2?8t Qkghhhiiiiiihhhhii kp)}M}ZS.r liiihhhhiiiiiiiiiihhhgkt Q2?8;8:999999999999999999===>>>>>>>>>>>>>>>>>>>>>>>>>>>==================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>==================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>==================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>===999999999999999999999;8:2?8t Qkghhhiiiiihhhhiii ls2VESXs:um iihhhhhiiiiiiiiiihhhgkt Q2?8;8:999999999999999999888888888888888888888888888888999999999999999999999888888888888888888888888888888888888888888888888888888888888888888888999999999999999999999888888888888888888888888888888888888888888888888888888888888888888888999999999999999999999888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888999999999999999999999;8:2?8t Qkghhhiiiiihhhhhi imu>_tZMj>$zo jiihhhhiiiiiiiiiihhhgkt Q2?8;8:999999999999999999999999999999999999999999999999222000000000000000666:::999999999999999999999999999999999999999999999999999999999999999999222000000000000000666:::999999999999999999999999999999999999999999999999999999999999999999222000000000000000666:::999999999999999999999999999999999999999999999999999999999999999:::999888888888888888999999999999999999999999999999999999999999999999999999999999999999999999888888888888888999:::999999999999999999999999999999999999999999999999999999999999999999999888888888888888999:::999999999999999999999999999999999999999999;8:2?8t Qkghhhiiiiihhhhii jp%{DrUiQҺ)}q k iihhhhhiiiiiiiiihhhgkt Q2?8;8:999999999999999999999999999999999999999999:::...%%%%%%%%%%%%%%%%%%'''666:::999999999999999999999999999999999999999999999999999999999:::...%%%%%%%%%%%%%%%%%%'''666:::999999999999999999999999999999999999999999999999999999999:::...%%%%%%%%%%%%%%%%%%'''666:::999999999999999999999999999999999999999999999999999999999:::555)))(((((((((((((((...999999999999999999999999999999999999999999999999999999999999999999...((((((((((((((()))555:::999999999999999999999999999999999999999999999999999999999999999...((((((((((((((()))555:::999999999999999999999999999999999999999;8:2?8t Qkghhhiiiihhhhhi i kq,TmBP6sm jihhhhhiiiiiiiiihhhgkt Q3?8;8:999999999999999999999999999999999999999999888'''&&&&&&&&&&&&&&&&&&%%%000;;;999999999999999999999999999999999999999999999999999999999888'''&&&&&&&&&&&&&&&&&&%%%000;;;999999999999999999999999999999999999999999999999999999999888'''&&&&&&&&&&&&&&&&&&%%%000;;;999999999999999999999999999999999999999999999999999999999999)))%%%&&&&&&&&&&&&&&&%%%222:::999999999999999999999999999999999999999999999999999999:::222%%%&&&&&&&&&&&&&&&%%%)))999999999999999999999999999999999999999999999999999999999999:::222%%%&&&&&&&&&&&&&&&%%%)))999999999999999999999999999999999999999999;8:3?8t Qkghhhiiiihhhhhi imt9T~OZcB=!xo jiihhhhiiiiiiiiihhhgks Q1@8>8;;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:989(((&&&&&&&&&&&&&&&&&&%%%101<:<;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:989(((&&&&&&&&&&&&&&&&&&%%%101<:<;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:989(((&&&&&&&&&&&&&&&&&&%%%101<:<;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:979'((&&&&&&&&&&&&&&&&&&%%%000=:<;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:=:<000%%%&&&&&&&&&&&&&&&&&&'((979;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:=:<000%%%&&&&&&&&&&&&&&&&&&'((979;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:;8:>8;1@8s Qkghhhiiiihhhhii jn"yBjBaWÊ Iݹ(|q kiihhhhhiiiiiiiihhhhi_B9>1@82?82?82?82?82?82?72?72?82?82?72?72?72>71?7295'''&&&&&&&&&&&&&&&&&&%%%-/.2?81>72?72?72?72?82?82?82?82?82?82?82?82?82?82?72?72?72>71?7295'''&&&&&&&&&&&&&&&&&&%%%-/.2?81>72?72?72?72?82?82?82?82?82?82?82?82?82?82?72?72?72>71?7295'''&&&&&&&&&&&&&&&&&&%%%-/.2?81>72?72?72?72?82?82?82?82?82?82?82?82?82?82?72?72?72>71?7295'''&&&&&&&&&&&&&&&&&&%%%-/.2?81>72?72?72?72?82?82?82?82?82?82?82?82?82?82?72?72?71>72?8-/.%%%&&&&&&&&&&&&&&&&&&'''2951?72>72?72?72?72?82?82?82?82?82?82?82?82?82?82?72?72?71>72?8-/.%%%&&&&&&&&&&&&&&&&&&'''2951?72>72?72?72?72?82?82?72?82?82?82?82?81@8B9>_ihhhhiiiihhhhii kq+~OƔ ^?P5sm jihhhhhiiiiiiiihhhhhj_s!Pt Qt Qt Qt Qt Qt Qt Qt Qt QtQtQtQsPyTR#>"'$'&&&&&&&&&&&&&&&&&$&%0&,qOuRtQtQtQt Qt Qt Qt Qt Qt Qt Qt Qt Qt QtQtQtQsPyTR#>"'$'&&&&&&&&&&&&&&&&&$&%0&,qOuRtQtQtQt Qt Qt Qt Qt Qt Qt Qt Qt Qt QtQtQtQsPyTR#>"'$'&&&&&&&&&&&&&&&&&$&%0&,qOuRtQtQtQt Qt Qt Qt Qt Qt Qt Qt Qt Qt QtQtQtQsPyTR#>"'$'&&&&&&&&&&&&&&&&&$&%0&,qOuRtQtQtQt Qt Qt Qt Qt Qt Qt Qt Qt Qt QtQtQtQuRqO0&,$&%&&&&&&&&&&&&&&&'&&"'$R#>yTsPtQtQtQt Qt Qt Qt Qt Qt Qt Qt Qt Qt QtQtQtQuRqO0&,$&%&&&&&&&&&&&&&&&'&&"'$R#>yTsPtQtQtQt Qt Qt Qt Qt Qt Qt Qt Qs!P_jhhhhhiiihhhhhi jmt8VDZcB>"xn jihhhhhiiiiiiiihhhhhhikkkkkkkkkkjjjik^+&*&'&&&&&&&&&&&&&(%')#^Alijjjkkkkkkkkkkjjjik^+&*&'&&&&&&&&&&&&&(%')#^Alijjjkkkkkkkkkkjjjik^+&*&'&&&&&&&&&&&&&(%')#^Alijjjkkkkkkkkkkjjjik^+&*&'&&&&&&&&&&&&&(%')#^Alijjjkkkkkkkkkkjjjil^A)#(%'&&&&&&&&&&&&&'&+&*^kijjjkkkkkkkkkkjjjil^A)#(%'&&&&&&&&&&&&&'&+&*^kijjjkkkkkkkkkihhhhhhiihhhhhhi jo#yCjCadOԺ+r kiihhhhhiiiiiiihhhhhhhgggggggggggggggc1%,$'&&&&&&&&&&&&&(%'*#lHkeggggggggggggggggggc1%,$'&&&&&&&&&&&&&(%'*#lHkeggggggggggggggggggc1%,$'&&&&&&&&&&&&&(%'*#lHkeggggggggggggggggggc1%,$'&&&&&&&&&&&&&(%'*#lHkeggggggggggggggggeklH*#(%'&&&&&&&&&&&&$'&1%,cggggggggggggggggggeklH*#(%'&&&&&&&&&&&&$'&1%,cgggggggggggggghhhhhhhiihhhhhi i kr-ShBSu7um jhhhhhhiiiiiiihhhhhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#jHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#jHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#jHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#jHkfhhhhhhhhhhhhhhhhfkjH*#(%'&&&&&&&&&&&&%'&0&,chhhhhhhhhhhhhhhhhhfkjH*#(%'&&&&&&&&&&&&%'&0&,chhhhhhhhhhhhhhhhhhhhhiihhhhhh jmu;VsPUnD&{p kihhhhhhiiiiiiihhhhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhfkkH*#(%'&&&&&&&&&&&&%'&0&,chhhhhhhhhhhhhhhhhhfkkH*#(%'&&&&&&&&&&&&%'&0&,chhhhhhhhhhhhhhhhhhhhhihhhhhhi kp'|HuZGW2s lihhhhhhiiiiiiihhhhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhhhc0&,%'&&&&&&&&&&&&&(%'*#kHkfhhhhhhhhhhhhhhhhfkkH*#(%'&&&&&&&&&&&&%'&0&,chhhhhhhhhhhhhhhhhhfkkH*#(%'&&&&&&&&&&&&%'&0&,chhhhhhhhhhhhhhhhhhhhiihhhhhhi lt4ZhPWE="xn jihhhhhiiiiiiiiiiiiiiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiiiiiiiiihhhhhi jn#y@]EU` Oٺ+~r kihhhhhhiiiiiiiiiiiiiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiiiiiiiihhhhhhi kr-SdCSw8um jhhhhhhiiiiiiiiiiiiiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiiiiiiiihhhhhh jmv;VuQUmD&{q kihhhhhhiiiiiiiiiiiiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiiiiiiihhhhhhi kq(|HuZQX2tl iihhhhhiiiiiiiiiiiiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&'&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiihhhglkH+#)&'&&&&&&&&&'&&%(&1&-cihhhhiiiiiiiiiiiiiiihhhhhi imt5\ k]fEB#zp jihhhhhhiiiiiiiiiiiiiiiiiiiihhhhic1&-%(&&&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&&&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhic1&-%(&&&&&&&&&&&&&)&'+#kHlghhhiiiiiiiiiihhhhid1&-%(&'&&&&&&&&&&&)&'+#lIlghhhiiiiiiiiiihhhgllI+#)&'&&&&&&&&&'&&%(&1&-dihhhhiiiiiiiiiihhhgllI+#)&'&&&&&&&&&'&&%(&1&-dihhhhiiiiiiiiiiiiiihhhhhhi jp$zFmEcUR1tl iihhhhhiiiiiiiiiiiiiiiiiiiiihhhic.','('*&')&')&')&',&)+#jGlghhhiiiiiiiiiiihhhic.','('*&')&')&')&',&)+#jGlghhhiiiiiiiiiiihhhic.','('*&')&')&')&',&)+#jGlghhhiiiiiiiiiihhhhic/',''''&&'&&'&&'&&*&(+$iGlghhhiiiiiiiiiihhhgliG+$*&('&&'&&'&&'&&'''/',cihhhhiiiiiiiiiihhhgliG+$*&('&&'&&'&&'&&'''/',cihhhhiiiiiiiiiiiiiihhhhhi ilt4WE\fFB#zp jihhhhhhiiiiiiiiiiiiiiiiiiiihhhhi_C,$+#+#+#+#,#0'- \jhhhhiiiiiiiiiiihhhhi_C,$+#+#+#+#,#0'- \jhhhhiiiiiiiiiiihhhhi_C,$+#+#+#+#,#0'- \jhhhhiiiiiiiiiiihhhhf;$2 *$&('%(&%(&%(&'(',$xNkghhhiiiiiiiiiihhhgkxN,$'('%(&%(&%(&&(' *$;$2fhhhhiiiiiiiiiiihhhgkxN,$'('%(&%(&%(&&(' *$;$2fhhhhiiiiiiiiiiiiiihhhhhhi jp$zFmFbPR1tl iihhhhhiiiiiiiiiiiiiiiiiiiihhhhigxNiGlHkHkHiG \khhhhhiiiiiiiiiiihhhhigxNiGlHkHkHiG \khhhhhiiiiiiiiiiihhhhigxNiGlHkHkHiG \khhhhhiiiiiiiiiiihhhgj Y:$10&,2&-1&-2&-.',_Cghhhhhiiiiiiiiiihhhhhg_C.',2&-1&-2&-0&,:$1 Yjghhhiiiiiiiiiiihhhhhg_C.',2&-1&-2&-0&,:$1 Yjghhhiiiiiiiiiiiiiihhhhhi ilt4WN\fGB#zp jihhhhhhiiiiiiiiiiiiiiiiiiihhhhhhlllllljghhhhhiiiiiiiiiiihhhhhhlllllljghhhhhiiiiiiiiiiihhhhhhlllllljghhhhhiiiiiiiiiiihhhhhjeccccciihhhhhiiiiiiiiiihhhhhiicccccejhhhhhiiiiiiiiiiihhhhhiicccccejhhhhhiiiiiiiiiiiiihhhhhhi jp$zFmGbPR1tl iihhhhhiiiiiiiiiiiiiiiiiiihhhhhhgggggghhhhhhhiiiiiiiiiiihhhhhhgggggghhhhhhhiiiiiiiiiiihhhhhhgggggghhhhhhhiiiiiiiiiiihhhhhghiiiiihhhhhhhiiiiiiiiiihhhhhhhiiiiihghhhhhiiiiiiiiiiihhhhhhhiiiiihghhhhhiiiiiiiiiiiiihhhhhiilt4WO\fGB#zp jihhhhhhiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhiiiiiiiiiiihhhhhhhhhhhhhhhhhhhiiiiiiiiiiihhhhhhhhhhhhhhhhhhhiiiiiiiiiiihhhhhhhhhhhhhhhhhhhiiiiiiiiiihhhhhhhhhhhhhhhhhhhiiiiiiiiiiihhhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhi jp$zFmGbOR2tm iihhhhhiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhi ilt4WN\fFC$yp kihhhhhhiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhiiiiiiiiiiiiihhhhhhhhhhhhhhhhhiiiiiiiiiiiiihhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhhhhhhhhhhhhhiiiiiiiiiiihhhhhhi kp%zGmFb_U5tm jhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhiiiiiiiiiiiiiihhhhhhhhhhhhhhhiiiiiiiiiiiiiiihhhhhhhhhhhhhhhiiiiiiiiiiiihhhhhhh jmu7Z[n{4D'|q kihhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhi kq)}H.oLNz8vn jhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhi jnv;UzQT‚I+~r ljhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhii ls,NƉ]Rdo>!xo jihhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhh kp"yAghTuPK3tm jhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhh jmt5O$ym_gAC'|r ljhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhj lr)}GlAcVX|= wo khhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhi ko!x@^|['|K3tm jhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhh jmu4O*~cgCC'|r liihhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhii lr)}GmCeSX|= wo khhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhh kp!x@^|X${K2um jihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhi jnu4O(~]gAD(|r l ihhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhh i ls*}HnAcZal@#yp kjhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhj kp#zBcf\(~M4wo jihhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhi jo w7QC^‘ Qڻ/un jhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhi jnu1Tƚ a\jBE*}sm jihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhi jms+~Il@gHSJ@&{q l ihhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhh i lr&{BWJK<b;"yp kihhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhi kp#y?eH jJ4vp kihhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhi kow6OkiÑ Qٻ/un khhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhi jnu2TȖ jXmAI,tn jhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhh knu.Mn?cM]GG,~tm ihhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhi jmt-KbGP[[JC*}sm jihhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhi jms+~F`J_MRI@%{r l jhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhh j lr'{CWIR1c<#yq l jhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhh j lq#z@e> mR:"xq l iihhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhi i lq#y=Voj&xO9!xp l jhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhh i lq"y,vqm j iiihhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhii i jmqw-@S„&\W`.XKM6&{to l j iiihhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhii i j lot'{7O[K3^l@r YD1!wso l iiiihhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhiii i los!x2G[z DiPC\8K?+~vrn k jiiihhhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhiii j knrw,AN_8VOWXfjL<+~wqm k jiiiihhhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhiiii j knrw+~=OgfX]T?Ê]|L:*~vqm l iiiiihhhhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhiiii i lmqv+~;M`|Ŏ@Wu[ kŇU~F9*}vrn k jiiiihhhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhiiii j knrv*~;IX~Ƌ m^vfU?tTG:*~wrn k jjiiiihhhhhhhhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhhhiiiij j knrv+~4,~(|$zvtsrqponnllllllllllllllllllllnnopqrstv$z(|,5>AGZOjcƇ}O_ZmX`IV4aJyZIAB;60)})|&{#yvutsssssssssssssssstuv#y&{)|)}06[$t7:wK 8(|q kihhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhh kt.Ah >CN.>v*~r jihhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhi lv3GeA@%{M:1˵wliihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhin%{;Z,&|P=>(}p jihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhi jr1InGG=V%6µw lihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhim&{@s<Q*~EN,r jhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhi ju8T9-VS`@x&|nihhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhh ip2Ja(}_C >!x kihhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhim*~HoID9w jihhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhi k&|A kK@T*4ʵu khhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhh k"yBiR:O-+p ihhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhh jv;lKIh/ܱnihhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhh jv=Õ YUk9s ihhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhh i!xCoZmp< w jhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhh j%zEo"yC"x khhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhh k)}LvE6K^(| khhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhh l/WOMBU4/nihhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhio6e+RKr 4Ҳqihhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhit>ǟ f x@v ihhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhh i#yG6>KF)} khhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhh l/WEGKl4ֲphhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhq<ȂY"yAwihhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhi"yG3@M@*~ khhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhh l1^<O;ɳqhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhs?T>K]&{ jhhhiiiiiiiiihhhhhhhhiiiiiiihhhhhhhhiiiiiiihhhhhhhhiiiiiiihhhhhhhhiiiiiiihhhhhhhhiiiiiiihhhhhhhhiiiiiihhh j+~W\GX7вphhhhiiiiiiiihhhgggghhhiiiiihhhgggghhhiiiiihhhgggghhhiiiiihhhhiihhhhiiiiihhhhiihhhhiiiiihhhhiihhhhiiiiihhhhq>c<J`%{ ihhhiiiiiiiiihhhkkkkihhiiiiihhhkkkkihhiiiiihhhkkkkihhiiiiihhihddgihhiiiiihhigddhihhiiiiihhigddhihhiiiiiihhh i)}T`EU7ѲphhhiiiiiiiiiihhikHF 6H7X?eihiiiiihhikHF 6H7X?eihiiiiihhikHF 6H7X?eihiiiiihhhQ<-&*0%,<#2 ajhiiiiihj a<#20%,-&*Q<hhhiiiiihj a<#20%,-&*Q<hhhiiiiiihhhhq>a=K^%{ jhhhiiiiiiiiiihid+'))"!'$+!Wkgiiiiihid+'))"!'$+!Wkgiiiiihid+'))"!'$+!Wkgiiiiihic,'*"($&&&*"VlgiiiiiglV*"&&&"($,'*cihiiiiiglV*"&&&"($,'*cihiiiiiiihhh i)}T^Ef9̲phhhiiiiiiiiiiihid2%-&'&*%(#(%Xkgiiiiihid2%-&'&*%(#(%Xkgiiiiihid2%-&'&*%(#(%Xkgiiiiihid2%,%'%)%'#)%XkgiiiiigkX#)%)%'%'%2%,dihiiiiigkX#)%)%'%'%2%,dihiiiiiiihhhhq@IWK)} jhhhiiiiiiiiiiihid1%,$'%(%'!)$Wkgiiiiihid1%,$'%(%'!)$Wkgiiiiihid1%,$'%(%'!)$Wkgiiiiihid1%,$'%(%'!)$WkgiiiiigkW!)$(%'$'%1%,dihiiiiigkW!)$(%'$'%1%,dihiiiiiiiihhh j-\@LpAthhhhiiiihhhhhhhhic0%,$'%(%'!($Wkghhhhhhic0%,$'%(%'!($Wkghhhhhhic0%,$'%(%'!($Wkghhhhhhic0%,$'%(%'!($WkghhhhhgkW!($(%'$'%0%,cihhhhhhgkW!($(%'$'%0%,cihhhhhhhhihhhhuF#zQl1lhhhiiiihhhhhhhhhic1%,$'%(%'!($ Wkghhhhhhic1%,$'%(%'!($ Wkghhhhhhic1%,$'%(%'!($ Wkghhhhhhic1%,$'%(%'!($ Wkghhhhhgk W!($(%'$'%1%,cihhhhhhgk W!($(%'$'%1%,cihhhhhhhhhihhhm5rR:Go!xihhhiiiihhjjjjjjijb.%*$&%(%' ($ Ulhjjjjjijb.%*$&%(%' ($ Ulhjjjjjijb.%*$&%(%' ($ Ulhjjjjjijb.%*$&%(%' ($ Ulhjjjjjhl U ($(%'$&%.%*bjijjjjjhl U ($(%'$&%.%*bjijjjjjihhihhhh%zQkA+6qhhhiiiihhjYT/EY-FX-FX,FX-FX,FZ,GN,@&'&&&&'&&#&$C+9[,HW,FX-FX-FX-FX-FX-FX,FZ,GN,@&'&&&&'&&#&$C+9[,HW,FX-FX-FX-FX-FX-FX,FZ,GN,@&'&&&&'&&#&$C+9[,HW,FX-FX-FX-FX-FX-FX,FZ,GN,@&'&&&&'&&#&$C+9[,HW,FX-FX-FX-FX-FX-FW,F[,HC+9#&$'&&&&&&'&N,@Z,GX,FX-FX-FX-FX-FX-FW,F[,HC+9#&$'&&&&&&'&N,@Z,GX,FX-FX-FY-FV.Ed'K fihiihhhr?CT', khhhiiiihhjU/E-@54;83<73<73<73<73<73:6'''%%%%%%%$%1634=83<73<73<73<73<73<73<73<73:6'''%%%%%%%$%1634=83<73<73<73<73<73<73<73<73:6'''%%%%%%%$%1634=83<73<73<73<73<73<73<73<74;7('($%$%%%%%%2643=83<73<73<73<73<73<73<73=8264%%%%%%$%$('(4;73<73<73<73<73<73<73<73<73=8264%%%%%%$%$('(4;73<73<73<73<73<74;7/@6\jhiihhi k2d(P;Gl!xihhhiiiihhjY,G4;7;79:89:89:89:89:89:89///***+++---878:89:89:89:89:89:89:89:89:89:89///***+++---878:89:89:89:89:89:89:89:89:89:89///***+++---878:89:89:89:89:89:89:89:89:89:99333//////111989:89:89:89:89:89:89:89:89:89989111//////333:99:89:89:89:89:89:89:89:89:89989111//////333:99:89:89:89:8::89;797;9\jgiihhhh%zPhDb=rhhhiiiiihhjX-F3<7:89999<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<============<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<============<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<============<<<<<<<<<<<<999999:895<8\jgiiihhhsC lX‚2mihhiiiiihhjX-F3<7:89:::  :::999:895<8\jgiiihhim8͟^IWE(| iihhiiiiihhjX-F3<7;9:555555::::895<8\jgiiihhi j,^;N3HviihhiiiiihhjX-F3<7;9:555555::::895<8\jgiiihhii"xNv=g<qhihiiiiiihhjX-F3<7;9:555555::::895<8\jgiiihhihsCl]ư 6ްmiihiiiiiihhjX-F3<7;9:555555::::895<8\jgiiiihiin;cUh+. jihhiiiiiihhjX-F3<7;9:555*-25,/*-14*-  04.1 "$15/2  /303 555::::895<8\jgiiiihhi k2r$ZESH'|iihhiiiiiihhjX-F3<7;9:5558<5EKLKF6,//|EKNLHA/}&)8HKKF*px)ls;3 &(9;INNT@ILKG9*-AF;HNMH8%'IN;9&(555::::895<8\jgiiiihhii*~YBK@Lw!xhihhiiiiiihhjX-F3<7;9:566>SONMNNMLJP >PNJDHKNI),INMNLT>:TH 59PS'ho)ltPLOMMOVGL-xXJEHMOF 'hoSP59555::::895<8\jgiiiihhih#zRqE&{CuhihhiiiiiihhjX-F3<7;9:555"Z`H)ks?C37EJ7LJK%((ksPK3 ;ND =MK6;?26(jrPU6NC 25KN%ah=BOJI)nv8<48SY5 37,t|%',wKO.{%ahNK25555::::895<8\jgiiiihhihvG0 k?rhihiiiiiiihhjX-F3<7;9:5551MN.z>NC7OD >BML77OD 26LO%bi9MLHN AN@%biOL36555::::895<8\jgiiiihhihrEmR6phihiiiiiiihhjX-F3<7;9:555),JL> CO77OD ,v~NK7;7OD 26LO%bi GN97OD %biOL36555::::895<8\jgiiiiihihq;f˖ 7ްmiihiiiiiiihhjX-F3<7;9:555EMD  CO97OD 9OC7OD 26LO%bi9=LN&dk @NA%biOL36555::::895<8\jgiiiiihiim<Ҷ j1miihiiiiiiihhjX-F3<7;9:555 HLC :NG #7OD ?O<7OD 26LO%biQWNMAF*nvLO0%biOL36555::::895<8\jgiiiiihiim5xk1liihiiiiiiihhjX-F3<7;9:555"\bNL;TZPK?JO26?C@ME  CO87OD 26LO%bi#^eOK"Z`*,472525252648  6LMG  %biOL36555::::895<8\jgiiiiihhil5yk1liihiiiiiiihhjX-F3<7;9:555.1HIP$ah2POPOQLKE  DO77OD 26LO%bi%ahPHMOOOONLP47QVINNA+.%biOL36555::::895<8\jgiiiiihhil5xk1liihiiiiiiihhjX-F3<7;9:555HMIJMB SX:CCO<7OD 47LO%ahPVNMIN(jrOK,/HKI?D%biOL36555::::895<8\jgiiiiihhil5xk1liihiiiiiiihhjX-F3<7;9:555  ?OHO=  7OD 6OD7OD @DMN"Z`8-yIN25IM@JP%ck6NK5;?37)nvCH7NG#\b488<3KM<EJOL2 *qyKOJO'*LK?'* ),DI%ahNK25555::::895<8\jgiiiiihiim5wˋ7liihiiiiiiihhjX-F3<7;9:555QWNM5>CUNMNLP?  EONNLT97NJPMNMLN9=/|QNHFMQ9-xQMJDLJ'hoSP59555::::895<8\jgiiiiihiim;ј9²ohihiiiiiiihhjX-F3<7;9:555%ahNN%dk58>GLKH3 4GKKG.{7NHAJLJADI%dkELMH1'ipELNI:IN;9&(555::::895<8\jgiiiiihihp;0m>rhihiiiiiiihhjX-F3<7;9:555"Y_NN*ow .214  /3/27OD !#36(*,0/3 ,/03 555::::895<8\jgiiiiihihrCm!yEuhihhiiiiiihhjX-F3<7;9:55537LK?7OD 555::::895<8\jgiiiihhihuI'|?J{!xhihhiiiiiihhjX-F3<7;9:555=MK6(*GL+s{7OD .1 555::::895<8\jgiiiihhih"yOzDETN'|iihhiiiiiihhjX-F3<7;9:555,0JMMJDFMP,/7OD .zPF(+555::::895<8\jgiiiihhii)}YLJTf0/ jihhiiiiiihhjX-F3<7;9:555*-;LOQPLB267OD  IIQ'fm555::::895<8\jgiiiihhi j1l.YX̙ 6miihiiiiiihhjX-F3<7;9:555.1 V\$ag U[257OC .zPF(+555::::895<8\jgiiiihhim9ѧ ]X>qhihiiiiiihhjX-F3<7;9:5555RI.1 555::::895<8\jgiiiihihrB]1IvhihhiiiiihhjX-F3<7;9:55559MR 555::::895<8\jgiiihhih wM6HWL(| iihhiiiiihhjX-F3<7;9:555555::::895<8\jgiiihhi i*~[JLXx3 lihhiiiiihhjX-F3<7:89:::  :::999:895<8\jgiiihhi l6ˀ\>qhhhiiiiihhjX-F3<7:89999<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<============<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<============<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<============<<<<<<<<<<<<999999:895<8\jgiiihhhqB2;Ky!xhhhhiiiihhjY,G4;7;79:89:89:89:89:89:89///***+++---878:89:89:89:89:89:89:89:89:89:89///***+++---878:89:89:89:89:89:89:89:89:89:89///***+++---878:89:89:89:89:89:89:89:89:89:99333//////111989:89:89:89:89:89:89:89:89:89989111//////333:99:89:89:89:89:89:89:89:89:89989111//////333:99:89:89:89:8::89;797;9\jgiihhhh"yOw?L_5- jhhhiiiihhjU/E-@54;83<73<73<73<73<73:6'''%%%%%%%$%1634=83<73<73<73<73<73<73<73<73:6'''%%%%%%%$%1634=83<73<73<73<73<73<73<73<73:6'''%%%%%%%$%1634=83<73<73<73<73<73<73<73<74;7('($%$%%%%%%2643=83<73<73<73<73<73<73<73=8264%%%%%%$%$('(4;73<73<73<73<73<73<73<73<73=8264%%%%%%$%$('(4;73<73<73<73<73<74;7/@6\jhiihhh j/c4QV:вohhhiiiihhjYT/EY-FX-FX,FX-FX,FZ,GN,@&'&&&&'&&#&$C+9[,HW,FX-FX-FX-FX-FX-FX,FZ,GN,@&'&&&&'&&#&$C+9[,HW,FX-FX-FX-FX-FX-FX,FZ,GN,@&'&&&&'&&#&$C+9[,HW,FX-FX-FX-FX-FX-FX,FZ,GN,@&'&&&&'&&#&$C+9[,HW,FX-FX-FX-FX-FX-FW,F[,HC+9#&$'&&&&&&'&N,@Z,GX,FX-FX-FX-FX-FX-FW,F[,HC+9#&$'&&&&&&'&N,@Z,GX,FX-FX-FY-FV.Ed'K fihiihhho>]7J xihhhiiiihhjjjjjjijb.%*$&%(%' ($ Ulhjjjjjijb.%*$&%(%' ($ Ulhjjjjjijb.%*$&%(%' ($ Ulhjjjjjijb.%*$&%(%' ($ Ulhjjjjjhl U ($(%'$&%.%*bjijjjjjhl U ($(%'$&%.%*bjijjjjjihhihhhi!yN:Nd*1 khhhiiiihhhhhhhhhic1%,$'%(%'!($ Wkghhhhhhic1%,$'%(%'!($ Wkghhhhhhic1%,$'%(%'!($ Wkghhhhhhic1%,$'%(%'!($ Wkghhhhhgk W!($(%'$'%1%,cihhhhhhgk W!($(%'$'%1%,cihhhhhhhhhihhh k3h)RPAshhhhiiiihhhhhhhhic0%,$'%(%'!($Wkghhhhhhic0%,$'%(%'!($Wkghhhhhhic0%,$'%(%'!($Wkghhhhhhic0%,$'%(%'!($WkghhhhhgkW!($(%'$'%0%,cihhhhhhgkW!($(%'$'%0%,cihhhhhhhhihhhhsDYGTW(} ihhhiiiiiiiiiiihid1%,$'%(%'!)$Wkgiiiiihid1%,$'%(%'!)$Wkgiiiiihid1%,$'%(%'!)$Wkgiiiiihid1%,$'%(%'!)$WkgiiiiigkW!)$(%'$'%1%,dihiiiiigkW!)$(%'$'%1%,dihiiiiiiiihhh i*~WUJZЦ :ܱnhhhiiiiiiiiiiihid2%-&'&*%(#(%Xkgiiiiihid2%-&'&*%(#(%Xkgiiiiihid2%-&'&*%(#(%Xkgiiiiihid2%,%'%)%'#)%XkgiiiiigkX#)%)%'%'%2%,dihiiiiigkX#)%)%'%'%2%,dihiiiiiiihhhho=ճ ^;M|$zihhhiiiiiiiiiihid+'))"!'$+!Wkgiiiiihid+'))"!'$+!Wkgiiiiihid+'))"!'$+!Wkgiiiiihic,'*"($&&&*"VlgiiiiiglV*"&&&"($,'*cihiiiiiglV*"&&&"($,'*cihiiiiiiihhhh%{R|>[z9nhhhiiiiiiiiiihhikHF 6H7X?eihiiiiihhikHF 6H7X?eihiiiiihhikHF 6H7X?eihiiiiihhhQ<-&*0%,<#2 ajhiiiiihj a<#20%,-&*Q<hhhiiiiihj a<#20%,-&*Q<hhhiiiiiiihhhn<̂_=M~$zihhhiiiiiiiiihhhkkkkihhiiiiihhhkkkkihhiiiiihhhkkkkihhiiiiihhihddgihhiiiiihhigddhihhiiiiihhigddhihhiiiiiihhhh%{R~@Z{9nhhhhiiiiiiiihhhgggghhhiiiiihhhgggghhhiiiiihhhgggghhhiiiiihhhhiihhhhiiiiihhhhiihhhhiiiiihhhhiihhhhiiiiihhhhn<ˁ^?O|%{ihhhiiiiiiiiihhhhhhhhiiiiiiihhhhhhhhiiiiiiihhhhhhhhiiiiiiihhhhhhhhiiiiiiihhhhhhhhiiiiiiihhhhhhhhiiiiiihhhi'|S{Ba̟ ;Ӳphhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhp=Ӭ cIU[,~ jhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhh j-YZL`CuhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhvGiQd%7nhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhn9k%UCOk*} jhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhh j+~TkEeCuhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhvG[Xn9phhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhp;s[M[M1lhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhl3^KPCPw*} jhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhh j+~SvDtG"y ihhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhh i#yJsaɘ CõvihhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhiwF˚ cRt>ʴtihhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhit@{WXl(;qihhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhir>p&[IZ/8qihhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhir;`/MM[/=t jhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhh iu@a/QHu?Ƶw ihhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhh i wAzKLsE÷$z jhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhh j$zHzPY"H,lihhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhm-J.\V[Pn4pihhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhiiq5Rm^Z_4]<=޶w kihhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhh j w?`;7bI{F,oiihhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhiio-H|LOTU]9w khhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhh k w;X[WQTClH/q ihhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhh ir1JpFVDfX>A̹+~q ihhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhh iq+~C[=mELFбV\<ո(}q jhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiiiiiiiiiihhhhhhhhhhh iq)}>X\кIOB3ґP\=Ը*~q jhhhhhhhhhhhhhiiiiiiiiiiiiiiiiiiiihhhhhhhhhhhhh jq+~?QZ֔6ElE6ϩW\@Ǻ.umihhhhhhhhhhhhhhhhhiiiiiihhhhhhhhhhhhhhhhhimv.BY\ѯ7Go_A/^:F7(|tlihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhihhhilt(}8G_90B_lE!xyS\C6(}uo kihhiiiiiiiiiiiiiiiiiiiiiihhi kpv)}7DU\z!xFnM3wWUH=ϻ2(|vqm kihhhhhhhhhhhhhhhhi knrv(}3=HXTy4N\F#yo!WXJA:2,%{!xtrrrrrrrrrru!x%{,2:BKXXq!#yH]WL+~kӨ s$X>VfG|IC=>>>>>>=CHG|VeY>t$թ  j,MXTK4 k k5LU_YKG?)}nqppppppqm)}@HLZ`???????????(@ @5rNgG-:B6F7F7F6F=BL,wT!w>+oB/-k%{wϳtrppqqqqsv"yͶ(}2dK(q5,oE'-wױqm jhggggggggggh jnr"y͹0{P$!s68#x>>%{r lhgghhhiiiiiiiihhhgghmt*}H7+~A-mB3&|q jgghhiiiiiiiiiiiiiiiihhgg js+~L-v4-a +r jghhhiiiiiiiiiiiiiiiiiiiihhhf ju3ʁ 4>$v>"2 bjihl~Q-&*pJlhiifH7>"2 bjihlwN(#hFlhij a1$,=!1eiihlhF(#wNlhiiif!xgHw4lgiii h hi c*&)(# ]j h glhG,W?k g hi c*&)(# ]j h gliG,Y?k g hj ]"($-%* ci h gkY?,iGl g h iigm9%zHը "yܭghihikklf1$,%'&amjjpqJ+`Bojklf1$,%'&amjjpqJ+`Bojjma%'&1$,flkjo`B+qJpjkihhg%{L2=Opfiii dWVWyQ)$'"$#qLWVUYQ<&F 6XUVWyQ)$'"$#qLWVUYQ<&F 6XUVWqL"$#)#'yQWVUXF 6&Q<XVV`iifqEN8 j, kgi hkV*@35=86A:5?:.//---4>96A:6A:6A:6A;285,*+1636A;6A:6A:6A:5?:.//---4>96A:6A:6A:5A;396.-.2745A;6A:6A:5A:5>9///0105?:5A:6A:6A:5A;274.-.3966A;5=8*A2c(Jk hg k1nֹ "y߭ghi hkW5=95/3$"#%#$$$$$$$%#$%#$%#$%#$%#$%$$$$$%$$%#$%#$%#$%#$%#$$$$$$$%#$%#$%#$%#$%#$%$$$$$%$$%#$%#$%#$%#$%#$$$$$$$%#$%#$%#$%#$%#$%$$$$$%$%$"#5035=8f&Kk hhg$zM5sgii hkV6A:5A:d%Jk hiftT19gngii hkV6A:5A:d%Jk higo>d0 khii hkV6B; $_f3.z/3%(.z3'ho !# $_f5 V\ JO36<@?D216:(ks4$`g NS6A;d%Jk hig l3(} ihii hkV7C==BH2>PLR !#K:)ltQ)ks7L64RXO6@"\bQ8:+rz149)muK38 *-(+KPRBGI2 26LQL-003N&el>:6? #&PKP 69Q6BBSYO  TZS !6C'&'$%$D0'&'$%$D0L2BL2AK2AM2C3+/ #!7,3N2CL0AF5>wRk hgm9%z@S1tfiihihhib/$*$'% ]jhgljG+Z?kghib/$*$'% ]jhgljG+Z?kghj \$'%.$*bihgkZ?+jGlghihifuX0C!+~ihiiiiij d1$,&&& _kihmmI*]Alhij d1$,&&& _kihmnI*]Alhik _'&'2#, djihl]A*nIm hiiihi-#5C[qfiiiiijd)&()" _ki hmjH -Y?mhijd)&()" _ki hmiG.W>mhik ^*!''' djihmW>.iGm hiiifqFZ8Q(}ҭhhiiiiihT~Qgiiij `uM ]jiiihT~Qgiiij \bDXjhiiemItLgiihjXbD \jiiihh*~V4C_qfiiiiihllhiiihjmjhiiihllhiiihjnkhiihhmlhiiihknjhiiifqF_6L+~Įihiiiii h hiiiiii hiiiiii h hiiiiii hhiiiii h hiiiiih hiiiihi-P;O<ufhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiifvT;?!x9nfiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiifn;"yZ-î jgiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiig j/^F` &{hhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihh'|d H7O= xghhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhg!xS<:2FOvghhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhgwIN43GK!xhghiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihgh"yJK48O9&{ jfhhiiiiiiiiiiiiiiiiiiiiiiiiiiiiiihhf j'|R8:8c.ofhhiiiiiiiiiiiiiiiiiiiiiiiiiiiihhfo0g;=u;~w ifhhiiiiiiiiiiiiiiiiiiiiiiiihhf iw<~q@=*}R..rifhhiiiiiiiiiiiiiiiiiiiihhfis/T.+~?O2FM*~ʲr jfghhiiiiiiiiiiiiiihhgf js+~HM4Q8-II-vo igfgghhhhhhhhggfg iow.KI,x:4pa ϐl!w'{qR(}ysܰm ihggghhhhgggh jnuٸ-rb!r,(|u*~sr kgghiiiiiiiiiiiihgg lt0mӝ.}8u94uѯ kggiiiiiiiiiiiiiiiiiiggl x̾D/$yE=e+lpggiiiiiiiiiiiiiiiiiiiiiiggr4elIEZ(|mfhiiiiiiiiiiiiiiiiiiiiiiiihfn-bOL"y kgiiiiiiiiiiiiiiiiiiiiiiiiiiiifl*~^ h)~ lgiiiiiiiiiiiiiiiiiiiiiiiiiiiiiifm0m"y2bnfiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii ifo9\&{4G(rfiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiftP';"yhhiii hiiii h hiiii h hiiii hiiii h hiiii hiihh&{)(}5Zmgiiiimjiihkmiiiimkhihjmiiihllhiiimjh ign:X,Fvϭgiiij ctM ajhjY{PhiihzO Zj hk ^gF bjijvM|Pjhj bgF ^kiig!xO*~6XmgiihlW,}Qm gmQm gmxN-Ylhj/%+="1l hlY-xNm h ign;V.<"yhihiim X)RmgnW=*$'fjje'%&\@ngn}P( Zlhj8!.E5lhl Z( }Pniihh$z@J$pg ikVN1B[0IK/?$)&G.=[1IW1GZ1I9,4***U0FX1HY1HT0E))):,5Z1IW1G[1IF.<%*'L/@Z1IX1HX0H/+.3,0Y1HW1HZ1IL/@%*'F.<[/IM0AYkfrU$0g lh hlm$O)>2&,($,(''(%*'#+&#+&#,'&*('(($,'#+&#+&$,('((%('#,&#,'#+&%*'('($*'#+&#,($-)')(&('#,'$-)#-($*'('($*').++@4t Qlgl5d$zih hks#R)3.  2<7xSlhi'|uԭgi hkr$R(4-$'9=2614  8<    :? <@    1=6wRl hgw^rgi hks%S%+#+0>9D-049@/|59D=2559:&fn-y;?E<,t|'fm7B !&): 1=6wSl igsfB4pgi hkr$R'3, +s{5F"[b5:$`gDH=)nu0=OU3(iq  ? 1=6wSl igpH38Engi hkr#Q(4-(ks9DQV02@.2BG<)lt.zBQW6:/237F,/&)= 1=6wSl igo=E8Fngi hkr#Q(4-PVM=A25/}>0~@-1CG<)lt/{B61H8BD6,026H1G/3QW>71#]c@,t} -0? 1=6wSl igo@BJ,pgi hks%S%+#&+G  SX.1'* U[ ;@@GL*-37JO;@JP  1=6wRl igqN+ssgi hkr$R(5.3@8GLEJ</3,w 1=6wSl igt|wǭhi hkr#Q(4-8<DI:>5(*$em 1=6wSl hh!x)} jh hkr#Q,50                        3<7xSlh j+~9Rmg ill#N(A32<6094//01:60=60<50=6/41///0<50<50<50<5.../410<5/920=51840/00:40<50<50<51211320=50<50=50:41011;63=7(A3t Qlfn'''hmmg$'%\@pjpQ* \okl8"/E5oko \*Qpkjh i*~h7F:pfi i gkY#(%Tk fl]A2$-f h i e0%+aCl fl}Q)"Zk gi:"0F6k gkZ)"}Ql g ifqJ:9E'|hhiiig bgiiie chiih ceiiie ^giii ` aiiig ^eiihh)}C6I6qf iiiijiiiiijiiiijiiiijkiiiikjiiiikji ifqL69 l. kgiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiig k0 lK֕ "xѭgiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiig#yڝ O8K0tfiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiifuN/:*}>NqfiiiiiiiiiiiiiiiiiiiiiiiiiiiiiifrAN+~!w9[rfhiiiiiiiiiiiiiiiiiiiiiiiiiihfr;["x({?JuggiiiiiiiiiiiiiiiiiiiiiiiigguAJ*|-~M*#yЯ kfhiiiiiiiiiiiiiiiiiiiihf k#zO*/2֘/rifhiiiiiiiiiiiiiiiihfir1؝43sL+(}q jgghiiiiiiiiiihgg jr)}N+t5C%yOL-+uo khggggggggh kou,N-M&yD5l @B-$zu۳rqqqqru۶$z-ABу l79qۭdJ.G1G1J.dܮq;???( @ !yeD%{Mvtsst x)}JMf%{s:*~=rm k iiiii i knt095 t-p1)r khhhiiiiiihhh lt9&s=f"yimhhiiiiiiiiiiiihhn(}d hFYv kgiiiiiiiiiiiiiiiig k"yaew ihiiiiiiiiiiiiiiiiiig j#zfs'|a khiiiiiiiiiiiiiiiiiii ig l,^u+?"ogiiiiijiiijiiijiijjiijjgpF!0\uh iigg i igh ihgh ige i iff iieg ihw[9.mgjplHdDolQ:TodD4bpVG6ko`BiFokG6 Wogn>-wii aeV?N ;fc;$2lKg[/&+XgtN7%/bfN ;U?fb7%/uOc ei!xpӬgl^*H(7.)(()**,1.,1.'''*,+,0-+/.'&&+.,,0.*-+())+0.,0.()))**+.,+0.())+,,.;3Zjp90mgk`,K $-(]jm=.)}] khl_'E'",t~0~ 9=0%ah*mu&el),;?+.6:3!W]+r{!W]  HO $_j k,[!x~ jhk`*I !V\&fm-wAE+s{/2/}@E$^dDINS$^eNS 4BF*mv`k i#z}w ihka,K 69='*.{,u}=B&ck;@!V]>BLR(iq,t}.y )mu=A$'(ir _k i!x w ihk`*I 3NSBG.y 49=)mt)mu;?SX!X^,u}:>515,s}`k i"y#yx jhl]"A:75  +sz&)IN U\/2:!W] &el<@QWHM,1"("^k i%zx,S khka,K #`i1 #9=)lt$'GN!$_j k.SC&ngk^-J       **      ".&\jmG%rȬgkh%LA7<8.54+/F3=E2=1,/;/6F3>B1:/,.?-6D,7=081,/D2,V:A.S9G3L5N8F0P ;gvcn k5&1/, $"/0 **  &$ )(& aslȳ n7'BC$V^)jr PW(+QW%_g"U]49 X`&DG [rǯnƱg@0CRU DI/3>C&`i+/NT>D-1 89 ]rưo k5%0 73 ! )&.'`st[ j~SD0lJM";jKO9cDW"B] C_ EV>_C hv[k mԴk bk aj bh c ee dfoԴx vM jj ij ijijjji kuMgsp jhhiiiiih jspg ivHm׮ jiiii jm״vH ipH uUpnnpuUJp??sachesi-2.0.4+ds/assets/sachesi.png000066400000000000000000001216011362064257300171760ustar00rootroot00000000000000PNG  IHDRxbKGD pHYs  tIME  / ^ IDATxy\Usνz {®# !((."(μΌ"($2@B6mADIwު.<ǽU]ݩ|Oܮ:<`aaaaaaaaaaaaaaaa.;_} (W'0eiV\Xd @w B,+E{&ʙv)} 3g7z+Yyۮ-. AV @0 l"_@>}!@!!RBJ"P6E{&̃]]~ō_ʯ} 2Lx_wV\][w 2!I4$A  $$!i"0GB"B ;" ZH! R !%(2 HHR R@R TN@^(:Hy\To#rqR0 !+y]s;%B&D IDdBg@@BTQA !)L.BKHAB )B"!aĠJZYB&zޛ^;! S`0[kUx@hȐBC"ٓ6$aGC@H0' %p?h D$L&ٻp!B `  D- :~F J %J1ccuiWrAaZ S+y쎝/Y t!@Cv "4aH?t>?KBң~޹Sso*" DǂG 0 )@TQt@ TJ(+/ 2(`a0Exۏ-',wHȐ0[yg!GÝ?"!z/GQl?" H/HJ!Zr4jJ ԑ(F*Ac{Om,f,s?'U r5po4@.A! }9sYD'@qKqP=xqE"!$B ȥ ń 0t2 -ʒBZ-(j%RqmD`:F0 IQx_G|H2$0lГQ?tXs^HQ_j.ʲ&2xaPj/@)JB(siK$*K@g'sDX0~G[ @MVWh/azi[[.й#&9}gW>V O' 3S0Qʠ&b 0,AʖFZB*[y’ψv0L=`/Y=2U ?K&F{P&((nr{tEwKa@b ?B-ÈTd([c'8i{ S+X0-KVH L& eFF2.ߊv$?Ξ}s0N!JH%0he li9)A3D\zMgj`$/Y=2E*?Kh2&@9}0 &aAEzJvoAƥaC"ځ(ʖRT0`ZL˰ջ*ϒ4b PqL_4gN?.j|f}0:|}@(ADY,GpӮ8q3LSr9kwfvc|2GC/a^@d=>Z9Z7'b5Ā)r$Z$ˑd%Ja`~'LgIA(vA_MvKs8|vME`IKEHRڎ T(ւ{OO}_DL@BM j~U~~>;Π,Q0(&02:HQD,GjHi;#NHHLܰ`bo?v$gN_hQ_ MtW(C(Æ,(K(55ʑv r$?O|;h˟s@!T`'$)GTd% 8ٽO<^2 JLŬ#}(>BDau4`~9vg@YH VBzVBFS-R1qOCkdT}tŎ{v҅Pˑd%Q`jO-3ᕋ)_ =k~.tTaVX7'-)L(HVRT4m_n[łLJƔW;6J \T3rI&u;poMZ}U`s@YcH ;CP,~}L+[^~L}/^ݱQRࡥݼ;~n~{RHjJHt ;)(\8U5|kW\W:(/Y=EIhhQFU-w&->@r)Gh%";)YXrq41cx]&52p> @Ku-2Kӿ?bAGR4GXIG.\͇1*u3*pr& 0>>4gϔBSgKZ;)I*${$}gm]kd'w |Ph^&jkvN}O5 m9öAi"'~,A߽hbE}L} ^|Pam#qX }>52Ļh !_ S ^s?/|&sS!qwXP`ei5*&u׮^)/g_ц ١|ֺ!oe&VYXxP@S^Π)"a4@:|ˑ:N)8-nê{_#׿km:3p Ú#-G{J=6l;f١1C3&FM`qwoXP@١@ ^5#mgmgugMOI|r ȘCzli& &.Te@9RI"A;dWcQ2Vchs G\']}n$k #Fhm}3@|Ft  JPv4Q0!U$I)pR%6W6[jAe]#v4L27Դy~vL+ҔB WF' *'2HvRNJR`$ziri̷)>w kkD࢈Qz~&pv)Di+NHi%o%R=Rg'=djmW?U;2=# HƂ~v (Q D+)stR }G ^mۈ>v`dYAh5"xDE~N/ OTǎaGO |4@VB ;)};0ѥN)gܽ' 7gݠ52g !d~v 8M)$ˑTJJJ8]q^@ շY~ꑾ &FY#R{ J+kbĎaFQ=B vRRRtY> 1Xr➸S9 07n6*.B!isSg!n!0H0,' HJ ӥtWbo ^[~'?FFahQL`0KS|m#N*9] ] }.?S*Qd Yx`O33LYSh%$ K? [ ^[wn/cT5v4?~5A?ԎX@B`'d`%)I" ne.ʼn3_Li*S.u*IJV :Bo:Ϲ~aʥaTu)"2nJbU0x>v`G-cl~v 4d )RlFRI픤dN4^mYKg \ky0Lid4 ?>xNKa9ҾnHxo .]ZDZ߉g44=ZXN.E͑/r/ ֥kvB/6d F~2_i' ZD+)MKIۂdEJyYqߗN=@̵:n k(p+b?C gfyI{*?z0kGkv ô2u[&tnK'?m,FD9'V#3 hUP0+ W( D='qw@ȸ/U5};v tX{k$A6@`&g yimyi o'}qߋV@C=v tqwD;u 2u隭}s~Q{&9!;azG%A?-78wָE ]o5N5V5F{&h0 l4Zh^?-/>R]eDe}%aI3 4R)?Oko_gq߇V@<2N?cx_.Οa1" "v.?c16W:}Z_EjkמIha?;aQ-P$NT(vn:ɞʯ_Jyv=tLvwDK?c1v} '`tiu& 9:,HutfJZ=ӢbW73[!x~kld0 3\rk,agD =I ^Z;u:np5p1g4Yck/,>1z>Οaf""tG<?6 mCc뼸oVX֥k}dܴ? Xa8Տ?0\Xk Z#uoFX֥kv Qe3FDΟa*% y AAbx;wnW7, XvѪ~~H?cD5F ΟaB!3 Ee {}f@CW=}%_K?kPiE^v 0QW0:(Hh1駵[/`ŵ+of@'gFYC'd0 S{) 5N4(Hzi#_x97nۛpûY#QѠ=&S @a:YuV~ț|Sn>saY#-a~j=?0(XM Dc"pgpFCOhU0&I8G{v 0"`̠@{h.IwlvI z}%Y# <ڏgZ=f32H+]ySvE Og xa?hug0 355E.ZA@4^xD6EG ޽v$g5FΟatj/r"p^FK?c3882\wg0 S>5[;ǵFУ঵F4vbQ`G ee)㣪W;aʩv (P e2}H@zEH,c0 S=1्&͍c'VP']0 4% /6yq_opûY03B(asQ,( ~z?nA [d \#tQs0LPKP(\#Hsz࡫HYѰmaZZvQ(~8hZDyB]4aZCϢp>97|&F IDAT3h7L@hy;aQ0(PCg "Y#|}OlKϵU۞g \ޞ'giMj~ )H%@#I)0kAjs \rq[k/ovЮA}T¼?;aEQ:ND @D@h Q% v[l GUo:cm&pQxaџ?0LԦ; L@{~~?c>DfnI_V^lE2Q_x#590 Ӯ: -#N)V!5͢G.nqW֊Jo* 2>3 ô'J F!&>Z$--d [\Ow".*!] a0 |dms^Rg Dw[ʧ7[vѪlA_xi5Jp_5*?0LsSP̥L[AצT@*n[-RBB{( Ca:j "?@T*[@vF K!#0 tSFSBK eJ#(|@x&@Mz?0LP͚]8*"@`|C \#w؈.[jh@ :(Lgp 0ꩀX aW?;a֥5|Y0 e-)]jAGDY?;a֧ҵ|Tb5}iZ~hRh e|j]0 t6H2b[FO Uoo~f嶹7vgf8I1xFh?˻az $h'It[Q~j={Yg~vqYMCW>F黆׀QaɨU*T@J{F.BrGPv qY-%֯I'ѩe蟝?0LRRBshgް&q^6Ce \=#~g0 TE"@hϠvQa=5CqX*-#_FF}L!h_? 0 S&4[)}"pejk[&1~7:I,,Wfra3S9A2p\~'> ࡫FxQߘa|MkAלּ6n'Z:qtX {&>k纓lmW{+94ǍFYFmGkciSLIJV o@x#Fyi-صL?TloLizIv VBAۢ4tMi@K[}+FӦvlT=#?οډ(V#m_{㶳IvL@Qm(vlTu hJUYxG(M[e3Hk=_4mR{h5}m_1R>Za?l- 5a&. ]ceS:.\kX߀֨Yv N4uuhoũ\[ IuߥkxNoTh|CeQik^Dy] RR3* (1OS e ›y0 486 x((5ATs w0O5Vdk'[E(L I{F4M h/L*(vWՊkoJ6wmfox((l(@D7*2~  Ň۾ZG:&͝dk';(QiZSDݿ8}* h VKa\2ޏ`Qvlb.\5CiN3 0F쎂Q$uࡺ5q\Z,`&EQaf(@IZ&@JعIŲmxG> 'tx0 ôS<#}D <#׮їp7ɓ_MN~gaupG&m- gI> 2J1 PaaL4@hDp@*j'/ 64p5&uhRB$gaJv@T& ĉ;]b Hmd& P&'1 0L[M%\G m @0Е1 ԄFGy*v 0 F#P$!vM I.*WױcaVѐ2B46=8Q64LY __8[aa\K&LCg8Lp5WLIK"0%PD]37;ZCڕ7"a4& h5;j@h쿕Za) zgjImbo'ډ K M hŦ'u\:ɉ&5hKBb(ze{N҄r*(̝gn~.O|i4{KCcE~^Î&^t[; *kg F>J㣹5U |{x[T?-28ͺ߱_S ^hMzi`Llogi# @d l ܽUMןU벥Wdp \`tިIw_ X45 B= +})%H)a@@3QloI$qt HFJ(GR&cttC'Z?={|׫2;8~X5Ѵ#,Pw0?lַd |߇"%Bq@ee{NMeyϜ]Vy E-N)trE]mߣ,~ uؽEY5 8h_woEE!e埣{{m˲ g+sb{vlmf{+9GdS}mJo;?އ\]M@b` pIjʇBF^LMeS#;y&8vl/vbd^ Tlo{ 0}K"ӧ}l+[&Khi럜uk 0,4DhHN7UHA!Ny%?&Hs`{^Plo)TH ƥ ԄB&LDXO@p/sLutڇeS!ck,drZNMR|ԤQl4D/?0 LFQ_9*Ȅ&@}ߥkҵx͚[Fh2 IPο.3 0XƂ9D2$c ޽Ud']$&I\EF0 0qS<\|xjF9ǧjy4?(M+f0 0LP7ml0ŠkVVkV-< 5(RD08I`aL/ FDMfΒj_.kK7iA$B4&k?ƍ0LBٳaM7JmsQvp>ev@meW\[)+0hHpwEDO?4g#D}}}Oz;G|;`dbQ2o 2&g!N6nL&`2 lܸ1˨ EL$a@Fgfռ^U`ScLX/ɐ Cr&5a:v]{G-@C2 (FjdWCA@ c-%iN9 0F<\r jgK% sF#!"C*`oa(RPZo*NDga)2jT,0 ԛbh!a4?;*h6(/1YRa( <9」!paZn  z=b^8tA/彏ab &x\x[<dC0NBb}ĴC#?gTܗ0 L%Ьfm]qʧ}οr94 ޾sm Ӥ< ]GlPQ VL_Bga B `؆o^VA%O;䢏!EH@ !YTPWF avR_7&@$@ cW/6e ƛ~KB"pGaEwn>y͆5(lo~͍b*(ѐ)|Ir)_}ɉz%0&y~afj~`'s,{+eAWʂ{% ࿖,~/36&ÚJ-vd?@Aa8o|&Q/-={^ܗ  4$wmV:ew lhB'_XBOǼfy߾)gvRI])x>B:};\ظi^8Lzzl6acAp/y…Gտ飗tuWP 4 l2_|=@ +5!%N܅Md S==2W{Rp Uto s-Җ4}Yzzwܦ1 |dxp·o|ٶr"ʼn uBH&ǓL(]7E 0MHa! !(*Z(225S?8A27_dܷa6d{<jS=Z!CYޭ,  ho!c9{H&TOV!et ԜqgC T ' `Es7,r,Tr#+6Cyip2{a0<~8[0L1p6񆤀Buiy$e4 P@󽇘F0Ϯ^st^ۜ|kufLXN-dy@iwԎn7ۮpƚH_v0 MpuRLY +{~tߦ/ M4А,] AZF\vU}Ywy= 0030cLw G>,kOӻYp0LX~Kj]}C7ia@{~ƇMF D ;)YwJ@? gTc7l;y| ~{axlv{g{םV?Ma+۲#⾔%ڄ; һTxψ|"X*+)@3_Xhw߷Ȕ= `nF=_1׆_?jܷacj$p3yNw L#KD`ȟHKO[kV&WW`0 0S@ t@ Ƈ[Kz°Z?&HVqjsd~w 0-30w@POQ\GTW?n q_60 @s%  "D@DD9_]cnc=tafOXqJyhI`f\!ֆc٫q0<q_60 Lt(!Ky@x1sj3 0cɵhKH%u$"]X>X^%0 0kIQEճ/a1՜ 97dtJmJ.i$)7GF3S֙ 08}ua 8pȼX0G;z{lReA*2 ]r ϻٿ}1n8ؽbwN, ? h=_g2@xe[6k`Aڣg zŁEӧېJ*H%>B:a[ [ %O\pp7,:~6~4p~/wz-pl ٬tFC64_'o'C{c9Ct@' 6$۶g?fjg2f:Xq#=zmo.t>UG^H GO%J=\Ճ١@F3`"4)S "w`tr9)'|{j;t[@:|a/qSf$NK /wtud|#i |.ůmJO;=p/B| F~t&v| Dp?]|ݢK'pBגERHp>BHȻ(Q㫡") -#N)HXAjtϴO}(ңj[qIbVkh8Cju-(@XO\Fvo m-l6z]T3+ ]&jk*);x⒣DW~߶fkQπ{o?Q|`G`ׂ7i{ç(Ufoh(}[;~ 7<9 ktvwzؔ`fJ3L][8-K?=!jQ) _S=u '[ZUJ­'̎'EWQLu:6pN]R 2׋Zw{)(*ݛsz K"+ZPncnirɧ{ }TR8( d5w6ԾZ\vّxf5_;aۍy. nbڤskf71+^ 8ЬDÀq- 3O  !@eղ̾́2;7 A:勯 5.y853ZOarQhc^3> s3;_쨚ݭ}px/N7Q&uM赢bbO!@8p˿N_˺s]) >yq_v H#q_rc> x m =ؖu^7ͪs^tab}SKQ0iHMSwM]D UIX+?⧿LCK=O= cmx3Է̅GVmk\~ Sko՝dBE#g:аA@/mISO0D6 +fa`ЇLրH>͆cKvʾ% wϻ޾|ͮyu]P{kn+Hw k3ge/Wx8M^{ 8YMJeh?G~7qMb#i #i ns ~|#Mfû߱tG@0ek)zta #}e` ¦iU۠C iXثtMOP| e#etq oY4?w߷࢜[^^#'a} xJNA_]nSN_Qb>:qr׆q'w] ^GvM鴁/}Ϥ 󀒞Oݴ}gE |Kue৿Lox=DӔSS᧔P$ـmL¸?/o[Q=Hg , z_chXõ7=G_+Fz;x'+`۹˃s?ʚZoi5ҙ笡V\S2󟈕kO>A_*q~~ut@3vͳ%9Bgh+.:~6T; YVw/5ԖRSQoW/ w5fp(bsُJYpuojIyd67.K~xr]p%g+1yk^^Sn4;vpHCåd~-rOtޓJI8):#$v{7UzLKQȏXn`vquG(Ï ~Wt@7;>_hKs>w e T^:e*lKV򮷐?{&Gq4ꞙݽ(ݝ$P €&I6A`rpp|& 0Ezm0$ IvgcfN{\Gܱkv`}{0g־#S6x,TLHo5f] |9=Ʉ_߰'q$Bm;l BY=ȒH*{i4 ݲ&7ᔭ- i7CmB<޿~gK Uar lɤo5?x,74b9'4{B'ڷ Q5)v 1-e%[;D$NMDv} KҜcmE2؞$t Հ6DpYk!pD7N;TǟOkkfA1KwovѰn K ^.?㽀JIGgpjXSa'2 e?y]?n9i/KYC&4o'M9N-0vt"&dY'tu/[3"bT<Tx|'%v¤Hee*6*:YɒOm-1g{hkQ!VlS 8np]e!NafN>mIq睹-0@E;p)O4`8|XW i&q&k6 .n^SO 7\+qPw@w~5틶O[Onx< qrZ.-vM8ybl12Ú Y4tt8iַ`GÏ;ÏhBo쥢)@N2|i1|ᐱ0qpH~ڛRxP3N*So]=Fu)%ab.%#J宿| ~e#x~pySĖ31Im~Kj?ꄳ.zQsVe4'㾲%u6X{'JRJh5'9Qs W0ïP} *EkC}M۾.>g؉TE})LKฯ[_{'\Q#{^1Plޡy|Y?Du'6+Lx|N$8(;?#O싹$\]qJqICjh꺷ύX,x ۘ 4>('5>L?WKoV6(&[8l=Niݦ9NT˅0,`cVdsuߵƧK ^ԟ?p4\|vWG/r5wlT,k֥O}'nN,~q]bmH8ǟvs/ן>hfW ̘feԢW#I7`R^r??J_xxaC?Ʉ}g'<Z40(Ӿ4+_A)SmI>o k~s޲컑m(6_ilpWO;9^Q_ o<0EEwP ZLyzB8ѹ591 Up_( BWq UgTfS|xupԩOg[gF1 N*^=` 7c*g_\ʀ5"=Z[,ia>ga$]m a>N_OC=%[3?Fg mb0ﶇzN4,p v*/~i)Y&xTkKd+&V BUg,l1xLV 9[)'k }gR/jȩϦWꔐ$q,bo'2ձL矵mZg[xvxu{K`谡'0 hn4`ܘ:m.-0eƼ);Yn Ç3@ λw~׬ ^JXjn=={(D XJSO G~q-ܻ>fC kB5FC/ԗ+ ZOQ%Y@^ j3{^>0j߽D^բGs/Y 'VXV:(,kZ?X~e] 85d-a;κE{\G} bɲNUy >?U@Az0g=ô cO_v.fN@>GeONN?[L!{VӟOٗ7 %-;N``6/#B 2ٍw np5k E-T~n>xƇu%-;^&?큇ƒ_g3r8ws.o]XFWLɬCBD'Bb!OvlX{CNnt.4ANK+`dC~ zР|$+>9ɀ8ӶoyaS+ ǟ0{Ú ac }^AuP0rh8|Xu9zO?X>eL3Epؼ10ohiay6[~n58Na&K mv?zy;:X XԊvY* /7ltk2Vs_ +M$C\ 0 0O+4jh|ɜüL(`Yط6 I 0 Sgk}BgH?iDa[% qJ! y#@! -saaC3]y5 MХT X^ݡ|gahPnO-ݿ|睲Y}<#'2 0L4lV% T(P9rCͺC>pD@a)&\^.QG $p$2 0Lk[@ .IcA %a:oŻ( 0 S:u` (ƸF./oR m;n 0 yӕ W$PDMl AM^AtIND@aɃz KK{d̞$Q{F9 0 0=p0(ruk$z/@by 0Lk@Lؿ$Io]~ON%zu40 0L>d34R$P /Oc'o_vz [Ո)H&.zyC6>0 Πe@)wIwM[Uu0{  x(0 M?ID)N5( o<aNAtQe2N=~oITġjAaT;H_D5n^X2~}= # $(R/a樆7H_HtH#$B\(g`CO HI Hz 05C"I&zy3PG@W XBx 4  DiӦ#j0 S*FӦM+6N,3uDU){2hhC)<DBcZ4X >'Oɓ'z) 0L0XL&?޲,HdyY^ar Bv$9& But'$D"Ш!P QzN2qgyYM%dEy !0PDs!C4%qu@b$Y\uu",o?C)ioo/YV l?I]:ب72@IIyي}aan,2E|卒Z2xjIZ7Jzub:.sXt px $Et|8a Z-ȩ/?$$ A< <'gyv 42z Y'{a@_ $8ã|Ȓ1PNh@kCXj~ 8%<ϯ'k] 2,/Zn"q"@3+9fL5,o ZczǦk M+ rAZǺN!?3<{(HCzeUjׯL1/Eӗ1  l:cs{_Ieyk[Z`/ZGD- R@ux \'IHQP~@.)* 1֮)^bak ]SɊ,/ֶ(o &!N_MC@Nx1+ BD}nM.B9Q&K"eF_ayYjd*yKg!0PH}} 8ۮTDPS C PC򈫁KǘOvykIZ7_r/>نI4ID ě-  * 00˲, 0 SH|c?wIopcBCRP`;O0P Jažv̨JL +C) 3@}bba|BdI -MVBp//Yu3"z +%aa"d?OCaL'PpׯWpo#LB! .0 0 !oYHIaλt!4&^ 2 aa"e?Hf(H翁H@eK, S- '6]oneyk^Z |NISB"HP_kbS>̞:IҎ4I u{wd[Nz@#5Y'LapK&,o[K֢E#{?-L/όOy~s7'۽d߈C =a !ɇY@I., 3N:ɀߛ7ABR$jKdabү0)eX.{xT4FMh `a od1ST`..iQ$a)&{\f; ߟ7ƈ%Inau$ O+ISm;=*UI 0 Ä!?$/ݏA M}[^`0QyF)!6a$W "i&i"5ﲹ_.b\?gNp^ vy$a)3:/s?%M-ݒ(~T1M# 0 T'M2OH$eX$E7,j_ҴMrQHW h^aL#I:ѵJ%S S, T$! ~w@0 0A?HaP/(2 `0&j"P њ\DPh~`}+CG՜cyY^a7 &%%r 'sX9H?~D4@Q^-oXO Z < ߿O3K(6u3r$j3~Zy(ԥFy# "ԥFV+z [Vò֞w0oY\f_ 5`_9`&f[\kK韜_"[K֢9]lʨ]`&6N4Jm5G+L OykŻd$;HuvI*tS %VF@ 9( ߡC +&VUdԒ(oh[z-I"H m&5Hb;4 .Pk- АP@V%N_>D ny%YkQвg[ӿE`g~P L&w0{+\o\pquY@r%BVd{Ddψ%OBHss~_W W̫*$gyYȚ槢d%YP|OKOS6@d+*jayW5x^Zw01UdgRV9ugl}j 0D<"k_ 4 %r:6ɵ}/{{ar'K0,rU~244An5z5)Pby˃r]Ô 89~̲Ϥ ` aʓr5, ZuǾ\a9"hǂP)wmSa*0 0Cκ=]D% fK-_6h줼A$}F@\,ۺ eJ&.'1"'[EZZBS@8ZEJ }= U+k`yWRYԒ(o}?HH KH`fC9ee\%I1z*dzۤKQYJiԊ.o$$5i"=ϔ %G0?Ć Lu:N NJrtU baCA`Hqz5HkW2dczB@-@ad+ a&9*iK G$I)a`oY EPF0LޟES&%D] Vb){`;# JJ/0% rIRAA.}afh"qw¤giFٟo=G)/!ILfKP0 5;' TdbJE.W&\m(qeT A`vLi3.Nbc =M1(e8/-dɂ&8ED `O0L92& { |.?SŠ⎼WzbMFe]]Qऔpmՠri 0 g;h$0, wW&:3ZJ-g_\ 0bC W0 0{_Ow$fLhҔ?@̄M[0Ie0P0 %Y]01,rihU^kNzr':z6*vv B`t"uFL(#.nNDp댺R˙ _/Ji(= 0LFa mĄTPo~k(&4Ik aGgm)װH1FR]i*89Mf) M$a[CZ;kiI z'KJvvvI* a#_׿c%6v+=/d{4qtӞizġaf2Kz]Z1 IDAT^Wz~ mՑ&PO=&˄AZA006aʁK$efFkf$>k/hwpE/~0ncۗb,M %'C`)Q'B5rvOeVs博d涙 \i0(I( . `)>Q)t?LMJ6cµȩFP/m0PI_B a#arVG"njʈ f + [oLp}$7% $i& ;sVQtl0 ¶YZ4Έ6z^)?vmz\]i'pS(D>0 rm3.\N8VT #=[J-s!*@6.zbN6Lr%?/.D>0 S"fc_ۛGڈ nj] 1R= )MLw BJrR 0LuuįD*i6bf\h#FGfzŠj ;~yo7ui8$ 8)a:N߈ 2;r-5_tQdI)JO @) k.aDƿG(3!\3!TNS:J-sZ@6yaV&I_ ܾP@_;Hn0 SJLC?aHKFLFLh3.lRj~D4I @H-)0_`ɍ'9~q̸j0~ {8 7LսAEʰZH? Al0 Ä%I:#O1Q_jKAMs;/ϡ['@(7F0Lq^gMZRQәj7phz\{vQ@N dQ6cLҟm&9E1;[V,5u%R=p. F0LቴkNfBfBU'xcuBM6GlHIMtHb2#`0 DKF_҈ Lj6cqk]Ԩp-I MR^`t]ӱ(6a 2IkʘP?:*>8?1'N*r.8)sS\GV a\M$oEʈ#.Je&vnCs>`xlu:0KvRNRRҵohaCaZʿ̈́LY FlWo= 5 m3PEZZD› "{F?dą6Bշʿ?l `t|?ބ"=7020 Ƚ^?0 d&R7q_oX<XWZvRӣI)pmDY 6 Ar?0B ̈́P#HR_utn*I02E\ {& f?`&4!eS?gR_rj_ӯmOg<ӡ(խq]'%naL 'z[7iq !buRfl x{%G*ҡ" `#aʥ ~|4b1buBuR]M@jRBM)Sd(64nvF?g6–1b 6=l|$$&H=X` `)4[li.=oT _U3svR ',7kh-C EQ}aQh&DҌ w'r{B0BbJHtH@=$d 0A ˆ njT} :ɐ򸿾-S*pm-'aڢ*iQҌ 46㢧g {Bnv.iJ $! L'aZ_1cob|̑_2R݀Lu<Zi 'aT߈ 4XqoC8Y#J}-*ypQ,^Yg2]GKVJ:(0ţ~ľ&?'UxJ}-*yrQ,^񶙰{GQR9Z)Wڟy T:>%̨';ƈ_6Nu:Pv]Gŕ@ 0 SM pcJZۈ0b6®oҧ~a#- @Hp8_?R2Ew-l@Bu4^%7  R{6=$&$mxtd;d{ =N5zʂw*珿Uw_L$" k+(+$8D@n S@~Dc ~Ja+~&V TRf ~ֳ u4)G+jj66@x *icI҈Sj-\Liݲ \?6~I)mVuPFQ]-ЮTL} 0Ƚ<0|寥I0PKHo<4r1wH|>1K`(Wo*Q7PBܳC+~N~AdA@վ=_!\ǰ/\ӳ;Gr-\Gr5 V!)qn&Eo?4ZZLK%ᠬ }y-ϜWcNl£?ǯpV r6\[t hP  lCsk S dd'uw-qZv4 ݗ<(/IPaKbTWZ9+LW&_ 0MR@K-S ݗ<HQ"@{ `@PnVߴ&I׆AnV}ԗZ>]ل/ ʯn>Y%ק}&y51!% ZkdA3E4_0O'@-$!ILePV9RT1C/6N8j{߯9:סaW2-U ?_j 4lJVcWB׿_HL Axeգ9NA-V0] ]DwO|ֹor562L! (̃)~Eo߫ZH$imR fPLd,#k/1: C@ie,fc *?,J>wH"|ZuYwok22bU1nDݗ! !Qm座怺RT0EXx}zPG+D({rJ&s4CTqP-A@`Jh^$T$%]?H[';Pjrw,G?ฎ&jt\m^#*@&զ2)P4MkOm(HuqXjٙ &r~wEƉl۾'+T?!]o(WZ{bէt1Wbʘ ~$%uI)QD-LRpw|%sqLmS0~uκ~>ŋץ:Q(GRWRʛ:Bժ+00<1FN!>`_l/%IZHLg'$MZNE&>jD1El'D.kRTKRNn$1ڿ03F8|`i F53*7N`(ݘ<7~ay̮w_zSqW,FA^ Vn9X>x[0 xgh8=6qxl)r4>$40DMf C`,"mYsB@EGOMyP0xeXyu[Oa+3Ɗ+6JN<}Y(QDuR*A3kY p T%B $fRjX^ c3g؇_IqP8~9)fj *y+_ofO>} T8 ,0q!p]Sa%؇Դs}2PeR>6 ^502\Oa,6fU#{zlɳ]^Z%XRX 3  F4+tkޏ5BJA97 Ϝ:+qhZk΁NXJpl?}\Nq%f@ 䆀LQExBV53@M~ۆvEymDECfRJHAĔ_)DZ}:} c91`ܶT{+9 Rer#Brџ5)dJ*H5^[ bPsaT"wͅ>A?s_1_^/u>GIw/?Qzrg \9 粴2EK WC(> iG@E} lB4j$T @01kAh٪`|.͉%"۠Dcd3) 1gr1gb'DGw l{n-=m@q[c#9zh4IKwlcf(LG L"4d &`A4 ^ &Hq |TLP( "A7JkY@Hb_#8%^+MirIDAT7vw+φatڊόMk2E!L @*"@%U$\PhaBaٟA$8 kvk^f-A ~(1a7۩ JgR)qA9%Y)Qr?}OO)bøE:qp/]\:ũFQ0f AZh4th/B0oK|iHMA3υ\By}:<4o`:4`o3?dڳ&/.4imW \X:o&C8Q!Cߛ"<+sEk!?"QHءrj Jig="]k]'=#C{nk[[.0UøIz{𞛊(O<}꽫t צkט%pCE VK&昀( s1 Ti_ѼY}=~<Ҧƭ~o7XD^"[=i-O:qW$ivF ZR#(IYS 5Bi^8oؖ,7 0 0 0 0 0 0 0 0 0 0 0 0 vKIENDB`sachesi-2.0.4+ds/carrier000066400000000000000000000000311362064257300151120ustar00rootroot00000000000000310 200 0 10.3.2.2789 10 sachesi-2.0.4+ds/qml/000077500000000000000000000000001362064257300143375ustar00rootroot00000000000000sachesi-2.0.4+ds/qml/generic/000077500000000000000000000000001362064257300157535ustar00rootroot00000000000000sachesi-2.0.4+ds/qml/generic/Backup.qml000066400000000000000000000331741362064257300177030ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Dialogs 1.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.1 import Qt.labs.settings 1.0 import BackupTools 1.0 import "UI" 1.0 Item { id: main visible: i.device !== null && i.completed && !i.loginBlock && !i.wrongPass anchors { fill: parent; leftMargin: 20; topMargin: 20 } MessageDialog { id: timeoutWarning title: qsTr("Timed out") informativeText: qsTr("The request timed out. Please backup blind. This is a bug that Blackberry needs to fix.") standardButtons: StandardButton.Ok } Timer { id: attemptLookup running: false interval: 40000 onTriggered: timeoutWarning.open() } ColumnLayout { ColumnLayout { Text { text: qsTr("Options") + translator.lang font.pointSize: 14 font.bold: true } id: options property int value: 0 RowLayout { visible: attemptLookup.running Text { text: qsTr("Refreshing backup sizes") + translator.lang font.pointSize: 12 } BusyIndicator { width: parent.height height: parent.height } } ColumnLayout { Repeater { onCountChanged: options.value = 0 model: i.backMethods delegate: CheckBox { property double curSize: i.backSizes[index] text: (i.backNames[index] + " (" + (i.backSizes[index] < 0 ? qsTr("Unknown Size") : qsTr("%1 MB").arg( ((index == 0 ? appData.selectedSize : i.backSizes[index]) / 1024 / 1024).toFixed(1))) + ")") + translator.lang // index onCurSizeChanged: if (index == 0) appData.selectedSize = curSize onTextChanged: if (attemptLookup.running) attemptLookup.stop() onCheckedChanged: { if (checked) { options.value += 1 << index; totalText.totalVal += i.backSizes[index]; } else { options.value -= 1 << index; totalText.totalVal -= i.backSizes[index] } } Button { anchors.left: parent.right visible: index == 0 && i.backSizes[0] > 0 && !appDataSelect.visible text: qsTr("Choose") + translator.lang onClicked: appDataSelect.show() } } } } Window { id: appDataSelect width: 600 height: 600 title: qsTr("Choose Application Data") + translator.lang onVisibleChanged: if (visible) { x = window.x + (window.width - width) / 2 y = window.y + (window.height - height) / 2 } ColumnLayout { anchors.fill: parent Text { text: qsTr("Total Application Data: %1 MB (%2 Apps)").arg(i.backMethods > 0 ? (i.backSizes[0] / 1024 / 1024).toFixed(1) : "0").arg(backAppView.count) + translator.lang Layout.fillWidth: true wrapMode: Text.Wrap } Text { id: appData Layout.fillWidth: true property double selectedSize: -1.0 text: qsTr("Selected Application Data: %1 MB").arg((selectedSize / 1024 / 1024).toFixed(1)) + translator.lang wrapMode: Text.Wrap } Component { id: sectionHeading RowLayout{ CheckBox { checked: true onClicked: appData.selectedSize += i.changeBackAppMode(section) } Rectangle { //width: container.width height: childrenRect.height color: "lightsteelblue" Text { text: section font.bold: true font.pixelSize: 20 } } } } ScrollView { Layout.fillWidth: true Layout.fillHeight: true ListView { anchors.fill: parent id: backAppView model: i.backAppList section.property: "type" section.criteria: ViewSection.FullString section.delegate: sectionHeading Menu { id: back_options_menu signal checkAll() signal uncheckAll() title: qsTr("Options") + translator.lang MenuItem { text: qsTr("Check All Visible") + translator.lang onTriggered: { back_options_menu.checkAll(); } } MenuItem { text: qsTr("Uncheck All Visible") + translator.lang onTriggered: { back_options_menu.uncheckAll() } } } MouseArea { acceptedButtons: Qt.RightButton onClicked: back_options_menu.popup() anchors.fill: parent } delegate: Item { visible: type !== ""; width: parent.width - 3 height: type === "" ? 0 : 26 Rectangle { anchors.fill: parent color: { switch(type) { case "bin": return "red"; case "data": return "purple"; case "system": return "steelblue"; default: return "transparent"; } } opacity: 0.2 } CheckBox { id: backDelegateBox text: friendlyName + " (" + type + ") " + version width: Math.min(implicitWidth, parent.width - sizeText.width) clip: true checked: isMarked // Per-app backup wasn't working when > 1 app selected enabled: false onCheckedChanged: isMarked = checked onClicked: { if (checked) appData.selectedSize += size; else appData.selectedSize -= size; } Connections { target: back_options_menu onCheckAll: { if (!backDelegateBox.checked) { backDelegateBox.checked = true; appData.selectedSize += size; } } onUncheckAll: { if (backDelegateBox.checked) { backDelegateBox.checked = false; appData.selectedSize -= size; } } } } Label { id: sizeText anchors.right: parent.right text: qsTr("%1 MB").arg((size / 1024 / 1024).toFixed(1)) + translator.lang font.pointSize: 12; } } } } } } Text { visible: i.backMethods id: totalText property double totalVal: 0.0 text: (qsTr("Total:") + " " + (totalVal < 0 ? qsTr("Unknown Size") : qsTr("%1 MB").arg((totalVal / 1024 / 1024).toFixed(1)))) + translator.lang font.pointSize: 12 } } Button { visible: /*!i.backMethods &&*/ !attemptLookup.running enabled: !i.installing && !i.backing && !i.restoring && i.device !== null && i.device.bbid !== "" text: qsTr("Refresh Backup Sizes") + translator.lang onClicked: { appDataSelect.hide(); totalText.totalVal = 0; i.backupQuery(); attemptLookup.start(); } } Label { visible: !settings.advanced text: qsTr("Loading backup sizes can sometimes fail. In this situation, you can backup 'blind'.") + translator.lang } RowLayout { visible: i.backMethods FileDialog { id: backup_files title: qsTr("Choose Backup Filename") + translator.lang folder: settings.backupFolder onAccepted: { i.backup(fileUrl, options.value) settings.backupFolder = folder; } selectExisting: false nameFilters: [ qsTr("Blackberry Backup (*.bbb)") + translator.lang ] } FileDialog { id: restore_files title: qsTr("Select Restore File") + translator.lang folder: settings.backupFolder onAccepted: { i.restore(fileUrl, options.value) settings.backupFolder = folder; } nameFilters: [ qsTr("Blackberry Backup (*.bbb)") + translator.lang ] } Button { text: (totalText.totalVal < 0 ? qsTr("Create Backup Blind") : qsTr("Create Backup")) + translator.lang enabled: !i.installing && !i.backing && !i.restoring && options.value != 0 && i.device !== null && i.device.bbid !== "" onClicked: backup_files.open(); } Button { text: qsTr("Restore Backup") + translator.lang enabled: !i.installing && !i.backing && !i.restoring && options.value != 0 && i.device !== null && i.device.bbid !== "" onClicked: restore_files.open(); } } Label { visible: i.device !== null && i.device.bbid === "" text: qsTr("Your device needs a Blackberry ID to perform backups or restores!") + translator.lang } Label { visible: !settings.advanced text: qsTr("Please note that backups can take a long time, depending on your device data.") + translator.lang } } Rectangle { id: progressBar visible: i.backing || i.restoring anchors {bottom: parent.bottom; bottomMargin: 20; horizontalCenter: parent.horizontalCenter } height: 66; width: parent.width - parent.width / 4; radius: 8 z: 5; color: "gray" opacity: 0.95 Column { anchors {verticalCenter: parent.verticalCenter; left: parent.left; leftMargin: parent.width / 2 - 150 } Text { font.pointSize: 12 text: (i.backing ? qsTr("Creating Backup (%1%)").arg(i.backProgress) : qsTr("Restoring Backup (%1%)").arg(i.backProgress)) + translator.lang } Row { spacing: 10 Text { font.pointSize: 12 text: i.backStatus + " (" + i.backCurProgress + "%)"; } BusyIndicator { width: parent.height height: parent.height } } Text { font.pointSize: 10 visible: i.backProgress == 0 text: "The device is currently generating a list of files to backup. Please wait." } } } } sachesi-2.0.4+ds/qml/generic/Boot.qml000066400000000000000000000045041362064257300173740ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.1 import "UI" 1.0 // This page is unix-only as Windows has an outdated driver model which is not compatible Column { id: toolsColumn anchors {top: parent.top; topMargin: 20; left: parent.left; leftMargin: 20 } width: parent.width - 40; spacing: 15 Text { text: qsTr("Boot Communication") + translator.lang font.pointSize: 14 font.bold: true } Row { spacing: 20 Button { text: qsTr("Info") + translator.lang onClicked: b.setCommandMode(1, postReboot.checked); } Button { text: qsTr("RimBoot") + translator.lang onClicked: b.setCommandMode(2, postReboot.checked); } Button { text: qsTr("Nuke") + translator.lang onClicked: b.setCommandMode(3, postReboot.checked); } Button { text: qsTr("Debug Mode") + translator.lang onClicked: b.setCommandMode(4, postReboot.checked); } } CheckBox { id: postReboot text: qsTr("Reboot after") + translator.lang } Row { visible: b.connecting spacing: 15 Text { text: qsTr("Connecting to bootrom") + translator.lang } BusyIndicator { height: parent.implicitHeight width: height } Button { text: qsTr("Cancel") + translator.lang onClicked: b.disconnect(); } } GroupBox { title: qsTr("Detected devices:") + translator.lang ScrollView { frameVisible: true ListView { width: parent.width height: 50 model: b.devices spacing: 3 delegate: Label { text: switch (modelData) { case "1": return "BlackBerry Bootloader"; case "8013": return "BlackBerry USB (Unix)"; case "8017": return "BlackBerry USB (Autodetect)"; default: return "BlackBerry USB (Windows)"; } font.pointSize: 12 } } } } } sachesi-2.0.4+ds/qml/generic/Device.qml000066400000000000000000000013451362064257300176700ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.1 Item { id: main TabView { id: deviceSubTabs anchors.fill: parent frameVisible: count > 1 tabsVisible: count > 1 // Placeholder. Todo: Add and Remove on wifi device from C++ Signal. // Somehow get the ip in the title too. //Component.onCompleted: deviceSubTabs.addTab(qsTr("Wifi"), deviceSubComponent ) // Permanent tab Tab { title: qsTr("USB") + translator.lang + ((i.device === null || i.device.battery < 0) ? (" [" + i.ip + "]") : "") DeviceSub { anchors.fill: parent } } } Component { id: deviceSubComponent DeviceSub { anchors.fill: parent } } } sachesi-2.0.4+ds/qml/generic/DeviceSub.qml000066400000000000000000000152011362064257300203360ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.1 Item { ColumnLayout { anchors { fill: parent; margins: 15 } Label { text: qsTr("Device Information") + translator.lang font.pointSize: 14 font.bold: true } GroupBox { visible: i.device !== null title: qsTr("Tools") + translator.lang RowLayout { Button { id: wipe text: qsTr("Wipe") + translator.lang onClicked: i.wipe(); } Button { id: factorywipe text: qsTr("Factory Reset") + translator.lang onClicked: i.factorywipe(); } Button { id: reboot text: qsTr("Reboot") + translator.lang onClicked: i.reboot(); } } } GridLayout { columns: 4 rowSpacing: 20 columnSpacing: 20 Label { text: qsTr("Name") + translator.lang font.bold: true } Label { id: deviceNameText text: i.device === null ? (qsTr("Unknown") + translator.lang) : i.device.friendlyName } Label { text: qsTr("HW Name") + translator.lang font.bold: true } Label { text: i.device === null ? (qsTr("Unknown") + translator.lang) : i.device.name font.pointSize: i.device === null ? -1 : 10 } Label { text: qsTr("BBID") + translator.lang font.bold: true } Label { text: i.device === null ? (qsTr("Unknown") + translator.lang) : i.device.bbid } Label { text: qsTr("PIN") + translator.lang font.bold: true } Label { text: i.device === null ? (qsTr("Unknown") + translator.lang) : i.device.pin } Label { text: qsTr("BSN") + translator.lang font.bold: true } Label { text: i.device === null ? (qsTr("Unknown") + translator.lang) : i.device.bsn } Label { font.bold: true } Label { } Label { text: qsTr("OS") + translator.lang font.bold: true } Label { text: i.device === null ? (qsTr("Unknown") + translator.lang) : i.device.os } Label { text: qsTr("Radio") + translator.lang font.bold: true } Label { text: i.device === null ? (qsTr("Unknown") + translator.lang) : i.device.radio } Label { text: qsTr("HW") + translator.lang font.bold: true } Label { text: i.device === null ? (qsTr("Unknown") + translator.lang) : i.device.hw } Label { text: qsTr("Restrictions") + translator.lang font.bold: true } Label { text: (i.device === null ? qsTr("Unknown") : (i.device.restrictions === "" ? qsTr("None") : i.device.restrictions)) + translator.lang } Label { text: qsTr("Setup Complete") + translator.lang font.bold: true } Label { text: (i.device === null ? qsTr("Unknown") : (i.device.setupComplete ? qsTr("True") : qsTr("False"))) + translator.lang } Label { text: qsTr("Developer Mode") + translator.lang font.bold: true } Label { text: (i.device === null ? qsTr("Unknown") : (i.device.devMode ? qsTr("True") : qsTr("False"))) + translator.lang } Label { text: qsTr("Battery") + translator.lang font.bold: true } Label { text: (i.device === null || i.device.battery < 0) ? (qsTr("Unknown") + translator.lang) : i.device.battery + "%" } Label { text: qsTr("Connection") + translator.lang font.bold: true } Label { text: ((i.device === null || i.device.battery < 0) ? qsTr("None") : qsTr("USB")) + translator.lang } Label { text: qsTr("Refurbished Date") + translator.lang font.bold: true } RowLayout { Label { id: refurbText text: ((i.device === null) ? qsTr("Unknown") : (i.device.refurbDate === "" ? qsTr("Never") : i.device.refurbDate)) + translator.lang } Button { visible: { if (i.device === null) return false; var strArray = i.device.os.split('.') if (strArray[0] !== 10 || strArray[1] > 3) return false; if (strArray[1] === 3 && strArray[2] > 1) return false; if (strArray[1] === 3 && strArray[2] === 1 && strArray[3] > 821) return false; return true; } property bool isSet: refurbText.text !== (qsTr("Never") + translator.lang) text: (isSet ? qsTr("Clear") : qsTr("Set")) + translator.lang onClicked: { var date = Math.floor(new Date().getTime() / 1000) i.setActionProperty("RefurbDate", isSet ? "0" : date.toString()) updateProps.start() } Timer { id: updateProps interval: 100 onTriggered: i.scanProps() } } } Label { text: qsTr("Free Disk Space") + translator.lang font.bold: true } Label { text: ((i.device === null || i.device.freeSpace === 0) ? qsTr("Unknown") : qsTr("%1 GB").arg((i.device.freeSpace / 1024 / 1024 / 1024).toFixed(3))) + translator.lang } } } } sachesi-2.0.4+ds/qml/generic/Downloader.qml000066400000000000000000000030321362064257300205620ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.1 import "UI" 1.0 // This page is hidden for now as it is not very useful. TabView { id: main Column { anchors {left: parent.left; leftMargin: 20; top: parent.top; topMargin: 20 } spacing: 20 /* Text { text: qsTr("#1. Change PIN (Requires OS10.2)") + translator.lang font.pointSize: 12 } Column { spacing: 20 Button { text: qsTr("Sign NVRAM") + translator.lang enabled: i.completed onClicked: i.resignNVRAM(); } Text { x: 10 text: qsTr("Current PIN %1").arg(i.knownPIN) + translator.lang font.pointSize: 10 } Row { spacing: 20 TextCouple { id: repin type: qsTr("New PIN") + translator.lang value: "2CCC0000" } Button { text: qsTr("Reassign") + translator.lang enabled: i.completed onClicked: i.newPin(repin.value); } } }*/ Text { text: qsTr("#1. Start RTAS (Requires OS10.2)") + translator.lang font.pointSize: 12 } Button { text: qsTr("Start RTAS") + translator.lang enabled: i.completed onClicked: i.startRTAS(); } } } sachesi-2.0.4+ds/qml/generic/Extract.qml000066400000000000000000000210431362064257300201000ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Dialogs 1.1 import QtQuick.Layouts 1.1 import "UI" 1.0 Item { Rectangle { visible: p.splitting anchors {bottom: parent.bottom; bottomMargin: 10; horizontalCenter: parent.horizontalCenter } height: 80; width: parent.width - 40; radius: 8 z: 5; color: "gray" opacity: 0.95 Column { spacing: 5 anchors {top: parent.top; topMargin: 10; left: parent.left; leftMargin: parent.width * 0.5 - splitText.splitType.length * 9 } Row { spacing: 10 Text { id: splitText property string splitType: ""; property int splitting: p.splitting + translator.lang onSplittingChanged: { switch(splitting) { case 1: splitType = qsTr("Splitting Autoloader"); break; case 2: splitType = qsTr("Combining Autoloader"); break; case 3: splitType = qsTr("Extracting Image"); break; case 4: splitType = qsTr("Extracting Apps"); break; case 5: splitType = qsTr("Fetching required files"); break; default: splitType = qsTr("Waiting"); break; } } text: splitType + ((p.splitting == 5) ? "" : " (" + p.splitProgress + "%)"); font.pointSize: 14 } BusyIndicator { width: parent.height height: parent.height } } Text { visible: p.splitProgress > 100 text: qsTr("Percentages are not entirely accurate for QNX6 files.") + translator.lang font.pointSize: 10 } } Button { z: 6; visible: p.splitting == 2 anchors {bottom: parent.bottom; bottomMargin: 10; horizontalCenter: parent.horizontalCenter} text: qsTr("Cancel") + translator.lang onClicked: p.abortSplit(); } } ColumnLayout { anchors { fill: parent; margins: 15 } Layout.fillHeight: true Text { text: qsTr("Extraction Tools") + translator.lang font.pointSize: 14 font.bold: true } ColumnLayout { RowLayout { FileDialog { id: combine_files title: qsTr("Create Autoloader") + translator.lang folder: settings.installFolder onAccepted: { p.combineAutoloader(fileUrls); settings.installFolder = folder; } nameFilters: [ qsTr("Signed Images") + " (*.signed *.bar *.zip)" + translator.lang ] } Button { text: qsTr("Create from Folder") + translator.lang enabled: !p.splitting onClicked: { combine_files.selectFolder = true combine_files.open() } } Button { text: qsTr("Create from Files") + translator.lang enabled: !p.splitting onClicked: { combine_files.selectFolder = false combine_files.selectMultiple = true combine_files.open() } } } Label { text: qsTr("Create Autoloader .exe from .signed images") + translator.lang font.bold: true; } } // Extract Signed ColumnLayout { RowLayout { FileDialog { id: split_files title: qsTr("Extract Signed") + translator.lang folder: settings.installFolder onAccepted: { p.splitAutoloader(fileUrl, userSelect.checked * 1 + osSelect.checked * 2 + radioSelect.checked * 4 + ifsSelect.checked * 8 + pinSelect.checked * 16); settings.installFolder = folder; } nameFilters: [ qsTr("Signed Containers") + " (*.exe *.bar *.zip)" + translator.lang ] } Button { text: qsTr("Extract Signed") + translator.lang enabled: !p.splitting onClicked: split_files.open() } CheckBox { visible: settings.advanced id: userSelect text: qsTr("User") + translator.lang checked: true } CheckBox { visible: settings.advanced id: osSelect text: qsTr("OS") + translator.lang checked: true } CheckBox { visible: settings.advanced id: radioSelect text: qsTr("Radio") + translator.lang checked: true } CheckBox { visible: settings.advanced id: ifsSelect text: "IFS" checked: true } CheckBox { visible: settings.advanced id: pinSelect text: qsTr("PINList") + translator.lang } } Label { text: qsTr("Split .signed from autoloader .exe, .bar or .zip") + translator.lang font.bold: true; } } // Extract Apps ColumnLayout { Button { text: qsTr("Extract Apps") + translator.lang enabled: !p.splitting onClicked: if (!p.splitting) p.extractImage(2, 2); } Label { text: qsTr("Extracts all bar archives from a debrick/repair .signed") + translator.lang font.bold: true; } Label { text: qsTr("Note: To extract apps from a .bar, please split it first (above)") + translator.lang } } // Extract Image ColumnLayout { visible: settings.advanced RowLayout { property int imageValue: rcfsImage.checked * 1 + qnxImage.checked * 2 + bootImage.checked * 4 Button { text: qsTr("Extract Image") + translator.lang enabled: !p.splitting && parent.imageValue onClicked: if (!p.splitting) p.extractImage(1, parent.imageValue); } CheckBox { id: rcfsImage checked: true text: "RCFS" } CheckBox { id: qnxImage text: "QNX6" } CheckBox { id: bootImage checked: true text: "IFS" } } Label { text: qsTr("Extracts filesystem image") + translator.lang font.bold: true; } } // Dump Contents ColumnLayout { visible: settings.advanced RowLayout { property int partValue: corePart.checked * 1 + userPart.checked * 2 + bootPart.checked * 4 Button { text: qsTr("Dump Contents") + translator.lang enabled: !p.splitting && parent.partValue onClicked: if (!p.splitting) p.extractImage(0, parent.partValue); } CheckBox { id: corePart checked: true text: qsTr("Core") + translator.lang } CheckBox { id: userPart checked: true text: qsTr("User") + translator.lang } CheckBox { id: bootPart checked: false text: qsTr("Boot") + translator.lang } } Label { text: qsTr("Dump all file contents") + translator.lang font.bold: true; } } } } sachesi-2.0.4+ds/qml/generic/Installer.qml000066400000000000000000000255311362064257300204310ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Dialogs 1.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.0 import "UI" 1.0 Item { id: main property string newLine: i.newLine property string details: "" property string lasterror: "\n" onNewLineChanged: details += i.newLine visible: i.device !== null && i.completed && !i.loginBlock && !i.wrongPass anchors.fill: parent Button { visible: i.dgProgress >= 0 && !installWin.visible anchors {bottom: parent.bottom; bottomMargin: 10; horizontalCenter: parent.horizontalCenter } text: qsTr("View Install (%1)").arg(i.dgProgress) + translator.lang onClicked: installWin.visible = true } Window { visible: i.extractInstallZip color: "lightgray" width: patientText.width + 20 height: patientText.height + 20 onVisibleChanged: if (visible) { x = window.x + (window.width - width) / 2 y = window.y + (window.height - height) / 2 } Label { id: patientText text: qsTr("Please be patient while the installation zip is extracted.") + translator.lang } } Window { id: installWin visible: i.dgProgress >= 0 width: parent.width / 3; height: Math.min(parent.height / 2, width + 20); onVisibleChanged: if (visible) { x = window.x + (window.width - width) / 2 y = window.y + (window.height - height) / 2 } color: "lightgray" title: (i.firmwareUpdate ? qsTr("Firmware Update") : qsTr("Install")) + translator.lang CircleProgress { width: parent.width height: parent.height anchors.bottom: parent.bottom currentValue: i.curDGProgress overallValue: i.dgProgress curId: i.dgPos + 1 maxId: i.dgMaxPos statusText: ((i.curDGProgress != 100) ? ( i.curDGProgress < 50 ? qsTr("Sending") : qsTr("Installing")) : qsTr("Sent")) + translator.lang text: i.curInstallName } } DropArea { id: dragArea anchors.fill: parent onDropped: { if (drop.hasUrls) { i.install(drop.urls); tabs.currentIndex = 1 } } } Rectangle { anchors.fill: parent color: dragArea.containsDrag ? Qt.rgba(0.2,0.2,0.6,0.1) : Qt.rgba(0.0,0.0,0.0,0.0) } ColumnLayout { anchors {fill: parent; margins: 15 } Label { Layout.fillWidth: true text: qsTr("To install .bar files such as applications or firmware, you can just Drag and Drop to this page. Otherwise, select the options below:") + translator.lang wrapMode: Text.Wrap font.pointSize: 12 } Row { spacing: 15 FileDialog { id: install_files title: qsTr("Install applications to device") + translator.lang folder: settings.installFolder onAccepted: { i.install(fileUrls) tabs.currentIndex = 1 settings.installFolder = folder; } selectMultiple: true nameFilters: [ qsTr("Blackberry Installable (*.bar)") + translator.lang ] } Button { text: qsTr("Install Folder") + translator.lang onClicked: { if (i.installing) details += qsTr("Error: Your device can only process one task at a time. Please wait for previous install to complete.
") + translator.lang else if (i.backing || i.restoring) details += qsTr("Error: Your device can only process one task at a time. Please wait for backup/restore process to complete.
") + translator.lang else { install_files.title = qsTr("Select Folder") + translator.lang install_files.selectFolder = true; install_files.open(); } } } Button { text: qsTr("Install Files") + translator.lang onClicked: { if (i.installing) details += qsTr("Error: Your device can only process one task at a time. Please wait for previous install to complete.
") + translator.lang else if (i.backing || i.restoring) details += qsTr("Error: Your device can only process one task at a time. Please wait for backup/restore process to complete.
") + translator.lang else { install_files.title = qsTr("Select Files") + translator.lang install_files.selectFolder = false; install_files.selectMultiple = true; install_files.open(); } } } CheckBox { checked: !i.allowDowngrades onCheckedChanged: i.allowDowngrades = !checked text: qsTr("Only install newer apps") + translator.lang anchors.verticalCenter: parent.verticalCenter } } TabView { id: tabs Layout.alignment: Qt.AlignBottom Layout.fillHeight: true Layout.fillWidth: true Button { id: list_files anchors { top: parent.top; topMargin:-height; right: parent.right } enabled: i.device !== null && i.device.setupComplete text: qsTr("Refresh") + translator.lang onClicked: i.scanProps(); } // Applications Tab { title: qsTr("Your Applications") + translator.lang id: app_tab Item { Image { id: uninstall_notifier visible: uninstalling property bool uninstalling: false anchors {centerIn: parent; verticalCenterOffset: -parent.height / 4} source: "trash.png" width: 75; height: 75 opacity: 0.8 BusyIndicator { anchors.fill: parent } } Text { visible: appView.count == 0 anchors.centerIn: parent font.pointSize: 14 text: ((i.device === null) ? qsTr("Device disconnected") : (i.device.setupComplete ? qsTr("Use 'Refresh' to update list") : qsTr("Your device has not completed setup"))) + translator.lang } ScrollView { anchors.fill: parent ListView { id: appView anchors.fill: parent spacing: 3 clip: true model: i.appList Menu { id: apps_menu visible: appView.count > 0 title: qsTr("Options") + translator.lang MenuItem { text: qsTr("Uninstall Marked") + translator.lang iconSource: "trash.png" enabled: !i.installing onEnabledChanged: if (enabled && uninstall_notifier.uninstalling) { uninstall_notifier.uninstalling = false; } onTriggered: { if (i.uninstallMarked()) uninstall_notifier.uninstalling = true; } } MenuItem { text: qsTr("Show Installed Apps") + translator.lang iconSource: "text.png" onTriggered: i.exportInstalled(); } } MouseArea { enabled: appView.count > 0 acceptedButtons: Qt.RightButton onClicked: apps_menu.popup() anchors.fill: parent } delegate: Item { visible: type !== ""; width: parent.width - 3 height: type === "" ? 0 : 26 Rectangle { anchors.fill: parent color: { switch(type) { case "os": return "red"; case "radio": return "purple"; case "application": if (friendlyName.indexOf("sys.data") === 0) return "lightblue"; else return "steelblue"; default: return "transparent"; } } opacity: 0.2 } CheckBox { text: friendlyName width: Math.min(implicitWidth, parent.width - versionText.width) clip: true checked: isMarked onCheckedChanged: isMarked = checked; } Label { id: versionText anchors.right: parent.right text: version font.pointSize: 12; } } } } } } // Log Tab { title: qsTr("Log") + translator.lang Item { id: log_tab TextArea { id: updateMessage width: tabs.width; height: tabs.height textFormat: TextEdit.RichText selectByKeyboard: true wrapMode: TextEdit.WrapAnywhere readOnly: true text: details } } } } } } sachesi-2.0.4+ds/qml/generic/Search.qml000066400000000000000000000574411362064257300177060ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.1 import "UI" 1.0 Item { property bool init: p.updateMessage === "" state: "initing" Window { id: downloadWin property int maxId: download.maxId onMaxIdChanged: visible = (maxId > 0) //visible: false onVisibleChanged: if (visible) { x = window.x + (window.width - width) / 2 y = window.y + (window.height - height) / 2 } width: parent.width / 3; height: Math.min(parent.height / 2, width + 20); color: "lightgray" title: qsTr("Download") + translator.lang ColumnLayout { anchors.fill: parent CircleProgress { Layout.fillHeight: true Layout.fillWidth: true currentValue: download.curProgress overallValue: download.progress curId: download.id + 1 maxId: download.maxId statusText: "Downloading" text: download.curName } Button { id: cancelButton text: qsTr("Cancel Download") + translator.lang onClicked: download.reset(); anchors.horizontalCenter: parent.horizontalCenter } } } Column { id: scanButton anchors { bottom: parent.bottom; bottomMargin: 15; leftMargin: 15 } ColumnLayout { anchors.horizontalCenter: parent.horizontalCenter Button { id: searchButton anchors.horizontalCenter: parent.horizontalCenter enabled: !p.scanning text: (p.scanning ? qsTr("Searching...") : qsTr("Search")) + translator.lang onClicked: { p.updateDetailRequest((/*delta.checked ? i.appDeltaMsg() :*/ ""), country.value, carrier.value, device.selectedItem, variant.selectedItem, mode.selectedItem /*, server.selectedItem , version.selectedItem*/) } } RadioButton { id: delta anchors.horizontalCenter: parent.horizontalCenter visible: false && settings.advanced && !p.scanning && typeof i !== 'undefined' && i.appCount > 0 checked: true text: qsTr("Delta") + translator.lang } Button { text: qsTr("Version Lookup") + translator.lang onClicked: versionLookup.visible = !versionLookup.visible } } Text { property string message: p.error visible: message.length > 1 && !p.multiscan && p.updateMessage == "" Layout.alignment: Qt.AlignHCenter font.bold: true onMessageChanged: if (message.length && message.length < 5) text = qsTr("Server did not respond as expected [%1].").arg(message) else if (message === "Success") text = qsTr("Success. No updates were available.") else text = message; } } RowLayout { id: urlLinks anchors { left: variables.right; right: parent.right; bottom: parent.bottom; margins: 15 } GroupBox { title: qsTr("Download For") + translator.lang TextCoupleSelect { id: downloadDevice type: qsTr("Device") + translator.lang selectedItem: 0 //property int familyType: (selectedItem == 0) ? i.device.hwFamily : selectedItem property string hwid: i.device === null ? "" : i.device.hw property int hwfam: i.device === null ? 0 : i.device.hwFamily property string familyName: (hwfam == 0 || hwfam > listModel.count) ? qsTr("Unknown") : listModel.get(hwfam).text subtext: hwid != "" ? hwid + " (" + familyName + ")" : "" onSubtextChanged: generateModel() Component.onCompleted: generateModel() function generateModel() { var newText = (hwid != "Unknown" && hwid != "") ? qsTr("Connected") : qsTr("As Searched") if (listModel.count < 2 || listModel.get(0).text !== newText) { listModel.clear() listModel.append({"text" : newText }) for (var i = 0; i < babyModel.count; i++) listModel.append({"text" : babyModel.get(i).text }) } } listModel: ListModel { ListElement { text: "As Searched" } } } } ColumnLayout { Button { enabled: p.updateCheckedCount > 0 Layout.alignment: Qt.AlignHCenter text: qsTr("Grab Links") + translator.lang onClicked: p.grabLinks(downloadDevice.selectedItem) } Button { enabled: p.updateCheckedCount > 0 && !download.verifying visible: !download.running Layout.alignment: Qt.AlignHCenter text: (download.verifying ? qsTr("Verifying") : qsTr("Download")) + translator.lang onClicked: { download.start(); p.downloadLinks(downloadDevice.selectedItem) } } Button { visible: download.running text: qsTr("View Download (%1%)").arg(download.progress) + translator.lang onClicked: downloadWin.visible = true } Button { visible: download.running text: qsTr("Cancel Download") + translator.lang onClicked: download.reset(); } } } ColumnLayout { id: variables anchors.top: parent.top anchors.topMargin: 30 anchors.leftMargin: 10 Layout.fillHeight: true height: (parent.height * 4) / 6 Button { id: hideWhitelistButton checkable: true text: checked ? qsTr("Hide") : qsTr("Show Settings") } ColumnLayout { id: whitelistSettings spacing: 25 scale: hideWhitelistButton.checked visible: scale !== 0.0 Behavior on scale { NumberAnimation { duration: 300 } } Label { font.pointSize: 14 text: qsTr("Whitelist Settings") + translator.lang font.bold: true } Label { visible: !settings.advanced text: qsTr("Finds updates approved by other carriers") + translator.lang } // On a new device becoming connected, update search results property bool devicePresent: i.completed property bool githubUpdateComplete: false onDevicePresentChanged: if (githubUpdateComplete && devicePresent && searchButton.enabled) searchButton.clicked() // Find latest country/carrier pair from github property string latestOS: "10.3.2.840" Component.onCompleted: { var http = new XMLHttpRequest() var url = "https://raw.githubusercontent.com/xsacha/Sachesi/master/carrier"; http.open("GET", url, true); http.send(null) http.onreadystatechange = function() { if(http.readyState == 4 && http.status == 200) { var array = http.responseText.split('\n'); array = array.filter(function(e){return e}); if (array.length > 3) { country.value = array[0] carrier.value = array[1] device.selectedItem = parseInt(array[2]) latestOS = array[3] if (array.length > 4) { variant.selectedItem = parseInt(array[4]) } if (searchButton.enabled) searchButton.clicked() githubUpdateComplete = true; } } } } TextCouple { id: country type: qsTr("Country") + translator.lang value: "310" subtext: carrierinfo.country restrictions: Qt.ImhDigitsOnly | Qt.ImhNoPredictiveText maxLength: 3 onValueChanged: if (value.length == 3) carrierinfo.mccChange(value); onClicked: searchButton.clicked(); helpLink: "https://en.wikipedia.org/w/index.php?title=Mobile_country_code" } TextCouple { id: carrier type: qsTr("Carrier") + translator.lang value: "200" subtext: carrierinfo.carrier restrictions: Qt.ImhDigitsOnly | Qt.ImhNoPredictiveText maxLength: 3 onValueChanged: if (value.length > 0 && value.length <= 3) carrierinfo.mncChange(("00" + value).slice(-3)); onClicked: searchButton.clicked(); } Image { source: carrierinfo.image <= 0 ? "" : "http://appworld.blackberry.com/ClientAPI/image/" + carrierinfo.image + "/150X/png" sourceSize {height: carrier.height * 2 } } GroupBox { title: qsTr("Search For") + translator.lang ColumnLayout { TextCoupleSelect { id: device selectedItem: 0 type: qsTr("Device") + translator.lang // List everything we know except abandoned models ListModel { id: advancedModel ListElement { text: "Z30 + Classic + Leap"} ListElement { text: "Z10 (OMAP)" } ListElement { text: "Z10 (QCOM) + P9982" } ListElement { text: "Z3 + Kopi/Cafe" } ListElement { text: "Passport" } ListElement { text: "Q5 + Q10 + P9983" } ListElement { text: "Developer" } ListElement { text: "Ontario"} } // Only list released models ListModel { id: babyModel ListElement { text: "Z30 + Classic" } ListElement { text: "Z10 (STL 100-1)" } ListElement { text: "Z10 (STL 100-2/3/4) + P9982" } ListElement { text: "Z3"} ListElement { text: "Passport"} ListElement { text: "Q5 + Q10 + P9983"} } function changeModel() { var selected = selectedItem listModel = settings.advanced ? advancedModel : babyModel selectedItem = Math.min(selected, listModel.count - 1); } function updateVariant() { if (variantModel != null) { variantModel.clear() if (p.variantCount(selectedItem) > 1) variantModel.append({ 'text': qsTr('Any')}); for (var i = 0; i < p.variantCount(selectedItem); i++) variantModel.append({ 'text': p.nameFromVariant(selectedItem, i)}) variant.selectedItem = 10; } } property bool advanced: settings.advanced onAdvancedChanged: changeModel() onSelectedItemChanged: updateVariant(); Component.onCompleted: { changeModel(); updateVariant(); } } TextCoupleSelect { id: variant type: qsTr("Variant") + translator.lang selectedItem: 9 // This is going to be hell to maintain. Maybe an identifier in dev[] for carrier-specific and its associated code? /*onSelectedItemChanged: if (device.text === "Z10 QCOM" && selectedItem == 3) { country.value = "311"; carrier.value = "480" } else if (device.text === "Q10" && selectedItem == 2) { country.value = "311"; carrier.value = "480" } else if (device.text === "Q10" && selectedItem == 4) { country.value = "310"; carrier.value = "120" } else if (device.text === "Z30" && selectedItem == 3) { country.value = "311"; carrier.value = "480" } else if (device.text === "Z30" && selectedItem == 4) { country.value = "310"; carrier.value = "120" }*/ listModel: ListModel { id: variantModel; } } } } TextCoupleSelect { visible: settings.advanced id: mode type: qsTr("Mode") + translator.lang listModel: [ qsTr("Upgrade") + translator.lang, qsTr("Debrick") + translator.lang ] } /*TextCoupleSelect { visible: settings.advanced id: server type: qsTr("Server") + translator.lang listModel: [ qsTr("Production") + translator.lang, qsTr("Beta") + translator.lang, qsTr("Beta 2") + translator.lang, qsTr("Alpha") + translator.lang, qsTr("Alpha 2") + translator.lang ] }*/ /*TextCoupleSelect { id: version type: "API" listModel: [ "2.1.0", "2.0.0", "1.0.0" ] }*/ } } VersionLookup { id: versionLookup } TextArea { id: updateMessage anchors {top: parent.top; left: variables.right; right: parent.right; margins: 15; } Layout.fillWidth: true height: parent.height / 10 text: p.updateMessage readOnly: true textFormat: TextEdit.RichText selectByKeyboard: true } // Changes required to make this workable // - Need to prevent it being horizontally scrollable or at least make it fit by default // - Requires Qt 5.3 for resizeToContents()!! // - Context menu doesn't work? We need (Un)Check All /*TableView { id: updateAppMessage anchors {top: updateMessage.bottom; bottom: urlLinks.top; left: variables.right; right: parent.right; margins: 15; } Layout.fillHeight: true Layout.fillWidth: true alternatingRowColors: false backgroundVisible: false model: p.updateAppList Menu { id: options_menu signal checkAll() signal uncheckAll() title: qsTr("Options") + translator.lang MenuItem { enabled: p.updateCheckedCount != p.updateAppCount text: qsTr("Check All") + translator.lang onTriggered: { options_menu.checkAll(); for (var i = 0; i < p.updateAppCount; i++) p.updateAppList[i].isMarked = true; } } MenuItem { enabled: p.updateCheckedCount > 0 text: qsTr("Uncheck All") + translator.lang onTriggered: { options_menu.uncheckAll() for (var i = 0; i < p.updateAppCount; i++) p.updateAppList[i].isMarked = false; } } } TableViewColumn { width: parent.width - verCol.width - sizeCol.width; id: nameCol; role: "friendlyName"; title: qsTr("Name") + translator.lang; } TableViewColumn { id: verCol; role: "version"; title: qsTr("Version") + translator.lang; resizable: false; } TableViewColumn { id: sizeCol; role: "size"; title: qsTr("Size") + translator.lang; resizable: false; } //onModelChanged: { verCol.resizeToContents(); sizeCol.resizeToContents(); } rowDelegate: Rectangle { opacity: 0.2 color: { switch(typeof modelData != 'undefined' ? modelData.type : "") { case "os": return "red"; case "radio": return "purple"; case "application": if (modelData.friendlyName.indexOf("sys.data") === 0) return "lightblue"; else return "steelblue"; default: return "transparent"; } } } itemDelegate: Text { property variant value: styleData.value text: styleData.role === "size" ? qsTr("%1 MB").arg((value / 1024 / 1024).toFixed(1)) + translator.lang : value horizontalAlignment: (styleData.role === "size") ? Qt.AlignRight : Qt.AlignLeft clip: true } }*/ // Cheat to get system widths of text here. Should use a TableView (above) later to replace it. // A Label with 6 characters and ' MB', like the maximum filesize of an app Label { visible: false; id: sizeHint; font.pointSize: 12; text: qsTr("1700.0 MB") + translator.lang; } GroupBox { id: updateAppMessage // Qt 5.2 width bug: Add an extra 8 spaces to message to compensate property string selectedMsg: qsTr("Selected: %1 Apps").arg(p.updateCheckedCount == p.updateAppCount ? qsTr("All (%1)").arg(p.updateAppCount) : (p.updateCheckedCount + "/" + p.updateAppCount)) + (p.updateNeededCount !== p.updateAppCount ? (" | " + qsTr("Needed: %1 Apps").arg(p.updateCheckedNeededCount == p.updateAppNeededCount ? qsTr("All (%1)").arg(p.updateAppNeededCount) : p.updateCheckedNeededCount + "/" + p.updateAppNeededCount)) : "") + " " + translator.lang title: selectedMsg anchors {top: updateMessage.bottom; bottom: urlLinks.top; left: variables.right; right: parent.right; margins: 15; } Layout.fillHeight: true Layout.fillWidth: true ScrollView { anchors.fill: parent ListView { anchors.fill: parent spacing: 3 clip: true model: p.updateAppList Menu { id: options_menu signal checkAll() signal checkAllNeeded() signal uncheckAll() title: qsTr("Options") + translator.lang MenuItem { enabled: p.updateCheckedCount !== p.updateAppCount text: qsTr("Check All") + translator.lang onTriggered: { options_menu.checkAll(); for (var i = 0; i < p.updateAppCount; i++) p.updateAppList[i].isMarked = true; } } MenuItem { enabled: p.updateCheckedNeededCount !== p.updateAppNeededCount text: qsTr("Check All Needed") + translator.lang onTriggered: { options_menu.checkAllNeeded(); for (var i = 0; i < p.updateAppCount; i++) p.updateAppList[i].isMarked = (p.updateAppList[i].isAvailable && !p.updateAppList[i].isInstalled); } } MenuItem { enabled: p.updateCheckedCount > 0 text: qsTr("Uncheck All") + translator.lang onTriggered: { options_menu.uncheckAll() for (var i = 0; i < p.updateAppCount; i++) p.updateAppList[i].isMarked = false; } } } MouseArea { acceptedButtons: Qt.RightButton onClicked: options_menu.popup() anchors.fill: parent } delegate: Item { visible: type !== ""; width: parent.width - 3 height: type === "" ? 0 : 26 Rectangle { anchors.fill: parent color: { switch(type) { case "os": return "red"; case "radio": return "purple"; case "application": if (friendlyName.indexOf("sys.data") === 0) return "lightblue"; else return "steelblue"; default: return "transparent"; } } opacity: 0.2 } CheckBox { id: delegateBox text: friendlyName + (isInstalled ? " " + qsTr("(older)") : (isAvailable ? "" : " " + qsTr("(downloaded)"))) + translator.lang opacity: (!isInstalled && isAvailable) ? 1.0 : 0.6 width: Math.min(implicitWidth, parent.width - versionText.width*versionText.visible - sizeText.width) clip: true checked: isMarked onCheckedChanged: isMarked = checked; Connections { target: options_menu onCheckAll: delegateBox.checked = true; onCheckAllNeeded: delegateBox.checked = isAvailable && !isInstalled; onUncheckAll: delegateBox.checked = false; } } Label { id: versionText anchors.right: sizeText.left; visible: (parent.width - sizeText.paintedWidth) > delegateBox.implicitWidth text: version } Label { id: sizeText anchors.right: parent.right width: sizeHint.width horizontalAlignment: Text.AlignRight text: qsTr("%1 MB").arg((size / 1024 / 1024).toFixed(1)) + translator.lang font.pointSize: 12; } } } } } states: [ State { name: "initing" when: init AnchorChanges { target: variables; anchors.horizontalCenter: parent.horizontalCenter; anchors.left: undefined } AnchorChanges { target: scanButton; anchors.horizontalCenter: parent.horizontalCenter; anchors.left: undefined } PropertyChanges { target: updateMessage; visible: false; opacity: 0.0; scale: 0.4 } PropertyChanges { target: updateAppMessage; visible: false; opacity: 0.0; scale: 0.4 } PropertyChanges { target: urlLinks; visible: false; opacity: 0.0; scale: 0.4 } }, State { name: "showing" when: !init AnchorChanges { target: variables; anchors.horizontalCenter: undefined; anchors.left: parent.left } AnchorChanges { target: scanButton; anchors.horizontalCenter: undefined; anchors.left: parent.left } PropertyChanges { target: updateMessage; visible: true; opacity: 1.0; scale: 1.0 } PropertyChanges { target: updateAppMessage; visible: true; opacity: 1.0; scale: 1.0 } PropertyChanges { target: urlLinks; visible: true; opacity: 1.0; scale: 1.0 } } ] transitions: Transition { from: "initing, showing, error" AnchorAnimation { duration: 200 } PropertyAnimation { property: "opacity"; duration: 200 } PropertyAnimation { property: "scale"; duration: 200 } } } sachesi-2.0.4+ds/qml/generic/Thumbs.db000066400000000000000000001660001362064257300175270ustar00rootroot00000000000000ࡱ>   !"#$%&'()*+,-./0123456789:;=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefhijklmnopqrstRoot Entryp,,.g256_7194f2c5d67084e8*!256_799cf84917f3c78f*M256_803ab6a9489a7ed7*<U  !"#$%&'()*+-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijk256_28e59fd59596bde2* 256_da900a49742b0833*, 9PNG  IHDRmmVsRGBgAMA a IDATx^՝}5X`~~DdHY?̌|Ji/޼y~}/n?իWo~Çۏ?p\_}wLJӧO___s曧^?oo?ǏKe$'_~Czzz-8Ko~믿׿S7AV9Ï7/_W_}8P y`^V/_>n/ŋj^UmT#`t=<<(qza?AEQa=Ǻ_W|6;pZHYEǏ4O"Xx"W^iyX>zj+N/. ߩMQQJ1}VpCUoIȯ_?*wBv"h̵ФQ^)\pfz_Up) m@n"=+>\t]Qdj/,wG,kjHϟ=̮I2"bV_g]pIaZ1el_Z]n{]= 9ډ ׂG.+6h# 8ﯴ֛gI5¥ f۶c>$HR7Ocvfqt笵9'ǯDEn%QOi[~-#_ɮ1k1 s^WrI'WFy;L',TB+W}Ӎ⿓#?ٗvX"tK&ClmB-5+wx;9eFfEXĻ>mW֋_j2FbT j'pϜqM 1fWZ@0^ ^.dXjKzKhXb 8U.>8 ` ! Si$CL>V`JlR-Ǧ_ Y9K^dIs2䈚!~ k*49P:nLDfǠ!+R>޿Eh=&;bSB)P=l,H0J-!m%\}=;%dLv!j+(z I"m&z}Bi$YTGo]_L|=x>+/~^,uGJxĖp|o񫷐ۻw~'LpXBOWŽmP I֖RNtׯ_ *ZMx] b+@8U6WZ ꛂS#oej}ftr,_eS]韩=NkEMۃ䳏8H[[-3\C1C8mQkmJImR}1z>,! l + ӛ~Qzha3MJ^1GaMY9k (n.!tBT8ا3sy(?#^ EV_iXy@zgڰ*_-^Yl?LJ n~gI. َ#<ƫ 5ΩS[,Nۓ.2NГnq/)]HP—wI۬=QIցSޗ'VqSWEM~ZVh[8L( 枾;ς ʩ6YJJ.JsP!KV),|ۗ-X>쉰Gu!8R_3Bϴ"qKp N7ۊ &ş`ѥշmR0;䌫硂d^Ej! bsuj b|m^ U.28 N:jl ;Yo?Op|JW" $I} 3Gc$K1ۖd.nKeшmGz)JgN N\E!hL$UW&'j#RpϘ+BN jCz[2'ђ~܎Zm.a7)yPNJa(0ID p-J&J\A0;{?}2ot%!LҀ8+z@E) 8Id*d<`^.i91Į_UHf=Onb@-@NAUtrMg\ [-6_ $66Z-&={JWҰ;SւX`/^AV `'Yo (@fm-qڱ9o;$c\RH)nfRY5ewZ J~p+b@&h΂r bTCTm.etė1c2ϐr)A}+aBt^ Ew Cٶ&JB*ي@=|x(e;%+I'4@;?,O 䙱+B8&]=PM>vըz5Iׄ׶6P  ĀqCHZM;\+o!Nz UɅ$hl|x]bS_JЁ<WZZc"VTSOQ8(w|Ԏ ._vc R) =f%f6_2eBOS_>?SVjT@(Lwlӊo9QlS_4;Ibn'?mV } q%fĩsvǗ^2G?zs {]cg1 ?>ȁתաA =g`sCeV/Xp+o;sow8k-)DĹ2x b~G xp ; ҾJmO[xګcqDvZ|NޒA"XNMXxάrAdނK t)ypg;qZJP Vځ{iKG7dKCcEX}(Pct򋳀~58? XV &+!x/vM< 0pDՆM61`.+71#2H3VbdisZSP Џ6U8AW<;nwQy_E|Tg1=c` P6A4F `]l[3xYπbߗe9ӓB@M,|ȇ!ƳU]p`;6ǁp0I1WGgXcRd$I#4<~r_7~jB[' d?͞@-R> EidK,m>L޾㠇1|[\llMzB&(BP p Bء=g{[Sd;F u"ѵ'p k\pvzZh"y -Cc_+c̲kP7dFxbܟ`ls}!.P|Q=b@^m֥ h=FVWw+ot%G`_ncUPL87mzdYO ӌ<#p#kdwh GƍeL3Sv3vema k1>a C ))F>; ڞqUsb3~(~k~5~˒Q}1F յ(,l)&F?y+*^ʪyI@X@l둙'#0Nn6{v ցP xŃkoebaUޙ}N=8qzGK{5+R;wkد)xϸNW>b_mLc>ul6 nr$ea%X&qb*` r%ho7e*%*}V4cOL~5'g[*FQLx@.sBCrpj&z? l^Qq_k8j!5N{8QvJZQ=YLv!vHԧĭ-S!~/.{ \h>LTPUo ~;FNd22nܦ\sS?E@ 9 1NJUttL98oqZ,6!XHρV\` 2T6H0'7J|J| Jϖۘml>xLo#1wȱ玙s,%GJVbVك˸EsH[o}τ֖3)sP'cw8.R R8_?Ueٚ ګFGUj>n? T7|CG;AIܭĮUbBwZ(5"_q2Wqxd RW _ER SO Z%ģya@)ĄWrZ|A8΄NatBXF)>}0]\`&"+ys@Q!Ϥ4f\l7qZ" eu`=w J`NcX2-D°VzGC㚱U__玴2baĔGwf^ )RMѥE3Xtˆ^|Ñ"w-l7FBwAI1Bg,M" v߉pAvq +ↂUe,zděɚ߫5wz9P錵L` Iqa உ}ĝVq;B\tQJ4_yZpZ$j,=Hl@k=D>Pbbpq,F P*xo> ˌX .e}&!`̵X2`T p3o=U}Nt߼Mm Yj YsD(]^o.1,p'W'!~?p{_/e=3BVFC{'jȲ soi|e"P7|ʞi.:.|(?`S2o M;#PC5P̰Ne5ʑ,S "v 8 VuS6ո/솩?'jXM#'շ@;q\ZBe%TeF@p_i7}5π-j+ӅK̶m }IYӥoƗ2@Yk!sN;x#ĸͳ E1pA߸D-h,J;X j;ӆU 7rPDm9oBfaReHdp=cLp<]vԵ1^qNEڒeqڞtqqt^~IBKrfJd,=}_U/jmׂ%@DGga@!&G%PW0PyTէౣȊ#w6j 3.N#e=  XS;4*2щc: m \kٮqǤ)ۅ^IQb%-RXa=Fgt-Pe%~ Y#"}*bKfTx5[ުCAEVA˪qp2 v ?ɹB-GP`s3Yjկ4>Thʞ·})ނɞ˘ ~T#E5c:k-LN!p08o{ӹhRn ]*Y}+)Aθz*8OE9_ 6-x-tIy/ lc?e- dvB,6mv2W9&Cbg8VK%th!Ke!k\PvWEg" ԉn,!'@  @5D5^{H'ۻO|3& )ԷF؋Gܨ# LW5h][}B&&!°=gg=4.JI|q`>TZnݢ]1c?6nvkIJXETp͂X>oRuq{ (F0w$pp:T':ܭD`1WocԥeHX89O ہT"[60Akt}$nO|&ϧZ%ss<4[<px•,t) 6'b|ɹ'OZ~-Ģo2LïLKIZLjG*5l(`rǨ~k) }SD3Æy}00nm\#x#8x9#&BVW!PFxVfOPQn;a"4˥գg̩Xv}N+k۫e.,[rG3r8;Qͦ)MM9?Hh9-:*?T5K*ՄQ HsrO8ɯ5#rʍlH1)Ƅqrr;>jog*I;z$;[A;XHYx$<}{-%̛ 6蠰;'e,zm`I%V!Tc 2Bk/"A>8r=W^)1\s4$R <};Qңfxd>ar߯^;ǗK}>o컻; $#uڠUՂKm5 1cڜ aYHw!޼ };Ut;[{ҫsp8<{TcPgH\$mHA`X ͚͍ w4pY!1JǮIN Թ"\\I532lfe$8A©o$MQ;ըN[jll 8q?3Z|*ca;72+pye@yAub[M>kcg7 cǩ&yiaQ1$y9{T5ŸiG1؃?Uk B G 9<~O$Gu"#e w)s_J:ۨ[뗇nTdHCumd1/mGOwXn-Դښ/H(zn :tɍ?zFQP Bk>{hV;Yҏ(x1~5\-EѤݷJ싱3};uԄu;_>f5iЖd:ډ;,*NZ0~9f `tUJ,`ޤ=lmdM' G_3Z[dkMfa*|+ՐKiq lNH2IeInXLtns,NcgQ.16qL.58V9߻"7~_<68c]:/00V|u<޵B2bGpC{SSmmDAMSX;Wdh{kq2 ;$[aG-FnSR==ƴG;\8x,8q cΓweI's^j0#kş;#<+N$`h~Ѕv#S8qTCke,25!RFsœ>%ReTpH bN88$榥[ 0Rz`Nal\8-#%<{z}k)ΊWFϾ=zǔ^y2I'1$JܞVr&cY_O 1n[)IǦ_Ck}Atgsެx.W+V`"W 9$?{V[Ź3nfyAW[\N[-?O79t8?jp (['ۮLȇvs:ӡ5=Ů :6H_$ ϶~an |ې*NX#k%n-qȑ`*Az67ِXܷ?ZǂY&uڡnpBd$km`~n -€1u^*v#]ݬ?\{=FVA&*qi [ٜ}'9ɵobmN`9A'ZV .O6! uv-C$}0*˛\H_͖QLK;-ȷn-O ILWZj_E#u$ҤfR=;J#P9'rs۞}f<潜Y|x0O$tZ-B̪+98=0<)%[q,0Lj%Al2x>s5c2=DYE*mc?Tq^ݙnAfoOpJ. P7#*G@gqJOVQ"@߽}yw`ORXoYo$\bG=0?R^M6oXAqDu1Asxۥ]DBâꮑguklNɍUR pGjɵx鄬[(,'{&CMFF} [Yr#8Ԋo:r;p ݊223G;J$B\'q9=k-g2/g0E܂l 9$Pi0w6 Fj?#*skGBy;V `ED`@#rU.u;TƬv9p@IRwIgfۅah~p9~?Ii3\D3>Yb8V`~%\H7@rpqN*2 "+r6>##fi7պDrՅc:\(( drc9|?aAx1V 8{Dշtumag<-sx0Qd:Zo$kQvbL$[a۟/A:mtYI\T]ř"y>H{KluZ*_]54'iBL wuJ_qac%f~S\`W3$*UU8Z%& rg𪌯.κ/]n;e@6Nr~R$7ޠԣԠBnUdٷr|Nx;XPHR8[xlNfPvQH KwSKEh8E+:!pN2Wcj+.K6Q6r3ƲmVK2eo H#vpOKS[ڤ.wh'ֺZ$ݮU"K#2_fʞS=i i:M%1ڶ- HHaU;4fDw/ \&J:垾|Ci"$D-C #SV9GVT; cR\FTt(h_G+l9&W Xvc;Y.ЛƐpF8z`Ӯ⾴2%?f0ɒc#>Vr6%ĩʄ'Eݬ m,"1܏V4\ȳ~A8ݏQ{*K+{p< ҋv@Q diM݋Yge$6xlc9qMW)-i݀Ha{·MYfmZ%`C,kI  y=AAF.^ZV+U`UVOOne)j+B^`d!$I?/bqpԞwKyk lcQmkH3D9\IR-2 (ʡp;K3׽V: >qFQ8 qǦIg-iy 3ya=H{I<"H* \^kebOڪAag8ihIAkwNvz8}O!B0}+Ѻ/-ɟK1K[7l w8szZZŵtwط9ڋG5^+i*! =IVA팏N ]uߜ~x_Ϊw}l@v%s$}FS}6aP+Ա?{~n[2(4<#lPH1SZ2p92E!xG pA8*fY1cܹOz+eqf6,$SHs\K,! ^H VdݗG$:3ӓmK[634v !Нk]]jjK{Pe眯% $q#.1ltwh84O= f2=y.#" pֶnRZ \+Mu7+>L6y'9&mkLmyܭ>u4:-} $CЎԥuv䶛΂(nT#>3I=Tsyvxӯ0݇ wws+I$NP~zʗ R7"1 :Nڙryw<6q&I$Sp9:d7)!pF<98/u"גiwNyo1 73ՋkdatD>F`XUbQI4Q>ֶѬl]Zޭ Z]BdZ,mH;H[5z96%(~}89lzsҔR˖SP-퍻*X mg9Ԛ[y#3ˌ2zv_٭e `n7t;5ygfHUIce*.m^+x-L~@rއ.8U۽BZH1ǷN~yC鉘SX^j[͒ ءg\.vLTm`]d+*iNRDثzqY̶.Z{~dR188=jԝv "#D20$u X:q5Z"ř-*[vO:5,2HT?f%kyg1[pġo0:nNGEc]$+>22@=QQތ.Կ\#I}pЩTF 1T}|euQ*AH@5Z'];[/Oj-&cjK$WFhT^&kSox98'ܜÑZs/.HС]OLtSk;Wٓh@Z# -.3X\rHSUn O#i ]Kp&ߑ&eBihagˣtGDPXЇ8%O?Fr5\M&40\*tPNΫXjpݘm/%e8stKb$$rXNVcC\?NS˜K[>H{y$ āz2y⴯.-{mm@GPGU>Zϒ] llX,8eۚe#`ygr@SOLfI"GMsKb#147+H%O=1ղm-C\,-UK:vr:UbpNJli͝pcg Ѥ^Eh)2Tu};bqZ$KoK˔,X rj֚zyh20Lw}YSVi$h^6OPFN?:tqqr,©j?_jil}=m_hlQ/SCz`xkn,T*2H!A aQu d$D-b);QqӮz펴7ub%vHQ>-pmK*xMU[[@d*7>T/l\3(WfY<|p{'tnbY [B:zfDzgL)ukյ&VE*@^NќcL~kYA,rB6~s c]53ds+ 6g8R9;xLK=>?GB8j̶pZK˂-ʿ;{+*ʷtĪMm+r19SڵMsi$c2mU8gL:Tw69B!\$'20Nge(8i~_C)j[[nhXa"B9fޟ֡.(8`C Ved%v ǮxeTHY-.y [ 8|'>Q3"B9=:լ!@V|#a$s֩i11C9<=M_GYʌ3 dl ~4^(l[Ihr?ƽ^KO)#e;NһѤKuȬ)R,HuXyqw#D Q M+i<۰#sLKjGe,̼1Ru۬nmB,'֨KIC{ OEA%]U]Y 3~jҳ EɎPyqqMI`M--zgԖ\Ӹ qOv{m7 W0Az ,fMFHYa^ǎM;+pllO?`} [7X1W?tv89Oֵo S"VUN`fNK/oAԒW62CevfЅ\`8IxNk 5hᕤJALҝs~&cB3{9'Rd;vs嫮\Iך8Keh]Py/rǧJɸQhY1qֶmH<\8ިDQ$~^=wޅ{[TkXdʐ0NO@I gɑlI̜.ҋf K !ٸ¯+I8,2Ua O/p6p>O=Xata "crpI#qMQ}Jq<3]=Ҁ|sqұԎ /NJ35?6ؑc~8P:5=#$$ƣw9==J~X{Ȉ(߻n>Q~}^A8A)+1s8uzֺg;.p~oiT^9/+9oqȵko o yaA‚?O#>GY7I#;WAg@Snbl @=~\)t+K}F\~p#Xz]t4-mqes0;u 1,+]s-=捥@ᔰ=cO>-M<{ n(i?>=+3OS$,[#'tӰ+ٴvy < fAu#\mi#9mm|&>YYs߱9[keD.cSDy=u9U{,W$n e( }]]۬+V!6H_gzJ"(uA |cqr?*m:X;=Y@ѹLMRr[>O|q`{Vqk!kya<AדHMR.G̝8+_Nx[ƃiXmRNrx&Nހކ%լ eWt40:: U|~qښcPȞnRanD "GkF0zuEDmLg^O{p.A>c7 9G4< *Ac褞rI`ukW$+=qB2IJ eDF>iTZ,/L8'@1?b=Ԧ4 KDvq23yM. "i&b~VsNl[]:$-̄}Ч^ղJt&4Zx/B^3Uc"IIJyvgYݽPT#a͈ }e<Z_0yq-@3w4䕴+N[죵#et+uK& cLFլmMŜ2&%eUGl޽:r"ZGf¶܂RnE̞B84N"i~c}+5nhS8 ª<\kNst¡c qӟÜfDA ;=3V8ukf$G3Jbi2๓}XgfsG`øiH# |Jt}*ݼvjʫ$9p͜j]^teHYs#WEd=Js:c\1P6mp:9l-+ɒHM֟G,3ۇ\p޽yjX,0e7ԀI鎢Kwk $i,/$$` =$zbżp6p߁4y qNY I^~5v[&Yޟ2ؽ+k46>~Oe7Ls~OV=CeO]o.oHTBcno1]Fz$eܐY:c[ٕIgTvHsgۭUo:YdmŒyQz:qM4[1oW;?P,d[RՔ>֎u$%򰳑|z-]dل1('x'Lt*z؇[-~RR%qR67VA٬H6Un3JS8ln` ᘜ`w##NA֢\yۄ#;rqxG-$+zXX. r7.ьz2LBu=OUè-ιܪ @`g>ɬ# 8 eIw1=O9s"QIXMG3='z{VriAk3]F `~_%5+uLhss' W^4֨4L#*8^[usa q6w̌zLӦ7Xre nc"l."9FfpZ yf;~%ŭq]NFB$,N9USW^B7zm‚K7ȫ+y>0y5kImneoX(dQ9;G''hѻy-f}r~`';sҳMG[kcUBe…#?VΜ^"we ^[\XTF۹ 00=Ȩ.hKՃPBET3ol[  G:s0. 8Y5 '=!!I d")bŭZ ıH1w# @_nwcGEw-L~Be\e3TӮVCgpT#=P{~UdGMIh-`82qߗaϵf"ɽHs.c*WwrDp}F1}(9= ARY=ċ~Jo @zKؒkE; -U&>eԹue:xfcYZ^]߳3y${V["KѺm$v{FE 4і ef8=*Ηce%a>~::Uι>lрY&/e|rR qn 2mʡ c P߼ܒBǯu[Q؆)WO9O8#*źJ*$y;Ul{>H|°˱}vlU'K8XvEvfK T@d3"0@zN՟DimFBJ `zGA3[ŘN& e':sK,^i-s,K9#bg8"7TaἵQbfdun{eRaz݁;7DQY SӜf x6fv!a; ` 9=lnwm&9I, sx\qlb~\ZqkkY·ٓ#7 M4?gH,& fӥV>fJUSP\u8S\ l/,`Bȧ!O|g$T\sc&koUI*{6#|[kYD9^,#t{ ,C \\$P}%`&Cl}sПgJ[=.C[@d;233v9pO5-LAs4qVcs ,xk#;q͐U73iiH k㘑VrzF`THrGNZՅߒ#I$1n=0{MZo24Ip]X ")9af#ӥAͺ\۠n$ppR޸n),w7OˈIcy9;ymL3}B H#w=T]֣gê3?2%G{ͽDg#nj)WOn1P >{R Վo01:*9,9R+v)i4:)nc.z694qlUҨ$ItzGtQPaf;H{t1m2߳$p2dllp*8帽t.˨G,q9!oc$ moM{ I$!G^>|u=;5{hlɒ+VgwxEE =xӞzJb 6:XE@F?Aaqpn/#+!)9,z"Emuh.#.b=:)1UM0]^Jt,Y?3t<@(1KsNK.haCl)k2I&Eqtdn EBԞrqׁ]͍6 +8!PČ%~:k<0?q5medٙɪbb 0s1 ~:rN?:t6VF`Pe3mF3חakKŞÑy$-dc9yJԵNM6G-u]I#pLa?=:uky${ya21#yJjmYgmQJıjyefLq=}2N=i 43OyI g9Ғɸʠ==c \Bk@vsӞP°( F`dzܵ=Z}5屜.OנZK{tG䲸`77v֡%K=M.-RXXG8lb|UXY@%AD1֢N}4CXodU[tJ_+"[UkhYS2l;Wz}7,г1(`2I=I9ojMYJX3m{pOZ+$o;7Pj7vhF#7eWt=AjI)..捰љ/jZ/e+[o8!I?xv9& yB⠆lø GZ䜹KWRS^HDo) 0qU;mV'!y5u =ӥ>h(R `3*}r{V,NůYeUVKn9Rܻ$`M;@p͑b9|zܶk#!-?:t)7V77px9vCVaMu[ۃR6[w7nz6j2 3?twMr4IBz(^$I>ң%Ķv[3$7$)oqk:$ W(瓌sPɳPO:AԊui4MڼZK#i3r1WCgq5RdirsߦqLuE}:]Dm 1uv8$sW-[X#Mc*" tcT.pbyGTp9@$kPuD j9fܼ<66vFźyMngo9\VxpĶۀmf;GnIb:)mm@ӷӊsDן&W8@+}b#t)m NPfu%y~ۮ.fXҐۇR-`Z+ 62$2-Ol'=kSFգ{&u HNcmX n,浍!e%Y9W<#-*K 9bTm@̱DZV4zY]j$ј-r>V=G 2Ɔ8A8obyӹMH8HޖJdrrGZX_Gcv6yز}8f̹J&$xaGg̕J>u{(m&7IIg֞}\E-BɶcxlzU,n! 5."B}Gǹe⬍kK6o/n S݉ cI$1Mekٲ0G~p ϯ^21Yogu 3k~<ڭkn`L;&y ?;s褔uI5,ldH,"Uz4 B~v=ny r@Yd`]{oVi@\I _543B_*!cNh=:iܓJm A#y{W*;|7cS]VxGZ?*_}訮'W~o2YZ@T4ơw \{i##JKc& nXMDg3,Iɭ;KX巑qݲ:v䚵5YvCiv9ymzwƐ П+%pзcD{~E7~^z+5ofx裞=sYxC92 P5bYGkhbàrį ?.hn kd5ŘmןJTUȽԱ9#(C(ځ8cAn$xP٠lkI8GNJE#4lZU`>$:m$z\vO.\>x`G`Z=Z1cfY]̣gNҠ˷gLG㙘;{}& ?~SO\HFpsyX0MyD bDc˂zϯ0KLRUۈ$`JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?]^~~Jv଀W7m]|M]|7"ntw-`I=Tv8[8Jlmi6o#Gw%dWv+1ݕH TIoVN#VlEڌrOOz<1'DdLu&YaZ|hk6?KEV#ʴ [P՞~*xN5'׭/ -$vX=Áɭr yw&>Hv5kERJ^9Q5׿־wWs]ٴ7CU: [m7ĖJrkoq\"ă^1, ?J-7A]Jj;#T-hV-̀ #|}3I,{<=˃ŮN^ڄVr@B8$?cW ~|;w/ X w$*ŸGNj2q{=WTՠ`&]z?s+KyϺ(,>S ⯴e{!hguLFa@ 犱/!QOA6¦w:ΟG[jʽxW + v~: IUC3\W&i53wC ÐedVǀ3uqOF:_8^JU#YA) Rw_ZL6"(Qp}u;K9OsopgOh3];"PNqӚđdՊB.dn޻\ ̎ަgԖ)B ɟ{Sx:.^"&GrXn}~U2d1Oa\؊nʤw_Վ*s^5xtA62"Fl=ĝT~{5ܑ :љ~RʅH$d`QO ،ʠw{9 8R|ș`MrѢÝq\ub*e[~E$yv-:4y@O -_ie[BGl7p>X#nMo%xM5ۨuo8#K8#SluZI*q~lNTbMY{moᶇuw5ݛMcp>_)mY*I<ʽX($7]ںc5ISrw9} T9$o{UHf6&X{œG:A:qk565 UkB񧅵uPpa~dXcXb~)r=MWV|IZ|eio"QDKL|){:dIWͽI43&VU#0 sVؐELSPMĩ8XkZ~mG"d㿊(oyAڤ1Ywg!~+ZMCMCH` ϩlxWWT$ msk.%TxjNk?_Aœu*'ZL7bp}~i#m0Fi+.fTa8P3=IH͍ίyhjTa EqqV,{7P%O_m4! qDP T֫4_IeY>LcqRi1,kk.S 2Wz5_Ѯr`yF:Gzٓ3h7OӦ se'̋9a׾=Ϛkڗw}m8u=;]p"?EyC籁M>IT(۹=<÷|IZ5X^^I13bk-$OcB1WKNRTQwEWP Up>^?!f| Ǣ*F;t#XךjTWwgo">g<;; 7S{|ҫۘ庚y$S(?vFbH&cZ _"H>60SלVKwLA6L`1Q۰&G|[X^*me=KïldO":`$w*|c6{3^K:i>%'*Or{{@zH:m8u=qzzT}xc/k]WbΒE2SD);w i>%ҭ,//Q$Pq{෌=aǫԧkhrunԢ:mo[žw@xQek*&#$sFEںk|=O^xC)mHr;UX9nD8E>qI$)NRmǴB.mFh]a ::'I%Ƙm2y}ݓoqץH1./։}w/x~?<(rY:anUcC N]fzqְM2PkkDѶC)Cmu :rq(!}Cso0HI_N 6$ZVɿN}θ(N ` ZkI|G3QZU# |8#z;`TXp>7<%P]'[1 ^M[{vǠzyN*E8{0RD$ԳªP}Y6Ă8Z܍vy]jǗs9\'ī߶/vjw+7٬QbBB_|+յ-7GubȰpǮzpw&߁mڗ[_alouԏ*5)Mp'%CC:eG;U@\鐓sMw+g\?Mt뚖D~(.{Z|Go׼9}M^XuW #\/jM:~F~uJ/`cz!WzW|yf׉Zx.fA(V[/(X0^ZXϧܛ|˗$CQ$ <@*mKᯀ/@(MUh'=ByJBwV J).]W|S~ Snmt]-'Zdݧj u9hg^PF@x禯_K? y mYJZhnc}qe{`w? @%WI? ?"zqJ*E8z0&%#+#ČZxUU dF" ޵c2HץyŨBxWs1 GށkO;!?oOڭQ)ÊC]Zcst4O7 -﵋hݬmeB`s2}q] d#,/G9< x!P.27{"zɻS蠣Ֆ!V/º^75[iaPЬ,'y3aGҷU\zGh֡myC9'{<')7e+͠zsk^9OenA$`;=jÝNxti|)ĐjeDVE?tZd|͜}/ǖH 2/jsqcv.Oe5SҝOStuӊyG$)⺫ d6T5)6YXut9RAi "rbZl"/ܤup s5|A{Y&82Qb@8/#83sw`z% vWPԀ=>r:d rbOj`SNymI~xg#7IRujERg'9]Tz~!Z71A]K?ATTKY.>}4_-i_5p~ };E0J%<q$g/Xx6ŌbhR[cDZ";m#kdr<M^ZT'g>p>K-Sx G\>j] xrUOQMH1PĻWLezbPxh6Ok@6REܲ09c쾸2J5EO7c +?;{T^MڜwgMެ?iҴ|gK `a=y6G o\y43pO"A${sɧOF2F3@u;tY<+İj+ar0?u*6|͜k+ǖH-2/jņs#RSU:u4OS' uE&nffOc\~s2+O +F=GwI6Zv9z_.o/q˄S`3;JWwto)kWYvod^T] =8?hI{s= 1G^Z*IߡZ\"> L) $j*=B==+8n,#T_2}sM񭆁\ӼW!G64=ا̲pmF_x:g "x>|0C Gv*:Mr[;4ߴ潒K,MkzX &] e]9=N~km{ o|rp3\дGtV+8V(c ~$'Efhf>xN\dŮfoßUugso1H Fr8q 4M3il?A[\yXm^d{יC_D}m4šxmKm@( 7i2zQTjN(iYl2mhB`qQjѢT=z}*L "j6wHam-ȹEW-UTHe'WѾuL׈I.O݃wԞ,}޹quU:MvY/w6_3Su9ҮV3n1\ڬZ.5ZcP\&w |oO!F\wXk{{*|YaW)HNq|KO_ j#G_}m| :I.NYw+tm775۬cM#rn>jM48aW۟C@M_i/t0]{abv¯4R,U} L ,Q|g5eC=Cʚ齎.[ Ư>|kakta M5[U?R9*jWt߃d "x>|0C 0wb\\%Νo|Mgu;MA (. Uu}s]Sk>೓VXMBYYıE>'I|/YfV$[XveaWep ^g #k-$ހJM(Qޛzu>Ehֶ]M6;jQ\[̛4tmE?7 .b}́5!r^I#5ޭ"GKv.PD=1U*QҼ4oS&)RKSݫ,}..IPu$K }RXž uCt0A1]KmR`uu狭\3|5KL9ti46,{WǞ,|HJRT認-.q1ּBy{8d{ech?f5a\=hs{D8:7L:5+Fvg^8aGb*O@Ovp?7ú\?`i9f't1-#ԖbO=;VwE`!u22wVUqM}LgZ勷O<xOQMVQb/ޔ) Rw`k WaVwWQֵۉsǵP'?UvUͤPFz|V{tpˌ|%i@\\81WZr^.;5Ӎ-A9Ykr/OQ`d|A]G-JљOvׇ[R>Qq8$_u`UX@yjx}kKDЬ;ltͧ8\@x<`&nN KڮS:]΍F}SDO+ۼ:)p{+讻V]R5i7JujL~3I%ⴉCv>ğ`@]PR`{b%ZMoԫ.T_x+O(]M%J3$?6:{dԖ [0Q{^Cs%ՌׅP %Ϧ=~S(pxǭsڤVb]/5ҜsqNxv2[ nr0Hں$szX lFH5 6k(P%ƛsm~<bk^f^Ȯ_Ǿ&_ l6>Xhv|e2gNI,Qu8$_u`UhJ@r|Qhz|;W6:f x?k _*t8?C\$k5MM>kLghpy޴aڲꑮ~o;IPGˮVg=7zO@H熙ۗd &;cӦBMJO~-t xn l %`~ͺmNJ|E^DrIo`EcaVT,!@k ޼|:wd3~xZN\Мtag š5PbT.vL̿( q~;k-ɂ-fFyںE;ޖ;HI4 @^CY**Nӕ' 'uOz/?g淋Kȷ)g||r5 n+4χBì@qr8acQzT޻>3ҵ,smoojדmjqӟeKiweV%Psz1l`c=MeX)^Y<}^#]Åju/iۉI[=.ؑ8O#oT\Xt1ۂZ.'3~O]ZaƾMS,lΣ'u;WS|xB'o-l/<Zuׁ4F>uгIMÞY˞tm;Ywntq6/ȷmeJ5e:X?$$gm1 5qN%H8Y[#6395ұEsAU~bOp/v`ë7eszWS5ӄV'Ǿ+¾VtEr?XՊ|ћr[Vʱ0o[*p5b>g,dv#_-}xޣp[2@%.VUtJGϗ?M*uWK:pQo}]R"GyfoxĪ&+'9R"{q_wkl1oO'+5| n.+4χDÓ8C\}܎0~1 M볾#ҵ,m#ڵs\|dV5T^<:?-`Gj=MeFnx91a{a!z?kcEӴYI[=.ؑ8O#oTqb |n Sh¿?<iyvƾMS$lν\z7^:do-Ћ<:&E]xMm] 87yg.z gݺ~фw".meN4,?;j @,EE3ɋbT 9'5O'/X6OpFXՊ|Rܭkǖ0ܫ6%N=]FG|ҭG\=Bጷ7>dM3] '*_:\M=4׊WK*L;Qok|:Odvt>FCc3I{[}nZ/1װS&ty&/4r;rd> $A,0>H$*. *EtL+"4{RX%ף+AQIm$*q=kMKNҢm$1 -ĒQKmrJk,h?]'}aU Wt)>Z#ӣkv)@8co<_/<&eӴ+v(kaៈ5 +Glucnx)yyE>ikk V-d\C*UU+(5kjvBu"}4v/ j2ծb"h89QW1m-,uq=I?Ľ6N ɫhyb kZJ'^ 6w_xO5?k&1 1XtluU$W?k K@.,oY$F5Q$7VҌ}G-^Oyo!Hp?+ht/K/K6٪ij/sl%6EoEbY-JC^JnmNd㯢9i|sard>'Ȏ_gܩAjĨz[oS *Et~+P༲%hXz2# %,c\o<mZ~ y!olm&$]Km~,F~Y4vLP>Ex?t)Z#4`܎ 񏋥D v~Y[xW =?ᗈ5 /Ֆ+#l'k|Se⏆Z -d^sUTխNu"}.Ý3{Ye(65PUs9hWƵԞMc|Kn-4O0&nX"?5܀s^95}CsvCp _xIsEfq[gO_@VtluUE-(܋_OԼ?4sB.H b;k /H$WVҌu$Id6Iwcs\HY~1g@jX״V|_Zad!\=O/'F[YK0Z_|K  s,Has8]rthmmty"Kd'+ZBQRm'yi53,TT6f2Rz1 s!\ج>Rx< A[v=knאFCg9=zXWpS M]t_֢4vlTdU>,c]W9WAo[oU #UA]] 46R<~F苌ډ=p?گv957+c$7d6&YvXP;Gx_QFnΛEp"#'BBZލSkǼ7࿍?4Fwd"k2(脪:ۂQgY] &NQ5Hø.i-Ǣ.&$&//~OpvZ0;XգSkWCq޹qQ|x9G fNE soj.NI\y/t!sK=Ns0n@9Kդx"xI'L橁tw\(ҳ>x9n|O6weYh,ݼ_j_? _x,qjh3[c'^}2ö[M[t6Χ!rs{´WCZt*a>.Eei KبFE^ D+k{`VDuʲzo)$~Wf?D\]GNkho $UHoRm.LʆNSםqU#O[/7F_ M֢ `1z=>e['5_;Ա@Iw5ə}%Tu-~;G]XKyV1OЕd2ku ;ۭjhdG!y.qk|' ^8?-؅۷@ Z;8yd6bLa?J[:6= !v>}Ó{Ĥ(5NK_RI)ʭދ;K#7N1!S^$`܏QZwW>8Xդx!өx4L橁tѧ“*)+"0+:27zKdkSxvY͘=6#'O%B}+xJ@h31C@ّ>R=vֹ+BJJz~G^qqtefZ8'hɭuڡ1^iP5ݴzİyarvXGr8$WE0 !bdge{CaT*|`4_]j nkg~CLČ DzW|-𥗃 vN:G\>f$:Y-}:N?i%f_:>bF:$]N4L>IhE(##W+_>RS:GB7}GOSU[a޹/tNN];,AcV:ՅTHZiі&eU`9`u7jӵ Ky|ԏIk2qZqmSj[kb)415hδHP8?u~[w%7#NbDѭK CTi(du-GS@@A5/CP'/~MS[n` nZո77kj< a*}j2gò^HcԣyRC|z֭uVHithC^¶^&:\>f'R>H}*n?i%~`HR*GӚs[$E UU#x`>|VO kW1{?^z!#XgKmSG?ze۰Q = jªqjwFm4Ki~c⺉UX5<1 Iy>'oPޝ\Ū[O4+zM̈pYֶ |ڦ9oGѮ^WR<gu<OS\_"Fa +\y=mK&k__Pjom$CLC+рu< Sm~Ծֺ^ui瓗=e9*TvkSudsuGB>-go $}*ǩEnr=+y [N%JNk挄܌OEG~m? _xZJkB15z>uնXxWga<(:NWnɢԵ'XyQ $'5ň7apwSe=Z-|=c#OW=YݎII# Z#X)=QYtn./'` 1(5k:sOฉ`o-֥^Mg;po4S(0k]sW;oA.-<v&F}dA@/げ+}KڧNObTl9c&Vz̓Aw V9t#⯦2xcּkFJ.ݛɯEytW/R$e^M9݂~TĞmn5;f,!yq:تIzk_5۩xf67?gS|/{%N??CxKuim";{h(F֭G4kl\dRskTV<97vVzw15An tl Iy(#mޞr]GGtFڙh"O.f#k9}T޳MQI:S_vGZEOO\[e)1W!Y.u1{%iowo+Zڡϑq+"7G+35#8r{ S3Uh\g}k&o~%VЂ&oAr:WQ[u׈5 6v<ƒr@Kƣx[1jZklF<{\."|C~ l; h~Ggc1VvbI?ZIefp~l1h̤Eg}tnn.`tcQj"uw7om5[ U\؁\? Rg\yGcyFzC򎹮vv'=s{xfݥ s܁^$W-h9)߆N~s6ܞ =3OSJԭ5K4{Xrִ2cNucּzJ.ݛɯEwt_}ũxj@~~SĞmn5;j;+H^y\\);8xk>jZmv^$X]4S@_&Y*QŪu,4]6!KH?T`*Ѯ3c o`d`R %Mz*JǂIܷggsʑ塷M2J^gҀ?-ޞsGs]$ >QmޢMRd#g$Ius:Cmm,HXFY0@Z}A0XQL'wlnn%hcp888-hcTG>gƗkJ%7][IF5L]fܹeÿhT>pz؆A 'A'u^HZM1VM6fs1ZXVEA ~^kCV+cy5 ;^C7V|sʍï*@YԊH47'v}CTkHLsLФ-uе 7:g[G PjQǵc ǀkRRWGec *|.䵺z?u8eU@eM?fMjb͝whqTi*rw}I2 f'T)aitɉv߄z?|!M=n蛛IZ&`16Ś/| 41x#ÿ3HkgKomm{J=\]fܹmÿRouqpInWoLUMON=Y9zףGB%XÀ玝yc/o A7^õ1 ulq\:jW19T])أܵNѩv).m]|=hzV #9}A PjQڦՌ)<F3^dɔ\[Rܻ4g G-{⅐}6?y;Œ޶4'1ŏBוago8 Snv6y#= Ny|1՝g7OxjW+j7-<D1'qڭk2yz~ K^ Z Pq+Ӡ[И,Gd }:ѡU{qUe/uPܦ[㑓'Vg#4 y/)ߑ@iɽ`&, lKOEUVe&W5Vqz?~54NqbmhXJd,8ɨn#i$4մx3 ` :eպ`FA]4pQIxNI-|៊>6Ɔ<{mĪ%@_pBN I+x·^ Hk29b=Cû@r1^e,en[mj(o7iW"TUcXN{Sߎ3Tfa3.= EjoYʗ,2?ZB2k)5tẙ*@^Ljl|7_xTCЙn=I$(ō]Ӛ6[zE;gl}0SyVcN Օ~xXZ]iu .w.oZ#ev8#o?5G=q-]..[80B)\beZ:7i{b,r&#9?FkM'I.[_麋yZu8Uq=9/,WIQ\gFk>)hWUE2Z>=CڳJ1X7ՔԨˮ5{0rI{/tȒ :~iֺmX%fAHZ˾Ĉ @=#tkVqՋю d拉mUfmr8# 侚V_jy(]}/-}qm YMK;1g0[g˽|=k voF Kg(C^[JحZ>R )?)8 6|2L|p4r[!؅0P=1LtK6dq]4QJM6rܿu/ |Q񷄮t1*KmF Y%C,l+x·^ &I̥XP۔xw}ӢjVw^|`c-iu"kQGо1=+Ea*fwajj-^ Q [̻#ZvaqoqVq]C*\RmR$ :W(跿W|VEZ9뎜!jν8cRSN_'nv}3^Kk/8Gq"J0q{]Zv6څ[[XdƬ>Yn$C!~GNEtG]uAb:lGSsɨ|{<&%FIﵩ^.vCc"EVn _tԴnV= J][Pkv-C)94Q^ L.)m2yRO!$d/?O 7բ8ƹ>VTh9{2/,V{R pHu# u8MC|Q7"_kRRH0(P{E*Qĥ{Rsл%պy%n_b\EzQj7w"TxkTuƎ-.Aq;(%灰(lB)\6?9ek^&/5zʑ~p߅z2Y|t:[n~3aw.o63vqlq+Gi4vѮ}AR^U(f?|Ebˈ:4/`mYHoXE+#̃wbG`*ocveHpY1'EZn:1/ibF'-BAy_~(Ԟo+ qr#:}(j|⫮y{?? `ʞ!ICPNG  IHDR((msRGBgAMA a hIDATXGXyTkJ%\c0*8,1WeE݈,qɖki6Ҧ[5-[{-9w??g#..ݻÇH֭[N: ++I&+S"eumL1hРgϞw(++/_4i"7o.lmm9%c(Zh=mڴ PP/7o(--u!o߼ySzJ$ܭ[7s |}}Qgl| JO`ll,w.=4u"'6lׯ_+BjZUTT)z@;>}T{NiqOb''"CJs5J0((hǏod[FOORmpd֯_{uMMM5/^Sw:5h/000^6FK.VZq EǎCm)C9sx`8h4a6 QM$:+'''!ĶR,~>|uOOzcĉ@,\P+P7ݵkWٳgUI+ՀS-x`^nbb-[ccc egvtt,3nR6/I0 #W'OpࢆiӦ.ʆ/v$ @b]dj7l-&Tm(~8nܸzL N:t^U>BVQVt Nrvv...yJ֭[_2p/!D ۳gϚ+CvZTVVM͚5sS#Vhm&пd;wڵZ ;w\I"<1:~8g&l<ŋGcʕ+axHQ޽{j b`?i&W^J 1AРbIɏ $tҳgOŅ,8s@6LGp S(TbJ=x1xp!/رcAJm UưۋyQMR=ʬ| ҙ xb<Əv;yd6,`SOG8Cq l$RJ[:DGC\Qn߽{h9PA*pa t0a"l#=z[n㐊"))I>|X%} <6}! m߼y# m$'-ANt#,#c؎*DLBDFF Lo >9G!glCT(C!Y-And*x E5ߑq| CrMT03f ,fjW*>.%H W||L@@2AS**| }!l$˨E[tFhn+ A$9i mVVǺXd% e7333TWΝ<5/>>>\,궤BBeDse|`^a{QM{C,z̙G圏E ؇%~ ш] bj&ΰ]@~jQQT,-INt9j*oH @݋F-bsssի |Īa8)0bVRRbCa*`B9w:aҥK^.Tv2ر8Q& 胱A3 Uʠ_dhOې*QTm MOO]֭[w4p ==!HT33($>\t0gb΁tmQ%4QBb%@3~eqqq&1b#1t2`d=R_G3ⷁޗ H6,64ӑ* (;s}*J*pbb?ʗ:]Wr,`SSS;%nggJ(jSNe!XCUu8dxi2ҝt/pT#"DulI@m"הּ+T6nnnʐYLC=!!!Q"QBJT})[»}o>>111SǍ=$u UD4&tPM*2 5˜!j>ԆRF j(S,\AիOFTbC5$X~!3 $ݛ ȕ |rgeHseWL%`2/,,l4@e˖H^H́$b{lm c}ncٸ| Գ;]ԉ 1b-[~7@̐@K, ?$1AC [E&xd1{0 ,_ buTT6Ԅ:KPk֬Gs댌 Jv-B?3Uz JV6 .)pB_AkgŦMP # B#P҉*Ν[;! {qeGsIENDB`~H JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?zR|QJ?vzRcր݆}rۇ+ҐoݟΌ gvN|g}ݟ΀-r(2?vzP?vzwaZ_pzR~0@7ay^~WހZF/ͼr'ͅ:==i1r6 |߻4P7m^S'͇}֐٠|JO 2i@JLp?vz۰ܯ.[pzRс~nփ``~巎W6^JGZ?+KnJB8o=(/+փA߻W8Z\|g&8_ݞewn;g4xzR \2%~o7.WwA݆}W܌/ojQjh;]Ni\/i6ZBFoj2nҝ~VOZ`+Z oR^Sv+~RnxzR +ր|K?+KnJnW9|N)֐ewn;g4xzR \2%~o7.WwA݆}W܌/ojQjh;]Ni\/i6ZBFoj2nҝ~VOZ`+Z oR^Sv+~RnxzR +րw޴ /_Z7.&WjR8oݟΌ|g!+u+ctgv~p?xzYp;{PVGFWwH ~#?Zv>o}i>Թ]p?vzG 4\5>՞c֌sH ~@ G ֗0M%p;{Rsvzg~Jv`nW֐F|@ G я~%p߼nRwn``~74Z .oj\|ր8=hX)\5p'N4|ڗ+ctZw+Ơ|ڀ7֌p?vzѕi\6h6vzяY@/Z?+K9^p߻?.g ݅zwa_z1 @ JOjh?vzzJ7Ji4|g ݅zwa_z_ݞavh~mÕ()ցJLp?vzKa_z_psB/ݞv u_zWgp߻?/ͼr ݵy^cVzz րW!7KY@7ay^~Wތp=h~+ғڼZ1ݞ0=h;2ҍ+Қz?;0@7ay^~Wހgoݚ_ps>}u}ғݞew=)2;u +ҐnڼZBW .Ww҃ۇ+Ҁ9|ڔnh;2ew=9ۭ;8ZBW j\7Nh;/ϸr(> 7'ZCews!\5;9^vN.W?wa_z\_ \7}6!݇ހ &WnpzR W@JvXzPwa_z_pzPr;g4oR^Wv_zL4;u|v@JsKvW4u|ڜ7aFHwa_zLd=9+Ƨ|+Ґnր>a֌ ֌ғ+^hHoݚ1ݞKsۥ /_Zvi2_7oj\ gp=}i27J\~pHH|ڗ+Pgp߻? +Z oOZ@Z]&W@7Nor7J00?vzf+ڀ~j+ct|ךRf|g!+{R\@ ւ8oݚL+ڀp@/_ZLҀWnp߻h?vzR09cccA7H |ւFojv֐րWwIPÕMZ\ g *a_z_pzR>oݟΌ PWHwa_zvzG _xsݞ /ZSJB>.րnڼZ?+@gp߻46)֗pH@݇}6awF>og(ݵy^vg 7v^W.Ґvz۰/͸r)7F}F;p=h#/ͼrvzGOZ@g)݇}W!{fp@7m^Svc ւ8oݚ_xzR|WK~`~X(|֜72vz@Yp߼<+ctm}~}Õ@ p;PJ߼=x|G+׊O&֝JAjր|Kwg=9a_z_w\uW jQ ˎn]+Zpݼr)>l/+ր~񏧽.ۥw//͸r4᠕~S/ZC/&WwFWnp8+׊Bˆ]+nKJh+ڂW;9^R|~W7.onxzR W^oj]˻>a ܮ4oR^Wv\wr84\ zӆIay^?vzzv_ݞQ@{f0Eavzz7PY@ݞQ@!0j(_ZF袀0H ֊(=gwҊ(00?vzG 4Q@ Cg'eEviqݞQ@ g~E՞E1~oݟΊ(qݞp=h?sachesi-2.0.4+ds/qml/generic/Title.qml000066400000000000000000000103511362064257300175470ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Window 2.1 import Qt.labs.settings 1.0 import "UI" 1.0 ApplicationWindow { id: window title: "Sachesi " + version width: 820 height: 680 minimumHeight: 540 minimumWidth: 640 function isNewer(newV, oldV){ var result=false; if(typeof newV !== 'object'){ newV=newV.toString().split('.'); } if(typeof oldV !== 'object'){ oldV=oldV.toString().split('.'); } for(var i=0; i<(Math.max(newV.length, oldV.length)); i++){ if(newV[i] == undefined){ newV[i]=0; } if(oldV[i] == undefined){ oldV[i]=0; } if(Number(newV[i])>Number(oldV[i])){ result=true; break; } if(newV[i] != oldV[i]){ break; } } return(result); } Component.onCompleted: { var http = new XMLHttpRequest() var url = "https://raw.githubusercontent.com/xsacha/Sachesi/master/Sachesi.pro"; http.open("GET", url, true); http.send(null) http.onreadystatechange = function() { if(http.readyState == 4 && http.status == 200) { var array = http.responseText.split('\n'); array = array.filter(function(e){return e}); for (var i = 1; i < 10; i++) { if (array[i].substring(0, 7) === "VERSION") { var newVer = array[i].split(' ').pop() if (isNewer(newVer, version)) { console.log("NEW VERSION FOUND. DO STUFF HERE") } break; } } } } } Settings { id: settings property alias x: window.x property alias y: window.y property alias width: window.width property alias height: window.height property url installFolder property url backupFolder property bool advanced: false property bool nativelang: true } Label { visible: !mobile id: title font.pointSize: 18 text: "SACHESI" font.letterSpacing: (parent.width / 2) / text.length font.weight: Font.DemiBold smooth: true anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenterOffset: font.letterSpacing / 2 } Button { anchors { horizontalCenter: parent.horizontalCenter; top: parent.top; topMargin: 1 } height: title.height + 7; width: height checkable: true checked: settings.advanced onClicked: settings.advanced = !settings.advanced text: checked ? "+H+" : "H" tooltip: qsTr("Advanced") + translator.lang } ComboBox { model: [ Qt.locale().nativeLanguageName, "English" ] anchors { right: parent.right; top: parent.top; topMargin: 1 } visible: translator.exists currentIndex: settings.nativelang ? 0 : 1 onCurrentIndexChanged: { if (currentIndex === 0) { translator.load(); settings.nativelang = true; } else { translator.remove(); settings.nativelang = false; } } } TabView { id: titleRow width: parent.width // Qt5.2 bug requires timer to redraw layout correctly Timer { interval: 10 // Any number works running: true onTriggered: titleRow.currentIndex = 4 } anchors {top: title.bottom; bottom: parent.bottom } Tab { title: qsTr("Device") + translator.lang Device { anchors.fill: parent } } Tab { title: qsTr("Extract") + translator.lang Extract { anchors.fill: parent } } Tab { title: qsTr("Search") + translator.lang Search { anchors.fill: parent } } Tab { title: qsTr("Backup") + translator.lang Backup { anchors.fill: parent } } Tab { title: qsTr("Install") + translator.lang Installer { anchors.fill: parent } } USBConnect { anchors.fill: parent } } } sachesi-2.0.4+ds/qml/generic/UI/000077500000000000000000000000001362064257300162705ustar00rootroot00000000000000sachesi-2.0.4+ds/qml/generic/UI/CircleProgress.qml000066400000000000000000000102011362064257300217230ustar00rootroot00000000000000import QtQuick 2.0 import QtQml 2.1 Canvas { id: canvas //width: 240 //height: 240 property color primaryColor: "darkorange" property color secondaryColor: "steelblue" property real centerWidth: width / 2 property real centerHeight: height / 2 property real radius: Math.min(canvas.width - 8, canvas.height - 8) / 2 property real minimumValue: 0 property real maximumValue: 100 property real currentValue: 0 // To show on click property real overallValue: -1 property real viewValue: (overallValue > -1 && mouseArea.pressed) ? overallValue : currentValue property real curId: 0 property real maxId: 0 // this is the angle that splits the circle in two arcs // first arc is drawn from 0 radians to angle radians // second arc is angle radians to 2*PI radians property real angle: (viewValue - minimumValue) / (maximumValue - minimumValue) * 2 * Math.PI // we want both circle to start / end at 12 o'clock // without this offset we would start / end at 9 o'clock property real angleOffset: -Math.PI / 2 property string text: "" property string statusText: "" signal clicked() onPrimaryColorChanged: requestPaint() onSecondaryColorChanged: requestPaint() onMinimumValueChanged: requestPaint() onMaximumValueChanged: requestPaint() onHeightChanged: requestPaint(); // Keeps turning off onViewValueChanged: { requestPaint(); antialiasing = true; } onPaint: { var ctx = getContext("2d"); ctx.save(); ctx.clearRect(0, 0, canvas.width, canvas.height); // fills the mouse area when pressed // the fill color is a lighter version of the // secondary color if (mouseArea.pressed) { ctx.beginPath(); ctx.lineWidth = 1; ctx.fillStyle = Qt.lighter(canvas.primaryColor, 1.25); ctx.arc(canvas.centerWidth, canvas.centerHeight, canvas.radius, 0, 2*Math.PI); ctx.fill(); } // First, thinner arc // From angle to 2*PI ctx.beginPath(); ctx.lineWidth = 3; ctx.strokeStyle = primaryColor; ctx.arc(canvas.centerWidth, canvas.centerHeight, canvas.radius, angleOffset + canvas.angle, angleOffset + 2*Math.PI); ctx.stroke(); // Second, thicker arc // From 0 to angle ctx.beginPath(); ctx.lineWidth = 8; ctx.strokeStyle = canvas.secondaryColor; ctx.arc(canvas.centerWidth, canvas.centerHeight, canvas.radius, canvas.angleOffset, canvas.angleOffset + canvas.angle); ctx.stroke(); ctx.restore(); } Column { anchors.centerIn: parent spacing: height / 6 add: Transition { NumberAnimation { duration: 100 properties: "y" easing.type: Easing.OutBounce } } Text { text: canvas.statusText font.bold: true anchors.horizontalCenter: parent.horizontalCenter } Text { text: canvas.text.substring(0, 35) //color: canvas.primaryColor anchors.horizontalCenter: parent.horizontalCenter } // Show count if we are looking at individual percents Text { visible: !mouseArea.pressed && maxId > 0; text: qsTr("%1 of %2").arg(curId).arg(maxId) + translator.lang anchors.horizontalCenter: parent.horizontalCenter } Text { text: viewValue + "%" font.bold: true scale: mouseArea.pressed ? 2.0 : 1.0 Behavior on scale { NumberAnimation { duration: 100 } } color: canvas.secondaryColor anchors.horizontalCenter: parent.horizontalCenter } } MouseArea { id: mouseArea anchors.fill: parent onClicked: canvas.clicked(); onPressedChanged: canvas.requestPaint() } } sachesi-2.0.4+ds/qml/generic/UI/TextCouple.qml000066400000000000000000000034561362064257300211070ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 Item { id: itemroot property string type property alias value: textValue.text property alias subtext: subtextValue.text property alias thisid: textValue property alias textColor: textValue.textColor property alias restrictions: textValue.inputMethodHints property alias maxLength: textValue.maximumLength property string helpLink signal clicked(); // Evil: hardcoded width/height height: (16.5) * 1.5 width: (16.5) * 15 ColumnLayout { RowLayout { id: topRow Layout.fillWidth: true Label { text: type font.bold: true Rectangle { visible: helpLink.length > 0 color: "#AAAAAA" anchors { left: parent.right; leftMargin: 5 } width: childrenRect.width height: childrenRect.height radius: 4 Label { text: "?" font.bold: true font.pointSize: 12 MouseArea { anchors.fill: parent onClicked: Qt.openUrlExternally(helpLink); } } } } TextField { id: textValue anchors {left: parent.left; leftMargin: itemroot.width / 2 - 10} text: value onAccepted: itemroot.clicked() } } Label { id: subtextValue anchors {left: parent.left; leftMargin: 10; top: parent.top; topMargin: topRow.implicitHeight - 2 } text: "" } } } sachesi-2.0.4+ds/qml/generic/UI/TextCoupleSelect.qml000066400000000000000000000020441362064257300222370ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 Item { id: comboBox property string type property string initialText property alias text: comboButton.currentText property alias subtext: subtextValue.text property alias listModel: comboButton.model property alias selectedItem: comboButton.currentIndex // Evil: hardcoded width/height height: (16.5) * 1.5 width: (16.5) * 15 ColumnLayout { RowLayout { id: topRow Layout.fillWidth: true Label { id: typeText text: type font.bold: true } ComboBox { id: comboButton currentIndex: selectedItem anchors { left: parent.left; leftMargin: comboBox.width / 2 - 10} } } Label { id: subtextValue anchors {left: parent.left; leftMargin: 10; top: parent.top; topMargin: topRow.implicitHeight - 2} text: "" } } } sachesi-2.0.4+ds/qml/generic/UI/qmldir000066400000000000000000000002011362064257300174740ustar00rootroot00000000000000Config 1.0 Config.qml TextCouple 1.0 TextCouple.qml TextCoupleSelect 1.0 TextCoupleSelect.qml TitleObject 1.0 TitleObject.qml sachesi-2.0.4+ds/qml/generic/USBConnect.qml000066400000000000000000000100271362064257300204310ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 Item { id:main visible: titleRow.currentIndex > 2 && (i.device === null || i.loginBlock || i.wrongPass || !i.completed) anchors.fill: parent ColumnLayout { id: develText anchors.centerIn: parent height: parent.height / 2 Label { Layout.alignment: Qt.AlignTop | Qt.AlignHCenter text: qsTr("These tools require a USB connection") + translator.lang } RowLayout { Layout.alignment: Qt.AlignHCenter ColumnLayout { Label { text: qsTr("Password:") + translator.lang } Label { id: subtextValue visible: i.wrongPass text: qsTr("Incorrect") + translator.lang color: "red" } } TextField { id: passText property bool showing: false property string pass_init: i.password text: pass_init; Timer { id: try_again; interval: 100; onTriggered: i.wrongPass = false } onTextChanged: { if (i.password !== text) { i.password = text if (i.wrongPass) try_again.restart() } } echoMode: showing ? TextInput.Normal : TextInput.Password } Button { tooltip: passText.showing ? qsTr("Hide Password") : qsTr("Show Password") + translator.lang anchors { left: passText.right; leftMargin: 10; verticalCenter: passText.verticalCenter } iconSource: "showpass.png" onClicked: passText.showing = !passText.showing } } ColumnLayout { visible: i.loginBlock Label { text: qsTr("There was an issue connecting.") + translator.lang } Button { text: qsTr("Try Again") + translator.lang onClicked: i.loginBlock = false } } RowLayout { Layout.alignment: Qt.AlignBottom | Qt.AlignHCenter ColumnLayout { Label { visible: !i.loginBlock && !detected.visible text: qsTr("Searching for USB device") + translator.lang } Label { id: detected property int numDevices: typeof b != 'undefined' ? b.devices.length : 0 property int deviceType: numDevices ? b.devices[0] : 0 property string deviceName: switch(deviceType) { case 1: return "Bootloader" case 8012: return "Windows" case 8013: return "Unix" case 8017: return "Autodetect" default: return "" } visible: numDevices text: qsTr("Detected %1 Blackberry USB device(s) in %2 mode.").arg(numDevices).arg(deviceName) + translator.lang } Label { visible: i.possibleDevices Layout.alignment: Qt.AlignHCenter text: qsTr("Talking to %1 possible device(s).").arg(i.possibleDevices) + translator.lang } Button { visible: i.hasLog Layout.alignment: Qt.AlignHCenter text: qsTr("Connection Log")+ translator.lang onClicked: i.openLog(); } } BusyIndicator { visible: !i.loginBlock height: parent.implicitHeight + 7 width: height } } } } sachesi-2.0.4+ds/qml/generic/VersionLookup.qml000066400000000000000000000144551362064257300213160ustar00rootroot00000000000000import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.1 Window { title: "Sachesi " + version + " – " + qsTr("Version Lookup") + translator.lang visible: false onVisibleChanged: if (visible) { x = window.x + (window.width - width) / 2 y = window.y + (window.height - height) / 2 } height: 290 width: 490 ColumnLayout { height: parent.height width: parent.width anchors { left: parent.left; leftMargin: 10 } RowLayout { GroupBox { title: qsTr("Stop on:") + translator.lang Column { ExclusiveGroup { id: group onCurrentChanged: scanner.findExisting = current.item } RadioButton { property int item: 0 text: qsTr("Next Found") + translator.lang exclusiveGroup: group checked: true } RadioButton { property int item: 1 text: qsTr("Next Available Links") + translator.lang exclusiveGroup: group } RadioButton { property int item: 2 text: qsTr("Never") + translator.lang exclusiveGroup: group } Button { text: scanner.isAuto ? qsTr("Stop Scan") : qsTr("Autoscan") + translator.lang onClicked: { scanner.isAuto = !scanner.isAuto; if (scanner.isAuto) { build.value += 3; relookup.clicked(); } } property bool finished: scanner.finishedScan onFinishedChanged: { if (finished && !scanner.isActive && scanner.isAuto) { if (build.value >= 9998) { minor.value++; build.value = (build.value+3) % 10000; } else build.value += 3; relookup.clicked(); } } } } } ColumnLayout { Layout.alignment: Qt.AlignVCenter visible: scanner.curRelease !== null && scanner.curRelease.srVersion !== "" Text { Layout.alignment: Qt.AlignHCenter text: qsTr("SR: %1").arg(scanner.curRelease !== null ? scanner.curRelease.srVersion : "") + " | " + qsTr("OS: %1").arg(scanner.curRelease !== null ? scanner.curRelease.osVersion : "") + translator.lang font.pointSize: 12 } Label { Layout.alignment: Qt.AlignHCenter text: { var ret = "" if (scanner.curRelease !== null) { if (scanner.curRelease.activeServers & 1) ret += qsTr("Production") + " " if (scanner.curRelease.activeServers & 2) ret += qsTr("Beta") + " " if (scanner.curRelease.activeServers & 4) ret += qsTr("Alpha") + " " if (ret.length > 0) ret = qsTr("Servers:") + " " + ret } return ret + translator.lang; } } RowLayout { Layout.alignment: Qt.AlignHCenter property string osVersion: "" visible: scanner.curRelease !== null && scanner.curRelease.srVersion != "" Button { id: grabPotential enabled: scanner.curRelease !== null && scanner.curRelease.baseUrl !== "" text: enabled ? qsTr("Grab Public Links") : qsTr("No Links Available") + translator.lang onClicked: scanner.generatePotentialLinks() } } } } RowLayout { Layout.alignment: Qt.AlignTop | Qt.AlignHCenter Row { spacing: 1 property string latestOS: whitelistSettings.latestOS onLatestOSChanged: { var array = latestOS.split('.') if (array.length > 3) { major.prefix = array[0] + "." major.value = parseInt(array[1]) minor.value = parseInt(array[2]) build.value = parseInt(array[3]) } } SpinBox { id: major width: qt_new ? implicitWidth : implicitWidth + 25 maximumValue: 255 } SpinBox { id: minor width: qt_new ? implicitWidth : implicitWidth + 25 maximumValue: 255 } SpinBox { id: build width: qt_new ? implicitWidth : implicitWidth + 25 maximumValue: 9999 stepSize: 3 } } Button { id: relookup text: qsTr("Lookup") + translator.lang enabled: !scanner.isActive && !scanner.isAuto onClicked: scanner.reverseLookup("10." + major.value + "." + minor.value + "." + build.value); } } RowLayout { Layout.alignment: Qt.AlignBottom | Qt.AlignHCenter Button { visible: scanner.history.length > 0 text: qsTr("History") + translator.lang onClicked: scanner.exportHistory() } Button { text: qsTr("Hide") + translator.lang onClicked: close(); } } } } sachesi-2.0.4+ds/qml/generic/showpass.png000066400000000000000000000043621362064257300203350ustar00rootroot00000000000000PNG  IHDR((mgAMA a pHYsktEXtSoftwarePaint.NET v3.5.100rnIDATXGݘOW[b2ai&LQ~9xAn((^Q +3/iƇ6mt&SӴjC''d绻SIp^{֏_6WZ6ѹ,իcutĉ^OxÏ͛gvF۷-8?ᵍJ;5E_zPVci;a]ل <iE2Һk:'hSRƍf>,pOZu=5k\׺QJ $av(IONZH. 7$̉j=@: /v s\wh 4aAE4 #:YE|r.bͶ:G'n"T,QFYT& "L5(']9uQ oCDHJ# >>iP`I"FN)9Ҷ(.\T8Woqݻww8b}7պ)6~Kzݩ$^זnY3ܡh~>|,d- wENܯjP^JB2r#F F!wqN{dÇL {H$qDn֨{]WZtB>/ҥKZEDN)LEgϢ9!pÆ t揹^ R^'V Ͽ^ a3יfrɇ-t;.ć򭅜n|} ? 볈^nsKL-CțW"fBT1u#tlNC|rn~:t+lB KD SBI!Z)D=NZ?IENDB`sachesi-2.0.4+ds/qml/generic/text.png000066400000000000000000000021461362064257300174500ustar00rootroot00000000000000PNG  IHDR>agAMA a pHYs(JtEXtSoftwarePaint.NET v3.5.100rIDATx^ۿjA @ةxZIIR,$@X{ހm:{Tb9 efȰ~!nV+ #<>C#<|rREacy~p R_u@`@܎>Za- 3NY kil80,j0p#sM~M?Ӣ};%ZSDP4.g8vFδ3p3igL;#g8@䭁o;~WWDw#PGC!=(k`ʴ3p3igL;#gYS3e8vFδ3p3tF>&c@_@@Gp t:B{P;ܗ'G￉ʴ3p3igL;#gYZs'z)eF?tYP&.G X0JdA FCilT3,x,nknE3%=P3igL;#g8vFδ*{g`_l-!.ך #s`-*l8o7,j4J~üҮb^g0Fx8ۖcI_b`zi #<>C# vpAg01bKGD̿IDATx]ipTU>o;I&&t6@@dm,)5ԨS3*Y,-ܨqK,EqtuptL@t^{v}wyKVle9^1>#Q˻4f%|fPIn+j`6=vӮiĨV/(A#ڵ@ImWO*DA#}ce=į\vwkVmj/vWZ+NCsW]"Lmk_hܷ?ҫ~oɤ[ww*NOGJF<(D8Ex.K~K^ lǃFʳb:. t[mf 5`+(gÓ'G<~U'o5s/~t^X?i+rgulZp'WΔ?,B EK8B6ۄkC ߼ ibCh\Τ j}vhv\;>\ /ބ^ik!.wG3`WM=M,}-σ=g-e@f~ ˍ?7Uݩ:.q yZe*6!xB2YGҝh#;9pކ"a ;B=ÑK 7i)i@4@eZ*b%./Rt $bO =wj dۋJa`1})5l@T;9dh`Rd#MLح$0p`jh3 Ss%#wh0gq@#A,jFZbE0\0 X`Qgoj TZl yJp[lbn"3NPY"2Tqd5]"3_ud֮{y*a@ Sfw2+㤫V*Ź57j ~%pKbwgw0YY#mH\u ܳݼ"v$4KA17uJKtwQa(Bf/nC_  UM\BiUZ)Udh$DqL=WIƁ x?_r!p*{ I{(s0)Qk\Y\>%RLx3}_?~jk']NZ Ro{HD`udY"|ϋ Lań8bZ,։Cd[Vlesܾי%tEXtdate:create2010-02-11T12:50:18-06:00p %tEXtdate:modify2009-10-22T23:37:14-05:00?#<IENDB`sachesi-2.0.4+ds/src/000077500000000000000000000000001362064257300143355ustar00rootroot00000000000000sachesi-2.0.4+ds/src/apps.cpp000066400000000000000000000044301362064257300160050ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #include "apps.h" #define SET_QML2(type, name, caps) \ type Apps::name() const { \ return _ ## name; \ } \ void Apps::caps(const type &var) { \ if (var != _ ## name) { \ _ ## name = var; \ emit name ## Changed(); \ } \ } Apps::Apps(QObject *parent) : QObject(parent) , _name(""), _url(""), _friendlyName(""), _packageId("") , _code(0), _size(0) , _isMarked(false), _isAvailable(true), _isInstalled(false) , _type("") , _installedVersion(""), _version(""), _versionId("") , _checksum("") { } Apps::Apps(const Apps* app, QObject *parent) : QObject(parent) , _name(app->name()), _url(app->url()), _friendlyName(app->friendlyName()), _packageId(app->packageId()) , _code(app->code()), _size(app->size()) , _isMarked(app->isMarked()), _isAvailable(app->isAvailable()), _isInstalled(app->isInstalled()) , _type(app->type()) , _installedVersion(app->installedVersion()), _version(app->version()), _versionId(app->versionId()) , _checksum(app->checksum()) { } SET_QML2(QString, name, setName) SET_QML2(QString, url, setUrl) SET_QML2(QString, friendlyName, setFriendlyName) SET_QML2(QString, packageId, setPackageId) SET_QML2(int, code, setCode) SET_QML2(int, size, setSize) SET_QML2(bool, isMarked, setIsMarked) SET_QML2(bool, isAvailable, setIsAvailable) SET_QML2(bool, isInstalled, setIsInstalled) SET_QML2(QString, type, setType) SET_QML2(QString, installedVersion, setInstalledVersion) SET_QML2(QString, version, setVersion) SET_QML2(QString, versionId, setVersionId) SET_QML2(QString, checksum, setChecksum) sachesi-2.0.4+ds/src/apps.h000066400000000000000000000075501362064257300154600ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include #if QT_VERSION > QT_VERSION_CHECK(5, 0, 0) #include #else #include #define QQmlListProperty QDeclarativeListProperty #endif class Apps : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged) Q_PROPERTY(QString packageId READ packageId WRITE setPackageId NOTIFY packageIdChanged) Q_PROPERTY(QString friendlyName READ friendlyName WRITE setFriendlyName NOTIFY friendlyNameChanged) Q_PROPERTY(int code READ code WRITE setCode NOTIFY codeChanged) Q_PROPERTY(int size READ size WRITE setSize NOTIFY sizeChanged) Q_PROPERTY(bool isMarked READ isMarked WRITE setIsMarked NOTIFY isMarkedChanged) Q_PROPERTY(bool isAvailable READ isAvailable WRITE setIsAvailable NOTIFY isAvailableChanged) Q_PROPERTY(bool isInstalled READ isInstalled WRITE setIsInstalled NOTIFY isInstalledChanged) Q_PROPERTY(QString type READ type WRITE setType NOTIFY typeChanged) Q_PROPERTY(QString installedVersion READ installedVersion WRITE setInstalledVersion NOTIFY installedVersionChanged) Q_PROPERTY(QString version READ version WRITE setVersion NOTIFY versionChanged) Q_PROPERTY(QString versionId READ versionId WRITE setVersionId NOTIFY versionIdChanged) Q_PROPERTY(QString checksum READ checksum WRITE setChecksum NOTIFY checksumChanged) public: Apps(QObject *parent = 0); Apps(const Apps* app, QObject *parent = 0); QString name() const; QString url() const; QString packageId() const; QString friendlyName() const; int code() const; int size() const; bool isMarked() const; bool isAvailable() const; bool isInstalled() const; QString type() const; QString installedVersion() const; QString version() const; QString versionId() const; QString checksum() const; void setName(const QString &str); void setUrl(const QString &str); void setPackageId(const QString &str); void setFriendlyName(const QString &str); void setCode(const int &num); void setSize(const int &num); void setIsMarked(const bool &marked); void setIsAvailable(const bool &available); void setIsInstalled(const bool &installed); void setType(const QString &str); void setInstalledVersion(const QString &str); void setVersion(const QString &str); void setVersionId(const QString &str); void setChecksum(const QString &str); signals: void nameChanged(); void urlChanged(); void packageIdChanged(); void friendlyNameChanged(); void codeChanged(); void sizeChanged(); void isMarkedChanged(); void isAvailableChanged(); void isInstalledChanged(); void typeChanged(); void installedVersionChanged(); void versionChanged(); void versionIdChanged(); void checksumChanged(); private: QString _name; QString _url; QString _friendlyName; QString _packageId; int _code; int _size; bool _isMarked; bool _isAvailable; bool _isInstalled; QString _type; QString _installedVersion; QString _version; QString _versionId; QString _checksum; }; sachesi-2.0.4+ds/src/autoloaderwriter.h000066400000000000000000000062011362064257300201010ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include #include #include #include "fs/fs.h" // QNXStream #include "ports.h" class AutoloaderWriter: public QFile { Q_OBJECT public: AutoloaderWriter(QList devices) : _devHandle(devices) , _finished(false) { } void kill() { _finished = true; if (isOpen()) close(); remove(); } void create(QString name) { _maxSize = 0x10000000; // A very large number :) // Find potential file QString append = ".exe"; for (int f = 2; QFile::exists(name + append); f++) { append = QString("-%1.exe").arg(QString::number(f)); } // Start the autoloader as a cap file setFileName(name + append); open(QIODevice::WriteOnly); QFile cap(capPath()); cap.open(QIODevice::ReadOnly); appendFile(&cap); // This code is used as a separator write(QByteArray::fromBase64("at9dFE5LT0dJSE5JTk1TDRAMBRceERhTLUY8T0crSzk5OVNOT1FNT09RTU9RSEhwnNXFl5zVxZec1cWX").constData(), 60); // This is a placeholder for a password write(QByteArray(80, 0), 80); QByteArray dataHeader; QNXStream dataStream(&dataHeader, QIODevice::WriteOnly); dataStream << (quint64)_devHandle.count(); quint64 counter = pos() + 64; foreach (QIODevice* file, _devHandle) { dataStream << counter; counter += file->size(); } for (int i = _devHandle.count() - 1; i < 6; i++) dataStream << (qint64)0; write(dataHeader); _read = 100 * pos(); _maxSize = counter; resize(_maxSize); foreach (QIODevice* file, _devHandle) if (!_finished) appendFile(file); if (isOpen()) close(); } void appendFile(QIODevice* file) { while (!file->atEnd()) { // Check if we are being told to leave qApp->processEvents(); if (_finished) return; int writeSize = write(file->read(FAST_BUFFER_LEN)); if (writeSize < 0) { kill(); return; } _read += 100 * writeSize; emit newProgress((int)(_read / _maxSize)); } file->close(); } signals: void newProgress(int percent); private: qint64 _read, _maxSize; QList _devHandle; bool _finished; }; sachesi-2.0.4+ds/src/backupinfo.cpp000066400000000000000000000136261362064257300171720ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #include "backupinfo.h" BackupInfo::BackupInfo() : _progress(0), _size(0), _maxSize(1), _mode(0), _curMode(0), _numMethods(0), _rev(2), _appMode(0) { _numMethods = 3; _appMode = 7; // 1 + 2 + 3 represents system + bin + data categories.append(new BackupCategory("app", "Application Data")); categories.append(new BackupCategory("media", "Media")); categories.append(new BackupCategory("settings", "Device Settings and Local Contacts/Calendar Data")); _curSize.append(QList() << 0 << 0 << 0); _curMaxSize.append(QList() << 1 << 1 << 1); } void BackupInfo::clearModes() { _curMode = 0; _numMethods = 0; categories.clear(); _curSize.clear(); _curMaxSize.clear(); foreach(Apps* app, apps) app->deleteLater(); apps.clear(); emit curModeChanged(); emit numMethodsChanged(); } void BackupInfo::addMode(QXmlStreamAttributes cat) { categories.append(new BackupCategory(cat)); _numMethods++; emit numMethodsChanged(); _curSize.append(0); _curMaxSize.append(1); } void BackupInfo::addApp(QXmlStreamAttributes cat) { Apps* newApp = new Apps(); foreach(QXmlStreamAttribute attr, cat) { QString att = attr.name().toString(); QString val = attr.value().toString(); if (att == "pkgid") newApp->setPackageId(val); else if (att == "name") newApp->setFriendlyName(val); else if (att == "bytesize") newApp->setSize(val.toInt()); else if (att == "version") newApp->setVersion(val); else if (att == "type") newApp->setType(val); } newApp->setIsMarked(true); apps.append(newApp); std::sort(apps.begin(), apps.end(), [=](const Apps* i, const Apps* j) { if (i->type() == "system" && j->type() != "system") return true; if (j->type() == "bin" && i->type() != "bin") return true; return false; } ); } void BackupInfo::sortApps() { } QString BackupInfo::modeString() { QString ret = ""; for (int i = 0; i < _numMethods; i++) { if (_mode & (1 << i)) { if (ret != "") ret += "_"; ret += categories.at(i)->id; } } return ret; } QString BackupInfo::stringFromMode(int mode) { if (mode >= 0 && mode < _numMethods) return categories.at(mode)->id; if (mode == _numMethods) return "complete"; return ""; } int BackupInfo::progress() const { return _progress; } void BackupInfo::setProgress(const int &progress) { _progress = progress; emit progressChanged(); } int BackupInfo::mode() const { return _mode; } int BackupInfo::appMode() const { return _appMode; } void BackupInfo::setMode(const int &mode) { _mode = mode; emit modeChanged(); } qint64 BackupInfo::setAppMode(QString mode) { qint64 totalSize = 0; int type = 0; if (mode == "system") type = 1; else if (mode == "data") type = 2; else if (mode == "bin") type = 4; if (type == 0) return 0; // Toggle mode based on string _appMode ^= type; // Are we enabling this mode or disabling it? bool enable = _appMode & type; for (int i = 0; i < apps.count(); i++) { if (apps.at(i)->type() == mode && apps.at(i)->isMarked() != enable) { apps.at(i)->setIsMarked(enable); totalSize += (enable ? 1 : -1) * apps.at(i)->size(); } } return totalSize; } QString BackupInfo::curMode() const { if (_curMode == _numMethods) return "complete"; return categories.at(_curMode)->id; } void BackupInfo::setCurMode(int increment) { if (increment) { if (_numMethods > _curMode && _curSize[_curMode]) _size += _curSize[_curMode]; _curMode++; } else { _curMode = 0; } while (!(_mode & (1 << _curMode)) && _curMode < _numMethods) _curMode++; if (_curMode < _numMethods) _curSize[_curMode] = 0; emit curModeChanged(); emit curSizeChanged(); emit sizeChanged(); } qint64 BackupInfo::size() const { return _size; } void BackupInfo::setSize(const qint64 &val) { _size = val; emit sizeChanged(); } qint64 BackupInfo::maxSize() const { return _maxSize; } void BackupInfo::setMaxSize(const qint64 &val) { _maxSize = val; emit maxSizeChanged(); } qint64 BackupInfo::curSize() const { if (_curMode == _numMethods) return 1; if (_numMethods > _curMode) return _curSize[_curMode]; else return 0; } void BackupInfo::setCurSize(const qint64 &val) { if (_numMethods > _curMode) _curSize[_curMode] = val; emit curSizeChanged(); } qint64 BackupInfo::curMaxSize() const { if (_curMode == _numMethods || _numMethods <= _curMode) return 1; return _curMaxSize[_curMode]; } void BackupInfo::setCurMaxSize(const qint64 &val) { if (_numMethods > _curMode) _curMaxSize[_curMode] = val; emit curMaxSizeChanged(); } void BackupInfo::setCurMaxSize(int index, const qint64 &val) { _curMaxSize[index] = val; emit curMaxSizeChanged(); } int BackupInfo::numMethods() const { return categories.size(); } int BackupInfo::rev() const { return _rev; } sachesi-2.0.4+ds/src/backupinfo.h000066400000000000000000000061321362064257300166310ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include #include #include #include #include "apps.h" class BackupCategory { public: BackupCategory(QXmlStreamAttributes cat) { foreach(QXmlStreamAttribute attr, cat) { QString att = attr.name().toString(); QString val = attr.value().toString(); if (att == "id") id = val; else if (att == "name") name = val; else if (att == "count") count = val; else if (att == "bytesize") bytesize = val; else if (att == "perimetertype") perimetertype = (val != "personal") ? "1" : "0"; } } BackupCategory(QString idGiven, QString nameGiven) { id = idGiven; name = nameGiven; count = "0"; bytesize = "-1"; perimetertype = "0"; } ~BackupCategory() {} QString id; QString name; QString count; QString bytesize; QString perimetertype; }; class BackupInfo : public QObject { Q_OBJECT public: BackupInfo(); ~BackupInfo() {} QString modeString(); QString stringFromMode(int mode); int mode() const; int appMode() const; int numMethods() const; void addMode(QXmlStreamAttributes cat); void addApp(QXmlStreamAttributes cat); void sortApps(); void clearModes(); void setMode(const int &val); qint64 setAppMode(QString mode); QString curMode() const; void setCurMode(int increment); qint64 size() const; void setSize(const qint64 &val); qint64 maxSize() const; void setMaxSize(const qint64 &val); qint64 curSize() const; void setCurSize(const qint64 &val); qint64 curMaxSize() const; void setCurMaxSize(const qint64 &val); void setCurMaxSize(int index, const qint64 &val); int progress() const; void setProgress(const int &progress); QList categories; QList apps; int rev() const; signals: void modeChanged(); void progressChanged(); void curModeChanged(); void sizeChanged(); void maxSizeChanged(); void curSizeChanged(); void curMaxSizeChanged(); void numMethodsChanged(); private: int _progress; qint64 _size, _maxSize; QList _curSize, _curMaxSize; int _mode; int _curMode; int _numMethods; int _rev; int _appMode; }; sachesi-2.0.4+ds/src/blitzinfo.cpp000066400000000000000000000047131362064257300170460ustar00rootroot00000000000000#include "blitzinfo.h" #include #include #include BlitzInfo::BlitzInfo(QList filenames, QString deviceOS, QString deviceRadio) : QObject() , osIsSafe(false) , radioIsSafe(false) , radioCount(0) , osCount(0) , _deviceOS(deviceOS) , _deviceRadio(deviceRadio) { foreach(QString barFile, filenames) blitzCheck(barFile); } void BlitzInfo::blitzCheck(QString name) { BarType fileType = NotInstallableType; // Check if it's a 'hidden' file as we use these for temporary file downloads. if (QFileInfo(name).fileName().startsWith('.')) return; QuaZipFile manifest(name, "META-INF/MANIFEST.MF", QuaZip::csSensitive); if (!manifest.open(QIODevice::ReadOnly)) return; QString appName, type; while (!manifest.atEnd()) { QByteArray newLine = manifest.readLine(); if (newLine.startsWith("Package-Name:") || newLine.startsWith("Patch-Package-Name:")) { appName = newLine.split(':').last().simplified(); if (newLine.startsWith("Patch") && type == "system") { if (appName.contains("radio")) fileType = RadioType; else fileType = OSType; } } else if (newLine.startsWith("Package-Type:") || newLine.startsWith("Patch-Package-Type:")) { type = newLine.split(':').last().simplified(); if (type == "system" && fileType == NotInstallableType) fileType = OSType; else if (type != "patch") break; } else if (newLine.startsWith("System-Type:")) { if (newLine.split(':').last().simplified() == "radio") fileType = RadioType; break; } } if (fileType != OSType && fileType != RadioType) return; if (fileType == OSType) { osCount++; QString installableOS = appName.split("os.").last().remove(".desktop").replace("verizon", "factory"); if (_deviceOS == "" || installableOS == _deviceOS || (installableOS.contains("8974") && _deviceOS.contains("8974"))) { osIsSafe = true; } } else if (fileType == RadioType) { radioCount++; QString installableRadio = appName.split("radio.").last().remove(".omadm"); if (_deviceRadio == "" || installableRadio == _deviceRadio) { radioIsSafe = true; } } return; } sachesi-2.0.4+ds/src/blitzinfo.h000066400000000000000000000014271362064257300165120ustar00rootroot00000000000000#ifndef BLITZINFO_H #define BLITZINFO_H #include enum BarType { NotInstallableType = 0, AppType, RadioType, OSType, }; // Blitz means it has more than one OS or Radio. // In this situation we need to work out which one is intended instead of asking about every single one. // If there is only one good OS and Radio, the intention is clear. class BlitzInfo: public QObject { Q_OBJECT public: BlitzInfo(QList filenames, QString deviceOS, QString deviceRadio); void blitzCheck(QString name); bool isSafe() { return (osIsSafe && radioIsSafe); } bool isBlitz() { return (osCount > 1 || radioCount > 1); } bool osIsSafe, radioIsSafe; int radioCount, osCount; private: QString _deviceOS, _deviceRadio; }; #endif // BLITZINFO_H sachesi-2.0.4+ds/src/boot.cpp000066400000000000000000000341411362064257300160070ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #include "ports.h" #include "boot.h" #include "installer.h" #include #include #include #include #include Boot::Boot() : _bootloaderMode(0xFF), _connecting(false), _command(0), _rebootAfter(false), _packetNum(0), _kill(false) { bootloaderModeData.append(new QByteArray("RIM REINIT\0\0\0\0\0\0\1", 17)); bootloaderModeData.append(new QByteArray("RIM-BootLoader\0\0\1",17)); bootloaderModeData.append(new QByteArray("RIM-RAMLoader\0\0\0\1", 17)); bootloaderModeData.append(new QByteArray("RIM UPL\0\0\0\0\0\0\0\0\0\1", 17)); bootloaderModeData.append(new QByteArray("RIM-BootNUKE\0\0\0\0\1", 17)); // If Android does not have USB permissions, this will fail and we need to handle it. _kill = libusb_init(nullptr) != 0; // Should probably just shut down and notify qml if _kill is false at this stage. } Boot::~Boot() { libusb_exit(nullptr); } libusb_device_handle* Boot::openDevice(libusb_device* dev) { int configuration = 0; libusb_device_handle* handle = nullptr; libusb_open(dev, &handle); if (handle == nullptr) { #ifdef _WIN32 QMessageBox::information(nullptr, "Error", "You need to install WinUSB driver for the Blackberry device."); #else QMessageBox::information(nullptr, "Error", "You need to run this application with root privileges (i.e. sudo)."); #endif libusb_close(handle); return nullptr; } libusb_get_configuration(handle, &configuration); libusb_detach_kernel_driver(handle, 1); if(configuration != 1) { if (libusb_set_configuration(handle, 1) < 0) { QMessageBox::information(nullptr, "Error", "Error #1001"); return nullptr; } } libusb_detach_kernel_driver(handle, 0); int err = libusb_claim_interface(handle, 0); if (err < 0) { #ifdef Q_OS_MAC QMessageBox::information(nullptr, "Error", "Please reboot your device manually."); return nullptr; #else QMessageBox::information(nullptr, "Error", "Error #1002 (" + QString::number(err) + ")"); return nullptr; #endif } return handle; } int Boot::closeDevice(libusb_device_handle* handle) { if (handle == nullptr) { return -1; } libusb_release_interface(handle, 0); libusb_release_interface(handle, 1); libusb_close(handle); return 0; } int Boot::sendControlMessage(libusb_device_handle* aHandle, uint8_t aCommand, QByteArray* aData, int aDataSize) { QByteArray buffer(aDataSize + 8, 0); ControlMessageHeader* header = (ControlMessageHeader*)buffer.data(); header->type = 0x00; header->packetSize = aDataSize + 8; header->command = aCommand; header->mode = _bootloaderMode; header->packetId = _packetNum++; if (aData) memcpy((unsigned char*)buffer.data()+8, (const unsigned char*)aData->constData(), aDataSize); int transferred = 0; int err = libusb_bulk_transfer(aHandle, _found == 0x1 ? 0x2 : 0x1, (unsigned char*)buffer.data(), aDataSize + 8, &transferred, 1000); if (err == -7) { QMessageBox::information(nullptr, "Error", "Was not able to send message to connected device."); return -7; } return transferred; } int Boot::receiveControlMessage(libusb_device_handle* aHandle, ControlMessageHeader* aHeader, QByteArray* aData) { QByteArray buffer(BUFFER_SIZE, 0); int transferred = 0; int ret = libusb_bulk_transfer(aHandle, _found == 0x1 ? 0x82 : 0x81, (unsigned char*)buffer.data(), BUFFER_SIZE, &transferred, 1000); if (ret == -7) { if (_found != 0x1) { QMessageBox::information(nullptr, "Error", "Try rebooting your device manually."); } else { QMessageBox::information(nullptr, "Error", "The connected device did not respond as expected."); } return -7; } if (transferred < 8) return 0; /*qDebug() << "Header: " << buffer.mid(4,4).toHex(); if (transferred > 8) { if ((transferred - 8) < 40) qDebug() << "Text: " << buffer.mid(8,transferred - 8); qDebug() << "Hex: " << buffer.mid(8,transferred - 8).toHex(); }*/ if (aHeader) memcpy(aHeader, (const unsigned char*)buffer.constData(), 8); if (aData) memcpy((unsigned char*)aData->data(), (const unsigned char*)buffer.constData() + 8, transferred - 8); return transferred; } void Boot::commandReboot(libusb_device_handle* aHandle) { sendControlMessage(aHandle, kCommandReboot, nullptr, 0); // reboots the device (message 00 command 03) receiveControlMessage(aHandle, nullptr, nullptr); // discard the reboot confirmation message (message 00 command 04) } void Boot::commandPing(libusb_device_handle* aHandle) { sendControlMessage(aHandle, kCommandPing, nullptr, 0); // reboots the device (message 00 command 03) receiveControlMessage(aHandle, nullptr, nullptr); // discard the reboot confirmation message (message 00 command 04) } int Boot::commandGetVariable(libusb_device_handle* aHandle, int variable, QByteArray* buffer, int bufferSize) { // gets a variable from the device (message 00 command 05) // returns the amount of data received // 0-1: buffer size // 2-3: variable uint16_t data[2]; data[0] = bufferSize; data[1] = variable; QByteArray newBuf((const char*)data, 4); int err = sendControlMessage(aHandle, kCommandGetVariable, &newBuf, 4); if (err == -7) return -7; err = receiveControlMessage(aHandle, nullptr, buffer); if (err == -7) return -7; return err - 8; } void Boot::commandSetMode(libusb_device_handle* aHandle, BootloaderMode mode) { sendControlMessage(aHandle, kCommandSetMode, bootloaderModeData.at(mode), 16); // set the bootloader mode on the device (message 00 command 07) // update the bootloader mode ControlMessageHeader header; receiveControlMessage(aHandle, &header, nullptr); _bootloaderMode = header.mode; } void Boot::commandLoadBootloader(libusb_device_handle* aHandle) { sendControlMessage(aHandle, kCommandLoadTransferredData, nullptr, 0); // loads the usb bootloader or reboots (message 00 command 0B) // needs to be in mode 01 (RIM-BootLoader) to work receiveControlMessage(aHandle, nullptr, nullptr); // discard the mode confirmation message (message 00 command 0C) } void Boot::commandTransferData(libusb_device_handle* aHandle) { sendControlMessage(aHandle, kCommandReadyForDataTransfer, nullptr, 0); // loads the usb bootloader or reboots (message 00 command 0B) // needs to be in mode 01 (RIM-BootLoader) to work receiveControlMessage(aHandle, nullptr, nullptr); } bool Boot::commandSendPass(libusb_device_handle* aHandle) { sendControlMessage(aHandle, kCommandGetPasswordInfo, nullptr, 0); // get the device password info (message 00 command 0A) // needs to be in mode 01 (RIM-BootLoader) to work QByteArray challengeData(100,0); receiveControlMessage(aHandle, nullptr, &challengeData); // discard the mode confirmation message (message 00 command 0E) QByteArray challenge = challengeData.mid(4, 4); QByteArray salt = challengeData.mid(12,8); QByteArray countData = challengeData.mid(20,4); QDataStream stream(countData); stream.setByteOrder(QDataStream::LittleEndian); int iterations; stream >> iterations; QByteArray hashedData = QByteArray(_password.toLatin1()); int count = 0; bool challenger = true; do { QByteArray buf(4 + salt.length() + hashedData.length(),0); QDataStream buffer(&buf,QIODevice::WriteOnly); buffer.setByteOrder(QDataStream::LittleEndian); buffer << qint32(count); buffer.writeRawData(salt, salt.length()); buffer.writeRawData(hashedData, hashedData.length()); if (!count) hashedData.resize(64); SHA512((const unsigned char*)buf.data(), buf.length(), (unsigned char *)hashedData.data()); if ((count == iterations - 1) && challenger) { count = -1; challenger = false; hashedData.prepend(challenge); } } while (++count < iterations); hashedData.prepend(QByteArray::fromHex("00004000"),4); sendControlMessage(aHandle, kCommandSendPassword, &hashedData, 68); ControlMessageHeader header; receiveControlMessage(aHandle, &header, nullptr); if (header.command == 0x10) return true; else { QMessageBox::information(nullptr,"Wrong Password.","You entered the wrong password. I don't handle this yet but basically you will need to go to the Install tab, fix your password, then try again."); return false; } } void Boot::commandSendLoader(libusb_device_handle *aHandle) { // Should be telling us it's ready. ControlMessageHeader header; receiveControlMessage(aHandle, &header, nullptr); if (header.command != kCommandReadyForDataTransfer) return; int transferred = 0; unsigned char startSend[] = {0x1, 0x0, 0x10, 0x0, 0x7B, 0x9, 0x2B, 0x96, 0xC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xF0}; libusb_bulk_transfer(aHandle, 0x2, startSend, 16, &transferred, 1000); receiveControlMessage(aHandle, &header, nullptr); // Should give Info basically } void Boot::search() { if (_kill) return; libusb_device_handle* handle = nullptr; libusb_device *dev = nullptr; libusb_device **list; libusb_get_device_list(nullptr, &list); _found = 0; struct libusb_device_descriptor desc; _devices.clear(); for (int i = 0; (dev = list[i]) != nullptr; i++) { libusb_get_device_descriptor(dev, &desc); if (desc.idVendor == BLACKBERRY_VENDOR_ID) { _devices.append(QString::number(desc.idProduct,16)); if (connecting()) { _found = desc.idProduct; handle = openDevice(list[i]); // We need to reboot it to talk to it if (desc.idProduct != 0x1) { if (handle != nullptr) { commandReboot(handle); closeDevice(handle); } else _found = 0; } } break; } } libusb_free_device_list(list, 1); emit devicesChanged(); if (_found != 0x1) { // We'll be rebooting this guy QTimer::singleShot(10000, this, SLOT(search())); return; } QTimer::singleShot(1500, this, SLOT(search())); if (handle == nullptr) { if (_found == 0x1) setConnecting(false); return; } setConnecting(false); switch(command()) { case commandInfo: { commandSetMode(handle, kBootloaderRimBoot); QByteArray buffer(2000, 0); int received = commandGetVariable(handle, 2, &buffer, 2000); if (received == -7) return; QFile info(getSaveDir() + "info.txt"); info.open(QIODevice::WriteOnly); QBuffer bufferStream(&buffer); bufferStream.open(QIODevice::ReadOnly); QDataStream stream(&bufferStream); stream.setByteOrder(QDataStream::LittleEndian); short messageSize; stream >> messageSize; info.write(QByteArray("Message Size: ") + QByteArray::number(messageSize) + "\n"); bufferStream.seek(16); unsigned int pin; stream >> pin; info.write(QByteArray("Hardware ID: 0x") + QByteArray::number(pin, 16) + " " + bufferStream.read(64).split('\0').first() + "\n"); info.write(QByteArray("Build User: ") + bufferStream.read(16).split('\0').first() + "\n"); info.write(QByteArray("Build Date: ") + bufferStream.read(16).split('\0').first() + "\n"); info.write(QByteArray("Build Time: ") + bufferStream.read(16).split('\0').first() + "\n"); unsigned int unk1; stream >> unk1; info.write(QByteArray("Unknown value: 0x" + QByteArray::number(unk1, 16)) + "\n"); bufferStream.seek(188); unsigned int baseId, brId; stream >> baseId; stream >> brId; info.write(QByteArray("Hardware OS ID: 0x" + QByteArray::number(baseId, 16)) + "\n"); info.write(QByteArray("BR ID: 0x" + QByteArray::number(brId, 16)) + "\n"); info.write(buffer.mid(bufferStream.pos(), received - bufferStream.pos())); bufferStream.close(); openFile(info.fileName()); info.close(); } break; case commandBootloader: commandSetMode(handle, kBootloaderRimBoot); commandSendPass(handle); if (commandSendPass(handle)) commandSendLoader(handle); break; case commandNuke: commandSetMode(handle, kBootloaderRimBoot); commandSetMode(handle, kBootloaderRimBootNuke); break; case commandDebug: _found = 8000; commandSetMode(handle, kBootloaderRimBoot); break; default: commandSetMode(handle, kBootloaderRimBoot); break; } if (_rebootAfter) commandReboot(handle); closeDevice(handle); } void Boot::connect() { setConnecting(true); } void Boot::disconnect() { setConnecting(false); } void Boot::newPassword(QString newPass) { _password = newPass; } void Boot::setCommandMode(int mode, bool rebootAfter) { setCommand(mode); connect(); _rebootAfter = rebootAfter; } #define SET_QML2(type, name, caps) \ type Boot::name() const { \ return _ ## name; \ } \ void Boot::caps(const type &var) { \ if (var != _ ## name) { \ _ ## name = var; \ emit name ## Changed(); \ } \ } SET_QML2(bool, connecting, setConnecting) SET_QML2(int, command, setCommand) sachesi-2.0.4+ds/src/boot.h000066400000000000000000000114521362064257300154540ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include #include #include #include #include #include #define BLACKBERRY_VENDOR_ID 0x0FCA #define BUFFER_SIZE 2048 #ifdef _WIN32 #pragma pack(push,1) #endif // message with type 00 typedef struct ControlMessageHeader { unsigned short type; // 0000 unsigned short packetSize; unsigned char command; unsigned char mode; unsigned short packetId; // any data specific to the message is added after } #ifndef _WIN32 __attribute__((packed)) ControlMessageHeader; #else ControlMessageHeader; #pragma pack(pop) #endif enum SachesiCommand { commandInfo = 0x01, commandBootloader = 0x02, commandNuke = 0x03, commandDebug = 0x04 }; enum ControlMessageCommand { kCommandPing = 0x01, // OUT kCommandPingResponse = 0x02, // IN kCommandReboot = 0x03, // OUT kCommandRebootResponse = 0x04, // IN kCommandGetVariable = 0x05, // OUT kCommandGetVariableResponse = 0x06, // IN kCommandSetMode = 0x07, // OUT kCommandSetModeSuccess = 0x08, // IN kCommandSetModeFailure = 0x09, // IN kCommandGetPasswordInfo = 0x0A, // OUT kCommandLoadTransferredData = 0x0B, // IN/OUT kCommandLoadTransferredDataSuccess = 0x0C, // IN/OUT kCommandLoadTransferredDataFailure = 0x0D, // IN/OUT kCommandGetPasswordInfoResponse = 0x0E, // IN kCommandSendPassword = 0x0F, // OUT kCommandSendPasswordCorrect = 0x10, // IN kCommandUnkn1 = 0x11, // ? kCommandUnkn1Response = 0x12, // OUT kCommandReadyForDataTransfer = 0x13 // IN }; typedef enum BootloaderMode { // value = mode entry in message 00 struct when mode is set kBootloaderRimReinit = 0x0, kBootloaderRimBoot = 0x1, kBootloaderRamBoot = 0x2, kBootloaderUPL = 0x3, // Modes with no 'Mode Entry' kBootloaderRimBootNuke = 0x4 // Wipe User partition } BootloaderMode; class Boot : public QThread { Q_OBJECT Q_PROPERTY(bool connecting READ connecting WRITE setConnecting NOTIFY connectingChanged) Q_PROPERTY(int command READ command WRITE setCommand NOTIFY commandChanged) Q_PROPERTY(QStringList devices MEMBER _devices NOTIFY devicesChanged) public: Boot(); ~Boot(); libusb_device_handle* openDevice(libusb_device* dev); int closeDevice(libusb_device_handle* handle); int sendControlMessage(libusb_device_handle* aHandle, uint8_t aCommand, QByteArray* aData, int aDataSize); int receiveControlMessage(libusb_device_handle* aHandle, ControlMessageHeader* aHeader, QByteArray* aData); void commandReboot(libusb_device_handle* aHandle); void commandPing(libusb_device_handle* aHandle); bool commandSendPass(libusb_device_handle* aHandle); void commandSendLoader(libusb_device_handle* aHandle); int commandGetVariable(libusb_device_handle* aHandle, int variable, QByteArray* buffer, int bufferSize); void commandSetMode(libusb_device_handle* aHandle, BootloaderMode mode); void commandLoadBootloader(libusb_device_handle* aHandle); void commandTransferData(libusb_device_handle* aHandle); bool connecting() const; int command() const; void setCommand(const int &command); void setConnecting(const bool &connecting); signals: void connectingChanged(); void commandChanged(); void devicesChanged(); public slots: Q_INVOKABLE void search(); Q_INVOKABLE void connect(); Q_INVOKABLE void disconnect(); Q_INVOKABLE void setCommandMode(int mode, bool rebootAfter); void newPassword(QString newPass); void exit() { _kill = true; } private: int _bootloaderMode; bool _connecting; int _command; bool _rebootAfter; short _packetNum; int _found; QString _password; QStringList _devices; QList bootloaderModeData; bool _kill; }; sachesi-2.0.4+ds/src/carrierinfo.h000066400000000000000000000101321362064257300170060ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include #include #include #include class CarrierInfo : public QObject { Q_OBJECT Q_PROPERTY(QString country MEMBER _country NOTIFY resultChanged) Q_PROPERTY(QString carrier MEMBER _carrier NOTIFY resultChanged) Q_PROPERTY(QString mcc MEMBER _mcc NOTIFY resultChanged) Q_PROPERTY(QString mnc MEMBER _mnc NOTIFY resultChanged) Q_PROPERTY(int image MEMBER _image NOTIFY resultChanged) public: CarrierInfo(QObject* parent = 0) : QObject(parent) { _manager = new QNetworkAccessManager(); _timer = new QTimer(); _timer->setSingleShot(true); connect(_timer, SIGNAL(timeout()), this, SLOT(update())); _mcc = ""; _mnc = ""; _image = 0; } Q_INVOKABLE void mccChange(QString mcc) { if (mcc != _mcc) { _mcc = mcc; update(); } } Q_INVOKABLE void mncChange(QString mnc){ if (mnc != _mnc) { _mnc = mnc; update(); } } public slots: void update() { // Make sure the search is still relevant when the results come in QString mcc = _mcc; QString mnc = _mnc; if (mcc == "" || mnc == "") return; QNetworkRequest request(QString("http://appworld.blackberry.com/ClientAPI/checkcarrier?homemcc=%1&homemnc=%2&devicevendorid=-1&pin=0") .arg(_mcc) .arg(_mnc)); request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, "AppWorld/5.1.0.60"); QNetworkReply* reply = _manager->get(request); connect(reply, &QNetworkReply::finished, [=] { int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (reply->size() > 0 && (status == 200 || (status > 300 && status <= 308))) { if (mcc != _mcc || mnc != _mnc) return; QXmlStreamReader xml(reply->readAll()); while(!xml.atEnd() && !xml.hasError()) { if(xml.tokenType() == QXmlStreamReader::StartElement) { if (xml.name() == "country") { _country = xml.attributes().value("name").toString(); } else if (xml.name() == "carrier") { _carrier = xml.attributes().value("name").toString().split(" ").first(); _image = xml.attributes().value("icon").toInt(); // Don't show generic if (_carrier == "default") { _carrier = ""; _image = 0; } } } xml.readNext(); } } emit resultChanged(); reply->deleteLater(); }); connect(reply, static_cast(&QNetworkReply::error), [=]() { _country = ""; _carrier = ""; _image = 0; reply->deleteLater(); }); } signals: void resultChanged(); private: QString _country, _carrier; QString _mcc, _mnc; int _image; QNetworkAccessManager* _manager; QTimer* _timer; }; sachesi-2.0.4+ds/src/deviceinfo.h000066400000000000000000000102421362064257300166200ustar00rootroot00000000000000#pragma once #include #include class DeviceInfo : public QObject { Q_OBJECT Q_PROPERTY(QString friendlyName MEMBER friendlyName NOTIFY friendlyNameChanged) Q_PROPERTY(QString os MEMBER os WRITE setOs NOTIFY osChanged) Q_PROPERTY(QString radio MEMBER radio WRITE setRadio NOTIFY radioChanged) Q_PROPERTY(int battery MEMBER battery WRITE setBattery NOTIFY batteryChanged) Q_PROPERTY(QString name MEMBER name WRITE setName NOTIFY nameChanged) Q_PROPERTY(QString pin MEMBER pin WRITE setPin NOTIFY pinChanged) Q_PROPERTY(QString hw MEMBER hw WRITE setHw NOTIFY hwChanged) Q_PROPERTY(int hwFamily MEMBER hwFamily WRITE setHwFamily NOTIFY hwFamilyChanged) Q_PROPERTY(QString bbid MEMBER bbid WRITE setBbid NOTIFY bbidChanged) Q_PROPERTY(int protocol MEMBER protocol WRITE setProtocol NOTIFY protocolChanged) Q_PROPERTY(bool devMode MEMBER devMode WRITE setDevMode NOTIFY devModeChanged) Q_PROPERTY(bool setupComplete MEMBER setupComplete WRITE setSetupComplete NOTIFY setupCompleteChanged()) Q_PROPERTY(QString restrictions MEMBER restrictions WRITE setRestrictions NOTIFY restrictionsChanged) Q_PROPERTY(QString refurbDate MEMBER refurbDate WRITE setRefurbDate NOTIFY refurbDateChanged) Q_PROPERTY(quint64 freeSpace MEMBER freeSpace WRITE setFreeSpace NOTIFY freeSpaceChanged) Q_PROPERTY(QString bsn MEMBER bsn WRITE setBsn NOTIFY bsnChanged) public: DeviceInfo() : QObject() , radio("N/A") , battery(-1) , hwFamily(0) , protocol(1) , devMode(false) , freeSpace(0) { } QString friendlyName; QString os; QString radio; int battery; QString name; QString pin; QString hw; int hwFamily; QString bbid; int protocol; bool devMode; bool setupComplete; QString restrictions; QString refurbDate; quint64 freeSpace; QString bsn; void setFriendlyName(const QString &input) { friendlyName = input; emit friendlyNameChanged(); } void setOs(const QString &input) { os = input; emit osChanged(); } void setRadio(const QString &input) { radio = input; emit radioChanged(); } void setBattery(const int &input) { battery = input; emit batteryChanged(); } void setName(const QString &input) { name = input; emit nameChanged(); } void setPin(const QString &input) { pin = input; emit pinChanged(); } void setHw(const QString &input) { hw = input; emit hwChanged(); } void setHwFamily(const int &input) { hwFamily = input; emit hwFamilyChanged(); } void setBbid(const QString &input) { bbid = input; emit bbidChanged(); } void setProtocol(const int &input) { protocol = input; emit protocolChanged(); } void setDevMode(const bool &input) { devMode = input; emit devModeChanged(); } void setSetupComplete(const bool &input) { setupComplete = input; emit setupCompleteChanged(); } void setRestrictions(const QString &input) { restrictions = input; emit restrictionsChanged(); } void setRefurbDate(const QString &input) { if (input.toInt() == 0) refurbDate = ""; else refurbDate = QDateTime::fromTime_t(input.toInt()).toString(); emit refurbDateChanged(); } void setFreeSpace(const quint64 &input) { freeSpace = input; emit freeSpaceChanged(); } void setBsn(const QString &input) { bsn = input; emit bsnChanged(); } signals: void friendlyNameChanged(); void osChanged(); void radioChanged(); void batteryChanged(); void nameChanged(); void pinChanged(); void hwChanged(); void hwFamilyChanged(); void bbidChanged(); void protocolChanged(); void devModeChanged(); void setupCompleteChanged(); void restrictionsChanged(); void refurbDateChanged(); void freeSpaceChanged(); void bsnChanged(); }; Q_DECLARE_METATYPE(DeviceInfo* ); sachesi-2.0.4+ds/src/downloadinfo.h000066400000000000000000000347221362064257300172010ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include #include #include #include #include #include #include #include "apps.h" #include "ports.h" // Need to get creative with CURR_FILE when threading comes enum { CURR_FILE = -1, }; class DownloadInfo : public QObject { Q_OBJECT Q_PROPERTY(int id MEMBER id NOTIFY idChanged) Q_PROPERTY(int maxId MEMBER maxId NOTIFY appsChanged) Q_PROPERTY(int curProgress MEMBER curProgress NOTIFY sizeChanged) Q_PROPERTY(int progress MEMBER progress NOTIFY sizeChanged) Q_PROPERTY(int size MEMBER size NOTIFY sizeChanged) Q_PROPERTY(qint64 totalSize MEMBER totalSize NOTIFY appsChanged) Q_PROPERTY(QString curName READ getName NOTIFY idChanged) Q_PROPERTY(bool verifying READ verifying NOTIFY verifyingChanged) Q_PROPERTY(bool running MEMBER running NOTIFY idChanged) public: DownloadInfo(QObject* parent = 0) : QObject(parent) , baseDir("") , id(0), maxId(0) , progress(0), curProgress(0) , size(0), totalSize(0) , starting(false) , toVerify(0) , running(false) { _manager = new QNetworkAccessManager(); } Q_INVOKABLE void reset() { if (_updateFile.isOpen()) _updateFile.close(); starting = false; running = false; id = 0; maxId = 0; size = 0; totalSize = 0; toVerify = 0; foreach(Apps* app, apps) { if (app != nullptr) { delete app; app = nullptr; } } apps.clear(); emit sizeChanged(); emit idChanged(); emit appsChanged(); } Q_INVOKABLE void start() { starting = true; if (_updateFile.isOpen()) { _updateFile.close(); _updateFile.remove(); } } bool isStarting() { bool isRequested = starting; starting = false; return isRequested; } bool verifying() const { return toVerify > 0; } void verifyDelta(int i) { toVerify++; emit verifyingChanged(); QString url = apps.at(i)->url(); QString oldVersion = apps.at(i)->installedVersion(); oldVersion.replace('.','_'); url.chop(4); // Remove extension url.append(QString("+patch+%1.bar").arg(oldVersion)); QNetworkReply* reply = _manager->head(QNetworkRequest(url)); QObject::connect(reply, &QNetworkReply::finished, [=]() { uint status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toUInt(); if (status == 200 || (status > 300 && status <= 308)) { uint realSize = reply->header(QNetworkRequest::ContentLengthHeader).toUInt(); // Adjust the expected size totalSize += realSize - apps.at(i)->size(); apps.at(i)->setSize(realSize); apps.at(i)->setUrl(url); emit sizeChanged(); emit appsChanged(); } toVerify--; emit verifyingChanged(); // Verified. Now to complete if (toVerify == 0) startDownload(); reply->deleteLater(); }); QObject::connect(reply, static_cast(&QNetworkReply::error), [=]() { toVerify--; emit verifyingChanged(); // Verified. Now to complete if (toVerify == 0) startDownload(); reply->deleteLater(); }); } void verifyLink(QString url, QString type, bool delta) { toVerify++; emit verifyingChanged(); QNetworkReply* reply = _manager->head(QNetworkRequest(url)); QObject::connect(reply, &QNetworkReply::finished, [=]() { uint status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toUInt(); if (status == 200 || (status > 300 && status <= 308)) { uint realSize = reply->header(QNetworkRequest::ContentLengthHeader).toUInt(); // Adjust the expected size foreach(Apps* app, apps) { if (app->type() == type.toLower()) { totalSize += realSize - app->size(); app->setSize(realSize); emit sizeChanged(); emit appsChanged(); } } toVerify--; emit verifyingChanged(); // Verified. Now to complete if (toVerify == 0) download(delta); } else { reset(); QMessageBox::information(NULL, "Error", "The server did not have the " + type + " for the selected 'Download Device'.\n\nPlease try a different search result or a different download device."); } reply->deleteLater(); }); QObject::connect(reply, static_cast(&QNetworkReply::error), [=]() { if (toVerify > 0) { QMessageBox::information(NULL, "Error", "Encountered an error when attempting to verify the " + type +".\n Aborting download."); reset(); } reply->deleteLater(); }); } void download(bool delta) { // Check for deltas if (delta) { // Checking for connected for (int i = 0; i < apps.count(); i++) { if (!apps.at(i)->isMarked() || apps.at(i)->installedVersion().isEmpty()) continue; if (isVersionNewer(apps.at(i)->version(), apps.at(i)->installedVersion(), false)) verifyDelta(i); } } if (toVerify == 0) startDownload(); } void startDownload() { running = true; // OS and Radio files were just verified, so lets check if we already have them for (int i = 0; i < apps.count(); i++) { if (apps[i]->type() == "os" || apps[i]->type() == "radio") { QFileInfo fileInfo(baseDir + "/" + apps[i]->name()); if (fileInfo.exists() && fileInfo.size() == apps[i]->size()) { totalSize -= apps[i]->size(); delete apps[i]; apps.removeAt(i--); maxId--; emit appsChanged(); emit sizeChanged(); } } } if (apps.count() == 0) { running = false; return; } emit idChanged(); // For above, running=true and if any apps changed QDir(baseDir).mkpath("."); downloadNextFile(); } void downloadNextFile() { curSize = 0; QNetworkRequest request(getUrl()); request.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); // Set to a temporary filename _updateFile.setFileName(baseDir + "/." + apps.at(id)->name()); // Obviously something is wrong if this file is bigger than what we want if (_updateFile.size() > apps.at(id)->size()) { if (QMessageBox::warning(nullptr, "Issue", QString("Expected filesize of %1 did not match (Expected %2, Received %3). Ignore the warning or discard the file to try again?").arg(apps.at(id)->name()).arg(apps.at(id)->size()).arg(_updateFile.size()), QMessageBox::Discard, QMessageBox::Ignore) == QMessageBox::Discard) { _updateFile.remove(); } } // In the unlikely event that we missed that we had already downloaded it // Maybe the user copied the relevant files in to a new folder during download? if (_updateFile.size() == apps.at(id)->size()) { size += _updateFile.size(); _updateFile.rename(baseDir + "/" + apps.at(id)->name()); completedFile(); return; } // It is possible that it already exists and, in this case, we would want to resume if (_updateFile.size() > 0) { size += _updateFile.size(); curSize += _updateFile.size(); request.setRawHeader("Range", QString("bytes=%1-").arg(_updateFile.size()).toLatin1()); } _updateFile.open(QIODevice::WriteOnly | QIODevice::Append); QNetworkReply* replydl = _manager->get(request); connect(replydl, &QNetworkReply::readyRead, [=]() { if (!running) { replydl->abort(); replydl->deleteLater(); return; } QByteArray data = replydl->readAll(); // Is this our first receive? if (size == 0) { if (data.startsWith("abort(); replydl->deleteLater(); reset(); return; } } progressSize(data.size()); _updateFile.write(data); }); connect(replydl, &QNetworkReply::finished, [=]() { replydl->deleteLater(); // Then why are we here? if (!running) return; // This should always match otherwise I'm pretty sure something bad happened if (_updateFile.size() == apps.at(id)->size()) _updateFile.rename(baseDir + "/" + apps.at(id)->name()); else { if (QMessageBox::warning(nullptr, "Issue", QString("Expected filesize of %1 did not match (Expected %2, Received %3). Ignore the warning or discard the file to try again?").arg(apps.at(id)->name()).arg(apps.at(id)->size()).arg(_updateFile.size()), QMessageBox::Discard, QMessageBox::Ignore) == QMessageBox::Discard) { // Discard and try again size -= _updateFile.size(); _updateFile.close(); _updateFile.remove(); downloadNextFile(); return; } // Pretend like that didn't happen } completedFile(); }); QObject::connect(replydl, static_cast(&QNetworkReply::error), [=]() { replydl->deleteLater(); reset(); if (replydl->error() == 5) return; // User cancelled qDebug() << "DL Error: " << replydl->error() << replydl->errorString(); }); } void completedFile() { if (nextFile()) { downloadNextFile(); } else { /*if (size != totalSize) QMessageBox::information(NULL, "Warning", QString("Your update completed successfully.\n" "However, the update size does not match the download size. This is probably just be a bug that you can ignore.\n" "Downloaded: %1\n" "Expected: %2") .arg(size) .arg(totalSize));*/ QDesktopServices::openUrl(QUrl(QFileInfo(_updateFile).absolutePath())); reset(); } } void setApps(QList newApps, QString& version) { baseDir = getSaveDir() + "/" + version; // Check which apps user wanted foreach (Apps* newApp, newApps) { if (!newApp->isMarked()) continue; // We have to verify OS/Radio first if (newApp->type() == "application") { // Check which apps user has already downloaded QFileInfo fileInfo(baseDir + "/" + newApp->name()); if (fileInfo.exists() && fileInfo.size() == newApp->size()) continue; } apps.append(new Apps(newApp, this)); totalSize += newApp->size(); } maxId = apps.count(); nextFile(0); emit appsChanged(); } QString getName(int i = CURR_FILE) { if (i == CURR_FILE) i = id; if (i >= 0 && i < maxId) return apps[i]->friendlyName(); else return ""; } QString getUrl(int i = CURR_FILE) { if (i == CURR_FILE) i = id; if (i >= 0 && i < maxId) return apps[i]->url(); else return ""; } int getSize(int i = CURR_FILE) { if (i == CURR_FILE) i = id; if (i >= 0 && i < maxId) return apps[i]->size(); else return 0; } void progressSize(qint64 bytes) { size += bytes; curSize += bytes; curProgress = 100*curSize / apps.at(id)->size(); if (totalSize == 0) progress = 0; else progress = 100*size / totalSize; emit sizeChanged(); } bool nextFile(int i = CURR_FILE) { curSize = 0; if (i == CURR_FILE) id++; else id = i; emit idChanged(); return (id < maxId); } QString baseDir; QList apps; int id, maxId; int progress, curProgress; qint64 curSize, size, totalSize; bool starting; qint16 toVerify; bool running; signals: void idChanged(); void sizeChanged(); void appsChanged(); void verifyingChanged(); private: QFile _updateFile; QNetworkAccessManager* _manager; }; sachesi-2.0.4+ds/src/fs/000077500000000000000000000000001362064257300147455ustar00rootroot00000000000000sachesi-2.0.4+ds/src/fs/fs.cpp000066400000000000000000000076371362064257300160760ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #include "fs.h" #ifdef _WIN32 void fixFileTime(QString filename, int time) { FILETIME pft; LONGLONG ll = Int32x32To64(time, 10000000) + 116444736000000000; pft.dwLowDateTime = (DWORD)ll; pft.dwHighDateTime = ll >> 32; HANDLE fd_handle = CreateFile(filename.toStdWString().c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ|FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); SetFileTime(fd_handle, &pft,(LPFILETIME) nullptr, &pft); CloseHandle(fd_handle); } #endif QFileSystem::QFileSystem(QString filename, QIODevice* file, qint64 offset, qint64 size, QString path, QString imageExt) : QObject(nullptr) , _file(file) , _offset(offset) , _size(size) , _path(path) , _filename(filename) , _imageExt(imageExt) { if (_file == nullptr) { _file = new QFile(filename); _file->open(QIODevice::ReadOnly); size = _file->size(); } } // Free anything we made here QFileSystem::~QFileSystem() { // TODO: Better tracking of whether we created this? // Assume offset of 0 means we decided to let QFileSystem make it if (_offset == 0) { if (_file->isOpen()) _file->close(); delete _file; } } // A method to append numbers to a filename/folder until it is unique QString QFileSystem::uniqueDir(QString name) { int counter = 0; while (QDir(name + (counter++ > 0 ? QString::number(counter + 1) : "")).exists()); return name + (counter > 1 ? QString::number(counter) : ""); } QString QFileSystem::uniqueFile(QString name) { int counter = 0; while (QFile::exists(name + (counter++ > 0 ? QString::number(counter + 1) : ""))); return name + (counter > 1 ? QString::number(counter) : ""); } // A generic method of working out new name based on old name QString QFileSystem::generateName(QString imageExt) { QString name = QFileInfo(_filename).completeBaseName(); if (imageExt.isEmpty()) return uniqueDir(name); return uniqueFile(name + imageExt); } // Entry for requesting an extration of the filesystem image bool QFileSystem::extractImage() { curSize = 0; maxSize = _size; return this->createImage(this->generateName(_imageExt)); } // A generic method for extracting an entire image of maxSize bool QFileSystem::createImage(QString name) { return writeFile(name, _offset, maxSize); } // Entry for requesting an extration of the filesystem contents bool QFileSystem::extractContents() { curSize = 0; maxSize = _size; _path += "/" + this->generateName(); return this->createContents(); } // A method to write writeSize bytes from a QIODevice to a new file, named filename bool QFileSystem::writeFile(QString fileName, qint64 offset, qint64 writeSize, bool absolute) { _file->seek(offset); QFile newFile; if (absolute) newFile.setFileName(fileName); else newFile.setFileName(_path + "/" + fileName); if (!newFile.open(QIODevice::WriteOnly)) return false; qint64 endSize = curSize + writeSize; while (endSize > curSize) { int diff = newFile.write(_file->read(qMin(BUFFER_LEN, endSize - curSize))); if (diff <= 0) return false; increaseCurSize(diff); } newFile.close(); return true; } sachesi-2.0.4+ds/src/fs/fs.h000066400000000000000000000065711362064257300155370ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include #include #include #include #include #include #include // node.mode flags #define QCFM_IS_COMPRESSED ((1 << 22) | (1 << 23) | (1 << 24)) // lzo1x_decompress_safe #define QCFM_IS_LZO_COMPRESSED (1 << 23) // ucl_nrv2b_decompress_8 #define QCFM_IS_UCL_COMPRESSED ((1 << 22) | (1 << 23)) #define QCFM_IS_GZIP_COMPRESSED (1 << 22) #define QCFM_IS_DIRECTORY (1 << 14) #define QCFM_IS_SYMLINK (1 << 13) // QNX6 maximum filename length #define QNX6_MAX_CHARS 510 // Utility function to quickly grab from stream #define READ_TMP(x, y) x y; stream >> y; // Maximum amount to store in RAM at any time between reading QIODevice and writing to disk. #define BUFFER_LEN (qint64)4096 #define FAST_BUFFER_LEN (qint64)819200 #ifdef _WIN32 #include "Windows.h" // We need to update the time of the extracted file based on the unix filesystem it comes from. void fixFileTime(QString filename, int time); #endif // Since QNX files are always LittleEndian but Qt defaults to BigEndian, this wrapper exists class QNXStream : public QDataStream { public: QNXStream(QIODevice * device) : QDataStream(device) { setByteOrder(QDataStream::LittleEndian); } QNXStream(QByteArray * a, QIODevice::OpenMode flags) : QDataStream(a, flags) { setByteOrder(QDataStream::LittleEndian); resetStatus(); } int grabInt() { int tmp; this->operator >>(tmp); return tmp; } unsigned short grabUShort() { unsigned short tmp; this->operator >>(tmp); return tmp; } unsigned char grabUChar() { unsigned char tmp; this->operator >>(tmp); return tmp; } }; class QFileSystem : public QObject { Q_OBJECT public: explicit QFileSystem(QString filename, QIODevice* file = nullptr, qint64 offset = 0, qint64 size = 0, QString path = ".", QString imageExt = ""); ~QFileSystem(); // Basis of size reporting void increaseCurSize(qint64 read) { if (read <= 0) return; curSize += read; emit sizeChanged(read); } QString uniqueDir(QString name); QString uniqueFile(QString name); virtual QString generateName(QString imageExt = ""); bool writeFile(QString fileName, qint64 offset, qint64 writeSize, bool absolute = false); bool extractImage(); virtual bool createImage(QString name); bool extractContents(); virtual bool createContents() = 0; qint64 curSize; qint64 maxSize; signals: void sizeChanged(qint64 delta); protected: QIODevice* _file; qint64 _offset, _size; QString _path, _filename; QString _imageExt; }; sachesi-2.0.4+ds/src/fs/ifs.cpp000066400000000000000000000115751362064257300162430ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #include "ifs.h" namespace FS { binode IFS::createBNode(int offset, qint64 startPos) { QNXStream stream(_file); _file->seek(startPos + offset); binode ind; stream >> ind.mode >> ind.size >> ind.time; ind.name = QString(_file->readLine(QNX6_MAX_CHARS)); if (ind.name == "") ind.name = "."; ind.offset = _file->pos() - startPos; return ind; } QString IFS::generateName(QString imageExt) { _file->seek(_offset + 0x40); QString builder = QString(_file->readLine(16)); // ec_agent, developer or username if (builder == "ec_agent") builder = "prod"; else if (builder == "developer") builder = "trunk"; _file->seek(_offset + 0x50); QStringList buildDateList = QString(_file->readLine(16)).split(' '); // Mmm dd yyyy buildDateList.swap(0,1); // Swap day and month QString buildDate = buildDateList.join(""); _file->seek(_offset + 0xAC); QNXStream stream(_file); READ_TMP(short, build); READ_TMP(quint8, majorminor); READ_TMP(quint8, os); QString ifs_ver = QString("%1.%2.%3.%4") .arg(os) .arg(majorminor >> 3) .arg(majorminor & 0x7) .arg(build); QString name = QString("boot-%1-%2-%3") .arg(ifs_ver) .arg(builder) .arg(buildDate); if (imageExt.isEmpty()) return uniqueDir(name); return uniqueFile(name + imageExt); } void IFS::extractDir(int offset, int numNodes, QString basedir, qint64 startPos) { Q_UNUSED(offset); Q_UNUSED(numNodes); Q_UNUSED(basedir); Q_UNUSED(startPos); // TODO: Nodes seem to fall apart. This must be whole-image compressed? /* QNXStream stream(_file); QDir mainDir(basedir); for (int i = 0; i < numNodes; i++) { binode node = createBNode(offset + (i * 0x20), startPos); qDebug() << QString::number(node.mode,16) << node.name << QString::number(node.offset,16); if (node.mode & QCFM_IS_DIRECTORY) { qDebug() << "Is directory"; //extractRCFSDir(node.offset, node.size / 0x20, node.path_to + "/" + node.name, startPos); } else { if (node.mode & QCFM_IS_SYMLINK) { _file->seek(startPos + node.offset); qDebug() << "Symlink: " << node.name << " -> " << _file->readLine(QNX6_MAX_CHARS); } else if (node.mode & QCFM_IS_COMPRESSED) { qDebug() << "Is compressed file"; } else { qDebug() << "Is regular file"; } } }*/ } bool IFS::createContents() { QNXStream stream(_file); _file->seek(_offset + 1); qint8 type; qint32 boot_size, startup_size; stream >> type; if (type == 3) { // Qualcomm _file->seek(_offset + 0x1020); // boot @ 0 with boot_size; stream >> boot_size; boot_size &= 0xfffff; } else { // 1 // OMAP // No boot.bin boot_size = 0x808; } _file->seek(_offset + boot_size); // Make sure there is a startup header if (_file->read(4) != QByteArray::fromHex("EB7EFF00")) { // It may be offset by 0x1000 boot_size += 0x1000; _file->seek(_offset + boot_size); if (_file->read(4) != QByteArray::fromHex("EB7EFF00")) { return false; // Not a valid IFS image } } _file->seek(_offset + boot_size + 0x20); // startup @ boot_size + 0x100 with startup_size - 0x100 stream >> startup_size; // imagefs @ boot_size + startup_size //extractBootDir(0xC, 1, _path, _offset + boot_size + startup_size); // Temporarily dump the components until a full extraction is available QDir(_path).mkpath("."); // -- Dump boot.bin -- if (boot_size > 0x1100) // Does it have a boot.bin? Some speciality images don't and start at 0x1008 QFileSystem::writeFile("boot.bin", _offset + 0x1100, boot_size - 0x1100); // -- Dump startup.bin QFileSystem::writeFile("startup.bin", _offset + boot_size + 0x100, startup_size - 0x100); // -- Dump imagefs.bin QFileSystem::writeFile("imagefs.bin", _offset + boot_size + startup_size, maxSize - boot_size - startup_size); // Display result QDesktopServices::openUrl(QUrl(_path)); return true; } } sachesi-2.0.4+ds/src/fs/ifs.h000066400000000000000000000025151362064257300157020ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include "fs.h" namespace FS { struct binode { int mode; QString name; int offset; // TODO: Sizes greater than 16-bit? qint16 size; int time; QString path_to; int chunks; }; class IFS : public QFileSystem { Q_OBJECT public: explicit IFS(QString filename, QIODevice* file, qint64 offset, qint64 size, QString path) : QFileSystem(filename, file, offset, size, path, ".ifs") {} binode createBNode(int offset, qint64 startPos); QString generateName(QString imageExt = ""); void extractDir(int offset, int numNodes, QString basedir, qint64 startPos); bool createContents(); }; } sachesi-2.0.4+ds/src/fs/qnx6.cpp000066400000000000000000000336511362064257300163550ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #include "qnx6.h" namespace FS { qint64 QNX6::findIndexFromSig(unsigned char* signature, int startFrom, int distanceFrom, unsigned int maxBlocks, int num) { if (startFrom != -1) _file->seek(startFrom); int readlen = BUFFER_LEN; while (!_file->atEnd() && (maxBlocks-- != 0)) { QByteArray tmp = _file->read(readlen); if (tmp.size() < 0) break; for (int i = 0; i < tmp.size(); i++) { bool found = true; for (int j = 0; j < num; j++) if ((unsigned char)tmp[i+j] != signature[j]) { found = false; break; } if (found) return (qint64)(_file->pos() - tmp.size() + i + distanceFrom); } } return 0; } qinode QNX6::createNode(int node) { QNXStream stream(_file); qinode ind; qint64 base = findNode(node); _file->seek(base); stream >> ind.size; _file->seek(base + 0x10); stream >> ind.time; _file->seek(base + 0x20); stream >> ind.perms; stream.skipRawData(2); int sector; for (int i = 0; i < 16; i++) { stream >> sector; if (sector != -1) ind.sectors.append(sector); } // No sectors appears to be caused by empty files. These need to be extracted if (ind.sectors.count() == 0) { ind.sectors.append(0); } stream >> ind.tiers; return ind; } QPair QNX6::nodeInfo(QNXStream* stream, qint64 offset) { _file->seek(offset); QPair ret = {0, ""}; ret.first = stream->grabInt(); if (ret.first == 0) return ret; int count = stream->grabUChar(); if (count == 0xFF) { _file->seek(offset + 0x8); int item = stream->grabInt(); if (item > lfn.count()) item = 1; _file->seek(findSector(lfn.at(item))); count = stream->grabUShort(); } ret.second = _file->read(count); return ret; } // Specialised function which takes a directory and finds META-INF/MANIFEST.MF and grabs its data void QNX6::extractManifest(int nodenum) { QNXStream stream(_file); foreach(int num, createNode(nodenum).sectors) { for (int i = 0; i < 0x1000; i += 0x20) { QPair info = nodeInfo(&stream, findSector(num) + i); if (info.second == "META-INF") { foreach(int num, createNode(info.first).sectors) { for (int i = 0; i < 0x1000; i += 0x20) { info = nodeInfo(&stream, findSector(num) + i); if (info.second == "MANIFEST.MF") { qinode ind = createNode(info.first); QList sections; if (ind.tiers == 0 && (ind.sectors[0] > 0)) { foreach(int sector, ind.sectors) { if (sector != -1) sections.append(sector); } } else if (ind.tiers > 0 ) { foreach (int sector, ind.sectors) { if (sector == -1) break; _file->seek(findSector(sector)); for (int j = 0; j < 0x400; j++) { stream >> sector; if (sector > 0) sections.append(sector); } } if (ind.tiers == 2) { QList nodes = sections; sections.clear(); foreach (int fn, nodes) { if (fn == -1) break; _file->seek(findSector(fn)); for (int tmpx = 0; tmpx < 1024; tmpx++) { stream >> fn; if (fn > 0) sections.append(fn); } } } } QByteArray manifestDump; if (ind.size != 0) { foreach(int section, sections) { _file->seek(findSector(section)); int len = sectorSize; if (section == sections.last() && (ind.size % sectorSize)) len = ind.size % sectorSize; manifestDump.append(_file->read(len)); } } QString name = ""; QString version = ""; QString arch = ""; QString strSigned = ""; foreach(QByteArray manifestString, manifestDump.split('\n')) { QString tmp = QString(manifestString).simplified(); if (tmp.startsWith("Package-Name:")) { name = tmp.split(": ").last().split('.').last(); } else if (tmp.startsWith("Package-Version:")) { version = tmp.split(": ").last(); } else if (tmp.startsWith("Package-Architecture:")) { arch = tmp.split(": ").last(); } else if (tmp.startsWith("Package-Author-Certificate-Hash:")) { strSigned = "+signed"; } else if (tmp.startsWith("Archive-Asset-Name:")) { manifestApps.append(tmp.split(": ").last()); } } if (currentZip == nullptr && name != "") { currentZip = new QuaZip(QString("%1/%2-%3-nto+%4%5.bar").arg(_path).arg(name).arg(version).arg(arch).arg(strSigned)); emit currentNameChanged(QString("%1-%2").arg(name).arg(version)); currentZip->open(QuaZip::mdCreate); } // Leave now return; } } } return; } } } } void QNX6::extractDir(int nodenum, QString basedir, int tier) { QDir mainDir; if (!extractApps) mainDir.mkdir(basedir); QNXStream stream(_file); qinode ind = createNode(nodenum); foreach(int num, ind.sectors) { for (int i = 0; i < 0x80; i++) { // TODO: Nice place to check if we want to quit QPair info = nodeInfo(&stream, findSector(num) + (i * 0x20)); if (info.second == "." || info.second == "..") continue; qinode ind2 = createNode(info.first); if (ind2.perms & QCFM_IS_DIRECTORY) { if (extractApps) { if (info.second == "apps" && tier == 0) { mainDir.mkdir(basedir); extractDir(info.first, basedir, 1); continue; } else if (tier == 1) { currentZip = nullptr; extractManifest(info.first); if (currentZip != nullptr) { extractDir(info.first, "", 2); currentZip->close(); manifestApps.clear(); delete currentZip; } continue; } else if (tier == 2) { extractDir(info.first, info.second, 3); continue; } } extractDir(info.first, basedir + "/" + info.second, tier ? tier + 1 : 0); continue; } if (extractApps) { if (tier <= 1) continue; QString thisFile = (tier == 2) ? info.second : (basedir + "/" + info.second); if (!manifestApps.isEmpty() && !(tier == 3 && basedir == "META-INF") && !manifestApps.contains(thisFile)) continue; } QList sections; if (ind2.tiers == 0 && (ind2.sectors[0] > 0)) { foreach(int sector, ind2.sectors) { if (sector != -1) sections.append(sector); } } else if (ind2.tiers > 0 ) { foreach (int sector, ind2.sectors) { if (sector == -1) break; _file->seek(findSector(sector)); for (int j = 0; j < 0x400; j++) { stream >> sector; if (sector > 0) sections.append(sector); } } if (ind2.tiers == 2) { QList nodes = sections; sections.clear(); foreach (int fn, nodes) { if (fn == -1) break; _file->seek(findSector(fn)); for (int tmpx = 0; tmpx < 1024; tmpx++) { stream >> fn; if (fn > 0) sections.append(fn); } } } } QuaZipFile* zipFile = 0; QFile* newFile = 0; if (extractApps) { Q_ASSERT(currentZip != nullptr); zipFile = new QuaZipFile(currentZip); QuaZipNewInfo newInfo((tier == 2) ? info.second : (basedir + "/" + info.second)); newInfo.setPermissions(QFileDevice::Permission(0x7774)); newInfo.dateTime.setTime_t(ind.time); zipFile->open(QIODevice::WriteOnly, newInfo); } else { newFile = new QFile(basedir + "/" + info.second); newFile->open(QIODevice::WriteOnly); } if (ind2.size != 0) { foreach(int section, sections) { _file->seek(findSector(section)); int len = sectorSize; if (section == sections.last() && (ind2.size % sectorSize)) len = ind2.size % sectorSize; QByteArray tmp = _file->read(len); increaseCurSize(tmp.size()); if (extractApps) zipFile->write(tmp); else newFile->write(tmp); } } if (extractApps) { Q_ASSERT(zipFile->isOpen()); zipFile->close(); delete zipFile; } else { newFile->close(); #ifdef _WIN32 fixFileTime(newFile->fileName(), ind.time); #endif delete newFile; } } } } bool QNX6::createContents() { _file->seek(_offset+8); QNXStream stream(_file); READ_TMP(unsigned char, typeQNX); // 0x10 = no offset; 0x08 = has offset unsigned char qnx6Sig[] = {0x22, 0x11, 0x19, 0x68}; unsigned char fsSig[] = {0xDD, 0xEE, 0xE6, 0x97}; if ( (findIndexFromSig(qnx6Sig, -1, 0, 1)) == 0) { return false; } if ( (_offset = findIndexFromSig(fsSig, -1, 0)) == 0) { return false; } _file->seek(_offset+48); stream >> sectorSize; if (sectorSize % 512) { return false; } _offset += sectorSize; // Find sectorOffset qinode ind2 = createNode(1); // Try all offsets if (typeQNX == 0x10) { sectorOffset = 0; _file->seek((ind2.sectors[0] - sectorOffset)*sectorSize + _offset); READ_TMP(int, offsetCheck); if (offsetCheck != 1) typeQNX = 8; } if (typeQNX != 0x10) { for (sectorOffset = 0x6320; sectorOffset < 0x6400; sectorOffset += 0x10) { _file->seek((ind2.sectors[0] - sectorOffset)*sectorSize + _offset); READ_TMP(int, offsetCheck); if (offsetCheck == 1) break; } } for (qint64 s = _offset - 0xF10; true; s+=4) { _file->seek(s); READ_TMP(int, next); if (next == -1) break; _file->seek(findSector(next)); for (int i = 0; i < 0x400; i++) { stream >> next; if (next > 0) lfn.append(next); } } extractDir(1, _path, 0); emit currentNameChanged(""); QDesktopServices::openUrl(QUrl(_path)); return true; } } sachesi-2.0.4+ds/src/fs/qnx6.h000066400000000000000000000041761362064257300160220ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include #include #include #include "fs.h" namespace FS { struct qinode { int size; QList sectors; quint8 tiers; int time; quint16 perms; }; class QNX6 : public QFileSystem { Q_OBJECT public: explicit QNX6(QString filename, QIODevice* file, qint64 offset, qint64 size, QString path) : QFileSystem(filename, file, offset, size, path, ".qnx6") , currentZip(nullptr) {} inline qint64 findSector(qint64 sector) { return _offset + ((sector - sectorOffset) * sectorSize); } inline qint64 findNode(int node) { return _offset + (0x80 * (node - 1)); } qint64 findIndexFromSig(unsigned char* signature, int startFrom, int distanceFrom, unsigned int maxBlocks = -1, int num = 4); qinode createNode(int node); // TODO: Read ./.rootfs.os.version or ./var/pps/system/installer/coreos/0 //QString generateName(QString imageExt = ""); void extractManifest(int nodenum); void extractDir(int offset, QString basedir, int numNodes); bool createContents(); // TODO: These need to have a better method of passing from Splitter bool extractApps; signals: void currentNameChanged(QString name); private: QPair nodeInfo(QNXStream* stream, qint64 offset); quint16 sectorSize; quint16 sectorOffset; QList lfn; QuaZip* currentZip; QList manifestApps; }; } sachesi-2.0.4+ds/src/fs/rcfs.cpp000066400000000000000000000160561362064257300164160ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #include "rcfs.h" #ifdef _LZO2_SHARED #include #else #include "lzo.h" #endif namespace FS { rinode RCFS::createNode(int offset) { QNXStream stream(_file); _file->seek(_offset + offset + 4); rinode ind; stream >> ind.mode >> ind.nameoffset >> ind.offset >> ind.size >> ind.time; _file->seek(_offset + ind.nameoffset); ind.name = QString(_file->readLine(QNX6_MAX_CHARS)); if (ind.name == "") ind.name = "."; return ind; } QString RCFS::generateName(QString imageExt) { _file->seek(_offset + 8); QString board = "rcfs"; QString variant = "unk"; QString cpu = "unk"; QString version = "unk"; if (_file->readLine(4).startsWith("fs-")) { _file->seek(_offset + 0x1038); QNXStream stream(_file); READ_TMP(qint32, offset); rinode dotnode = createNode(offset); for (int i = 0; i < dotnode.size / 0x20; i++) { rinode slashdotnode = createNode(dotnode.offset + (i * 0x20)); if (slashdotnode.name == "etc") { for (int i = 0; i < slashdotnode.size / 0x20; i++) { rinode node = createNode(slashdotnode.offset + (i * 0x20)); if (node.name == "os.version" || node.name == "radio.version") { QByteArray versionData = extractFile(_offset + node.offset, node.size, node.mode); version = QString(versionData).simplified(); } } } if (slashdotnode.name.endsWith(".tdf")) { QByteArray boardData = extractFile(_offset + slashdotnode.offset, slashdotnode.size, slashdotnode.mode); foreach (QString config, QString(boardData).split('\n')) { if (config.startsWith("CPU=")) { cpu = config.split('=').last().remove('"'); } else if (config.startsWith("BOARD=")) { board = config.split('=').last().remove('"'); if (board != "radio") board.prepend("os."); } else if (config.startsWith("BOARD_CONFIG=") || config.startsWith("RADIO_BOARD_CONFIG=")) { variant = config.split('=').last().remove('"'); } } } } } QString name = QString("%1.%2.%3.%4") .arg(board) .arg(variant) .arg(version) .arg(cpu); if (imageExt.isEmpty()) return uniqueDir(name); return uniqueFile(name + imageExt); } // Returning the array of data might be dangerous if it's huge. Consider taking a var instead QByteArray RCFS::extractFile(qint64 node_offset, int node_size, int node_mode) { QByteArray ret; _file->seek(node_offset); QNXStream stream(_file); if (node_mode & QCFM_IS_LZO_COMPRESSED) { READ_TMP(int, next); int chunks = (next - 4) / 4; QList sizes, offsets; offsets.append(next); for (int s = 0; s < chunks; s++) { stream >> next; offsets.append(next); sizes.append(offsets[s+1] - offsets[s]); } char* buffer = new char[node_size]; foreach(int size, sizes) { char* readData = new char[size]; _file->read(readData, size); size_t write_len = 0x4000; lzo1x_decompress_safe(reinterpret_cast(readData), size, reinterpret_cast(buffer), &write_len, nullptr); ret.append(buffer, write_len); delete [] readData; } delete [] buffer; } else { for (qint64 i = node_size; i > 0;) { QByteArray data = _file->read(qMin(BUFFER_LEN, i)); i -= data.size(); ret.append(data); } } return ret; } void RCFS::extractDir(int offset, int numNodes, QString basedir, qint64 _offset) { QNXStream stream(_file); QDir mainDir(basedir); for (int i = 0; i < numNodes; i++) { rinode node = createNode(offset + (i * 0x20)); node.path_to = basedir; QString absName = node.path_to + "/" + node.name; if (node.mode & QCFM_IS_DIRECTORY) { mainDir.mkpath(node.name); if (node.size > 0) extractDir(node.offset, node.size / 0x20, absName, _offset); } else { _file->seek(node.offset + _offset); if (node.mode & QCFM_IS_SYMLINK) { #ifdef _WIN32 QString lnkName = absName + ".lnk"; QFile::link(node.path_to + "/" + _file->readLine(QNX6_MAX_CHARS), lnkName); fixFileTime(lnkName, node.time); #else QFile::link(node.path_to + "/" + _file->readLine(QNX6_MAX_CHARS), absName); #endif continue; } if (node.mode & QCFM_IS_LZO_COMPRESSED) { QFile newFile(absName); newFile.open(QFile::WriteOnly); READ_TMP(int, next); int chunks = (next - 4) / 4; QList sizes, offsets; offsets.append(next); for (int s = 0; s < chunks; s++) { stream >> next; offsets.append(next); sizes.append(offsets[s+1] - offsets[s]); } char* buffer = new char[node.size]; foreach(int size, sizes) { char* readData = new char[size]; _file->read(readData, size); size_t write_len = 0x4000; lzo1x_decompress_safe(reinterpret_cast(readData), size, reinterpret_cast(buffer), &write_len, nullptr); newFile.write(buffer, (qint64)write_len); increaseCurSize(size); // Uncompressed size delete [] readData; } delete [] buffer; newFile.close(); } else { writeFile(absName, node.size, true); } #ifdef _WIN32 fixFileTime(absName, node.time); #endif } } } bool RCFS::createContents() { _file->seek(_offset + 0x1038); QNXStream stream(_file); READ_TMP(qint32, offset); extractDir(offset, 1, _path, _offset); // Display result QDesktopServices::openUrl(QUrl(_path)); return true; } } sachesi-2.0.4+ds/src/fs/rcfs.h000066400000000000000000000025671362064257300160650ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include "fs.h" namespace FS { struct rinode { int mode; QString name; int nameoffset; int offset; int size; int time; QString path_to; int chunks; }; class RCFS : public QFileSystem { Q_OBJECT public: explicit RCFS(QString filename, QIODevice* file, qint64 offset, qint64 size, QString path) : QFileSystem(filename, file, offset, size, path, ".rcfs") {} rinode createNode(int offset); QString generateName(QString imageExt = ""); QByteArray extractFile(qint64 node_offset, int node_size, int node_mode); void extractDir(int offset, int numNodes, QString basedir, qint64 startPos); bool createContents(); }; } sachesi-2.0.4+ds/src/installer.cpp000066400000000000000000002003451362064257300170420ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #define DEBUG_LOG 0 #include "installer.h" #include "ports.h" #include #include #include #include InstallNet::InstallNet( QObject* parent) : QObject(parent), device(nullptr), manager(nullptr), reply(nullptr), cookieJar(nullptr), _wrongPass(false), _loginBlock(false), _state(0), _dlBytes(0), _dlTotal(0), _dgProgress(-1), _curDGProgress(-1), _completed(false), _extractInstallZip(false), _allowDowngrades(false), _installing(false), _restoring(false), _backing(false), _hadPassword(true), currentBackupZip(nullptr), _zipFile(nullptr) { #ifdef _MSC_VER WSAStartup(MAKEWORD(2,0), &wsadata); #endif connectTimer = new QTimer(); connectTimer->setInterval(3000); connectTimer->start(); connect(connectTimer, SIGNAL(timeout()), this, SLOT(login())); QSettings settings("Qtness","Sachesi"); connect(&_back, SIGNAL(curModeChanged()), this, SIGNAL(backStatusChanged())); connect(&_back, SIGNAL(curSizeChanged()), this, SIGNAL(backCurProgressChanged())); connect(&_back, SIGNAL(numMethodsChanged()), this, SIGNAL(backMethodsChanged())); logFile = new QTemporaryFile("XXXXXXXX.txt"); logFile->open(); // This will autoclose and autoremove by default when ~InstallNet QByteArray hashedPass = settings.value("pass", "").toByteArray(); if (hashedPass.isEmpty()) { _password = ""; } else { int passSize = QByteArray::fromBase64(hashedPass.left(4))[0]; hashedPass = QByteArray::fromBase64(hashedPass.mid(4)); char * decPass = new char[passSize+1]; for (int i = 0; i < passSize; i++) { decPass[i] = hashedPass[i] ^ ((0x40 + 5 * i - passSize) % 127); } decPass[passSize] = 0; _password = QString(decPass); delete decPass; } emit newPassword(_password); login(); } InstallNet::~InstallNet() { // Windows has issues autoremoving logFile logFile->close(); logFile->remove(); #ifdef _MSC_VER WSACleanup(); #endif } QNetworkRequest InstallNet::setData(QString page, QString contentType) { QNetworkRequest request = QNetworkRequest(); request.setUrl(QUrl("https://" + _ip + "/cgi-bin/" + page)); request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, "QNXWebClient/1.0"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/" + contentType); return request; } QNetworkReply* InstallNet::postQuery(QString page, QString contentType, const QUrlQuery& query) { reply = manager->post(setData(page, contentType), query.encodedQuery()); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(restoreError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(finished()), this, SLOT(restoreReply())); return reply; } QNetworkReply* InstallNet::getQuery(QString page, QString contentType) { reply = manager->get(setData(page, contentType)); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(restoreError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(finished()), this, SLOT(restoreReply())); return reply; } void InstallNet::scanProps() { if (checkLogin()) { QUrlQuery postData; postData.addQueryItem("Get Dynamic Properties","Get Dynamic Properties"); postQuery("dynamicProperties.cgi", "x-www-form-urlencoded", postData); } } BarInfo InstallNet::checkInstallableInfo(QString name, bool blitz) { BarInfo barInfo = {name, "", "", NotInstallableType}; // Check if it's a 'hidden' file as we use these for temporary file downloads. if (QFileInfo(name).fileName().startsWith('.')) return barInfo; QuaZipFile manifest(name, "META-INF/MANIFEST.MF", QuaZip::csSensitive); if (!manifest.open(QIODevice::ReadOnly)) return barInfo; QString appName, type; while (!manifest.atEnd()) { QByteArray newLine = manifest.readLine(); if (newLine.startsWith("Package-Name:") || newLine.startsWith("Patch-Package-Name:")) { appName = newLine.split(':').last().simplified(); if (newLine.startsWith("Patch") && type == "system") { if (appName.contains("radio")) barInfo.type = RadioType; else barInfo.type = OSType; } } else if (newLine.startsWith("Package-Type:") || newLine.startsWith("Patch-Package-Type:")) { type = newLine.split(':').last().simplified(); if (type == "system" && barInfo.type == NotInstallableType) barInfo.type = OSType; } else if (newLine.startsWith("Package-Version:")) { barInfo.version = newLine.split(':').last().simplified(); } else if (newLine.startsWith("Package-Id:")) { barInfo.packageid = newLine.split(':').last().simplified(); } else if (newLine.startsWith("System-Type:")) { if (newLine.split(':').last().simplified() == "radio") barInfo.type = RadioType; } else if (newLine.startsWith("Archive-Asset-Name:")) { // A sign we need to get out of here when it is listing assets break; } } // Check if we are about to make a huge mistake! if (barInfo.type == OSType) { if (!_allowDowngrades && isVersionNewer(device->os, barInfo.version, true)) { setNewLine(QString("OS %1 skipped. Newer version is installed(%2)").arg(barInfo.version).arg(device->os)); barInfo.type = NotInstallableType; return barInfo; } QString installableOS = appName.split("os.").last().remove(".desktop").replace("verizon", "factory"); if (_knownConnectedOSType != "" && installableOS != _knownConnectedOSType && !(installableOS.contains("8974") && _knownConnectedOSType.contains("8974"))) { if (blitz) { barInfo.type = NotInstallableType; return barInfo; } else { int choice = QMessageBox::critical(nullptr, "WARNING", "The OS file you have selected to install is for a different device!\nOS Type: " + installableOS + "\nYour Device: " + _knownConnectedOSType, "Ignore Warning [Stupid]", "Skip OS", "Cancel Install", 2); if (choice == 1) { barInfo.type = NotInstallableType; return barInfo; } if (choice == 2) { barInfo.name = "EXIT"; return barInfo; } } } } else if (barInfo.type == RadioType) { if (!_allowDowngrades && isVersionNewer(device->radio, barInfo.version, true)) { setNewLine(QString("Radio %1 skipped. Newer version is installed(%2)").arg(barInfo.version).arg(device->radio)); barInfo.type = NotInstallableType; return barInfo; } QString installableRadio = appName.split("radio.").last().remove(".omadm"); if (_knownConnectedRadioType != "" && installableRadio != _knownConnectedRadioType) { if (blitz) { barInfo.type = NotInstallableType; return barInfo; } else { int choice = QMessageBox::critical(nullptr, "WARNING", "The Radio file you have selected to install is for a different device!\nRadio Type: " + installableRadio + "\nYour Device: " + _knownConnectedRadioType, "Ignore Warning [Stupid]", "Skip Radio", "Cancel Install", 2); if (choice == 1) { barInfo.type = NotInstallableType; return barInfo; } if (choice == 2) { barInfo.name = "EXIT"; return barInfo; } } } } // Check if we are upgrading firmware or just installing apps. if (type == "system") { // Only if installing setNewLine(QString("Installing ") + (barInfo.type == OSType ? "OS: " : "Radio: " ) + barInfo.version + ""); setFirmwareUpdate(true); } else if (!type.isEmpty()) barInfo.type = AppType; manifest.close(); return barInfo; } void InstallNet::install(QList files) { _installInfo.clear(); setFirmwareUpdate(false); QList filenames; // Zip install if (files.count() == 1 && files.first().toLocalFile().endsWith(".zip")) { // First, ensure it is a real zip QFile testZip(files.first().toLocalFile()); testZip.open(QIODevice::ReadOnly); if (testZip.read(2).toHex() != "504b") { QMessageBox::information(nullptr, "Error", "The selected .zip file is, in fact, not a zip file."); return; } testZip.close(); // A collection of bars _extractInstallZip = true; emit extractInstallZipChanged(); QuaZip zip(files.first().toLocalFile()); zip.open(QuaZip::mdUnzip); QuaZipFile file(&zip); QFileInfo zipInfo(zip.getZipName()); QString baseName = zipInfo.absolutePath() + "/" + zipInfo.completeBaseName(); if (!QDir(baseName).mkpath(".")) QMessageBox::information(nullptr, "Error", "Was unable to extract the zip container."); for(bool f=zip.goToFirstFile(); f; f=zip.goToNextFile()) { QString thisFile = baseName + "/" + zip.getCurrentFileName().split('/').last(); if (QFile::exists(thisFile)) { QuaZipFileInfo info; zip.getCurrentFileInfo(&info); if (QFile(thisFile).size() == info.uncompressedSize) { filenames.append(thisFile); continue; } else { QFile(thisFile).remove(); } } if (!file.open(QIODevice::ReadOnly)) continue; // Check we have a zip if (file.read(2).toHex() == "504b") { QFile writeFile(thisFile); if (!writeFile.open(QIODevice::WriteOnly)) { file.close(); continue; } writeFile.write(QByteArray::fromHex("504b")); while (!file.atEnd()) { qApp->processEvents(); writeFile.write(file.read(8192000)); } filenames.append(thisFile); } file.close(); } _extractInstallZip = false; emit extractInstallZipChanged(); } else { // Grab file names (first pass) foreach(QUrl url, files) { if (!url.isLocalFile()) continue; QString name = url.toLocalFile(); if (QFileInfo(name).isDir()) { QStringList barFiles = QDir(name).entryList(QStringList("*.bar")); foreach (QString barFile, barFiles) { filenames.append(name + "/" + barFile); } } else { filenames.append(name); } } } // Detect Blitz files (second pass) BlitzInfo blitz(filenames, _knownConnectedOSType, _knownConnectedRadioType); if (blitz.isBlitz()) { setNewLine(QString("%1 Blitz detected. %2 OSes and %3 Radios") .arg(blitz.isSafe() ? "Safe " : "Unsafe") .arg(blitz.osCount) .arg(blitz.radioCount)); if (_knownConnectedRadioType == "" && blitz.radioCount > 1) { QMessageBox::critical(nullptr, "Error", "Your device is reporting no Radio. The blitz install is unable to detect the correct Radio for your system and cannot continue."); return; } else if (_knownConnectedOSType == "" && blitz.osCount > 1) { QMessageBox::critical(nullptr, "Error", "Your device is reporting no OS. The blitz install is unable to detect the correct OS for your system and cannot continue."); return; } if (!blitz.isSafe()) { QMessageBox::critical(nullptr, "Error", QString("The blitz file does not contain compatible firmware.") + (!blitz.osIsSafe ? "\nNo compatible OS" : "") + (!blitz.radioIsSafe ? "\nNo compatible Radio" : "")); return; } } int skipCount = 0; // Detect everything (third pass) foreach(QString barFile, filenames) { BarInfo info = checkInstallableInfo(barFile, blitz.isBlitz()); if (info.name == "EXIT") return setNewLine("Install aborted."); else if (info.type == AppType) { foreach(Apps* app, _appList) { if (!_allowDowngrades && info.packageid == app->packageId()) { if (isVersionNewer(app->version(), info.version, true)) { setNewLine(QString("%1 was skipped. Version %2 already installed").arg(QFileInfo(info.name).completeBaseName()).arg(app->version())); info.type = NotInstallableType; } break; } } } if (info.type != NotInstallableType) _installInfo.append(info); else skipCount++; } if (_installInfo.isEmpty()) { setNewLine(QString("None of the selected files were installable. Skipped %2.") .arg(skipCount)); return; } setNewLine(QString("Installing %1 .bar(s).%2.") .arg(_installInfo.count()) .arg(skipCount > 0 ? QString(" Skipped %1").arg(skipCount) : "")); install(); } void InstallNet::install() { setInstalling(true); if (checkLogin()) { QUrlQuery postData; _downgradePos = 0; _downgradeInfo.clear(); foreach (auto filePair, _installInfo) _downgradeInfo.append(filePair.name); emit dgPosChanged(); emit dgMaxPosChanged(); _dlDoneBytes = 0; _dlOverallTotal = 0; for(QString filename : _downgradeInfo) _dlOverallTotal += QFile(filename).size(); if (_dlOverallTotal * 1.5 > device->freeSpace) { QMessageBox::critical(nullptr, "Free Space", QString("Sachesi has determined you may not have enough free space on your device to continue this update.\n" "It is estimated that you would need %1 GB but you only have %2 GB. Please free up some space.") .arg(QString::number((_dlOverallTotal * 1.5) / 1024 / 1024 / 1024, 'g', 3)) .arg(QString::number((device->freeSpace * 1.0) / 1024 / 1024 / 1024, 'g', 3))); setNewLine("Install aborted. No free space."); setInstalling(false); return; } postData.addQueryItem("mode", _firmwareUpdate ? "os" : "bar"); postData.addQueryItem("size", QString::number(_dlOverallTotal)); postQuery("update.cgi", "x-www-form-urlencoded", postData); } } void InstallNet::uninstall(QStringList packageids, bool firmwareUpdate) { Q_UNUSED(firmwareUpdate) // Dangerous! // Tested with OS and it removed the old OS. Not entirely what I wanted. if (packageids.isEmpty()) return; setInstalling(true); if (checkLogin()) { QUrlQuery postData; _installInfo.clear(); _downgradePos = 0; _downgradeInfo = packageids; emit dgPosChanged(); emit dgMaxPosChanged(); postData.addQueryItem("mode", "app"); postData.addQueryItem("size", "0"); postQuery("update.cgi", "x-www-form-urlencoded", postData); } } bool InstallNet::uninstallMarked() { QStringList marked; bool firmwareUpdate = false; for(Apps* app : _appList) { if (app->isMarked()) { marked.append(app->packageId()); app->setIsMarked(false); if (app->type() != "application") firmwareUpdate = true; app->setType(""); } } if (marked.isEmpty()) return false; uninstall(marked, firmwareUpdate); return true; } // For finding Blackberry Link backup path /*#ifdef _WIN32 QFile linkSettings(QDir::homePath()+"/AppData/Roaming/Research In Motion/BlackBerry 10 Desktop/Settings.config"); linkSettings.open(QIODevice::WriteOnly); QXmlStreamReader xml(&linkSettings); for (xml.readNext(); !xml.atEnd(); xml.readNext()) { if (xml.isStartElement()) { if (xml.name() == "Configuration" && xml.attributes().count() > 1 && xml.attributes().at(0).value() == "BackupFolderLocation") { dir = xml.attributes().at(1).value().toString(); } } } linkSettings.close(); #endif*/ void InstallNet::backup() { setBacking(true); if (checkLogin()) { currentBackupZip = new QuaZip(_backupFileName); currentBackupZip->setZip64Enabled(true); currentBackupZip->open(QuaZip::mdCreate); if (!currentBackupZip->isOpen()) { QMessageBox::critical(nullptr, "Error", "Unable to write backup. Please ensure you have permission to write to " + _backupFileName); delete currentBackupZip; currentBackupZip = nullptr; setBacking(false); return; } QuaZipFile* manifest; manifest = new QuaZipFile(currentBackupZip); QuaZipNewInfo newInfo("Manifest.xml"); newInfo.setPermissions(QFileDevice::Permission(0x7774)); manifest->open(QIODevice::WriteOnly, newInfo); QString manifestXML = "\n" "3.0" "pin + "\">" ""; foreach(BackupCategory* cat, _back.categories) { if (_back.modeString().contains(cat->id)) manifestXML.append("id + "\" name=\"" + cat->name + "\" count=\"" + cat->count + "\" bytesize=\"" + cat->bytesize + "\" keyid=\"" + device->bbid + "\" perimetertype=\"" + cat->perimetertype + "\"/>"); } manifestXML.append(""); manifest->write(manifestXML.toStdString().c_str()); manifest->close(); delete manifest; QUrlQuery postData; //postData.addQueryItem("action", "backup"); if (_back.rev() == 2) { /*QString packageXML = ""; packageXML += ""; packageXML += ""; packageXML += ""; QNetworkRequest request = setData("backup.cgi?opt=rev2&mode=" + _back.modeString(), "x-www-form-urlencoded"); reply = manager->post(request, packageXML.toLatin1()); //connect(reply, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(installProgress(qint64,qint64))); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(restoreError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(finished()), this, SLOT(restoreReply()));*/ postData.addQueryItem("mode", _back.modeString()); postData.addQueryItem("opt", "rev2"); postQuery("backup.cgi", "x-www-form-urlencoded", postData); } else { postData.addQueryItem("mode", _back.modeString()); postQuery("backup.cgi", "x-www-form-urlencoded", postData); } } } void InstallNet::backup(QUrl url, int options) { if (!url.isLocalFile()) return; _backupFileName = url.toLocalFile(); if(!_backupFileName.endsWith(".bbb")) _backupFileName.append(".bbb"); if (_backupFileName.isEmpty()) return; _back.setMode(options); _back.setCurMode(0); backup(); } void InstallNet::backupQuery() { if (checkLogin()) { QUrlQuery postData; //postData.addQueryItem("action", "backup"); postData.addQueryItem("query", "list"); if (_back.rev() == 2) postData.addQueryItem("opt", "rev2"); // Per-app backups postQuery("backup.cgi", "x-www-form-urlencoded", postData); } } void InstallNet::restore() { setRestoring(true); if (checkLogin()) { QUrlQuery postData; postData.addQueryItem("action", "restore"); postData.addQueryItem("mode", _back.modeString()); postData.addQueryItem("totalsize", QString::number(_back.maxSize())); postQuery("backup.cgi", "x-www-form-urlencoded", postData); } } void InstallNet::restore(QUrl url, int options) { if (!url.isLocalFile()) return; _backupFileName = url.toLocalFile(); if (!QFile::exists(_backupFileName)) return; currentBackupZip = new QuaZip(_backupFileName); currentBackupZip->open(QuaZip::mdUnzip); if (!currentBackupZip->isOpen()) { QMessageBox::critical(nullptr, "Error", "Could not open backup file."); delete currentBackupZip; currentBackupZip = nullptr; return; } for (int i = 0; i < _back.numMethods(); i++) { if (options & (1 << i)) { // We want to restore this file currentBackupZip->setCurrentFile(QString("Archive/" + _back.stringFromMode(i) + ".tar")); if (!currentBackupZip->hasCurrentFile()) { // But this file doesn't exist? options &= ~(1 << i); } else { // Set the size from this file QuaZipFileInfo info; currentBackupZip->getCurrentFileInfo(&info); _back.setCurMaxSize(i, info.uncompressedSize); qint64 startSize = (_back.maxSize() > 1) ? _back.maxSize() : 0; _back.setMaxSize(startSize + info.uncompressedSize); } } } if (!options) return; _back.setMode(options); _back.setCurMode(0); restore(); } void InstallNet::wipe() { if (QMessageBox::critical(nullptr, "Loss of data", "Are you sure you want to wipe your device?\nThis will result in a permanent loss of data.", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) return; QUrlQuery postData; postData.addQueryItem("wipe", "wipe"); postQuery("wipe.cgi", "x-www-form-urlencoded", postData); } void InstallNet::startRTAS() { QUrlQuery postData; postData.addQueryItem("wipe", "start_rtas"); postQuery("wipe.cgi", "x-www-form-urlencoded", postData); } void InstallNet::newPin(QString pin) { QUrlQuery postData; postData.addQueryItem("wipe", "pin"); postData.addQueryItem("newpin", pin.left(8)); postQuery("wipe.cgi", "x-www-form-urlencoded", postData); } void InstallNet::resignNVRAM() { QUrlQuery postData; postData.addQueryItem("wipe", "re_sign"); postQuery("wipe.cgi", "x-www-form-urlencoded", postData); } void InstallNet::factorywipe() { if (QMessageBox::critical(nullptr, "Loss of data", "Are you sure you want to wipe your device?\nThis will result in a permanent loss of data.", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) return; QUrlQuery postData; postData.addQueryItem("wipe", "wipe"); postData.addQueryItem("factorywipe", "1"); postData.addQueryItem("nopoweroff", "1"); postQuery("wipe.cgi", "x-www-form-urlencoded", postData); } void InstallNet::reboot() { QUrlQuery postData; postData.addQueryItem("reset", "true"); postQuery("reset.cgi", "x-www-form-urlencoded", postData); } void InstallNet::getPIN() { QUrlQuery postData; postData.addQueryItem("wipe", "getpin"); postQuery("wipe.cgi", "x-www-form-urlencoded", postData); } void InstallNet::dumpLogs() { QUrlQuery postData; postData.addQueryItem("facility", "dumplog"); postQuery("support.cgi", "x-www-form-urlencoded", postData); } void InstallNet::setActionProperty(QString name, QString value) { QUrlQuery postData; postData.addQueryItem("action", "set"); postData.addQueryItem("name", name); postData.addQueryItem("value", value); postQuery("dynamicProperties.cgi", "x-www-form-urlencoded", postData); } void InstallNet::login() { if (_loginBlock || _wrongPass) return; // How did we get here? if (_state) { checkLogin(); return; } QStringList ips; int flags = QNetworkInterface::IsUp | QNetworkInterface::IsRunning | QNetworkInterface::CanBroadcast | QNetworkInterface::CanMulticast; foreach(QNetworkInterface inter, QNetworkInterface::allInterfaces()) { if ((inter.flags() & (flags | QNetworkInterface::IsLoopBack)) == flags) { foreach(QNetworkAddressEntry addr, inter.addressEntries()) { if (addr.ip().protocol() == QAbstractSocket::IPv4Protocol) { QList addrParts; foreach(QString addrString, addr.ip().toString().split('.')) addrParts.append(addrString.toInt()); if (addrParts.at(0) > 100) { int masked = addrParts.at(3) - 1; if (masked == 0) masked = 128; ips.append(QString("%1.%2.%3.%4").arg(addrParts.at(0)).arg(addrParts.at(1)).arg(addrParts.at(2)).arg(masked)); } } } } } ips.removeDuplicates(); // Keep the user updated with how many potential devices we are dealing with here. setPossibleDevices(ips.count()); if (ips.isEmpty()) return; if (manager == nullptr) { manager = new SslNetworkAccessManager(); manager->setProxy(QNetworkProxy::NoProxy); } if (cookieJar == nullptr) { cookieJar = new QNetworkCookieJar(this); manager->setCookieJar(cookieJar); } for(QString ip_addr : ips) { QNetworkRequest request; request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, "QNXWebClient/1.0"); request.setAttribute(QNetworkRequest::CustomVerbAttribute, ip_addr); request.setUrl(QUrl("http://"+ip_addr+"/cgi-bin/discovery.cgi")); QNetworkReply* replyTemp = manager->get(request); connect(replyTemp, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(restoreError(QNetworkReply::NetworkError))); connect(replyTemp, SIGNAL(finished()), this, SLOT(discoveryReply())); } } void InstallNet::discoveryReply() { QNetworkReply* replyTemp = (QNetworkReply*)sender(); QNetworkRequest request = replyTemp->request(); QString ip_addr = request.attribute(QNetworkRequest::CustomVerbAttribute).toString(); // Just to prevent fighting between two devices if (_state /*&& ip_addr != _ip*/) return; QByteArray data = replyTemp->readAll(); //qDebug() << "Message:\n" << QString(data).simplified().left(3500); QXmlStreamReader xml(data); xml.readNextStartElement(); // RimTabletResponse xml.readNextStartElement(); if (xml.name() == "DeviceCharacteristics") { // Valid device setIp(ip_addr); setState(1); if (device != nullptr) device->deleteLater(); device = new DeviceInfo(); emit deviceChanged(); while (!xml.atEnd()) { xml.readNext(); if (xml.isStartElement()) { if (xml.name() == "BbPin") { device->setPin(QString::number(xml.readElementText().toInt(),16).toUpper()); } else if (xml.name() == "SystemMachine") { device->setName(xml.readElementText().replace("_", " ")); setNewLine(QString("Connected to %1 at %2.").arg(device->name).arg(_ip)); } else if (xml.name() == "OsType") { if (xml.readElementText() == "BlackBerry PlayBook OS") { device->setName("Playbook QNX6.6.0"); setNewLine(QString("Connected to Playbook at %1.").arg(_ip)); } } else if (xml.name() == "PlatformVersion") { device->setOs(xml.readElementText()); } else if (xml.name() == "ModelName") { device->setHw(xml.readElementText()); } else if (xml.name() == "MinSupportedProtocolVersion") { // Min comes before max if (xml.readElementText().toInt() == 1) device->setProtocol(1); } else if (xml.name() == "MaxSupportedProtocolVersion") { if (xml.readElementText().toInt() >= 3) device->setProtocol(3); } else if (xml.name() == "DeveloperModeEnabled") { device->setDevMode(xml.readElementText().toUInt()); } else if (xml.name() == "OobeCompleted") { device->setSetupComplete(xml.readElementText().toUInt()); if (!device->setupComplete) { QMessageBox::information(nullptr, tr("Warning"), tr("You have not completed setup on your device. Due to this issue, it will not allow communication.")); } } } } emit deviceChanged(); setCompleted(false); // Don't even attempt because it will kick us if (!device->setupComplete) checkLogin(); } sender()->deleteLater(); } bool InstallNet::checkLogin() { if (!_state) return false; if (!_completed) { getQuery(QString("login.cgi?request_version=%1").arg(QString::number(device->protocol)), "x-www-form-urlencoded"); return false; } return true; } void InstallNet::installProgress(qint64 pread, qint64) { if (pread == 0) return; _dlBytes = 50*pread; setCurDGProgress(qMin((int)50, (int)(_dlBytes / _dlTotal))); } void InstallNet::backupProgress(qint64 pread, qint64) { _back.setCurSize(100*pread); _back.setProgress(qMin((int)100, (int)(_back.curSize() / _back.curMaxSize()))); } void InstallNet::restoreProgress(qint64 pwrite, qint64) { _back.setCurSize(100*pwrite); _back.setProgress(qMin((int)100, (int)(_back.curSize() / _back.curMaxSize()))); } void InstallNet::backupFileFinish() { _zipFile->close(); _zipFile->deleteLater(); _zipFile = nullptr; _back.setCurMode(1); QUrlQuery postData; postData.addQueryItem("action", "backup"); if (_back.curMode() != "complete") { _back.setProgress(0); postData.addQueryItem("type", _back.curMode()); } postQuery("backup.cgi", "x-www-form-urlencoded", postData); } QPair InstallNet::getConnected(int downloadDevice, bool specialQ30) { if (downloadDevice == 0) { if (device != nullptr && device->hw != "" && device->hw != "Unknown") { return qMakePair(_knownConnectedOSType, _knownConnectedRadioType); } return {"", ""}; } return getFamilyFromDevice(downloadDevice, specialQ30); } void InstallNet::determineDeviceFamily() { QString radio = _knownConnectedRadioType; if (radio == "qc8960.wtr5") { device->setHwFamily(Z30Family); } else if (radio == "m5730") { device->setHwFamily(OMAPFamily); } else if (radio == "qc8960") { device->setHwFamily(Z10Family); } else if (radio == "qc8930.wtr5") { device->setHwFamily(Z3Family); } else if (radio == "qc8960.wtr") { device->setHwFamily(Q10Family); } else if (radio == "qc8974.wtr2") { device->setHwFamily(Q30Family); } else { device->setHwFamily(UnknownFamily); } } void InstallNet::restoreReply() { if (reply == nullptr) return; QByteArray data = reply->readAll(); #if DEBUG_LOG for (int s = 0; s < data.size(); s+=3500) qDebug() << "Message:\n" << QString(data).simplified().mid(s, 3500); #endif if (data.size() == 0) { if (_restoring) { QMessageBox::information(nullptr, "Restore Error", "There was an error loading the backup file.\nThe device encountered an unrecoverable bug.\nIt is not designed to restore this backup."); qIoSafeFree(_zipFile); ioSafeFree(currentBackupZip); setRestoring(false); } } QUrlQuery postData; QString element; QXmlStreamReader xml(data); xml.readNextStartElement(); // RimTabletResponse xml.readNextStartElement(); QString hwid; if (xml.name() == "Rev") { xml.readNextStartElement(); xml.readNextStartElement(); } if (xml.name() == "AuthChallenge") { // We need to verify QString salt, challenge; int iCount = 0; while (xml.readNextStartElement()) { element = xml.readElementText(); if (xml.name() == "Salt") salt = element; else if (xml.name() == "Challenge") challenge = element; else if (xml.name() == "ICount") iCount = element.toInt(); else if (xml.name() == "ErrorDescription") { setLoginBlock(true); return; } } QByteArray saltHex(salt.toLatin1()); QByteArray challenger(challenge.toLatin1()); QByteArray result = HashPass(challenger, QByteArray::fromHex(saltHex), iCount); QNetworkRequest request = reply->request(); request.setUrl(QUrl("https://"+ _ip +":443/cgi-bin/login.cgi?challenge_data=" + result.toHex().toUpper() + "&request_version=" + QString::number(device->protocol))); reply = manager->get(request); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(restoreError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(finished()), this, SLOT(restoreReply())); } else if (xml.name() == "Auth") { // We are authenticated while (!xml.atEnd()) { xml.readNext(); if (xml.isStartElement()) { QString name = xml.name().toString(); if (name == "Error") { //No we aren't! return; } else if (name == "Status") { if (xml.readElementText() == "Denied") { if (!data.contains("Attempts>0")) { setWrongPass(true); setCompleted(false); setState(false); } else { setLoginBlock(true); setCompleted(false); setState(false); } return; } } } } setCompleted(true); if (_installing) install(); else if (_backing) backup(); else if (_restoring) restore(); else /*if (_hadPassword)*/ scanProps(); // This can take up to 40 seconds to respond and all communication on device is Blocking! // backupQuery(); } else if (xml.name() == "DynamicProperties") { for (xml.readNext(); !xml.atEnd(); xml.readNext()) { if (xml.isStartElement()) { QString name = xml.name().toString(); if (name == "ErrorDescription") { QMessageBox::information(nullptr, "Error", xml.readElementText(), QMessageBox::Ok); } else if (name == "DeviceSoftware") { // About to get the apps _appList.clear(); _appRemList.clear(); } else if (name == "Application") { Apps* newApp = new Apps(); while(!xml.atEnd()) { xml.readNext(); if (xml.isStartElement()) { if (xml.name() == "Name") { QString longName = xml.readElementText(); newApp->setName(longName); if (longName.contains(".test")) longName = longName.split(".test").first(); else if (longName.contains(".andrB")) longName = longName.split(".andrB").first(); else longName = longName.split(".gY").first(); QStringList newLineParts = longName.split('.'); if (newLineParts.last().isEmpty()) newLineParts.removeLast(); longName = ""; for (int i = 0; i < newLineParts.size() - 1; i++) longName += newLineParts.at(i) + "."; longName += "" + newLineParts.last() + ""; newApp->setObjectName(newLineParts.last().toLower()); newApp->setFriendlyName(longName); } else if (xml.name() == "Type") newApp->setType(xml.readElementText()); else if (xml.name() == "PackageId") newApp->setPackageId(xml.readElementText()); else if (xml.name() == "PackageVersionId") newApp->setVersionId(xml.readElementText()); else if (xml.name() == "PackageVersion") newApp->setVersion(xml.readElementText()); else if (xml.name() == "Fingerprint") newApp->setChecksum(xml.readElementText()); } else if (xml.isEndElement() && xml.name() == "Application") break; } if (newApp->type() != "") _appList.append(newApp); else _appRemList.append(newApp); if (newApp->type() == "os") { _knownConnectedOSType = newApp->name().split("os.").last().remove(".desktop").replace("verizon", "factory").replace("qc8974.factory_sfi","qc8960.factory_sfi_hybrid_qc8974"); device->setOs(newApp->version()); } else if (newApp->type() == "radio") { _knownConnectedRadioType = newApp->name().split("radio.").last().remove(".omadm"); device->setRadio(newApp->version()); } } // These give the wrong result some times. Installed apps are a better indication. // Although they also sometimes don't match Settings -> About (eg. from a Core/Radio Autoloader). // else if (name == "PlatformVersion") // else if (name == "RadioVersion") else if (name == "BatteryLevel") device->setBattery(xml.readElementText().toInt()); else if (name == "HardwareID") { // If the firmware reports the device as unknown (eg. Dev Alpha on 10.3), show the Hardware ID if (device->hw == "Unknown") { hwid = xml.readElementText().remove(0, 2); // If we already know the name, make it nicer if (hwid == "8d00270a") hwid = "Alpha C"; else if (hwid == "4002607") hwid = "Alpha B"; device->setHw(hwid); } } else if (name == "Bbid") { device->setBbid(xml.readElementText()); } else if (name == "DeviceName") { // name that the user calls their phone device->setFriendlyName(xml.readElementText()); } else if (name == "PolicyRestrictions") { device->setRestrictions(xml.readElementText().simplified()); } else if (xml.name() == "RefurbDate") { device->setRefurbDate(xml.readElementText()); } else if (xml.name() == "FreeApplicationSpace") { device->setFreeSpace(xml.readElementText().toLongLong()); } else if (xml.name() == "BoardSerialNumber") { device->setBsn(xml.readElementText()); } } } // We do have the radio type but we don't entirely trust it. The user could have installed anything or nothing! QString temporaryRadioType = _knownConnectedRadioType; _knownConnectedRadioType = ""; // Not future-proof, but will work for most. Families # hardcoded to 7 for (int i = 1; i < (7 * 2) && _knownConnectedRadioType.isEmpty(); i+=2) { for (int j = 0; j < dev[i].count(); j++) { if (dev[i][j] == hwid.toUpper()) { // Q30 OS status doesn't matter, so we set 0 _knownConnectedRadioType = getFamilyFromDevice(j + 1, 0).second; break; } } } // Well, our detection failed, so let's trust the current system. if (_knownConnectedRadioType.isEmpty()) { _knownConnectedRadioType = temporaryRadioType; } // Now we can work out the real family determineDeviceFamily(); std::sort(_appList.begin(), _appList.end(), [=](const Apps* i, const Apps* j) { if (i->type() != "application" && j->type() == "application") return true; if (j->type() != "application" && i->type() == "application") return false; return (i->objectName() < j->objectName()); } ); emit deviceChanged(); emit appListChanged(); } else if (xml.name() == "RTASChallenge") { QFile rtasData(getSaveDir() + "rtasdata.txt"); rtasData.open(QIODevice::WriteOnly | QIODevice::Text); rtasData.write(QByteArray("Use these values for RLT:\n\n")); for (xml.readNext(); !xml.atEnd(); xml.readNext()) { if (xml.isStartElement()) { if (xml.name() == "Challenge") rtasData.write(QString("Challenge: "+xml.readElementText()+"\n").toLocal8Bit()); else if (xml.name() == "ProcessorInfo") rtasData.write(QString("Processor Info: "+xml.readElementText()+"\n").toLocal8Bit()); else if (xml.name() == "ProcessorId") rtasData.write(QString("Processor Id: "+xml.readElementText()+"\n").toLocal8Bit()); else if (xml.name() == "BSN") rtasData.write(QString("BSN: "+xml.readElementText()+"\n").toLocal8Bit()); else if (xml.name() == "IMEI") rtasData.write(QString("IMEI: "+xml.readElementText()+"\n\n").toLocal8Bit()); else if (xml.name() == "Log") rtasData.write(QString(xml.readElementText()+"\n").toLocal8Bit()); } } setCompleted(false); setState(false); QMessageBox::information(nullptr, "Success", "RTAS has been started.\nSachesi will now terminate its connection.", QMessageBox::Ok); openFile(rtasData.fileName()); rtasData.close(); } else if (xml.name() == "DevicePIN") { while (!xml.atEnd()) { xml.readNext(); if (xml.isStartElement()) { if (xml.name() == "PIN") { device->setPin(xml.readElementText().split('X').last()); emit deviceChanged(); } } } } else if (xml.name() == "Wipe") { for (xml.readNext(); !xml.atEnd(); xml.readNext()) { if (xml.isStartElement()) { if (xml.name() == "ErrorDescription") { QMessageBox::critical(nullptr, "Error", xml.readElementText(), QMessageBox::Ok); } } } } else if (xml.name() == "re_pin" || xml.name() == "re_sign" || xml.name() == "start_rtas") { for (xml.readNext(); !xml.atEnd(); xml.readNext()) { if (xml.isStartElement()) { if (xml.name() == "Log") { QMessageBox::critical(nullptr, "Error", xml.readElementText(), QMessageBox::Ok); } } } } else if (xml.name() == "DeleteRequest") { postData.addQueryItem("type", "bar"); postData.addQueryItem("packageid", _downgradeInfo.at(_downgradePos)); postQuery("update.cgi", "x-www-form-urlencoded", postData); } else if (xml.name() == "DeleteProgress") { _downgradePos++; emit dgPosChanged(); postData.addQueryItem("type", "bar"); // Send another packageid if more to delete or update. if (_downgradePos == _downgradeInfo.count()) postData.addQueryItem("status", "success"); else postData.addQueryItem("packageid", _downgradeInfo.at(_downgradePos)); postQuery("update.cgi", "x-www-form-urlencoded", postData); } else if (xml.name() == "UpdateStart") { if (_installInfo.count()) { compressedFile = new QFile(_downgradeInfo.at(_downgradePos)); compressedFile->open(QIODevice::ReadOnly); _dlBytes = 0; _dlTotal = compressedFile->size(); BarInfo info = _installInfo.at(_downgradePos); if (info.type == OSType) setCurInstallName(info.version + " Core OS"); else if (info.type == RadioType) setCurInstallName(info.version + " Radio"); else { // TODO: Extract naming from bar too, if possible setCurInstallName(QFileInfo(info.name).completeBaseName()); } if (info.type == RadioType) reply = manager->post(setData("update.cgi?type=radio", "octet-stream"), compressedFile); else reply = manager->post(setData("update.cgi?type=bar", "octet-stream"), compressedFile); compressedFile->setParent(reply); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(restoreError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(installProgress(qint64,qint64))); connect(reply, SIGNAL(finished()), this, SLOT(restoreReply())); } else { postData.addQueryItem("type", "bar"); postData.addQueryItem("packageid", _downgradeInfo.at(_downgradePos)); reply = manager->post(setData("update.cgi", "x-www-form-urlencoded"), postData.encodedQuery()); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(restoreError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(finished()), this, SLOT(restoreReply())); } } else if (xml.name() == "UpdateSend") { for (xml.readNext(); !xml.atEnd(); xml.readNext()) { if (xml.isStartElement()) { if (xml.name() == "Status") { if (xml.readElementText() == "Error") { return; } } else if (xml.name() == "ErrorDescription") { QMessageBox::critical(nullptr, "Error", xml.readElementText(), QMessageBox::Ok); } } } postQuery("update.cgi", "x-www-form-urlencoded", postData); } else if (xml.name() == "UpdateProgress") { bool inProgress = false; while (xml.readNextStartElement()) { element = xml.readElementText(); if (xml.name() == "Status") { if (element == "InProgress") inProgress = true; else if (element == "Success") { inProgress = false; _dlDoneBytes += 100 * _dlTotal; compressedFile->close(); if (_downgradePos == _downgradeInfo.count() - 1) { postData.addQueryItem("status","success"); reply = manager->post(setData("update.cgi", "x-www-form-urlencoded"), postData.encodedQuery()); } else { _downgradePos++; emit dgPosChanged(); compressedFile = new QFile(_downgradeInfo.at(_downgradePos)); compressedFile->open(QIODevice::ReadOnly); _dlBytes = 0; _dlTotal = compressedFile->size(); BarInfo info = _installInfo.at(_downgradePos); if (info.type == OSType) setCurInstallName(info.version + " Core OS"); else if (info.type == RadioType) setCurInstallName(info.version + " Radio"); else { setCurInstallName(QFileInfo(info.name).completeBaseName()); } QNetworkRequest request; if (info.type == RadioType) request = setData("update.cgi?type=radio", "octet-stream"); else request = setData("update.cgi?type=bar", "octet-stream"); request.setHeader(QNetworkRequest::ContentLengthHeader, compressedFile->size()); request.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, true); reply = manager->post(request, compressedFile); compressedFile->setParent(reply); connect(reply, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(installProgress(qint64,qint64))); } } else if (element == "Error") { QString errorText = QString(data).split("ErrorDescription>").at(1); errorText.chop(2); setNewLine("
While sending: " + _downgradeInfo.at(_downgradePos).split("/").last()); setNewLine("  Error: " + errorText); setInstalling(false); setCurDGProgress(-1); } } else if (xml.name() == "Progress") { if (_downgradePos == (_downgradeInfo.count() - 1)) setCurDGProgress(50 + element.toInt()/2); else setCurDGProgress(inProgress ? (50 + element.toInt()/2) : 0); bool resend = false; if (device->os.startsWith("2.")) // Playbook OS support resend = !data.contains("100"); else resend = inProgress; if (resend) // No 100%! { reply = manager->post(setData("update.cgi", "x-www-form-urlencoded"), postData.encodedQuery()); } } } connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(restoreError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(finished()), this, SLOT(restoreReply())); } else if (xml.name() == "Backup" || xml.name() == "BackupGet") { for (xml.readNext(); !xml.atEnd(); xml.readNext()) { if (xml.isStartElement()) { if (xml.name() == "Status") { if (xml.readElementText() == "Error") { if (_backing) { setBacking(false); if (currentBackupZip != nullptr) { currentBackupZip->close(); delete currentBackupZip; currentBackupZip = nullptr; QFile::remove(_backupFileName); } } else _hadPassword = false; } } else if (xml.name() == "ErrorDescription") { QMessageBox::information(nullptr, "Error", xml.readElementText().remove("HTTP_COOKIE="), QMessageBox::Ok); } } } } else if (xml.name() == "UpdateEnd") { setInstalling(false); setNewLine("Completed Update."); setCurDGProgress(-1); } else if (xml.name() == "BackupCheck") { if (_back.curMode() != "complete") { //postData.addQueryItem("action", "backup"); postData.addQueryItem("type", _back.curMode()); reply = manager->post(setData("backup.cgi", "x-www-form-urlencoded"), postData.encodedQuery()); _zipFile = new QuaZipFile(currentBackupZip); QuaZipNewInfo newInfo("Archive/" + _back.curMode() + ".tar"); newInfo.setPermissions(QFileDevice::Permission(0x7774)); _zipFile->open(QIODevice::WriteOnly, newInfo); connect(reply, SIGNAL(downloadProgress(qint64,qint64)),this, SLOT(backupProgress(qint64, qint64))); connect(reply, &QNetworkReply::readyRead, [=]() { _zipFile->write(reply->readAll()); }); connect(reply, SIGNAL(finished()), this, SLOT(backupFileFinish())); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(restoreError(QNetworkReply::NetworkError))); } else { postData.addQueryItem("status", "success"); postQuery("backup.cgi", "x-www-form-urlencoded", postData); currentBackupZip->close(); delete currentBackupZip; currentBackupZip = nullptr; setBacking(false); } } else if (xml.name() == "BackupList") { _back.clearModes(); while(!xml.atEnd() && !xml.hasError()) { QXmlStreamReader::TokenType token = xml.readNext(); if(token == QXmlStreamReader::StartElement && xml.attributes().count() > 3) { if (xml.attributes().at(0).name() == "id") _back.addMode(xml.attributes()); else if (xml.attributes().at(0).name() == "pkgid") _back.addApp(xml.attributes()); } } _back.sortApps(); } else if (xml.name() == "BackupStart") { if (data.contains("Error")) { setBacking(false); setRestoring(false); return; } postData.addQueryItem("query", "activity"); if (_back.rev() == 2) postData.addQueryItem("opt", "rev2"); postQuery("backup.cgi", "x-www-form-urlencoded", postData); } else if (xml.name() == "BackupStartActivity") { postData.addQueryItem("type", _back.curMode()); if (_back.rev() == 2) { postData.addQueryItem("opt", "rev2"); // Select app by pkgid: //postData.addQueryItem("pkgid", "gYABgGhMIKEe6t-zx-otuOtK1JM"); // Select apps by pkgtype (system, bin, data): //postData.addQueryItem("pkgtype", "data"); // Perform backup by pkgtype: system, bin, data if (_back.curMode() == "app") { QString pkgtype; if (_back.appMode() & 1) pkgtype += "system,"; if (_back.appMode() & 2) pkgtype += "data,"; if (_back.appMode() & 4) pkgtype += "bin,"; pkgtype.chop(1); postData.addQueryItem("pkgtype", pkgtype); } } reply = manager->post(setData("backup.cgi", "x-www-form-urlencoded"), postData.encodedQuery()); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(restoreError(QNetworkReply::NetworkError))); while (!xml.atEnd()) { xml.readNext(); if (xml.isStartElement()) { if (xml.name() == "Status" && xml.readElementText() == "InProgress") { connect(reply, SIGNAL(finished()), this, SLOT(restoreReply())); } else if (xml.name() == "TotalSize") { _back.setMaxSize(xml.readElementText().toLongLong()); _zipFile = new QuaZipFile(currentBackupZip); QuaZipNewInfo newInfo("Archive/" + _back.curMode() + ".tar"); newInfo.setPermissions(QFileDevice::Permission(0x7774)); _zipFile->open(QIODevice::WriteOnly, newInfo); connect(reply, &QNetworkReply::readyRead, [=]() { _zipFile->write(reply->readAll()); }); connect(reply, SIGNAL(finished()), this, SLOT(backupFileFinish())); connect(reply, SIGNAL(downloadProgress(qint64,qint64)),this, SLOT(backupProgress(qint64, qint64))); } else { for (int i = 0; i < _back.numMethods(); i++) { // This is usually App, Media and Settings but make it future-proof if (xml.name().compare(_back.stringFromMode(i), Qt::CaseInsensitive) == 0) { _back.setCurMaxSize(i, xml.readElementText().toLongLong()); } } } } } } else if (xml.name() == "RestoreStart") { if (data.contains("Error")) { setBacking(false); setRestoring(false); return; } restoreSendFile(); } else if (xml.name() == "RestoreSend") { qIoSafeFree(_zipFile); _back.setCurMode(1); if (_back.curMode() == "complete") { postData.addQueryItem("status", "success"); postQuery("backup.cgi", "x-www-form-urlencoded", postData); setRestoring(false); currentBackupZip->close(); delete currentBackupZip; currentBackupZip = nullptr; } else { restoreSendFile(); } } } void InstallNet::restoreSendFile() { currentBackupZip->setCurrentFile(QString("Archive/" + _back.curMode() + ".tar")); _zipFile = new QuaZipFile(currentBackupZip); _zipFile->open(QIODevice::ReadOnly); QNetworkRequest request = setData("backup.cgi?action=restore&type="+_back.curMode()+"&size="+_back.curMaxSize(), "octet-stream"); request.setHeader(QNetworkRequest::ContentLengthHeader, _zipFile->size()); request.setAttribute(QNetworkRequest::DoNotBufferUploadDataAttribute, true); reply = manager->post(request, _zipFile); _zipFile->setParent(reply); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(restoreError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(uploadProgress(qint64,qint64)), this, SLOT(restoreProgress(qint64, qint64))); connect(reply, SIGNAL(finished()), this, SLOT(restoreReply())); } qint64 InstallNet::changeBackAppMode(QString type) { return _back.setAppMode(type); } void InstallNet::resetVars() { qSafeFree(manager); qSafeFree(reply); qSafeFree(cookieJar); setCompleted(false); setInstalling(false); setRestoring(false); setBacking(false); qIoSafeFree(_zipFile); ioSafeFree(currentBackupZip); if (device != nullptr) { device->deleteLater(); device = nullptr; emit deviceChanged(); } setState(0); setCurDGProgress(-1); _dlBytes = 0; _dlTotal = 0; } void InstallNet::restoreError(QNetworkReply::NetworkError error) { if (error == 5 || error == 99) // On purpose or unreachable return; // This is only if it's a discovery pong. Otherwise it will be empty string. QNetworkRequest request = ((QNetworkReply*)sender())->request(); QString ip_addr = request.attribute(QNetworkRequest::CustomVerbAttribute).toString(); // If it's a discovery pong, the IP will be 169.x.x.x (at least 9 chars) QString this_ip = (ip_addr.length()) >= 9 ? ip_addr : _ip; if (_state && this_ip == _ip) { resetVars(); } else if (_state) { return; } QString errString = QString("Communication Error: %1 (%2) from %3") .arg(error) .arg( ((QNetworkReply*)sender())->errorString() ) .arg(this_ip); setNewLine(errString); qDebug() << errString; } void InstallNet::logadd(QString logtxt) { Q_UNUSED(logtxt); return; } QByteArray InstallNet::HashPass(QByteArray challenge, QByteArray salt, int iterations) { /* Create Hashed Password */ QByteArray hashedData = QByteArray(_password.toLatin1()); int count = 0; bool challenger = true; do { QByteArray buf(4 + salt.length() + hashedData.length(),0); QDataStream buffer(&buf,QIODevice::WriteOnly); buffer.setByteOrder(QDataStream::LittleEndian); buffer << qint32(count); buffer.writeRawData(salt, salt.length()); buffer.writeRawData(hashedData, hashedData.length()); if (!count) hashedData.resize(64); SHA512((const unsigned char*)buf.data(), buf.length(), (unsigned char *)hashedData.data()); if ((count == iterations - 1) && challenger) { count = -1; challenger = false; hashedData.prepend(challenge); } } while (++count < iterations); return hashedData; } void InstallNet::disconnected() { setState(0); setCompleted(false); setRestoring(false); setBacking(false); setInstalling(false); } void InstallNet::connected() { requestConfigure(); } QString InstallNet::appDeltaMsg() { if (_appList.count() == 0) return ""; QString delta = ""; for (int i = 0; i < _appList.count(); i++) { delta.append("packageId() + "\" name=\"" + _appList.at(i)->name() + "\" type=\"" + _appList.at(i)->type() + "\">versionId() + "\">" + _appList.at(i)->version() + "" + _appList.at(i)->checksum() + ""); } delta.append(""); return delta; } void InstallNet::exportInstalled() { QFile installedTxt(getSaveDir() + "/installed.txt"); installedTxt.open(QIODevice::WriteOnly | QIODevice::Text); installedTxt.write("Installed Applications:\n"); for (int i = 0; i < _appList.count(); i++) { if (_appList.at(i)->type() != "") { QString appLine = _appList.at(i)->friendlyName().remove("").remove("").leftJustified(55); appLine.append(_appList.at(i)->version() + "\n"); installedTxt.write(appLine.toStdString().c_str()); } } if (_appRemList.count()) { installedTxt.write("\n\nRemoved Applications:\n"); for (int i = 0; i < _appRemList.count(); i++) { QString appLine = _appRemList.at(i)->friendlyName().remove("").remove("").leftJustified(55); appLine.append(_appRemList.at(i)->version() + "\n"); installedTxt.write(appLine.toStdString().c_str()); } } openFile(installedTxt.fileName()); installedTxt.close(); } //Network Manager QNetworkReply* SslNetworkAccessManager::createRequest(Operation op, const QNetworkRequest& req, QIODevice* outgoingData) { QNetworkReply* reply = QNetworkAccessManager::createRequest(op, req, outgoingData); reply->ignoreSslErrors(); return reply; } sachesi-2.0.4+ds/src/installer.h000066400000000000000000000253001362064257300165030ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #include #include #include #endif #include #include #include "apps.h" #include "backupinfo.h" #include "deviceinfo.h" #include "blitzinfo.h" struct BarInfo { QString name; QString version; QString packageid; BarType type; }; class SslNetworkAccessManager : public QNetworkAccessManager { Q_OBJECT public: SslNetworkAccessManager() {} protected: QNetworkReply* createRequest(Operation op, const QNetworkRequest & req, QIODevice * outgoingData = 0); }; class InstallNet : public QObject { Q_OBJECT Q_PROPERTY(QString password MEMBER _password WRITE setPassword NOTIFY passwordChanged) Q_PROPERTY(bool wrongPass MEMBER _wrongPass WRITE setWrongPass NOTIFY wrongPassChanged) Q_PROPERTY(bool loginBlock MEMBER _loginBlock WRITE setLoginBlock NOTIFY loginBlockChanged) Q_PROPERTY(int possibleDevices MEMBER _possibleDevices WRITE setPossibleDevices NOTIFY possibleDevicesChanged) Q_PROPERTY(QString ip MEMBER _ip WRITE setIp NOTIFY ipChanged) Q_PROPERTY(QString newLine MEMBER _newLine WRITE setNewLine NOTIFY newLineChanged) Q_PROPERTY(int state MEMBER _state WRITE setState NOTIFY stateChanged) Q_PROPERTY(int dgPos MEMBER _downgradePos NOTIFY dgPosChanged) Q_PROPERTY(int dgMaxPos READ dgMaxPos NOTIFY dgMaxPosChanged) Q_PROPERTY(int dgProgress MEMBER _dgProgress NOTIFY curDGProgressChanged) Q_PROPERTY(int curDGProgress MEMBER _curDGProgress WRITE setCurDGProgress NOTIFY curDGProgressChanged) Q_PROPERTY(QString curInstallName MEMBER _curInstallName WRITE setCurInstallName NOTIFY curInstallNameChanged) Q_PROPERTY(bool completed MEMBER _completed NOTIFY completedChanged) Q_PROPERTY(bool installing MEMBER _installing WRITE setInstalling NOTIFY installingChanged) Q_PROPERTY(bool restoring MEMBER _restoring WRITE setRestoring NOTIFY restoringChanged) Q_PROPERTY(bool backing MEMBER _backing WRITE setBacking NOTIFY backingChanged) Q_PROPERTY(bool firmwareUpdate MEMBER _firmwareUpdate WRITE setFirmwareUpdate NOTIFY firmwareUpdateChanged) Q_PROPERTY(bool extractInstallZip MEMBER _extractInstallZip NOTIFY extractInstallZipChanged) Q_PROPERTY(bool allowDowngrades MEMBER _allowDowngrades WRITE setAllowDowngrades NOTIFY allowDowngradesChanged) Q_PROPERTY(QStringList firmwareNames MEMBER _firmwareNames NOTIFY firmwareNamesChanged) Q_PROPERTY(QStringList firmwarePaths MEMBER _firmwarePaths NOTIFY firmwarePathsChanged) Q_PROPERTY(int knownHWFamily MEMBER _knownHWFamily NOTIFY appListChanged) Q_PROPERTY(DeviceInfo* device MEMBER device NOTIFY deviceChanged) Q_PROPERTY(QQmlListProperty appList READ appList NOTIFY appListChanged) Q_PROPERTY(int appCount READ appCount NOTIFY appListChanged) Q_PROPERTY(bool hasLog READ hasLog NOTIFY hasLogChanged) Q_PROPERTY(QString backStatus READ backStatus NOTIFY backStatusChanged) Q_PROPERTY(int backProgress READ backProgress NOTIFY backCurProgressChanged) Q_PROPERTY(int backCurProgress READ backCurProgress NOTIFY backCurProgressChanged) Q_PROPERTY(int backMethods READ backMethods NOTIFY backMethodsChanged) Q_PROPERTY(QStringList backNames READ backNames NOTIFY backMethodsChanged) Q_PROPERTY(QList backSizes READ backSizes NOTIFY backMethodsChanged) Q_PROPERTY(QQmlListProperty backAppList READ backAppList NOTIFY backMethodsChanged) public: InstallNet(QObject* parent = 0); ~InstallNet(); Q_INVOKABLE void keepAlive(); Q_INVOKABLE void scanProps(); Q_INVOKABLE void install(QList files); Q_INVOKABLE void uninstall(QStringList packageids, bool firmwareUpdate); Q_INVOKABLE bool uninstallMarked(); Q_INVOKABLE void restore(QUrl url, int options); Q_INVOKABLE void backup(QUrl url, int options); Q_INVOKABLE void wipe(); Q_INVOKABLE void startRTAS(); Q_INVOKABLE void newPin(QString pin); Q_INVOKABLE void resignNVRAM(); Q_INVOKABLE void factorywipe(); Q_INVOKABLE void reboot(); Q_INVOKABLE void getPIN(); Q_INVOKABLE void setActionProperty(QString name, QString value); Q_INVOKABLE void backupQuery(); Q_INVOKABLE qint64 changeBackAppMode(QString type); Q_INVOKABLE void exportInstalled(); Q_INVOKABLE void openLog(); Q_INVOKABLE bool hasLog(); Q_INVOKABLE QString appDeltaMsg(); void requestConfigure(); void requestChallenge(); void replyChallenge(); void requestAuthenticate(); void authorise(); QByteArray HashPass(QByteArray challenge, QByteArray salt, int iterations); void logadd(QString logtxt); void AESEncryptSend(QByteArray &plain, int code); QString password() const; int dgMaxPos() const; QQmlListProperty appList(); QQmlListProperty backAppList(); QList appQList() { return _appList; } int appCount() const { return _appList.count(); } BackupInfo* back(); QString backStatus() const; int backProgress() const; int backCurProgress() const; int backMethods() const; QStringList backNames() const; QList backSizes() const; QPair getConnected(int downloadDevice, bool specialQ30); DeviceInfo* device; void setIp(const QString &ip); void setPassword(const QString &password); void setWrongPass(const bool &wrong); void setLoginBlock(const bool &wrong); void setPossibleDevices(const int &devices); void setNewLine(const QString &newLine); void setState(const int &state); void setCurDGProgress(const int &progress); void setCurInstallName(const QString &name); void setCompleted(const bool &exists); void setInstalling(const bool &installing); void setRestoring(const bool &restoring); void setBacking(const bool &backing); void setFirmwareUpdate(const bool &firmwareUpdate); void setAllowDowngrades(const bool &allowDowngrades); signals: void passwordChanged(); void newPassword(QString newPass); void wrongPassChanged(); void loginBlockChanged(); void possibleDevicesChanged(); void ipChanged(); void newLineChanged(); void stateChanged(); void completedChanged(); void dgPosChanged(); void dgMaxPosChanged(); void curDGProgressChanged(); void curInstallNameChanged(); void installingChanged(); void restoringChanged(); void backingChanged(); void backStatusChanged(); void backCurProgressChanged(); void backMethodsChanged(); void firmwareUpdateChanged(); void firmwareNamesChanged(); void firmwarePathsChanged(); void extractInstallZipChanged(); void allowDowngradesChanged(); void appListChanged(); void backAppListChanged(); void deviceChanged(); void hasLogChanged(); private slots: bool checkLogin(); void login(); void connected(); void disconnected(); //void endConnect() void restoreSendFile(); void restoreReply(); void discoveryReply(); void resetVars(); void restoreError(QNetworkReply::NetworkError error); void installProgress(qint64 pread, qint64); void backupProgress(qint64 pread, qint64 psize); void restoreProgress(qint64 pwrite, qint64 psize); void backupFileFinish(); void dumpLogs(); private: QNetworkRequest setData(QString page, QString contentType); QNetworkReply* postQuery(QString page, QString contentType, const QUrlQuery& query); QNetworkReply* getQuery(QString page, QString contentType); BarInfo checkInstallableInfo(QString name, bool blitz); BarInfo blitzCheck(QString name); void install(); void restore(); void backup(); void determineDeviceFamily(); QTcpSocket* sock; unsigned char* serverChallenge; RSA* privkey; QByteArray sessionKey; QByteArray hashedPassword; QTimer* connectTimer; QProcess proc; #ifdef _MSC_VER WSADATA wsadata; #endif QNetworkAccessManager* dlmanager; SslNetworkAccessManager* manager; QNetworkReply *reply; QNetworkCookieJar* cookieJar; QTemporaryFile* logFile; QFile* compressedFile; QStringList _firmwareNames; QStringList _firmwarePaths; int _knownHWFamily; QString _knownConnectedOSType; QString _knownConnectedRadioType; QStringList _firmwareInfo; QStringList _downgradeInfo; int _downgradePos; int _firmwarePos; QString _ip; QString _password; bool _wrongPass; bool _loginBlock; int _possibleDevices; QString _newLine; QFile _firmware; int _state; // Urgh, the below should be in a class quint64 _dlBytes; quint64 _dlTotal; quint64 _dlDoneBytes; quint64 _dlOverallTotal; int _dgProgress; int _curDGProgress; QString _curInstallName; BackupInfo _back; bool _completed; bool _firmwareUpdate; bool _extractInstallZip; bool _allowDowngrades; QList _installInfo; QString _backupFileName; QStringList _currentApps; bool _installing; bool _restoring; bool _backing; bool _hadPassword; QuaZip* currentBackupZip; QuaZipFile* _zipFile; QList _appList; QList _appRemList; }; sachesi-2.0.4+ds/src/installer_auth.cpp000066400000000000000000000053431362064257300200640ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #include "installer.h" void InstallNet::requestAuthenticate() { logadd(QString("8. Request Authenticate")); QByteArray buffer; QDataStream config(&buffer, QIODevice::WriteOnly); config << qint16(6) /*messageSize*/ << qint16(2) /*version*/ << qint16(8) /*code*/; sock->write(buffer); } void InstallNet::AESEncryptSend(QByteArray &plain, int code) { /* Encrypt Plain Text */ unsigned char* iv = new unsigned char[16]; RAND_bytes(iv, 16); int ilen, tlen; unsigned char* encrypt = new unsigned char[plain.length() + 16]; EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_init(ctx); const EVP_CIPHER *cipher = EVP_aes_128_cbc(); EVP_EncryptInit(ctx, cipher, (unsigned char*)sessionKey.data(), iv); EVP_EncryptUpdate(ctx, encrypt, &ilen, (unsigned char*)plain.data(), plain.length()); EVP_EncryptFinal(ctx, encrypt + ilen, &tlen); EVP_CIPHER_CTX_free(ctx); /* Create buffer */ QByteArray buffer; QDataStream Auth(&buffer, QIODevice::WriteOnly); Auth << qint16(ilen + tlen) << qint16(plain.length()); Auth.writeRawData((char*)iv,16); Auth.writeRawData((char*)encrypt, ilen+tlen); /* Package and Send */ QByteArray header; QDataStream FrameHead(&header, QIODevice::WriteOnly); FrameHead << qint16(6 + buffer.length()) << qint16(2) << qint16(code); buffer.prepend(header); sock->write(buffer); delete [] iv; delete [] encrypt; } void InstallNet::authorise() { logadd(QString("10. Authorise")); /* Construct Plain Text */ QByteArray plain(2+hashedPassword.length(),0); QDataStream plainText(&plain, QIODevice::WriteOnly); plainText << qint16(hashedPassword.length()); plainText.writeRawData(hashedPassword.data(),hashedPassword.length()); AESEncryptSend(plain, 10); } void InstallNet::keepAlive() { logadd(QString("6. Keep Alive")); QByteArray buffer; QDataStream config(&buffer, QIODevice::WriteOnly); config << qint16(6) /*messageSize*/ << qint16(2) /*version*/ << qint16(6) /*code*/; sock->write(buffer); } sachesi-2.0.4+ds/src/installer_establish.cpp000066400000000000000000000063501362064257300211000ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #include "installer.h" // Compatibility for old OpenSSL #if OPENSSL_VERSION_NUMBER < 0x10100005L static void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) { if(n != NULL) *n = r->n; if(e != NULL) *e = r->e; if(d != NULL) *d = r->d; } #endif void InstallNet::requestConfigure() { if (!_state) return; logadd(QString("1. Request Configuration")); QByteArray buffer; QDataStream config(&buffer, QIODevice::WriteOnly); config << qint16(6) /*messageSize*/ << qint16(2) /*version*/ << qint16(1) /*code*/; sock->write(buffer); } void InstallNet::requestChallenge() { logadd(QString("3. Request Challenge")); BIGNUM* e = BN_new(); BN_set_word(e, 65537); privkey = RSA_new(); RSA_generate_key_ex(privkey, 1024, e, nullptr); const BIGNUM* bign; RSA_get0_key(privkey, &bign, nullptr, nullptr); unsigned char* privkey_mod = new unsigned char[BN_num_bytes(bign)]; BN_bn2bin(bign, privkey_mod); QByteArray buffer(128,0); buffer = QByteArray::fromRawData((char*)privkey_mod,128); QByteArray header; QDataStream FrameHead(&header, QIODevice::WriteOnly); FrameHead << qint16(8 + buffer.length()) << qint16(2) << qint16(3) << qint16(buffer.length()); buffer.prepend(header); sock->write(buffer); delete privkey_mod; } void InstallNet::replyChallenge() { logadd(QString("5. Reply Challenge")); const signed char QCONNDOOR_PERMISSIONS[] = {3, 4, 118, -125, 1}; const char EMSA_SHA1_HASH[] = {48, 33, 48, 9, 6, 5, 43, 14, 3, 2, 26, 5, 0, 4, 20}; QCryptographicHash sha1(QCryptographicHash::Sha1); /* Encrypt challenge */ QByteArray challengeBuffer = QByteArray((char*)serverChallenge, 30).append((const char*)QCONNDOOR_PERMISSIONS, 5); sha1.addData(challengeBuffer); QByteArray hash = QByteArray(EMSA_SHA1_HASH, 15).append(sha1.result()); QByteArray signature(128, 0); RSA_private_encrypt(hash.length(),(unsigned char*)hash.data(), (unsigned char*)signature.data(), privkey, RSA_PKCS1_PADDING); /* Find Session Key */ sessionKey = challengeBuffer.mid(8,16); /* Construct Plain Text */ QByteArray plain(12 + challengeBuffer.length() + signature.length(),0); QDataStream plainText(&plain, QIODevice::WriteOnly); plainText << qint16(4 + challengeBuffer.length() + signature.length()) << qint16(challengeBuffer.length()) << qint16(signature.length()); plainText.writeRawData(challengeBuffer.data(),35); plainText.writeRawData(signature.data(),128); AESEncryptSend(plain, 5); } sachesi-2.0.4+ds/src/installer_qml.cpp000066400000000000000000000122621362064257300177120ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi // This file is just Qt hooks that don't need to be seen. It should really do this for you anyway. #include "installer.h" #include "ports.h" #define WRITE_QML(type, name, caps) \ void InstallNet::caps(const type &var) { \ if (var != _ ## name) { \ _ ## name = var; \ emit name ## Changed(); \ } \ } // _() WRITE_QML(bool, loginBlock, setLoginBlock) WRITE_QML(int, possibleDevices, setPossibleDevices) void InstallNet::setState(const int &state) { if (!state) { qSafeFree(manager); qSafeFree(cookieJar); } _state = state; emit stateChanged(); } int InstallNet::dgMaxPos() const { return _downgradeInfo.count(); } WRITE_QML(QString, curInstallName, setCurInstallName) WRITE_QML(bool, installing, setInstalling) WRITE_QML(bool, firmwareUpdate, setFirmwareUpdate) WRITE_QML(bool, allowDowngrades, setAllowDowngrades) void InstallNet::setCurDGProgress(const int &curDGProgress) { _curDGProgress = curDGProgress; if (curDGProgress == -1) { _dgProgress = -1; } else { qint64 curBytes = ((qint64)_curDGProgress * _dlTotal); _dgProgress = (curBytes + _dlDoneBytes) / _dlOverallTotal; } emit curDGProgressChanged(); } QQmlListProperty InstallNet::appList() { return QQmlListProperty(this, _appList); } QQmlListProperty InstallNet::backAppList() { return QQmlListProperty(this, _back.apps); } void InstallNet::setIp(const QString &ip) { _ip = ip; emit ipChanged(); QSettings settings("Qtness","Sachesi"); if (settings.value("ip","169.254.0.1").toString() != ip) settings.setValue("ip",ip); } void InstallNet::setWrongPass(const bool &wrong) { _wrongPass = wrong; if (wrong) { setState(0); setCompleted(false); } emit wrongPassChanged(); } void InstallNet::setCompleted(const bool &exists) { _completed = exists; if (_loginBlock && _completed) setLoginBlock(false); emit completedChanged(); } void InstallNet::setNewLine(const QString &newLine) { // Prefix with date QString newLog = QTime().currentTime().toString("[hh:mm:ss] ") + newLine; _newLine = ""; // Reset qproperty and notify emit newLineChanged(); _newLine = newLog + "
"; emit newLineChanged(); newLog.append("\n"); logFile->write(newLog.toLatin1()); logFile->flush(); // Update file so it can be viewed in real-time emit hasLogChanged(); } bool InstallNet::hasLog() { return logFile->size() > 0; } void InstallNet::openLog() { if (logFile->fileName() != "") openFile(logFile->fileName()); } QString InstallNet::backStatus() const { QString ret = _back.curMode(); ret[0] = ret[0].toUpper(); return ret; } int InstallNet::backProgress() const { return qMin((int)100, (int)((_back.size() + _back.curSize()) / _back.maxSize())); } int InstallNet::backCurProgress() const { return _back.progress(); } QStringList InstallNet::backNames() const { QStringList names; foreach(BackupCategory* cat, _back.categories) names.append(cat->name); return names; } QList InstallNet::backSizes() const { QList sizes; foreach(BackupCategory* cat, _back.categories) sizes.append(cat->bytesize.toLongLong()); return sizes; } int InstallNet::backMethods() const { return _back.numMethods(); } void InstallNet::setBacking(const bool &backing) { _backing = backing; emit backingChanged(); if (!backing) { _back.setMode(0); for (int i = 0; i < _back.numMethods(); i++) _back.setCurMaxSize(i, 1); _back.setSize(0); _back.setMaxSize(1); } } void InstallNet::setRestoring(const bool &restoring) { _restoring = restoring; emit restoringChanged(); if (!restoring) { _back.setMode(0); for (int i = 0; i < _back.numMethods(); i++) _back.setCurMaxSize(i, 1); _back.setSize(0); _back.setMaxSize(1); } } // Note: not overly secure if user is specifically targeted. void InstallNet::setPassword(const QString &password) { _password = password; emit newPassword(password); QByteArray tmp = password.toLatin1(); for (int i = 0; i < password.length(); i++) { tmp[i] = tmp[i] ^ ((0x40 + 5 * i - password.length()) % 127); } QByteArray tmp2(1, tmp.length()); QByteArray hashedPass = tmp.toBase64(); hashedPass.prepend(tmp2.toBase64()); QSettings settings("Qtness","Sachesi"); settings.setValue("pass", hashedPass); emit passwordChanged(); } sachesi-2.0.4+ds/src/ports.cpp000066400000000000000000000123371362064257300162160ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #include "ports.h" // For portability between platforms and Qt versions. // Clears up the code in the more important files. #ifndef BLACKBERRY #include #include #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #include #endif #include QPair getFamilyFromDevice(int device, bool specialQ30) { QPair ret = {"", ""}; switch(device) { case Z30Family: ret = {"qc8960.factory_sfi", "qc8960.wtr5"}; break; case OMAPFamily: ret = {"winchester.factory_sfi", "m5730"}; break; case Z10Family: ret = {"qc8960.factory_sfi", "qc8960"}; break; case Z3Family: ret = {"qc8960.factory_sfi_hybrid_qc8x30", "qc8930.wtr5"}; break; case Q30Family: if (specialQ30) ret = {"qc8974.factory_sfi", "qc8974.wtr2"}; else ret = {"qc8960.factory_sfi_hybrid_qc8974", "qc8974.wtr2"}; break; case Q10Family: ret = {"qc8960.factory_sfi", "qc8960.wtr"}; break; } return ret; } QString capPath(bool temp) { #ifndef BLACKBERRY QSettings ini(QSettings::IniFormat, QSettings::UserScope, QCoreApplication::organizationName(), QCoreApplication::applicationName()); QString capPath = QFileInfo(ini.fileName()).absolutePath(); if (temp) { // On Windows this folder may not exist even if the .ini is meant to be there! So make it when writing temp file. QDir(capPath).mkdir("."); return capPath + "/.cap.exe"; } return capPath + "/cap.exe"; #else return "cap.exe"; #endif } bool isVersionNewer(QString first, QString second, bool orSame) { QStringList firstVer = first.split('.'); QStringList secondVer = second.split('.'); if (firstVer.count() < 4) return false; for (int i = 0; i < 4; i++) { int newBuild = firstVer.at(i).toInt(); int oldBuild = secondVer.at(i).toInt(); if (newBuild > oldBuild) return true; if (newBuild < oldBuild) return false; } return orSame; } #ifndef BLACKBERRY QFileDialog* selectFiles(QString title, QString dir, QString nameString, QString nameExt) { QFileDialog* finder = new QFileDialog(); finder->setWindowTitle(title); finder->setDirectory(dir); finder->setNameFilter(nameString + "(" + nameExt + ")"); QListView *l = finder->findChild("listView"); if (l) l->setSelectionMode(QAbstractItemView::ExtendedSelection); QTreeView *t = finder->findChild(); if (t) t->setSelectionMode(QAbstractItemView::ExtendedSelection); return finder; } #endif QString getSaveDir() { #ifdef BLACKBERRY #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) return "/accounts/1000/shared/misc/Sachesi/"; #else return QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).first() + "/Sachesi/"; #endif #else QString writable = QDir::currentPath() + "/"; if (QFileInfo(writable).isWritable()) return writable; return QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); #endif } bool checkCurPath() { QDir dir; #ifdef BLACKBERRY #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) QString path = "/accounts/1000/shared/misc/Sachesi/"; #else QString path = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).first() + "/Sachesi/"; #endif dir.mkpath(path); QDir::setCurrent(path); #elif defined(ANDROID) // Iterate through removable to local QString sdcard = "/sdcard1/Sachesi/"; dir.mkpath(sdcard); if (!dir.exists(sdcard)) { sdcard = "/sdcard/Sachesi"; dir.mkpath(sdcard); } QDir::setCurrent(sdcard); #else QDir::setCurrent(QApplication::applicationDirPath()); #if defined(__APPLE__) dir = QDir(QDir::currentPath()); // Use .app path instead of binary path. Should really use a different method. if (dir.absolutePath().endsWith("Contents/MacOS")) { while (!dir.absolutePath().endsWith(".app")) dir.cdUp(); dir.cdUp(); } QDir::setCurrent(dir.absolutePath()); #endif #endif return true; } void openFile(QString name) { QDesktopServices::openUrl(QUrl::fromLocalFile(name)); } void writeDisplayFile(QString type, QString writeText) { QDir(getSaveDir()).mkpath("."); QFile displayFile(getSaveDir() + "/" + type + ".txt"); if (!displayFile.open(QIODevice::WriteOnly | QIODevice::Text)) return; displayFile.write(writeText.toUtf8()); openFile(displayFile.fileName()); displayFile.close(); } sachesi-2.0.4+ds/src/ports.h000066400000000000000000000104571362064257300156640ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once // For portability between platforms and Qt versions. // Clears up the code in the more important files. // Also a make-shift utility file #ifndef BLACKBERRY #include #include #include #include #endif #include #include #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) #include #else #include #define encodedQuery query(QUrl::FullyEncoded).toUtf8 #endif // TODO: Maybe name families by radio enum DeviceFamily { UnknownFamily = 0, Z30Family, OMAPFamily, Z10Family, Z3Family, Q30Family, Q10Family, }; static QStringList dev[] = { // 0 = Z30 (A Series) + Classic + Leap QStringList() << "STA 100-1" << "STA 100-2" << "STA 100-3" << "STA 100-4" << "STA 100-5" << "STA 100-6" << "SQC 100-1" << "SQC 100-2" << "SQC 100-3" << "SQC 100-4" << "SQC 100-5" << "STR 100-1" << "STR 100-2", QStringList() << "8C00240A" << "8D00240A" << "8E00240A" << "8F00240A" << "9500240A" << "B500240A" << "9600270A" << "9400270A" << "9500270A" << "9700270A" << "9C00270A" << "07002E0A" << "06002E0A", // 1 = Z10 (L Series) OMAP QStringList() << "STL 100-1", QStringList() << "4002607", // 2 = Z10 (L Series) Qualcomm + P9982 (TK Series) QStringList() << "STL 100-2" << "STL 100-3" << "STL 100-4" << "STK 100-1" << "STK 100-2", QStringList() << "8700240A" << "8500240A" << "8400240A" << "A500240A" << "A600240A", // 3 = Z3 (J Series) + Cafe QStringList() << "STJ 100-1" << "STJ 100-2" << "Kopi NA" << "Kopi Europe/ME/Asia" << "Cafe ROW" << "Cafe AT/T" << "Cafe LatinAm" << "Cafe Verizon" << "Cafe Sprint", QStringList() << "04002E07" << "05002E07" << "87002A07" << "8C002A07" << "9600240A" << "9700240A" << "9C00240A" << "A700240A" << "AC00240A", // 4 = Passport / Q30 (W Series) QStringList() << "SQW 100-1" << "SQW 100-2" << "SQW 100-3" << "SQW 100-4" << "Passport Wichita", QStringList() << "87002C0A" << "85002C0A" << "84002C0A" << "86002C0A" << "8C002C0A", // 5 = Q5 (R Series) + Q10 (N Series) + P9983 (QK Series) QStringList() << "SQR 100-1" << "SQR 100-2" << "SQR 100-3" << "SQN 100-1" << "SQN 100-2" << "SQN 100-3" << "SQN 100-4" << "SQN 100-5" << "SQK 100-1" << "SQK 100-2", QStringList() << "84002A0A" << "85002A0A" << "86002A0A" << "8400270A" << "8500270A" << "8600270A" << "8C00270A" << "8700270A" << "8F00270A" << "8E00270A", // 6 = Dev Alpha QStringList() << "Alpha A" << "Alpha B" << "Alpha C", QStringList() << "4002307" << "4002607" << "8D00270A", // 7 = Ontario Series QStringList() << "Ontario NA" << "Ontario Verizon" << "Ontario Sprint" << "Ontario ROW" << "China", QStringList() << "AE00240A" << "AF00240A" << "B400240A" << "B600240A" << "BC00240A", }; QPair getFamilyFromDevice(int device, bool specialQ30); QString capPath(bool temp = false); #ifndef BLACKBERRY QFileDialog* selectFiles(QString title, QString dir, QString nameString, QString nameExt); #endif QString getSaveDir(); bool checkCurPath(); void openFile(QString name); void writeDisplayFile(QString type, QString writeText); bool isVersionNewer(QString first, QString second, bool orSame); // These may not be entirely necessary but there have been issues in the past #define qSafeFree(x) \ if (x != nullptr) { \ x->deleteLater(); \ x = nullptr; \ } #define qIoSafeFree(x) \ if (x != nullptr) { \ if (x->isOpen()) \ x->close(); \ x->deleteLater(); \ x = nullptr; \ } #define ioSafeFree(x) \ if (x != nullptr) { \ if (x->isOpen()) \ x->close(); \ delete x; \ x = nullptr; \ } sachesi-2.0.4+ds/src/sachesi.cpp000066400000000000000000000132401362064257300164600ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #include #include #include #include #include #include #include "search/mainnet.h" #include "search/scanner.h" #ifndef BLACKBERRY #include "installer.h" #include "backupinfo.h" #endif #include "carrierinfo.h" #include "translator.h" #ifdef BOOTLOADER_ACCESS #include "boot.h" #endif // Known bugs // Crash: If you are looking at a different tab when backup size results return. Unknown cause! // Feature and function TODO // TODO: We can make blitz faster by checking OTAs. Do this in download stage and perhaps in live blitz // TODO: Allow installing directly from zip (difficult due to pipelining and requirement of seek which QuaZip won't support) // TODO: Make extraction handle decent % tracking for QNX FS. Perhaps a per-file extractor for progress circle. // TODO: Completely redo how Splitter is accessed. Possibly make it a class with per-function jobs/threads // Need help: Check and improve the USB Loader (Boot). // TODO: Use CircleProgress in Extract section. Pass a class to QML that contains file count, current and total progress // Platform TODO // TODO: Window {} not working on Android. Maybe special QML files required? // TODO: Blackberry is having a lot of issues with Qt5. For now I have a custom Cascades version with reduced feature-set // Need testing of policies: // Personal: policy_block_backup_and_restore, policy_backup_and_restore // Enterprise: policy_disable_devmode, policy_log_submission, policy_block_computer_access_to_device Q_DECL_EXPORT int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::AA_X11InitThreads, true); QApplication app(argc, argv); app.setOrganizationName("Qtness"); app.setOrganizationDomain("qtness.com"); app.setApplicationName("Sachesi"); app.setApplicationVersion("2.0.4"); // Use system proxy except where not possible QNetworkProxyFactory::setUseSystemConfiguration(true); QQmlApplicationEngine engine; QQmlContext *context = engine.rootContext(); // Do we have a suitable place to store files that the user will be able to find? if (!checkCurPath()) { QMessageBox::critical(NULL, "Error", "Could not find a suitable storage path.\nPlease report this."); return 0; } // *** Static QML Variables that describe the environment // Useful as QML is unable to detect this natively // Send the version across. Preferably via the .pro context->setContextProperty("version", QVariant::fromValue(QApplication::applicationVersion())); // Check if we have at least Qt 5.3 available. If not, do some workarounds for bugs. context->setContextProperty("qt_new", QVariant::fromValue(QT_VERSION > QT_VERSION_CHECK(5, 3, 0))); // Check if this is a mobile device as they often do not have enough space. context->setContextProperty("mobile", QVariant::fromValue( #if defined(BLACKBERRY) || defined(ANDROID) 1 #else 0 #endif )); // *** C++ Classes that are passed to the QML pages. // Heavy lifting to be done by the compiled and feature-packed language. InstallNet i; context->setContextProperty("i", &i); MainNet p(&i); Scanner scanner; Translator translator; #ifdef BOOTLOADER_ACCESS Boot b; QObject::connect(&b, SIGNAL(started()), &b, SLOT(search())); QObject::connect(&b, SIGNAL(finished()), &b, SLOT(exit())); QObject::connect(&i, SIGNAL(newPassword(QString)), &b, SLOT(newPassword(QString))); b.start(); context->setContextProperty("b", &b); // Boot #endif CarrierInfo info; // Set contexts for the classes context->setContextProperty("p", &p); // MainNet context->setContextProperty("scanner", &scanner); context->setContextProperty("download", p.currentDownload); context->setContextProperty("carrierinfo", &info); context->setContextProperty("translator", &translator); // *** Register types for the QML language to understand types used by C++, when passed #ifndef BLACKBERRY qmlRegisterType("BackupTools", 1, 0, "BackupInfo"); #endif qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); #if defined(_WIN32) && defined(STATIC) engine.addImportPath("qrc:/qml/"); #endif // *** Now let's try to show the QML file and check for errors QScopedPointer comp(new QQmlComponent(&engine)); comp->loadUrl(QUrl("qrc:/qml/generic/Title.qml")); if (comp->status() == QQmlComponent::Error) { QMessageBox::information(nullptr, "Error", qPrintable(comp->errorString()), QMessageBox::Ok); return 0; } QQuickWindow *window = qobject_cast(comp->create()); window->show(); int ret = app.exec(); #ifdef BOOTLOADER_ACCESS b.quit(); b.wait(1000); #endif delete window; return ret; } sachesi-2.0.4+ds/src/search/000077500000000000000000000000001362064257300156025ustar00rootroot00000000000000sachesi-2.0.4+ds/src/search/discoveredrelease.h000066400000000000000000000031041362064257300214410ustar00rootroot00000000000000#pragma once #include #include class DiscoveredRelease : public QObject { Q_OBJECT Q_PROPERTY(QString osVersion READ osVersion WRITE setOsVersion NOTIFY osVersionChanged) Q_PROPERTY(QString srVersion READ srVersion WRITE setSrVersion NOTIFY srVersionChanged) Q_PROPERTY(int activeServers READ activeServers NOTIFY activeServersChanged) Q_PROPERTY(QString baseUrl READ baseUrl WRITE setBaseUrl NOTIFY baseUrlChanged) public: DiscoveredRelease() : QObject(), _activeServers(0) { } DiscoveredRelease(const DiscoveredRelease& release) : QObject() , _osVersion(release.osVersion()) , _srVersion(release.srVersion()) , _activeServers(release.activeServers()) {} QString osVersion() const { return _osVersion; } QString srVersion() const { return _srVersion; } int activeServers() const { return _activeServers; } QString baseUrl() const { return _baseUrl; } void setOsVersion(QString osVersion) { _osVersion = osVersion; emit osVersionChanged(); } void setSrVersion(QString srVersion) { _srVersion = srVersion; emit srVersionChanged(); } void setActiveServers(int activeServers) { _activeServers |= activeServers; emit activeServersChanged(); } void setBaseUrl(QString baseUrl) { _baseUrl = baseUrl; emit baseUrlChanged(); } signals: void osVersionChanged(); void srVersionChanged(); void activeServersChanged(); void baseUrlChanged(); private: QString _osVersion; QString _srVersion; int _activeServers; QString _baseUrl; }; Q_DECLARE_METATYPE(DiscoveredRelease* ); sachesi-2.0.4+ds/src/search/mainnet.cpp000066400000000000000000000600051362064257300177420ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #include "mainnet.h" #include "splitter.h" #include "ports.h" #include #include MainNet::MainNet(InstallNet *installer, QObject* parent) : QObject(parent) , _i(installer) , _scanning(0) , _splitting(0) , _splitProgress(0) { manager = new QNetworkAccessManager(); currentDownload = new DownloadInfo(); if (_i != nullptr) connect(_i, SIGNAL(appListChanged()), this, SLOT(newDeviceConnected())); } MainNet::~MainNet() { } void MainNet::splitConnectStart() { connect(splitter, SIGNAL(finished()), splitThread, SLOT(quit())); connect(splitter, SIGNAL(finished()), this, SLOT(cancelSplit())); connect(splitter, SIGNAL(progressChanged(int)), this, SLOT(setSplitProgress(int))); connect(splitThread, SIGNAL(finished()), splitter, SLOT(deleteLater())); connect(splitThread, SIGNAL(finished()), splitThread, SLOT(deleteLater())); splitThread->start(); } void MainNet::splitAutoloader(QUrl url, int options) { if (url.isEmpty()) return; QString fileName = url.toLocalFile(); _options = options; QFileInfo fileInfo(fileName); _splitting = SplittingAuto; emit splittingChanged(); splitThread = new QThread(); splitter = new Splitter(fileName, _options); splitter->moveToThread(splitThread); if (fileInfo.suffix() == "exe") connect(splitThread, SIGNAL(started()), splitter, SLOT(processSplitAutoloader())); else connect(splitThread, SIGNAL(started()), splitter, SLOT(processSplitBar())); splitConnectStart(); } void MainNet::combineAutoloader(QList selectedFiles) { // Download required files. if (!QFileInfo(capPath()).exists()) { QString capUrl = "http://ppsspp.mvdan.cc/cap3.11.0.11.exe"; _splitting = FetchingCap; emit splittingChanged(); QNetworkReply* reply = manager->get(QNetworkRequest(capUrl)); QObject::connect(reply, &QNetworkReply::readyRead, [=]() { // Download to a temporary file first QFile capFile(capPath(true)); capFile.open(QIODevice::WriteOnly | QIODevice::Append); capFile.write(reply->readAll()); capFile.close(); }); QObject::connect(reply, &QNetworkReply::finished, [=]() { // If the download was successful, copy it to the path we check. Some users quit before this happens! QFile::rename(capPath(true), capPath()); splitThread = new QThread; _splitting = CreatingAuto; emit splittingChanged(); splitter = new Splitter(selectedFiles); splitter->moveToThread(splitThread); connect(splitThread, SIGNAL(started()), splitter, SLOT(processCombine())); splitConnectStart(); reply->deleteLater(); }); QObject::connect(reply, static_cast(&QNetworkReply::error), [=]() { // Some users may experience difficult downloading it at all, or the link may be outdated. QMessageBox::information(NULL, "Error", "Was unable to download CAP, which is a component of Autoloaders.\nAs a workaround, you can provide your own CAP to " + capPath()); _splitting = SplittingIdle; emit splittingChanged(); splitThread->deleteLater(); splitter->deleteLater(); reply->deleteLater(); }); return; } else { splitThread = new QThread; _splitting = CreatingAuto; emit splittingChanged(); splitter = new Splitter(selectedFiles); splitter->moveToThread(splitThread); connect(splitThread, SIGNAL(started()), splitter, SLOT(processCombine())); splitConnectStart(); } } void MainNet::extractImage(int type, int options) { _options = options; _type = type; QString filter = "*.exe *.signed"; if (type == 0 && _options & 1) filter += " *.rcfs"; if (type == 2 || (type == 0 && _options & 2)) filter += " *.qnx6"; if (type == 0 && _options & 4) filter += " *.ifs"; QFileDialog* finder = selectFiles("Extract Image", getSaveDir(), "Filesystem Containers", filter); if (finder->exec()) extractImageSlot(finder->selectedFiles()); finder->deleteLater(); } void MainNet::extractImageSlot(const QStringList& selectedFiles) { if (selectedFiles.empty()) return; // TODO: Actually detect file by inspection QFileInfo fileInfo(selectedFiles.first()); if (_type == 2 && fileInfo.size() < 500 * 1024 * 1024) { QString errorMsg = "You can only extract apps from debrick OS images."; if (fileInfo.size() < 120 * 1024 * 1024) errorMsg.append("\nThis appears to be a Radio file. Radios have no apps."); QMessageBox::information(nullptr, "Warning", errorMsg, QMessageBox::Ok); return; } _splitting = (_type == 2) ? ExtractingApps : ExtractingImage; emit splittingChanged(); splitThread = new QThread; splitter = new Splitter(selectedFiles.first()); switch (_type) { case 1: splitter->extractImage = true; break; case 2: splitter->extractApps = true; break; case 0: default: break; } splitter->extractTypes = _options; splitter->moveToThread(splitThread); // Wrapper should detect file type and deal extract everything inside, according to _options; connect(splitThread, SIGNAL(started()), splitter, SLOT(processExtractWrapper())); splitConnectStart(); } void MainNet::cancelSplit() { _splitting = SplittingIdle; emit splittingChanged(); } void MainNet::abortSplit() { emit splitter->killSplit(); cancelSplit(); } void MainNet::grabLinks(int downloadDevice) { _downloadDevice = downloadDevice; writeDisplayFile("Updates", convertLinks("Links have been converted to work on your selected device.\n\n").toLocal8Bit()); } QString MainNet::fixVariantName(QString name, QString replace, int type) { if (type == 0) { // OS QStringList urlSplit = name.split('/'); QStringList nameSplit = urlSplit.last().split('-'); if (nameSplit.first().endsWith(".desktop")) nameSplit.first() = replace + ".desktop"; else nameSplit.first() = replace; urlSplit.last() = nameSplit.join('-'); return urlSplit.join('/'); } else if (type == 1) { // Radio QStringList urlSplit = name.split('/'); QStringList nameSplit = urlSplit.last().split('-'); nameSplit.first() = replace; urlSplit.last() = nameSplit.join('-'); return urlSplit.join('/'); } return name; } // Permanently converts the currentDownload object apps to the current 'Download Device' // Important not to change this object during the, rather large, download! void MainNet::fixApps() { if (_i != nullptr) { QPair results = _i->getConnected(_downloadDevice, _versionRelease.startsWith("10.3.0")); if (results.first == "" && results.second == "") return; foreach (Apps* app, currentDownload->apps) { if (results.first != "" && app->type() == "os") { app->setUrl(fixVariantName(app->url(), results.first, 0)); app->setName(app->url().split("/").last()); app->setFriendlyName(QFileInfo(app->name()).completeBaseName()); currentDownload->verifyLink(app->url(), "OS", _downloadDevice == 0 && _i != nullptr && _i->device != nullptr); } else if (results.second != "" && app->type() == "radio") { app->setUrl(fixVariantName(app->url(), results.second, 1)); app->setName(app->url().split("/").last()); app->setFriendlyName(QFileInfo(app->name()).completeBaseName()); currentDownload->verifyLink(app->url(), "Radio", _downloadDevice == 0 && _i != nullptr && _i->device != nullptr); } } // Refresh the names in QML currentDownload->nextFile(0); } } // Creates a string with a list of URLs based on current 'Search Device' // and converted to current 'Download Device' QString MainNet::convertLinks(QString prepend) { bool convert = true; QPair results; if (_i != nullptr) { results = _i->getConnected(_downloadDevice, _versionRelease.startsWith("10.3.0")); if (results.first == "" && results.second == "") convert = false; } QString updated; foreach (Apps* app, _updateAppList) { if (!app->isMarked()) continue; QString item = app->url(); if (convert) { if (results.first != "" && app->type() == "os") item = fixVariantName(item, results.first, 0); else if (results.second != "" && app->type() == "radio") item = fixVariantName(item, results.second, 1); } updated.append(item + "\n"); } if (convert) updated.prepend(prepend); return updated; } void MainNet::downloadLinks(int downloadDevice) { _downloadDevice = downloadDevice; // Have we been here before? Starting but ids already generated. Maybe links were verified, so skip this if (currentDownload->maxId == 0) { currentDownload->setApps(_updateAppList, _versionRelease); fixApps(); // Did we find any apps? if (currentDownload->maxId == 0) { currentDownload->reset(); return; } } if (currentDownload->toVerify == 0) { currentDownload->download(_downloadDevice == 0 && _i != nullptr && _i->device != nullptr); } } QString MainNet::NPCFromLocale(int carrier, int country) { QString homeNPC; homeNPC.sprintf("%03d%03d%d", carrier, country, carrier ? 30 : 60); return homeNPC; } QString MainNet::nameFromVariant(unsigned int device, unsigned int variant) { Q_ASSERT(variantCount(device) > variant); return dev[device*2][variant]; } QString MainNet::hwidFromVariant(unsigned int device, unsigned int variant) { Q_ASSERT(variantCount(device) > variant); return dev[device*2+1][variant]; } unsigned int MainNet::variantCount(unsigned int device) { return dev[device*2].count(); } void MainNet::updateDetailRequest(QString delta, QString carrier, QString country, int device, int variant, int mode) { QString up; QString requestUrl = "https://cs.sl.blackberry.com/cse/updateDetails/2.2/"; QString version = "2.2.1"; // Blackberry doesn't return results on these servers anymore, so their usefulness is gone /* switch (server) { case 4: requestUrl = "https://alpha2.sl.eval.blackberry.com/slscse/updateDetails/"; break; case 3: requestUrl = "https://alpha.sl.eval.blackberry.com/slscse/updateDetails/"; break; case 2: requestUrl = "https://beta2.sl.eval.blackberry.com/slscse/updateDetails/"; break; case 1: requestUrl = "https://beta.sl.eval.blackberry.com/slscse/updateDetails/"; break; case 0: default: requestUrl = "https://cs.sl.blackberry.com/cse/updateDetails/"; break; } // Alpha and Beta servers support newer API QString version; switch(server) { case 4: case 3: case 2: case 1: requestUrl += "2.2/"; // They support 2.3 version = "2.2.1"; // They support 2.3.0 break; case 0: default: requestUrl += "2.2/"; version = "2.2.1"; break; }*/ QString homeNPC = NPCFromLocale(carrier.toInt(), country.toInt()); switch (mode) { case 1: up = "repair"; break; case 0: default: up = "upgrade"; break; } // We either selected 'Any' (if there was more than one variant) or we picked a specific variant. int start = (variant != 0) ? (variant - 1) : 0; int end = (variant != 0) ? variant : variantCount(device); setScanning((variant != 0) ? 1 : variantCount(device)); if (_scanning > 1) setMultiscan(true); for (int i = start; i < end; i++) { QString query = QString("" "" "" "" "0x2FFFFFB311281213610044011392692400x%3" "" "" "0x%489014104255505565333" "" "" "en_USen_US" "" "" "true%5falsetrueNOTIFICATION_CHECK" "manual" "" "%6" "application" "" "%7" "") .arg(version) // API Version .arg(QDateTime::currentMSecsSinceEpoch()) // Current time, in case it cares one day .arg(hwidFromVariant(device, i)) // Search Device HWID .arg(homeNPC) // Country + Carrier .arg(up) // Upgrade or Repair? // 2.3.0 doesn't support 'latest'. Does this mean we need to do availableBundles lookup first? .arg((version == "2.3.0") ? "" : "") .arg(delta); // User installed applications (to support REDBEND patching) _error = ""; emit errorChanged(); // Pass the variant in the request so it can be retrieved out-of-order QNetworkRequest request; request.setRawHeader("Content-Type", "text/xml;charset=UTF-8"); request.setUrl(QUrl(requestUrl)); request.setAttribute(QNetworkRequest::CustomVerbAttribute, nameFromVariant(device, i)); QNetworkReply* reply = manager->post(request, query.toUtf8()); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(serverError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(finished()), this, SLOT(serverReply())); } } void MainNet::serverReply() { QNetworkReply *reply = (QNetworkReply *)sender(); QByteArray data = reply->readAll(); //for (int i = 0; i < data.size(); i += 3000) qDebug() << data.mid(i, 3000); showFirmwareData(data, reply->request().attribute(QNetworkRequest::CustomVerbAttribute).toString()); sender()->deleteLater(); } void MainNet::showFirmwareData(QByteArray data, QString variant) { QXmlStreamReader xml(data); QString ver = ""; QString os = ""; QString radio = ""; QString addr = ""; QString currentaddr = ""; QList newApps; while(!xml.atEnd() && !xml.hasError()) { if(xml.tokenType() == QXmlStreamReader::StartElement) { if (xml.name() == "package") { Apps* newApp = new Apps(); // Remember: this name *can* change newApp->setFriendlyName(xml.attributes().value("name").toString()); // Remember: this name *can* change newApp->setName(xml.attributes().value("path").toString().split('/').last()); // Remember: this size *can* change newApp->setSize(xml.attributes().value("downloadSize").toString().toInt()); newApp->setVersion(xml.attributes().value("version").toString()); newApp->setPackageId(xml.attributes().value("id").toString()); newApp->setChecksum(xml.attributes().value("checksum").toString()); QString type = xml.attributes().value("type").toString(); if (type == "system:os" || type == "system:desktop") { os = newApp->version(); newApp->setType("os"); newApp->setIsMarked(true); newApp->setIsAvailable(true); if (_i != nullptr && _i->device != nullptr) { newApp->setIsInstalled(isVersionNewer(_i->device->os, newApp->version(), true)); newApp->setInstalledVersion(_i->device->os); } } else if (type == "system:radio") { radio = newApp->version(); newApp->setType("radio"); newApp->setIsMarked(true); newApp->setIsAvailable(true); if (_i != nullptr && _i->device != nullptr) { newApp->setIsInstalled(isVersionNewer(_i->device->radio, newApp->version(), true)); newApp->setInstalledVersion(_i->device->radio); } } else { newApp->setType("application"); if (_i != nullptr) { foreach(Apps* app, _i->appQList()) { bool isSameApp = newApp->packageId().compare(app->packageId()) == 0; if (isSameApp) { newApp->setIsInstalled(isVersionNewer(app->version(), newApp->version(), true)); newApp->setInstalledVersion(app->version()); break; } } } } // For lack of a better name, the url newApp->setUrl(currentaddr + "/" + newApp->name()); newApp->setName(newApp->name().split("/").last()); newApps.append(newApp); } else if (xml.name() == "friendlyMessage") { _error = xml.readElementText().split(QChar('.'))[0]; emit errorChanged(); } else if (xml.name() == "fileSet") { currentaddr = xml.attributes().value("url").toString(); if (addr == "") addr = currentaddr; } else if (xml.name() == "softwareReleaseMetadata") { ver = xml.attributes().value("softwareReleaseVersion").toString(); } else if (xml.name() == "bundle") { QString newver = xml.attributes().value("version").toString(); if (ver == "" || (ver.split(".").last().toInt() < newver.split(".").last().toInt())) ver = newver; } // No longer check for descriptions /*else if (xml.name() == "description" && desc == "") { desc = xml.readElementText(); }*/ } xml.readNext(); } // Check if the version string is newer. bool isNewer = (!_multiscan || _multiscanVersion == ""); if (!isNewer && ver != "") { isNewer = isVersionNewer(ver, _multiscanVersion, false); } if (isNewer) { // Update software release versions if (_multiscan) _multiscanVersion = ver; _versionRelease = ver; if (ver == "") { _updateMessage = ""; } else { // Delete old list if (_updateAppList.count()) { foreach (Apps* app, _updateAppList) { app->disconnect(SIGNAL(isMarkedChanged())); app->deleteLater(); } _updateAppList.clear(); } // Put this new list up for display _updateAppList = newApps; // Connect every isMarkedChanged to the list signal and check if it should be marked foreach (Apps* app, _updateAppList) { connect(app, SIGNAL(isMarkedChanged()), this, SIGNAL(updateCheckedCountChanged())); // No need to check OS and Radio as they are variable if (app->type() == "application") { bool exists = QFile(getSaveDir() + "/" + _versionRelease + "/" + app->name()).size() == app->size(); app->setIsAvailable(!exists); } app->setIsMarked(app->isAvailable() && !app->isInstalled()); } // Server uses some funny order. // Put in order of largest to smallest with OS and Radio first and already downloaded last. std::sort(_updateAppList.begin(), _updateAppList.end(), [=](const Apps* i, const Apps* j) { if (i->type() != "application" && j->type() == "application") return true; if (j->type() != "application" && i->type() == "application") return false; if (i->isMarked() != j->isMarked()) return i->isMarked(); return (i->size() > j->size()); } ); _updateMessage = QString("Update %1 available for %2!
%3 %4") .arg(ver) .arg(variant) .arg(os != "" ? QString(" OS: %1").arg(os) : "") .arg(radio != "" ? QString(" Radio: %1").arg(radio) : ""); _error = ""; emit errorChanged(); } // TODO: Putting it here makes user see the values changed. However, putting it below crashes emit updateMessageChanged(); emit updateCheckedCountChanged(); } setScanning(_scanning-1); // All scans complete if (_scanning <= 0) { setMultiscan(false); } } void MainNet::newDeviceConnected() { foreach(Apps* newApp, _updateAppList) { foreach(Apps* app, _i->appQList()) { bool isSameApp = newApp->packageId().compare(app->packageId()) == 0; if (isSameApp) { newApp->setIsInstalled(isVersionNewer(app->version(), newApp->version(), true)); newApp->setInstalledVersion(app->version()); break; } else { newApp->setIsInstalled(false); newApp->setInstalledVersion(""); } } } } void MainNet::serverError(QNetworkReply::NetworkError err) { setScanning(_scanning-1); // Only show error if we are doing single scan or multiscan version is empty. if (!_multiscan || (_multiscanVersion == "" && _scanning == 0)) { QString errormsg = QString("Error %1 (%2)") .arg(err) .arg( ((QNetworkReply*)sender())->errorString() ); _error = errormsg; emit errorChanged(); _updateMessage = ""; emit updateMessageChanged(); } if (_scanning == 0) setMultiscan(false); } void MainNet::setMultiscan(const bool &multiscan) { _multiscan = multiscan; emit multiscanChanged(); _multiscanVersion = ""; emit updateMessageChanged(); } void MainNet::setScanning(const int &scanning) { _scanning = scanning; emit scanningChanged(); } void MainNet::setSplitProgress(const int &progress) { if (_splitProgress > 1000) _splitProgress = 0; else _splitProgress = progress; emit splitProgressChanged(); } sachesi-2.0.4+ds/src/search/mainnet.h000066400000000000000000000133241362064257300174110ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include #include #include "apps.h" #include "splitter.h" #include "downloadinfo.h" #ifdef BLACKBERRY class InstallNet; #else #include "installer.h" #endif enum SplitType { SplittingIdle = 0, SplittingAuto = 1, CreatingAuto = 2, ExtractingImage = 3, ExtractingApps = 4, FetchingCap = 5, }; class MainNet : public QObject { Q_OBJECT Q_PROPERTY(QString softwareRelease MEMBER _softwareRelease NOTIFY softwareReleaseChanged) // from reverse lookup Q_PROPERTY(QString updateMessage MEMBER _updateMessage NOTIFY updateMessageChanged) Q_PROPERTY(QQmlListProperty updateAppList READ updateAppList NOTIFY updateMessageChanged) Q_PROPERTY(int updateAppCount READ updateAppCount NOTIFY updateMessageChanged) Q_PROPERTY(int updateCheckedCount READ updateCheckedCount NOTIFY updateCheckedCountChanged) Q_PROPERTY(int updateAppNeededCount READ updateAppNeededCount NOTIFY updateMessageChanged) Q_PROPERTY(int updateCheckedNeededCount READ updateCheckedNeededCount NOTIFY updateCheckedCountChanged) Q_PROPERTY(QString error MEMBER _error NOTIFY errorChanged) Q_PROPERTY(QString multiscanVersion MEMBER _multiscanVersion NOTIFY updateMessageChanged) Q_PROPERTY(bool hasBootAccess READ hasBootAccess CONSTANT) Q_PROPERTY(bool multiscan MEMBER _multiscan WRITE setMultiscan NOTIFY multiscanChanged) Q_PROPERTY(int scanning MEMBER _scanning WRITE setScanning NOTIFY scanningChanged) Q_PROPERTY(int maxId MEMBER _maxId NOTIFY maxIdChanged) Q_PROPERTY(int splitting MEMBER _splitting NOTIFY splittingChanged) Q_PROPERTY(int splitProgress MEMBER _splitProgress WRITE setSplitProgress NOTIFY splitProgressChanged) public: MainNet(InstallNet* installer = nullptr, QObject* parent = 0); ~MainNet(); Q_INVOKABLE void updateDetailRequest(QString delta, QString carrier, QString country, int device, int variant, int mode/*, int server, int version*/); Q_INVOKABLE void downloadLinks(int downloadDevice = 0); Q_INVOKABLE void splitAutoloader(QUrl, int options); Q_INVOKABLE void combineAutoloader(QList selectedFiles); Q_INVOKABLE void extractImage(int type, int options); Q_INVOKABLE void grabLinks(int downloadDevice); Q_INVOKABLE void abortSplit(); void splitConnectStart(); Q_INVOKABLE QString nameFromVariant(unsigned int device, unsigned int variant); Q_INVOKABLE QString hwidFromVariant(unsigned int device, unsigned int variant); Q_INVOKABLE unsigned int variantCount(unsigned int device); bool hasBootAccess() const { return #ifdef BOOTLOADER_ACCESS true; #else false; #endif } void setMultiscan(const bool &multiscan); void setScanning(const int &scanning); QQmlListProperty updateAppList() { return QQmlListProperty(this, _updateAppList); } int updateAppCount() const { return _updateAppList.count(); } int updateAppNeededCount() const { int count = 0; foreach (Apps* app, _updateAppList) { if (app->isAvailable() && !app->isInstalled()) count++; } return count; } int updateCheckedCount() const { int checked = 0; foreach (Apps* app, _updateAppList) if (app->isMarked()) checked++; return checked; } int updateCheckedNeededCount() const { int checked = 0; foreach (Apps* app, _updateAppList) if (app->isMarked() && (app->isAvailable() && !app->isInstalled())) checked++; return checked; } DownloadInfo* currentDownload; public slots: void setSplitProgress(const int &progress); void newDeviceConnected(); signals: void softwareReleaseChanged(); void updateMessageChanged(); void updateCheckedCountChanged(); void errorChanged(); void multiscanChanged(); void scanningChanged(); void hasBootAccessChanged(); void maxIdChanged(); void splittingChanged(); void splitProgressChanged(); private slots: void serverReply(); void showFirmwareData(QByteArray data, QString variant); void serverError(QNetworkReply::NetworkError error); void cancelSplit(); // Blackberry void extractImageSlot(const QStringList& selectedFiles); private: // Utils: InstallNet* _i; void verifyLink(QString url, QString type); QString convertLinks(QString prepend); QString fixVariantName(QString name, QString replace, int type); void fixApps(); QString NPCFromLocale(int country, int carrier); QThread* splitThread; Splitter* splitter; QNetworkAccessManager *manager; QList _updateAppList; QString _updateMessage; QString _softwareRelease; QString _versionRelease; QString _error; QString _multiscanVersion; bool _multiscan; int _scanning; QFile _currentFile; int _maxId, _dlBytes, _dlTotal; int _splitting, _splitProgress; int _options; int _type; int _downloadDevice; }; sachesi-2.0.4+ds/src/search/scanner.cpp000066400000000000000000000205151362064257300177420ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #include #include "scanner.h" #include "../ports.h" void Scanner::clearHistory() { foreach(DiscoveredRelease* rel, _history) { rel->deleteLater(); } _history.clear(); emit historyChanged(); } void Scanner::exportHistory() { QString historyText; foreach(DiscoveredRelease* rel, _history) { historyText.append(tr("SR: ") + " " + rel->srVersion() + " | " + tr("OS: ") + rel->osVersion() + " ["); if (rel->activeServers() & 1) historyText.append(tr("Production") + ", "); if (rel->activeServers() & 2) historyText.append(tr("Beta") + ", "); if (rel->activeServers() & 4) historyText.append(tr("Alpha") + ", "); historyText.chop(2); historyText.append("]\n"); } writeDisplayFile(tr("History"), historyText); } void Scanner::reverseLookup(QString OSver) { // If we only want real links, we're only going to want the server we can download from _isActive = true; emit isActiveChanged(); _curRelease = new DiscoveredRelease(); _curRelease->setOsVersion(OSver); emit curReleaseChanged(); QString query = QString("" "" "0x2FFFFFB311400118780x85002c0a" "%2" "" "") .arg(QDateTime::currentMSecsSinceEpoch()) .arg(OSver); QNetworkRequest request; request.setRawHeader("Content-Type", "text/xml;charset=UTF-8"); QStringList serverList = QStringList("cs.sl"); if (_findExisting != 1) { serverList << "beta2.sl.eval" << "alpha.sl.eval"; } _scansActive = serverList.count(); foreach(QString server, serverList) { request.setUrl(QUrl(QString("https://%1.blackberry.com/%2cse/srVersionLookup/2.0/").arg(server).arg(server == "cs.sl" ? "" : "sls"))); QNetworkReply* reply = _manager->post(request, query.toUtf8()); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(serverError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(finished()), this, SLOT(newSRVersion())); } } void Scanner::newSRVersion() { QNetworkReply* reply = (QNetworkReply*)sender(); QString swRelease; QByteArray data = reply->readAll(); //for (int i = 0; i < data.size(); i += 3000) qDebug() << data.mid(i, 3000); QXmlStreamReader xml(data); while(!xml.atEnd() && !xml.hasError()) { if(xml.tokenType() == QXmlStreamReader::StartElement) { if (xml.name() == "softwareReleaseVersion") { swRelease = xml.readElementText(); } } xml.readNext(); } // Software release has a version if (swRelease.startsWith('1')) { QString replyHost = reply->url().host(); if (replyHost.startsWith("cs")) { _curRelease->setActiveServers(1); } else if (replyHost.startsWith("beta")) { _curRelease->setActiveServers(2); } else if (replyHost.startsWith("alpha")) { _curRelease->setActiveServers(4); } // Software release is new so we should check if it has a release if (swRelease != _curRelease->srVersion()) { _curRelease->setSrVersion(swRelease); // Don't care about Links? if (_findExisting == 2) { completeScan(); } else { QCryptographicHash hash(QCryptographicHash::Sha1); hash.addData(swRelease.toLatin1()); QString url = "http://cdn.fs.sl.blackberry.com/fs/qnx/production/" + QString(hash.result().toHex()); QNetworkRequest request; request.setRawHeader("Content-Type", "text/xml;charset=UTF-8"); request.setUrl(QUrl(url)); QNetworkReply* replyTmp = _manager->head(request); connect(replyTmp, SIGNAL(finished()), this, SLOT(validateDownload())); } reply->deleteLater(); return; } } completeScan(); reply->deleteLater(); } void Scanner::validateDownload() { QNetworkReply* reply = (QNetworkReply*)sender(); uint status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toUInt(); // Seems to give 301 redirect if it's real if (status == 200 || (status > 300 && status <= 308)) { _curRelease->setBaseUrl(reply->url().toString()); } emit softwareReleaseChanged(); completeScan(); reply->deleteLater(); } void appendNewHeader(QString *potentialText, QString name, QString devices) { potentialText->append("\n" + name + ": " + devices + " (Debrick + Core OS)\n"); } void Scanner::appendNewLink(QString *potentialText, QString linkType, QString hwType, QString version) { bool os = (linkType == "Core" || linkType == "Debrick"); if (!os) { potentialText->append(linkType + " Radio\n"); } potentialText->append(_curRelease->baseUrl() + "/" + hwType + "-" + version + "-nto+armle-v7+signed.bar\n"); } void Scanner::generatePotentialLinks() { QStringList parts = _curRelease->osVersion().split('.'); // Just a guess that the Radio is +1. In some builds this isn't true. int build = parts.last().toInt() + 1; QString radioVersion = ""; for (int i = 0; i < 3; i++) radioVersion += parts.at(i) + "."; radioVersion += QString::number(build); QString potentialText = QString("Potential OS and Radio links for SR " + _curRelease->srVersion() + " (OS " + _curRelease->osVersion() + " + Radio " + radioVersion + ")\n\n" "* Operating Systems *\n"); appendNewHeader(&potentialText, "QC8974", "Blackberry Passport"); appendNewLink(&potentialText, "Debrick", "qc8960.factory_sfi_hybrid_qc8974.desktop", _curRelease->osVersion()); appendNewLink(&potentialText, "Core", "qc8960.factory_sfi_hybrid_qc8974", _curRelease->osVersion()); appendNewHeader(&potentialText, "QC8960", "Blackberry Z10/Z30/Q5/Q10"); appendNewLink(&potentialText, "Debrick", "qc8960.factory_sfi.desktop", _curRelease->osVersion()); appendNewLink(&potentialText, "Core", "qc8960.factory_sfi", _curRelease->osVersion()); appendNewHeader(&potentialText, "QC8x30", "Blackberry Z3"); appendNewLink(&potentialText, "Debrick", "qc8960.factory_sfi_hybrid_qc8x30.desktop", _curRelease->osVersion()); appendNewLink(&potentialText, "Core", "qc8960.factory_sfi_hybrid_qc8x30", _curRelease->osVersion()); appendNewHeader(&potentialText, "OMAP", "Blackberry Z10 STL 100-1"); appendNewLink(&potentialText, "Debrick", "winchester.factory_sfi.desktop", _curRelease->osVersion()); appendNewLink(&potentialText, "Core", "winchester.factory_sfi", _curRelease->osVersion()); potentialText.append("\n\n* Radios *\n"); // Touch appendNewLink(&potentialText, "Z30 + Classic + Leap", "qc8960.wtr5", radioVersion); appendNewLink(&potentialText, "Z10 (STL 100-1)", "m5730", radioVersion); appendNewLink(&potentialText, "Z10 (STL 100-2/3/4) + P9982", "qc8960", radioVersion); appendNewLink(&potentialText, "Z3", "qc8930.wtr5", radioVersion); // QWERTY appendNewLink(&potentialText, "Passport + Ontario", "qc8974.wtr2", radioVersion); appendNewLink(&potentialText, "Q5 + Q10 + P9983", "qc8960.wtr", radioVersion); writeDisplayFile(tr("VersionLookup"), potentialText); } void Scanner::serverError(QNetworkReply::NetworkError err) { Q_UNUSED(err); completeScan(); sender()->deleteLater(); } sachesi-2.0.4+ds/src/search/scanner.h000066400000000000000000000101221362064257300174000ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include #include #include #include #include "discoveredrelease.h" class Scanner : public QObject { Q_OBJECT Q_PROPERTY(bool finishedScan READ finishedScan NOTIFY signalFinished) Q_PROPERTY(bool isAuto READ isAuto WRITE setIsAuto NOTIFY isAutoChanged) Q_PROPERTY(bool isActive READ isActive WRITE setIsActive NOTIFY isActiveChanged) Q_PROPERTY(int findExisting READ findExisting WRITE setFindExisting NOTIFY findExistingChanged) Q_PROPERTY(DiscoveredRelease* curRelease READ curRelease NOTIFY curReleaseChanged) Q_PROPERTY(QQmlListProperty history READ history NOTIFY historyChanged) public: Scanner() : QObject() , _finishedScan(false) , _isAuto(false) , _isActive(false) , _findExisting(0) , _scansActive(0) , _curRelease(NULL) { _manager = new QNetworkAccessManager(); } virtual ~Scanner() {} bool isAuto() const { return _isAuto; } bool isActive() const { return _isActive; } int findExisting() const { return _findExisting; } QString softwareRelease() const { return _softwareRelease; } DiscoveredRelease* curRelease() { return _curRelease; } QQmlListProperty history() { return QQmlListProperty(this, _history); } void completeScan() { _scansActive--; if (_scansActive < 0) _scansActive = 0; if (_scansActive == 0) { // Did we get a version that either exists or we didn't care? if (_curRelease->srVersion().startsWith('1')) { if (_findExisting != 2 && (_findExisting != 1 || _curRelease->baseUrl() != "")) { setIsAuto(false); } _history.prepend(_curRelease); emit historyChanged(); } else { _curRelease->setSrVersion(tr("No Release")); } setIsActive(false); finishedFunc(); } emit curReleaseChanged(); } // Because Cascades is stupid void finishedFunc() { emit signalFinished(); _finishedScan = true; emit signalFinished(); _finishedScan = false; } void setIsAuto(bool isAuto) { _isAuto = isAuto; emit isAutoChanged(); } void setIsActive(bool isActive) { _isActive = isActive; emit isActiveChanged(); } void setFindExisting(int findExisting) { _findExisting = findExisting; emit findExistingChanged(); } Q_INVOKABLE void clearHistory(); Q_INVOKABLE void exportHistory(); Q_INVOKABLE void reverseLookup(QString OSver); Q_INVOKABLE void generatePotentialLinks(); bool finishedScan() const { return _finishedScan; } private slots: void newSRVersion(); void validateDownload(); void serverError(QNetworkReply::NetworkError err); Q_SIGNALS: void signalFinished(); void isAutoChanged(); void isActiveChanged(); void findExistingChanged(); void softwareReleaseChanged(); void curReleaseChanged(); void historyChanged(); private: bool _finishedScan; bool _isAuto, _isActive; int _findExisting; int _scansActive; QString _softwareRelease; DiscoveredRelease* _curRelease; QList _history; QNetworkAccessManager* _manager; void appendNewLink(QString *potentialText, QString linkType, QString hwType, QString version); }; sachesi-2.0.4+ds/src/splitter.cpp000066400000000000000000000346431362064257300167210ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #include "splitter.h" // This is our entry point for extraction that will determine which filetype we started with. // In general we have a container (.exe, .bar, .zip) which may contain further containers. // Underneath this we may have disk images with partition tables (.signed) // And below this we can have filesystem images (.rcfs, .qnx6, .ifs) // Although, the user could make us enter at any point! void Splitter::processExtractWrapper() { extracting = true; progressInfo.clear(); partitionInfo.clear(); QFileInfo fileInfo(selectedFile); // We can probably use a better method, similar to processExtractType, for all files! if (fileInfo.suffix() == "exe") processExtractAutoloader(); else if (fileInfo.suffix() == "signed") processExtractSigned(); else if (fileInfo.suffix() == "bar" || fileInfo.suffix() == "zip") processExtractBar(); else // Assume it is a rcfs/qnx6/ifs processExtractType(); // Now we should be done parsing through the entire file and its components // So run through the partition Info we collected // First gather sizes so we can have an established goal in the UI for (int i = 0; i < partitionInfo.count(); i++) { if (partitionInfo[i].size <= 65536) // Too small to have anything of value partitionInfo.removeAt(i--); else maxSize += partitionInfo[i].size; } // All files will be extracted relative to the given container file QString baseDir = QFileInfo(selectedFile).absolutePath(); foreach(PartitionInfo info, partitionInfo) { int unique = newProgressInfo(info.size); // If we are extracting FS images (only type supported for this method right now), then we want to create a filesystem type QFileSystem* fs = createTypedFileSystem(selectedFile, info.dev, info.type, info.offset, info.size, baseDir); if (fs == nullptr) continue; connect(fs, &QFileSystem::sizeChanged, [=] (qint64 delta) { updateCurProgress(unique, fs->curSize, delta); }); // TODO: This should be cleaner if (info.type == FS_QNX6) { qobject_cast(fs)->extractApps = extractApps; // We need to make Splitter a QML-exposed class, then it's nicer to push these signals //QObject::connect(fs, SIGNAL(currentNameChanged(QString)), this, SLOT())); } if (extractImage) fs->extractImage(); else fs->extractContents(); delete fs; } emit finished(); cleanDevHandle(); partitionInfo.clear(); progressInfo.clear(); } // Process an Autoloader with the aim of extracting files void Splitter::processExtractAutoloader() { // We hardcode this only to speed it up. It isn't required and may cause issues later on. #define START_CAP_SEARCH 0x400000 #define END_CAP_SEARCH 0x1000000 QFile* autoloaderFile = new QFile(selectedFile); devHandle.append(autoloaderFile); autoloaderFile->open(QIODevice::ReadOnly); read = 0; maxSize = 1; int findHeader = 0; autoloaderFile->seek(START_CAP_SEARCH); for (int b = START_CAP_SEARCH; b < END_CAP_SEARCH; ) { QByteArray tmp = autoloaderFile->read(BUFFER_LEN); for (int i = 0; i < BUFFER_LEN - 12; i++) { if (tmp.at(i) == (char)0x9C && tmp.at(i+1) == (char)0xD5 && tmp.at(i+2) == (char)0xC5 && tmp.at(i+3) == (char)0x97 && tmp.at(i+8) == (char)0x9C && tmp.at(i+9) == (char)0xD5 && tmp.at(i+10) == (char)0xC5 && tmp.at(i+11) == (char)0x97) { findHeader = b+i+20; } } b += tmp.size(); } if (!findHeader) { return die(tr("Was not a Blackberry Autoloader file.")); } qint64 files; QList offsets; QNXStream dataStream(autoloaderFile); autoloaderFile->seek(findHeader); // Search for offset table for (int attempts = 0; attempts < 32; attempts++) { qint64 tmp; dataStream >> tmp; if ((tmp - autoloaderFile->pos()) < 500 && (tmp - autoloaderFile->pos()) > -500) { autoloaderFile->seek(autoloaderFile->pos() - 16); dataStream >> files; break; } } if (files < 1 || files > 20) { return die(tr("Unknown Blackberry Autoloader file.")); } // Collect offsets for (int i = 0; i < files; i++) { offsets.append(0); dataStream >> offsets[i]; } offsets.append(autoloaderFile->size()); // End of file // Create sizes and files QString baseName = selectedFile; baseName.chop(4); // TODO: Use detection based on header. OS, Radio and PINList notify their header (but not RFOS?). // Not a priority as the size indication is 100% accurate as long as it is actually an Autoloader for (int i = 0; i < files; i++) { QString filename = baseName; qint64 size = offsets[i+1] - offsets[i]; // Detection on type int type = 0; for (int index = 0; index < 1000 - 32; index += 4) { autoloaderFile->seek(offsets[i] + index); if (autoloaderFile->read(4) == QByteArray("pfcq",4)) { autoloaderFile->seek(offsets[i] + index + 12); qint8 typeChar; dataStream >> typeChar; if (typeChar == 5) { type |= PACKED_FILE_USER; } else if (typeChar == 6) { type |= PACKED_FILE_OS; } else if (typeChar == 12) { // Always seem to have 10 as well, sometimes 11 type |= PACKED_FILE_RADIO; } else if (typeChar == 8) { type |= PACKED_FILE_IFS; break; } } } // Detect by size alone if (type == 0) { if (size > 1024*1024*70) type = PACKED_FILE_OS; else if (size > 1024*1024*5) type = PACKED_FILE_RADIO; else type = PACKED_FILE_PINLIST; } // Append strings if (type & PACKED_FILE_USER) filename += "@User"; if (type & PACKED_FILE_OS) filename += "@OS"; if (type & PACKED_FILE_RADIO) filename += "@Radio"; if (type & PACKED_FILE_IFS) filename += "@IFS"; if (type & PACKED_FILE_PINLIST) filename += "@PINList"; if (splitting) { if (option & type) { maxSize += size; QString unique = ""; for (int i = 0; QFile::exists(filename + unique + ".signed"); i++) unique = QString(".%1").arg(i); tmpFile.append(new QFile(filename+".signed")); } else tmpFile.append(nullptr); } else if (extracting) { if (type != PACKED_FILE_PINLIST) processExtract(autoloaderFile, size, offsets[i]); } } if (splitting) { // Write them out for (int i = 0; i < files; i++) { if (tmpFile.at(i) == nullptr) continue; autoloaderFile->seek(offsets[i]); tmpFile.at(i)->open(QIODevice::WriteOnly); tmpFile.at(i)->resize(offsets[i+1] - offsets[i]); QByteArray tmp; for (qint64 b = offsets[i]; b < offsets[i+1]; ) { qint64 read_len = qMin(BUFFER_LEN, offsets[i+1] - b); tmp = autoloaderFile->read(read_len); tmpFile.at(i)->write(tmp); b += updateProgress(tmp.size()); } tmpFile.at(i)->close(); } foreach (QFile* file, tmpFile) file->deleteLater(); tmpFile.clear(); autoloaderFile->close(); delete autoloaderFile; autoloaderFile = nullptr; } } // Process a Disk Image with the aim of extracting files void Splitter::processExtractSigned() { QFile* file = new QFile(selectedFile); // Gets cleaned up later devHandle.append(file); // By this if (!file->open(QIODevice::ReadOnly)) { return die("Could not open " + selectedFile); } processExtract(file, file->size(), 0); } // Process a zip container with the aim of extracting files void Splitter::processExtractBar() { QuaZip barFile(selectedFile); barFile.open(QuaZip::mdUnzip); foreach (QString signedName, barFile.getFileNameList()) { if (QFileInfo(signedName).suffix() == "signed") { // Create a new internal QuaZip instance so we can successfully close the search instance but leave devHandle open QuaZipFile* signedFile = new QuaZipFile(selectedFile, signedName); devHandle.append(signedFile); barFile.setCurrentFile(signedName); signedFile->open(QIODevice::ReadOnly); if (splitting) { qint64 size = signedFile->size(); int type = 0; if (size > 1024 * 1024 * 120) type = PACKED_FILE_OS; else if (size > 1024 * 1024 * 5) type = PACKED_FILE_RADIO | PACKED_FILE_IFS; // Could be either else type = PACKED_FILE_PINLIST; if (option & type) { read = 0; maxSize = size; progressChanged(0); QFile outputSigned(QFileInfo(selectedFile).canonicalPath() + "/" + signedName); outputSigned.open(QIODevice::WriteOnly); outputSigned.resize(size); QByteArray tmp; for (qint64 b = size; b > 0; ) { qint64 read_len = qMin(BUFFER_LEN, b); tmp = signedFile->read(read_len); outputSigned.write(tmp); b -= updateProgress(tmp.size()); } outputSigned.close(); } } else if (extracting) { progressChanged(0); if (signedFile->size() > 1024*1024*5) processExtract(signedFile, signedFile->size(), 0); } } } barFile.close(); } // Process a Filesystem Image with the aim of extracting files void Splitter::processExtractType() { QFile* imageFile = new QFile(selectedFile); // Gets cleaned up later devHandle.append(imageFile); // By this if (!imageFile->open(QIODevice::ReadOnly)) { return die("Could not open " + selectedFile); } partitionInfo.append(PartitionInfo(imageFile, 0, imageFile->size())); } QFileSystem* Splitter::createTypedFileSystem(QString name, QIODevice* dev, QFileSystemType type, qint64 offset, qint64 size, QString baseDir) { if (type == FS_RCFS) return new FS::RCFS(name, dev, offset, size, baseDir); else if (type == FS_QNX6) return new FS::QNX6(name, dev, offset, size, baseDir); else if (type == FS_IFS) return new FS::IFS(name, dev, offset, size, baseDir); return nullptr; } void Splitter::processExtract(QIODevice* dev, qint64 signedSize, qint64 signedPos) { if (signedPos > 0) dev->seek(signedPos); if (dev->read(4) != QByteArray("mfcq", 4)) { QMessageBox::information(nullptr, "Error", "Was not a Blackberry .signed image."); return; } QList partInfo; dev->seek(signedPos+12); // We are now at the partition table QByteArray partitionTable = dev->read(4000); QBuffer buffer(&partitionTable); buffer.open(QIODevice::ReadOnly); QNXStream tableStream(&buffer); int numPartitions, firstOffset, unknown, startSearchOffset; tableStream >> numPartitions >> firstOffset; tableStream >> unknown >> startSearchOffset; if (numPartitions > 15) { QMessageBox::information(nullptr, "Error", "Bad partition table."); return; } // Possible issue with other Signed? So far this is always zero /*qint64 signedOffset; for (int i = startSearchOffset; i < 1000 - 32; i+=4) { dev->seek(signedPos+i); signedOffset = -1; if (dev->read(4) == QByteArray("pfcq",4) && partitionTable.at(i) == 5) { // PFCQ User FS int nextVal; buffer.seek(i+8); tableStream >> nextVal; qDebug() << nextVal; if (nextVal == 3) { qDebug() << "Got a WEIRD offset"; int blockOffset, endOffset; buffer.seek(i+44); tableStream >> blockOffset; buffer.seek(i+56); tableStream >> endOffset; signedOffset = endOffset - blockOffset*16; break; } else if (nextVal == 1) { signedOffset = 0; break; } } }*/ partInfo.append(PartitionInfo(dev, signedPos + firstOffset)); for (int i = startSearchOffset; i < 1000 - 32; i += 4) { dev->seek(signedPos+i); if (dev->read(4) == QByteArray("pfcq",4)) { qint64 blocks = 0; int count = partitionTable.at(i+8); for (int j = 0; j < count; j++) { buffer.seek(i + j*16 + 44); int blockCount; tableStream >> blockCount; blocks += blockCount; } partInfo.last().size = blocks * (qint64)65536; partInfo.append(PartitionInfo(dev, partInfo.last().offset + partInfo.last().size)); } } partInfo.last().size = signedPos + signedSize - partInfo.last().offset; if (partInfo.last().size < 65536) partInfo.removeLast(); // Add to the main partition list foreach(PartitionInfo info, partInfo) { if (info.type == FS_UNKNOWN) continue; if (extractTypes & info.type) { partitionInfo.append(info); } } } sachesi-2.0.4+ds/src/splitter.h000066400000000000000000000223671362064257300163660ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include #include #include #include #include #include #include #ifndef BLACKBERRY #include #endif #include "ports.h" #include "fs/qnx6.h" #include "fs/rcfs.h" #include "fs/ifs.h" #include "autoloaderwriter.h" enum QFileSystemType { FS_UNKNOWN = 0, FS_RCFS = 1, FS_QNX6 = 2, FS_IFS = 4, }; struct ProgressInfo { int unique; qint64 curSize; qint64 maxSize; double progress; }; struct PartitionInfo { qint64 offset; qint64 size; QFileSystemType type; QIODevice* dev; QString image; // We are grabbing a partition of a container or image PartitionInfo(QIODevice* file, qint64 loc, qint64 size = 0) : offset(loc) , size(size) , dev(file) { detectType(dev); } void detectType(QIODevice* device) { // Check what sort of image we are dealing with if (!device->isSequential()) device->seek(offset); QByteArray header = device->read(4); if (header == QByteArray("rimh", 4)) type = FS_RCFS; else if (header == QByteArray::fromHex("EB109000")) type = FS_QNX6; else if (header == QByteArray::fromHex("FE0300EA") // Qualcomm || header == QByteArray::fromHex("FE0100EA")) // OMAP type = FS_IFS; else type = FS_UNKNOWN; } }; #define PACKED_FILE_USER (1 << 0) #define PACKED_FILE_OS (1 << 1) #define PACKED_FILE_RADIO (1 << 2) #define PACKED_FILE_IFS (1 << 3) #define PACKED_FILE_PINLIST (1 << 4) class Splitter: public QObject { Q_OBJECT public: Splitter(QString file) : selectedFile(file) { reset(); } Splitter(QString file, int options) : option(options), selectedFile(file) { reset(); } Splitter(QStringList files) : selectedFiles(files) { reset(); } Splitter(QList urls) : selectedUrls(urls) { reset(); } ~Splitter() { } bool extractApps, extractImage; int extractTypes; public slots: void reset() { kill = false; read = 0; maxSize = 1; emit progressChanged(0); extractApps = false; extractImage = false; extractTypes = 0; extracting = false; splitting = false; } void killSplit() { kill = true; die(); } void cleanDevHandle() { // Cleanup all pointers we had. foreach (QIODevice* dev, devHandle) { // QIODevice's are automatically closed. if (dev != nullptr) { dev->deleteLater(); dev = nullptr; } } devHandle.clear(); } void die(QString message = "") { qDebug() << "Tool terminated early due to unforseen circumstances."; if (extracting) { cleanDevHandle(); } if (combining) { newAutoloader->kill(); cleanDevHandle(); } if (splitting) { for (int i = 0; i < tmpFile.count(); i++) { if (tmpFile.at(i)->isOpen()) { tmpFile.at(i)->close(); delete tmpFile.at(i); tmpFile.removeAt(i); } } tmpFile.clear(); } extracting = false; combining = false; splitting = false; #ifndef BLACKBERRY if (!message.isEmpty()) QMessageBox::information(nullptr, "Error", message); #endif emit finished(); } void processSplitBar() { // And it seems we need a wrapper for splitting too. // Splitting is just extracting .signed, so lets integrate those after splitting = true; processExtractBar(); cleanDevHandle(); emit finished(); } void processExtractBar(); void processSplitAutoloader() { // Ditto with the above split bar splitting = true; processExtractAutoloader(); emit finished(); } void processExtractAutoloader(); static bool compareSizes(QFileInfo i, QFileInfo j) { return i.size() > j.size(); } void processCombine() { // Convert URL list to FileInfo list QList splitFiles; foreach(QUrl url, selectedUrls) { QFileInfo fileInfo = QFileInfo(url.toLocalFile()); if (fileInfo.isDir()) { QStringList suffixOnly = fileInfo.absoluteDir().entryList(QStringList() << "*.signed" << ".bar" << ".zip"); foreach (QString suffix, suffixOnly) { splitFiles.append(QFileInfo(fileInfo.absoluteFilePath() + "/" + suffix)); } } else if (fileInfo.suffix() == "signed" || fileInfo.suffix() == "bar" || fileInfo.suffix() == "zip") splitFiles.append(fileInfo); } // Sort files by size qSort(splitFiles.begin(), splitFiles.end(), compareSizes); // Convert FileInfo list to dev handle list cleanDevHandle(); foreach (QFileInfo info, splitFiles) { if (info.suffix() == "signed") { QFile* newFile = new QFile(info.absoluteFilePath()); newFile->open(QIODevice::ReadOnly); devHandle.append(newFile); } else { QuaZip barFile(info.absoluteFilePath()); barFile.open(QuaZip::mdUnzip); foreach (QString signedName, barFile.getFileNameList()) { if (QFileInfo(signedName).suffix() == "signed") { // Create a new internal QuaZip instance so we can successfully close the search instance but leave devHandle open QuaZipFile* signedFile = new QuaZipFile(info.absoluteFilePath(), signedName); // Couldn't open file. Shouldn't ever happen so don't worry about error if (!signedFile->open(QIODevice::ReadOnly)) { continue; } devHandle.append(signedFile); } } barFile.close(); } } if (devHandle.isEmpty()) return; if (devHandle.count() > 6) { QMessageBox::information(nullptr, "Error", "Autoloaders can only have a maximum of 6 signed files."); cleanDevHandle(); return; } // All good, lets officially start creating combining = true; // Create new Autoloader object newAutoloader = new AutoloaderWriter(devHandle); connect(newAutoloader, &AutoloaderWriter::newProgress, [=](int percent) { emit this->progressChanged(percent); }); // This is blocking, but we are in a thread newAutoloader->create(splitFiles.first().absolutePath() + "/" + splitFiles.first().completeBaseName()); cleanDevHandle(); delete newAutoloader; emit finished(); } void processExtractSigned(); void processExtract(QIODevice* dev, qint64 signedSize, qint64 signedPos); void processExtractType(); QFileSystem* createTypedFileSystem(QString name, QIODevice* dev, QFileSystemType type, qint64 offset = 0, qint64 size = 0, QString baseDir = "."); void processExtractWrapper(); // Old, compatibility quint64 updateProgress(qint64 delta) { if (delta < 0) return 0; read += 100 * delta; emit progressChanged((int)(read / maxSize)); return delta; } int newProgressInfo(qint64 size) { progressInfo.append(ProgressInfo()); progressInfo.last().maxSize = size; return progressInfo.count() - 1; } void updateCurProgress(int unique, qint64 bytes, qint64 delta) { // New, unused if (progressInfo.count() <= unique) return; progressInfo[unique].curSize = bytes; progressInfo[unique].progress = (double)(100*progressInfo[unique].curSize) / (double)progressInfo[unique].maxSize; // Old, compatibility read += 100 * delta; emit progressChanged((int)(read / maxSize)); } signals: void finished(); void progressChanged(int progress); void error(QString err); private: bool kill; quint64 read, maxSize; bool splitting; bool combining; bool extracting; int option; QString selectedFile; QStringList selectedFiles; QList selectedUrls; QList tmpFile; // New QList progressInfo; QList partitionInfo; QList devHandle; AutoloaderWriter* newAutoloader; }; sachesi-2.0.4+ds/src/translator.h000066400000000000000000000035331362064257300167030ustar00rootroot00000000000000// Copyright (C) 2014 Sacha Refshauge // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3.0. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 3.0 for more details. // A copy of the GPL 3.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official GIT repository and contact information can be found at // http://github.com/xsacha/Sachesi #pragma once #include #include class Translator : public QObject { Q_OBJECT Q_PROPERTY(bool exists MEMBER _exists NOTIFY existsChanged) Q_PROPERTY(QString lang MEMBER _lang NOTIFY langChanged) public: Translator() : QObject() , _exists(false) { // Install translator by locale language string // zh_HK is considered 'Chinese' language but the characters are entirely different. if ((QLocale().name() != "zh_HK") && _appTranslator.load(QString(":/translations/%1.qm") .arg(QLocale::languageToString(QLocale().language()))) ) { _exists = true; } emit existsChanged(); } Q_INVOKABLE void load() { if (_exists) { qApp->installTranslator(&_appTranslator); emit langChanged(); } } Q_INVOKABLE void remove() { qApp->removeTranslator(&_appTranslator); emit langChanged(); } signals: void existsChanged(); void langChanged(); private: bool _exists; QString _lang; QTranslator _appTranslator; }; sachesi-2.0.4+ds/translations.qrc000066400000000000000000000005451362064257300170020ustar00rootroot00000000000000 translations/English.ts translations/Russian.ts translations/French.ts translations/Spanish.ts translations/German.ts translations/Dutch.ts translations/Chinese.ts sachesi-2.0.4+ds/translations/000077500000000000000000000000001362064257300162675ustar00rootroot00000000000000sachesi-2.0.4+ds/translations/Chinese.ts000066400000000000000000001144031362064257300202200ustar00rootroot00000000000000 Backup Options 选项 Refreshing backup sizes 正在刷新备份大小 Unknown Size 未知大小 %1 MB %1 MB Total: 总计: Refresh Backup Sizes 刷新备份大小 Loading backup sizes can sometimes fail. In this situation, you can backup 'blind'. 加载备份的大小有时可能会失败。在这种情况下,你可以选择模糊备份。 Create Backup Blind 创建模糊备份 Choose Backup Filename 选择备份文件名 Select Restore File 选择恢复文件 Create Backup 创建备份 Restore Backup 还原备份 Blackberry Backup (*.bbb) 黑莓备份(*.bbb) Your device needs a Blackberry ID to perform backups or restores! 您的设备需要的BlackBerry ID来执行备份或还原! Please note that backups can take a long time, depending on your device data. 请注意,备份可能需要很长的时间,具体取决于您的设备数据。 Creating Backup (%1%) 创建备份(%1%) Restoring Backup (%1%) 恢复备份(%1%) Boot Boot Communication 启动通信 Info 信息 RimBoot 启动 Nuke 核心 Debug Mode 调试模式 Reboot after 稍后重启 Connecting to bootrom 正在连接到bootrom Cancel 取消 Detected devices: 已检测到设备: CircleProgress %1 of %2 %1的%2 Device Device Information 设备信息 Tools 工具 Wipe 擦除 Factory Reset 恢复出厂设置 Reboot 重启 Name 名字 Unknown 未知 HW Name 硬件名称 BBID BBID PIN PIN码 BSN 主板序列号 OS 操作系统 Radio 无线电 HW 硬件 Restrictions 限制 None Setup Complete 安装完成 True 已完成 True False 未完成 False Developer Mode 开发模式 Battery 电池 Connection 连接 USB USB Refurbished Date 刷新日期 Never 从不 Clear 清除 Set 设置 Free Disk Space 可用磁盘空间 %1 GB %1 GB Downloader #1. Start RTAS (Requires OS10.2) #1.开始RTAS( 要求OS10.2) Start RTAS 开始RTAS Extract Percentages are not entirely accurate for QNX6 files. Percentages不是完全准确的QNX6文件。 Cancel 取消 Extract Signed 提取签名 User 用户 OS 操作系统 Radio 无线电 PINList PIN列表 Create Autoloader 创建Autoloader Create from Folder 从文件夹中创建 Create from Files 从文件中创建 Create Autoloader .exe from .signed images 创建Autoloader .exe来自 .signed图片 Extraction Tools 提取工具 Split .signed from autoloader .exe, .bar or .zip 从autoloader.exe,.bar或者.zip文件中分离出.signed文件 Extracts all bar archives from a debrick/repair .signed 从debrick/repair .signed文件中提取所有的bar文件 Note: To extract apps from a .bar, please split it first (above) 注意:要从.bar提取应用程序,请首先将其分离(参见上面的功能) Dump Contents 转储内容 Core 核心 Boot 启动 Dump all file contents 转储所有文件内容 Extract Image 提取图片 Splitting Autoloader 正在分离Autoloader Combining Autoloader 组合Autoloader Extracting Image 提取图片 Extracting Apps 提取应用程序 Fetching required files 获取所需文件 Waiting 请稍后 Signed Images 签名图片 Signed Containers 签名容器 Extracts filesystem image 提取文件系统图片 Extract Apps 提取程序 Installer Please be patient while the installation zip is extracted. 请耐心等待提取.zip安装包。 Firmware Update 固件更新 Install 安装 To install <b>.bar</b> files such as applications or firmware, you can just <b>Drag and Drop</b> to this page. Otherwise, select the options below: 要安装<b>.bar</b>文件例如应用程序或固件,您只能<b>拖放到</b>该页面。否则,请选择下面的选项: Install applications to device 安装程序到设备 Blackberry Installable (*.bar) 黑莓手机可安装文件(*. bar) Install Folder 安装文件夹 Error: Your device can only process one task at a time. Please wait for backup/restore process to complete.<br> 错误:您的设备一次只能处理一个任务。请等待备份/恢复过程完成。<br> Select Folder 选择文件夹 Install Files 安装文件 Select Files 选择文件 View Install (%1) 查看安装(%1) Sending %1 发送%1 Installing %1 安装%1 Sent %1 发送%1 Error: Your device can only process one task at a time. Please wait for previous install to complete.<br> 错误:您的设备一次只能处理一个任务。请等待前面的安装完成。<br> Only install newer apps 只安装新的应用程序 Refresh 刷新 Your Applications 您的应用程序 Log 日志 Use 'Refresh' to update list 使用“刷新”以更新列表 Your device has not completed setup 您的设备尚未完成安装 Device disconnected 设备断开连接 Options 选项 Uninstall Marked 卸载标记 Show Installed Apps 显示已安装程序 Scanner No Release 没有释放 Search Download 下载 Cancel Download 取消下载 Searching... 搜索中... Search 搜索 Delta 增量 Version Lookup 版本查找 Success. No updates were available. 成功。无可用更新。 Download For 下载 Device 设备 Unknown 未知 Connected 已连接 As Searched 上次搜索 Grab Links 获取链接 Verifying 验证中 Server did not respond as expected [%1]. 服务器没有响应[%1]。 View Download (%1%) 查看下载(%1%) Hide 隐藏 Show Settings 显示设置 Whitelist Settings 白名单设置 Finds updates approved by other carriers 查找其他运营商推送的更新 Country 国家 Carrier 运营商 Search For 搜索 Any 任意 Variant 版本 Mode 模式 Upgrade 升级 Debrick 修复 Selected: %1 Apps 选择:%1应用程序 All (%1) 全部(%1) Needed: %1 Apps 需要:%1应用程序 (older) (更早的) (downloaded) (已下载) %1 MB %1 MB 1700.0 MB Options 选项 Check All 检查全部 Check All Needed 检查所需 Uncheck All 取消检查全部 Title Advanced 高级 Device 设备 Extract 提取 Search 搜索 Backup 备份 Install 安装 USBConnect These tools require a USB connection 该工具需要USB连接 Password: 密码: Incorrect 错误 Hide Password 隐藏密码 Show Password 显示密码 Detected %1 Blackberry USB device(s) in %2 mode. 在%2模式检测%1黑莓USB设备(多个)。 Talking to %1 possible device(s). 正在尝试和 %1 个设备建立对话. There was an issue connecting. 存在连接问题。 Try Again 再试一次 Searching for USB device 搜索USB设备中 VersionLookup Version Lookup 版本查找 Stop on: 停止条件: Next Found 发现下一个版本 Next Available Links 发现下一个可用链接 Never 从不 SR: %1 软件版本:%1 OS: %1 操作系统:%1 Servers: 服务器: Production 正式版 Beta Beta版 Alpha Alpha版 Lookup 查找 Stop Scan 停止扫描 Autoscan 自动扫描 Grab Public Links 获取公开链接 No Links Available 无可用链接 History 历史 Hide 隐藏 sachesi-2.0.4+ds/translations/Czech.ts000066400000000000000000001153721362064257300177040ustar00rootroot00000000000000 Backup Options Nastavení Unknown Size Neznámá velikost %1 MB %1 MB Total: Celkem: Refresh Backup Sizes Obnovit velikost zálohy Choose Backup Filename Vyberte soubor zálohy Select Restore File Vyberte soubor obnovy Create Backup Vytvořit zálohu Restore Backup Obnovit zálohu Blackberry Backup (*.bbb) Blackberry záloha (*.bbb) Refreshing backup sizes Obnovit velikosti záloh Loading backup sizes can sometimes fail. In this situation, you can backup 'blind'. Nahrání velké zálohy může někdy selhat. V tom případě použijte zálohu 'blind'. Create Backup Blind Vytvořit zálohu Blind Your device needs a Blackberry ID to perform backups or restores! Vaše zařízení potřebuje zadat Blackberry ID pro vytvoření zálohy nebo obnovy! Please note that backups can take a long time, depending on your device data. Prosím berte na vědomí že vytvoření zálohy může trvat delší dobu v závislosti na velikosti dat. Creating Backup (%1%) Vytváření zálohy (%1%) Restoring Backup (%1%) Obnovování zálohy (%1%) Boot Boot Communication Startovací komunikace Info Informace RimBoot RimBoot Nuke Zničit Debug Mode Ladící mód Reboot after Restart po Connecting to bootrom Připojování do bootrom Cancel Zrušit Detected devices: Detekované zařízení: CircleProgress %1 of %2 %1 z %2 Device Device Information Informace o zařízení Tools Nástroje Wipe Vymazání Factory Reset Tovární nastavení Reboot Reboot Name Jméno Unknown Neznámé HW Name HW jméno BBID BBID PIN PIN BSN BSN OS OS Radio Radio HW HW Restrictions Omezení None Nic Setup Complete Nastavení kompletní True Ano False Ne Developer Mode Developerský mód Battery Baterie Connection Připojení USB USB Refurbished Date Datum repasování Never Nikdy Clear Vyčištěno Set Nastavit Free Disk Space Volné místo na disku %1 GB %1 GB Downloader #1. Start RTAS (Requires OS10.2) #1. Start RTAS (Minimálně OS10.2) Start RTAS Start RTAS Extract Percentages are not entirely accurate for QNX6 files. Procenta nejsou zcela přesná pro QNX6 soubory. Cancel Zrušit Signed Images Podepsané soubory Extract Signed Extrahovat podepsané Signed Containers Podepsané kontejnery User Uživatel OS OS Radio Radio PINList PINList Create Autoloader Vytvořit Autoloader Splitting Autoloader Rozdělení Autoloaderu Combining Autoloader Kombinace Autoloaderu Extracting Image Extrahovat soubory Extracting Apps Extrahovat Aplikace Fetching required files Načíst požadované soubory Waiting Počkat Create from Folder Vytvořit ze složky Create from Files Vytvořit ze souborů Create Autoloader .exe from .signed images Vytvořit Autoloader .exe z podepsaných souborů Extraction Tools Extrahovací nástroje Split .signed from autoloader .exe, .bar or .zip Rozdělit podepsané soubory z autoloaderu .exe, .bar nebo .zip Extracts all bar archives from a debrick/repair .signed Extrahovat všechny bar soubory z debrick/repair .signed Note: To extract apps from a .bar, please split it first (above) Poznámka: Chcete-li extrahovat aplikace ze .bar, prosím nejprve rozdělte (nahoře) Dump Contents Vypsat obsah Core Core Boot Boot Dump all file contents Vypsat všechny soubory Extract Image Extrahovat obrázky Extracts filesystem image Extrahovat systémové soubory Extract Apps Extrahovat aplikace Installer Please be patient while the installation zip is extracted. Prosím buďte trpeliví, zatím se extrahuje instalační zip. Firmware Update Aktualizace firmware Install Instalace To install <b>.bar</b> files such as applications or firmware, you can just <b>Drag and Drop</b> to this page. Otherwise, select the options below: Chcete-li nainstalovat <b> .bar </b> soubory, jako je například aplikace nebo firmware, můžete je <b> Vzít a pustit </b> na této stránce. V opačném případě nastavte níže: Install applications to device Instalovat apligace do zařízení Blackberry Installable (*.bar) Blackberry instalační soubor (*.bar) Install Folder Instalační složky View Install (%1) Zobrazit instalaci (%1) Sending %1 Odesílání %1 Installing %1 Instalování %1 Sent %1 Odesílání %1 Error: Your device can only process one task at a time. Please wait for previous install to complete.<br> Chyba: Vaše zařízení může zpracovávat pouze jednu úlohu. Prosím počkejte na dokončení předchozí instalace.<br> Error: Your device can only process one task at a time. Please wait for backup/restore process to complete.<br> Chyba: Vaše zařízení může zpracovávat pouze jednu úlohu. Prosím počkejte na dokončení předchozí zálohy/obnovy.<br> Select Folder Vybrat složku Install Files Instalovat soubory Select Files Vybrat soubory Only install newer apps Instalovat pouze nové aplikace Refresh Aktualizovat Your Applications Vaše aplikace Log Log Use 'Refresh' to update list Stiskněte 'Aktualizovat' pro obnovení seznamu Your device has not completed setup Ve vašem zařízení není dokončeno nastavení Device disconnected Zařízení odpojené Options Nastavení Uninstall Marked Odinstalovat označené Show Installed Apps Zobrazit nainstalované aplikace Scanner No Release Zádná verze není k dispozici Search Download Stáhnout Cancel Download Zrušit stahování Searching... Vyhledávání... Search Hledat Delta Delta Version Lookup Najít verzi Success. No updates were available. Hotovo. Žádná aktualizace není dostupná. Download For Stáhnout pro Device Zařízení Unknown Neznámé Connected Připojeno As Searched Hledaný Grab Links Zobrazit odkazy Verifying Ověřit Server did not respond as expected [%1]. Server neodpověděl jak se očekávalo [%1]. View Download (%1%) Zobrazit stahování (%1%) Hide Skrýt Show Settings Zobrazit nastavení Whitelist Settings Whitelist nastavení Finds updates approved by other carriers Najít aktualizace schválené jiným operátorem Country Země Carrier Operátor Search For Hledat Any Jakýkoli Variant Varianta Mode Mód Upgrade Aktualizace Debrick Debrick Selected: %1 Apps Vybrané: %1 Aplikace All (%1) Všel (%1) Needed: %1 Apps Požadované: %1 Aplikace (older) (starší) (downloaded) (stažené) %1 MB %1 MB 1700.0 MB 1700.0 MB Options Nastavení Check All Vybrat vše Check All Needed Vybrat vše požadované Uncheck All Zrušit výběr Title Advanced Rozšířené Device Zařízení Extract Extrahovat Search Hledat Backup Zálohovat Install Instalovat USBConnect These tools require a USB connection Tento nástroj vyžaduje USB spojení Password: Heslo: Incorrect Nesprávné Hide Password Skrýt heslo Show Password Zobrazit heslo Detected %1 Blackberry USB device(s) in %2 mode. Detekováno %1 Blackberry USB zařízení v %2 módu. Talking to %1 possible device(s). Komunikace s %1 možným zařízením. There was an issue connecting. Zjištěný problém s připojením. Try Again Zkuste znovu Searching for USB device Vyhledávání zařízení připojené přes USB VersionLookup Version Lookup Hledat verzi Stop on: Zastavit na: Next Found Další nalezené Next Available Links Další dostupné odkazy Never Nikdy Production Produkční Beta Beta Alpha Alfa Lookup Vyhledat Stop Scan Zastavit scanování Autoscan Automatické hledání SR: %1 SR: %1 OS: %1 OS: %1 Servers: Servery: Grab Public Links Zobrazit veřejné odkazy No Links Available Odkazy nejsou dostupné History Historie Hide Skrýt sachesi-2.0.4+ds/translations/Dutch.ts000066400000000000000000001155141362064257300177150ustar00rootroot00000000000000 Backup Options Opties Refreshing backup sizes Bezig met vernieuwen van backup-groottes Unknown Size Grootte onbekend %1 MB %1 MB Total: Totaal: Refresh Backup Sizes Vernieuw backup-groottes Loading backup sizes can sometimes fail. In this situation, you can backup 'blind'. Backup-groottes kunnen niet altijd vernieuwd worden. In dat geval kunt u 'blind' een backup aanmaken. Create Backup Blind Maak backup blind aan Choose Backup Filename Voer backup-bestandsnaam in Select Restore File Kies herstelbestand Create Backup Maak backup aan Restore Backup Herstel backup Blackberry Backup (*.bbb) BlackBerry-backup-bestand (*.bbb) Your device needs a Blackberry ID to perform backups or restores! Uw toestel heeft een BlackBerry ID nodig om backups aan te maken of te herstellen! Please note that backups can take a long time, depending on your device data. Het aanmaken van een backup kan enige tijd in beslag nemen, afhankelijk van uw hoeveelheid data. Creating Backup (%1%) Bezig met aanmaken van backup (%1%) Restoring Backup (%1%) Bezig met herstellen van backup (%1%) Boot Boot Communication Boot-communicatie Info Info RimBoot RimBoot Nuke Nuke Debug Mode Debugmodus Reboot after Herstart na het proces Connecting to bootrom Bezig te verbinden met bootrom Cancel Annuleer Detected devices: Gevonden toestellen: CircleProgress %1 of %2 %1 van %2 Device Device Information Toestelinformatie Tools Gereedschappen Wipe Schoon toestel op Factory Reset Herstel fabrieksinstellingen Reboot Herstart toestel Name Naam toestel Unknown Onbekend HW Name Naam hardware BBID BlackBerry ID PIN PIN BSN Serienummer bord OS Besturingssysteem Radio Radio HW Modelnummer Restrictions Restricties None Geen Setup Complete Setup voltooid True Ja False Nee Developer Mode Ontwikkelingsmodus Battery Batterij Connection Verbinding USB USB Refurbished Date Revisie Never Nooit Clear Verwijder Set Voer in Free Disk Space Beschikbaar geheugen %1 GB %1 GB Downloader #1. Start RTAS (Requires OS10.2) #1. Start RTAS (vereist versie 10.2) Start RTAS Start RTAS Extract Percentages are not entirely accurate for QNX6 files. Percentages zijn voor QNX6-bestanden niet nauwkeurig. Cancel Annuleer Extract Signed Extraheer gesigneerde bestanden User Gebruiker OS Systeem Radio Radio PINList PINList Create Autoloader Maak autoloader aan Create from Folder Maak aan uit map Create from Files Maak aan uit bestanden Create Autoloader .exe from .signed images Maakt autoloader uit bestanden aan (.exe of .signed) Extraction Tools Extractiegereedschappen Split .signed from autoloader .exe, .bar or .zip Haalt gesigneerde bestanden uit autoloader (.exe, .bar of .zip) Extracts all bar archives from a debrick/repair .signed Haalt alle .bar-bestanden uit een debrick-/repair-bestand (.signed) Note: To extract apps from a .bar, please split it first (above) Opmerking: Om apps uit een .bar-bestand te halen, moet u eerst gesigneerde bestanden extraheren (zie boven) Dump Contents Extraheer inhouden Core Kern Boot Boot Dump all file contents Extraheert alle bestandsinhouden Extract Image Extraheer kopie Splitting Autoloader Bezig met extraheren van bestanden uit autoloader Combining Autoloader Bezig met aanmaken van autoloader Extracting Image Bezig met extraheren van kopie Extracting Apps Bezig met extraheren van apps Fetching required files Bezig met verzamelen van benodigde bestanden Waiting Wacht Signed Images Gesigneerde bestanden Signed Containers Gesigneerde archieven Extracts filesystem image Extraheert een kopie van het bestandssysteem Extract Apps Extraheer apps Installer Please be patient while the installation zip is extracted. Wacht totdat het installatie-archief is uitgepakt. Firmware Update Firmware-update Install Installatie To install <b>.bar</b> files such as applications or firmware, you can just <b>Drag and Drop</b> to this page. Otherwise, select the options below: Om <b>.bar</b>-bestanden zoals apps of firmware te installeren, kunt u deze bestanden op dit programma <b>slepen en neerzetten</b>. Als alternatief kunt u de volgende opties gebruiken: Install applications to device Installeer apps op toestel Blackberry Installable (*.bar) BlackBerry-installatiebestand (*.bar) Install Folder Installeer map Error: Your device can only process one task at a time. Please wait for backup/restore process to complete.<br> Fout: Uw toestel kan slechts een proces per keer verwerken. Wacht tot het backup-/herstelproces is voltooid.<br> Select Folder Kies map Install Files Installeer bestanden Select Files Kies bestanden View Install (%1) Bekijk installatie (%1) Sending %1 %1 wordt verstuurd Installing %1 %1 wordt geïnstalleerd Sent %1 %1 verstuurd Error: Your device can only process one task at a time. Please wait for previous install to complete.<br> Fout: Uw toestel kan slechts een proces per keer verwerken. Wacht tot het installatieproces is voltooid.<br> Only install newer apps Installeer alleen nieuwere apps Refresh Vernieuw Your Applications Uw apps Log Protocol Use 'Refresh' to update list Klik op 'Vernieuw' om de lijst te vernieuwen Your device has not completed setup Uw toestel heeft het proces niet voltooid Device disconnected Toestel is niet meer verbonden Options Opties Uninstall Marked Verwijder geselecteerde apps Show Installed Apps Toon geïnstalleerde apps Scanner No Release Niets gevonden Search Download Download Cancel Download Annuleer download Searching... Bezig met zoeken... Search Zoek Delta Delta Version Lookup Versiezoeker Success. No updates were available. Succes. Geen updates beschikbaar. Download For Download voor Device Toestel Unknown Onbekend Connected Verbonden As Searched Als gezocht Grab Links Laad links Verifying Bezig met verificiëren Server did not respond as expected [%1]. Server reageerde niet zoals verwacht [%1]. View Download (%1%) Bekijk download (%1%) Hide Verberg Show Settings Toon instellingen Whitelist Settings Whitelist-instellingen Finds updates approved by other carriers Vindt door providers toegestane updates Country Land Carrier Provider Search For Zoek voor Any Alle Variant Variant Mode Modus Upgrade Upgrade Debrick Debrick Selected: %1 Apps Geselecteerd: %1 apps All (%1) alle (%1) Needed: %1 Apps Benodigd: %1 apps (older) (ouder) (downloaded) (gedownload) %1 MB %1 MB 1700.0 MB 1700,0 MB Options Opties Check All Selecteer alle Check All Needed Selecteer benodigde Uncheck All Hef selectie op Title Advanced Geavanceerde opties Device Toestel Extract Extractie Search Zoeken Backup Backup Install Installatie USBConnect These tools require a USB connection Deze gereedschappen vereisen een USB-verbinding Password: Wachtwoord: Incorrect Onjuist Hide Password Verberg wachtwoord Show Password Toon wachtwoord Detected %1 Blackberry USB device(s) in %2 mode. %1 Blackberry-USB-toestel(len) in %2-modus gevonden. Talking to %1 possible device(s). Spreekt met %1 beschikb. toestel(len). There was an issue connecting. Bij het verbinden is een fout opgetreden. Try Again Probeer opnieuw Searching for USB device Bezig met zoeken naar USB-toestel VersionLookup Version Lookup Versiezoeker Stop on: Stop: Next Found Bij eerstvolgende vondst Next Available Links Bij eerstbeschikbare links Never Nooit SR: %1 Software: %1 OS: %1 Systeem: %1 Servers: Server: Production Productie Beta Bèta Alpha Alfa Lookup Zoek op Stop Scan Stop Autoscan Scan Grab Public Links Laad openbare links No Links Available Geen links beschikbaar History Geschiedenis Hide Verberg sachesi-2.0.4+ds/translations/English.ts000066400000000000000000001144211362064257300202330ustar00rootroot00000000000000 Backup Options Unknown Size %1 MB Total: Refresh Backup Sizes Choose Backup Filename Select Restore File Create Backup Restore Backup Blackberry Backup (*.bbb) Refreshing backup sizes Loading backup sizes can sometimes fail. In this situation, you can backup 'blind'. Create Backup Blind Your device needs a Blackberry ID to perform backups or restores! Please note that backups can take a long time, depending on your device data. Creating Backup (%1%) Restoring Backup (%1%) Boot Boot Communication Info RimBoot Nuke Debug Mode Reboot after Connecting to bootrom Cancel Detected devices: CircleProgress %1 of %2 Device Device Information Tools Wipe Factory Reset Reboot Name Unknown HW Name BBID PIN BSN OS Radio HW Restrictions None Setup Complete True False Developer Mode Battery Connection USB Refurbished Date Never Clear Set Free Disk Space %1 GB Downloader #1. Start RTAS (Requires OS10.2) Start RTAS Extract Percentages are not entirely accurate for QNX6 files. Cancel Signed Images Extract Signed Signed Containers User OS Radio PINList Create Autoloader Splitting Autoloader Combining Autoloader Extracting Image Extracting Apps Fetching required files Waiting Create from Folder Create from Files Create Autoloader .exe from .signed images Extraction Tools Split .signed from autoloader .exe, .bar or .zip Extracts all bar archives from a debrick/repair .signed Note: To extract apps from a .bar, please split it first (above) Dump Contents Core Boot Dump all file contents Extract Image Extracts filesystem image Extract Apps Installer Please be patient while the installation zip is extracted. Firmware Update Install To install <b>.bar</b> files such as applications or firmware, you can just <b>Drag and Drop</b> to this page. Otherwise, select the options below: Install applications to device Blackberry Installable (*.bar) Install Folder View Install (%1) Sending %1 Installing %1 Sent %1 Error: Your device can only process one task at a time. Please wait for previous install to complete.<br> Error: Your device can only process one task at a time. Please wait for backup/restore process to complete.<br> Select Folder Install Files Select Files Only install newer apps Refresh Your Applications Log Use 'Refresh' to update list Your device has not completed setup Device disconnected Options Uninstall Marked Show Installed Apps Scanner No Release Search Download Cancel Download Searching... Search Delta Version Lookup Success. No updates were available. Download For Device Unknown Connected As Searched Grab Links Verifying Server did not respond as expected [%1]. View Download (%1%) Hide Show Settings Whitelist Settings Finds updates approved by other carriers Country Carrier Search For Any Variant Mode Upgrade Debrick Selected: %1 Apps All (%1) Needed: %1 Apps (older) (downloaded) %1 MB 1700.0 MB Options Check All Check All Needed Uncheck All Title Advanced Device Extract Search Backup Install USBConnect These tools require a USB connection Password: Incorrect Hide Password Show Password Detected %1 Blackberry USB device(s) in %2 mode. Talking to %1 possible device(s). There was an issue connecting. Try Again Searching for USB device VersionLookup Version Lookup Stop on: Next Found Next Available Links Never Production Beta Alpha Lookup Stop Scan Autoscan SR: %1 OS: %1 Servers: Grab Public Links No Links Available History Hide sachesi-2.0.4+ds/translations/French.ts000066400000000000000000001167271362064257300200620ustar00rootroot00000000000000 Backup Options Options Refreshing backup sizes Mise à jour de la Taille des Sauvegardes Unknown Size Taille Inconnue %1 MB %1 MB Total: Total: Refresh Backup Sizes Actualiser la Tailles des Sauvegardes Loading backup sizes can sometimes fail. In this situation, you can backup 'blind'. Charger les Tailles de sauvegardes peut parfois échouer. Dans cette situation, vous pouvez sauvegarder en «aveugle». Create Backup Blind Créer une sauvegarde aveugle Choose Backup Filename Choisissez le Nom du fichier de la Sauvegarde Select Restore File Sélectionnez le fichier à restaurer Create Backup Créer une sauvegarde Restore Backup Restaurer la sauvegarde Blackberry Backup (*.bbb) Sauvegarde Blackberry (*.bbb) Your device needs a Blackberry ID to perform backups or restores! Votre appareil a besoin d'un identifiant BlackBerry ID pour effectuer des sauvegardes ou restaurations! Please note that backups can take a long time, depending on your device data. S'il vous plaît veuillez noter que les sauvegardes peuvent prendre un certain temps, en fonction des données de votre appareil. Creating Backup (%1%) Création d'une sauvegarde (%1%) Restoring Backup (%1%) Restauration d'une sauvegarde (%1%) Boot Boot Communication Communication de Démarrage (Boot) Info Info RimBoot RimBoot Nuke Nuke Debug Mode Mode de Débogage Reboot after Redémarrer après Connecting to bootrom Connexion au secteur de démarrage (bootrom) Cancel Annuler Detected devices: Périphériques détectés: CircleProgress %1 of %2 %1 sur %2 Device Device Information Informations sur le périphérique Tools Outils Wipe Nettoyage de sécurité Factory Reset Réinitialisation sortie d'usine Reboot Redémarrage Name Nom Unknown Inconnu HW Name Nom du Matériel BBID BBID PIN PIN BSN Numéro de Série BSN OS OS Radio Radio HW Matériel Restrictions Restrictions None Aucun Setup Complete Configuration terminée True Vrai False Faux Developer Mode Mode développeur Battery Batterie Connection Connexion USB USB Refurbished Date Date de remise au propre Never Jamais Clear Effacer Set Valider Free Disk Space Espace disque disponible %1 GB %1 GB Downloader #1. Start RTAS (Requires OS10.2) #1. Démarrer RTAS ( Nécessite OS10.2 ) Start RTAS Démarrer RTAS Extract Percentages are not entirely accurate for QNX6 files. Les pourcentages ne sont pas tout à fait exacts pour les fichiers QNX6. Cancel Annuler Extract Signed Extraire Fichiers Signés User Utilisateur OS OS Radio Radio PINList PINList Create Autoloader Créer un Lanceur (Autoloader) Create from Folder Créer à partir du Dossier Create from Files Créer à Partir des Fichiers Create Autoloader .exe from .signed images Créer un lanceur (Autoloader) .exe à partir d'images .signed Extraction Tools Outils d'Extraction Split .signed from autoloader .exe, .bar or .zip Scinder le .signed venant du Lanceur (Autoloader) .exe, .bar ou .zip Extracts all bar archives from a debrick/repair .signed Extraction de toutes les archives .bar à partir d'un fichier debrick/repair .signed Note: To extract apps from a .bar, please split it first (above) Remarque: Pour extraire des applications à partir d'un .bar, s'il vous plaît veuillez le scinder d'abord(ci-dessus) Dump Contents Vider le Contenu Core Core Boot Démarrage Dump all file contents Vider tous le contenus des fichiers Extract Image Extraire l'Image Splitting Autoloader Découpage du Lanceur (Autoloader) Combining Autoloader Combinaison du Lanceur (Autoloader) Extracting Image Extraction de l'image Extracting Apps Extraction des Apps Fetching required files Téléchargement des fichiers requis Waiting Attente Signed Images Images signées Signed Containers Conteneurs signés Extracts filesystem image Extrait d'image du système de fichiers Extract Apps Extrait les Applications Installer Please be patient while the installation zip is extracted. S'il vous plaît soyez patient pendant que le zip d'installation est extrait. Firmware Update Mise à jour du Firmware Install Installation To install <b>.bar</b> files such as applications or firmware, you can just <b>Drag and Drop</b> to this page. Otherwise, select the options below: Pour installer des <b>fichiers .bar</b> tels que des applications ou firmware, vous pouvez simplement <b>glisser-déposer</b>. Sinon, sélectionnez les options ci-dessous: Install applications to device Installer les applications sur l'appareil Blackberry Installable (*.bar) Fichier Blackberry Installable (*.bar) Install Folder Dossier d'Installation Error: Your device can only process one task at a time. Please wait for backup/restore process to complete.<br> Erreur: Votre appareil ne peut traiter qu'une seule tâche à la fois. S'il vous plaît attendre pour la sauvegarde / processus de restauration à compléter.<br> Select Folder Sélectionner un Dossier Install Files Installer les Fichiers Select Files Sélectionnez les Fichiers View Install (%1) Voir l'Installation (%1) Sending %1 Envoi de %1 Installing %1 Installation de %1 Sent %1 %1 Envoyé Error: Your device can only process one task at a time. Please wait for previous install to complete.<br> Erreur: Votre appareil ne peut traiter une tâche à la fois. Se il vous plaît attendre l'installation précédente à compléter. <br> Only install newer apps N'iInstaller uniquement que les applications avec une version plus récente Refresh Rafraîchir Your Applications Vos Applications Log Log Use 'Refresh' to update list Utilisez 'Rafraîchir' pour mettre à jour la liste Your device has not completed setup Votre appareil n'a pas terminé la configuration Device disconnected Appareil déconnecté Options Options Uninstall Marked Désinstaller la sélection Show Installed Apps Voir les Apps Installées Scanner No Release Pas de numéro de version Search Download Télécharger Cancel Download Annuler le Téléchargement Searching... Recherche... Search Rechercher Delta Delta Version Lookup Recherche de Version Success. No updates were available. Succès. Aucune mise à jour sont disponibles. Download For Télécharger pour Device Appareil Unknown Inconnu Connected Connecté As Searched Comme recherché Grab Links Télécharger les Liens Verifying Vérification Server did not respond as expected [%1]. Le serveur n'a pas répondu comme prévu [%1]. View Download (%1%) Voir Téléchargement (%1%) Hide Cacher Show Settings Afficher les paramètres Whitelist Settings Paramètres de la liste blanche Finds updates approved by other carriers Trouver les mises à jour approuvées par d'autres opérateurs Country Pays Carrier Opérateur Search For Rechercher pour Any Tout Variant Variante Mode Mode Upgrade Mise à jour Debrick Debrick Selected: %1 Apps Sélectionné: %1 Apps All (%1) Tous (%1) Needed: %1 Apps Requis: %1 Apps (older) (plus ancien) (downloaded) (téléchargé) %1 MB %1 MB 1700.0 MB 1700.0 Мo Options Options Check All Tout Cocher Check All Needed Cocher Tout le Nécessaire Uncheck All Tout Décocher Title Advanced Avancé Device Appareil Extract Extraire Search Chercher Backup Sauvegarde Install Installer USBConnect These tools require a USB connection Ces outils nécessitent une connexion USB Password: Mot de passe: Incorrect Incorrect Hide Password Masquer mot de passe Show Password Afficher le mot de passe Detected %1 Blackberry USB device(s) in %2 mode. %1 appareil(s) USB Blackberry en mode %2. Talking to %1 possible device(s). En discussion avec %1 appareil(s) possible(s). There was an issue connecting. Il y avait un problème de connexion. Try Again Essayer à Nouveau Searching for USB device Recherche d'un appareil USB VersionLookup Version Lookup Recherche de Version Stop on: Arrêt sur: Next Found Suivant Trouvé Next Available Links Liens suivants disponibles Never Jamais SR: %1 SR: %1 OS: %1 OS: %1 Servers: Serveurs: Production Production Beta Bêta Alpha Alpha Lookup Rechercher Stop Scan Arrêter de chercher Autoscan Autobalayage Grab Public Links Récupérer le Liens Publics No Links Available Pas de liens disponibles History Historique Hide Cacher sachesi-2.0.4+ds/translations/German.ts000066400000000000000000001157241362064257300200620ustar00rootroot00000000000000 Backup Options Optionen Refreshing backup sizes Aktualisiere Backup-Größen Unknown Size Größe unbekannt %1 MB %1 MB Total: Gesamt: Refresh Backup Sizes Backup-Größen aktualisieren Loading backup sizes can sometimes fail. In this situation, you can backup 'blind'. Backup-Größen können nicht immer aktualisiert werden. In diesem Fall können Sie 'blind' ein Backup erstellen. Create Backup Blind Backup blind erstellen Choose Backup Filename Backup-Dateiname eingeben Select Restore File Wiederherstellungsdatei auswählen Create Backup Backup erstellen Restore Backup Backup wiederherstellen Blackberry Backup (*.bbb) BlackBerry-Backup-Datei (*.bbb) Your device needs a Blackberry ID to perform backups or restores! Ihr Gerät erfordert eine BlackBerry ID um Backups zu erstellen oder wiederherzustellen! Please note that backups can take a long time, depending on your device data. Das Erstellen eines Backups kann einige Zeit dauern, abhängig von Ihrer Datenmenge. Creating Backup (%1%) Backup erstellen (%1%) Restoring Backup (%1%) Backup wiederherstellen (%1%) Boot Boot Communication Boot-Kommunikation Info Info RimBoot RimBoot Nuke Nuke Debug Mode Debugmodus Reboot after Nach dem Vorgang neustarten Connecting to bootrom Verbinde mit Bootrom Cancel Abbrechen Detected devices: Gefundene Geräte: CircleProgress %1 of %2 %1 von %2 Device Device Information Geräteinformationen Tools Werkzeuge Wipe Gerät vollständig löschen Factory Reset Auf Werkseinstellungen zurücksetzen Reboot Gerät neustarten Name Gerätename Unknown Unbekannt HW Name Hardwarename BBID BlackBerry ID PIN PIN BSN Platine-Seriennummer OS Betriebssystem Radio Radio HW Modellnummer Restrictions Einschränkungen None Keine Setup Complete Setup abgeschlossen True Ja False Nein Developer Mode Entwicklungsmodus Battery Akku Connection Verbindung USB USB Refurbished Date Generalüberholung Never Niemals Clear Zurücks. Set Eingeben Free Disk Space Freier Speicher %1 GB %1 GB Downloader #1. Start RTAS (Requires OS10.2) #1. RTAS starten (erfordert Version 10.2) Start RTAS RTAS starten Extract Percentages are not entirely accurate for QNX6 files. Prozentangaben sind für QNX6-Dateien ungenau. Cancel Abbrechen Extract Signed Signierte Dateien extrahieren User Anwender OS System Radio Radio PINList PINList Create Autoloader Autoloader erstellen Create from Folder Aus Ordner erstellen Create from Files Aus Dateien erstellen Create Autoloader .exe from .signed images Erstellt Autoloader aus Dateien (.exe oder .signed) Extraction Tools Extraktionswerkzeuge Split .signed from autoloader .exe, .bar or .zip Extrahiert signierte Dateien aus Autoloader (.exe, .bar oder .zip) Extracts all bar archives from a debrick/repair .signed Extrahiert alle .bar-Dateien aus einer Debrick-/Repair-Datei (.signed) Note: To extract apps from a .bar, please split it first (above) Hinweis: Um Apps aus einer .bar-Datei zu extrahieren, müssen Sie zuerst signierte Dateien extrahieren (siehe oben) Dump Contents Inhalte abladen Core Kern Boot Boot Dump all file contents Läd alle Dateiinhalte ab Extract Image Abbild extrahieren Splitting Autoloader Extrahiere Dateien aus Autoloader Combining Autoloader Erstelle Autoloader Extracting Image Extrahiere Abbild Extracting Apps Extrahiere Apps Fetching required files Sammle benötigte Dateien Waiting Warte Signed Images Signierte Dateien Signed Containers Signierte Archive Extracts filesystem image Extrahiert ein Abbild des Dateisystems Extract Apps Apps extrahieren Installer Please be patient while the installation zip is extracted. Bitte warten Sie bis das Installationsarchiv extrahiert wurde. Firmware Update Firmware-Update Install Installation To install <b>.bar</b> files such as applications or firmware, you can just <b>Drag and Drop</b> to this page. Otherwise, select the options below: Um <b>.bar</b>-Dateien wie Apps oder Firmware zu installieren, können Sie die Dateien auf diese Anwendung <b>ziehen und ablegen</b>. Alternativ können Sie die folgenden Optionen verwenden: Install applications to device Apps auf Gerät installieren Blackberry Installable (*.bar) BlackBerry-Installationsdatei (*.bar) Install Folder Ordner installieren Error: Your device can only process one task at a time. Please wait for backup/restore process to complete.<br> Fehler: Ihr Gerät kann nur eine Aufgabe gleichzeitig verarbeiten. Bitte warten Sie bis der Backup-/Wiederherstellungsvorgang abgeschlossen ist.<br> Select Folder Ordner auswählen Install Files Dateien installieren Select Files Dateien auswählen View Install (%1) Installation einblenden (%1) Sending %1 %1 wird versendet Installing %1 %1 wird installiert Sent %1 %1 versendet Error: Your device can only process one task at a time. Please wait for previous install to complete.<br> Fehler: Ihr Gerät kann nur eine Aufgabe gleichzeitig verarbeiten. Bitte warten Sie bis der Installationsvorgang abgeschlossen ist.<br> Only install newer apps Nur neuere Apps installieren Refresh Aktualisieren Your Applications Ihre Apps Log Protokoll Use 'Refresh' to update list Klicken Sie auf 'Aktualisieren' um die Liste zu aktualisieren Your device has not completed setup Ihr Gerät hat den Vorgang nicht abgeschlossen Device disconnected Gerät ist nicht mehr verbunden Options Optionen Uninstall Marked Ausgewählte Apps deinstallieren Show Installed Apps Installierte Apps zeigen Scanner No Release Nichts gefunden Search Download Herunterladen Cancel Download Ladevorgang abbrechen Searching... Suche ... Search Suchen Delta Delta Version Lookup Versionssuche Success. No updates were available. Erfolg. Keine Updates verfügbar. Download For Herunterladen für Device Gerät Unknown Unbekannt Connected Verbunden As Searched Wie gesucht Grab Links Links laden Verifying Verifiziere Server did not respond as expected [%1]. Server reagierte nicht wie erwartet [%1]. View Download (%1%) Ladevorgang einblenden (%1%) Hide Ausblenden Show Settings Einstellungen einblenden Whitelist Settings Whitelist-Einstellungen Finds updates approved by other carriers Findet von Netzbetreibern genehmigte Updates Country Land Carrier Netzbetreiber Search For Suchen für Any Alle Variant Variante Mode Modus Upgrade Upgrade Debrick Debrick Selected: %1 Apps Ausgewählt: %1 Apps All (%1) Alle (%1) Needed: %1 Apps Benötigt: %1 Apps (older) (älter) (downloaded) (heruntergeladen) %1 MB %1 MB 1700.0 MB 1700,0 MB Options Optionen Check All Alle auswählen Check All Needed Alle Benötigten auswählen Uncheck All Auswahl aufheben Title Advanced Fortgeschrittene Optionen Device Gerät Extract Extraktion Search Suche Backup Backup Install Installation USBConnect These tools require a USB connection Diese Werkzeuge erfordern eine USB-Verbindung Password: Kennwort: Incorrect Falsch Hide Password Kennwort ausblenden Show Password Kennwort einblenden Detected %1 Blackberry USB device(s) in %2 mode. %1 Blackberry-USB-Gerät(e) im %2-Modus gefunden. Talking to %1 possible device(s). Rede mit %1 verfügb. Gerät(en). There was an issue connecting. Beim Verbinden ist ein Fehler aufgetreten. Try Again Erneut versuchen Searching for USB device Suche nach USB-Gerät VersionLookup Version Lookup Versionssuche Stop on: Anhalten: Next Found Beim nächsten Fund Next Available Links Bei nächstverfügbaren Links Never Niemals SR: %1 Software: %1 OS: %1 System: %1 Servers: Server: Production Produktion Beta Beta Alpha Alpha Lookup Nachschauen Stop Scan Anhalten Autoscan Scannen Grab Public Links Öffentliche Links laden No Links Available Keine Links verfügbar History Verlauf Hide Ausblenden sachesi-2.0.4+ds/translations/Hungarian.ts000066400000000000000000001156611362064257300205650ustar00rootroot00000000000000 Backup Options Beállítások Refreshing backup sizes Mentés méretének frissítése Unknown Size Ismeretlen méret %1 MB %1 МБ Total: Összesen: Refresh Backup Sizes Mentés méretének frissítése Loading backup sizes can sometimes fail. In this situation, you can backup 'blind'. A mentés méretének kiszámítása néha nem sikerül. Ebben az esetben készíthetsz mentést "vakon". Create Backup Blind Mentés "vakon" Choose Backup Filename Válaszd ki a mmentés nevét Select Restore File Visszaállítási fájl kiválasztása Create Backup Mentés készítése Restore Backup Mentés visszaállítása Blackberry Backup (*.bbb) Blackberry mentés (*.bbb) Your device needs a Blackberry ID to perform backups or restores! Az eszközödnek szüksége van egy Blackberry ID-ra a mentéshez, visszaállításhoz! Please note that backups can take a long time, depending on your device data. A mentés hosszú ideig is eltarthat az eszközön lévő adatoktól függően. Creating Backup (%1%) Mentés készítése (%1%) Restoring Backup (%1%) Mentés visszaállítása (%1%) Boot Boot Communication Kommunikáció indításkor Info Információ RimBoot RimBoot Nuke Nuke Debug Mode Hibakeresési mód Reboot after Újraindítás Connecting to bootrom Csatlakozás a bootrom-hoz Cancel Leállít Detected devices: Érzékelt eszközök: CircleProgress %1 of %2 %1 a %2 -ból Device Device Information Eszköz információ Tools Eszközök Wipe Törlés Factory Reset Gyári beállítás Reboot Újraindítás Name Név Unknown Ismeretlen HW Name Hardver megnevezése BBID BBID PIN PIN BSN BSN OS OS Radio Rádió HW Hardver Restrictions Korlátozások None Nincs Setup Complete Beállítás befejezve True Igaz False Hamis Developer Mode Fejlesztői mód Battery Akkumulátor Connection Csatlakozás USB USB Refurbished Date Újrahasznosítás dátuma Never Soha Clear Töröl Set Beállít Free Disk Space Szabad terület %1 GB %1 GB Downloader #1. Start RTAS (Requires OS10.2) #1. RTAS indítás (OS10.2 szükséges) Start RTAS RTAS indítás Extract Percentages are not entirely accurate for QNX6 files. Percentages are not entirely accurate for QNX6 files. Cancel Leállít Extract Signed Signed fájl kitömörítés Signed Containers Signed Containers User Felhasználó OS ОS Radio Rádió PINList PIN Lista Create Autoloader Autoloader készítése Signed Images Signed képfájlok Create from Folder Készítés könyvtárból Create from Files Készítés fájlból Create Autoloader .exe from .signed images Autoloader.exe készítése .signed fájlból Extraction Tools Kitömörítési eszközök Split .signed from autoloader .exe, .bar or .zip Signed fájl kitömörítése autoloader.exe, .bar vagy .zip fájlból Extracts all bar archives from a debrick/repair .signed Minde .bar fájl kitömörítése debrick/repair .signed fájlból Note: To extract apps from a .bar, please split it first (above) Info: A .bar fájlok kitömörítéséhez előbb szét kell őket szedni. Dump Contents Tartalom kimentése Core Core Boot Boot Dump all file contents Összes fájl tartalmának kimentése Extract Image Image fájl kitömörítése Splitting Autoloader Autoloader szétvágása Combining Autoloader Autoloader összerakása Extracting Image Image fájl kitömörítése Extracting Apps Alkalmazások kitömörítése Fetching required files Szükséges fájlok megszerzése Waiting Várakozás Extracts filesystem image Fájlrendszer kép kitömörítése Extract Apps Alkalmazások kitömörítése Installer Please be patient while the installation zip is extracted. Légy türelemmel, amíg a telepítöt kitömörítem. Firmware Update Firmware Install Telepítés To install <b>.bar</b> files such as applications or firmware, you can just <b>Drag and Drop</b> to this page. Otherwise, select the options below: A .bar fájlok telepítéséhez csak húzd ide a fájlt. Egyébiránt választhatod a következő opciókat is: Install applications to device Alkalmazások telepítése az eszközre Blackberry Installable (*.bar) Blackberry alkalmazás (*.bar) Install Folder Könyvtárból telepítés Error: Your device can only process one task at a time. Please wait for backup/restore process to complete.<br> Hiba: Az eszközöd csak egy folyamatot kezel egy időben. Kérlek várd meg a mentés/visszaállítás folyamat végét.<br> Select Folder Válassz könyvtárat Install Files Fájlok telepítése Select Files Fájlok kiválasztása View Install (%1) (%1) telepítésének megtekintése Sending %1 %1 küldése Installing %1 %1 telepítése Sent %1 %1 elküldve Error: Your device can only process one task at a time. Please wait for previous install to complete.<br> Hiba: Az eszközöd csak egy folyamatot kezel egy időben. Kérlek várd meg az előző telepítési folyamat végét.<br> Only install newer apps Csak az újabb alkalmazások telepítése Refresh Lista frissítése Your Applications Alkalmazásaid Log Napló Use 'Refresh' to update list Használd a "Lista frissítése" gombot az újratöltéshez Your device has not completed setup Nem fejezted be az eszköz beállítását Device disconnected Eszköz leválasztva Options Beállítások Uninstall Marked Kijelöltek törlése Show Installed Apps Telepített alkalmazások mutatása Scanner No Release Nincs újabb kiadás Search Download Letöltés Cancel Download Letöltés megszakítása Searching... Keresés... Search Keresés Delta Delta Version Lookup Verzió keresés Success. No updates were available. Sikeres. Nincs elérhető frissítés. Download For Letöltés Device Készülék Unknown Ismeretlen készülék Connected Csatlakoztatva As Searched As Searched Grab Links Linkek mentése Verifying Ellenőrzés Server did not respond as expected [%1]. A szerver nem válaszol [%1]. View Download (%1%) Letöltés megtekintése (%1%) Hide Elrejt Show Settings Beállítások megtekintése Whitelist Settings Engedélyezési beállítások Finds updates approved by other carriers Más szolgáltatótól származó frissítés keresése Country Ország Carrier Szolgáltató Search For Keresés Any Bármelyik Variant Verzió Mode Mód Upgrade Frissítés Debrick Debrick Selected: %1 Apps Kiválasztva: %1 alkalmazás All (%1) Összes (%1) Needed: %1 Apps Szükséges: %1 alkalmazás (older) (régebbi) (downloaded) (letöltve) %1 MB %1 МБ 1700.0 MB 1700.0 МБ Options Beállítások Check All Összes kijelölése Check All Needed Mind szükséges Uncheck All Összes kiválasztás törlése Title Advanced Hozzáértő mód Device Eszköz Extract Kitömörítés Search Keres Backup Mentés Install Telepítés USBConnect These tools require a USB connection Az eszköz USB kapcsolatot igényel Password: Jelszó: Incorrect Helytelen Hide Password Jelszó elrejtése Show Password Jelszó mutatása Detected %1 Blackberry USB device(s) in %2 mode. %1 Blackberry eszközt érzékelek %2 módban. Talking to %1 possible device(s). Kommunikáció %1 lehetséges eszközzel. There was an issue connecting. Hiba történt a kapcsolódás során. Try Again Újra próbál Searching for USB device USB eszközök keresése VersionLookup Version Lookup Verzió keresés Stop on: Megállít: Next Found Következő találat Next Available Links Következő elérhető link Never Soha SR: %1 SR: %1 OS: %1 ОS: %1 Servers: Szerverek: Production Production Beta Beta Alpha Alpha Lookup Keresés Stop Scan Megállít Autoscan Automatikus keresés Grab Public Links Linkek mentése No Links Available Nincs elérhető link History Keresési történet Hide Elrejt sachesi-2.0.4+ds/translations/Italian.ts000066400000000000000000001157031362064257300202270ustar00rootroot00000000000000 Backup Options Opzioni Refreshing backup sizes Aggiornando le dimensioni del file di backup... Unknown Size Dimensione Sconosciuta %1 MB %1 MB Total: Totale: Refresh Backup Sizes Aggiorna Dimensione del File di Backup Loading backup sizes can sometimes fail. In this situation, you can backup 'blind'. Il calcolo delle dimensioni del file di backup può fallire a volte. In tal caso, è possibile procedere al backup 'alla cieca'. Create Backup Blind Crea Backup alla Cieca Choose Backup Filename Scegli il Nome per il File di Backup Select Restore File Seleziona File da Ripristinare Create Backup Crea Backup Restore Backup Ripristina Backup Blackberry Backup (*.bbb) Backup BlackBerry (*.bbb) Your device needs a Blackberry ID to perform backups or restores! Il tuo device deve essere associato ad un BlackBerry ID per poter effettuare operazioni di backup e restore! Please note that backups can take a long time, depending on your device data. Notare che i backup possono richiedere molto tempo, a seconda dei dati presenti sul device. Creating Backup (%1%) Effettuando il Backup (%1%) Restoring Backup (%1%) Ripristinando il Backup (%1%) Boot Boot Communication Comunicazione col Boot Info Info RimBoot RimBoot Nuke Nuke Debug Mode Modalità Debug Reboot after Riavvia dopo Connecting to bootrom Connessione alla Bootrom in corso... Cancel Annulla Detected devices: Device trovati: CircleProgress %1 of %2 %1 di %2 Device Device Information Informazioni sul Device Tools Strumenti Wipe Pulisci unità (wipe) Factory Reset Ripristino di Fabbrica Reboot Riavvia Name Nome Unknown Sconosciuto HW Name Nome Hardware BBID BlackBerry ID PIN PIN BSN BSN OS Sistema Operativo Radio Radio HW Codice modello Restrictions Restrizioni None Niente Setup Complete Setup Completato True Vero False Falso Developer Mode Modalità di Sviluppo Battery Batteria Connection Connessione USB USB Refurbished Date Ripristina Data Never Mai Clear Pulisci Set Imposta Free Disk Space Spazio Libero sul Disco %1 GB %1 GB Downloader #1. Start RTAS (Requires OS10.2) #1. Lancia RTAS (richiede 10.2) Start RTAS Lancia RTAS Extract Percentages are not entirely accurate for QNX6 files. Le percentuali non sono perfettamente accurate per i file QNX6. Cancel Annulla Extract Signed Estrai Signed User Utente OS Sistema Operativo Radio Radio PINList PINList Create Autoloader Crea Autoloader Create from Folder Crea da Cartella Create from Files Crea da File Create Autoloader .exe from .signed images Crea un autoloader da file Signed (.exe da .signed) Extraction Tools Strumenti di Estrazione Split .signed from autoloader .exe, .bar or .zip Estrai i file Signed da un autoloader (.exe, .bar o .zip) Extracts all bar archives from a debrick/repair .signed Estrai tutti i file Bar da un archivio di debrick/riparazione (.signed) Note: To extract apps from a .bar, please split it first (above) N.B.: Per estrarre applicazioni da un file Bar, estrarre prima lo stesso (vedi sopra) Dump Contents Dump dei Contenuti Core Core Boot Boot Dump all file contents Dump di tutti i Contenuti Extract Image Estrai Immagine Splitting Autoloader Estraendo l'autoloader Combining Autoloader Unendo l'autoloader Extracting Image Estrazione dell'immagine in corso... Extracting Apps Estrazione delle applicazioni in corso... Fetching required files Recupero dei file richiesti in corso... Waiting In attesa... Signed Images Signed Containers Extracts filesystem image Estrai l' del File System Extract Apps Estrai Applicazioni Installer Please be patient while the installation zip is extracted. Attendi mentre il file di installazione zip viene estratto. Firmware Update Aggiorna il Firmware Install Installa To install <b>.bar</b> files such as applications or firmware, you can just <b>Drag and Drop</b> to this page. Otherwise, select the options below: Per installare file <b>.bar</b> come applicazioni o firmware, puoi semplicemente fare un <b>Drag and Drop</b> su questa pagina. Altrimenti seleziona le opzioni sotto: Install applications to device Installa le applicazioni nel device Blackberry Installable (*.bar) File di Installazione BlackBerry (*.bar) Install Folder Installa Cartella Error: Your device can only process one task at a time. Please wait for backup/restore process to complete.<br> Errore: il tuo device può eseguire una sola azione alla volta. Attendi il completamento del backup/restore precedente.<br> Select Folder Seleziona Cartella Install Files installa Files Select Files Seleziona Files View Install (%1) Avanzamento Installazione (%1) Sending %1 Invio: %1 Installing %1 Installazione: %1 Sent %1 %1 inviato Error: Your device can only process one task at a time. Please wait for previous install to complete.<br> Errore: il tuo device può eseguire una sola azione alla volta. Attendi il completamento della precedente installazione.<br> Only install newer apps Refresh Aggiorna Your Applications Le tue Applicazioni Log Log Use 'Refresh' to update list Clicca 'Aggiorna' per aggiornare la lista Your device has not completed setup Il tuo device non ha completato il setup Device disconnected Device disconnesso Options Opzioni Uninstall Marked Selezionato per la disinstallazione Show Installed Apps Mostra le Applicazioni Installate Scanner No Release Nessuna Release Search Download Download Cancel Download Annulla Download Searching... Ricerca in Corso... Search Cerca Delta Delta Version Lookup Ricerca Versione Success. No updates were available. Completato. Nessun aggiornamento trovato. Download For Scarica per Device Device Unknown Sconosciuto Connected Connesso As Searched Come Cercato Grab Links Trova i link Verifying Verifica in Corso... Server did not respond as expected [%1]. Risposta del server inaspettata [%1]. View Download (%1%) Avanzamento Download (%1%) Hide Nascondi Show Settings Whitelist Settings Impostazioni whilelist Finds updates approved by other carriers Trova aggiornamenti rilasciati da altri operatori Country Stato Carrier Provider Search For Cerca per Any Tutti Variant Variante Mode Modo Upgrade Aggiorna Debrick Debrick Selected: %1 Apps Selezionate %1 applicazioni All (%1) Totale (%1) Needed: %1 Apps Richeste %1 applicazioni (older) (più vecchie) (downloaded) (scaricate) %1 MB %1 MB 1700.0 MB 1700,0 MB Options Opzioni Check All Seleziona tutto Check All Needed Seleziona tutte quelle richieste Uncheck All Deseleziona tutto Title Advanced Opzioni Avanzate Device Device Extract Estrai Search Cerca Backup Backup Install Installa USBConnect These tools require a USB connection Questi strumenti richiedono una connessione USB Password: Password: Incorrect Non corretta Hide Password Nascondi Password Show Password Mostra Password Detected %1 Blackberry USB device(s) in %2 mode. Trovato %1 device BlackBerry (USB) in modalità %2 Talking to %1 possible device(s). Dialogando con %1 possibili device. There was an issue connecting. Errore di connessione. Try Again Riprova Searching for USB device Ricerca device USB in corso... VersionLookup Version Lookup Ricerca Versione Stop on: Ferma a: Next Found Prossima trovata Next Available Links Prossima disponibile per il download Never Mai SR: %1 Software Release: %1 OS: %1 Versione Sistema Opeativo: %1 Servers: Server: Production Produzione Beta Beta Alpha Alfa Lookup Cerca Stop Scan Ferma Scansione Autoscan Ricerca automatica Grab Public Links Trova link pubblici No Links Available Nessun link disponibile History Hide Nascondi sachesi-2.0.4+ds/translations/Russian.ts000066400000000000000000001411051362064257300202650ustar00rootroot00000000000000 Backup Options Параметры Refreshing backup sizes Определение размера резервной копии Unknown Size Размер неизвестен %1 MB %1 МБ Choose Выбрать Choose Application Data Выбрать данные приложений Total Application Data: %1 MB (%2 Apps) Общий размер данных: %1 МБ (%2 программ) Selected Application Data: %1 MB Выбрано данных приложений: %1 МБ Check All Visible Отметить все Uncheck All Visible Снять все отметки Total: Всего: Refresh Backup Sizes Определить размер резервной копии Loading backup sizes can sometimes fail. In this situation, you can backup 'blind'. Определение размера резервной копии иногда завершается ошибкой, Вы можете сделать копию "вслепую". Create Backup Blind Создание резервной копии "вслепую" Choose Backup Filename Введите имя файла резервной копии Select Restore File Выберите файл восстановления Create Backup Создать резервную копию Restore Backup Восстановить из резервной копии Blackberry Backup (*.bbb) Blackberry Backup (*.bbb) Timed out Время ожидания истекло The request timed out. Please backup blind. This is a bug that Blackberry needs to fix. Время ожидания запроса истекло. Пожалуйста выполните операцию резервного копирования вслепую. Your device needs a Blackberry ID to perform backups or restores! Устройство нуждается в верификации Blackberry ID для выполнения резервного копирования и восстановления! Please note that backups can take a long time, depending on your device data. Пожалуйста, обратите внимание, что резервное копирование может занять много времени в зависимости от объёма данных Вашего устройства. Creating Backup (%1%) Создание резервной копии (%1%) Restoring Backup (%1%) Восстановление (%1%) Boot Boot Communication Параметры загрузчика Info Информация RimBoot RimBoot Nuke Nuke Debug Mode Режим отладки Reboot after Перезагрузить после завершения Connecting to bootrom Подключение к загрузчику Cancel Отмена Detected devices: Обнаруженные устройства: CircleProgress %1 of %2 %1 из %2 Device Device Information Информация об устройстве Tools Инструменты Wipe Очистка Factory Reset Сброс настроек Reboot Перезагрузка Name Имя устройства Unknown Неизвестно HW Name Ревизия BBID BBID PIN PIN BSN Серийный номер OS ОС Radio Радиомодуль HW Номер модели Restrictions Ограничения None Нет Setup Complete Настройка завершена True Да False Нет Developer Mode Режим разработки Battery Батарея Connection Подключение USB USB Refurbished Date Дата восстановления Never Никогда Clear Чисто Set Установить Free Disk Space Свободное дисковое пространство %1 GB %1 ГБ DeviceSub Device Information Информация об устройстве Tools Инструменты Wipe Очистка Factory Reset Сброс настроек Reboot Перезагрузка Name Имя устройства Unknown Неизвестно HW Name Ревизия BBID BBID PIN PIN BSN Серийный номер OS ОС Radio Радиомодуль HW Номер модели Restrictions Ограничения None Нет Setup Complete Настройка завершена True Да False Нет Developer Mode Режим разработки Battery Батарея Connection Подключение USB USB Refurbished Date Дата восстановления Never Никогда Clear Чисто Set Установить Free Disk Space Свободное дисковое пространство %1 GB %1 ГБ Downloader #1. Start RTAS (Requires OS10.2) #1. Запуск RTAS (требуется OS10.2) Start RTAS Запуск RTAS Extract Percentages are not entirely accurate for QNX6 files. Проценты не совсем точны для файлов QNX6. Cancel Отмена Extract Signed Распаковать Signed Containers Подписанные Контейнеры User User OS OS Radio Радиомодуль PINList PIN-лист Create Autoloader Создать автолоадер Signed Images Подписанные образы Create from Folder Создать из папки Create from Files Создать из файлов Create Autoloader .exe from .signed images Создание Autoloader.exe из подписанных образов Extraction Tools Инструменты для работы с автолоадером Split .signed from autoloader .exe, .bar or .zip Распаковка autoloader.exe, .bar или .zip Extracts all bar archives from a debrick/repair .signed Извлечение всех bar-файлов из разобранного автолоадера Note: To extract apps from a .bar, please split it first (above) Примечание: Для того, чтобы извлечь bar-файлы, необходимо сначала произвести распаковку автолоадера. Dump Contents Дамп содержимого Core Ядро Boot Загрузчик Dump all file contents Дамп содержимого всех файлов Extract Image Извлечь образ Splitting Autoloader Разборка автолоадера Combining Autoloader Сборка автолоадера Extracting Image Извлечение образа Extracting Apps Извлечение приложений Fetching required files Получение необходимых файлов Waiting Ожидание Extracts filesystem image Извлечение образов файловой системы Extract Apps Извлечь приложения Installer Please be patient while the installation zip is extracted. Пожалуйста, дождитесь окончания распаковки архива. Firmware Update Обновление прошивки Install Установка Sending Отправка Installing Установка Sent Отправлено To install <b>.bar</b> files such as applications or firmware, you can just <b>Drag and Drop</b> to this page. Otherwise, select the options below: Для установки <b>bar</b>-файлов, таких, как приложения или прошивки, просто <b> перетащите</b> их на эту страницу. Либо выберите нужные параметры ниже: Install applications to device Установка приложений на устройство Blackberry Installable (*.bar) Установочный файл для Blackberry (*.bar) Install Folder Установить из папки Error: Your device can only process one task at a time. Please wait for backup/restore process to complete.<br> Ошибка: Устройство может обрабатывать только одну задачу за один раз. Пожалуйста, дождитесь завершения операции резервного копирования/восстановления.<br> Select Folder Выбор папки Install Files Установить файлы Select Files Выбрать файлы View Install (%1) Показать установленные (%1) Sending %1 Отправка %1 Installing %1 Установка %1 Sent %1 Отправлено %1 Error: Your device can only process one task at a time. Please wait for previous install to complete.<br> Ошибка: Устройство может обрабатывать только одну задачу за один раз. Пожалуйста, дождитесь окончания предыдущей установки.<br>; Only install newer apps Устанавливать только новые версии приложений Refresh Обновить Your Applications Установлено Log Лог Use 'Refresh' to update list Нажмите "Обновить" для обновления списка Your device has not completed setup Настройка устройства не завершена Device disconnected Устройство отключено Options Опции Uninstall Marked Удалить отмеченные Show Installed Apps Показать установленные приложения Scanner No Release Нет обновлений Search Download Загрузить прошивку Cancel Download Прекратить загрузку Searching... Идет поиск... Search Найти Delta Дельта Version Lookup Поиск версии Success. No updates were available. Готово. Нет доступных обновлений. Download For Загрузить для Device Устройство Unknown Неизвестное устройство Connected Подключенное As Searched Как искали Grab Links Получить ссылки Verifying Проверка Server did not respond as expected [%1]. Удалённый сервер не отвечает [%1]. View Download (%1%) Посмотреть загрузку (%1%) Hide Скрыть Show Settings Показать настройки Whitelist Settings Настройка белого списка Finds updates approved by other carriers Поиск доступных обновлений у операторов сотовой связи Country Страна Carrier Оператор Search For Искать для Any Любая Variant Вариант Mode Режим Upgrade Обновление Debrick Восстановление Selected: %1 Apps Выбрано: %1 приложений All (%1) Все (%1) Needed: %1 Apps Нужно: %1 приложений (older) (старая версия) (downloaded) (загружено) %1 MB %1 МБ 1700.0 MB 1700.0 МБ Options Параметры Check All Отметить все Check All Needed Отметить все необходимые Uncheck All Снять все отметки Title Advanced Расширенный режим Device Устройство Extract Извлечение Search Поиск Backup Резервное копирование Install Установка USBConnect These tools require a USB connection Эти инструменты требуют подключения по USB Password: Пароль: Incorrect Неверно Hide Password Скрыть пароль Show Password Показать пароль Detected %1 Blackberry USB device(s) in %2 mode. Обнаружено %1 Blackberry USB устройство(а ) в режиме %2. Talking to %1 possible device(s). Запрос к %1 возможному устройству. Connection Log Лог подключения There was an issue connecting. Возникли проблемы с подключением. Try Again Попробуйте еще раз Searching for USB device Поиск USB-устройств VersionLookup Version Lookup Поиск версии Stop on: Остановить на: Next Found Доступной версии ПО Next Available Links Доступной ссылке для загрузки Never Никогда SR: %1 ПО: %1 OS: %1 ОС: %1 Servers: Серверы: Production Производство Beta Бета Alpha Альфа Lookup Поиск Stop Scan Остановить поиск Autoscan Автопоиск Grab Public Links Получить ссылки No Links Available Нет доступных ссылок History История Hide Скрыть sachesi-2.0.4+ds/translations/Spanish.ts000066400000000000000000001161531362064257300202530ustar00rootroot00000000000000 Backup Options Opciones Refreshing backup sizes Refrescando tamaños de Copia de Seguridad Unknown Size Tamaño Desconocido %1 MB %1 MB Total: Total: Refresh Backup Sizes Actualizar Tamaños de Copia de Seguridad Loading backup sizes can sometimes fail. In this situation, you can backup 'blind'. Cargando tamaños de copia de seguridad a veces puede fallar. En esa situación, usted puede realizar copias de seguridad "a ciegas". Create Backup Blind Crear copia de seguridad de Ciegos Choose Backup Filename Elija el nombre de archivo de copia de seguridad Select Restore File Seleccionar archivo de restauración Create Backup Crear Copia de Seguridad Restore Backup Restaurar Copia de Seguridad Blackberry Backup (*.bbb) Copia de Seguridad Blackberry (*.bbb) Your device needs a Blackberry ID to perform backups or restores! Su dispositivo necesita un ID de Blackberry para realizar copias de seguridad o restauraciones! Please note that backups can take a long time, depending on your device data. Por favor, tenga en cuenta que las copias de seguridad pueden llevar mucho tiempo, dependiendo de los datos del dispositivo. Creating Backup (%1%) Crear Copia de Seguridad (%1 %) Restoring Backup (%1%) Restaurando Copia de Seguridad (%1 %) Boot Boot Communication Comunicación de Inicio Info Info RimBoot RimBoot Nuke Destruir Debug Mode Modo de Depuración Reboot after Reiniciar Después Connecting to bootrom Conectando con la ROM de inicio Cancel Cancelar Detected devices: Dispositivos Detectados: CircleProgress %1 of %2 %1 de %2 Device Device Information Información del dispositivo Tools Herramientas Wipe Borrar Factory Reset Restaurar a Valores de Fabrica Reboot Reiniciar Name Nombre Unknown Desconocido HW Name Nombre HW BBID BBID PIN PIN BSN Número de Serie OS SO Radio Radio HW HW Restrictions Restricciones None Nada Setup Complete Configuración Completa True Verdadero False Falso Developer Mode Modo Desarrollador Battery Batería Connection Conexión USB USB Refurbished Date Fecha de Reconstrucción Never Nunca Clear Limpiar Set Fijar Free Disk Space Espacio Libre en el Disco %1 GB %1 GB Downloader #1. Start RTAS (Requires OS10.2) #1. Iniciar RTAS (Requiere SO 10.2) Start RTAS Iniciar RTAS Extract Percentages are not entirely accurate for QNX6 files. Los porcentajes no son del todo exactos para archivos QNX6. Cancel Cancelar Extract Signed Extrar Firmadox User Usuario OS SO Radio Radio PINList ListaPIN Create Autoloader Crear Instalador Create from Folder Crear a Partir de la Carpeta Create from Files Crear a Partir de Archivos Create Autoloader .exe from .signed images Crear Instalador .exe de imágenes firmadas Extraction Tools Herramientas de Extracción Split .signed from autoloader .exe, .bar or .zip Seperar .signed de instalador .exe, .bar o .zip Extracts all bar archives from a debrick/repair .signed Extrae todos los archivos de la barra de un debrick/reparación .signed Note: To extract apps from a .bar, please split it first (above) Nota: Para extraer aplicaciones desde un .bar, por favor divídalo primero (arriba) Dump Contents Volcar el Contenido Core Núcleo Boot Arranque Dump all file contents Volcar el contenido del archivo Extract Image Extraer Imagen Splitting Autoloader Separando Instalador Combining Autoloader Combinando Instalador Extracting Image Extrayendo Imagen Extracting Apps Extrayendo Aplicaciones Fetching required files Obteniendo de archivos necesarios Waiting Esperando Signed Images Imágenes Firmadas Signed Containers Contenedores Firmados Extracts filesystem image Extrae la imagen del sistema de ficheros Extract Apps Extraer Aplicaciones Installer Please be patient while the installation zip is extracted. Por favor sea paciente mientras que el zip de instalación se extrae. Firmware Update Actualización del Firmware Install Instalar To install <b>.bar</b> files such as applications or firmware, you can just <b>Drag and Drop</b> to this page. Otherwise, select the options below: Para instalar .<b>bar</b> archivos como aplicaciones y firmware, puede simplemente <b>arrastrar y soltar</b> a esta página. De lo contrario, seleccione las opciones a continuación: Install applications to device Instalar aplicaciones al dispositivo Blackberry Installable (*.bar) Instalable Blackberry (*.bar) Install Folder Instalar Carpeta Error: Your device can only process one task at a time. Please wait for backup/restore process to complete.<br> Error: Su dispositivo sólo puede procesar una tarea a la vez. Por favor espere a que la copia de seguridad/proceso de restauración sea completado. <br> Select Folder Seleccionar Carpeta Install Files Instalar Archivos Select Files Seleccionar Archivos View Install (%1) Ver Instalación (%1) Sending %1 Enviando (%1) Installing %1 Instalando %1 Sent %1 Enviado %1 Error: Your device can only process one task at a time. Please wait for previous install to complete.<br> Error: Su dispositivo sólo puede procesar una tarea a la vez. Por favor, espere a que la instalación anterior sea completado. <br> Only install newer apps Sólo instale aplicaciones más nuevas Refresh Actualizar Your Applications Sus Aplicaciones Log Reporte Use 'Refresh' to update list Use 'Actualizar' para actualizar la lista de Your device has not completed setup El dispositivo no ha completado la configuración Device disconnected Dispositivo desconectado Options Opciones Uninstall Marked Desinstalar Marcada Show Installed Apps Mostrar Aplicaciones Instaladas Scanner No Release Sin Autorización Search Download Descargar Cancel Download Cancelar Descarga Searching... Buscando... Search Buscar Delta Delta Version Lookup Buscar Version Success. No updates were available. Éxito. No hay actualizaciones disponibles. Download For Descargar Para Device Dispositivo Unknown Desconocido Connected Conectado As Searched Como Buscado Grab Links Obtener Enlaces Verifying Verificando Server did not respond as expected [%1]. Servidor no respondió como se esperaba [%1]. View Download (%1%) Ver Descargar (%1 %) Hide Ocultar Show Settings Mostrar configuración Whitelist Settings Ajustes Autoridad Finds updates approved by other carriers Encuentra actualizaciones aprobadas por otros proveedores Country País Carrier Operador Search For Buscar para Any Cualquiera Variant Variante Mode Modo Upgrade Actualizar Debrick Restaurar Selected: %1 Apps Seleccionado: %1 Apps All (%1) Todo (%1) Needed: %1 Apps Necesario: %1 Apps (older) (más viejo) (downloaded) (descargados) %1 MB %1 MB 1700.0 MB 1700.0 MB Options Opciones Check All Seleccionar Todos Check All Needed Compruebe Todo lo Necesario Uncheck All Desmarcar Todos Title Advanced Avanzado Device Dispositivo Extract Extraer Search Buscar Backup Copia de seguridad Install Instalar USBConnect These tools require a USB connection Estas herramientas requieren una conexión USB Password: Contraseña: Incorrect Incorrecta Hide Password Ocultar contraseña Show Password Mostrar contraseña Detected %1 Blackberry USB device(s) in %2 mode. Detectado %1 Blackberry USB dispositivo(s) en el modo de %2. Talking to %1 possible device(s). Hablando con %1 dispositivo(s) posible. There was an issue connecting. Hubo un problema de conexión. Try Again Inténtalo de nuevo Searching for USB device Búsqueda de dispositivo USB VersionLookup Version Lookup Buscar Version Stop on: Detener en: Next Found Siguiente encontrado Next Available Links Próximos enlaces disponibles Never Nunca SR: %1 VS: %1 OS: %1 SO: %1 Servers: Servidores: Production Producción Beta Beta Alpha Alfa Lookup Buscar Stop Scan Detener Exploración Autoscan Autoexploración Grab Public Links Obtener Enlaces Públicos No Links Available No Hay Enlaces Disponibles History Historia Hide Ocultar sachesi-2.0.4+ds/translations/Ukrainian.ts000066400000000000000000001237031362064257300205660ustar00rootroot00000000000000 Backup Options Параметри Refreshing backup sizes Визначення розміру резервної копії Unknown Size Розмір невідомий %1 MB %1 МБ Total: Всього: Refresh Backup Sizes Визначити розмір резервної копії Loading backup sizes can sometimes fail. In this situation, you can backup 'blind'. Визначення розміру резервної копії іноді закіфнчується помилкою, Ви можете зробити копію "наосліп". Create Backup Blind Створення резервної копії "наосліп" Choose Backup Filename Введіть ім'я файла резервної копії Select Restore File Виберіть файл відновлення Create Backup Створити резервну копію Restore Backup Відновити з резервної копії Blackberry Backup (*.bbb) Файли резервніх копій Blackberry (*.bbb) Your device needs a Blackberry ID to perform backups or restores! Пристрою необхідно перевірити Blackberry ID для виконання резервного копіювання і відновлення! Please note that backups can take a long time, depending on your device data. Будь ласка, зверніть увагу, що резервне копіювання може зайняти багато часу в залежності від об'єму даних Вашого пристрою. Creating Backup (%1%) Створення резервної копії (%1%) Restoring Backup (%1%) Відновлення (%1%) Boot Boot Communication Параметри завантажувача Info Інформація RimBoot RimBoot Nuke Nuke Debug Mode Режим відладки Reboot after Перезавантажити після завершення Connecting to bootrom Підключення до завантажувача Cancel Скасувати Detected devices: Знайдені пристрої: CircleProgress %1 of %2 %1 з %2 Device Device Information Інформація про пристрій Tools Інструменти Wipe Очистка Factory Reset Скидання налаштувань Reboot Перезавантаження Name Ім'я пристрою Unknown Невідомо HW Name Ревізія BBID BBID PIN PIN BSN Серійний номер OS ОС Radio Радіомодуль HW Номер моделі Restrictions Обмеження None Нема Setup Complete Налаштування закінчене True Так False Ні Developer Mode Режим розробки Battery Батарея Connection Підключення USB USB Refurbished Date Дата відновлення Never Николи Clear Чисто Set Встановити Free Disk Space Вільний дисковий простір %1 GB %1 ГБ Downloader #1. Start RTAS (Requires OS10.2) #1. Запуск RTAS (Необхідна OS10.2) Start RTAS Запуск RTAS Extract Percentages are not entirely accurate for QNX6 files. Проценти не зовсім точні для файлів QNX6. Cancel Скасувати Extract Signed Розпакувати Signed Containers Підписані контейнери User Користувач OS ОС Radio Радіомодуль PINList PIN-список Create Autoloader Створити автозавантажувач Signed Images Підписані образи Create from Folder Створити з папки Create from Files Створити з файлів Create Autoloader .exe from .signed images Створення Autoloader.exe з підписаних образів Extraction Tools Інструменти для роботи с автозавантажувачем Split .signed from autoloader .exe, .bar or .zip Розпаковка autoloader.exe, .bar чи .zip Extracts all bar archives from a debrick/repair .signed Витягування всіх bar-файлів з розібраного автозавантажувача Note: To extract apps from a .bar, please split it first (above) Примітка: Для того, щоб витягнути bar-файли, необхідно спочатку провести розпаковку автозавантажувача. Dump Contents Дамп вмісту Core Ядро Boot Завантажувач Dump all file contents Дамп всіх файлів Extract Image Витягнути образ Splitting Autoloader Розбирання автозавантажувача Combining Autoloader Збирання автозавантажувача Extracting Image Витягування образу Extracting Apps Витягування програм Fetching required files Отримання необхідних файлів Waiting Очікування Extracts filesystem image Витягування образу файлової системи Extract Apps Витягнути програми Installer Please be patient while the installation zip is extracted. Будь ласка, дочекайтеся закінчення розпаковки архіву. Firmware Update Оновлення прошивки Install Установка To install <b>.bar</b> files such as applications or firmware, you can just <b>Drag and Drop</b> to this page. Otherwise, select the options below: Для установки <b>bar</b>-файлів (програм чи прошивок) просто <b> перетягніть</b> їх на цю сторінку. Або ж виберіть необхідні параметри нижче: Install applications to device Установка програм на пристрій Blackberry Installable (*.bar) Установочний файл Blackberry (*.bar) Install Folder Встановити з папки Error: Your device can only process one task at a time. Please wait for backup/restore process to complete.<br> Ошибка: Пристрій може обробляти лише одну задачу за один раз. Будь ласка, дочекайтеся завершення операції резервного копіювання/відновлення.<br> Select Folder Вибір папки Install Files Встановити файли Select Files Вибрати файли View Install (%1) Показати установку (%1) Sending %1 відправка %1 Installing %1 Установка %1 Sent %1 Відправка %1 Error: Your device can only process one task at a time. Please wait for previous install to complete.<br> Помилка: Пристрій може обробляти лише одну задачу за один раз. Будь ласка, дочекайтеся завершення попередньої установки.<br>; Only install newer apps Встановлювати лише нові версії програм Refresh Оновити Your Applications Встановлено Log Лог Use 'Refresh' to update list Натисніть "Оновити" для оновлення списку Your device has not completed setup Налаштування пристрою не завершене Device disconnected Пристрій відключений Options Опції Uninstall Marked Видалити відмічене Show Installed Apps Показати встановлені програми Scanner No Release Немає оновлень Search Download Завантажити прошивку Cancel Download Скасувати завантаження Searching... Йде пошук... Search Знайти Delta дельта Version Lookup Пошук версії Success. No updates were available. Готово. Доступні оновлення відсутні. Download For Завантажити для Device Пристрій Unknown Невідомий пристрій Connected Підключено As Searched Як шукали Grab Links Отримати посилання Verifying Перевірка Server did not respond as expected [%1]. Віддалений сервер не відповідає [%1]. View Download (%1%) Дивитися завантаження (%1%) Hide Приховати Show Settings Показати налаштування Whitelist Settings Налаштування білого списку Finds updates approved by other carriers Пошук доступних оновлень у інших операторів зв'язку Country Країна Carrier Оператор Search For Шукати для Any Любая Variant Варіант Mode Режим Upgrade Оновлення Debrick Відновлення Selected: %1 Apps Вибрано: %1 програм All (%1) Всі (%1) Needed: %1 Apps Необхідно: %1 програм (older) (стара версія) (downloaded) (завантажено) %1 MB %1 МБ 1700.0 MB 1700.0 МБ Options Параметри Check All Відмітити все Check All Needed Відмітити всі необхідні Uncheck All Зняти всі помітки Title Advanced Розширений режим Device Пристрій Extract Витягнення Search Пошук Backup Резервне копіювання Install Встановлення USBConnect These tools require a USB connection Ці інструменти потребують підключення по USB Password: Пароль: Incorrect Невірно Hide Password Приховати пароль Show Password Показати пароль Detected %1 Blackberry USB device(s) in %2 mode. Виявлено %1 пристрій(-оїв) Blackberry USB в режиме %2. Talking to %1 possible device(s). Запит до %1 можливого пристрою. There was an issue connecting. Виникли проблеми з підключенням Try Again Спробуйте ще раз Searching for USB device Пошук пристроїв USB VersionLookup Version Lookup Пошук версії Stop on: Зупинити на: Next Found Доступній версії ПО Next Available Links Доступному посиланні для завантаження Never Ніколи SR: %1 ПО: %1 OS: %1 ОС: %1 Servers: Сервери: Production Реліз Beta Бета Alpha Альфа Lookup Пошук Stop Scan Зупинити Autoscan Автопошук Grab Public Links Отримати посилання No Links Available Відсутні доступні посилання History Історія Hide Приховати