From: Project Frankenstein Date: Wed, 15 Jul 2026 22:30:00 +0000 (+0000) Subject: PFES-REPO-008: Import FriiDump 0.5.3.5 Project Frankenstein baseline X-Git-Tag: v0.5.3.5-pf1^0 X-Git-Url: https://git.jtryba.com/browse/friidump.git/commitdiff_plain/d44030dfa440b1967ec7dcd3ccfe46fb69934486 PFES-REPO-008: Import FriiDump 0.5.3.5 Project Frankenstein baseline --- d44030dfa440b1967ec7dcd3ccfe46fb69934486 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0be769d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +* text=auto + +*.c text +*.h text + +# Preserve exact imported CRLF bytes for baseline files. +.vscode/msvc32.cmd -text +"example commands.txt" -text +libfriidump/xbox_ref/scsi_structs.h -text +libfriidump/xbox_ref/xbe_cert.h -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..568cc49 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# CMake and generic build output +/CMakeCache.txt +/CMakeFiles/ +/Makefile +/cmake_install.cmake +/install_manifest.txt +/BUILD/ +/build/ +/build-*/ +/out/ + +# Windows/MSVC output +/.vs/ +/build-msvc32/ +/friidump.exe +/friidump.pdb +/friidump.ilk +/friidump.exp +/friidump.lib +/friidump_msvc32_build_results.zip +*.obj + +# Runtime logs and generated media artifacts +/friidump.log +*.log +*.iso +*.xiso +*.raw +*.pfi.bin +*.dmi.bin +*.redump.json +*.media.json +*.xdvd.json + +.DS_Store +Thumbs.db diff --git a/.vscode/msvc32.cmd b/.vscode/msvc32.cmd new file mode 100644 index 0000000..e02e74e --- /dev/null +++ b/.vscode/msvc32.cmd @@ -0,0 +1,21 @@ +@echo off +setlocal EnableExtensions + +set "VCVARS2019=C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\\Build\vcvars32.bat" +set "VCVARS=" + +if exist "%VCVARS2019%" set "VCVARS=%VCVARS2019%" + +if defined VCVARS goto have_vcvars + +echo [ERROR] Could not find vcvars32.bat. +echo Checked: +echo %VCVARS2019% +exit /b 1 + +:have_vcvars +call "%VCVARS%" +if errorlevel 1 exit /b %ERRORLEVEL% + +%* +exit /b %ERRORLEVEL% diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..6d8acb6 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,57 @@ +{ + "version": "2.0.0", + "options": { + "cwd": "${workspaceFolder}", + "shell": { + "executable": "cmd.exe", + "args": [ + "/d", + "/c" + ] + } + }, + "tasks": [ + { + "label": "build-friidump-msvc32", + "type": "shell", + "command": "if not exist \"${workspaceFolder}\\build-msvc32\" mkdir \"${workspaceFolder}\\build-msvc32\" && \"${workspaceFolder}\\.vscode\\msvc32.cmd\" cl @\"${workspaceFolder}\\msvc32_friidump.rsp\"", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$msCompile" + ] + }, + { + "label": "help-friidump", + "type": "shell", + "command": "\"${workspaceFolder}\\friidump.exe\" --help", + "group": "test", + "problemMatcher": [] + }, + { + "label": "zip-build-results", + "type": "shell", + "command": "powershell -NoProfile -ExecutionPolicy Bypass -Command \"Compress-Archive -Force -Path friidump.exe,docs,README,AUTHORS,COPYING,config.h,msvc32_friidump.rsp,build_msvc32.cmd,.vscode -DestinationPath friidump_msvc32_build_results.zip\"", + "problemMatcher": [] + }, + { + "label": "clean-friidump-msvc32", + "type": "shell", + "command": "if exist \"${workspaceFolder}\\friidump.exe\" del /q \"${workspaceFolder}\\friidump.exe\" & if exist \"${workspaceFolder}\\build-msvc32\" rmdir /s /q \"${workspaceFolder}\\build-msvc32\" & if exist \"${workspaceFolder}\\friidump_msvc32_build_results.zip\" del /q \"${workspaceFolder}\\friidump_msvc32_build_results.zip\"", + "problemMatcher": [] + }, + { + "label": "build-all", + "dependsOrder": "sequence", + "dependsOn": [ + "build-friidump-msvc32", + "help-friidump", + "zip-build-results" + ], + "group": "build", + "problemMatcher": [] + } + ] +} diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..93308a9 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,54 @@ +FriiDump came to existance thanks to the work by a lot of people, most of which +are probably not aware of this fact ;). So here is proper credit: + +The DVD seed bruteforcing algorithm and code were taken from unscrambler 0.4 +by Victor Muñoz (xt5@ingenieria-inversa.cl, +http://www.ingenieria-inversa.cl/?lp_lang_pref=en). + +The theoritical basis of the dumping methods were suggested in several comments +to Victor's post. The most important comments came from Victor himself (xt5), +FuzzyLogic and svpe. + +The code to actually perform the dumping was derived from the work of Kevin +East, AKA SeventhSon (kev@kev.nu, http://www.kev.nu/360/), which, in turn, +derives from work by a lot of other people. See his page for full details. + +Many hints were taken from RawDump, whose author is unknown. + +A program that helped me a lot to understand the drive cache behaviour is +PLScsi by Pat LaVarre (http://members.aol.com/plscsi/). + +Nintendo disc structure information was taken from: +- http://www.gc-linux.org/docs/yagcd.html +- http://www.wiili.org/index.php/GameCube_Optical_Disc + +Code to tell whether a Wii disc contains an update or was inspired by a program +by wiidevel@stacktic.org. Sorry but I cannot find the URL anymore :(. + +Other minor pieces of code were taken from tcpdump (www.tcpdump.org), Python +(www.python.org) and the glibc printf manpage. + +GDR8163B and Windows testing was performed by tasso85. + +Thanks also go out to the ConsoleTribe staff for giving me the possibility to +use their forum for the program support. + +Glue, endless hours spent understanding drive cache behaviour and rest of the +code are by me, Arep . + +Finally, obvious thanks go out to Nintendo for making all of their great +consoles, who constantly help me to waste the rest of my spare time I do not +spend coding ;). + +Xbox/XGD integration, redump-style metadata output, XISO output, and current +maintenance changes in this branch are by jtryba. + +The Original Xbox GDR-8050L challenge-table handshake is based on scsi1.pas / +"XBOX 1 DVD-Drive unlocker tool v0.1" by The Specialist, with the credits +carried there to xboxhacker.net and especially Anita999, Bluecop, loser, +MacDennis, Kev, and the other contributors who documented the authentication +method. + +DiscImageCreator by sarami provided useful reference material for the +Kreon/wxripper-style FF 08 01 feature-list and lock-state command family used +by GDR-3120L-style and compatible Xbox-capable drives. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b985f1b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,163 @@ +cmake_minimum_required(VERSION 2.8) + +# The name of our project is "HELLO". CMakeLists files in this project can +# refer to the root source directory of the project as ${HELLO_SOURCE_DIR} and +# to the root binary directory of the project as ${HELLO_BINARY_DIR}. +project (FriiDump) + +if (MSVC) + # msvc2005 deprecated warnings + add_definitions (-D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE) +else (MSVC) + if (NOT WIN32) + add_definitions (-fPIC) + endif (NOT WIN32) +endif (MSVC) + + +include (TestBigEndian) + +test_big_endian (CMAKE_WORDS_BIGENDIAN) + + +include (CheckIncludeFiles) + +check_include_files (stdbool.h HAVE_STDBOOL_H) + + +include (CheckFunctionExists) + +check_function_exists (fseeko HAVE_FSEEKO) +check_function_exists (ftello HAVE_FTELLO) +check_function_exists (fseek64 HAVE_FSEEK64) +check_function_exists (ftell64 HAVE_FTELL64) + + +include(CheckTypeSize) + +set (CMAKE_REQUIRED_DEFINITIONS -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64) + +set (CMAKE_EXTRA_INCLUDE_FILES sys/types.h) +check_type_size ("off_t" OFF_T) +set (CMAKE_EXTRA_INCLUDE_FILES) + +set (CMAKE_EXTRA_INCLUDE_FILES stdio.h) +check_type_size ("fpos_t" FPOS_T) +set (CMAKE_EXTRA_INCLUDE_FILES) + +set (CMAKE_REQUIRED_DEFINITIONS) + + +option ( + DEBUG + "Enable debugging messages" + OFF +) + + +option ( + BUILD_STATIC_BINARY + "Build a static binary (has precedence over ALL_LIBS_SHARED)" + OFF +) + +option ( + BUILD_ALL_LIBS_SHARED + "Build all libraries as shared" + OFF +) + +if (BUILD_STATIC_BINARY) + set (libmultihash_type STATIC) + set (libfriidump_type STATIC) +elseif (BUILD_ALL_LIBS_SHARED) + set (libmultihash_type SHARED) + set (libfriidump_type SHARED) +else (BUILD_STATIC_BINARY) + # This is how we build libraries by default + set (libmultihash_type STATIC) + set (libfriidump_type SHARED) +endif (BUILD_STATIC_BINARY) + + +# set (CMAKE_BUILD_TYPE superoptimized) +set (CMAKE_C_FLAGS_SUPEROPTIMIZED "-march=athlon-xp -m3dnow -O3 -funroll-all-loops") + +set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall") + +# set (CMAKE_BUILD_TYPE release) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) +add_definitions(-DHAVE_CONFIG_H) +include_directories ( + ${FriiDump_BINARY_DIR} +) + +# Recurse into the "Hello" and "Demo" subdirectories. This does not actually +# cause another cmake executable to run. The same process will walk through +# the project's entire directory structure. +add_subdirectory (libmultihash) +add_subdirectory (libfriidump) +add_subdirectory (src) + + +if (WIN32) + install (FILES AUTHORS DESTINATION / RENAME Authors.txt) + #install (CODE "exec_program (${CMAKE_CURRENT_SOURCE_DIR}/utils/unix2dos.exe ${CMAKE_OUTPUT_BINARY_DIR} ARGS Authors.txt)") + install (FILES ChangeLog DESTINATION / RENAME ChangeLog.txt) + install (FILES COPYING DESTINATION / RENAME Copying.txt) + install (FILES README DESTINATION / RENAME ReadMe.txt) + install (FILES TODO DESTINATION / RENAME ToDo.txt) +endif (WIN32) + + +# CPack stuff +include (InstallRequiredSystemLibraries) + +set (CPACK_PACKAGE_NAME "friidump") +set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Dump Nintendo GameCube/Wii discs") +set (CPACK_PACKAGE_VENDOR "Arep") +set (CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README") +set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING") +set (CPACK_PACKAGE_VERSION_MAJOR "0") +set (CPACK_PACKAGE_VERSION_MINOR "3") +set (CPACK_PACKAGE_VERSION_PATCH "0") +set (CPACK_PACKAGE_INSTALL_DIRECTORY "FriiDump ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") +set (CPACK_PACKAGE_EXECUTABLES "friidump" "FriiDump") + +set (CPACK_SOURCE_GENERATOR "TBZ2;ZIP") +set (CPACK_SOURCE_IGNORE_FILES + "/CVS/" + "/\\\\.svn/" + "~$" + "tags" + "\\\\.kdevses$" + "\\\\.kdevelop\\\\.pcs$" + "/BUILD.*/" + "/RELEASES/" + "/utils/" + "/doc.*/" +) +set (CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") + +if(WIN32 AND NOT UNIX) + set (CPACK_GENERATOR "NSIS;ZIP") + # There is a bug in NSI that does not handle full unix paths properly. Make + # sure there is at least one set of four (4) backlasshes. +# set (CPACK_PACKAGE_ICON "${CMake_SOURCE_DIR}/Utilities/Release\\\\InstallIcon.bmp") +# set (CPACK_NSIS_INSTALLED_ICON_NAME "bin\\\\MyExecutable.exe") + set (CPACK_NSIS_DISPLAY_NAME "${CPACK_PACKAGE_INSTALL_DIRECTORY} installer") + set (CPACK_NSIS_HELP_LINK "http:\\\\\\\\wii.console-tribe.com") + set (CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.my-personal-home-page.com") + set (CPACK_NSIS_CONTACT "arep@no.net") + set (CPACK_NSIS_MODIFY_PATH ON) + + set (CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}-Win32") +else(WIN32 AND NOT UNIX) + set (CPACK_GENERATOR "TBZ2") + set (CPACK_STRIP_FILES "bin/friidump;lib/libfriidump.so.1.0.0") +# set (CPACK_SOURCE_STRIP_FILES "") + set (CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}-LinuxBin") +endif(WIN32 AND NOT UNIX) + +include (CPack) diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..5b6e7c6 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 2 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, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..9f2f12e --- /dev/null +++ b/Doxyfile @@ -0,0 +1,284 @@ +# Doxyfile 1.5.1-KDevelop + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = FriiDump +PROJECT_NUMBER = 0.3pre +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +USE_WINDOWS_ENCODING = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = YES +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +BUILTIN_STL_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +HIDE_UNDOC_MEMBERS = YES +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = NO +SHOW_DIRECTORIES = NO +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = libfriidump \ + libmultihash +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.C \ + *.CC \ + *.C++ \ + *.II \ + *.I++ \ + *.H \ + *.HH \ + *.H++ \ + *.CS \ + *.PHP \ + *.PHP3 \ + *.M \ + *.MM \ + *.PY \ + *.C \ + *.H \ + *.tlh \ + *.diff \ + *.patch \ + *.moc \ + *.xpm \ + *.dox +RECURSIVE = YES +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = YES +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = doxygen-html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = YES +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = YES +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +MAX_DOT_GRAPH_DEPTH = 1000 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = YES diff --git a/PROJECT_FRANKENSTEIN_IMPORT_PROVENANCE.json b/PROJECT_FRANKENSTEIN_IMPORT_PROVENANCE.json new file mode 100644 index 0000000..69247d2 --- /dev/null +++ b/PROJECT_FRANKENSTEIN_IMPORT_PROVENANCE.json @@ -0,0 +1,37 @@ +{ + "format": "project-frankenstein-friidump-import-provenance", + "version": 1, + "source_workspace": "D:\\vscode-workspace\\friidump", + "core_intake": { + "filename": "friidump-core-source-intake.zip", + "sha256": "fbea5c39dc613db171243a14830609479877a8be480f6a8f2b6abbbb980fa0ad", + "files": 90 + }, + "build_helpers_intake": { + "filename": "friidump-build-helpers-intake.zip", + "sha256": "1f4a0212e629a749ca9d4f5e444a3114add25c1e5b41beb4f9baa22481fa6be5", + "files": 2 + }, + "imported_source_files": 92, + "repository_added_files": [ + "README.md", + "docs/BUILDING.md", + "docs/REPOSITORY_SCOPE.md", + "PROJECT_FRANKENSTEIN_IMPORT_PROVENANCE.json" + ], + "firmware_included": false, + "disc_images_included": false, + "generated_logs_included": false, + "selected_input_files": 92, + "byte_exact_input_files": 90, + "intentionally_modified_input_metadata_files": [ + ".gitattributes", + ".gitignore" + ], + "line_ending_preservation_exceptions": [ + ".vscode/msvc32.cmd", + "example commands.txt", + "libfriidump/xbox_ref/scsi_structs.h", + "libfriidump/xbox_ref/xbe_cert.h" + ] +} diff --git a/README b/README new file mode 100644 index 0000000..6d7c62b --- /dev/null +++ b/README @@ -0,0 +1,457 @@ +FriiDump - A program to dump Nintendo Wii, GameCube, DVD, and Xbox discs +=============================================================================== + +This version adds native Xbox/XGD disc support for selected Xbox optical +drives, including the GDR-8050L challenge-table handshake path, the GDR-3120L +FF 08 01 vendor lock-state path, redump-style metadata output, optional +XISO output, and a convenient forced-DVD mode (`-D` / `--dvd`). See +docs/XBOX.md for Xbox-specific details. + +FriiDump is a program that lets you dump Nintendo Wii and GameCube disc from +your computer, without using original Nintendo hardware. It basically performs +the same functions as the famous "RawDump" program, but with a big difference, +which should be clear straight from its name: FriiDump is free software, where +"free" is to be intended both as in "free speech" and in "free beer". As such, +FriiDump is distributed with its sources. + +This leads to a number of good consequences: +- Having the sources available, it can be easily ported to different operating + systems and hardware platforms. At the moment it is developed under a + GNU/Linux system, but it also runs natively on Windows. A MacOS X version can + be easily created, although I don't have a Mac, so I can't do it myself. +- Also, having the sources and these being well-organized (I know I'm a modest + guy) allows support for new DVD-ROM drives to be added relatively easily. At + the moment the same drives as RawDump are supported, but this might improve + in the future, if anyone takes the effort... See README.technical for + details. +- The sources might also be used as a reference for several things regarding + Nintendo Wii/GameCube discs and the hacks used to read them on an ordinary + drive. + +Furthermore, FriiDump also features some functional improvements over RawDump: +- FriiDump can use 4 different methods to read the disc, with different + performance. +- FriiDump dumps a lot of useful information about the discs it dumps, + such as whether the disc contains an update or not, which can help avoid + bricking your Wii ;). +- FriiDump calculates CRC32, MD5, SHA-1, and SHA-256 hashes of dumped + discs, so you can immediately know if your dump is good or not, by comparing + the hashes with the well-known ones available on several Internet sites. +- FriiDump comes in the form of a library and a command-line front-end, which + allows its functions to be easily reused in other programs. + +Unfortunately, there is also a main downfall: +- Even the fastest dump method used by FriiDump is not as fast as RawDump (but + not that much slower, either, see the table below). + +Anyway, I'm sure that people who cannot use RawDump (i.e.: GNU/Linux, *BSD and +MacOS X users) will be happy anyway. Besides, you get the sources, so you can +improve them yourself. + +Note that FriiDump is primarily useful for dumping original Nintendo discs, +standard DVD-ROM media, and selected Xbox/XGD media through supported or forced +drive profiles. To dump ordinary backup copies you can also use a generic DVD +dumping program (i.e.: dd under UNIX ;)). + +FriiDump came to existance thanks to the work by a lot of people, most of which +are probably not aware of this fact ;). Please see the AUTHORS file for the +credits. + + +=============================================================================== +Supported drives +=============================================================================== +At the moment the same drives as RawDump are supported. This is due to various +reasons, explained in the README.technical file, which also contains +information about what is needed to add support for more drives. + +Currently supported drives are: + +Nintendo GC/Wii supported-drive list: +- Hitachi-LG GDR-8082N, GDR-8083N, GDR-8084N +- Hitachi-LG GDR-8161B, GDR-8162B, GDR-8163B, GDR-8164B +- Hitachi-LG GCC-4160N, GCC-4240N, GCC-4243N, GCC-4244N, GCC-4247N +- Hitachi-LG GDR-8085N, GDR-8087N, and GCC-4246N are listed as probable but + untested GC/Wii-capable candidates. +- Hitachi-LG GCC-4241N and GCC-4242N are listed as capable but error-prone. + +Additional drive profiles recognized in this branch: +- HL-DT-ST GSA-4163B as a Hitachi-LG/GC-Wii experimental profile. +- HL-DT-ST GDR-3120L 0046 as an experimental read-only GC/Wii candidate + when the HLDS 0xE7 profile is selected; it remains historically an Xbox + reference drive until live GC/Wii dumps validate it. +- TSSTcorp/Samsung TS-H352C, TS-H353A, SH-D162C, SH-D162D, SH-D163A, and + SH-D163B as Samsung/Kreon-style Xbox-capable candidates when firmware + supports the FF 08 01 command family. + +Stage5B-promoted HLDS 0xE7 profile metadata recognized in this branch: +- GCC-4244N B103: CDB 0x894, gate 0x900386FB, + tokens HL;IT;RPC;RPC_JCS3;RPC_SUFFIX; full Sonic Mega Collection + GameCube dump validated on a live drive. +- GDR-8163B 0B30: CDB 0x5E0, gate 0x90025030, + tokens HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX; full Sonic Mega Collection + GameCube dump validated on a live drive. +- GDR-8163B 0D20: CDB 0x5E0, gate 0x90024AE7, + tokens HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX; full Sonic Mega Collection + GameCube dump validated on a live drive. +- GDR-8163B 0E15: CDB 0x5D8, gate 0x900247D1, + tokens HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX; full Sonic Mega Collection + GameCube dump validated on a live drive. +- GDR-8163B 0L20: CDB 0x5E0, gate 0x90024C8F, + tokens HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX; full Sonic Mega Collection + GameCube dump validated on a live drive. +- GDR-8163B 0L23: CDB 0x5E0, gate 0x90024D5A, + tokens HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX; full Sonic Mega Collection + GameCube dump validated on a live drive. +- GDR-8163B 0L30: CDB 0x5E0, gate 0x90025021, + tokens HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX; full Sonic Mega Collection + GameCube dump validated on a live drive. +- GDR-8163B 0M26: CDB 0x5E0, gate 0x90024FF7, + tokens HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX. + +The static CDB/gate values above are firmware-analysis evidence used for +profile reporting and confidence. They are not host-side commands and are not +used as runtime addresses by FriiDump. + +Xbox/XGD native profiles: +- Xbox GDR-8050L (HL-DT-ST/DVD-ROM GDR8050L, challenge-table handshake). +- Xbox GDR-3120L (HL-DT-ST/DVD-ROM GDR3120L or GDR-3120L, FF 08 01 + vendor feature-list and lock-state unlock path). + +HD-DVD and BD dumping are documented as DiscImageCreator reference areas, but +FriiDump remains a DVD/GC/Wii/Xbox-focused tool in this branch; no full BD or +HD-DVD feature set is claimed here. + +Other drives might work, most likely those based on the Hitachi MN103 +microcontroller. If you find any of them, please report so that they can be +added to the compatibility list. + + +=============================================================================== +Installation +=============================================================================== +If you are a Windows user, probably you will have downloaded the binaries, +either zipped or together with an installer, so the installation should be +straightforward. + +If you downloaded the sources, you will need to compile them. FriiDump uses +CMake, for easy portability, so you will need to get it from cmake.org. On +Windows you will also need a compiler like Visual Studio (the only tested one, +so far) or CygWin/MinGW. On UNIX just do the following, from the directory +where you unpacked the sources into: + +$ mkdir BUILD +$ cd BUILD +$ cmake .. +$ make +$ make install + +Linux-specific note: You need root privileges to issue certain commands to the +DVD-ROM drive. Hence you have the following possibilities: +- Run FriiDump as root: discouraged. +- Run it through sudo: better but nevertheless discouraged. +- Set the setuid bit on the executable: this is the recommended way to run + FriiDump under Linux. This way, the code run with superuser privileges will + be reduced to a minimum, guaranteeing a certain level of security (note that + security-related bugs might exist anyway!!!). Also note that, even when the + setuid bit is set, the attempt to open the drive for reading will be done + after privileges have been dropped, so you will need explicit read access to + the DVD-ROM drive. Usually having the system administrator add you to the + "cdrom" group is enough. To set the setuid bit on the executable, run as + root: + + $ chown root:root /usr/local/bin/friidump + $ chmod u+s /usr/local/bin/friidump + + +=============================================================================== +Usage +=============================================================================== +FriiDump is a command-line program, so you will need to run it from a terminal +or a command-prompt under Windows. The basic usage is as follows: + +friidump -d -a + +For a plain DVD-ROM dump from any readable drive, use `-D` / `--dvd` or the +numeric equivalent `-T 3`: + +friidump -d -D -i dvd.iso + +For Xbox discs, use automatic detection on known Xbox drives or force Xbox mode +with `-T 4`. Xbox mode is fail-fast on drives that are not wired into the Xbox +unlock backend; it does not fall back to GC/Wii methods. To make a redump-style +Original Xbox/XGD1 ISO, use: + +friidump -d -T 4 -i + +On GDR-8050L, bare `-i` derives `Title[MediaID].iso` from the XBE title and +DMI media ID, matching the reference dumper. Use `-i xbox.iso` only when you +want to override that filename. + +For Xbox/XGD media, `-i` is not just a game-partition copy. It reconstructs the +full redump-style 2048-byte-sector layout: visible DVD-video L0, pregame padding, +32-sector game lead-in, unlocked game/XDVDFS data, postgame padding, and visible +DVD-video L1. FriiDump also attempts to save `xbox.pfi.bin`, `xbox.dmi.bin`, and +`xbox.redump.json` next to the ISO. The redump-style layout constants are +checked against DiscImageCreator's Original Xbox/XGD1 model: total size +3,820,880 sectors, layer break LBA 1,913,776, DVD start PSN 0x30000, and +Xbox/game start PSN 0x60600. + +On a GDR-8050L, FriiDump follows the original dumper's state order and timing: +primary handshake, media-cycle, re-handshake, RefreshVolume plus full settle +delays, metadata/XBE probe, media-cycle back to the visible DVD-video view, +video capture, final handshake, game data write, metadata write, and STOP UNIT +cleanup. The 32-sector game lead-in in this path +is zero-filled like the original option-1 dumper. + +To dump only the Xbox game partition as an XISO-style image, use: + +friidump -d -T 4 -X + +On GDR-8050L, bare `-X` derives `Title[MediaID].xiso`; use `-X xbox.xiso` only +when you want to override that filename. + +where will usually be something like "/dev/hda" on Unix-like systems, +and something like "e:" for Windows users. With -a, the disc will be dumped +to an ISO image file with an automatically-chosen name. Drop -a and use the -i +option if you prefer to specify the filename yourself. If you want to resume an +existing dump, use -s. Xbox redump-style `-i` does not support resume because +the drive view changes from visible DVD-video to unlocked/game during one run. If you +want to dump a Nintendo disc to a raw format image file, use -r. Note that you +can create a raw and an ISO image at the same time for Nintendo disc types; Xbox +redump-style ISO output should be run with `-i` only, and Xbox XISO output (-X) +is a separate output mode that cannot be combined with -r, -i, or -a. + +Other options you might want to use are -1 through -4, to set the dump method, +although the default is method 4, which is the fastest one, so most likely you +will not need them. + +The -A/--allmethods option tries every supported command/method combination. It +reopens the drive for each command so vendor-specific memory-dump handlers are +rebound before each method is tested. This is useful for experimental drive +profiles, but it can be slow and noisy. + +For HLDS 0xE7 testing, `--hlds-profile-report ` writes the selected +profile, support tier, parser-token family, Stage5B record tag, static CDB/gate +evidence, cache base, memory-window count, selected method, and safety note to +a JSON file next to the run logs. This is intended for fleet validation and +should be included with test-result ZIPs. + +Finally, use -h for a listing of all available options. + + +=============================================================================== +Performance +=============================================================================== +As stated above, FriiDump is not as fast as RawDump. On my PC (Athlon64 3200+), +performance is as follows: + +------------------------------------------------------------------------------- +| Method | Dump speed | GameCube disc dump time | Wii disc dump time | +------------------------------------------------------------------------------- +| 1 | Too slow ;) | Eternity | More than eternity | +| 2 | ~570 MB/h | 2.5 hours | 8 hours | +| 3 | ~740 MB/h | 2 hours | 6 hours | +| 4 | ~1250 MB/h | 1.2 hours | 3.5 hours | +------------------------------------------------------------------------------- + + +=============================================================================== +Support +=============================================================================== +I'm releasing this program under the nickname of "Arep". This is because I am +not sure about the legal status of the program, and I do not want to encounter +any consequences. Actually, I'm pretty sure FriiDump goes against the DMCA, +being a program that circumvents copy-protection, but it might be objected that +the format used by Nintendo discs is not a copy-protection method, but just +their own, undocumented, disc format. Although, I think it can be freely used +in Europe and other coutries without laws similar to the DMCA. + +For the same reason, I am not putting an e-mail address here (that @no.net you +find in the program is obviously a pun), but support will be provided through +the forums of the Italian ConsoleTribe forum, at http://wii.console-tribe.com. +If you need help, just open a thread in any section there, even in English: I +will *not* reply, but you might stand assured I will read everything you write. +FriiDump users are encouraged to help each other there ;). + +Patches are welcome, too: just attach them to your post, and maybe put +something like "[PATCH]" in the topic subject, so that I can easily spot them. + +New releases will be announced on that forum, and also on QJ.net, if I find a +good way to notify them. + +If you want to donate to the project, do not do it, and donate to one of the +free Wii modchip projects out there, such as OpenWii, WiiFree or YAOSM. + + +=============================================================================== +Disclaimer +=============================================================================== +FriiDump is distributed under the GNU General Public License version 2. See the +COPYING file for details. + +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. + +Also, please note that this program is not meant to be used to spread game +piracy, but rather to be instrument to make backups of your own precious +legally-bought games. + +Windows MSVC32 build shortcut +----------------------------- + +A VS Code task file is included for the direct cl.exe build used by this +working tree. It uses .vscode\msvc32.cmd to initialize the Visual Studio +2019 32-bit compiler environment and then compiles FriiDump directly, without +requiring CMake or NMake for the normal Windows test build. + +From VS Code: + + Terminal -> Run Build Task... -> build-friidump-msvc32 + +or press Ctrl+Shift+B. + +From PowerShell or cmd: + + .\build_msvc32.cmd + +The build writes friidump.exe to the source root, runs friidump.exe --help, and +creates friidump_msvc32_build_results.zip. + +GCC-4243N/GCC-4244N Method 8 split-recovery note +-------------------------------------------------- +For HLDS GCC-4243N and GCC-4244N GameCube/Wii dumping, the default Hitachi command-2 / Method-8 path now has three visible recovery layers: the existing Method-8 5-block/window read reconstruction retry loop, the dump-level retry envelope, and a Method-8 split-recovery path for a failed 16-sector block. The split-recovery path reconstructs the failed block by trying 8-sector, 4-sector, 2-sector, then 1-sector streaming chunks, validates the rebuilt 16-sector raw block with the normal unscrambler/EDC check, and only caches/writes it if validation succeeds. All retry and split-recovery messages are mirrored through the shared FriiDump log file, so a failure such as `Dump failed at sectors: N..N+15` should now include whether 8/4/2/1 chunk recovery was attempted and where it failed. Existing Xbox logging remains on the same shared log path; GDR-3120L Xbox support remains on the explicit vendor lock/unlock path. + +HLDS 0xE7 DIC profile-layer update +------------------------------------ +This branch now classifies HLDS/MN103 0xE7 GC/Wii dumping drives into DIC-style profiles before assigning the FriiDump Hitachi cache reader. The selected profile is printed in the run log after Command/Method: + +- Type1: GCC-4160N/GCC-4240N, cache base 0x00a13000, one 16-sector memory window. +- Type3: GCC-4243N/GCC-4244N/GCC-4246N/GCC-4247N and GDR8083N/GDR8084N, cache base 0x80000000, five 16-sector memory windows. +- Type4: GDR8082N/GDR8161B/GDR8162B/GDR8163B/GDR8164B and related DVD-ROM profiles, cache base 0x80000000, five 16-sector memory windows. +- GDR-8050L modified 0xE7 speed-probe/fallback: GDR8050L/GDR-8050L with cross-flashed or modified firmware that adds 0xE7 memdump, cache base 0x80000000. Single-window is proven; this build probes guarded 3-window, 2-window, and 5-window no-prefetch schedules before falling back to the proven one-window Method 8 profile. +- Type2_1/Type2_2: GCC-4241N/GCC-4242N are identified as experimental DIC Type2 shapes, but this branch does not yet claim DIC parity for their moving-cache behavior. + +The Type1 base address comes from the DIC source behavior for GCC-4160N/GCC-4240N. Method 8 now honors the selected profile's memory-window count, so Type1 reads/cache-validates one 16-sector block per request instead of assuming the Type3/Type4 five-window cache layout. GCC-4243N and GCC-4244N remain on Method 8 by default. GDR-8050L is split into a modified-firmware GC/Wii 0xE7 path: stock GDR-8050L firmware is still expected to use the Xbox path only, while a cross-flashed/modified GDR-8050L with 0xE7 memdump added can use Method 8. The proven GDR-8050L fallback is one window/no-prefetch; this build probes guarded multi-window no-prefetch speed profiles and reverts to the proven single-window path if an accelerated read fails. GDR-3120L Xbox ripping support remains on the separate explicit vendor lock/unlock path and is not described as GC/Wii support. + + + +2026-06-29 / HLDS 0xE7 Windows volume guard +--------------------------------------------- + +* The existing Xbox FSCTL_LOCK_VOLUME-style guard is now exposed as a shared + FriiDump volume lock helper. +* HLDS 0xE7 GC/Wii paths apply the guard before disc seed retrieval, so Windows + Explorer/AutoPlay is less likely to interrupt GCC-4160N/GCC-4240N Type1 seed + reads with an "insert a disc" prompt. +* The guard is warning-only: if Windows already owns a transient handle, FriiDump + logs the failure and continues so the hardware read result remains authoritative. + + +2026-06-29 / HLDS 0xE7 AutoPlay warning +------------------------------------------------ + +* Added an explicit Windows AutoPlay warning before the HLDS 0xE7 GC/Wii + volume-lock and seed-retrieval phase. +* GCC-4160N Type1 testing showed the volume lock works, but Windows AutoPlay + can still open an "insert a disc" dialog and interfere until AutoPlay is + disabled and File Explorer/dialogs are closed. +* The warning is printed for all non-Xbox HLDS 0xE7 GC/Wii profiles before + FriiDump attempts the shared volume guard. + + +HLDS 0xE7 validation summary and observed speeds +-------------------------------------------------- + +For HLDS 0xE7 GC/Wii runs, FriiDump now prints a compact validation summary at the end of the run. The summary records the selected profile, cache base, memory-window count, seed-read status, seed-retrieval elapsed time, dump status, STOP UNIT status, duration, and an observed average speed computed from the ISO payload size and elapsed dump time. The hash comparison remains manual: compare the printed hashes against the known-good target for the test disc. + +Seed retrieval timing note: normal GC/Wii seed cracking is usually a seconds-to-tens-of-seconds step, with the original technical note describing the brute-force portion as roughly 30 seconds on the old reference path. Experimental HLDS 0xE7 profiles can take longer because this fork may also probe cache profiles and guard against Windows polling. This build does not add a hard seed timeout because aborting a blocked optical-drive command from inside FriiDump is less safe than letting Windows/drive firmware return or letting the user cancel with Ctrl+C. As an operational rule, a seed phase above about 5 minutes is suspicious, and above about 10-15 minutes should usually be treated as a failed profile/drive state: cancel, confirm AutoPlay/File Explorer are closed, power-cycle or tray-cycle the drive if needed, and rerun with the log preserved. + +Observed Sonic Mega Collection (US) fleet results so far: + +| Model | Firmware | HLDS profile | Cache base | Windows | Result | FriiDump final displayed rate | Duration | Notes | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| GCC-4244N | B103 | promoted GCC_424x profile | 0x80000000 | 5 | PASS | 2152.04 MiB/h | 2329.16 s | Live INQUIRY `GCC4244/B103`; exact profile; media preflight, seed read, full Sonic dump, STOP UNIT, and Redump hashes confirmed. | +| GCC-4243N | A102 | Type3 | 0x80000000 | 5 | PASS | 2534.09 MB/h | 1522.95 s | Confirmed matching Sonic hashes after resume/retry work. | +| GCC-4160N | 0010 | Type1 | 0x00a13000 | 1 | PASS | 1613.08 MB/h | 3127.98 s | AutoPlay had to be disabled; volume guard OK; hashes matched known-good Sonic dump. | +| GDR-8050L modified | 0012 | GDR-8050L modified 0xE7 single-window proven fallback | 0x80000000 | 1 | PASS | 776.49 MiB/h / 782.59 MB/h | 6455.22 s | Cross-flashed/modified firmware with 0xE7 memdump; hashes matched Sonic. Current build adds guarded speed probes. | +| GDR-8163B | 0L30 | Type4 / promoted GDR_816x profile | 0x80000000 | 5 | PASS | 678.55 MiB/h | 7387.03 s | Germany-batch drive with case label 0L23 but inquiry 0L30; full Sonic dump validated; Stage5B parser signature recovered at CDB 0x5E0 / gate 0x90025021. | + +Known-good Sonic Mega Collection (US) target hashes: + +``` +CRC32 01b52739 +MD5 85a525df1481d0ad67d8761f832dca12 +SHA-1 06eb6d15b4d7f90ec0fed9ce9a77db41358d74ed +SHA-256 30098da93f5de9ece8da44f8afdfb85cf9bcdc24d77131221e64e3038a529010 +``` + +Use this table as an observed fleet log, not a promise that every firmware revision of a model behaves identically. + + +HLDS GDR-8050L modified 0xE7 note (2026-06-30): + A cross-flashed GDR-8163B running modified GDR-8050L firmware with 0xE7 memdump added reached seed retrieval and dumped the first 320 sectors, then failed at sectors 320..335 when treated as a normal five-window Type4 cache. A later single-window/no-prefetch run completed Sonic Mega Collection and matched the known hashes at roughly 776.49 MiB/h / 782.59 MB/h. This build keeps that single-window path as the proven fallback, but adds a speed probe before seed cracking: 3-window no-prefetch, 2-window no-prefetch, 5-window guarded no-prefetch, then fallback to one window. If an accelerated GDR-8050L profile later fails during a dump, FriiDump logs the failure and reverts to the proven one-window profile for the rest of the run. Stock GDR-8050L remains an Xbox path, not a GC/Wii memdump drive. + + +GDR-8081N experimental 0xE7 probe layer +----------------------------------------- +GDR-8081N is now recognized as an experimental HLDS 0xE7 GC/Wii candidate. +Unlike GDR8082N/GDR816x, it was not in the confirmed DIC dump list, so FriiDump +does not hard-code it as a normal Type4 drive. It starts as `GDR-8081N +experimental 0xE7 probe` and, during seed retrieval, tries small sector-0 +validation reads across these cache profiles: + +- Probe A Type4-derived: base 0x80000000, 5 windows. +- Probe B single-window: base 0x80000000, 1 window. +- Probe C Type1-base: base 0x00a13000, 1 window. +- Probe D moving-cache candidate: base 0x7fff7f00, 1 window. + +The first candidate that can read/validate sector 0 is selected for the rest of +the run and logged. If all candidates fail, seed retrieval stops and the log +records each failed probe. Unsupported/non-HLDS drives now keep zeroed HLDS +profile fields so stale cache-base/window values are not printed. + +GDR-8050L speed-probe visibility note: + Builds after the seed-timer package report the modified GDR-8050L as `speed-probe pending` at initial drive-info time, then print each guarded candidate during seed retrieval. If all accelerated candidates fail, the run visibly selects the proven single-window fallback. + +Probe v2 note: + The GDR-8050L speed-probe-pending profile now defaults to Method 8, so the probe branch is actually exercised by default. Seed cracking now fails the run if the experimental 0xE7 profile probe cannot validate a cache candidate, instead of continuing after an unsupported/failed probe. The first visible probe message starts on a fresh line after the seed-retrieval prompt for easier log review. + +HLDS 0xE7 logging note: GDR-8050L modified-0xE7 read schedule selection is logged once per run/profile selection, not once per read chunk, to keep long dump logs readable. + + +FriiDump HLDS 0xE7 probe-v2 log-once cleanup +------------------------------------------------ +The GDR-8050L modified 0xE7 selected schedule is now reported by the profile probe only. The per-read Method 8 path no longer prints the schedule line for every 16-sector chunk. + +### Experimental HLDS 0xE7 scan mode + +For drives such as GDR-8081N where the HIT 0xE7 command surface appears plausible but the cache base is not known, use scan mode before attempting a full dump: + +```powershell +.\friidump.exe -d f: --hlds-e7-scan -T 0 --scan-log "gdr8081n_e7_scan.json" + +$zip = "friidump_gdr8081n_e7_scan_results.zip" +if (Test-Path $zip) { Remove-Item $zip -Force } +Compress-Archive -Path .\gdr8081n_e7_scan.json,.\friidump.log -DestinationPath $zip -Force +``` + +This mode writes a JSON report and stops; it does not crack seeds or dump the disc. + +FriiDump 0.5.3.4 HLDS media-preflight correction +---------------------------------------- + +This build corrects the media-ready preflight before GameCube/Wii/Xbox disc initialization and seed retrieval. FriiDump now treats non-GOOD SCSI status as failure even when the Windows pass-through ioctl itself succeeds, then requires both TEST UNIT READY and a valid READ CAPACITY(10) result before vendor seed/cache commands are allowed. This covers optical drives and USB bridges that report TEST UNIT READY=GOOD with an empty tray. + +The live SCSI INQUIRY identity `HL-DT-ST CDRW/DVD GCC4244 B103` is normalized to the promoted GCC-4244N B103 Stage5B profile (CDB evidence 0x894, gate evidence 0x900386FB). Static firmware addresses remain reporting evidence only and are never emitted as host-side write/update commands. + +See `docs/reports/FRIIDUMP_HLDS_LIVE_VALIDATION_0.5.3.5.md` for the real-hardware validation matrix and reference hashes. + + +FriiDump 0.5.3.5 GCC-4244N B103 live validation +------------------------------------------------ + +The live SCSI INQUIRY identity `HL-DT-ST CDRW/DVD GCC4244 B103` is now marked `known_supported_profile_hardening_live_validated`. Using the exact promoted JCS3 parser profile (CDB evidence `0x894`, gate evidence `0x900386FB`), FriiDump completed Sonic Mega Collection (US) with media preflight OK, seed retrieval in 4 seconds, full dump OK, STOP UNIT OK, and hashes matching the Redump reference. The observed average was `2152.04 MiB/h` over `1392.34 MiB`, with a total duration of `2329.16` seconds. + +See `docs/reports/FRIIDUMP_HLDS_LIVE_VALIDATION_0.5.3.5.md` for the consolidated validation matrix. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8bb5e21 --- /dev/null +++ b/README.md @@ -0,0 +1,145 @@ +# FriiDump — Project Frankenstein Branch + +FriiDump is a command-line optical-disc dumping tool for Nintendo GameCube, +Nintendo Wii, standard DVD-ROM, and selected Original Xbox/XGD workflows. This +repository preserves the Project Frankenstein development branch based on +FriiDump **0.5.3.5**. + +The branch extends the original FriiDump code with: + +- analyzer-derived HLDS `0xE7` drive profiles for GameCube/Wii dumping; +- native GDR-8050L and GDR-3120L Xbox/XGD paths; +- redump-style Original Xbox/XGD1 reconstruction and metadata; +- optional game-partition XISO output; +- media preflight, profile reporting, and STOP UNIT cleanup; +- validated Windows/MSVC and Linux/CMake build paths. + +The original detailed user documentation remains in [`README`](README). +Xbox-specific behavior is documented in [`docs/XBOX.md`](docs/XBOX.md). + +## Repository status + +This is the first canonical-source candidate for the standalone `friidump` +repository. The imported baseline is intentionally conservative: source and +maintained documentation were preserved, while disc images, live logs, compiled +binaries, build directories, result archives, and unrelated reverse-engineering +work were excluded. + +The baseline source came from: + +```text +D:\vscode-workspace\friidump +``` + +No firmware binaries or disc images belong in this Git repository. + +## Build + +### Windows — validated MSVC 32-bit path + +The maintained Windows helper invokes the Visual Studio 2019 Build Tools +32-bit environment and compiles from `msvc32_friidump.rsp`: + +```powershell +.\build_msvc32.cmd +``` + +Clean generated output with: + +```powershell +.\build_msvc32.cmd clean +``` + +The VS Code build tasks are in `.vscode/tasks.json`. The helper currently +expects: + +```text +C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\ +``` + +### Linux / Unix — CMake + +```bash +cmake -S . -B build -DBUILD_STATIC_BINARY=ON +cmake --build build +./build/src/friidump --help +``` + +The repository candidate was locally compiled with GCC using this path. The +build completed successfully. One inherited warning remains in +`libfriidump/rs.c` concerning a right-shift count; it is recorded as technical +debt and was not silently changed during repository import. + +## Common commands + +List command-line options: + +```text +friidump --help +``` + +GameCube/Wii dump using automatic drive/profile detection: + +```text +friidump -d -8 -s -i game.iso +``` + +Standard readable DVD: + +```text +friidump -d -D -i dvd.iso +``` + +Original Xbox/XGD redump-style image: + +```text +friidump -d -T 4 -i xbox.iso +``` + +Original Xbox/XGD game-partition XISO: + +```text +friidump -d -T 4 -X xbox.xiso +``` + +Consult [`README`](README), [`docs/options`](docs/options), and +[`docs/XBOX.md`](docs/XBOX.md) before hardware use. + +## Safety and evidence rules + +FriiDump issues low-level SCSI/MMC and model-specific vendor commands. + +- Use only drives and media you own or are authorized to examine. +- Do not assume two firmware revisions share the same private-command layout. +- Prefer exact analyzer-derived profiles over guessed addresses or legacy + fallbacks. +- Treat live hardware behavior as the final validation boundary. +- Keep firmware, media images, memory dumps, and generated logs outside Git. +- Preserve provenance for every firmware/profile conclusion. + +## Source layout + +```text +src/ command-line program +libfriidump/ disc, drive, dumping, Xbox, and vendor-command logic +libmultihash/ checksum implementations +docs/ user, Xbox, validation, and historical documentation +.vscode/ maintained Windows build helper and tasks +build_msvc32.cmd Windows build entry point +CMakeLists.txt portable build entry point +``` + +## Project boundaries + +- **PFES** — engineering specification, decisions, evidence status, and history. +- **xbox-dvd-tools** — firmware acquisition, extraction, analysis, disc tools, + and shared validation utilities. +- **mn103s-emulator** — MN103S CPU/emulator and GDR-8050L emulation work. +- **friidump** — this dumping application and its maintained drive profiles. +- **Firmware archive** — managed server artifacts outside Git. + +## License + +FriiDump is distributed under the GNU General Public License, version 2 or +later. See [`COPYING`](COPYING). Existing source-file copyright notices and +authors are preserved. diff --git a/build_msvc32.cmd b/build_msvc32.cmd new file mode 100644 index 0000000..b91bb7c --- /dev/null +++ b/build_msvc32.cmd @@ -0,0 +1,35 @@ +@echo off +setlocal EnableExtensions +cd /d "%~dp0" + +if /I "%~1"=="clean" goto clean + +if not exist "config.h" ( + echo [ERROR] config.h is missing from the source root. + echo Please extract the full source ZIP before building. + exit /b 1 +) + +findstr /n /c:"#define snprintf" "%~dp0libmultihash\multihash.h" "%~dp0libfriidump\win32compat.h" >nul 2>nul +if not errorlevel 1 ( + echo [ERROR] Legacy snprintf macro still present. Overwrite libmultihash\multihash.h and libfriidump\win32compat.h from this package. + exit /b 1 +) + + +if not exist "build-msvc32" mkdir "build-msvc32" + +call "%~dp0.vscode\msvc32.cmd" cl @"%~dp0msvc32_friidump.rsp" +if errorlevel 1 exit /b %ERRORLEVEL% + +"%~dp0friidump.exe" --help +if errorlevel 1 exit /b %ERRORLEVEL% + +powershell -NoProfile -ExecutionPolicy Bypass -Command "Compress-Archive -Force -Path friidump.exe,docs,README,AUTHORS,COPYING,config.h,msvc32_friidump.rsp,build_msvc32.cmd,.vscode -DestinationPath friidump_msvc32_build_results.zip" +exit /b %ERRORLEVEL% + +:clean +if exist "friidump.exe" del /q "friidump.exe" +if exist "build-msvc32" rmdir /s /q "build-msvc32" +if exist "friidump_msvc32_build_results.zip" del /q "friidump_msvc32_build_results.zip" +exit /b 0 diff --git a/config.h b/config.h new file mode 100644 index 0000000..4574907 --- /dev/null +++ b/config.h @@ -0,0 +1,23 @@ +/* Static GCC/MinGW config for the root Makefile. Use CMake for auto-detected config. */ +#ifndef FRIIDUMP_CONFIG_H +#define FRIIDUMP_CONFIG_H + +#define HAVE_STDBOOL_H 1 + +/* MSVC does not provide POSIX fseeko/ftello. Use _fseeki64/_ftelli64 + through libfriidump/misc.c instead of advertising HAVE_FSEEKO/HAVE_FTELLO. */ +#if !defined(_MSC_VER) +#define HAVE_FSEEKO 1 +#define HAVE_FTELLO 1 +#endif + +#define HAVE_OFF_T 1 +#define SIZEOF_OFF_T 8 +#define HAVE_FPOS_T 1 +#define SIZEOF_FPOS_T 8 +#define _FILE_OFFSET_BITS 64 +#define _LARGEFILE_SOURCE 1 +#define _LARGE_FILES 1 +#define HAVE_LARGEFILE_SUPPORT 1 + +#endif /* FRIIDUMP_CONFIG_H */ diff --git a/config.h.cmake b/config.h.cmake new file mode 100644 index 0000000..67aedcf --- /dev/null +++ b/config.h.cmake @@ -0,0 +1,42 @@ +/* Define to 1 if your processor stores words with the most significant byte + first (like Motorola and SPARC, unlike Intel and VAX). */ +#cmakedefine WORDS_BIGENDIAN ${CMAKE_WORDS_BIGENDIAN} + +/* Define to 1 if stdbool.h conforms to C99. */ +#cmakedefine HAVE_STDBOOL_H 1 + +/* Debug support */ +#cmakedefine DEBUG 1 + +#cmakedefine HAVE_FSEEKO +#cmakedefine HAVE_FTELLO +#cmakedefine HAVE_FSEEK64 +#cmakedefine HAVE_FTELL64 + +#cmakedefine HAVE_OFF_T +#ifdef HAVE_OFF_T +#cmakedefine OFF_T ${OFF_T} +#define SIZEOF_OFF_T OFF_T +#endif + +#cmakedefine HAVE_FPOS_T +#ifdef HAVE_FPOS_T +#cmakedefine FPOS_T ${FPOS_T} +#define SIZEOF_FPOS_T FPOS_T +#endif + + +/* Large file support - Can't this be done better with CMake!? What about on Windows? */ +#define _FILE_OFFSET_BITS 64 +#define _LARGEFILE_SOURCE 1 +#define _LARGE_FILES +#define HAVE_LARGEFILE_SUPPORT + +#if defined(_FILE_OFFSET_BITS) +# if (_FILE_OFFSET_BITS<64) +# undef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +#else +# define _FILE_OFFSET_BITS 64 +#endif \ No newline at end of file diff --git a/docs/BUILDING.md b/docs/BUILDING.md new file mode 100644 index 0000000..a9fb65a --- /dev/null +++ b/docs/BUILDING.md @@ -0,0 +1,38 @@ +# Building FriiDump + +## Windows + +The maintained direct build path is: + +```powershell +.\build_msvc32.cmd +``` + +It calls `.vscode\msvc32.cmd`, which currently locates the Visual Studio 2019 +Build Tools 32-bit environment, then invokes: + +```text +cl @msvc32_friidump.rsp +``` + +The build script runs `friidump.exe --help` after compilation and packages a +local result ZIP. These generated files are ignored by Git. + +## Linux / Unix + +```bash +cmake -S . -B build -DBUILD_STATIC_BINARY=ON +cmake --build build +./build/src/friidump --help +``` + +The standalone repository candidate was tested with GCC 14.2 and CMake. The +build succeeded. The inherited `libfriidump/rs.c` shift-count warning remains +open and should be corrected only as a separately reviewed source change. + +## External runtime requirements + +Dumping requires operating-system access to the selected optical drive. +Privileges and device permissions vary by platform. Avoid broadly running +unrelated tools with elevated privileges; grant only the access required for +the optical device. diff --git a/docs/ChangeLog b/docs/ChangeLog new file mode 100644 index 0000000..68c3906 --- /dev/null +++ b/docs/ChangeLog @@ -0,0 +1,430 @@ +2026-07-10 - FriiDump 0.5.3.5 + * Promoted live INQUIRY `HL-DT-ST CDRW/DVD GCC4244/B103` to `known_supported_profile_hardening_live_validated`. + * Recorded exact GCC-4244N B103 Stage5B profile evidence: JCS3 tokens, CDB base 0x894, gate 0x900386FB. + * Recorded successful media preflight, 4-second seed retrieval, full Sonic Mega Collection dump, STOP UNIT, and Redump-matching hashes. + * Recorded 2329.16-second runtime and 2152.04 MiB/h observed average. + * Added FriiDump 0.5.3.5 consolidated HLDS live-validation report and advanced the next test target to GCC-4243N. + +2026-07-08 - HLDS profile documentation sync v58 + + * README now documents the Stage5B-promoted HLDS 0xE7 profile rows added in + v57: GCC-4244N B103 and GDR-8163B revisions 0B30, 0D20, 0E15, 0L20, + 0L23, 0L30, and 0M26. + * README now documents --hlds-profile-report and records the validated + GDR-8163B 0L30 Sonic Mega Collection dump in the observed fleet table. + * ChangeLog now records the v57 profile-table update so package docs match + the code. + +2026-07-08 - HLDS promoted parser-signature profile update v57 + + * Added GCC-4244N B103 as a promoted Stage5B E7 parser-signature profile + using CDB base 0x894, gate 0x900386FB, and tokens + HL;IT;RPC;RPC_JCS3;RPC_SUFFIX. + * Promoted the observed GDR-8163B inventory variants from family fallback + rows to exact parser-signature profile rows: 0B30, 0D20, 0E15, 0L20, + 0L23, 0L30, and 0M26. + * Recorded the exact GDR-8163B CDB/gate evidence: 0B30 0x5E0/0x90025030, + 0D20 0x5E0/0x90024AE7, 0E15 0x5D8/0x900247D1, 0L20 + 0x5E0/0x90024C8F, 0L23 0x5E0/0x90024D5A, 0L30 + 0x5E0/0x90025021, and 0M26 0x5E0/0x90024FF7. + * Kept the static CDB/gate fields as report/evidence metadata only; FriiDump + does not use them as host-side commands or runtime addresses. + * No FLASHUP, 3B06, 3B07, updater, write, flash, erase, or commit paths were + added or touched. + + +2026-07-01 - HLDS 0xE7 log-noise cleanup + + * GDR-8050L modified-0xE7 no-prefetch read-schedule logging is now emitted once per run/profile selection instead of once per read chunk. + * Accelerated-profile fallback resets the one-shot schedule log so the fallback profile is still visible exactly once. +2026-06-30 / GDR-8050L + GDR-8081N probe v2 + +* Fixed the modified GDR-8050L speed-probe-pending profile so it defaults to Method 8; the prior combined build could still present/run like the static single-window fallback. +* Made seed cracking fail the run when the experimental GDR-8081N cache-profile probe cannot validate any candidate, instead of continuing after a failed probe. +* Improved visible probe logging so the first probe line starts on a fresh line after the seed-retrieval prompt. + +2026-06-30 / GDR-8050L speed-probe visibility fix + +* Corrected the GDR-8050L modified-0xE7 speed-probe build so the initial + drive info reports an explicit speed-probe-pending profile instead of + looking like the old static single-window fallback. +* Probe status is now printed with direct visible output even in non-VERBOSE + builds, so runs show each GDR-8050L/GDR-8081N candidate and the selected + profile during the seed-retrieval phase. + +2026-06-30 / seed-retrieval elapsed timer + +* Added elapsed timing to the GC/Wii seed retrieval line. The console/log now reports + `Retrieving disc seeds, this might take a while... [Elapsed:HH:MM:SS] OK` + or the same elapsed marker before `Failed`. +* Added `Seed elapsed` to the HLDS 0xE7 validation summary. +* Documented seed-retrieval expectations and kept timeout behavior manual/operational + rather than adding a hard timeout that could interrupt a blocked optical-drive I/O + command unsafely. + +2026-06-30 / combined GDR-8081N probe + GDR-8050L speed-probe update + +* Combined the GDR-8081N experimental 0xE7 cache-profile probe layer with a + GDR-8050L modified-firmware speed probe so two fleet tests can be run from the + same source package. +* GDR-8050L modified 0xE7 now keeps the proven 0x80000000 one-window/no-prefetch + profile as fallback, but probes guarded no-prefetch 3-window, 2-window, and + 5-window candidates before seed cracking. +* If an accelerated GDR-8050L profile later fails during dumping, FriiDump logs + the failure and falls back to the proven single-window profile for the rest of + the run. +* GDR-8081N remains experimental: it is recognized as a candidate, enters the + seed path, and probes multiple cache profiles rather than being advertised as + confirmed GC/Wii support. +* README wording now clarifies that GDR-3120L support means the Xbox ripping path + is preserved; it is not claimed as a GC/Wii-tested drive. + +2026-06-30 / GDR-8081N experimental 0xE7 probe layer + +* Added GDR8081N/GDR-8081N as an experimental HLDS 0xE7 GC/Wii candidate. +* Added a small runtime cache-profile probe before seed cracking: Type4-derived + 0x80000000/5-window, single-window 0x80000000, Type1-base 0x00a13000, + and a moving-cache candidate 0x7fff7f00. +* Fixed unsupported-drive HLDS profile display by zero-initializing dvd_drive + state and returning zero cache/window fields when no 0xE7 profile is active. + +2026-06-30 / GDR-8050L modified 0xE7 GC/Wii test profile + +* Added a special HLDS profile for GDR8050L/GDR-8050L drives running cross-flashed + or modified firmware with 0xE7 memdump support added. +* The special profile keeps the Type4 cache geometry, base 0x80000000 and five + 16-sector memory windows, but defaults GC/Wii tests to Method 8. +* Stock GDR-8050L firmware is still documented as Xbox-path only; this GC/Wii + path is explicitly a modified-firmware experiment and does not replace the + existing GDR-8050L challenge-table Xbox dumper or GDR-3120L vendor-unlock path. + +FriiDump 0.5.3.2 HLDS 0xE7 validation summary and speed-log update + +- Added an end-of-run HLDS 0xE7 validation summary for GC/Wii dumping. +- The summary records model, DIC-style profile, cache base, memory-window count, seed-read status, dump status, STOP UNIT status, duration, and observed average ISO-payload speed. +- Documented observed Sonic Mega Collection validation speeds for GCC-4244N/B103 and GCC-4243N/A102, with GCC-4160N/0010 left as in-progress pending hash verification. + +2026-06-29 / HLDS 0xE7 AutoPlay warning + +* Added explicit Windows AutoPlay warning before non-Xbox HLDS 0xE7 GC/Wii + volume-lock and seed reads. +* GCC-4160N Type1 testing confirmed users may need to disable AutoPlay and + close File Explorer/"insert a disc" dialogs even when FriiDump applies + the shared volume guard. + +FriiDump 0.5.3.2 HLDS 0xE7 DIC profile-layer update + +- Added a DIC-style HLDS 0xE7 profile layer for GC/Wii cache dumping. +- Type1 GCC-4160N/GCC-4240N now use cache base 0x00a13000 with one + 16-sector memory window. +- Type3/Type4 retain cache base 0x80000000 with five 16-sector memory windows. +- GCC-4241N/GCC-4242N are identified as Type2 experimental profiles but are + not yet promoted to DIC-equivalent support. +- Command/method logging now prints the selected HLDS 0xE7 profile, cache base, + and memory-window count. +- GDR-3120L Xbox ripping support remains on the explicit vendor lock/unlock + path; unsupported Xbox-forced drives still fail fast rather than falling back + to GC/Wii methods. + +0.5.3.2-gcc4243n-method8-split-recovery (29/06/2026) +- Added Method 8 split recovery for GCC-4243N/GCC-4244N GC/Wii dumping. + After normal Method 8 and dump-level retries fail for a 16-sector block, + FriiDump now tries to reconstruct that block using 8-, 4-, 2-, then + 1-sector streaming chunks, validates the rebuilt block with the existing + unscrambler/EDC path, and logs every attempted subrange. +- Preserved the shared FriiDump/Xbox log path and the GDR-3120L FF 08 01 + Xbox vendor lock/unlock path. +- Confirmed the DIC source lists TSSTcorp/Samsung Kreon-firmware candidates + and GSA-4163B as an Xbox swap candidate; TSST/Kreon candidates remain + recognized as vendor-unlock experiment profiles, while GSA-4163B remains + documented as a separate swap-workflow target rather than being misrouted + into the vendor-unlock path. +> jtryba / ChatGPT + +FriiDump 0.5.3.2 GCC-4243/4244 default path and shared logging update + +- Promoted HLDS GCC-4243N/GCC4243* and GCC-4244N/GCC4244* drives to + Hitachi command 2 / Method 8 by default for GC/Wii dumping. GCC-4244N was + validated with Sonic Mega Collection; GCC-4243N is routed to the same path + for the next fleet test. +- Extended the existing Xbox reference logger into a shared FriiDump run logger. + Every invocation now creates a log: `.log` when an output file is + known, `friidump_.log` when only a drive is known, otherwise + `friidump.log`. +- Kept GDR-3120L Xbox ripping support on the FF 08 01 vendor lock/unlock path. +- Forced Xbox/XGD mode now refuses non-supported Xbox unlock profiles instead + of falling back to GC/Wii dumping methods. + + +2026-06-29 jtryba + + * libfriidump/xbox_ref/utils.c: Add SHA-256 to the copied GDR-8050L + reference full-file hash calculation, console/log summary, and XDVD/media + sidecar hash metadata. + * src/friidump.c: Suppress the legacy FriiDump hash footer for the GDR-8050L + copied-reference path so empty CRC32/MD5/SHA-1/SHA-256 lines are not + printed after the authoritative Xbox reference summary. + + +2026-06-28 jtryba / ChatGPT + + * libfriidump/xbox_ref/xbox_ref_log.[ch], libfriidump/xbox_ref/utils.c: + Keep carriage-return progress updates on the console only. The GDR-8050L + log now remains a line-oriented diagnostic record with SCSI dumps, + phase markers, hash summaries, metadata writes, and final elapsed time, + instead of recording every transient progress repaint as a separate line. + Full READ DVD STRUCTURE 0xAD payloads remain untruncated in the log. + + +2026-06-29 jtryba + + * GDR-8050L reference hook: preserve the original dumper timing sleeps + exactly (tray-cycle 3000 ms, readiness settle 1500 ms or 10000 ms + fallback, RefreshVolume's 1000 ms plus the explicit post-refresh + 2000 ms settle). + * Xbox/GDR-8050L filename behavior now matches the reference dumper: the + first unlock/metadata pass derives Title[MediaID].iso/.xiso from the XBE + title and DMI media ID by default. Supplying -i or -X is an + explicit filename override; bare -i or bare -X uses the derived name. + * If FSCTL_LOCK_VOLUME fails, report that the copied reference dumper treats + it as fatal before XBE/title probing, instead of collapsing the error into + a generic sector 0..15 dump failure. + + +2026-06-28 jtryba + + * GDR-8050L Xbox redump/XISO path: dispatch native Windows runs directly + into a copied-in reference implementation from the original Xbox dumper + (main.c/utils.c/unlock.c helpers) instead of approximating the state + machine inside FriiDump's generic read loop. + * Preserve GDR-3120L on the separate FF 08 01 vendor command path and leave + non-native drives on FriiDump's existing command/method fallback behavior. + * Keep STOP UNIT/spin-down cleanup after the reference dump attempt. + + +2026-06-29 jtryba + + * libfriidump/dumper.c, libfriidump/dvd_drive.c, libfriidump/disc.c: + Align the GDR-8050L Xbox redump state machine more literally with the + reference dumper. UnlockDrive() calls are treated as required state actions + rather than fatal capacity gates; RefreshVolume/settle/ready/speed/volume-lock + behavior is mirrored for the post-media-change metadata phase; STOP UNIT is + reserved for final cleanup. + * src/friidump.c: + Initialize Xbox dump failure-sector reporting before setup begins. +FriiDump 0.5.3.2 GDR-8050L main.c alignment follow-up + +- Relaxed the GDR-8050L UnlockDrive() port to match the original dumper: the + final READ CAPACITY observation is diagnostic, not a fatal condition. This + allows the first handshake to prime the drive before the required media-change + event instead of failing early while the visible DVD-video view still reports + 6,992 sectors. +- Added visible stage messages for the GDR-8050L redump path: primary handshake, + media-cycle, re-handshake, metadata/auth kick, video-view restore, and final + game-view handshake. +- Changed the GDR-8050L redump 32-sector game lead-in to the original dumper's + zero-fill behavior. The lead-in read/fallback experiment remains for non-8050L + Xbox strategies. +- Preserved GDR-3120L on the separate FF 08 01 vendor lock-state path and kept + non-native drives on FriiDump's original behavior unless Xbox mode is forced. +> jtryba + +FriiDump 0.5.3.2 Xbox flow/build follow-up + +- Corrected the GDR-8050L redump-style state machine to follow the original + dumper order: unlock/read game metadata, media-cycle back to the visible + DVD-video view, capture video L0/L1, unlock again, read game lead-in/game + data, then write PFI/DMI/redump JSON. +- Added START STOP UNIT LoEj tray-cycle support for the GDR-8050L redump path. +- Added unconditional STOP UNIT / spin-down cleanup after dump attempts, on + success or failure. +- Clarified docs to avoid describing the DVD-video section itself as locked; + the video section is the normal visible DVD-video view. + +0.5.3.2 (28/06/2026) +- Added Xbox redump retry/recovery kicks around GDR-8050L view-switch reads, + mirroring the original dumper cadence: re-trigger auth, set maximum speed, + and issue harmless READ(10) probes before retrying game-view reads. +- Added visible Xbox failure diagnostics that report the failing phase, source + LBA, and reconstructed output LBA even in non-VERBOSE builds. +- Improved Xbox redump failure reporting so the generic final sector range no + longer always falls back to 0..15 after a later layout phase fails. +> jtryba + +0.5.3.2 (28/06/2026) +- Fixed forced Xbox/XGD (`-T 4`) routing so GDR-8050L/GDR-3120L dumps use + the native Xbox direct MMC/SCSI path instead of displaying or selecting the + original GC/Wii Hitachi command 2 / method 9 memdump path. +- Reworded the runtime status text: Xbox initialization no longer says + "Retrieving disc seeds", because seed cracking is only for GC/Wii paths. +- Xbox `-i` now prints that it is writing an Xbox/XGD redump-style ISO. +- Added single-sector READ(10) fallback inside Xbox redump range reads so + visible DVD-video boundaries and other edge ranges are retried like the original + dumper before the dump is declared failed. +- Preserved the program version at 0.5.3.2; the preceding MSVC-only build-task + refresh did not receive a program version bump. +> jtryba + +0.5.3.2 (28/06/2026) +- Added `-D` / `--dvd` as a clear standard DVD-ROM mode available to all + drives. It is equivalent to forcing disc type `-T 3` and uses FriiDump's + original DVD/raw/ISO path rather than Xbox unlock or metadata paths. +- Added DiscImageCreator-compatible Original Xbox/XGD1 redump layout constants + to the redump JSON: XBOX_SIZE 3,820,880 sectors, layer break LBA 1,913,776, + DVD start PSN 0x30000, Xbox/game start PSN 0x60600, and output game start + LBA 0x30600 / 198144. +- Added a runtime sanity check that the hardcoded Xbox redump layout still + matches the DiscImageCreator-compatible XGD1 constants before writing an + Xbox redump-style ISO. +- Updated README, README.technical, XBOX.md, options, and CLI help to make + standard DVD dumping and Xbox redump/XISO dumping commands clearer. +> jtryba +0.5.3.1 (28/06/2026) +- Added GDR-3120L/Kreon-style vendor helpers: get feature list (`FF 08 01 10`), + set lock state 0/2 (`FF 08 01 11 xx`), and disable error-skip + (`FF 08 01 15 00`) before dumping. +- Added Samsung/Kreon-style Xbox-capable candidates TS-H352C, TS-H353A, + SH-D162C, SH-D162D, SH-D163A, and SH-D163B for forced Xbox experiments + when the drive firmware supports the FF 08 01 command family. +- Expanded the Hitachi-LG supported-drive list for GC/Wii profiles to include + GDR-8082N/8083N/8084N, GDR-8161B/8162B/8163B/8164B, + GCC-4160N/4240N/4243N/4244N/4247N, with notes for probable/error-prone + related models; added GSA-4163B as an experimental Hitachi-LG profile. +- Added AUTHORS credit for scsi1.pas / The Specialist and the xboxhacker.net + contributors for the GDR-8050L challenge-table handshake, and DiscImageCreator + reference credit for the FF 08 01 vendor lock-state path. +- Added native Xbox/XGD disc support. +- Added Xbox disc type selector: `-T 4`. +- Added GDR-8050L profile with challenge-table handshake, partition unlock, + sticky descramble enablement, and direct 2048-byte READ(10) dumping. +- Corrected the GDR-3120L profile to use the DiscImageCreator-style FF 08 01 vendor feature-list and lock-state command family instead of treating it as a plain direct READ(10) drive. +- Changed Xbox `-i` output from a simple unlocked host-view copy to a + redump-style Original Xbox/XGD1 2048-byte-sector reconstruction with locked + video L0/L1, padding, 32-sector game lead-in, and unlocked game/XDVDFS data. +- Deferred the GDR-8050L unlock until dump time so redump-style ISO output can + capture the visible DVD-video view before switching to the unlocked game view. +- Added optional Xbox redump metadata output: `.pfi.bin`, `.dmi.bin`, and + `.redump.json` next to Xbox `-i` ISO dumps when the drive exposes PFI/DMI. +- Xbox `-i` and `-X` both attempt to read the 32-sector game lead-in and + zero-fill only unreadable lead-in sectors. +- Confirmed non-Xbox drives remain on FriiDump's original autodetection and + command/method paths unless Xbox mode is explicitly forced with `-T 4`. +- Added Xbox XISO output mode: `-X ` / `--xiso `. +- XISO output probes XDVDFS at LBA 306112, falls back to LBA 32, copies + dual-layer retail-style images through LBA 1913920, and uses the XDVDFS + VolumeSize byte field for single-layer/homebrew-style images. +- XISO output attempts to read the 32-sector lead-in from the active drive view + and zero-fills only unreadable lead-in sectors. +- Added MMC/SCSI helpers for READ CAPACITY(10), READ(10), MODE SENSE(10), + MODE SELECT(10), MODE SELECT(6), READ DVD STRUCTURE, and TEST UNIT READY. +- Updated documentation for Xbox usage, options, output modes, and the + GDR-8050L handshake. +- Fixed `-A` / `--allmethods` so each command/method attempt opens a + fresh drive/disc object and binds the command-specific memory-dump function + before testing the method. The sweep now includes Xbox direct READ(10) + method 10. +- Fixed progress timing/statistics propagation from the dump routine to the + command-line front-end. +- Fixed the command-line process return code so successful operations return + `EXIT_SUCCESS` and failures return `EXIT_FAILURE`. +- Replaced the legacy MD5 and SHA-1 hash implementations with the fixed + implementations from the merged hash-upgrade branch. +- Added SHA-256 hashing/reporting for raw ISO, unscrambled ISO, and Xbox XISO + outputs while preserving existing CRC32/MD5/SHA-1 reporting. +- Updated the GDR-8050L challenge-table path to use the fixed SHA-1 + implementation for its RC4 key derivation. +> jtryba +0.5.3 (15/03/2010) +- Fixed failing after 1st DL media layer with non-Hitachi methods. +- Fixed still hashing with 'nohash' parameter when resuming. +- Fixed resuming larger files (~4 GB). +- Fixed unscrambling larger files. +- Faster file unscrambling. +- Slight modifications to methods; + possible performance increase with Hitachi based devices. +- Restructured methods and added some new ones. +- Added layer break information. +- Added current position output when error occurs. +- Added SH-D162A, SH-D162B, SH-D162C & SH-D162D as supported. +> Jackal, gorelord4e, themabus +0.5.2 (10/01/2010) +- Corrected handling of standard DVDs + (type should be forced to 3, when dumping or unscrambling). +- Better response to 'speed' parameter. +- Uniform raw output for all devices: unscrambled data + headers. +- Slight performance increase (~1650 MB/h on LH-18A1H). +- Added LH-18A1P, LH-20A1H, LH-20A1P to list of supported devices. +> Jackal, themabus +0.5.1 (01/12/2009) +- New command 'vanilla 2384'. +- Restructured methods, some now support optional parameters. +- Ability to select standard DVDs as source. +- Limited recognized Lite-On drives to LH-18A1H. +> Jackal, themabus +0.5.0 (27/11/2009) +- Regions: Italy, France, Germany, Spain, Australia, PAL-X, PAL-Y. +- Updated publisher list from http://wiitdb.com/Company/HomePage +- Included GDR8082N & GDR8161B as supported Hitachi drives. +- Lite-On, Renesas & vanilla memory buffer access commands. + Lite-On tested on LH-18A1H, should work on many more + (LH*, SH, DH*, DW* & possibly other MediaTek drives) +- Shifted methods 1..4 to 0..3 and added new ones 4..6 + Associated known drives with default methods. +- Additional commandline parameters: + stop, speed, command, type, size +- Some minor changes and fixes. +> Jackal, Truman, themabus +0.4 (08/03/2008) +- Support for DL Wii DVDs. +> mado3689 +0.3 (06/10/2007) +- First public release. + +Build packaging note: + - Fixed direct MSVC response-file build for copied GDR-8050L reference hook by avoiding duplicate sha1.obj output names and by exporting disc_get_device()/dvd_get_device(). + + +2026-06-29 / HLDS 0xE7 Windows volume guard +--------------------------------------------- + +* The existing Xbox FSCTL_LOCK_VOLUME-style guard is now exposed as a shared + FriiDump volume lock helper. +* HLDS 0xE7 GC/Wii paths apply the guard before disc seed retrieval, so Windows + Explorer/AutoPlay is less likely to interrupt GCC-4160N/GCC-4240N Type1 seed + reads with an "insert a disc" prompt. +* The guard is warning-only: if Windows already owns a transient handle, FriiDump + logs the failure and continues so the hardware read result remains authoritative. + + +2026-06-30 / GCC-4160N Type1 confirmed +--------------------------------------- + +* Updated observed Sonic Mega Collection (US) fleet results: GCC-4160N/0010 + Type1 (0x00a13000, one memory window) completed a verified matching dump. +* Recorded observed rate 1613.08 MB/h and total runtime 3127.98 seconds. +* Documented that AutoPlay had to be disabled for reliable GCC-4160N seed + retrieval even though the shared volume guard succeeded. + +2026-07-01 HLDS 0xE7 GDR-8081N scan mode + + * Added --hlds-e7-scan / --scan-log for fast HIT 0xE7 cache-base discovery without seed cracking or full dumping. + * The scan fills the drive cache with tiny reads, tries candidate cache/memdump bases, scores raw-sector ID headers, and writes a JSON report. + * Seeded GDR-8081N scan candidates from known HLDS families plus first-pass GDR-8081N firmware/binary constants and aliases. + * Preserved GDR-8050L modified-firmware read-quiet behavior and GDR-3120L Xbox-only support wording. + +2026-07-10 - FriiDump 0.5.3.3 + * Added media-ready preflight before disc detection and GC/Wii seed retrieval. + * TEST UNIT READY now blocks vendor seed/cache commands when no disc is present. + * Added bounded retry for transient becoming-ready / unit-attention states. + * Added clear no-disc and not-ready sense reporting. + * Added live INQUIRY alias `GCC4244/B103` for the promoted GCC-4244N B103 profile. + * Promoted the successfully validated GDR-8163B 0L20, 0D20, 0B30, 0E15, and 0M26 rows to live-validated status; retained 0L30 live validation. + * Added a consolidated real-hardware validation report with Redump comparisons. + + +2026-07-10 - FriiDump 0.5.3.4 + * Corrected Windows/Linux MMC error semantics: ignore_errors now suppresses diagnostics only and no longer converts failed commands into success. + * Windows SCSI pass-through now treats non-GOOD SCSI status as command failure even when DeviceIoControl succeeds. + * Strengthened media preflight: TEST UNIT READY is followed by READ CAPACITY(10), requiring a nonzero 2048-byte logical-block medium before vendor seed/cache commands. + * Fixed empty-tray false positive observed on HL-DT-ST CDRW/DVD GCC4244/B103 over USB. + * Added completed GDR-8163B 0L23 Redump-matching validation result. diff --git a/docs/INSTALL b/docs/INSTALL new file mode 100644 index 0000000..a195537 --- /dev/null +++ b/docs/INSTALL @@ -0,0 +1 @@ +See README. diff --git a/docs/LOCAL_BUILD_VALIDATION.json b/docs/LOCAL_BUILD_VALIDATION.json new file mode 100644 index 0000000..d5bbbdb --- /dev/null +++ b/docs/LOCAL_BUILD_VALIDATION.json @@ -0,0 +1,12 @@ +{ + "format": "project-frankenstein-friidump-local-build-report", + "version": 1, + "cmake_configure": "pass", + "cmake_build": "pass", + "binary_help_smoke": "pass", + "binary_version_observed": "0.5.3.5", + "help_exit_code": 1, + "warning_present": true, + "warning_location": "libfriidump/rs.c", + "source_modified_to_suppress_warning": false +} diff --git a/docs/NEWS b/docs/NEWS new file mode 100644 index 0000000..57c27ec --- /dev/null +++ b/docs/NEWS @@ -0,0 +1,53 @@ +Since last official version original method 1 have been renamed to method 0 +and it undergone certain changes. Methods 2, 3, 4 have been renamed to 7, 8 +and 9 respectively. Method 0 should work with all drives as long as they +are supported by one of memory dump commands, so if drive is unrecognized it +is preferable to keep method at 0, and try all commands. If one of such +combinations turns out to work, you can proceed then testing other methods +with this commad. In case none of commands work, you could try to determine +drive's Read Buffer command's parameters with supplied 'BruteForce3C.exe'. + +Generally program's overall bahaviour regarding commandline haven't changed +and you should be able to use same options as with official versions, though +in case you were using unrecognized drive, which would nevertheless work with +Hitachi command, you'll need to set command to 2 now (e.g. --command 2) and +method to 7, 8 or 9. + +Performance have increased since official release and should be now about the +same as with 'RawDump'. + +Regarding supported drives: + +1. Hitachi-LG GDR8161B, GDR8162B, GDR8163B, GDR8164B, GDR8082N +Those drives can read GC/Wii media without swapping. Expected performance is +1600..1900 MB/h for *4B, *3B and 2100..2600 MB/h for *2B, *1B. Custom memory +dump command is used, which returns 2064 bytes of data. It was reproted that +they can not read other (e.g. PC) discs this way though, this needs +confirmation. + +2. Lite-On LH-18A1H, DVDRW LH-18A1P, DVDRW LH-20A1H, DVDRW LH-20A1P +Reading performance for PC DVDs can go up to 5000 MB/h, which means program's +core as well as new methods are capable to output data at least at this rate. +Reading performance for GC was about 1600..1700 MB/h so likely this slowdown is +caused by drive logic itself. Though I only had one GC game to test with, so +possibly better results can be achieved depending on media. Best results were +obtained, when using method 5 with parameter 16,27 (--method5=16,27). This +combination isn't set as default because it can cause noticable delays +depending on medium quality and to make methods more general for use with other +devices. Lite-On won't read GC/Wii DVDs at all without swapping. Lite-On +returns 2384 bytes of data (2064 + ECC) by means of vendor specific READ BUFFER +command. Tested with models LH-18A1H, LH-18A1P and LH-20A1H. + +3. Plextor +Plextor would return 2064 bytes of already unscrambled data with READ BUFFER +command. It works good with ordinary DVDs but due the lack of streamed reading +support is practically useless for GC/Wii dumping because of very low +performance. Works nevertheless and could be used for some experiments and +testing. Results from PX-760A. + +4. Toshiba Samsung SH-D162A, SH-D162B, SH-D162C, SH-D162D +Returns 2384 data bytes per sector like Lite-On does. Appears to support +streamed reading but performance with tested model (SH-D162D) was somewhat low +and unstable even with ordinary DVDs. Looks promising, if only good-working +method could be determined. Latest drives added, definitely need more testing +at this point. diff --git a/docs/README.technical b/docs/README.technical new file mode 100644 index 0000000..058de16 --- /dev/null +++ b/docs/README.technical @@ -0,0 +1,525 @@ +FriiDump Technical info +=============================================================================== + +This document is a reworking and unification of information found all over the +net, regarding the structure of Nintendo Gamecube/Wii Optical Discs and how to +read them on an ordinary DVD-ROM drive. All the due credits can be found in the +AUTHORS file. + + +=============================================================================== +Nintendo Gamecube/Wii Optical Disc (GOD/WOD) structure +=============================================================================== + +In order to understand how a Gamecube or WII Optical Disk is made, let us +first take a look at a standard DVD-ROM. The complete standard is explained in +the ECMA-267 Standard. + +The user data stored on the DVD is divided in blocks, each 2048 bytes long. +Each 2048-byte block is then encapsulated in a 2064-byte structure, adding some +other data needed for error-correction and head positioning. A 2064-byte block +is called a "Data frame", and its logical layout is as follows: + + 4bytes 2bytes 6bytes 2048bytes 4bytes + - - - - - - - - - - - - - - - - - - - - - - - - - - +| ID | IED | CPR_MAI | User Data Frame | EDC | + - - - - - - - - - - - - - - - - - - - - - - - - - - + +- Identification Data (ID): Contains the PSN (Physical Sector Number), info + about the sector itself, like the layer, reflectivity, zone, etc. +- ID Error Detection Code (IED) +- Copyright Management Information (CPR_MAI): Its use is application-specific, + for instance it can be used to store a sector key in videos that use CSS, or + a scrambling key in the XBox and XBox360 Security Sectors. +- User Data: This is the data available for the end user. +- Error Detection Code (EDC): It is the checksum data for all the fields above, + its polinomial is x^32 + x^31 + x^4 + 1. + +For various reasons (not related to copy protection), the User Data Frame is +XOR'ed with a stream cipher generated by an 15bits LFSR (Linear Feedback Shift +Register), with bits 10 and 14 used as taps. The seeds are obtained from a +table of the ECMA-267 standard, the index of the seed is the 4 MSB of the last +byte of the "ID" field of the Data Frame. The same stream cipher is then used +by 16 consecutive Data Frames: for this and other reasons (again related to +error correction), data from the DVD are always read in 16-data frame blocks. + + 4bytes 2bytes 6bytes 2048bytes 4bytes + - - - - - - - - - - - - - - - - - - - - - - - - - - +| ID | IED | CPR_MAI | User Data Frame | EDC | + - - - - - - - - - - - - - - - - - - - - - - - - - - + ^ | 2048bytes cipher stream | + ^ - - - - - - - - - - + Scrambling + seed index + +Now, the first problem when dealing with Gamecube/Wii Optical Discs is that +they use a different (and yet unknown) set of seeds. This means that when an +ordinary DVD-ROM drive tries to read a GOD/WOD disc, it will unscramble the +User Data Frame with the wrong seed, causing the EDC check to fail and a read +error to be reported to the operating system, which means the inability to read +the disc. + +Furthermore, Gamecube/Wii Optical Disks use a slightly different structure for +the Data Frame, as shown in the following figure: + + 4bytes 2bytes 2048bytes 6bytes 4bytes + - - - - - - - - - - - - - - - - - - - - - - - - - - +| ID | IED | User Data Frame | CPR_MAI | EDC | + - - - - - - - - - - - - - - - - - - - - - - - - - - + | 2048bytes cipher stream | + - - - - - - - - - - + +Basically, the User Data Frame and the CPR_MAI fields are swapped, while the +scrambled bytes remain the same. + + +=============================================================================== +Tricks used to read a GOD/WOD on a standard DVD-ROM drive. +=============================================================================== +To cope with the above-mentioned problems, some methods have been suggested by +many people on the net. They vary in performance, but the basic idea is always +the same: + +1. Issue a read command for the sector of interest (this is actually a + 16-sector block, not a single sector, as stated above). +2. Let the read return failure. +3. At this moment, the DVD-ROM drive must have cached the data read from the + disc somewhere in its internal memory, to be able to unscramble them and to + check the EDC. So, we can dump the data from the drive's internal RAM. + +Unfortunately, this is not as easy as it seems, due to the fact that there is +no standard "Dump drive memory" command. This is probably implemented in many +drives for firmware debugging purposes, but, as such, it is a vendor-specific +command, which are usually undocumented and vary from vendor to vendor (or even +from drive model to drive model). + +This is where the work that many hackers around the world have done, in order +to crack the Microsoft XBox360 console, becomes useful. They needed some way to +read and write data to the XBox360 drive's internal memory, so they +disassembled the drive's firmware and patiently tried to understand what each +of the vendor-specific commands did. This way, they discovered that a certain +command could be used to read an arbitrary portion of the drive's internal RAM. +They also managed to map the RAM addresses, so that it is known where data +read from the disc are temporarily stored. + +Fortunately, the XBox360 drive is not too different from some retail DVD-ROM +drives, namely some models from LG, which means that the discovered command +works on them as well. Hence, we now have some sort of access to the disc data. + +Although, this comes at the price of speed: dumping the drive's internal memory +is a slow process, as it uses Programmed I/O (PIO) instead of Direct Memory +Access (DMA) to transfer data from the drive to the computer's memory. For this +reason, some have proposed the use of the "streaming read" command: it is a +standard command thought for those applications where the constant flow of data +is more important than its absolute correctness, such as audio or video +applications. Thus, this command does not perform the EDC on the data read from +the disc, but returns it immediately. Anyway, the command will only return the +User Data Frame, which means that only a portion of the data will be read this +way, while the rest (i.e.: the first 12 and the last 4 bytes) will have to be +read using the memory dump method. Nevertheless, this combined method will be +faster overall, as only some dozens of bytes have to be transfered through PIO, +instead of the whole sectors. + +We still have to cope with the unknown seeds. This problem can be easily solved +through the use of brute force: as there are only 15 bits to try (and commonly +only 17 seeds per GOD/WOD), this approach only takes about 30 seconds. The +bruteforce process is very simple: the LFSR is seeded from 0 to 7FFFh and for +each seed the corresponding stream cipher is generated and XOR'ed with the +proper section of the Data Frame and the EDC is computed. If the EDC is the same +as the one in the EDC field then we have the correct seed. + + +=============================================================================== +The FriiDump approach +=============================================================================== +FriiDump can use four different disc dump methods. They have been developed +gradually, empirically and heuristically, by experimenting with the PLScsi tool +and comparing the retrieved data with a known-good dump. In this section the +inner working of every dump method will be described. The code implementing the +different methods can be found in the "disc_read_sector_X" functions of disc.c. + +Please note that the desibed behaviour is that of my LG GDR8164B drive, which I +assume to be shared by similar drives. Other, more different drives, might +behave differently and require totally different methods. + +Also note that all of the methods read 16-sector blocks. + + +Method 1 + +This method is very slow. So slow that I have never dumped a whole disc with +it. Although, it served me to prove that I was going in the right direction and +that my efforts could eventually come to a working end. It also showed me that +the first versions of RawDump create bad dumps, sometimes. + +Basically this method is the same used by those early versions of RawDump, +which took 50+ hours to dump a whole disc. It works like this: + +1. Issue a read command for the required sector. This will cause the 16-sector + block to which the sector belongs to be placed at the beginning of the + drive's cache memory. Do not even bother to see what the read command + returns, as it will surely be a data read error. +2. Dump the 16-sector block. + + +Method 2 + +Method 2 is similar to method 1, but uses the above-mentioned "streaming read" +method, which somehow allows us to dump 5 blocks at a time. + +1. Issue a "streaming read" command for the required sector. This will cause + the 16-sector block to which the sector belongs to be placed at the + beginning of the drive's cache memory, together with the 4 following + 16-sector blocks. Do not bother to see what the read command returns. +2. Dump the 5 16-sector blocks. + + +Method 3 + +This is similar to the previous method, but instead of dumping the whole +sectors from memory, it uses the (not EDC checked) data returned by the +"streaming read" command. and completes it dumping only the missing bits from +memory. + +1. Issue a "streaming read" command for the required sector. This will cause + the 16-sector block to which the sector belongs to be placed at the + beginning of the drive's cache memory, together with the 4 following + 16-sector blocks. +2. For each sector of each block, reconstruct the whole Data Frame, as follows: + - Dump 12 bytes from memory. + - Use 2048 bytes returned by the read command. + - Dump 4 more bytes from memory. + +Note that this method has a small issue, as sometimes the beginning of the +cache will be dirty and contain invalid data, needing the sector to be read +again. As this seems to happen quite often, we always read a dummy sector +before the requested sector. + + +Method 4 + +Method 4 is just method 3 with a trick to use a single dump command for every +sector that has to be reconstructed, instead of two. It is the faster dump +method currently supported and, as such, the default one. + +1. Issue a "streaming read" command for the required sector. This will cause + the 16-sector block to which the sector belongs to be placed at the + beginning of the drive's cache memory, together with the 4 following + 16-sector blocks. +2. For each sector of each block, reconstruct the whole Data Frame, as follows: + - If this is the first sector of a block, dump 12 bytes from memory. + Otherwise, use the last 12 bytes of the preceding dump. + - Use 2048 bytes returned by the read command. + - Dump 16 bytes from memory, and use the first 4. This leaves 12 bytes to be + used for the reconstruction of the next sector. + +Note that the issue of method 3 applies to this method, too. + + + +=============================================================================== +Xbox/XGD direct-read and redump-style support +=============================================================================== + +Xbox/XGD support is separate from the Nintendo GOD/WOD cache-dump methods above. +FriiDump only switches into the Xbox-specific planner when the disc type is +forced with `-T 4` or when the drive identifies as one of the two native Xbox +profiles currently wired for autodetection: + +- HL-DT-ST DVD-ROM GDR8050L +- HL-DT-ST DVD-ROM GDR3120L / GDR-3120L + +All other drives continue to use FriiDump's original command/method selection, +autodetection, seed cracking, cache reads, and raw/ISO dumping behavior unless +the user explicitly forces Xbox mode. This keeps normal GameCube, Wii, Wii DL, +and plain DVD workflows on the original FriiDump path. + +Plain DVD-ROM dumping is also exposed with the convenience flag `-D` / `--dvd`, +which is equivalent to forcing disc type `-T 3`. It is available to all drives +and deliberately uses the original FriiDump DVD/raw/ISO path; it does not invoke +Xbox unlocks, XISO planning, or Xbox metadata output. + +For Xbox media, FriiDump reads 2048-byte sectors with MMC READ(10). Method 10 is +reserved internally for this direct Xbox READ(10) path. The older Nintendo raw +2064-byte format is not meaningful for Xbox redump output. + + +GDR-8050L challenge-table handshake +------------------------------------------------------------------------------- + +The GDR-8050L normally exposes a small visible DVD-video view before Xbox +authentication. For redump-style output, FriiDump follows the original dumper's +state machine rather than a single continuous read: primary handshake, +media-cycle, re-handshake, metadata/auth probe, media-cycle back to the visible +DVD-video view, video L0/L1 capture, final handshake, game data write, metadata +write, and STOP UNIT cleanup. + +The handshake sequence is based on scsi1.pas / "XBOX 1 DVD-Drive unlocker tool +v0.1" by The Specialist and the xboxhacker.net contributors credited there. +The sequence is: + +1. READ CAPACITY(10) to observe the current visible view. +2. MODE SENSE(10), page 0x3E. +3. READ DVD STRUCTURE, format 0xC0, using this twelve-byte CDB: + + AD 00 FF 02 FD FF FE 00 06 64 00 C0 + + The requested transfer length is 0x0664 bytes. If this format 0xC0 request + does not return a usable table, FriiDump falls back to the Hitachi-family + vendor challenge-table command 0xFD. + +4. Decode the challenge table: copy 0x2C bytes from offset 0x4A3, hash them + with SHA-1, use the first seven SHA-1 bytes as the RC4 key, and RC4-decrypt + the response table beginning at offset 774. +5. Select usable challenge entries whose decoded table entry begins with 0x01. +6. MODE SELECT(10), page 0x3E, first challenge. +7. MODE SENSE(10), page 0x3E, verify. +8. MODE SELECT(10), page 0x3E, second challenge. +9. MODE SENSE(10), page 0x3E, verify. +10. MODE SELECT(10), page 0x3E, partition-1 unlock. +11. MODE SELECT(6), page 0x31, to enable sticky descrambling. +12. READ CAPACITY(10) again as an observation. Like the original dumper, this + verification is diagnostic; the later XDVDFS probe determines whether the + game view is actually available. + + +GDR-3120L / Kreon-style FF 08 01 vendor lock-state path +------------------------------------------------------------------------------- + +The GDR-3120L path is not the GDR-8050L challenge-table handshake and is not a +plain direct READ(10) path. DiscImageCreator documents and uses a vendor command +family beginning with `FF 08 01`; FriiDump now mirrors the relevant parts: + +- `FF 08 01 10` - get feature list. A valid reply begins with words + `A55A 5AA5` and then feature words such as Xbox unlock-state support. +- `FF 08 01 11 00` - set lock state 0, returning the drive to the normal + vendor lock state 0 / normal DVD-video state when supported. FriiDump uses this before the + redump-style `-i` planner captures the visible/video view. +- `FF 08 01 11 02` - set lock state 2 / wxripper state. FriiDump uses this + before reading the game/XDVDFS view. +- `FF 08 01 15 00` - disable vendor error-skip before dumping so real read + failures remain visible to FriiDump. + +Samsung/Kreon-style drives such as TS-H352C, TS-H353A, SH-D162C, SH-D162D, +SH-D163A, and SH-D163B are not autodetected as Xbox mode by default; ordinary +FriiDump GC/Wii/DVD behavior remains unchanged. To test those drives with Xbox +media, force Xbox mode explicitly with `-T 4`. + + +Xbox dump outputs +------------------------------------------------------------------------------- + +`-i / --iso` writes a redump-style Original Xbox/XGD1 2048-byte-sector image. +This is more complex than copying the game partition. For retail dual-layer +media FriiDump writes this layout: + +- output LBA 0..6831: visible DVD-video L0, read from source LBA 0..6831; +- output LBA 6832..198143: pregame padding, currently zero-filled; +- output LBA 198144..198175: 32-sector game lead-in; FriiDump attempts to read + these sectors from the active unlocked/game view and zero-fills only unreadable + sectors; +- output LBA 198176..3629407: unlocked game/XDVDFS data, normally read from + source LBA 32 on GDR-8050L, or from the detected XDVDFS source LBA on other + supported profiles; +- output LBA 3629408..3820719: postgame padding, currently zero-filled; +- output LBA 3820720..3820879: visible DVD-video L1 tail, captured from locked + source LBA 6832..6991 before the unlock. + +The full redump-style output is 3,820,880 sectors. FriiDump also tries to save +PFI and DMI READ DVD STRUCTURE captures next to the ISO as `.pfi.bin` and +`.dmi.bin`, and writes a `.redump.json` file describing the reconstructed +layout, hashes, drive profile, PFI/DMI filenames, lead-in read/zero-fill counts, +and DiscImageCreator-compatible layout constants. The code checks the layout +against DiscImageCreator's Original Xbox/XGD1 model: `XBOX_SIZE = 3820880`, +`XBOX_LAYER_BREAK = 1913776`, DVD start PSN `0x30000`, Xbox start PSN `0x60600`, +and therefore game-output start LBA `0x30600` / `198144`. This is dump metadata +output, not required input for the ISO or XISO. + +`-X / --xiso` writes only the game partition as an XISO-style image. FriiDump +unlocks first when needed, probes for the XDVDFS descriptor at LBA 306112 first, +then falls back to LBA 32. For retail dual-layer media it copies from the +detected XDVDFS start LBA through LBA 1913920 and prepends a 32-sector game +lead-in. For single-layer/homebrew-style media it treats the XDVDFS VolumeSize +field as a byte count and converts it to 2048-byte sectors. + +Both Xbox output styles attempt to read their 32-sector game lead-in from the +active drive view and zero-fill only sectors that are not readable through the +selected drive/profile. + + +Supported-drive notes +------------------------------------------------------------------------------- + +GC/Wii Hitachi-LG list now documented in this branch: + +- GDR-8082N, GDR-8083N, GDR-8084N +- GDR-8161B, GDR-8162B, GDR-8163B, GDR-8164B +- GCC-4160N, GCC-4240N, GCC-4243N, GCC-4244N, GCC-4247N + - GCC-4243N/GCC4243* and GCC-4244N/GCC4244* default to Method 8. +- GDR-8085N, GDR-8087N, and GCC-4246N are probable but not confirmed here. +- GCC-4241N and GCC-4242N are capable but known to have many errors. + +Additional reference candidates from DiscImageCreator: + +- XBOX/XBOX 360 Kreon firmware candidates: TS-H353A, TS-H352C, SH-D162C, + SH-D162D, SH-D163A, SH-D163B. +- XBOX/XBOX 360 swap candidate: GSA-4163B. +- HD-DVD and BD are DiscImageCreator-supported disc classes, but this FriiDump + branch does not claim DiscImageCreator-equivalent HD-DVD/BD coverage. + +Implementation map: + +- MMC/SCSI helpers, GDR-8050L challenge-table unlock, and FF 08 01 vendor + lock-state helpers: `libfriidump/dvd_drive.c` +- Xbox disc detection/unlock timing: `libfriidump/disc.c` +- Redump-style ISO and XISO dumping: `libfriidump/dumper.c` + +=============================================================================== +How to add support for a new drive +=============================================================================== +If you read all the above stuff, it should be clear that, in order to add +support for a new DVD-ROM drive, all that is needed is a way to dump the +drive's internal memory, in particular the portion where the data read from the +disc is cached. As explained above, this function might not be present in all +drives, and might not be easy to find or to use. In case you manage to discover +it, just copy the file "hitachi.c" to a new one, and modify it opportunely. + +Some modifications will also be needed in "dvd_drive.c", in order to add +autodetection for the new drive, in the "dvd_assign_functions" function. + +Apart from this, the cache behaviour of the new drive might not be the same +as that of the currently supported models, so the program architecture might +need radical changes. In this case, please report. + +Current fleet test notes +------------------------ + +The current uploaded inventory.cvs lists: GDR-8163B x15, GDR-8081N x3, +GCC-4241N x5, GCC-4243N x5, GCC-4244N x9, GCC-4160N x3, GWA-4164B x1, +GSA-H73N x1, and GH22NS30 x1. The planned validation order remains incremental: +first confirm GCC-4243N against the known-good Sonic Mega Collection hash from +the GCC-4244N run, then repeat the same disc on GCC-4244N if reinstalled, then +move through GCC-4241N/GCC-4160N/GDR-8081N/GDR-8163B and finally the SATA DVD +writer family. Do not test the entire fleet at once; keep one drive/disc/hash +record per run. + +GCC-4243N/GCC-4244N Method 8 split-recovery note +-------------------------------------------------- +For HLDS GCC-4243N and GCC-4244N GameCube/Wii dumping, the default Hitachi command-2 / Method-8 path now has three visible recovery layers: the existing Method-8 5-block/window read reconstruction retry loop, the dump-level retry envelope, and a Method-8 split-recovery path for a failed 16-sector block. The split-recovery path reconstructs the failed block by trying 8-sector, 4-sector, 2-sector, then 1-sector streaming chunks, validates the rebuilt 16-sector raw block with the normal unscrambler/EDC check, and only caches/writes it if validation succeeds. All retry and split-recovery messages are mirrored through the shared FriiDump log file, so a failure such as `Dump failed at sectors: N..N+15` should now include whether 8/4/2/1 chunk recovery was attempted and where it failed. Existing Xbox logging remains on the same shared log path; GDR-3120L Xbox support remains on the explicit vendor lock/unlock path. + +HLDS 0xE7 DIC profile-layer update +------------------------------------ +This branch now classifies HLDS/MN103 0xE7 GC/Wii dumping drives into DIC-style profiles before assigning the FriiDump Hitachi cache reader. The selected profile is printed in the run log after Command/Method: + +- Type1: GCC-4160N/GCC-4240N, cache base 0x00a13000, one 16-sector memory window. +- Type3: GCC-4243N/GCC-4244N/GCC-4246N/GCC-4247N and GDR8083N/GDR8084N, cache base 0x80000000, five 16-sector memory windows. +- Type4: GDR8082N/GDR8161B/GDR8162B/GDR8163B/GDR8164B and related DVD-ROM profiles, cache base 0x80000000, five 16-sector memory windows. +- GDR-8050L modified 0xE7 speed-probe/fallback: GDR8050L/GDR-8050L with cross-flashed or modified firmware that adds 0xE7 memdump, cache base 0x80000000. Single-window is proven; this build probes guarded 3-window, 2-window, and 5-window no-prefetch schedules before falling back to the proven one-window Method 8 profile. +- Type2_1/Type2_2: GCC-4241N/GCC-4242N are identified as experimental DIC Type2 shapes, but this branch does not yet claim DIC parity for their moving-cache behavior. + +The Type1 base address comes from the DIC source behavior for GCC-4160N/GCC-4240N. Method 8 now honors the selected profile's memory-window count, so Type1 reads/cache-validates one 16-sector block per request instead of assuming the Type3/Type4 five-window cache layout. GCC-4243N and GCC-4244N remain on Method 8 by default. GDR-8050L is split into a modified-firmware GC/Wii 0xE7 path: stock GDR-8050L firmware is still expected to use the Xbox path only, while a cross-flashed/modified GDR-8050L with 0xE7 memdump added can use Method 8. The proven GDR-8050L fallback is one window/no-prefetch; this build probes guarded multi-window no-prefetch speed profiles and reverts to the proven single-window path if an accelerated read fails. GDR-3120L Xbox ripping support remains on the separate explicit vendor lock/unlock path and is not described as GC/Wii support. + + + +2026-06-29 / HLDS 0xE7 Windows volume guard +--------------------------------------------- + +* The existing Xbox FSCTL_LOCK_VOLUME-style guard is now exposed as a shared + FriiDump volume lock helper. +* HLDS 0xE7 GC/Wii paths apply the guard before disc seed retrieval, so Windows + Explorer/AutoPlay is less likely to interrupt GCC-4160N/GCC-4240N Type1 seed + reads with an "insert a disc" prompt. +* The guard is warning-only: if Windows already owns a transient handle, FriiDump + logs the failure and continues so the hardware read result remains authoritative. + + +2026-06-29 / HLDS 0xE7 AutoPlay warning +------------------------------------------------ + +* Added an explicit Windows AutoPlay warning before the HLDS 0xE7 GC/Wii + volume-lock and seed-retrieval phase. +* GCC-4160N Type1 testing showed the volume lock works, but Windows AutoPlay + can still open an "insert a disc" dialog and interfere until AutoPlay is + disabled and File Explorer/dialogs are closed. +* The warning is printed for all non-Xbox HLDS 0xE7 GC/Wii profiles before + FriiDump attempts the shared volume guard. + + +HLDS 0xE7 validation summary and observed speeds +-------------------------------------------------- + +For HLDS 0xE7 GC/Wii runs, FriiDump now prints a compact validation summary at the end of the run. The summary records the selected profile, cache base, memory-window count, seed-read status, seed-retrieval elapsed time, dump status, STOP UNIT status, duration, and an observed average speed computed from the ISO payload size and elapsed dump time. The hash comparison remains manual: compare the printed hashes against the known-good target for the test disc. + +Seed retrieval timing note: normal GC/Wii seed cracking is usually a seconds-to-tens-of-seconds step, with the original technical note describing the brute-force portion as roughly 30 seconds on the old reference path. Experimental HLDS 0xE7 profiles can take longer because this fork may also probe cache profiles and guard against Windows polling. This build does not add a hard seed timeout because aborting a blocked optical-drive command from inside FriiDump is less safe than letting Windows/drive firmware return or letting the user cancel with Ctrl+C. As an operational rule, a seed phase above about 5 minutes is suspicious, and above about 10-15 minutes should usually be treated as a failed profile/drive state: cancel, confirm AutoPlay/File Explorer are closed, power-cycle or tray-cycle the drive if needed, and rerun with the log preserved. + +Observed Sonic Mega Collection (US) fleet results so far: + +| Model | Firmware | HLDS profile | Cache base | Windows | Result | FriiDump final displayed rate | Duration | Notes | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| GCC-4244N | B103 | Type3 | 0x80000000 | 5 | PASS | 2184.83 MB/h | 2294.36 s | First confirmed GC dump; Dolphin playback confirmed. | +| GCC-4243N | A102 | Type3 | 0x80000000 | 5 | PASS | 2534.09 MB/h | 1522.95 s | Confirmed matching Sonic hashes after resume/retry work. | +| GCC-4160N | 0010 | Type1 | 0x00a13000 | 1 | PASS | 1613.08 MB/h | 3127.98 s | AutoPlay had to be disabled; volume guard OK; hashes matched known-good Sonic dump. | +| GDR-8050L modified | 0012 | GDR-8050L modified 0xE7 single-window proven fallback | 0x80000000 | 1 | PASS | 776.49 MiB/h / 782.59 MB/h | 6455.22 s | Cross-flashed/modified firmware with 0xE7 memdump; hashes matched Sonic. Current build adds guarded speed probes. | + +Known-good Sonic Mega Collection (US) target hashes: + +``` +CRC32 01b52739 +MD5 85a525df1481d0ad67d8761f832dca12 +SHA-1 06eb6d15b4d7f90ec0fed9ce9a77db41358d74ed +SHA-256 30098da93f5de9ece8da44f8afdfb85cf9bcdc24d77131221e64e3038a529010 +``` + +Use this table as an observed fleet log, not a promise that every firmware revision of a model behaves identically. + + +HLDS GDR-8050L modified 0xE7 note (2026-06-30): + A cross-flashed GDR-8163B running modified GDR-8050L firmware with 0xE7 memdump added reached seed retrieval and dumped the first 320 sectors, then failed at sectors 320..335 when treated as a normal five-window Type4 cache. A later single-window/no-prefetch run completed Sonic Mega Collection and matched the known hashes at roughly 776.49 MiB/h / 782.59 MB/h. This build keeps that single-window path as the proven fallback, but adds a speed probe before seed cracking: 3-window no-prefetch, 2-window no-prefetch, 5-window guarded no-prefetch, then fallback to one window. If an accelerated GDR-8050L profile later fails during a dump, FriiDump logs the failure and reverts to the proven one-window profile for the rest of the run. Stock GDR-8050L remains an Xbox path, not a GC/Wii memdump drive. + + +GDR-8081N experimental 0xE7 probe layer +----------------------------------------- +GDR-8081N is now recognized as an experimental HLDS 0xE7 GC/Wii candidate. +Unlike GDR8082N/GDR816x, it was not in the confirmed DIC dump list, so FriiDump +does not hard-code it as a normal Type4 drive. It starts as `GDR-8081N +experimental 0xE7 probe` and, during seed retrieval, tries small sector-0 +validation reads across these cache profiles: + +- Probe A Type4-derived: base 0x80000000, 5 windows. +- Probe B single-window: base 0x80000000, 1 window. +- Probe C Type1-base: base 0x00a13000, 1 window. +- Probe D moving-cache candidate: base 0x7fff7f00, 1 window. + +The first candidate that can read/validate sector 0 is selected for the rest of +the run and logged. If all candidates fail, seed retrieval stops and the log +records each failed probe. Unsupported/non-HLDS drives now keep zeroed HLDS +profile fields so stale cache-base/window values are not printed. + +GDR-8050L speed-probe visibility note: + Builds after the seed-timer package report the modified GDR-8050L as `speed-probe pending` at initial drive-info time, then print each guarded candidate during seed retrieval. If all accelerated candidates fail, the run visibly selects the proven single-window fallback. + +Probe v2 note: + The GDR-8050L speed-probe-pending profile now defaults to Method 8, so the probe branch is actually exercised by default. Seed cracking now fails the run if the experimental 0xE7 profile probe cannot validate a cache candidate, instead of continuing after an unsupported/failed probe. The first visible probe message starts on a fresh line after the seed-retrieval prompt for easier log review. + +HLDS 0xE7 logging note: GDR-8050L modified-0xE7 read schedule selection is logged once per run/profile selection, not once per read chunk, to keep long dump logs readable. + + +FriiDump HLDS 0xE7 probe-v2 log-once cleanup +------------------------------------------------ +The GDR-8050L modified 0xE7 selected schedule is now reported by the profile probe only. The per-read Method 8 path no longer prints the schedule line for every 16-sector chunk. + +## HLDS 0xE7 scan mode for GDR-8081N and other experimental drives + +This branch adds a fast discovery mode for experimental HLDS HIT 0xE7 drives: + +```powershell +.\friidump.exe -d f: --hlds-e7-scan -T 0 --scan-log "gdr8081n_e7_scan.json" + +$zip = "friidump_gdr8081n_e7_scan_results.zip" +if (Test-Path $zip) { Remove-Item $zip -Force } +Compress-Archive -Path .\gdr8081n_e7_scan.json,.\friidump.log -DestinationPath $zip -Force +``` + +The scan mode does not crack GameCube/Wii seeds and does not dump disc data. It is intended to avoid long failed seed-retrieval runs while discovering whether a drive exposes a usable HIT 0xE7 cache/memdump base. It performs small cache-fill reads, tries a bounded set of candidate memory bases, captures raw-sector header/tail bytes, scores expected DVD raw sector IDs, and writes a JSON report. + +For GDR-8081N the first candidate set includes known Type3/Type4 bases, nearby 0x80000000 offsets, Type1/GCC-4160N neighborhood bases, low SRAM aliases, and a firmware-mapping sanity candidate. Future analyzer work should use firmware control-flow around the 0xE7 handler and live scan deltas to rank candidates instead of treating firmware/disassembly mapping base as the drive SRAM cache base. diff --git a/docs/REPOSITORY_SCOPE.md b/docs/REPOSITORY_SCOPE.md new file mode 100644 index 0000000..02ddac0 --- /dev/null +++ b/docs/REPOSITORY_SCOPE.md @@ -0,0 +1,24 @@ +# Repository Scope + +## Included + +- FriiDump application and libraries. +- Portable CMake build definitions. +- Maintained MSVC 32-bit response file, helper, and VS Code tasks. +- Licensing and authorship records. +- Current user, Xbox, HLDS profile, and validation documentation. +- Historical upstream documentation needed to understand the codebase. + +## Excluded + +- Disc images and media-specific sidecars. +- Dump logs, debug traces, and generated validation artifacts. +- Compiled executables, object files, build directories, and result ZIPs. +- Firmware binaries, update utilities, memory dumps, and analyzer outputs. +- GDR-8081N reverse-engineering utilities that happened to share the previous + workspace. +- PFES, analyzer-chain, and MN103S-emulator source trees. + +Excluded materials remain in their appropriate project repositories or managed +artifact storage; exclusion from this Git repository is not an instruction to +delete them. diff --git a/docs/TODO b/docs/TODO new file mode 100644 index 0000000..2c83d17 --- /dev/null +++ b/docs/TODO @@ -0,0 +1,13 @@ +- Hashes are wrongly calculated on big-endian machines. This just needs fixing + of some #define's in the multihash library. +- Add MacOS X-specific code. All that is needed is a way to send MMC commands + to the DVD-ROM drive. Inspiration can be taken from the libcdio sources. If + only I had a Mac... Same goes for *BSD, anyway. +- Maybe write a graphical client. I will not do this as I am no GUI fanatic, + but I am sure many Windows users will still use RawDump as it has a GUI, + while we do not. This is actually quite easy, as the program is split into a + library (libfriidump) and a frontend. If anybody does it, please try to make + a portable GUI, maybe by using GTK+, qt, wxWidgets or whatever similar + toolkit you might find. +- Make FriiDump as fast as RawDump (i.e.: 2.5 hours for a Wii disc). +- Improve and better document the library API. diff --git a/docs/XBOX.md b/docs/XBOX.md new file mode 100644 index 0000000..0b203ed --- /dev/null +++ b/docs/XBOX.md @@ -0,0 +1,226 @@ +# Xbox disc support + +This version adds a native Xbox/XGD disc path to FriiDump. It uses the selected +optical drive directly through MMC/SCSI commands. The only extra Xbox metadata +written by FriiDump is optional dump metadata such as PFI/DMI binary captures +and the redump-style JSON descriptor. + +## Supported Xbox drive profiles + +- `HL-DT-ST DVD-ROM GDR8050L` + - Detected as an Xbox-capable drive. + - Keeps the visible DVD-video view during detection so `-i` can capture video L0/L1. + - Runs the Xbox challenge-table handshake only when the dumper needs the + unlocked game/XDVDFS view. + - Enables sticky descrambling with Mode Select page `0x31` after the unlock. +- `HL-DT-ST DVD-ROM GDR3120L` / `GDR-3120L` + - Detected as an Xbox-capable drive. + - Uses the DiscImageCreator-style `FF 08 01` feature-list and lock-state + command family. + - For redump-style `-i`, FriiDump tries lock state `0` first to capture the + visible/video view, then lock state `2` / wxripper state to read the game + view. + - The GDR-8050L challenge-table handshake is not applied to this model. + +Samsung/Kreon-style candidates such as `TS-H352C`, `TS-H353A`, `SH-D162C`, +`SH-D162D`, `SH-D163A`, and `SH-D163B` are recognized as drive profiles, but +are not automatically redirected into Xbox mode. Force Xbox mode with `-T 4` +when testing those drives and firmware that supports the `FF 08 01` command +family. + +Other drives continue to use FriiDump's original drive command/method logic for +ordinary GameCube, Wii, Wii DL, or DVD dumps. If Xbox mode is explicitly forced +with `-T 4` on a drive that is not wired into the Xbox unlock backend, FriiDump +now refuses the run instead of falling back to GC/Wii methods. + +For ordinary DVD-ROM media, use `-D` / `--dvd` as a clear alias for `-T 3`: + +```sh +friidump -d -D -i dvd.iso +``` + +That DVD mode is available to all drives and stays on FriiDump's original +DVD/raw/ISO dumping path. It does not run Xbox unlocks or write Xbox metadata. + +## Recommended commands + +### Redump-style Original Xbox/XGD1 ISO + +```sh +friidump -d -T 4 -i xbox.iso +``` + +For Xbox/XGD media, `-i` reconstructs a redump-style 2048-byte-sector image. It +is not just a game-partition dump. For retail dual-layer media the layout is: + +- video L0 at output LBA `0`, read from the visible DVD-video view; +- pregame padding up to output LBA `198144`; +- a 32-sector game lead-in at output LBA `198144`; +- unlocked game/XDVDFS data beginning at output LBA `198176`; +- postgame padding; +- video L1 tail at output LBA `3820720`, captured from the visible DVD-video view + before the unlock. + +When available, FriiDump also writes these files next to the ISO: + +- `.pfi.bin` - READ DVD STRUCTURE format `0x00` / Physical Format + Information; +- `.dmi.bin` - READ DVD STRUCTURE format `0x04` / Disc Manufacturing + Information; +- `.redump.json` - JSON metadata describing the layout, hashes, drive + profile, PFI/DMI filenames, and lead-in capture status. + +For the GDR-8050L redump path, the 32-sector game lead-in follows the original +dumper's option-1 behavior and is written as zero-fill before copying unlocked +XDVDFS data from source LBA `32`. For non-8050L Xbox strategies, FriiDump still +attempts to read lead-in sectors when the active drive/profile can expose them, +and zero-fills only unreadable lead-in sectors. + +FriiDump also records a `discimagecreator_reference` section in the JSON. The +redump-style constants are checked against DiscImageCreator's Original +Xbox/XGD1 model: `XBOX_SIZE = 3820880`, `XBOX_LAYER_BREAK = 1913776`, DVD start +PSN `0x30000`, and Xbox/game start PSN `0x60600`. That gives the FriiDump +virtual layout start at output LBA `0x30600` / `198144`, matching the +DiscImageCreator layout model for Original Xbox discs. + +For the GDR-8050L redump-style path, FriiDump follows the original dumper's +state order: primary handshake, media-cycle, re-handshake, game metadata/auth +probe, media-cycle back to the visible DVD-video view, video L0/L1 capture, final +handshake, game lead-in/game data write, metadata write, and STOP UNIT cleanup. + +For GDR-3120L/Kreon-style vendor-lock-state drives, FriiDump sends lock state +`0` before the video-view capture and lock state `2` before the game-view read. + +### Game partition XISO + +```sh +friidump -d -T 4 -X xbox.xiso +``` + +`-X / --xiso` writes only the game partition as an XISO-style image. The XISO +path unlocks first when needed, probes XDVDFS at LBA `306112`, then falls back +to LBA `32`. For dual-layer retail-style media it copies through LBA `1913920` +using `1913920 - startLba` source sectors. For single-layer/homebrew-style media +it uses `VolumeSize / 2048` from the XDVDFS header. + +Like `-i`, XISO mode attempts to read the 32-sector game lead-in and falls back +to per-sector zero-fill only where reads fail. + +`--xiso` is a separate output mode and cannot be combined with `-i`, `-r`, or +`-a` in one run. + +## GDR-8050L handshake sequence + +The GDR-8050L path performs this sequence when switching from visible DVD-video view +to unlocked game view. This method is credited to scsi1.pas / "XBOX 1 +DVD-Drive unlocker tool v0.1" by The Specialist and the xboxhacker.net +contributors credited in that source. + +1. READ CAPACITY(10). +2. MODE SENSE(10), page `0x3E`. +3. READ DVD STRUCTURE, format `0xC0`, using CDB + `AD 00 FF 02 FD FF FE 00 06 64 00 C0`. +4. If needed, fall back to the Hitachi-family vendor challenge-table command + `0xFD`. +5. Decode the challenge table with SHA-1 and RC4. +6. MODE SELECT(10) first challenge. +7. MODE SENSE(10) verify. +8. MODE SELECT(10) second challenge. +9. MODE SENSE(10) verify. +10. MODE SELECT(10) partition-1 unlock. +11. MODE SELECT(6), page `0x31`, to enable sticky descrambling. +12. READ CAPACITY(10) again as an observation. Like the original dumper, this + verification is diagnostic; the later XDVDFS probe determines whether the + game view is actually available. + +## GDR-3120L / Kreon-style vendor unlock sequence + +The GDR-3120L/Kreon-style path follows the DiscImageCreator `FF 08 01` command +family: + +1. `FF 08 01 10` - get feature list; valid replies begin with `A55A 5AA5`. +2. `FF 08 01 11 00` - set lock state 0 / visible DVD-video view when redump-style `-i` + needs the visible/video area. +3. `FF 08 01 11 02` - set lock state 2 / wxripper state when the game/XDVDFS + view is needed. +4. `FF 08 01 15 00` - disable vendor error-skip before dumping. +5. READ CAPACITY(10), READ DVD STRUCTURE, and READ(10) are then used by the + selected dump layout. + +## GC/Wii and candidate drive list + +Hitachi-LG GC/Wii support list reflected in this branch: + +- `GDR-8082N`, `GDR-8083N`, `GDR-8084N` +- `GDR-8161B`, `GDR-8162B`, `GDR-8163B`, `GDR-8164B` +- `GCC-4160N`, `GCC-4240N`, `GCC-4243N`, `GCC-4244N`, `GCC-4247N` +- `GDR-8085N`, `GDR-8087N`, and `GCC-4246N` are probable but not confirmed here. +- `GCC-4241N` and `GCC-4242N` are capable but known to have many errors. + +DiscImageCreator reference candidates noted but not fully cloned here: + +- XBOX/XBOX 360 Kreon firmware candidates: `TS-H353A`, `TS-H352C`, `SH-D162C`, + `SH-D162D`, `SH-D163A`, `SH-D163B`. +- XBOX/XBOX 360 swap candidate: `GSA-4163B`. +- HD-DVD and BD are DiscImageCreator-supported disc classes, but this FriiDump + branch does not claim DiscImageCreator-equivalent HD-DVD/BD feature coverage. + +Implementation map: + +- MMC/SCSI helpers, the GDR-8050L unlock path, and the `FF 08 01` vendor + helpers: `libfriidump/dvd_drive.c` +- Xbox disc detection/unlock timing: `libfriidump/disc.c` +- Redump-style ISO and XISO dumping: `libfriidump/dumper.c` + +### GDR-8050L reference path + +On Windows, GDR-8050L `-i` and `-X` are dispatched to a copied-in reference implementation from jtryba's original Xbox dumper (`main.c`, `utils.c`, `unlock.c`, and helpers). This preserves the original state-machine order instead of approximating it in FriiDump's generic read loop. It also preserves the reference timing sleeps: tray-cycle 3000 ms, readiness settle 1500 ms after success or 10000 ms fallback, RefreshVolume's 1000 ms wait, and the explicit post-refresh 2000 ms settle. + +For GDR-8050L, bare `-i` derives `Title[MediaID].iso` from the XBE title and DMI media ID, and bare `-X` derives `Title[MediaID].xiso`. Supplying `-i ` or `-X ` is an explicit filename override. The first unlock/metadata pass exists for that size/title/media-ID discovery before the actual dump mode runs. + +GDR-3120L remains on the separate `FF 08 01` vendor lock/unlock path. Forced Xbox mode on non-supported Xbox unlock profiles is rejected rather than routed through FriiDump GC/Wii fallback methods. + + +## Logging + +The original Xbox reference logger is now the shared FriiDump run logger. Every +run opens a log file before the banner is printed. When an output filename is +known, the log is retargeted to `.log`. If no output filename is known +but a drive is known, it uses `friidump_.log`; otherwise it uses +`friidump.log`. Xbox reference diagnostics continue to be written to the same +log. + +GCC-4243N/GCC-4244N Method 8 split-recovery note +-------------------------------------------------- +For HLDS GCC-4243N and GCC-4244N GameCube/Wii dumping, the default Hitachi command-2 / Method-8 path now has three visible recovery layers: the existing Method-8 5-block/window read reconstruction retry loop, the dump-level retry envelope, and a Method-8 split-recovery path for a failed 16-sector block. The split-recovery path reconstructs the failed block by trying 8-sector, 4-sector, 2-sector, then 1-sector streaming chunks, validates the rebuilt 16-sector raw block with the normal unscrambler/EDC check, and only caches/writes it if validation succeeds. All retry and split-recovery messages are mirrored through the shared FriiDump log file, so a failure such as `Dump failed at sectors: N..N+15` should now include whether 8/4/2/1 chunk recovery was attempted and where it failed. Existing Xbox logging remains on the same shared log path; GDR-3120L Xbox support remains on the explicit vendor lock/unlock path. + +HLDS 0xE7 DIC profile-layer update +------------------------------------ +This branch now classifies HLDS/MN103 0xE7 GC/Wii dumping drives into DIC-style profiles before assigning the FriiDump Hitachi cache reader. The selected profile is printed in the run log after Command/Method: + +- Type1: GCC-4160N/GCC-4240N, cache base 0x00a13000, one 16-sector memory window. +- Type3: GCC-4243N/GCC-4244N/GCC-4246N/GCC-4247N and GDR8083N/GDR8084N, cache base 0x80000000, five 16-sector memory windows. +- Type4: GDR8082N/GDR8161B/GDR8162B/GDR8163B/GDR8164B and related DVD-ROM profiles, cache base 0x80000000, five 16-sector memory windows. +- Type4 GDR-8050L modified 0xE7 test: GDR8050L/GDR-8050L with cross-flashed or modified firmware that adds 0xE7 memdump, cache base 0x80000000, five 16-sector memory windows, Method 8 default for GC/Wii tests. +- Type2_1/Type2_2: GCC-4241N/GCC-4242N are identified as experimental DIC Type2 shapes, but this branch does not yet claim DIC parity for their moving-cache behavior. + +The Type1 base address comes from the DIC source behavior for GCC-4160N/GCC-4240N. Method 8 now honors the selected profile's memory-window count, so Type1 reads/cache-validates one 16-sector block per request instead of assuming the Type3/Type4 five-window cache layout. GCC-4243N and GCC-4244N remain on Method 8 by default. GDR-8050L is now split into a special modified-firmware GC/Wii test profile: stock GDR-8050L firmware is still expected to use the Xbox path only, while a cross-flashed/modified GDR-8050L with 0xE7 memdump added can be tested on the Method 8 GC/Wii cache reader. GDR-3120L Xbox ripping support remains on the separate explicit vendor lock/unlock path. + + +GDR-8081N experimental 0xE7 probe layer +----------------------------------------- +GDR-8081N is now recognized as an experimental HLDS 0xE7 GC/Wii candidate. +Unlike GDR8082N/GDR816x, it was not in the confirmed DIC dump list, so FriiDump +does not hard-code it as a normal Type4 drive. It starts as `GDR-8081N +experimental 0xE7 probe` and, during seed retrieval, tries small sector-0 +validation reads across these cache profiles: + +- Probe A Type4-derived: base 0x80000000, 5 windows. +- Probe B single-window: base 0x80000000, 1 window. +- Probe C Type1-base: base 0x00a13000, 1 window. +- Probe D moving-cache candidate: base 0x7fff7f00, 1 window. + +The first candidate that can read/validate sector 0 is selected for the rest of +the run and logged. If all candidates fail, seed retrieval stops and the log +records each failed probe. Unsupported/non-HLDS drives now keep zeroed HLDS +profile fields so stale cache-base/window values are not printed. diff --git a/docs/options b/docs/options new file mode 100644 index 0000000..b39d268 --- /dev/null +++ b/docs/options @@ -0,0 +1,90 @@ +FriiDump 0.5.3.2 - Copyright (C) 2007 Arep +This software comes with ABSOLUTELY NO WARRANTY. +This is free software, and you are welcome to redistribute it +under certain conditions; see COPYING for details. + +Official support forum: http://wii.console-tribe.com + +Forum for this UNOFFICIAL VERSION: http://forum.redump.org + + +Available command line options: + + -h, --help Show this help + -a, --autodump Dump the disc to an ISO file with an + automatically-generated name, resuming the dump + if possible + -g, --gui Use more verbose output that can be easily + parsed by a GUI frontend + -d, --device Dump disc from device + -p, --stop Instruct device to stop disc rotation + -D, --dvd Force standard DVD-ROM mode for any drive + (equivalent to -T 3; uses FriiDump original + DVD/raw/ISO paths, not Xbox mode) + -c, --command Force memory dump command: + 0 - vanilla 2064 + 1 - vanilla 2384 + 2 - Hitachi + 3 - Lite-On + 4 - Renesas + -x, --speed Set streaming speed (1, 24, 32, 64, etc., + where 1 = 150 KiB/s and so on) + -T, --type Force disc type: + 0 - GameCube + 1 - Wii + 2 - Wii_DL + 3 - DVD + 4 - Xbox/XGD 2048-byte-sector mode + Native profiles: GDR-8050L and GDR-3120L. + Forced Xbox mode requires a supported Xbox + unlock profile; no GC/Wii fallback is used. + -S, --size Force disc size + -r, --raw Output to file in raw format (2064-byte + sectors) + -i, --iso Output to file in ISO format (2048-byte + sectors). For Xbox/XGD this reconstructs the + redump-style XGD1 layout and writes .pfi.bin, + .dmi.bin, and .redump.json metadata when possible + -X, --xiso Output Xbox/XGD game partition as XISO (.xiso). + Attempts to read the 32-sector game lead-in + from drive-readable sectors and zero-fills only + unreadable lead-in sectors. Use -i for + redump-style ISO + PFI/DMI/JSON; use -X only + for game-partition XISO. + -u, --unscramble Convert (unscramble) raw image contained in + to ISO format + -H, --nohash Do not compute CRC32/MD5/SHA-1/SHA-256 hashes + for generated files + -s, --resume Resume partial dump + - General ----------------------------------- + -0, --method0[=,] Use dumping method 0 (Optional argument + specifies how many sectors to request from disc + and read from cache at a time. Values should be + separated with a comma. Default 16,16) + - Non-Streaming ----------------------------- + -1, --method1[=,] Use dumping method 1 (Default 16,16) + -2, --method2[=,] Use dumping method 2 (Default 16,16) + -3, --method3[=,] Use dumping method 3 (Default 16,16) + - Streaming --------------------------------- + -4, --method4[=,] Use dumping method 4 (Default 27,27) + -5, --method5[=,] Use dumping method 5 (Default 27,27) + -6, --method6[=,] Use dumping method 6 (Default 27,27) + - Hitachi ----------------------------------- + -7, --method7 Use dumping method 7 (Read and dump 5 blocks + at a time, using streaming read) + -8, --method8 Use dumping method 8 (Read and dump 5 blocks + at a time, using streaming read, using DMA) + -9, --method9 Use dumping method 9 (Read and dump 5 blocks + at a time, using streaming read, using DMA and + some speed tricks) + -A, --allmethods Try all known command/method combinations until + one works. Reopens the drive for each command so + command-specific vendor handlers are rebound. + +Xbox dump notes: + - GDR-8050L redump-style -i follows the original dumper order: + primary handshake, media-cycle, re-handshake, metadata/auth probe, + media-cycle back to visible DVD-video view, capture video L0/L1, final + handshake, zero-fill the 32-sector game lead-in, read game data, then write + .pfi.bin, .dmi.bin, and .redump.json. + - FriiDump sends STOP UNIT / spin-down after dump attempts, success or failure. diff --git a/docs/reports/FRIIDUMP_HLDS_LIVE_VALIDATION_0.5.3.3.md b/docs/reports/FRIIDUMP_HLDS_LIVE_VALIDATION_0.5.3.3.md new file mode 100644 index 0000000..43775ae --- /dev/null +++ b/docs/reports/FRIIDUMP_HLDS_LIVE_VALIDATION_0.5.3.3.md @@ -0,0 +1,72 @@ +# FriiDump 0.5.3.3 — HLDS Live Validation Report + +## Scope + +This report records real-hardware GC/Wii dumping validation for the Stage5B-derived HLDS 0xE7 profile work. All listed dump tests are read-only. Static CDB-base and gate addresses are firmware-analysis evidence only; FriiDump does not execute those addresses and does not issue flash, erase, updater, `3B06`, or `3B07` commands. + +## Reference disc: Sonic Mega Collection (US) + +Redump reference: + +- Sectors: `712880` +- Size: `1,459,978,240` bytes +- CRC32: `01b52739` +- MD5: `85a525df1481d0ad67d8761f832dca12` +- SHA-1: `06eb6d15b4d7f90ec0fed9ce9a77db41358d74ed` +- SHA-256 observed consistently: `30098da93f5de9ece8da44f8afdfb85cf9bcdc24d77131221e64e3038a529010` + +## GDR-8163B validation matrix + +| Firmware | OEM/batch note | CDB base | Gate | Seed | Full dump | Redump match | Duration | Avg MiB/h | +|---|---|---:|---:|---|---|---|---:|---:| +| 0L30 | LG, case label 0L23, Germany batch | 0x5E0 | 0x90025021 | OK | OK | Yes | 7387.03 s | 678.55 | +| 0E15 | HP/OEM, ImgBurn `0E15-EC` | 0x5D8 | 0x900247D1 | OK | OK | Yes | 7384.97 s | 678.73 | +| 0B30 | HP/OEM | 0x5E0 | 0x90025030 | OK | OK | Yes | 7269.46 s | 689.52 | +| 0M26 | Lenovo/OEM, Malaysia | 0x5E0 | 0x90024FF7 | OK | OK | Yes | 7241.31 s | 692.20 | +| 0L20 | Common Germany-batch branch | 0x5E0 | 0x90024C8F | OK | OK | Yes | 7250.93 s | 691.28 | +| 0D20 | Germany-batch branch | 0x5E0 | 0x90024AE7 | OK | OK | Yes | 7225.72 s | 693.69 | +| 0L23 | Stage5B reference branch | 0x5E0 | 0x90024D5A | pending final uploaded result | pending | pending | — | — | + +All completed variants produced the same full-disc hashes listed above. + +## GCC-4244N / GCC4244 B103 + +Observed hardware identity: + +- Case label: `B101` +- Live SCSI INQUIRY product: `CDRW/DVD GCC4244` +- Live SCSI INQUIRY revision: `B103` +- Stage5B promoted parser profile: GCC-4244N B103 +- Static CDB-base evidence: `0x894` +- Static gate evidence: `0x900386FB` +- Tokens: `HL;IT;RPC;RPC_JCS3;RPC_SUFFIX` + +FriiDump 0.5.3.3 adds the exact live-INQUIRY alias `GCC4244/B103`, preventing fallback to generic Type3 reporting. Seed retrieval was already observed successful through the legacy Type3 path in about three seconds with Super Smash Bros. Melee; a complete dump remains pending after the firmware capture. + +## Media preflight validation target + +Before `disc_init()` or seed retrieval, FriiDump now: + +1. Issues TEST UNIT READY. +2. Stops immediately with a clear message on `NOT READY / MEDIUM NOT PRESENT` (`02/3A/xx`). +3. Retries transient becoming-ready or unit-attention states for up to 15 seconds. +4. Proceeds to seed retrieval only after ready media is confirmed. + +Expected no-disc output: + +```text +Checking for ready media before disc seed retrieval... No disc present (sense 02/3A/00). Insert a disc and retry. +``` + +Expected ready-disc output: + +```text +Checking for ready media before disc seed retrieval... OK +Retrieving disc seeds, this might take a while... +``` + +## Remaining validation + +- Complete the GDR-8163B 0L23 full dump and compare hashes. +- Re-run GCC4244/B103 with 0.5.3.3 to confirm exact profile selection, then complete and verify the Melee Rev 2 dump. +- Validate GCC-4243N, then GCC-4241N and GCC-4242N error-prone profiles. diff --git a/docs/reports/FRIIDUMP_HLDS_LIVE_VALIDATION_0.5.3.4.md b/docs/reports/FRIIDUMP_HLDS_LIVE_VALIDATION_0.5.3.4.md new file mode 100644 index 0000000..3d6e381 --- /dev/null +++ b/docs/reports/FRIIDUMP_HLDS_LIVE_VALIDATION_0.5.3.4.md @@ -0,0 +1,72 @@ +# FriiDump 0.5.3.4 — HLDS Live Validation Report + +## Scope + +This report records real-hardware GC/Wii dumping validation for the Stage5B-derived HLDS 0xE7 profile work. All listed dump tests are read-only. Static CDB-base and gate addresses are firmware-analysis evidence only; FriiDump does not execute those addresses and does not issue flash, erase, updater, `3B06`, or `3B07` commands. + +## Reference disc: Sonic Mega Collection (US) + +Redump reference: + +- Sectors: `712880` +- Size: `1,459,978,240` bytes +- CRC32: `01b52739` +- MD5: `85a525df1481d0ad67d8761f832dca12` +- SHA-1: `06eb6d15b4d7f90ec0fed9ce9a77db41358d74ed` +- SHA-256 observed consistently: `30098da93f5de9ece8da44f8afdfb85cf9bcdc24d77131221e64e3038a529010` + +## GDR-8163B validation matrix + +| Firmware | OEM/batch note | CDB base | Gate | Seed | Full dump | Redump match | Duration | Avg MiB/h | +|---|---|---:|---:|---|---|---|---:|---:| +| 0L30 | LG, case label 0L23, Germany batch | 0x5E0 | 0x90025021 | OK | OK | Yes | 7387.03 s | 678.55 | +| 0E15 | HP/OEM, ImgBurn `0E15-EC` | 0x5D8 | 0x900247D1 | OK | OK | Yes | 7384.97 s | 678.73 | +| 0B30 | HP/OEM | 0x5E0 | 0x90025030 | OK | OK | Yes | 7269.46 s | 689.52 | +| 0M26 | Lenovo/OEM, Malaysia | 0x5E0 | 0x90024FF7 | OK | OK | Yes | 7241.31 s | 692.20 | +| 0L20 | Common Germany-batch branch | 0x5E0 | 0x90024C8F | OK | OK | Yes | 7250.93 s | 691.28 | +| 0D20 | Germany-batch branch | 0x5E0 | 0x90024AE7 | OK | OK | Yes | 7225.72 s | 693.69 | +| 0L23 | Stage5B reference branch | 0x5E0 | 0x90024D5A | OK | OK | Yes | 7222.19 s | 694.03 | + +All completed variants produced the same full-disc hashes listed above. + +## GCC-4244N / GCC4244 B103 + +Observed hardware identity: + +- Case label: `B101` +- Live SCSI INQUIRY product: `CDRW/DVD GCC4244` +- Live SCSI INQUIRY revision: `B103` +- Stage5B promoted parser profile: GCC-4244N B103 +- Static CDB-base evidence: `0x894` +- Static gate evidence: `0x900386FB` +- Tokens: `HL;IT;RPC;RPC_JCS3;RPC_SUFFIX` + +FriiDump 0.5.3.4 retains the exact live-INQUIRY alias `GCC4244/B103`, preventing fallback to generic Type3 reporting. Seed retrieval was already observed successful through the legacy Type3 path in about three seconds with Super Smash Bros. Melee; a complete dump remains pending after the firmware capture. + +## Media preflight validation target + +The first 0.5.3.3 empty-drive test exposed a false positive on `HL-DT-ST CDRW/DVD GCC4244/B103`: TEST UNIT READY returned GOOD through the USB bridge even though the tray was empty, so seed retrieval was attempted and failed after nine seconds. + +FriiDump 0.5.3.4 corrects this in two layers: + +1. MMC command execution now returns failure for non-GOOD SCSI status; `ignore_errors` suppresses messages only. +2. Media preflight requires both TEST UNIT READY and a valid READ CAPACITY(10) response with more than one 2048-byte logical block. +3. Empty, invalid-capacity, becoming-ready, unit-attention, and explicit `02/3A/xx` no-medium states stop before `disc_init()` and before any vendor seed/cache command. + +Expected no-disc output: + +```text +Checking for ready media before disc seed retrieval... No readable disc present (...). Insert a disc, wait for spin-up, and retry. +``` + +Expected ready-disc output: + +```text +Checking for ready media before disc seed retrieval... OK +Retrieving disc seeds, this might take a while... +``` + +## Remaining validation + +- Re-run GCC4244/B103 with 0.5.3.4 to confirm exact profile selection, then complete and verify the Melee Rev 2 dump. +- Validate GCC-4243N, then GCC-4241N and GCC-4242N error-prone profiles. diff --git a/docs/reports/FRIIDUMP_HLDS_LIVE_VALIDATION_0.5.3.5.md b/docs/reports/FRIIDUMP_HLDS_LIVE_VALIDATION_0.5.3.5.md new file mode 100644 index 0000000..13b068d --- /dev/null +++ b/docs/reports/FRIIDUMP_HLDS_LIVE_VALIDATION_0.5.3.5.md @@ -0,0 +1,86 @@ +# FriiDump 0.5.3.5 — HLDS Live Validation Report + +## Scope + +This report records real-hardware GC/Wii dumping validation for the Stage5B-derived HLDS 0xE7 profile work. All listed dump tests are read-only. Static CDB-base and gate addresses are firmware-analysis evidence only; FriiDump does not execute those addresses and does not issue flash, erase, updater, `3B06`, or `3B07` commands. + +## Reference disc: Sonic Mega Collection (US) + +Redump reference: + +- Sectors: `712880` +- Size: `1,459,978,240` bytes +- CRC32: `01b52739` +- MD5: `85a525df1481d0ad67d8761f832dca12` +- SHA-1: `06eb6d15b4d7f90ec0fed9ce9a77db41358d74ed` +- SHA-256 observed consistently: `30098da93f5de9ece8da44f8afdfb85cf9bcdc24d77131221e64e3038a529010` + +## GDR-8163B validation matrix + +| Firmware | OEM/batch note | CDB base | Gate | Seed | Full dump | Redump match | Duration | Avg MiB/h | +|---|---|---:|---:|---|---|---|---:|---:| +| 0L30 | LG, case label 0L23, Germany batch | 0x5E0 | 0x90025021 | OK | OK | Yes | 7387.03 s | 678.55 | +| 0E15 | HP/OEM, ImgBurn `0E15-EC` | 0x5D8 | 0x900247D1 | OK | OK | Yes | 7384.97 s | 678.73 | +| 0B30 | HP/OEM | 0x5E0 | 0x90025030 | OK | OK | Yes | 7269.46 s | 689.52 | +| 0M26 | Lenovo/OEM, Malaysia | 0x5E0 | 0x90024FF7 | OK | OK | Yes | 7241.31 s | 692.20 | +| 0L20 | Common Germany-batch branch | 0x5E0 | 0x90024C8F | OK | OK | Yes | 7250.93 s | 691.28 | +| 0D20 | Germany-batch branch | 0x5E0 | 0x90024AE7 | OK | OK | Yes | 7225.72 s | 693.69 | +| 0L23 | Stage5B reference branch | 0x5E0 | 0x90024D5A | OK | OK | Yes | 7222.19 s | 694.03 | + +All completed variants produced the same full-disc hashes listed above. + +## GCC-4244N / GCC4244 B103 + +Observed hardware identity: + +- Case label: `B101` +- Live SCSI INQUIRY product: `CDRW/DVD GCC4244` +- Live SCSI INQUIRY revision: `B103` +- Stage5B promoted parser profile: GCC-4244N B103 +- Static CDB-base evidence: `0x894` +- Static gate evidence: `0x900386FB` +- Tokens: `HL;IT;RPC;RPC_JCS3;RPC_SUFFIX` + +FriiDump 0.5.3.5 marks the exact live-INQUIRY alias `GCC4244/B103` as `known_supported_profile_hardening_live_validated`, preventing fallback to generic Type3 reporting. A complete Sonic Mega Collection (US) run using the exact promoted profile produced: + +- Media preflight: `OK` +- Seed retrieval: `OK`, `4.00` seconds +- Full dump: `OK` +- STOP UNIT: `OK` +- Sectors: `712880` +- Duration: `2329.16` seconds +- Observed average: `2152.04 MiB/h` over `1392.34 MiB` +- CRC32: `01b52739` +- MD5: `85a525df1481d0ad67d8761f832dca12` +- SHA-1: `06eb6d15b4d7f90ec0fed9ce9a77db41358d74ed` +- SHA-256: `30098da93f5de9ece8da44f8afdfb85cf9bcdc24d77131221e64e3038a529010` + +The hashes match the Sonic Mega Collection Redump reference. The case label reads `B101`, while live SCSI INQUIRY reports `B103`; the live identity is authoritative for profile matching. + +## Media preflight validation target + +The first 0.5.3.3 empty-drive test exposed a false positive on `HL-DT-ST CDRW/DVD GCC4244/B103`: TEST UNIT READY returned GOOD through the USB bridge even though the tray was empty, so seed retrieval was attempted and failed after nine seconds. + +FriiDump 0.5.3.4 corrects this in two layers: + +1. MMC command execution now returns failure for non-GOOD SCSI status; `ignore_errors` suppresses messages only. +2. Media preflight requires both TEST UNIT READY and a valid READ CAPACITY(10) response with more than one 2048-byte logical block. +3. Empty, invalid-capacity, becoming-ready, unit-attention, and explicit `02/3A/xx` no-medium states stop before `disc_init()` and before any vendor seed/cache command. + +Expected no-disc output: + +```text +Checking for ready media before disc seed retrieval... No readable disc present (...). Insert a disc, wait for spin-up, and retry. +``` + +Expected ready-disc output: + +```text +Checking for ready media before disc seed retrieval... OK +Retrieving disc seeds, this might take a while... +``` + +## Remaining validation + +- Validate GCC-4243N next, then GCC-4241N and GCC-4242N error-prone profiles. +- Preserve exact live INQUIRY strings and firmware revisions for every tested drive; do not infer them from case labels. diff --git a/docs/reports/FRIIDUMP_HLDS_PROFILE_UPDATE_V60.md b/docs/reports/FRIIDUMP_HLDS_PROFILE_UPDATE_V60.md new file mode 100644 index 0000000..f5b718a --- /dev/null +++ b/docs/reports/FRIIDUMP_HLDS_PROFILE_UPDATE_V60.md @@ -0,0 +1,25 @@ +# FriiDump HLDS Profile Update v60 + +Version: FriiDump 0.5.3.4 + +## Media-preflight correction + +The 0.5.3.3 empty-drive test on `HL-DT-ST CDRW/DVD GCC4244/B103` showed that TEST UNIT READY could return GOOD through the USB bridge with an empty tray. FriiDump therefore proceeded to seed retrieval and failed after nine seconds. + +0.5.3.4 fixes that behavior: + +- Windows SCSI pass-through treats non-GOOD SCSI status as command failure even when `DeviceIoControl` succeeds. +- `ignore_errors` suppresses diagnostics only; it no longer converts failed MMC commands into success. +- Media preflight requires both TEST UNIT READY and a valid READ CAPACITY(10) response with more than one 2048-byte logical block. +- Empty or invalid-capacity media stops before `disc_init()` and before HLDS vendor seed/cache reads. + +## Validation sync + +The consolidated validation report now includes the completed GDR-8163B 0L23 Sonic Mega Collection dump: + +- CRC32: `01b52739` +- MD5: `85a525df1481d0ad67d8761f832dca12` +- SHA-1: `06eb6d15b4d7f90ec0fed9ce9a77db41358d74ed` +- SHA-256: `30098da93f5de9ece8da44f8afdfb85cf9bcdc24d77131221e64e3038a529010` +- Duration: `7222.19` seconds +- Average: `694.03 MiB/h` diff --git a/docs/reports/FRIIDUMP_HLDS_PROFILE_UPDATE_V61.md b/docs/reports/FRIIDUMP_HLDS_PROFILE_UPDATE_V61.md new file mode 100644 index 0000000..2646ebd --- /dev/null +++ b/docs/reports/FRIIDUMP_HLDS_PROFILE_UPDATE_V61.md @@ -0,0 +1,34 @@ +# FriiDump HLDS Profile Update v61 + +Version: FriiDump 0.5.3.5 + +## Purpose + +Promote the live `HL-DT-ST CDRW/DVD GCC4244/B103` profile from analysis-backed profile hardening to real-hardware live-validated status after a complete Redump-matching GameCube dump. + +## GCC-4244N B103 validation + +- Case label: `B101` +- Live SCSI INQUIRY: `HL-DT-ST/CDRW/DVD GCC4244/B103` +- Profile: `GCC-4244N B103 promoted E7 parser profile` +- Tokens: `HL;IT;RPC;RPC_JCS3;RPC_SUFFIX` +- Static CDB evidence: `0x894` +- Static gate evidence: `0x900386FB` +- Media preflight: OK +- Seed retrieval: OK in 4 seconds +- Full Sonic Mega Collection dump: OK +- STOP UNIT: OK +- Duration: 2329.16 seconds +- Observed average: 2152.04 MiB/h +- Hash result: matches Redump + +Static firmware addresses remain reporting evidence only. FriiDump does not execute those addresses or emit firmware flash/write/update commands. + +## Next validation target + +GCC-4243N. Start with the live INQUIRY identity and revision reported by the drive. The current exact Stage5B profiles are: + +- GCC-4243N `0000`: CDB `0x884`, gate `0x90037929` +- GCC-4243N `1.08`: CDB `0x880`, gate `0x90036813` + +If the live drive reports a different product spelling or revision, preserve the log and firmware-dump it before adding an exact alias. diff --git a/example commands.txt b/example commands.txt new file mode 100644 index 0000000..fb4d4fa --- /dev/null +++ b/example commands.txt @@ -0,0 +1,12 @@ +# Standard DVD dump +.\friidump.exe -d e: -D -i dvd.iso + +# Xbox redump-style ISO + .pfi.bin/.dmi.bin/.redump.json +.\friidump.exe -d e: -T 4 -i xbox_redump.iso + +# Xbox game-partition XISO +.\friidump.exe -d e: -T 4 -X xbox_game.xiso + +# Gamecube +.\friidump.exe -d e: -T 0 -a +.\friidump.exe -d f: -T 0 -8 -s -a \ No newline at end of file diff --git a/friidump-handoff.md b/friidump-handoff.md new file mode 100644 index 0000000..93f3c75 --- /dev/null +++ b/friidump-handoff.md @@ -0,0 +1,101 @@ + +# FriiDump / GC-Wii Work Handoff + +**Purpose:** Continue the current FriiDump + Project Frankenstein GameCube/Wii support work without requiring the user to re-explain project context. + +## Scope + +This is **not** a project specification. It is a handoff for the next engineering agent. + +## Current Objective + +Use Project Frankenstein Stage5B firmware analysis to improve FriiDump's HLDS GameCube/Wii support, then validate every supported profile on real hardware. + +The workflow is: + +1. Recover parser information from firmware (Stage5B). +2. Integrate into FriiDump. +3. Validate on owned hardware. +4. Feed results back into the analyzer. + +## Major accomplishments + +- Added analyzer-derived HLDS profile support. +- Added media preflight before vendor seed retrieval. +- Added STOP UNIT after successful dumps. +- Added profile reporting. +- Added live INQUIRY alias support. +- Validated multiple GDR-8163B firmware revisions on real hardware. +- Validated GCC-4244 B101 and B103 on real hardware. +- Validated GCC-4243N A102 on real hardware. +- Captured canonical firmware dumps for GCC-4243N A102 and GCC-4244 B101. + +## Important engineering observations + +- Trust live firmware/INQUIRY over stickers. +- Preserve firmware provenance. +- Prefer exact Stage5B-derived parser profiles over legacy Type3 fallbacks. +- Hardware validates analyzer discoveries. + +## Current hardware validation status + +### Validated + +**GDR-8163B** +- 0B30 +- 0D20 +- 0E15 +- 0L20 +- 0L23 +- 0L30 +- 0M26 + +**GCC-4244** +- B101 +- B103 + +**GCC-4243N** +- A102 + +## Firmware dumps completed + +- GCC-4243N A102 (canonical) +- GCC-4244 B101 (canonical) + +These should be analyzed by Stage5B to determine whether new promoted parser profiles or aliases are appropriate. + +## Owned hardware inventory (validation targets) + +- GDR-8163B x15 +- GDR-8081N x3 +- GCC-4241N x5 +- GCC-4243N x5 +- GCC-4244N x9 +- GCC-4160N x3 +- GWA-4164B x1 +- GSA-H73N x1 +- GH22NS30 x1 + +Do **not** ask the user to test drives outside this inventory. + +## Immediate next work + +1. Analyze GCC-4243N A102 firmware. +2. Analyze GCC-4244 B101 firmware. +3. Update FriiDump with any new aliases/promoted profiles. +4. Continue owned-hardware validation in this order: + - GCC-4241N + - GCC-4160N + - GWA-4164B + - GSA-H73N + - GH22NS30 + +GDR-8081N remains a separate research effort and should not be modified until its firmware evidence supports doing so. + +## Notes for the next agent + +- Avoid re-implementing already completed features (media preflight, STOP UNIT, profile reporting). +- Use analyzer results as the primary source of truth. +- Prefer evidence over assumptions. +- When suggesting validation work, consult the user's current hardware inventory first. +- The long-term architectural document is PFES; this file is only a transition handoff. diff --git a/libfriidump.pc.in b/libfriidump.pc.in new file mode 100644 index 0000000..43cd7c1 --- /dev/null +++ b/libfriidump.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: LibFriiDump +Description: Library to dump Nintendo GameCube/Wii discs +Version: @VERSION@ +URL: http://wii.console-tribe.com +Libs: -L${libdir} -lfriidump +Cflags: -I${includedir}/libfriidump diff --git a/libfriidump/CMakeLists.txt b/libfriidump/CMakeLists.txt new file mode 100644 index 0000000..1356bab --- /dev/null +++ b/libfriidump/CMakeLists.txt @@ -0,0 +1,118 @@ +# Create a library called "Hello" which includes the source file "hello.cxx". +# The extension is already found. Any number of sources could be listed here. +add_library ( + friidumplib + ${libfriidump_type} + #SHARED + #STATIC + + brickblocker.h + brickblocker.c + byteorder.h + constants.h + disc.h + disc.c + dumper.h + dumper.c + dvd_drive.h + dvd_drive.c + hitachi.c + ecma-267.h + ecma-267.c + lite-on.c + misc.h + misc.c + renesas.c + rs.h + rs.c + unscrambler.h + unscrambler.c + vanilla_2064.c + vanilla_2384.c + win32compat.h + win32compat.c + xbox_ref_bridge.h + xbox_ref_bridge.c + xbox_ref/xbox_ref_log.c +) + +if (WIN32) + target_sources (friidumplib PRIVATE + xbox_ref/utils.c + xbox_ref/unlock.c + xbox_ref/rc4.c + xbox_ref/xbox_ref_sha1.c + ) + target_include_directories (friidumplib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/xbox_ref) +endif (WIN32) + +set_target_properties (friidumplib PROPERTIES OUTPUT_NAME "friidump") + +include_directories ( + ${FriiDump_SOURCE_DIR}/libmultihash +) + +# Make sure the linker can find the Hello library once it is built. +link_directories ( + ${FriiDump_BINARY_DIR}/libmultihash +) + +# Link the executable to the Hello library. +target_link_libraries ( + friidumplib + + multihashlib +) + +# Before making a release, the LTVERSION string should be modified. +# The string is of the form CURRENT:REVISION:AGE. +# +# CURRENT (C) +# The most recent interface number that this library implements. +# +# REVISION (R) +# The implementation number that this library implements. +# +# AGE (A) +# The difference between the newest and oldest interfaces that this +# library implements. In other works, the library implements all the +# interface numbers in the range from number 'CURRENT - AGE' to +# 'CURRENT'. +# +# This means that: +# +# - If interfaces have been changed or added, but binary compatibility has +# been preserved, change to C+1:0:A+1 +# +# - If binary compatibility has been broken (eg removed or changed +# interfaces) change to C+1:0:0 +# +# - If the interface is the same as the previous version, change to C:R+1:A +# +#set_target_properties (friidumplib PROPERTIES SOVERSION 1.0.0) + + +# Windows stuff to correctly build DLL or static library +#get_target_property (libfriidump_type friidumplib TYPE) +if (WIN32) + if (libfriidump_type STREQUAL "SHARED") +# MESSAGE ("Building libfriidump DLL") + ADD_DEFINITIONS (-DFRIIDUMPLIB_BUILD_DLL) + set_target_properties (friidumplib PROPERTIES DEFINE_SYMBOL FRIIDUMPLIB_EXPORTS) + + install ( + TARGETS friidumplib + RUNTIME DESTINATION / + #ARCHIVE DESTINATION lib + ) + endif (libfriidump_type STREQUAL "SHARED") +else (WIN32) + # Install stuff, only if a shared library is being built + if (libfriidump_type STREQUAL "SHARED") + install ( + TARGETS friidumplib + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib/static + ) + endif (libfriidump_type STREQUAL "SHARED") +endif (WIN32) diff --git a/libfriidump/brickblocker.c b/libfriidump/brickblocker.c new file mode 100644 index 0000000..0280e39 --- /dev/null +++ b/libfriidump/brickblocker.c @@ -0,0 +1,22 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/* wiidevel@stacktic.org */ diff --git a/libfriidump/brickblocker.h b/libfriidump/brickblocker.h new file mode 100644 index 0000000..884d583 --- /dev/null +++ b/libfriidump/brickblocker.h @@ -0,0 +1,20 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ diff --git a/libfriidump/byteorder.h b/libfriidump/byteorder.h new file mode 100644 index 0000000..b912ab3 --- /dev/null +++ b/libfriidump/byteorder.h @@ -0,0 +1,77 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "misc.h" +#include + +/*************************** BYTE SWAPPING MACROS ***************************/ +// WORDS_BIGENDIAN is defined by the AC_C_BIGENDIAN autoconf macro, in case. On Windows please (un)define manually. +#ifdef WORDS_BIGENDIAN //!< Swapping macros on big endian systems (Where host b. o. = network b. o.) + +/* This machine's order to big endian */ +#define bo_my2big_16(a) ((u_int16_t)(a)) +#define bo_my2big_32(a) ((u_int32_t)(a)) + +/* This machine's order to little endian */ +#define bo_my2little_16(a) ( \ + ((((u_int16_t) (a)) & 0x00FF) << 8) + \ + (((u_int16_t) (a)) >> 8) \ + ) +#define bo_my2little_32(a) ( \ + ((((u_int32_t) (a)) & 0x000000FF) << 24) + \ + ((((u_int32_t) (a)) & 0x0000FF00) << 8) + \ + ((((u_int32_t) (a)) & 0x00FF0000) >> 8) + \ + (((u_int32_t) (a)) >> 24) \ + ) +#else //!< Swapping macros on little endian systems + /* This machine's order to big endian */ +#define bo_my2big_16(a) ( \ + ((((u_int16_t) (a)) & 0x00FF) << 8) + \ + (((u_int16_t) (a)) >> 8) \ + ) +#define bo_my2big_32(a) ( \ + ((((u_int32_t) (a)) & 0x000000FF) << 24) + \ + ((((u_int32_t) (a)) & 0x0000FF00) << 8) + \ + ((((u_int32_t) (a)) & 0x00FF0000) >> 8) + \ + (((u_int32_t) (a)) >> 24) \ + ) + +/* This machine's order to little endian */ +#define bo_my2little_16(a) ((u_int16_t)(a)) +#define bo_my2little_32(a) ((u_int32_t)(a)) +#endif + +/* These will be handy */ +/* Big endian to this machine's order */ +#define bo_big2my_16(x) bo_my2big_16(x) +#define bo_big2my_32(x) bo_my2big_32(x) + +/* Little endian to this machine's order */ +#define bo_little2my_16(x) bo_my2little_16(x) +#define bo_little2my_32(x) bo_my2little_32(x) + +/* There are the most useful ones */ +#define my_htons(x) bo_my2big_16(x) +#define my_htonl(x) bo_my2big_32(x) +#define my_ntohs(x) my_htons(x) +#define my_ntohl(x) my_htonl(x) + +/************************ END OF BYTE SWAPPING MACROS ***********************/ diff --git a/libfriidump/constants.h b/libfriidump/constants.h new file mode 100644 index 0000000..fcf5152 --- /dev/null +++ b/libfriidump/constants.h @@ -0,0 +1,46 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/*! \file + * \brief Nintendo GameCube/Wii disc geometry constants. + * + * This file contains constants that describe the general layout of Nintendo GameCube/Wii discs and that can be used throughout the whole program. + */ + +#ifndef CONSTANTS_H_INCLUDED +#define CONSTANTS_H_INCLUDED + +/*! \brief Size of a scrambled sector */ +#define RAW_SECTOR_SIZE 2064 + +/*! \brief Size of an unscrambled sector */ +#define SECTOR_SIZE 2048 + +/*! \brief Number of sectors in a block */ +#define SECTORS_PER_BLOCK 16 + +/*! \brief Size of a scrambled block */ +#define RAW_BLOCK_SIZE (RAW_SECTOR_SIZE * SECTORS_PER_BLOCK) + +/*! \brief Size of an unscrambled block */ +#define BLOCK_SIZE (SECTOR_SIZE * SECTORS_PER_BLOCK) + +#endif diff --git a/libfriidump/disc.c b/libfriidump/disc.c new file mode 100644 index 0000000..c132d59 --- /dev/null +++ b/libfriidump/disc.c @@ -0,0 +1,3358 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/*! \file + * \brief Analyser and dumper for Nintendo GameCube/Wii discs. + * + * The functions in this file can be used to retrieve information about a Nintendo GameCube/Wii optical disc. Information is both structural (i.e.: Number of + * sectors, partitions, etc) and game-related (i.e.: Game Title, version, etc). This is the main object that should be used by applications. + * + * Most of the disc structure information used in this file comes from http://www.gc-linux.org/docs/yagcd.html and + * http://www.wiili.org/index.php/GameCube_Optical_Disc . + */ + +#include "misc.h" +#include +#include +#include +#include +#ifdef WIN32 +#include +#else +#include +#endif +//#include +#include "constants.h" +#include "byteorder.h" +#include "disc.h" +#include "dvd_drive.h" + +static void hlds_e7_visible_probe_log (const char *fmt, ...) { + static bool started = false; + va_list ap; + if (!started) { + fprintf (stderr, "\n"); + started = true; + } + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fprintf (stderr, "\n"); + fflush (stderr); +} +#include "unscrambler.h" + +// #define cachedebug(...) debug (__VA_ARGS__); +#define cachedebug(...) + + +/* Cache always deals with 16-sector blocks. All numbers refer to the 16-sector blocks */ +#define DISC_MINIMUM_CACHE_SIZE 5 +#define DISC_DEFAULT_CACHE_SIZE 40 +#define CACHE_ENTRY_INVALID ((u_int32_t) -1) + + +#define DISC_GAMECUBE_SECTORS_NO 0x0AE0B0 /* 712880 */ +#define DISC_WII_SECTORS_NO_SL 0x230480 /* 2294912 */ +#define DISC_WII_SECTORS_NO_DL 0x3F69C0 /* 4155840 */ +#define DISC_XBOX_GDR8050L_UNLOCKED_SECTORS_NO 0x345B60 /* 3431264 */ + + +#define MAX_READ_RETRIES 5 + +#define DEFAULT_READ_METHOD 0 +#define DEFAULT_READ_SECTOR disc_read_sector_0 + + +typedef int (*disc_read_sector_func) (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata); + +u_int8_t buf[1024*1024*4]; +u_int8_t buf_unscrambled[1024*1024*4]; + +//struct timeval tim; +//double t1, t2; + +/*! \brief A structure that represents a Nintendo GameCube/Wii optical disc. + */ +struct disc_s { + dvd_drive *dvd; //!< The structure for the DVD-drive the disc is inserted in. + disc_type type; //!< The disc type. + char system_id; //!< A letter identifying the target system. + char game_id[2 + 1]; //!< Two letters identifying the game. + disc_region region; //!< The disc region. + char maker[3]; //!< Two letters identifying the maker of the game. + u_int8_t version; //!< A number identifying the game version. + char *version_string; //!< The same as version, in a more human-understandable format. + char *title; //!< The game title. + bool has_update; //!< True if the game contains a system update (Only possible for Wii discs). + u_int32_t sectors_no; //!< The number of sectors of the disc. + u_int32_t layerbreak; //!< For dual-layer DVDs. + + u_int32_t sec_disc; + u_int32_t sec_mem; + u_int32_t max_cnt; + u_int32_t max_blk; + + /* Read function & stuff */ + int command; //!< Buffer access command ID. + int read_method; //!< The read method ID. +// int def_read_method; //!< Default read method ID. + disc_read_sector_func read_sector; //!< The actual function that will be used to perform read operations, corresponding to read_method. + bool unscrambling; //!< If true, raw data read from the disc will be unscrambled to assure it is error-free. Disabling this is only useful for raw performance tests. + unscrambler *u; //!< The unscrambler structure that will be used to perform the unscrambling. + + /* Read cache */ + u_int32_t cache_size; //!< The number of blocks that will be cached when read. + bool hlds_e7_read_schedule_logged; //!< True once the selected HLDS 0xE7 read schedule has been logged for this run. + u_int8_t **raw_cache; //!< Memory area for raw sectors cache. + u_int8_t **cache; //!< Memory area for unscrambled sectors cache. + u_int32_t *cache_map; //!< Data structure used by the caching system to know which blocks are in memory. +}; + + +static void disc_cache_init (disc *d, u_int32_t size) { + u_int32_t i; + + if (size < DISC_MINIMUM_CACHE_SIZE) { + error ("Invalid cache size %u (must be >= %u)", size, DISC_MINIMUM_CACHE_SIZE); + exit (3); + } else { + d -> cache_size = size; + d -> cache = (u_int8_t **) malloc (sizeof (u_int8_t *) * size); + d -> raw_cache = (u_int8_t **) malloc (sizeof (u_int8_t *) * size); + for (i = 0; i < size; i++) { + d -> cache[i] = (u_int8_t *) malloc (sizeof (u_int8_t) * BLOCK_SIZE); + d -> raw_cache[i] = (u_int8_t *) malloc (sizeof (u_int8_t) * RAW_BLOCK_SIZE); + } + + d -> cache_map = (u_int32_t *) malloc (sizeof (u_int32_t) * size); + for (i = 0; i < size; i++) + d -> cache_map[i] = CACHE_ENTRY_INVALID; + } + + return; +} + + +static void disc_cache_destroy (disc *d) { + u_int32_t i; + + my_free (d -> cache_map); + + for (i = 0; i < d -> cache_size; i++) { + my_free (d -> cache[i]); + my_free (d -> raw_cache[i]); + } + my_free (d -> cache); + my_free (d -> raw_cache); + d -> cache_size = 0; + + return; +} + +static void disc_cache_clear (disc *d) { + u_int32_t i; + + if (!d || !d -> cache_map) + return; + for (i = 0; i < d -> cache_size; i++) + d -> cache_map[i] = CACHE_ENTRY_INVALID; +} + + +void disc_cache_add_block (disc *d, u_int32_t block, u_int8_t *data, u_int8_t *rawdata) { + u_int32_t pos; + u_int32_t cnt; + + pos = block % d -> cache_size; + //uniform unscrambled output + memcpy (d -> cache[pos], data, BLOCK_SIZE); + if (d -> type == DISC_TYPE_DVD || d -> type == DISC_TYPE_XBOX) { + for (cnt = 0; cnt < SECTORS_PER_BLOCK; cnt++) { + memcpy (rawdata+(cnt*RAW_SECTOR_SIZE)+12, data+(cnt*SECTOR_SIZE), SECTOR_SIZE); + } + } else { + for (cnt = 0; cnt < SECTORS_PER_BLOCK; cnt++) { + memcpy (rawdata+(cnt*RAW_SECTOR_SIZE)+6, data+(cnt*SECTOR_SIZE), SECTOR_SIZE); + } + } + memcpy (d -> raw_cache[pos], rawdata, RAW_BLOCK_SIZE); + d -> cache_map[pos] = block; + + cachedebug ("Cached block %u (sectors %u-%u) at position %u", block, block * SECTORS_PER_BLOCK, (block + 1) * SECTORS_PER_BLOCK - 1, pos); + + return; +} + + +static bool disc_cache_lookup_block (disc *d, u_int32_t block, u_int8_t **data, u_int8_t **rawdata) { + u_int32_t pos; + bool out; + + pos = block % d -> cache_size; + + if (d -> cache_map[pos] == block) { + cachedebug ("Cache HIT for block %u", block); + if (data) + *data = d -> cache[pos]; + if (rawdata) + *rawdata = d -> raw_cache[pos]; + out = true; + } else { + cachedebug ("Cache MISS for block %u", block); + if (data) + *data = NULL; + if (rawdata) + *rawdata = NULL; + out = false; + } + + return (out); +} + + +static int disc_read_sector_generic (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata, u_int32_t method) { + bool out; + u_int32_t start_block; + int ret, retry; + u_int32_t step, cnt, max_cnt, max_blk; + u_int32_t block_len, block_size, _block_size, last_block_size, block_cnt; +//fprintf (stdout,"disc_read_sector_%d", method); + start_block = sector_no / SECTORS_PER_BLOCK; + + out = false; + step = d->sec_mem; + max_cnt = d->max_cnt; + max_blk = d->max_blk; + + block_size = step*2064; + last_block_size = block_size; + block_len = 1; + if (block_size > 27 * 2064) { + block_len = block_size / (27*2064); + if (block_size % (27*2064) != 0) block_len += 1; + block_size = 27*2064; + last_block_size = (step*2064) - (27*2064*(block_len-1)); + } + _block_size=block_size; + + for (retry = 0; !out && retry < MAX_READ_RETRIES; retry++) { + /* Assume everything will turn out well */ + out = true; + + //Streaming read + if (retry < 3) { + cnt=0; + while (cnt <= max_cnt){ + + _block_size=block_size; + if (method == 0 || method == 1 || method == 4) { + if (sector_no+(cnt*step) +992 +16 <= d -> sectors_no) //smaller than last sector + dvd_read_sector_dummy (d -> dvd, sector_no+(cnt*step) +992, 16, NULL, NULL, 0); + else if (sector_no+(cnt*step) -992 >= 0) //larger than first sector + dvd_read_sector_dummy (d -> dvd, sector_no+(cnt*step) -992, 16, NULL, NULL, 0); + else dvd_flush_cache_READ12 (d -> dvd, sector_no+(cnt*step), NULL); + } + + if (method == 0 || method == 2 || method == 5) dvd_flush_cache_READ12 (d -> dvd, sector_no+(cnt*step), NULL); + if (method == 0 || method == 1 || method == 2 || method == 3) ret = dvd_read_sector_dummy (d -> dvd, sector_no+(cnt*step), d->sec_disc, NULL, &buf_unscrambled[0], 2064*step); + if (method == 4 || method == 5 || method == 6) ret = dvd_read_streaming (d -> dvd, sector_no+(cnt*step), d->sec_disc, NULL, &buf_unscrambled[0], 2064*step); + if (ret >= 0) { + for (block_cnt=0; block_cnt dvd, block_cnt*27*2064, 1, _block_size, &buf[(cnt*(2064 * step))+(block_cnt*27*2064)]) < 0) { + error ("Memdump failed"); + //retry = MAX_READ_RETRIES; /* Well, if this fails going on is useless */ //no it's not! + out = false; + break; + } + if (block_cnt==block_len-1) _block_size = last_block_size; + } + if (!out) break; + //do this check only on 1st layer + else if (((buf[cnt*(2064*step)] & 1) == 0) && ((buf[cnt*(2064*step)+1]<<16)+(buf[cnt*(2064*step)+2]<<8)+(buf[cnt*(2064*step)+3]) != 0x30000 + sector_no+(cnt*step))) { + out = false; + break; + } + else cnt += 1; + } else { + error ("dvd_read_streaming() failed with %d", ret); + out = false; + break; + } + + } + + if (cnt < max_cnt) out = false; + else { +#ifdef DEBUG + if (d -> unscrambling) { +#endif + /* Try to unscramble all data to see if EDC fails */ + //for(cnt=0; cnt <= 4; cnt++) { + for(cnt=max_blk; cnt--;) { + if (!unscrambler_unscramble_16sectors (d -> u, sector_no+(cnt*16), &buf[cnt*(2064*16)], &buf_unscrambled[cnt*(2048*16)])) + out = false; + } +#ifdef DEBUG + } +#endif + } + if (out) { + /* If data were unscrambled correctly, add them to the cache */ + //for(cnt = 0; cnt <= 4; cnt++) { + for(cnt=max_blk; cnt--;) { + disc_cache_add_block (d, start_block+cnt, &buf_unscrambled[cnt*(2048*16)], &buf[cnt*(2064*16)]); + } + } + } //if (retry < 3) + + //Simple read on 4rth try + else { + if (sector_no +992 +16 <= d -> sectors_no) //smaller than last sector + dvd_read_sector_dummy (d -> dvd, sector_no +992, 16, NULL, NULL, 0); + else if (sector_no -992 >= 0) //larger than first sector + dvd_read_sector_dummy (d -> dvd, sector_no -992, 16, NULL, NULL, 0); + else dvd_flush_cache_READ12 (d -> dvd, sector_no, NULL); + + dvd_flush_cache_READ12 (d -> dvd, sector_no, NULL); + ret = dvd_read_sector_dummy (d -> dvd, sector_no, SECTORS_PER_BLOCK, NULL, NULL, 0); + if (ret >= 0) { + if (dvd_memdump (d -> dvd, 0, 1, RAW_BLOCK_SIZE, buf) < 0) { + error ("Memdump failed"); + //retry = MAX_READ_RETRIES; /* Well, if this fails going on is useless */ + out = false; + } + else if ( ((*(buf) & 1) == 0) && ((*(buf+1)<<16)+(*(buf+2)<<8)+(*(buf+3)) != 0x30000+sector_no) ) out = false; + else { +#ifdef DEBUG + if (d -> unscrambling) { +#endif + /* Try to unscramble all data to see if EDC fails */ + if (!unscrambler_unscramble_16sectors (d -> u, sector_no, buf, buf_unscrambled)) + out = false; +#ifdef DEBUG + } +#endif + } + if (out) { + /* If data were unscrambled correctly, add them to the cache */ + disc_cache_add_block (d, start_block, buf_unscrambled, buf); + } + } else { + error ("dvd_read_sector_dummy() failed with %d", ret); + out = false; + } + } //else + } //for + + if (!out) + error ("Too many retries, giving up"); + + return (out); +} + + +static int disc_read_sector_xbox (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) { + bool out; + u_int32_t start_block, block_start, sectors_to_read; + u_int8_t readbuf[BLOCK_SIZE]; + u_int8_t rawbuf[RAW_BLOCK_SIZE]; + + (void) data; + (void) rawdata; + + start_block = sector_no / SECTORS_PER_BLOCK; + block_start = start_block * SECTORS_PER_BLOCK; + if (block_start >= d -> sectors_no) + return false; + + sectors_to_read = SECTORS_PER_BLOCK; + if (block_start + sectors_to_read > d -> sectors_no) + sectors_to_read = d -> sectors_no - block_start; + + memset (readbuf, 0, sizeof (readbuf)); + memset (rawbuf, 0, sizeof (rawbuf)); + + out = dvd_read_10 (d -> dvd, block_start, sectors_to_read, NULL, readbuf, sizeof (readbuf)) >= 0; + if (out) + disc_cache_add_block (d, start_block, readbuf, rawbuf); + else + error ("Xbox READ(10) failed at sector %u", block_start); + + return out; +} + + +///////////////////////////// General ///////////////////////////// +static int disc_read_sector_0 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) { + return disc_read_sector_generic (d, sector_no, data, rawdata, 0); +} + + + +////////////////////////// Non-Streaming ////////////////////////// +static int disc_read_sector_1 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) { + return disc_read_sector_generic (d, sector_no, data, rawdata, 1); + +} + +static int disc_read_sector_2 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) { + return disc_read_sector_generic (d, sector_no, data, rawdata, 2); + +} + + +static int disc_read_sector_3 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) { + return disc_read_sector_generic (d, sector_no, data, rawdata, 3); + +} + + + +//////////////////////////// Streaming //////////////////////////// +static int disc_read_sector_4 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) { + return disc_read_sector_generic (d, sector_no, data, rawdata, 4); +} + + +static int disc_read_sector_5 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) { + return disc_read_sector_generic (d, sector_no, data, rawdata, 5); +} + + +static int disc_read_sector_6 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) { + return disc_read_sector_generic (d, sector_no, data, rawdata, 6); +} + + + +///////////////////////////// Hitachi ///////////////////////////// +static int disc_read_sector_7 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) { + bool out; + u_int32_t start_block; + int j, ret, retry; + u_int8_t buf[5][16 * 2064]; + u_int8_t buf_unscrambled[5][16 * 2048]; +//fprintf (stdout,"disc_read_sector_7"); + start_block = sector_no / SECTORS_PER_BLOCK; + + out = false; + for (retry = 0; !out && retry < MAX_READ_RETRIES; retry++) { + /* Assume everything will turn out well */ + out = true; + + if (retry > 0) { + warning ("Read retry %d for sector %u", retry, sector_no); + + /* Try to reset in-memory data by seeking to a distant sector */ +// if (sector_no > 1000) +// dvd_read_sector_streaming (d -> dvd, 0, NULL, NULL, 0); +// else +// dvd_read_sector_streaming (d -> dvd, 1500, NULL, NULL, 0); + if (sector_no +992 +16 <= d -> sectors_no) //smaller than last sector + dvd_read_sector_dummy (d -> dvd, sector_no +992, 16, NULL, NULL, 0); + else if (sector_no -992 >= 0) //larger than first sector + dvd_read_sector_dummy (d -> dvd, sector_no -992, 16, NULL, NULL, 0); + else dvd_flush_cache_READ12 (d -> dvd, sector_no, NULL); + } + + if ((ret = dvd_read_sector_streaming (d -> dvd, sector_no, NULL, NULL, 0)) >= 0) { + for (j = 0; j < 5 && sector_no + j * 16 < d -> sectors_no && out; j++) { + if (dvd_memdump (d -> dvd, 0 + (j * 16 * 2064), 1, 16 * 2064, buf[j]) < 0) { /* Dumping in a single block is faster */ + error ("Memdump failed"); + out = false; + retry = MAX_READ_RETRIES; /* Well, if this fails going on is useless */ + } else { +#ifdef DEBUG + if (d -> unscrambling) { +#endif + /* Try to unscramble all data to see if EDC fails */ + if (!unscrambler_unscramble_16sectors (d -> u, sector_no + (j * 16), buf[j], buf_unscrambled[j])) + out = false; +#ifdef DEBUG + } +#endif + } + } + + if (out) { + /* It seems all data was unscrambled correctly, so cache them out */ + for (j = 0; j < 5 && sector_no + j * 16 < d -> sectors_no; j++) + disc_cache_add_block (d, start_block + j, buf_unscrambled[j], buf[j]); + + } + } else { + error ("dvd_read_sector_streaming() failed with %d", ret); + out = false; + } + } + + if (!out) + error ("Too many retries, giving up"); + + return (out); +} + + + +static bool disc_read_sector_8_split_recover_block (disc *d, u_int32_t block_sector) { + static const int chunk_sizes[] = { 8, 4, 2, 1 }; + bool out; + int c, chunk_len, chunk_start, k, ret; + u_int32_t ram_offset, block_no; + u_int8_t *sect; + u_int8_t raw_block[RAW_BLOCK_SIZE]; + u_int8_t iso_block[BLOCK_SIZE]; + u_int8_t readbuf[BLOCK_SIZE]; + + block_no = block_sector / SECTORS_PER_BLOCK; + + for (c = 0; c < (int) (sizeof (chunk_sizes) / sizeof (chunk_sizes[0])); c++) { + chunk_len = chunk_sizes[c]; + memset (raw_block, 0, sizeof (raw_block)); + memset (iso_block, 0, sizeof (iso_block)); + out = true; + + warning ("Method 8 split recovery: trying %d-sector chunks for sectors %u..%u", chunk_len, block_sector, block_sector + SECTORS_PER_BLOCK - 1); + + for (chunk_start = 0; chunk_start < SECTORS_PER_BLOCK && out; chunk_start += chunk_len) { + u_int32_t chunk_sector = block_sector + (u_int32_t) chunk_start; + memset (readbuf, 0, sizeof (readbuf)); + + if (chunk_sector + 992 + 16 <= d -> sectors_no) + dvd_read_sector_dummy (d -> dvd, chunk_sector + 992, 16, NULL, NULL, 0); + else if (chunk_sector >= 992) + dvd_read_sector_dummy (d -> dvd, chunk_sector - 992, 16, NULL, NULL, 0); + else + dvd_flush_cache_READ12 (d -> dvd, chunk_sector, NULL); + + ret = dvd_read_streaming (d -> dvd, chunk_sector, (u_int32_t) chunk_len, NULL, readbuf, (size_t) chunk_len * SECTOR_SIZE); + if (ret < 0) { + warning ("Method 8 split recovery: READ12 streaming failed for sectors %u..%u with %d", chunk_sector, chunk_sector + (u_int32_t) chunk_len - 1, ret); + out = false; + break; + } + + for (k = 0; k < chunk_len; k++) { + sect = &raw_block[(chunk_start + k) * RAW_SECTOR_SIZE]; + ram_offset = (u_int32_t) k * RAW_SECTOR_SIZE; + + if (dvd_memdump (d -> dvd, ram_offset, 1, 12, sect) < 0) { + warning ("Method 8 split recovery: header memdump failed at sector %u", chunk_sector + (u_int32_t) k); + out = false; + break; + } + if (dvd_memdump (d -> dvd, ram_offset + 2060, 1, 4, sect + 2060) < 0) { + warning ("Method 8 split recovery: EDC memdump failed at sector %u", chunk_sector + (u_int32_t) k); + out = false; + break; + } + + memcpy (sect + 12, readbuf + ((size_t) k * SECTOR_SIZE), SECTOR_SIZE); + } + } + + if (out && !unscrambler_unscramble_16sectors (d -> u, block_sector, raw_block, iso_block)) { + warning ("Method 8 split recovery: EDC/unscramble validation failed for %d-sector chunks at sectors %u..%u", chunk_len, block_sector, block_sector + SECTORS_PER_BLOCK - 1); + out = false; + } + + if (out) { + disc_cache_add_block (d, block_no, iso_block, raw_block); + warning ("Method 8 split recovery: recovered sectors %u..%u using %d-sector chunks", block_sector, block_sector + SECTORS_PER_BLOCK - 1, chunk_len); + return true; + } + } + + warning ("Method 8 split recovery: all chunk sizes failed for sectors %u..%u", block_sector, block_sector + SECTORS_PER_BLOCK - 1); + return false; +} + +static bool disc_hlds_type_is_gdr8050l_accel (u_int32_t type) { + return type == 442 || type == 443 || type == 445; +} + +static bool disc_hlds_type_is_gdr8050l_no_prefetch (u_int32_t type) { + return type == 44 || type == 45 || disc_hlds_type_is_gdr8050l_accel (type); +} + + +static bool disc_hlds_type_is_gdr8081n_search_guided (u_int32_t type) { + return type == 815; +} + +static bool hlds_e7_find_exact_raw_header_offset (const u_int8_t *dumpbuf, size_t dump_len, u_int32_t sector_no, size_t *out_off) { + size_t off; + u_int32_t expected; + u_int32_t got; + + if (out_off) + *out_off = (size_t) -1; + if (!dumpbuf || dump_len < RAW_SECTOR_SIZE) + return false; + expected = 0x30000U + sector_no; + for (off = 0; off + RAW_SECTOR_SIZE <= dump_len; off++) { + got = ((u_int32_t) dumpbuf[off + 1] << 16) | ((u_int32_t) dumpbuf[off + 2] << 8) | (u_int32_t) dumpbuf[off + 3]; + if (got != expected) + continue; + /* Avoid all-zero/all-ff false positives. Raw headers observed on HLDS + * families can have different first-byte control bits, so do not require + * exact parity here; the final unscrambler/EDC pass is the authority. */ + if (((dumpbuf[off] | dumpbuf[off + 1] | dumpbuf[off + 2] | dumpbuf[off + 3]) == 0x00) || + ((dumpbuf[off] & dumpbuf[off + 1] & dumpbuf[off + 2] & dumpbuf[off + 3]) == 0xFF)) + continue; + if (out_off) + *out_off = off; + return true; + } + return false; +} + +static int hlds_e7_count_exact_raw_headers_for_block (const u_int8_t *dumpbuf, size_t dump_len, u_int32_t block_sector, size_t offsets[SECTORS_PER_BLOCK]) { + int k; + int count; + size_t off; + count = 0; + if (offsets) { + for (k = 0; k < SECTORS_PER_BLOCK; k++) + offsets[k] = (size_t) -1; + } + for (k = 0; k < SECTORS_PER_BLOCK; k++) { + if (hlds_e7_find_exact_raw_header_offset (dumpbuf, dump_len, block_sector + (u_int32_t) k, &off)) { + if (offsets) + offsets[k] = off; + count++; + } + } + return count; +} + +static bool hlds_e7_raw_header_offsets_are_sector_shaped (const size_t offsets[SECTORS_PER_BLOCK]) { + int k; + int stride_matches; + size_t expected; + if (!offsets) + return false; + for (k = 0; k < SECTORS_PER_BLOCK; k++) { + if (offsets[k] == (size_t) -1) + return false; + } + /* A real raw cache block has one 2064-byte raw sector per logical sector. + * False positives observed on GDR-8081N v4 looked like SRAM/tables with + * sector-number patterns only 4 bytes apart, so require a sane 2064-byte + * sector stride before treating matches as real cache sectors. */ + stride_matches = 0; + for (k = 1; k < SECTORS_PER_BLOCK; k++) { + expected = offsets[0] + ((size_t) k * RAW_SECTOR_SIZE); + if (offsets[k] == expected) + stride_matches++; + } + return stride_matches >= 12; +} + +static size_t hlds_e7_find_command_echo_offset (const u_int8_t *buf, size_t len) { + static const u_int8_t sig[] = {0xE7, 0x48, 0x49, 0x54, 0x01}; + size_t off; + if (!buf || len < sizeof (sig)) + return (size_t) -1; + for (off = 0; off + sizeof (sig) <= len; off++) { + if (memcmp (buf + off, sig, sizeof (sig)) == 0) + return off; + } + return (size_t) -1; +} + +static int disc_read_sector_8_gdr8081n_search_guided (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) { + static bool logged = false; + bool out; + u_int32_t block_sector; + u_int32_t start_block; + u_int32_t profile_blocks; + u_int32_t scan_len; + int k; + int retry; + int ret; + int found_count; + int first_missing; + bool sector_shaped; + size_t offsets[SECTORS_PER_BLOCK]; + u_int8_t *scanbuf; + u_int8_t *sect; + u_int8_t raw_block[RAW_BLOCK_SIZE]; + u_int8_t iso_block[BLOCK_SIZE]; + u_int8_t readbuf[BLOCK_SIZE]; + + start_block = sector_no / SECTORS_PER_BLOCK; + block_sector = start_block * SECTORS_PER_BLOCK; + profile_blocks = dvd_get_hlds_e7_mem_blocks (d -> dvd); + if (profile_blocks < 1 || profile_blocks > 5) + profile_blocks = 5; + scan_len = profile_blocks * RAW_BLOCK_SIZE; + scanbuf = (u_int8_t *) malloc (scan_len); + if (!scanbuf) { + error ("GDR-8081N scan-guided Method 8: unable to allocate %u-byte scan buffer", scan_len); + return false; + } + if (!logged) { + hlds_e7_visible_probe_log ("GDR-8081N 0xE7: using scan-guided single-block Method 8 profile at 0x%08x, scan windows=%u", dvd_get_hlds_e7_cache_base (d -> dvd), profile_blocks); + logged = true; + } + + out = false; + for (retry = 0; !out && retry < 1; retry++) { + out = true; + if (retry > 0) + warning ("GDR-8081N scan-guided Method 8 retry %d for sectors %u..%u", retry, block_sector, block_sector + SECTORS_PER_BLOCK - 1); + + /* Keep this conservative: the v2 scanner showed sector-cache material inside + * the 0x80000000 five-window range, but not necessarily in the exact Type4 + * j/k slot map. For now, reconstruct only the requested 16-sector block + * from a full-window search. Do not require all five cached windows to map; + * that made v3 reject useful data before seed cracking could start. */ + if (block_sector > d -> sectors_no - 1000) + dvd_read_sector_streaming (d -> dvd, block_sector - 16 * 5 * 2, NULL, NULL, 0); + else + dvd_read_sector_streaming (d -> dvd, block_sector + 16 * 5, NULL, NULL, 0); + + if ((ret = dvd_read_sector_streaming (d -> dvd, block_sector, NULL, readbuf, sizeof (readbuf))) < 0) { + error ("GDR-8081N scan-guided Method 8: dvd_read_sector_streaming(%u) failed with %d", block_sector, ret); + out = false; + continue; + } + + memset (scanbuf, 0, scan_len); + if (dvd_memdump (d -> dvd, 0, profile_blocks, RAW_BLOCK_SIZE, scanbuf) < 0) { + error ("GDR-8081N scan-guided Method 8: full-window memdump failed"); + out = false; + continue; + } + + found_count = hlds_e7_count_exact_raw_headers_for_block (scanbuf, scan_len, block_sector, offsets); + sector_shaped = hlds_e7_raw_header_offsets_are_sector_shaped (offsets); + if (found_count == SECTORS_PER_BLOCK && !sector_shaped) { + warning ("GDR-8081N scan-guided Method 8: found 16/16 sector-number patterns for sectors %u..%u, but offsets are not 2064-byte sector-shaped; treating as SRAM/table false positive", + block_sector, block_sector + SECTORS_PER_BLOCK - 1); + out = false; + break; + } + if (found_count != SECTORS_PER_BLOCK) { + first_missing = -1; + for (k = 0; k < SECTORS_PER_BLOCK; k++) { + if (offsets[k] == (size_t) -1) { + first_missing = k; + break; + } + } + warning ("GDR-8081N scan-guided Method 8: found %d/16 exact raw headers for sectors %u..%u; first missing sector %u", + found_count, block_sector, block_sector + SECTORS_PER_BLOCK - 1, + first_missing >= 0 ? block_sector + (u_int32_t) first_missing : block_sector); + out = false; + continue; + } + + for (k = 0; k < SECTORS_PER_BLOCK; k++) { + sect = &raw_block[k * RAW_SECTOR_SIZE]; + memcpy (sect, scanbuf + offsets[k], 12); + memcpy (sect + 12, readbuf + ((size_t) k * SECTOR_SIZE), SECTOR_SIZE); + memcpy (sect + 2060, scanbuf + offsets[k] + 2060, 4); + } + + if (!unscrambler_unscramble_16sectors (d -> u, block_sector, raw_block, iso_block)) { + warning ("GDR-8081N scan-guided Method 8: EDC/unscramble validation failed for sectors %u..%u after finding all 16 headers", block_sector, block_sector + SECTORS_PER_BLOCK - 1); + out = false; + continue; + } + + disc_cache_add_block (d, start_block, iso_block, raw_block); + } + + free (scanbuf); + if (!out) + error ("GDR-8081N scan-guided Method 8: strict sector-layout validation failed"); + return out; +} + +static int disc_read_sector_8 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) { + if (disc_hlds_type_is_gdr8081n_search_guided (dvd_get_hlds_e7_type (d -> dvd))) + return disc_read_sector_8_gdr8081n_search_guided (d, sector_no, data, rawdata); + + bool out; + u_int32_t ram_offset; + int j, k, ret, retry; + u_int8_t *sect, buf[5][RAW_BLOCK_SIZE]; + u_int8_t readbuf[BLOCK_SIZE]; + u_int8_t buf_unscrambled[5][BLOCK_SIZE]; + u_int32_t start_block; + u_int32_t profile_blocks; +//fprintf (stdout,"disc_read_sector_8"); + start_block = sector_no / SECTORS_PER_BLOCK; + profile_blocks = dvd_get_hlds_e7_mem_blocks (d -> dvd); + if (profile_blocks < 1 || profile_blocks > 5) + profile_blocks = 5; + + out = false; + for (retry = 0; !out && retry < MAX_READ_RETRIES; retry++) { + /* Assume everything will turn out well */ + out = true; + + if (retry > 0) { + warning ("Read retry %d for sector %u", retry, sector_no); + + /* Try to reset in-memory data by seeking to a distant sector */ +// if (sector_no > 1000) +// dvd_read_sector_streaming (d -> dvd, 0, NULL, NULL, 0); +// else +// dvd_read_sector_streaming (d -> dvd, 1500, NULL, NULL, 0); + if (sector_no +992 +16 <= d -> sectors_no) //smaller than last sector + dvd_read_sector_dummy (d -> dvd, sector_no +992, 16, NULL, NULL, 0); + else if (sector_no -992 >= 0) //larger than first sector + dvd_read_sector_dummy (d -> dvd, sector_no -992, 16, NULL, NULL, 0); + else dvd_flush_cache_READ12 (d -> dvd, sector_no, NULL); + } + + /* First READ command. Type3/Type4 drives can expose several 16-sector + * cache windows after a nearby READ. The modified GDR-8050L test firmware + * proved seed retrieval and the first data runs, but failed when we drove it + * with the normal five-window prefetch schedule. For that profile, avoid + * the distant prefetch and consume only the current 16-sector window. */ + if (disc_hlds_type_is_gdr8050l_no_prefetch (dvd_get_hlds_e7_type (d -> dvd))) { + /* GDR-8050L modified-firmware no-prefetch scheduling is selected/logged + * by the profile probe, not from this hot per-read path. Keeping + * logging here spams one line for every 16-sector read/cache probe. */ + dvd_flush_cache_READ12 (d -> dvd, sector_no, NULL); + } else { + if (sector_no > d -> sectors_no - 1000) + dvd_read_sector_streaming (d -> dvd, sector_no - 16 * 5 * 2, NULL, NULL, 0); + else + dvd_read_sector_streaming (d -> dvd, sector_no + 16 * 5, NULL, NULL, 0); + } + if ((ret = dvd_read_sector_streaming (d -> dvd, sector_no, NULL, readbuf, sizeof (readbuf))) >= 0) { + for (j = 0; j < (int) profile_blocks && sector_no + j * 16 < d -> sectors_no && out; j++) { + /* Reconstruct raw sectors */ + for (k = 0; k < 16; k++) { + sect = &buf[j][k * RAW_SECTOR_SIZE]; + ram_offset = (j * RAW_BLOCK_SIZE) + k * RAW_SECTOR_SIZE; + /* Get first 12 bytes (ID. IED and CPR_MAI fields) and last 4 bytes (EDC field) with memdump */ + if (dvd_memdump (d -> dvd, ram_offset, 1, 12, sect) < 0) { + error ("Memdump (1) failed"); + out = false; + retry = MAX_READ_RETRIES; /* Well, if this fails going on is useless */ + } else if (dvd_memdump (d -> dvd, ram_offset + 2060, 1, 4, sect + 2060) < 0) { /* Dumping in a single block is faster */ + error ("Memdump (2) failed"); + out = false; + } + } + } + + /* Now the same for remaining cached 16-sector blocks. Type1 drives only + * expose one validated cache window at their DIC-derived base address. */ + for (j = 0; j < (int) profile_blocks && sector_no + j * 16 < d -> sectors_no && out; j++) { + if (j == 0 || (ret = dvd_read_sector_streaming (d -> dvd, sector_no + j * 16, NULL, readbuf, sizeof (readbuf))) >= 0) { + /* Copy "user data" field which has been incorrectly unscrambled by the DVD drive firmware */ + for (k = 0; k < 16; k++) { + sect = &buf[j][k * RAW_SECTOR_SIZE]; + memcpy (sect + 12, readbuf + k * SECTOR_SIZE, SECTOR_SIZE); + } +#ifdef DEBUG + if (d -> unscrambling) { +#endif + /* Try to unscramble all data to see if EDC fails */ + if (!unscrambler_unscramble_16sectors (d -> u, sector_no + (j * 16), buf[j], buf_unscrambled[j])) + out = false; +#ifdef DEBUG + } +#endif + } else { + error ("dvd_read_sector_streaming() failed with %d", ret); + out = false; + } + } + + if (out) { + /* It seems all data were unscrambled correctly, so cache them out */ + for (j = 0; j < (int) profile_blocks && sector_no + j * SECTORS_PER_BLOCK < d -> sectors_no; j++) + disc_cache_add_block (d, start_block + j, buf_unscrambled[j], buf[j]); + } + } else { + error ("dvd_read_sector_streaming() failed with %d", ret); + out = false; + } + } + + if (!out && disc_hlds_type_is_gdr8050l_accel (dvd_get_hlds_e7_type (d -> dvd))) { + u_int32_t failed_type = dvd_get_hlds_e7_type (d -> dvd); + warning ("GDR-8050L modified 0xE7: accelerated profile %s failed at sector %u; falling back to proven single-window profile for this run", + dvd_get_hlds_e7_profile_name (d -> dvd), sector_no); + dvd_set_hlds_e7_runtime_profile (d -> dvd, 44, 0x80000000U, 1); + d -> hlds_e7_read_schedule_logged = false; + disc_cache_clear (d); + out = disc_read_sector_8 (d, sector_no, data, rawdata); + if (!out) + warning ("GDR-8050L modified 0xE7: fallback from accelerated profile %u also failed", failed_type); + } + + if (!out) { + u_int32_t block_sector = start_block * SECTORS_PER_BLOCK; + warning ("Method 8 normal profile read failed at sectors %u..%u; entering split recovery", block_sector, block_sector + SECTORS_PER_BLOCK - 1); + out = disc_read_sector_8_split_recover_block (d, block_sector); + } + + if (!out) + error ("Too many retries, giving up"); + + return (out); +} + + +static int disc_read_sector_9 (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) { + bool out; + u_int32_t ram_offset; + int j, k, ret, retry; + u_int8_t *sect, buf[5][RAW_BLOCK_SIZE]; + u_int8_t readbuf[BLOCK_SIZE], tmp[16]; + u_int8_t buf_unscrambled[5][BLOCK_SIZE]; + u_int32_t start_block; +//fprintf (stdout,"disc_read_sector_9"); + start_block = sector_no / SECTORS_PER_BLOCK; + + out = false; + for (retry = 0; !out && retry < MAX_READ_RETRIES; retry++) { + /* Assume everything will turn out well */ + out = true; + + if (retry > 0) { + warning ("Read retry %d for sector %u", retry, sector_no); + + /* Try to reset in-memory data by seeking to a distant sector */ +// if (sector_no > 1000) +// dvd_read_sector_streaming (d -> dvd, 0, NULL, NULL, 0); +// else +// dvd_read_sector_streaming (d -> dvd, 1500, NULL, NULL, 0); + if (sector_no +992 +16 <= d -> sectors_no) //smaller than last sector + dvd_read_sector_dummy (d -> dvd, sector_no +992, 16, NULL, NULL, 0); + else if (sector_no -992 >= 0) //larger than first sector + dvd_read_sector_dummy (d -> dvd, sector_no -992, 16, NULL, NULL, 0); + else dvd_flush_cache_READ12 (d -> dvd, sector_no, NULL); + } + + /* First READ command, this will cache 5 16-sector blocks. Immediately dump relevant data */ + if (sector_no > d -> sectors_no - 1000) + dvd_read_sector_streaming (d -> dvd, sector_no - 16 * 5 * 2, NULL, NULL, 0); + else + dvd_read_sector_streaming (d -> dvd, sector_no + 16 * 5, NULL, NULL, 0); + if ((ret = dvd_read_sector_streaming (d -> dvd, sector_no, NULL, readbuf, BLOCK_SIZE)) >= 0) { + for (j = 0; j < 5 && sector_no + j * 16 < d -> sectors_no && out; j++) { + /* Reconstruct raw sectors */ + for (k = 0; k < 16; k++) { + sect = &buf[j][k * RAW_SECTOR_SIZE]; + ram_offset = (j * RAW_BLOCK_SIZE) + k * RAW_SECTOR_SIZE; + /* Get first 12 bytes (ID. IED and CPR_MAI fields) and last 4 bytes (EDC field) with memdump */ + if (j == 0 && k == 0) { + if (dvd_memdump (d -> dvd, ram_offset, 1, 12, sect) < 0) { + error ("Memdump (1) failed"); + out = false; + retry = MAX_READ_RETRIES; /* Well, if this fails going on is useless */ + } + } else { + memcpy (sect, tmp + 4, 12); + } + + if (out && dvd_memdump (d -> dvd, ram_offset + 2060, 1, 16, tmp) < 0) { /* Dumping in a single block is faster */ + error ("Memdump (2) failed"); + out = false; + } else { + memcpy (sect + 2060, tmp, 4); + } + } + } + + /* Now the same for remaining 4 16-sector blocks */ + for (j = 0; j < 5 && sector_no + j * 16 < d -> sectors_no && out; j++) { + if (j == 0 || (ret = dvd_read_sector_streaming (d -> dvd, sector_no + j * 16, NULL, readbuf, BLOCK_SIZE)) >= 0) { + /* Copy "user data" field which has been incorrectly unscrambled by the DVD drive firmware */ + for (k = 0; k < 16; k++) { + sect = &buf[j][k * RAW_SECTOR_SIZE]; + memcpy (sect + 12, readbuf + k * SECTOR_SIZE, SECTOR_SIZE); + } +#ifdef DEBUG + if (d -> unscrambling) { +#endif + /* Try to unscramble all data to see if EDC fails */ + if (!unscrambler_unscramble_16sectors (d -> u, sector_no + (j * 16), buf[j], buf_unscrambled[j])) + out = false; +#ifdef DEBUG + } +#endif + } else { + error ("dvd_read_sector_streaming() failed with %d", ret); + out = false; + } + } + + if (out) { + /* It seems all data were unscrambled correctly, so cache them out */ + for (j = 0; j < 5 && sector_no + j * SECTORS_PER_BLOCK < d -> sectors_no; j++) + disc_cache_add_block (d, start_block + j, buf_unscrambled[j], buf[j]); + } + } else { + error ("dvd_read_sector_streaming() failed with %d", ret); + out = false; + } + } + + if (!out) + error ("Too many retries, giving up"); + + return (out); +} + + +/* We could also use the 'System ID' (first byte of the image) to tell the discs apart */ +static disc_type disc_detect_type (disc *d, u_int32_t forced_type, u_int32_t sectors_no) { + req_sense sense; + + if (forced_type==0) { + d -> type = DISC_TYPE_GAMECUBE; + d -> sectors_no = DISC_GAMECUBE_SECTORS_NO; + } else if (forced_type==1) { + d -> type = DISC_TYPE_WII; + d -> sectors_no = DISC_WII_SECTORS_NO_SL; + } else if (forced_type==2) { + d -> type = DISC_TYPE_WII_DL; + d -> sectors_no = DISC_WII_SECTORS_NO_DL; + //dvd_get_layerbreak(d->dvd, &(d -> layerbreak), NULL); + } else if (forced_type==3) { + d -> type = DISC_TYPE_DVD; + if (sectors_no == -1) dvd_get_size(d->dvd, &(d -> sectors_no), NULL); + dvd_get_layerbreak(d->dvd, &(d -> layerbreak), NULL); + } else if (forced_type==4) { + d -> type = DISC_TYPE_XBOX; + d -> read_sector = disc_read_sector_xbox; + d -> read_method = 10; + if (sectors_no == -1) { + u_int32_t sector_size = 0; + /* Do not run the GDR-8050L handshake during type detection. + * Redump-style Xbox output must capture the visible DVD-video view + * before switching the drive into the unlocked game view. */ + if (dvd_read_capacity_10(d->dvd, &(d -> sectors_no), §or_size, NULL) < 0 || sector_size != SECTOR_SIZE) + d -> sectors_no = DISC_XBOX_GDR8050L_UNLOCKED_SECTORS_NO; + } + } else { + + if (dvd_is_xbox_drive(d->dvd)) { + d -> type = DISC_TYPE_XBOX; + d -> read_sector = disc_read_sector_xbox; + d -> read_method = 10; + { + u_int32_t sector_size = 0; + /* Keep the drive in its current/locked view for dump planning. + * The Xbox dumper explicitly unlocks only when it needs the + * game/XDVDFS view. */ + if (dvd_read_capacity_10(d->dvd, &(d -> sectors_no), §or_size, NULL) < 0 || sector_size != SECTOR_SIZE) + d -> sectors_no = DISC_XBOX_GDR8050L_UNLOCKED_SECTORS_NO; + } + if (sectors_no != -1) d -> sectors_no = sectors_no; + return (d -> type); + } + + /* Try to read a sector beyond the end of GameCube discs */ + if (!dvd_read_sector_dummy (d -> dvd, DISC_GAMECUBE_SECTORS_NO + 100, SECTORS_PER_BLOCK, &sense, NULL, 0) && sense.sense_key == 0x05 && sense.asc == 0x21) { + d -> type = DISC_TYPE_GAMECUBE; + d -> sectors_no = DISC_GAMECUBE_SECTORS_NO; + } else { + if (!dvd_read_sector_dummy (d -> dvd, DISC_WII_SECTORS_NO_SL + 100, SECTORS_PER_BLOCK, &sense, NULL, 0) && sense.sense_key == 0x05 && sense.asc == 0x21) { + d -> type = DISC_TYPE_WII; + d -> sectors_no = DISC_WII_SECTORS_NO_SL; + } else { + d -> type = DISC_TYPE_WII_DL; + d -> sectors_no = DISC_WII_SECTORS_NO_DL; + //dvd_get_layerbreak(d->dvd, &(d -> layerbreak), NULL); + } + } + + } + if (sectors_no != -1) d -> sectors_no = sectors_no; + + return (d -> type); +} + + +/** + * Reads a sector from the disc (or from the cache), using the preset read method. + * @param d The disc structure. + * @param sector_no The requested sector number. + * @param data A buffer to hold the unscrambled sector data (or NULL). + * @param rawdata A buffer to hold the raw sector data (or NULL). + * @return + */ +int disc_read_sector (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata) { + u_int32_t block; + u_int8_t *cdata, *crawdata; + int out; + + /* Unscrambled data cannot be requested if unscrambling was disabled */ + MY_ASSERT (!(data && !d -> unscrambling && d -> type != DISC_TYPE_XBOX)); + + block = sector_no / SECTORS_PER_BLOCK; + + /* See if sector is in cache */ + if (!(out = disc_cache_lookup_block (d, block, &cdata, &crawdata))) { + /* Requested block is not in cache, try to read it from media */ + out = d -> read_sector (d, sector_no, data, rawdata); + + /* Now requested sector is in cache, for sure ;) */ + if (out) + MY_ASSERT (disc_cache_lookup_block (d, block, &cdata, &crawdata)); + } + + if (out) { + if (data) + *data = cdata + (sector_no % SECTORS_PER_BLOCK) * SECTOR_SIZE; + if (rawdata) + *rawdata = crawdata + (sector_no % SECTORS_PER_BLOCK) * RAW_SECTOR_SIZE; + } else { + if (data) + *data = NULL; + if (rawdata) + *rawdata = NULL; + } + + return (out); +} + + +static bool disc_analyze (disc *d) { + u_int8_t *buf; + char tmp[0x03E0 + 1]; + bool unscramble_old, out; + + /* Force unscrambling for this read */ + unscramble_old = d -> unscrambling; + disc_set_unscrambling (d, true); + + if (disc_read_sector (d, 0, &buf, NULL)) { + /* System ID */ + d -> system_id = buf[0]; +// if (d -> system_id == 'G') { +// d -> type = DISC_TYPE_GAMECUBE; +// d -> sectors_no = DISC_GAMECUBE_SECTORS_NO; +// } else if (d -> system_id == 'R') { +// d -> type = DISC_TYPE_WII; +// d -> sectors_no = DISC_WII_SECTORS_NO; +// } else { +// error ("Unknown system ID: '%c'", d -> system_id); +// MY_ASSERT (false); +// } + + /* Game ID */ + strncpy (d -> game_id, (char *) buf + 1, 2); + d -> game_id[2] = '\0'; + + /* Region */ + switch (buf[3]) { + case 'P': + d -> region = DISC_REGION_PAL; + break; + case 'E': + d -> region = DISC_REGION_NTSC; + break; + case 'J': + d -> region = DISC_REGION_JAPAN; + break; + case 'U': + d -> region = DISC_REGION_AUSTRALIA; + break; + case 'F': + d -> region = DISC_REGION_FRANCE; + break; + case 'D': + d -> region = DISC_REGION_GERMANY; + break; + case 'I': + d -> region = DISC_REGION_ITALY; + break; + case 'S': + d -> region = DISC_REGION_SPAIN; + break; + case 'X': + d -> region = DISC_REGION_PAL_X; + break; + case 'Y': + d -> region = DISC_REGION_PAL_Y; + break; + default: + d -> region = DISC_REGION_UNKNOWN; + break; + } + + /* Maker code */ + strncpy (d -> maker, (char *) buf + 4, 2); + d -> maker[2] = '\0'; + + /* Version */ + d -> version = buf[7]; + snprintf (tmp, sizeof (tmp), "1.%02u", d -> version); + my_strdup (d -> version_string, tmp); + + /* Game title */ + memcpy (tmp, buf + 0x0020, sizeof (tmp) - 1); + tmp[sizeof (tmp) - 1] = '\0'; + strtrimr (tmp); + my_strdup (d -> title, tmp); + + out = true; + } else { + error ("Cannot analyze disc"); + out = false; + } + + disc_set_unscrambling (d, unscramble_old); + + return (out); +} + + +static char disc_type_strings[5][15] = { + "GameCube", + "Wii", + "Wii_DL", + "DVD", + "Xbox" +}; + +/** + * Retrieves the disc type. + * @param d The disc structure. + * @param dt This will be set to the disc type. + * @param dt_s This will point to a string describing the disc type. + * @return A string describing the disc type. + */ +char *disc_get_type (disc *d, disc_type *dt, char **dt_s) { + if (dt) + *dt = d -> type; + + if (dt_s) { + if (d -> type <= DISC_TYPE_XBOX) + *dt_s = disc_type_strings[d -> type]; + else + *dt_s = disc_type_strings[DISC_TYPE_DVD]; + } + + return (*dt_s); +} + + +/** + * Retrieves the disc game ID. + * @param d The disc structure. + * @param gid_s This will point to a string containing the game ID. + * @return A string containing the game ID. + */ +char *disc_get_gameid (disc *d, char **gid_s) { + if (gid_s) + *gid_s = d -> game_id; + + return (*gid_s); +} + + +static char disc_region_strings[11][15] = { + "Europe/PAL", + "USA/NTSC", + "Japan/NTSC", + "Australia/PAL", + "France/PAL", + "Germany/PAL", + "Italy/PAL", + "Spain/PAL", + "Europe(X)/PAL", + "Europe(Y)/PAL", + "Unknown" +}; + +/** + * Retrieves the disc region. + * @param d The disc structure. + * @param dr This will be set to the disc region. + * @param dr_s This will point to a string describing the disc region. + * @return A string describing the disc region. + */ +char *disc_get_region (disc *d, disc_region *dr, char **dr_s) { + if (dr) + *dr = d -> region; + + if (dr_s) { + if (d -> region < DISC_REGION_UNKNOWN) + *dr_s = disc_region_strings[d -> region]; + else + *dr_s = disc_region_strings[DISC_REGION_UNKNOWN]; + } + + return (*dr_s); +} + + +/* The following list has been derived from http://wiitdb.com/Company/HomePage */ +static struct { + char *code; + char *name; +} makers[] = { + {"0A", "Jaleco"}, + {"0B", "Coconuts Japan"}, + {"0C", "Coconuts Japan / G.X.Media"}, + {"0D", "Micronet"}, + {"0E", "Technos"}, + {"0F", "Mebio Software"}, + {"0G", "Shouei System"}, + {"0H", "Starfish"}, + {"0J", "Mitsui Fudosan / Dentsu"}, + {"0L", "Warashi Inc."}, + {"0N", "Nowpro"}, + {"0P", "Game Village"}, + {"0Q", "IE Institute"}, + {"01", "Nintendo"}, + {"02", "Rocket Games / Ajinomoto"}, + {"03", "Imagineer-Zoom"}, + {"04", "Gray Matter"}, + {"05", "Zamuse"}, + {"06", "Falcom"}, + {"07", "Enix"}, + {"08", "Capcom"}, + {"09", "Hot B Co."}, + {"1A", "Yanoman"}, + {"1C", "Tecmo Products"}, + {"1D", "Japan Glary Business"}, + {"1E", "Forum / OpenSystem"}, + {"1F", "Virgin Games (Japan)"}, + {"1G", "SMDE"}, + {"1J", "Daikokudenki"}, + {"1P", "Creatures Inc."}, + {"1Q", "TDK Deep Impresion"}, + {"2A", "Culture Brain"}, + {"2C", "Palsoft"}, + {"2D", "Visit Co.,Ltd."}, + {"2E", "Intec"}, + {"2F", "System Sacom"}, + {"2G", "Poppo"}, + {"2H", "Ubisoft Japan"}, + {"2J", "Media Works"}, + {"2K", "NEC InterChannel"}, + {"2L", "Tam"}, + {"2M", "Jordan"}, + {"2N", "Smilesoft / Rocket"}, + {"2Q", "Mediakite"}, + {"3B", "Arcade Zone Ltd"}, + {"3C", "Entertainment International / Empire Software"}, + {"3D", "Loriciel"}, + {"3E", "Gremlin Graphics"}, + {"3F", "K.Amusement Leasing Co."}, + {"4B", "Raya Systems"}, + {"4C", "Renovation Products"}, + {"4D", "Malibu Games"}, + {"4F", "Eidos"}, + {"4G", "Playmates Interactive"}, + {"4J", "Fox Interactive"}, + {"4K", "Time Warner Interactive"}, + {"4Q", "Disney Interactive"}, + {"4S", "Black Pearl"}, + {"4U", "Advanced Productions"}, + {"4X", "GT Interactive"}, + {"4Y", "RARE"}, + {"4Z", "Crave Entertainment"}, + {"5A", "Mindscape / Red Orb Entertainment"}, + {"5B", "Romstar"}, + {"5C", "Taxan"}, + {"5D", "Midway / Tradewest"}, + {"5F", "American Softworks"}, + {"5G", "Majesco Sales Inc"}, + {"5H", "3DO"}, + {"5K", "Hasbro"}, + {"5L", "NewKidCo"}, + {"5M", "Telegames"}, + {"5N", "Metro3D"}, + {"5P", "Vatical Entertainment"}, + {"5Q", "LEGO Media"}, + {"5S", "Xicat Interactive"}, + {"5T", "Cryo Interactive"}, + {"5W", "Red Storm Entertainment"}, + {"5X", "Microids"}, + {"5Z", "Data Design / Conspiracy / Swing"}, + {"6B", "Laser Beam"}, + {"6E", "Elite Systems"}, + {"6F", "Electro Brain"}, + {"6G", "The Learning Company"}, + {"6H", "BBC"}, + {"6J", "Software 2000"}, + {"6K", "UFO Interactive Games"}, + {"6L", "BAM! Entertainment"}, + {"6M", "Studio 3"}, + {"6Q", "Classified Games"}, + {"6S", "TDK Mediactive"}, + {"6U", "DreamCatcher"}, + {"6V", "JoWood Produtions"}, + {"6W", "Sega"}, + {"6X", "Wannado Edition"}, + {"6Y", "LSP (Light & Shadow Prod.)"}, + {"6Z", "ITE Media"}, + {"7A", "Triffix Entertainment"}, + {"7C", "Microprose Software"}, + {"7D", "Sierra / Universal Interactive"}, + {"7F", "Kemco"}, + {"7G", "Rage Software"}, + {"7H", "Encore"}, + {"7J", "Zoo"}, + {"7K", "Kiddinx"}, + {"7L", "Simon & Schuster Interactive"}, + {"7M", "Asmik Ace Entertainment Inc."}, + {"7N", "Empire Interactive"}, + {"7Q", "Jester Interactive"}, + {"7S", "Rockstar Games"}, + {"7T", "Scholastic"}, + {"7U", "Ignition Entertainment"}, + {"7V", "Summitsoft"}, + {"7W", "Stadlbauer"}, + {"8B", "BulletProof Software (BPS)"}, + {"8C", "Vic Tokai Inc."}, + {"8E", "Character Soft"}, + {"8F", "I'Max"}, + {"8G", "Saurus"}, + {"8J", "General Entertainment"}, + {"8N", "Success"}, + {"8P", "Sega Japan"}, + {"9A", "Nichibutsu / Nihon Bussan"}, + {"9B", "Tecmo"}, + {"9C", "Imagineer"}, + {"9F", "Nova"}, + {"9G", "Take2 / Den'Z / Global Star"}, + {"9H", "Bottom Up"}, + {"9J", "TGL (Technical Group Laboratory)"}, + {"9L", "Hasbro Japan"}, + {"9N", "Marvelous Entertainment"}, + {"9P", "Keynet Inc."}, + {"9Q", "Hands-On Entertainment"}, + {"12", "Infocom"}, + {"13", "Electronic Arts Japan"}, + {"15", "Cobra Team"}, + {"16", "Human / Field"}, + {"17", "KOEI"}, + {"18", "Hudson Soft"}, + {"19", "S.C.P."}, + {"20", "Destination Software / Zoo Games / KSS"}, + {"21", "Sunsoft / Tokai Engineering"}, + {"22", "POW (Planning Office Wada) / VR1 Japan"}, + {"23", "Micro World"}, + {"25", "San-X"}, + {"26", "Enix"}, + {"27", "Loriciel / Electro Brain"}, + {"28", "Kemco Japan"}, + {"29", "Seta"}, + {"30", "Viacom"}, + {"31", "Carrozzeria"}, + {"32", "Dynamic"}, + {"34", "Magifact"}, + {"35", "Hect"}, + {"36", "Codemasters"}, + {"37", "Taito / GAGA Communications"}, + {"38", "Laguna"}, + {"39", "Telstar / Event / Taito"}, + {"40", "Seika Corp."}, + {"41", "Ubi Soft Entertainment"}, + {"42", "Sunsoft US"}, + {"44", "Life Fitness"}, + {"46", "System 3"}, + {"47", "Spectrum Holobyte"}, + {"49", "IREM"}, + {"50", "Absolute Entertainment"}, + {"51", "Acclaim"}, + {"52", "Activision"}, + {"53", "American Sammy"}, + {"54", "Take 2 Interactive / GameTek"}, + {"55", "Hi Tech"}, + {"56", "LJN LTD."}, + {"58", "Mattel"}, + {"60", "Titus"}, + {"61", "Virgin Interactive"}, + {"62", "Maxis"}, + {"64", "LucasArts Entertainment"}, + {"67", "Ocean"}, + {"68", "Bethesda Softworks"}, + {"69", "Electronic Arts"}, + {"70", "Atari (Infogrames)"}, + {"71", "Interplay"}, + {"72", "JVC (US)"}, + {"73", "Parker Brothers"}, + {"75", "Sales Curve (Storm / SCI)"}, + {"78", "THQ"}, + {"79", "Accolade"}, + {"80", "Misawa"}, + {"81", "Teichiku"}, + {"82", "Namco Ltd."}, + {"83", "LOZC"}, + {"84", "KOEI"}, + {"86", "Tokuma Shoten Intermedia"}, + {"87", "Tsukuda Original"}, + {"88", "DATAM-Polystar"}, + {"90", "Takara Amusement"}, + {"91", "Chun Soft"}, + {"92", "Video System / Mc O' River"}, + {"93", "BEC"}, + {"95", "Varie"}, + {"96", "Yonezawa / S'pal"}, + {"97", "Kaneko"}, + {"99", "Marvelous Entertainment"}, + {"A0", "Telenet"}, + {"A1", "Hori"}, + {"A4", "Konami"}, + {"A5", "K.Amusement Leasing Co."}, + {"A6", "Kawada"}, + {"A7", "Takara"}, + {"A9", "Technos Japan Corp."}, + {"AA", "JVC / Victor"}, + {"AC", "Toei Animation"}, + {"AD", "Toho"}, + {"AF", "Namco"}, + {"AG", "Media Rings Corporation"}, + {"AH", "J-Wing"}, + {"AJ", "Pioneer LDC"}, + {"AK", "KID"}, + {"AL", "Mediafactory"}, + {"AP", "Infogrames / Hudson"}, + {"AQ", "Kiratto. Ludic Inc"}, + {"B0", "Acclaim Japan"}, + {"B1", "ASCII"}, + {"B2", "Bandai"}, + {"B4", "Enix"}, + {"B6", "HAL Laboratory"}, + {"B7", "SNK"}, + {"B9", "Pony Canyon"}, + {"BA", "Culture Brain"}, + {"BB", "Sunsoft"}, + {"BC", "Toshiba EMI"}, + {"BD", "Sony Imagesoft"}, + {"BF", "Sammy"}, + {"BG", "Magical"}, + {"BH", "Visco"}, + {"BJ", "Compile"}, + {"BL", "MTO Inc."}, + {"BN", "Sunrise Interactive"}, + {"BP", "Global A Entertainment"}, + {"BQ", "Fuuki"}, + {"C0", "Taito"}, + {"C2", "Kemco"}, + {"C3", "Square"}, + {"C4", "Tokuma Shoten"}, + {"C5", "Data East"}, + {"C6", "Tonkin House / Tokyo Shoseki"}, + {"C8", "Koei"}, + {"CA", "Konami / Ultra / Palcom"}, + {"CB", "NTVIC / VAP"}, + {"CC", "Use Co.,Ltd."}, + {"CD", "Meldac"}, + {"CE", "Pony Canyon / FCI"}, + {"CF", "Angel / Sotsu Agency / Sunrise"}, + {"CG", "Yumedia / Aroma Co., Ltd"}, + {"CJ", "Boss"}, + {"CK", "Axela / Crea-Tech"}, + {"CL", "Sekaibunka-Sha / Sumire Kobo / Marigul Management Inc."}, + {"CM", "Konami Computer Entertainment Osaka"}, + {"CN", "NEC Interchannel"}, + {"CP", "Enterbrain"}, + {"CQ", "From Software"}, + {"D0", "Taito / Disco"}, + {"D1", "Sofel"}, + {"D2", "Quest / Bothtec"}, + {"D3", "Sigma"}, + {"D4", "Ask Kodansha"}, + {"D6", "Naxat"}, + {"D7", "Copya System"}, + {"D8", "Capcom Co., Ltd."}, + {"D9", "Banpresto"}, + {"DA", "Tomy"}, + {"DB", "LJN Japan"}, + {"DD", "NCS"}, + {"DE", "Human Entertainment"}, + {"DF", "Altron"}, + {"DG", "Jaleco"}, + {"DH", "Gaps Inc."}, + {"DN", "Elf"}, + {"DQ", "Compile Heart"}, + {"E0", "Jaleco"}, + {"E2", "Yutaka"}, + {"E3", "Varie"}, + {"E4", "T&ESoft"}, + {"E5", "Epoch"}, + {"E7", "Athena"}, + {"E8", "Asmik"}, + {"E9", "Natsume"}, + {"EA", "King Records"}, + {"EB", "Atlus"}, + {"EC", "Epic / Sony Records"}, + {"EE", "IGS (Information Global Service)"}, + {"EG", "Chatnoir"}, + {"EH", "Right Stuff"}, + {"EL", "Spike"}, + {"EM", "Konami Computer Entertainment Tokyo"}, + {"EN", "Alphadream Corporation"}, + {"EP", "Sting"}, + {"ES", "Star-Fish"}, + {"F0", "A Wave"}, + {"F1", "Motown Software"}, + {"F2", "Left Field Entertainment"}, + {"F3", "Extreme Ent. Grp."}, + {"F4", "TecMagik"}, + {"F9", "Cybersoft"}, + {"FB", "Psygnosis"}, + {"FE", "Davidson / Western Tech."}, + {"FK", "The Game Factory"}, + {"FL", "Hip Games"}, + {"FM", "Aspyr"}, + {"FP", "Mastiff"}, + {"FQ", "iQue"}, + {"FR", "Digital Tainment Pool"}, + {"FS", "XS Games / Jack Of All Games"}, + {"FT", "Daiwon"}, + {"G0", "Alpha Unit"}, + {"G1", "PCCW Japan"}, + {"G2", "Yuke's Media Creations"}, + {"G4", "KiKi Co Ltd"}, + {"G5", "Open Sesame Inc"}, + {"G6", "Sims"}, + {"G7", "Broccoli"}, + {"G8", "Avex"}, + {"G9", "D3 Publisher"}, + {"GB", "Konami Computer Entertainment Japan"}, + {"GD", "Square-Enix"}, + {"GE", "KSG"}, + {"GF", "Micott & Basara Inc."}, + {"GH", "Orbital Media"}, + {"GJ", "Detn8 Games"}, + {"GL", "Gameloft / Ubi Soft"}, + {"GM", "Gamecock Media Group"}, + {"GN", "Oxygen Games"}, + {"GT", "505 Games"}, + {"GY", "The Game Factory"}, + {"H1", "Treasure"}, + {"H2", "Aruze"}, + {"H3", "Ertain"}, + {"H4", "SNK Playmore"}, + {"HJ", "Genius Products"}, + {"HY", "Reef Entertainment"}, + {"HZ", "Nordcurrent"}, + {"IH", "Yojigen"}, + {"J9", "AQ Interactive"}, + {"JF", "Arc System Works"}, + {"JW", "Atari"}, + {"K6", "Nihon System"}, + {"KB", "NIS America"}, + {"KM", "Deep Silver"}, + {"LH", "Trend Verlag / East Entertainment"}, + {"LT", "Legacy Interactive"}, + {"MJ", "Mumbo Jumbo"}, + {"MR", "Mindscape"}, + {"MS", "Milestone / UFO Interactive"}, + {"MT", "Blast !"}, + {"N9", "Terabox"}, + {"NK", "Neko Entertainment / Diffusion / Naps team"}, + {"NP", "Nobilis"}, + {"NR", "Data Design / Destineer Studios"}, + {"PL", "Playlogic"}, + {"RM", "Rondomedia"}, + {"RS", "Warner Bros. Interactive Entertainment Inc."}, + {"RT", "RTL Games"}, + {"RW", "RealNetworks"}, + {"S5", "Southpeak Interactive"}, + {"SP", "Blade Interactive Studios"}, + {"SV", "SevenGames"}, + {"TK", "Tasuke / Works"}, + {"UG", "Metro 3D / Data Design"}, + {"VN", "Valcon Games"}, + {"VP", "Virgin Play"}, + {"WR", "Warner Bros. Interactive Entertainment Inc."}, + {"XJ", "Xseed Games"}, + {"XS", "Aksys Games"}, + {NULL, NULL} +}; + +/** + * Retrieves the disk maker. + * @param d The disc structure. + * @param m This will point to a string containing the disc maker ID. + * @param m_s This will point to a string describing the disc maker. + * @return A string describing the disc maker. + */ +char *disc_get_maker (disc *d, char **m, char **m_s) { + u_int32_t i; + + if (m) + *m = d -> maker; + + if (m_s) { + for (i = 0; makers[i].code; i++) { + if (strcasecmp (d -> maker, makers[i].code) == 0) { + *m_s = makers[i].name; + break; + } + } + if (!makers[i].code) { + *m_s = "Unknown"; + } + } + + return (*m_s); +} + + +/** + * Retrieves the disc version. + * @param d The disc structure. + * @param v This will contain the version ID. + * @param v_s This will point to a string describing the disc version. + * @return A string describing the disc version. + */ +char *disc_get_version (disc *d, u_int8_t *v, char **v_s) { + if (v) + *v = d -> version; + + if (v_s) + *v_s = d -> version_string; + + return (*v_s); +} + + +/** + * Retrieves the disc game title. + * @param d The disc structure. + * @param t_s This will point to a string describing the disc title. + * @return A string describing the disc title. + */ +char *disc_get_title (disc *d, char **t_s) { + if (t_s) + *t_s = d -> title; + + return (*t_s); +} + + +/** + * Retrieves if the disc has an update. + * @param d The disc structure. + * @return True if the disc contains an update, false otherwise. + */ +bool disc_get_update (disc *d) { + return (d -> has_update); +} + + +/** + * Retrieves the number of sectors of the disc. + * @param d The disc structure. + * @return The number of sectors. + */ +u_int32_t disc_get_sectors_no (disc *d) { + return (d -> sectors_no); +} + +u_int32_t disc_get_layerbreak (disc *d) { + return (d -> layerbreak); +} + +u_int32_t disc_get_command (disc *d) { + return (d -> command); +} + +u_int32_t disc_get_method (disc *d) { + return (d -> read_method); +} + +u_int32_t disc_get_def_method (disc *d) { + return dvd_get_def_method(d -> dvd);//(d -> def_read_method); +} + +u_int32_t disc_get_sec_disc (disc *d) { + return (d -> sec_disc); +} + +u_int32_t disc_get_sec_mem (disc *d) { + return (d -> sec_mem); +} + +/* wiidevel@stacktic.org */ +static bool disc_check_update (disc *d) { + u_int8_t *buf; + u_int32_t x; + bool unscramble_old; + + if (d -> type == DISC_TYPE_WII || d -> type == DISC_TYPE_WII_DL) { + /* Force unscrambling for this read */ + unscramble_old = d -> unscrambling; + disc_set_unscrambling (d, true); + + /* We need to read offset 0x50004 of the disc. Sector 160 has offset 0x50000 */ + if (disc_read_sector (d, 160, &buf, NULL)) { + x = my_ntohl (*(u_int32_t *) (buf + 4)); + if (x == 0xA5BED6AE) + d -> has_update = false; + else + d -> has_update = true; + } else { + error ("disc_check_update() failed"); + } + + disc_set_unscrambling (d, unscramble_old); + } else { + /* GameCube discs never have an update, as actually the GC firmware cannot be upgrade */ + d -> has_update = false; + } + + return (d -> has_update); +} + + +/** + * Sets the disc read method. + * @param d The disc structure. + * @param method The requested method. + * @return True if the method was set correctly, false otherwise (i. e.: method too small/big). + */ +bool disc_set_read_method (disc *d, int method) { + bool out; + u_int32_t deviation; + u_int32_t counter; + u_int32_t cnt1; + + d -> command = dvd_get_command(d -> dvd); +// d -> def_read_method = dvd_get_def_method(d -> dvd); + d -> read_method = method; + + out = true; + switch (method) { + case 0: + d -> read_sector = disc_read_sector_0; + break; + case 1: + d -> read_sector = disc_read_sector_1; + break; + case 2: + d -> read_sector = disc_read_sector_2; + break; + case 3: + d -> read_sector = disc_read_sector_3; + break; + case 4: + d -> read_sector = disc_read_sector_4; + break; + case 5: + d -> read_sector = disc_read_sector_5; + break; + case 6: + d -> read_sector = disc_read_sector_6; + break; + case 7: + d -> read_sector = disc_read_sector_7; + break; + case 8: + d -> read_sector = disc_read_sector_8; + break; + case 9: + d -> read_sector = disc_read_sector_9; + break; + case 10: + d -> read_sector = disc_read_sector_xbox; + break; + default: + switch (dvd_get_def_method(d -> dvd)) { + case 0: + d -> read_method = 0; + d -> read_sector = disc_read_sector_0; + break; + case 1: + d -> read_method = 1; + d -> read_sector = disc_read_sector_1; + break; + case 2: + d -> read_method = 2; + d -> read_sector = disc_read_sector_2; + break; + case 3: + d -> read_method = 3; + d -> read_sector = disc_read_sector_3; + break; + case 4: + d -> read_method = 4; + d -> read_sector = disc_read_sector_4; + break; + case 5: + d -> read_method = 5; + d -> read_sector = disc_read_sector_5; + break; + case 6: + d -> read_method = 6; + d -> read_sector = disc_read_sector_6; + break; + case 7: + d -> read_method = 7; + d -> read_sector = disc_read_sector_7; + break; + case 8: + d -> read_method = 8; + d -> read_sector = disc_read_sector_8; + break; + case 9: + d -> read_method = 9; + d -> read_sector = disc_read_sector_9; + break; + case 10: + d -> read_method = 10; + d -> read_sector = disc_read_sector_xbox; + break; + default: + d -> read_method = DEFAULT_READ_METHOD; + d -> read_sector = DEFAULT_READ_SECTOR; + break; + } + } + + if (d->sec_disc==-1) { + if ((d->read_method == 4) || (d->read_method == 5) || (d->read_method == 6)) + d->sec_disc=27; + else + d->sec_disc=16; + } + if (d->sec_mem==-1) { + if ((d->read_method == 4) || (d->read_method == 5) || (d->read_method == 6)) + d->sec_mem=27; + else + d->sec_mem=16; + } + + deviation = d->sec_mem % SECTORS_PER_BLOCK; + counter=0; + + if (deviation>3) { + cnt1=deviation; + while (1==1) { + cnt1+=deviation; + counter++; + if (cnt1%SECTORS_PER_BLOCK<=1) break; + } + } + d -> max_cnt = counter; + d -> max_blk = ((d->sec_mem*(d->max_cnt+1))-((d->sec_mem*(d->max_cnt+1)) % SECTORS_PER_BLOCK)) / 16; + + if (out) { + debug ("Read method set to %d", d -> read_method); + } else { + error ("Cannot set read method\n"); + } + + return (out); +} + + +/** + * Controls the unscrambling process. + * @param d The disc structure. + * @param unscramble If true, every raw sectors read will be unscrambled to check if they are error-free, otherwise read data will be returned as-is. + */ +void disc_set_unscrambling (disc *d, bool unscramble) { + d -> unscrambling = unscramble; + debug ("Sectors unscrambling %s", unscramble ? "enabled" : "disabled"); + + return; +} + + + +static unsigned int hlds_e7_sector_header_value (const u_int8_t *hdr) { + if (!hdr) + return 0xFFFFFFFFU; + return ((unsigned int) hdr[1] << 16) | ((unsigned int) hdr[2] << 8) | (unsigned int) hdr[3]; +} + +static int hlds_e7_score_sector_header (const u_int8_t *hdr, u_int32_t sector_no) { + unsigned int got; + unsigned int expected; + int score; + + if (!hdr) + return 0; + got = hlds_e7_sector_header_value (hdr); + expected = 0x30000U + sector_no; + score = 0; + if ((hdr[0] & 1) == 0) + score += 5; + if (got == expected) + score += 100; + if ((hdr[0] | hdr[1] | hdr[2] | hdr[3]) == 0x00) + score -= 10; + if ((hdr[0] & hdr[1] & hdr[2] & hdr[3]) == 0xFF) + score -= 10; + return score; +} + +static void hlds_e7_json_escape (FILE *f, const char *s) { + const unsigned char *p; + if (!f) + return; + if (!s) + s = ""; + for (p = (const unsigned char *) s; *p; p++) { + if (*p == '"' || *p == '\\') + fprintf (f, "\\%c", *p); + else if (*p == '\n') + fprintf (f, "\\n"); + else if (*p == '\r') + fprintf (f, "\\r"); + else if (*p == '\t') + fprintf (f, "\\t"); + else if (*p < 32) + fprintf (f, "\\u%04x", (unsigned int) *p); + else + fputc (*p, f); + } +} + +static void hlds_e7_json_bytes (FILE *f, const u_int8_t *b, size_t n) { + size_t i; + fprintf (f, "\""); + if (b) { + for (i = 0; i < n; i++) { + if (i) + fprintf (f, " "); + fprintf (f, "%02x", (unsigned int) b[i]); + } + } + fprintf (f, "\""); +} + +static u_int32_t hlds_e7_fnv1a32 (const u_int8_t *buf, size_t len) { + size_t i; + u_int32_t h; + h = 2166136261U; + if (!buf) + return 0; + for (i = 0; i < len; i++) { + h ^= (u_int32_t) buf[i]; + h *= 16777619U; + } + return h; +} + +static size_t hlds_e7_count_byte_diffs (const u_int8_t *a, const u_int8_t *b, size_t len) { + size_t i; + size_t out; + out = 0; + if (!a || !b) + return 0; + for (i = 0; i < len; i++) { + if (a[i] != b[i]) + out++; + } + return out; +} + +static bool hlds_e7_probe_bytes_useful (const u_int8_t *p, size_t len) { + size_t i; + unsigned int orv; + unsigned int andv; + if (!p || len == 0) + return false; + orv = 0; + andv = 0xFF; + for (i = 0; i < len; i++) { + orv |= p[i]; + andv &= p[i]; + } + return !(orv == 0x00 || andv == 0xFF); +} + +static int hlds_e7_find_raw_header_match (const u_int8_t *buf, size_t len, u_int32_t block_sector, size_t *match_offset, u_int32_t *match_sector) { + size_t off; + int k; + u_int32_t expected; + int best_score; + int score; + best_score = 0; + if (match_offset) + *match_offset = (size_t) -1; + if (match_sector) + *match_sector = 0xFFFFFFFFU; + if (!buf || len < 4) + return 0; + for (off = 0; off + 4 <= len; off++) { + for (k = 0; k < SECTORS_PER_BLOCK; k++) { + expected = 0x30000U + block_sector + (u_int32_t) k; + if (((u_int32_t) buf[off + 1] << 16 | (u_int32_t) buf[off + 2] << 8 | (u_int32_t) buf[off + 3]) == expected) { + score = 90; + if ((buf[off] & 1) == 0) + score += 10; + if ((off % RAW_SECTOR_SIZE) == (size_t) (k * RAW_SECTOR_SIZE)) + score += 25; + else if ((off % RAW_SECTOR_SIZE) == 0) + score += 10; + if (score > best_score) { + best_score = score; + if (match_offset) + *match_offset = off; + if (match_sector) + *match_sector = block_sector + (u_int32_t) k; + } + } + } + } + return best_score; +} + +static int hlds_e7_find_user_data_match (const u_int8_t *dumpbuf, size_t dump_len, const u_int8_t *readbuf, size_t read_len, size_t *match_offset, u_int32_t *match_sector, size_t *read_offset) { + static const size_t probe_offsets[] = {0x00, 0x20, 0x80, 0x100, 0x400, 0x700}; + const size_t probe_len = 32; + size_t k; + size_t po; + size_t off; + const u_int8_t *needle; + if (match_offset) + *match_offset = (size_t) -1; + if (match_sector) + *match_sector = 0xFFFFFFFFU; + if (read_offset) + *read_offset = (size_t) -1; + if (!dumpbuf || !readbuf || dump_len < probe_len || read_len < SECTOR_SIZE) + return 0; + for (k = 0; k < SECTORS_PER_BLOCK && ((k * SECTOR_SIZE) + SECTOR_SIZE) <= read_len; k++) { + for (po = 0; po < sizeof (probe_offsets) / sizeof (probe_offsets[0]); po++) { + if (probe_offsets[po] + probe_len > SECTOR_SIZE) + continue; + needle = readbuf + k * SECTOR_SIZE + probe_offsets[po]; + if (!hlds_e7_probe_bytes_useful (needle, probe_len)) + continue; + for (off = 0; off + probe_len <= dump_len; off++) { + if (memcmp (dumpbuf + off, needle, probe_len) == 0) { + if (match_offset) + *match_offset = off; + if (match_sector) + *match_sector = (u_int32_t) k; + if (read_offset) + *read_offset = probe_offsets[po]; + return 70; + } + } + } + } + return 0; +} + +typedef struct { + u_int32_t type; + u_int32_t base; + u_int32_t windows; + const char *label; +} hlds_e7_probe_candidate; + +bool disc_hlds_e7_scan (disc *d, const char *json_path, const char *dump_prefix) { + typedef struct { + const char *label; + u_int32_t base; + u_int32_t windows; + const char *origin; + } scan_candidate; + static const scan_candidate candidates[] = { + {"type4_base_5win", 0x80000000U, 5, "known Type3/Type4 family base"}, + {"type4_base_1win", 0x80000000U, 1, "known Type3/Type4 base, conservative window"}, + {"type4_plus_0x8000", 0x80008000U, 1, "nearby +0x8000 alias candidate"}, + {"type4_plus_0x10000", 0x80010000U, 1, "nearby +0x10000 alias candidate"}, + {"type4_plus_0x20000", 0x80020000U, 1, "nearby +0x20000 alias candidate"}, + {"type4_plus_0x30000", 0x80030000U, 1, "nearby +0x30000 alias candidate"}, + {"type4_minus_0x8000", 0x7FFF8000U, 1, "moving/boundary candidate"}, + {"type4_minus_0x10000", 0x7FFF0000U, 1, "moving/boundary candidate"}, + {"type4_minus_0x18000", 0x7FFE8000U, 1, "moving/boundary candidate"}, + {"type1_a00000", 0x00A00000U, 1, "Type1 neighborhood"}, + {"type1_a13000", 0x00A13000U, 1, "GCC-4160N Type1 known base"}, + {"firmware_table_00380000",0x00380000U, 1, "observed 0x00380030 neighborhood, aligned down"}, + {"firmware_table_00380030",0x00380030U, 1, "observed stale/profile-garbage value; test only"}, + {"low_sram_00000000", 0x00000000U, 1, "low SRAM alias"}, + {"low_sram_00008000", 0x00008000U, 1, "low SRAM alias +0x8000"}, + {"low_sram_00010000", 0x00010000U, 1, "low SRAM alias +0x10000"}, + {"low_sram_00020000", 0x00020000U, 1, "low SRAM alias +0x20000"}, + {"firmware_sram_00001800", 0x00001800U, 1, "GDR-8081N plaintext firmware references 0x18xx SRAM/MMIO neighborhood"}, + {"firmware_sram_000018a8", 0x000018A8U, 1, "GDR-8081N plaintext firmware references 0x18a8"}, + {"firmware_sram_00009300", 0x00009300U, 1, "GDR-8081N plaintext firmware references 0x93xx"}, + {"firmware_sram_00009b00", 0x00009B00U, 1, "GDR-8081N plaintext firmware references 0x9bxx"}, + {"firmware_sram_0000a800", 0x0000A800U, 1, "GDR-8081N plaintext firmware references 0xa800"}, + {"firmware_alias_40000000",0x40000000U, 1, "firmware mapping base as alias sanity check"} + }; + static const u_int32_t probe_sectors[] = {0U, 320U}; + FILE *f; + u_int8_t sample[16]; + u_int8_t readbuf[BLOCK_SIZE]; + u_int8_t *dumpbuf; + u_int8_t *first_dump; + u_int32_t old_type; + u_int32_t old_base; + u_int32_t old_windows; + size_t i; + size_t j; + int k; + size_t scan_len; + size_t max_scan_len; + size_t raw_offset; + size_t exact_offsets[SECTORS_PER_BLOCK]; + int exact_count; + bool sector_shaped; + size_t command_echo_offset; + size_t user_offset; + size_t read_offset; + size_t window_diff; + u_int32_t raw_sector; + u_int32_t user_sector; + u_int32_t hash; + int read_ret; + int dump_ret; + int raw_score; + int user_score; + int sector_score; + char dump_path[512]; + FILE *df; + int total_score; + int best_score; + u_int32_t best_base; + u_int32_t best_windows; + const char *path; + bool any_readable; + + if (!d || !d -> dvd) + return false; + path = (json_path && json_path[0]) ? json_path : "hlds_e7_scan.json"; + f = fopen (path, "wb"); + if (!f) { + warning ("HLDS 0xE7 scan: could not open %s for writing", path); + return false; + } + + max_scan_len = 5U * RAW_BLOCK_SIZE; + dumpbuf = (u_int8_t *) malloc (max_scan_len); + first_dump = (u_int8_t *) malloc (max_scan_len); + if (!dumpbuf || !first_dump) { + if (dumpbuf) + free (dumpbuf); + if (first_dump) + free (first_dump); + fclose (f); + warning ("HLDS 0xE7 scan: out of memory"); + return false; + } + + old_type = dvd_get_hlds_e7_type (d -> dvd); + old_base = dvd_get_hlds_e7_cache_base (d -> dvd); + old_windows = dvd_get_hlds_e7_mem_blocks (d -> dvd); + best_score = -999999; + best_base = 0; + best_windows = 0; + any_readable = false; + + fprintf (stderr, "\nHLDS 0xE7 scan mode v5: strict cache/memdump validation without seed cracking\n"); + fprintf (stderr, "HLDS 0xE7 scan mode v5: requiring 2064-byte raw-sector stride or READ payload echo; table/command echoes are not promoted\n"); + fprintf (stderr, "HLDS 0xE7 scan mode v5: writing JSON report to %s\n", path); + + fprintf (f, "{\n"); + fprintf (f, " \"scan_version\": \"v5_strict_sector_shape_command_echo\",\n"); + fprintf (f, " \"drive\": \""); + hlds_e7_json_escape (f, disc_get_drive_model_string (d)); + fprintf (f, "\",\n"); + fprintf (f, " \"initial_profile\": \""); + hlds_e7_json_escape (f, dvd_get_hlds_e7_profile_name (d -> dvd)); + fprintf (f, "\",\n"); + fprintf (f, " \"initial_cache_base\": \"0x%08x\",\n", old_base); + fprintf (f, " \"initial_windows\": %u,\n", old_windows); + fprintf (f, " \"probe_sectors\": [%u, %u],\n", probe_sectors[0], probe_sectors[1]); + fprintf (f, " \"notes\": \"v5 does not promote sector-number table matches. It requires exact raw-sector IDs to be laid out with a 2064-byte stride or a direct READ payload echo. It also records HIT command-echo offsets because those indicate SRAM/command buffers, not proven sector cache.\",\n"); + fprintf (f, " \"candidates\": [\n"); + + for (i = 0; i < sizeof (candidates) / sizeof (candidates[0]); i++) { + scan_len = candidates[i].windows * RAW_BLOCK_SIZE; + if (scan_len == 0 || scan_len > max_scan_len) + scan_len = RAW_BLOCK_SIZE; + total_score = 0; + window_diff = 0; + dvd_set_hlds_e7_runtime_profile (d -> dvd, 9000U + (u_int32_t) i, candidates[i].base, candidates[i].windows); + fprintf (stderr, " [%02u/%02u] %-25s base=0x%08x windows=%u scan=%lu... ", + (unsigned int) (i + 1), (unsigned int) (sizeof (candidates) / sizeof (candidates[0])), + candidates[i].label, candidates[i].base, candidates[i].windows, (unsigned long) scan_len); + fprintf (f, " {\n"); + fprintf (f, " \"label\": \""); + hlds_e7_json_escape (f, candidates[i].label); + fprintf (f, "\",\n"); + fprintf (f, " \"base\": \"0x%08x\",\n", candidates[i].base); + fprintf (f, " \"windows\": %u,\n", candidates[i].windows); + fprintf (f, " \"scan_bytes\": %lu,\n", (unsigned long) scan_len); + fprintf (f, " \"origin\": \""); + hlds_e7_json_escape (f, candidates[i].origin); + fprintf (f, "\",\n"); + fprintf (f, " \"sector_tests\": [\n"); + for (j = 0; j < sizeof (probe_sectors) / sizeof (probe_sectors[0]); j++) { + memset (sample, 0, sizeof (sample)); + memset (readbuf, 0, sizeof (readbuf)); + memset (dumpbuf, 0, scan_len); + dvd_flush_cache_READ12 (d -> dvd, probe_sectors[j], NULL); + read_ret = dvd_read_sector_streaming (d -> dvd, probe_sectors[j], NULL, readbuf, sizeof (readbuf)); + dump_ret = dvd_memdump (d -> dvd, 0, candidates[i].windows ? candidates[i].windows : 1, RAW_BLOCK_SIZE, dumpbuf); + if (dump_ret >= 0) { + if (dump_prefix && dump_prefix[0]) { + snprintf (dump_path, sizeof (dump_path), "%s_%02lu_%s_sector_%u.bin", dump_prefix, (unsigned long) (i + 1), candidates[i].label, probe_sectors[j]); + df = fopen (dump_path, "wb"); + if (df) { + fwrite (dumpbuf, 1, scan_len, df); + fclose (df); + } + } + any_readable = true; + memcpy (sample, dumpbuf, sizeof (sample)); + hash = hlds_e7_fnv1a32 (dumpbuf, scan_len); + (void) hlds_e7_find_raw_header_match (dumpbuf, scan_len, probe_sectors[j], &raw_offset, &raw_sector); + exact_count = hlds_e7_count_exact_raw_headers_for_block (dumpbuf, scan_len, probe_sectors[j], exact_offsets); + sector_shaped = hlds_e7_raw_header_offsets_are_sector_shaped (exact_offsets); + command_echo_offset = hlds_e7_find_command_echo_offset (dumpbuf, scan_len); + if (sector_shaped) + raw_score = 220; + else if (exact_count > 0) + raw_score = exact_count; + else + raw_score = 0; + user_score = hlds_e7_find_user_data_match (dumpbuf, scan_len, readbuf, sizeof (readbuf), &user_offset, &user_sector, &read_offset); + sector_score = raw_score + user_score; + if (command_echo_offset != (size_t) -1 && !sector_shaped && user_score <= 0) + sector_score -= 10; + if (j == 0) + memcpy (first_dump, dumpbuf, scan_len); + else { + window_diff = hlds_e7_count_byte_diffs (first_dump, dumpbuf, scan_len); + if (window_diff > 4096) + sector_score += 40; + else if (window_diff > 512) + sector_score += 20; + else if (window_diff < 16) + sector_score -= 20; + } + } else { + hash = 0; + raw_score = -50; + user_score = 0; + sector_score = -50; + raw_offset = (size_t) -1; + exact_count = 0; + for (k = 0; k < SECTORS_PER_BLOCK; k++) + exact_offsets[k] = (size_t) -1; + raw_sector = 0xFFFFFFFFU; + sector_shaped = false; + command_echo_offset = (size_t) -1; + user_offset = (size_t) -1; + user_sector = 0xFFFFFFFFU; + read_offset = (size_t) -1; + } + if (read_ret < 0) + sector_score -= 20; + total_score += sector_score; + fprintf (f, " {\"sector\": %u, \"read_ret\": %d, \"window_memdump_ret\": %d, \"score\": %d, ", + probe_sectors[j], read_ret, dump_ret, sector_score); + fprintf (f, "\"raw_header_score\": %d, \"raw_header_offset\": ", raw_score); + if (raw_offset == (size_t) -1) + fprintf (f, "null, \"raw_header_sector\": null, "); + else + fprintf (f, "%lu, \"raw_header_sector\": %u, ", (unsigned long) raw_offset, raw_sector); + fprintf (f, "\"raw_header_found_count\": %d, \"raw_header_offsets\": [", exact_count); + for (k = 0; k < SECTORS_PER_BLOCK; k++) { + if (k) + fprintf (f, ", "); + if (exact_offsets[k] == (size_t) -1) + fprintf (f, "null"); + else + fprintf (f, "%lu", (unsigned long) exact_offsets[k]); + } + fprintf (f, "], "); + fprintf (f, "\"raw_header_sector_shaped\": %s, ", sector_shaped ? "true" : "false"); + fprintf (f, "\"command_echo_offset\": "); + if (command_echo_offset == (size_t) -1) + fprintf (f, "null, "); + else + fprintf (f, "%lu, ", (unsigned long) command_echo_offset); + fprintf (f, "\"user_data_score\": %d, \"user_data_offset\": ", user_score); + if (user_offset == (size_t) -1) + fprintf (f, "null, \"user_data_sector_index\": null, \"read_probe_offset\": null, "); + else + fprintf (f, "%lu, \"user_data_sector_index\": %u, \"read_probe_offset\": %lu, ", (unsigned long) user_offset, user_sector, (unsigned long) read_offset); + fprintf (f, "\"window_hash_fnv1a32\": \"0x%08x\", \"sample\": ", hash); + hlds_e7_json_bytes (f, sample, sizeof (sample)); + fprintf (f, "}%s\n", (j + 1 < sizeof (probe_sectors) / sizeof (probe_sectors[0])) ? "," : ""); + } + if (window_diff > 4096) + total_score += 40; + else if (window_diff > 512) + total_score += 20; + else if (window_diff < 16) + total_score -= 20; + fprintf (f, " ],\n"); + fprintf (f, " \"sector_window_diff_bytes\": %lu,\n", (unsigned long) window_diff); + fprintf (f, " \"sector_window_diff_per_1000\": %lu,\n", scan_len ? (unsigned long) ((window_diff * 1000U) / scan_len) : 0UL); + fprintf (f, " \"total_score\": %d,\n", total_score); + fprintf (f, " \"classification\": \"%s\"\n", total_score >= 220 ? "strict_cache_candidate" : (total_score >= 80 ? "needs_more_candidates" : (total_score > 0 ? "sram_or_table_match" : "no_match"))); + fprintf (f, " }%s\n", (i + 1 < sizeof (candidates) / sizeof (candidates[0])) ? "," : ""); + fprintf (stderr, "score=%d diff=%lu%s\n", total_score, (unsigned long) window_diff, total_score >= 220 ? " STRICT" : (total_score >= 80 ? " REVIEW" : "")); + if (total_score > best_score) { + best_score = total_score; + best_base = candidates[i].base; + best_windows = candidates[i].windows; + } + } + + fprintf (f, " ],\n"); + fprintf (f, " \"best\": {\"base\": \"0x%08x\", \"windows\": %u, \"score\": %d, \"confidence\": \"%s\"},\n", + best_base, best_windows, best_score, best_score >= 220 ? "strict" : (best_score >= 80 ? "review" : (best_score > 0 ? "weak" : "none"))); + fprintf (f, " \"sector_cache_candidate_found\": %s,\n", best_score >= 220 ? "true" : "false"); + fprintf (f, " \"promotion_recommendation\": \"%s\",\n", best_score >= 220 ? "candidate may be promoted into an experimental dump profile" : "do not promote; scan found command/SRAM/table echoes but no strict 2064-byte sector cache"); + fprintf (f, " \"next_recommended_action\": \"%s\",\n", best_score >= 220 ? "try the strict scan-guided normal probe" : "do not run normal seed retrieval yet; use firmware analysis or a wider address/subcommand sweep to find the real cache path"); + fprintf (f, " \"e7_memdump_command\": \"%s\"\n", any_readable ? "accepted_by_at_least_one_candidate" : "no_successful_window_memdump"); + fprintf (f, "}\n"); + fclose (f); + + dvd_set_hlds_e7_runtime_profile (d -> dvd, old_type, old_base, old_windows); + fprintf (stderr, "HLDS 0xE7 scan mode v5 complete: best base=0x%08x windows=%u score=%d (%s; %s)\n", + best_base, best_windows, best_score, + best_score >= 220 ? "strict" : (best_score >= 80 ? "review" : (best_score > 0 ? "weak" : "no match")), + best_score >= 220 ? "promotion allowed" : "do not promote"); + free (dumpbuf); + free (first_dump); + return best_score >= 80; +} + + +static int hlds_e7_raw_hit_data_in (disc *d, u_int8_t subcmd, u_int32_t offset, u_int32_t length, u_int8_t *buf) { + mmc_command mmc; + if (!d || !d -> dvd || !buf || length == 0 || length > 65535U) + return -1; + dvd_init_command (&mmc, buf, (int) length, NULL); + mmc.cmd[0] = 0xE7; + mmc.cmd[1] = 0x48; /* H */ + mmc.cmd[2] = 0x49; /* I */ + mmc.cmd[3] = 0x54; /* T */ + mmc.cmd[4] = subcmd; + mmc.cmd[6] = (u_int8_t) ((offset >> 24) & 0xFF); + mmc.cmd[7] = (u_int8_t) ((offset >> 16) & 0xFF); + mmc.cmd[8] = (u_int8_t) ((offset >> 8) & 0xFF); + mmc.cmd[9] = (u_int8_t) (offset & 0xFF); + mmc.cmd[10] = (u_int8_t) ((length >> 8) & 0xFF); + mmc.cmd[11] = (u_int8_t) (length & 0xFF); + return dvd_execute_cmd (d -> dvd, &mmc, true); +} + +typedef struct { + u_int8_t subcmd; + const char *label; +} hlds_e7_subcmd_probe; + +typedef struct { + u_int32_t address; + const char *label; +} hlds_e7_raw_addr_probe; + +bool disc_hlds_e7_subcmd_sweep (disc *d, const char *json_path, const char *dump_prefix) { + static const hlds_e7_subcmd_probe subcmds[] = { + {0x00, "subcmd_00"}, {0x01, "subcmd_01_known_memdump"}, + {0x02, "subcmd_02"}, {0x03, "subcmd_03"}, + {0x04, "subcmd_04"}, {0x05, "subcmd_05"}, + {0x06, "subcmd_06"}, {0x07, "subcmd_07"}, + {0x08, "subcmd_08"}, {0x09, "subcmd_09"}, + {0x0A, "subcmd_0a"}, {0x0B, "subcmd_0b"}, + {0x0C, "subcmd_0c"}, {0x0D, "subcmd_0d"}, + {0x0E, "subcmd_0e"}, {0x0F, "subcmd_0f"} + }; + static const hlds_e7_raw_addr_probe addrs[] = { + {0x80000000U, "type4_base"}, + {0x80008000U, "type4_plus_8000"}, + {0x80010000U, "type4_plus_10000"}, + {0x00000000U, "low_sram_0"}, + {0x00001800U, "firmware_sram_1800"}, + {0x000018A8U, "firmware_sram_18a8"}, + {0x00380000U, "table_00380000"}, + {0x40000000U, "firmware_alias_40000000"} + }; + static const u_int32_t probe_sectors[] = {0, 320}; + const char *path; + FILE *f; + FILE *df; + char dump_path[512]; + u_int8_t *buf; + u_int8_t readbuf[BLOCK_SIZE]; + u_int32_t len; + size_t i, a, j; + int ret; + int read_ret; + int exact_count; + int raw_score; + int user_score; + int best_score; + int total_promotable; + size_t raw_offset; + size_t user_offset; + size_t read_offset; + size_t command_echo_offset; + size_t offsets[SECTORS_PER_BLOCK]; + u_int32_t raw_sector; + u_int32_t user_sector; + u_int32_t hash; + bool sector_shaped; + u_int8_t sample[16]; + + if (!d) + return false; + path = (json_path && json_path[0]) ? json_path : "hlds_e7_subcmd_sweep.json"; + len = RAW_BLOCK_SIZE; /* one 16-sector raw-cache-sized window; enough to find strict stride without huge runtimes */ + buf = (u_int8_t *) malloc (len); + if (!buf) { + warning ("HLDS 0xE7 subcmd sweep: out of memory"); + return false; + } + f = fopen (path, "wb"); + if (!f) { + free (buf); + warning ("HLDS 0xE7 subcmd sweep: could not open %s for writing", path); + return false; + } + + fprintf (stderr, "\nHLDS 0xE7 subcommand sweep v2: probing HIT subcommands 0x00..0x0f without seed cracking\n"); + fprintf (stderr, "HLDS 0xE7 subcommand sweep v2: data-in only, %u-byte reads, no dump attempt\n", len); + fprintf (stderr, "HLDS 0xE7 subcommand sweep v2: writing JSON report to %s\n", path); + + best_score = -999999; + total_promotable = 0; + + fprintf (f, "{\n"); + fprintf (f, " \"sweep_version\": \"v1_hlds_hit_subcmd_address_probe\",\n"); + fprintf (f, " \"drive\": \""); + hlds_e7_json_escape (f, disc_get_drive_model_string (d)); + fprintf (f, "\",\n"); + fprintf (f, " \"notes\": \"This diagnostic sends HIT 0xE7 data-in commands with subcommands 0x00..0x0f over a small address set. It does not crack seeds or dump the disc. A promotable result requires sector-shaped 2064-byte raw headers or a direct READ-payload echo; command echoes alone are not promoted.\",\n"); + fprintf (f, " \"read_length\": %u,\n", len); + fprintf (f, " \"probe_sectors\": [%u, %u],\n", probe_sectors[0], probe_sectors[1]); + fprintf (f, " \"results\": [\n"); + + for (i = 0; i < sizeof (subcmds) / sizeof (subcmds[0]); i++) { + for (a = 0; a < sizeof (addrs) / sizeof (addrs[0]); a++) { + int total_score = 0; + int promotable = 0; + fprintf (stderr, " subcmd=0x%02x %-24s addr=0x%08x... ", + (unsigned int) subcmds[i].subcmd, subcmds[i].label, addrs[a].address); + fprintf (f, " {\n"); + fprintf (f, " \"subcmd\": \"0x%02x\",\n", (unsigned int) subcmds[i].subcmd); + fprintf (f, " \"subcmd_label\": \""); + hlds_e7_json_escape (f, subcmds[i].label); + fprintf (f, "\",\n"); + fprintf (f, " \"address\": \"0x%08x\",\n", addrs[a].address); + fprintf (f, " \"address_label\": \""); + hlds_e7_json_escape (f, addrs[a].label); + fprintf (f, "\",\n"); + fprintf (f, " \"sector_tests\": [\n"); + for (j = 0; j < sizeof (probe_sectors) / sizeof (probe_sectors[0]); j++) { + int score = 0; + int kk; + memset (buf, 0, len); + memset (readbuf, 0, sizeof (readbuf)); + memset (sample, 0, sizeof (sample)); + read_ret = dvd_read_sector_streaming (d -> dvd, probe_sectors[j], NULL, readbuf, sizeof (readbuf)); + ret = hlds_e7_raw_hit_data_in (d, subcmds[i].subcmd, addrs[a].address, len, buf); + raw_offset = (size_t) -1; + user_offset = (size_t) -1; + read_offset = (size_t) -1; + command_echo_offset = (size_t) -1; + raw_sector = 0xFFFFFFFFU; + user_sector = 0xFFFFFFFFU; + exact_count = 0; + for (kk = 0; kk < SECTORS_PER_BLOCK; kk++) + offsets[kk] = (size_t) -1; + sector_shaped = false; + raw_score = 0; + user_score = 0; + hash = 0; + if (ret >= 0) { + memcpy (sample, buf, sizeof (sample)); + hash = hlds_e7_fnv1a32 (buf, len); + (void) hlds_e7_find_raw_header_match (buf, len, probe_sectors[j], &raw_offset, &raw_sector); + exact_count = hlds_e7_count_exact_raw_headers_for_block (buf, len, probe_sectors[j], offsets); + sector_shaped = hlds_e7_raw_header_offsets_are_sector_shaped (offsets); + command_echo_offset = hlds_e7_find_command_echo_offset (buf, len); + user_score = hlds_e7_find_user_data_match (buf, len, readbuf, sizeof (readbuf), &user_offset, &user_sector, &read_offset); + raw_score = sector_shaped ? 220 : exact_count; + } + if (ret < 0) + score -= 50; + if (read_ret < 0) + score -= 20; + score += raw_score + user_score; + if (command_echo_offset != (size_t) -1 && !sector_shaped && user_score <= 0) + score -= 10; + if (sector_shaped || user_score > 0) + promotable++; + total_score += score; + fprintf (f, " {\"sector\": %u, \"read_ret\": %d, \"e7_ret\": %d, \"score\": %d, ", + probe_sectors[j], read_ret, ret, score); + fprintf (f, "\"raw_header_found_count\": %d, \"raw_header_sector_shaped\": %s, ", + exact_count, sector_shaped ? "true" : "false"); + fprintf (f, "\"raw_header_offset\": "); + if (raw_offset == (size_t) -1) + fprintf (f, "null, \"raw_header_sector\": null, "); + else + fprintf (f, "%lu, \"raw_header_sector\": %u, ", (unsigned long) raw_offset, raw_sector); + fprintf (f, "\"command_echo_offset\": "); + if (command_echo_offset == (size_t) -1) + fprintf (f, "null, "); + else + fprintf (f, "%lu, ", (unsigned long) command_echo_offset); + fprintf (f, "\"user_data_score\": %d, \"user_data_offset\": ", user_score); + if (user_offset == (size_t) -1) + fprintf (f, "null, \"user_data_sector_index\": null, \"read_probe_offset\": null, "); + else + fprintf (f, "%lu, \"user_data_sector_index\": %u, \"read_probe_offset\": %lu, ", + (unsigned long) user_offset, user_sector, (unsigned long) read_offset); + fprintf (f, "\"window_hash_fnv1a32\": \"0x%08x\", \"sample\": ", hash); + hlds_e7_json_bytes (f, sample, sizeof (sample)); + fprintf (f, "}%s\n", (j + 1 < sizeof (probe_sectors) / sizeof (probe_sectors[0])) ? "," : ""); + + /* + * If the caller requested raw sweep dumps, write every successful + * HIT 0xE7 data-in response, not only promotable sector-cache hits. + * + * v7 only dumped promotable windows. That meant a useful negative + * sweep produced no gdr8081n_subcmd_*.bin files at all, even though + * non-promotable command/SRAM echoes were exactly what we needed to + * inspect next. + */ + if (dump_prefix && dump_prefix[0] && ret >= 0) { + snprintf (dump_path, sizeof (dump_path), "%s_sub%02x_%s_sector_%u.bin", + dump_prefix, (unsigned int) subcmds[i].subcmd, addrs[a].label, probe_sectors[j]); + df = fopen (dump_path, "wb"); + if (df) { + fwrite (buf, 1, len, df); + fclose (df); + } + } + } + fprintf (f, " ],\n"); + fprintf (f, " \"total_score\": %d,\n", total_score); + fprintf (f, " \"promotable_sector_tests\": %d,\n", promotable); + fprintf (f, " \"classification\": \"%s\"\n", promotable > 0 ? "promotable_candidate" : (total_score > 0 ? "responds_nonpromotable" : "no_useful_response")); + fprintf (f, " }%s\n", + (i + 1 == sizeof (subcmds) / sizeof (subcmds[0]) && a + 1 == sizeof (addrs) / sizeof (addrs[0])) ? "" : ","); + fprintf (stderr, "score=%d%s\n", total_score, promotable > 0 ? " PROMOTABLE" : ""); + if (total_score > best_score) + best_score = total_score; + total_promotable += promotable; + } + } + + fprintf (f, " ],\n"); + fprintf (f, " \"promotable_candidate_found\": %s,\n", total_promotable > 0 ? "true" : "false"); + fprintf (f, " \"promotion_recommendation\": \"%s\",\n", total_promotable > 0 ? "review promotable candidates and try a targeted profile" : "do not promote; no subcommand/address pair exposed sector-shaped cache or READ-payload echo"); + fprintf (f, " \"next_recommended_action\": \"%s\"\n", total_promotable > 0 ? "send the JSON and any dumped promotable windows" : "continue firmware handler analysis; avoid normal seed retrieval on GDR-8081N until a promotable candidate appears"); + fprintf (f, "}\n"); + fclose (f); + free (buf); + + fprintf (stderr, "HLDS 0xE7 subcommand sweep v2 complete: promotable candidates=%d (%s)\n", + total_promotable, total_promotable > 0 ? "review JSON" : "none found"); + return true; +} + + +typedef struct { + u_int32_t start; + u_int32_t end; + u_int32_t step; + const char *label; +} hlds_e7_range_probe; + +bool disc_hlds_e7_memrange_sweep (disc *d, const char *json_path, const char *dump_prefix) { + static const hlds_e7_range_probe ranges[] = { + {0x7FFE0000U, 0x80080000U, 0x00000800U, "type4_dense_neighborhood"}, + {0x00000000U, 0x00040000U, 0x00000800U, "low_sram_dense"}, + {0x00370000U, 0x00390000U, 0x00000800U, "table_0038_dense"}, + {0x00A00000U, 0x00A40000U, 0x00000800U, "type1_dense_neighborhood"}, + {0x40000000U, 0x40010000U, 0x00000800U, "firmware_alias_dense"} + }; + static const u_int32_t probe_sectors[] = {0, 320}; + const char *path; + FILE *f; + FILE *df; + char dump_path[512]; + u_int8_t *buf; + u_int8_t readbuf[BLOCK_SIZE]; + u_int32_t len; + size_t r, j; + u_int32_t addr; + unsigned long tested; + unsigned long nonzero_windows; + unsigned long command_echo_windows; + unsigned long raw_table_windows; + unsigned long promotable_windows; + int best_score; + u_int32_t best_addr; + const char *best_range; + + if (!d) + return false; + path = (json_path && json_path[0]) ? json_path : "hlds_e7_memrange_sweep.json"; + len = RAW_BLOCK_SIZE; + buf = (u_int8_t *) malloc (len); + if (!buf) { + warning ("HLDS 0xE7 memrange sweep: out of memory"); + return false; + } + f = fopen (path, "wb"); + if (!f) { + free (buf); + warning ("HLDS 0xE7 memrange sweep: could not open %s for writing", path); + return false; + } + + fprintf (stderr, "\nHLDS 0xE7 memory-range sweep v1: using known memdump subcmd 0x01 only\n"); + fprintf (stderr, "HLDS 0xE7 memory-range sweep v1: dense address stride, no seed cracking, no dump attempt\n"); + fprintf (stderr, "HLDS 0xE7 memory-range sweep v1: writing JSON report to %s\n", path); + + tested = 0; + nonzero_windows = 0; + command_echo_windows = 0; + raw_table_windows = 0; + promotable_windows = 0; + best_score = -999999; + best_addr = 0; + best_range = "none"; + + fprintf (f, "{\n"); + fprintf (f, " \"sweep_version\": \"v1_known_memdump_dense_address_range\",\n"); + fprintf (f, " \"drive\": \""); + hlds_e7_json_escape (f, disc_get_drive_model_string (d)); + fprintf (f, "\",\n"); + fprintf (f, " \"notes\": \"This diagnostic uses only HIT 0xE7 subcmd 0x01, because the subcommand sweep showed only that subcommand returns nonzero data. It densely sweeps address ranges and promotes only sector-shaped 2064-byte raw headers or direct READ-payload echoes. Command echoes and sector-number table matches are recorded but not promoted.\",\n"); + fprintf (f, " \"read_length\": %u,\n", len); + fprintf (f, " \"probe_sectors\": [%u, %u],\n", probe_sectors[0], probe_sectors[1]); + fprintf (f, " \"ranges\": [\n"); + for (r = 0; r < sizeof (ranges) / sizeof (ranges[0]); r++) { + fprintf (f, " {\"label\": \""); + hlds_e7_json_escape (f, ranges[r].label); + fprintf (f, "\", \"start\": \"0x%08x\", \"end\": \"0x%08x\", \"step\": \"0x%08x\"}%s\n", + ranges[r].start, ranges[r].end, ranges[r].step, + (r + 1 < sizeof (ranges) / sizeof (ranges[0])) ? "," : ""); + } + fprintf (f, " ],\n"); + fprintf (f, " \"results\": [\n"); + + for (r = 0; r < sizeof (ranges) / sizeof (ranges[0]); r++) { + fprintf (stderr, " range %-28s 0x%08x..0x%08x step=0x%04x\n", + ranges[r].label, ranges[r].start, ranges[r].end, ranges[r].step); + for (addr = ranges[r].start; addr < ranges[r].end; addr += ranges[r].step) { + int addr_score = 0; + int addr_promotable = 0; + int addr_nonzero = 0; + int addr_command_echo = 0; + int addr_raw_table = 0; + bool first_result = (tested == 0); + + tested++; + if (!first_result) + fprintf (f, ",\n"); + fprintf (f, " {\n"); + fprintf (f, " \"range\": \""); + hlds_e7_json_escape (f, ranges[r].label); + fprintf (f, "\",\n"); + fprintf (f, " \"address\": \"0x%08x\",\n", addr); + fprintf (f, " \"sector_tests\": [\n"); + + for (j = 0; j < sizeof (probe_sectors) / sizeof (probe_sectors[0]); j++) { + int ret; + int read_ret; + int score = 0; + int exact_count = 0; + int raw_score = 0; + int user_score = 0; + int kk; + size_t raw_offset = (size_t) -1; + size_t user_offset = (size_t) -1; + size_t read_offset = (size_t) -1; + size_t command_echo_offset = (size_t) -1; + size_t offsets[SECTORS_PER_BLOCK]; + u_int32_t raw_sector = 0xFFFFFFFFU; + u_int32_t user_sector = 0xFFFFFFFFU; + u_int32_t hash = 0; + bool sector_shaped = false; + bool is_zero = true; + u_int8_t sample[16]; + + memset (buf, 0, len); + memset (readbuf, 0, sizeof (readbuf)); + memset (sample, 0, sizeof (sample)); + for (kk = 0; kk < SECTORS_PER_BLOCK; kk++) + offsets[kk] = (size_t) -1; + read_ret = dvd_read_sector_streaming (d -> dvd, probe_sectors[j], NULL, readbuf, sizeof (readbuf)); + ret = hlds_e7_raw_hit_data_in (d, 0x01, addr, len, buf); + if (ret >= 0) { + size_t zi; + memcpy (sample, buf, sizeof (sample)); + hash = hlds_e7_fnv1a32 (buf, len); + for (zi = 0; zi < len; zi++) { + if (buf[zi] != 0) { + is_zero = false; + break; + } + } + (void) hlds_e7_find_raw_header_match (buf, len, probe_sectors[j], &raw_offset, &raw_sector); + exact_count = hlds_e7_count_exact_raw_headers_for_block (buf, len, probe_sectors[j], offsets); + sector_shaped = hlds_e7_raw_header_offsets_are_sector_shaped (offsets); + command_echo_offset = hlds_e7_find_command_echo_offset (buf, len); + user_score = hlds_e7_find_user_data_match (buf, len, readbuf, sizeof (readbuf), &user_offset, &user_sector, &read_offset); + raw_score = sector_shaped ? 220 : exact_count; + score += raw_score + user_score; + if (command_echo_offset != (size_t) -1 && !sector_shaped && user_score <= 0) + score -= 10; + if (!is_zero) + addr_nonzero++; + if (command_echo_offset != (size_t) -1) + addr_command_echo++; + if (exact_count > 0 && !sector_shaped) + addr_raw_table++; + if (sector_shaped || user_score > 0) + addr_promotable++; + if (dump_prefix && dump_prefix[0] && !is_zero) { + snprintf (dump_path, sizeof (dump_path), "%s_%s_0x%08x_sector_%u.bin", + dump_prefix, ranges[r].label, addr, probe_sectors[j]); + df = fopen (dump_path, "wb"); + if (df) { + fwrite (buf, 1, len, df); + fclose (df); + } + } + } else { + score -= 50; + } + if (read_ret < 0) + score -= 20; + addr_score += score; + + fprintf (f, " {\"sector\": %u, \"read_ret\": %d, \"e7_ret\": %d, \"score\": %d, ", + probe_sectors[j], read_ret, ret, score); + fprintf (f, "\"nonzero\": %s, \"raw_header_found_count\": %d, \"raw_header_sector_shaped\": %s, ", + is_zero ? "false" : "true", exact_count, sector_shaped ? "true" : "false"); + fprintf (f, "\"raw_header_offset\": "); + if (raw_offset == (size_t) -1) + fprintf (f, "null, \"raw_header_sector\": null, "); + else + fprintf (f, "%lu, \"raw_header_sector\": %u, ", (unsigned long) raw_offset, raw_sector); + fprintf (f, "\"command_echo_offset\": "); + if (command_echo_offset == (size_t) -1) + fprintf (f, "null, "); + else + fprintf (f, "%lu, ", (unsigned long) command_echo_offset); + fprintf (f, "\"user_data_score\": %d, \"user_data_offset\": ", user_score); + if (user_offset == (size_t) -1) + fprintf (f, "null, \"user_data_sector_index\": null, \"read_probe_offset\": null, "); + else + fprintf (f, "%lu, \"user_data_sector_index\": %u, \"read_probe_offset\": %lu, ", + (unsigned long) user_offset, user_sector, (unsigned long) read_offset); + fprintf (f, "\"window_hash_fnv1a32\": \"0x%08x\", \"sample\": ", hash); + hlds_e7_json_bytes (f, sample, sizeof (sample)); + fprintf (f, "}%s\n", (j + 1 < sizeof (probe_sectors) / sizeof (probe_sectors[0])) ? "," : ""); + } + + if (addr_nonzero) + nonzero_windows += addr_nonzero; + if (addr_command_echo) + command_echo_windows += addr_command_echo; + if (addr_raw_table) + raw_table_windows += addr_raw_table; + if (addr_promotable) + promotable_windows += addr_promotable; + if (addr_score > best_score) { + best_score = addr_score; + best_addr = addr; + best_range = ranges[r].label; + } + + fprintf (f, " ],\n"); + fprintf (f, " \"total_score\": %d,\n", addr_score); + fprintf (f, " \"nonzero_sector_tests\": %d,\n", addr_nonzero); + fprintf (f, " \"command_echo_sector_tests\": %d,\n", addr_command_echo); + fprintf (f, " \"raw_table_like_sector_tests\": %d,\n", addr_raw_table); + fprintf (f, " \"promotable_sector_tests\": %d,\n", addr_promotable); + fprintf (f, " \"classification\": \"%s\"\n", + addr_promotable > 0 ? "promotable_candidate" : (addr_raw_table || addr_command_echo ? "sram_or_table_match" : (addr_nonzero ? "nonzero_no_cache" : "zero_or_no_response"))); + fprintf (f, " }"); + } + } + + fprintf (f, "\n ],\n"); + fprintf (f, " \"addresses_tested\": %lu,\n", tested); + fprintf (f, " \"nonzero_windows\": %lu,\n", nonzero_windows); + fprintf (f, " \"command_echo_windows\": %lu,\n", command_echo_windows); + fprintf (f, " \"raw_table_like_windows\": %lu,\n", raw_table_windows); + fprintf (f, " \"promotable_windows\": %lu,\n", promotable_windows); + fprintf (f, " \"best\": {\"range\": \""); + hlds_e7_json_escape (f, best_range); + fprintf (f, "\", \"address\": \"0x%08x\", \"score\": %d},\n", best_addr, best_score); + fprintf (f, " \"promotable_candidate_found\": %s,\n", promotable_windows ? "true" : "false"); + fprintf (f, " \"promotion_recommendation\": \"%s\",\n", + promotable_windows ? "review promotable address and try a targeted profile" : "do not promote; dense subcmd 0x01 address sweep found no sector-shaped cache or READ-payload echo"); + fprintf (f, " \"next_recommended_action\": \"%s\"\n", + promotable_windows ? "send JSON and matching dumped windows" : "focus on firmware handler/control-flow analysis or different pre-read/cache-fill sequences before another seed attempt"); + fprintf (f, "}\n"); + fclose (f); + free (buf); + + fprintf (stderr, "HLDS 0xE7 memory-range sweep v1 complete: addresses=%lu promotable_windows=%lu nonzero_windows=%lu command_echo_windows=%lu\n", + tested, promotable_windows, nonzero_windows, command_echo_windows); + return true; +} + + +static bool disc_probe_gdr8050l_e7_speed_profile (disc *d) { + static const hlds_e7_probe_candidate candidates[] = { + {443, 0x80000000U, 3, "Probe A 3-window no-prefetch: base 0x80000000, 3 windows"}, + {442, 0x80000000U, 2, "Probe B 2-window no-prefetch: base 0x80000000, 2 windows"}, + {445, 0x80000000U, 5, "Probe C 5-window guarded no-prefetch: base 0x80000000, 5 windows"}, + {44, 0x80000000U, 1, "Probe D proven fallback: base 0x80000000, 1 window"} + }; + static const u_int32_t probe_sectors[] = {0, 320}; + size_t i, j; + bool ok; + + if (!d || (dvd_get_hlds_e7_type (d -> dvd) != 44 && dvd_get_hlds_e7_type (d -> dvd) != 45)) + return true; + + hlds_e7_visible_probe_log ("GDR-8050L modified 0xE7 speed probe: single-window is proven; trying guarded no-prefetch multi-window profiles before seed cracking"); + for (i = 0; i < sizeof (candidates) / sizeof (candidates[0]); i++) { + hlds_e7_visible_probe_log ("GDR-8050L modified 0xE7 speed probe: %s", candidates[i].label); + dvd_set_hlds_e7_runtime_profile (d -> dvd, candidates[i].type, candidates[i].base, candidates[i].windows); + ok = true; + for (j = 0; j < sizeof (probe_sectors) / sizeof (probe_sectors[0]); j++) { + disc_cache_clear (d); + if (!disc_read_sector (d, probe_sectors[j], NULL, NULL) || dvd_get_hlds_e7_type (d -> dvd) != candidates[i].type) { + ok = false; + break; + } + } + disc_cache_clear (d); + if (ok) { + hlds_e7_visible_probe_log ("GDR-8050L modified 0xE7 speed probe: selected %s", candidates[i].label); + return true; + } + hlds_e7_visible_probe_log ("GDR-8050L modified 0xE7 speed probe: failed %s", candidates[i].label); + } + + dvd_set_hlds_e7_runtime_profile (d -> dvd, 44, 0x80000000U, 1); + disc_cache_clear (d); + hlds_e7_visible_probe_log ("GDR-8050L modified 0xE7 speed probe: all accelerated profiles failed; using proven single-window fallback"); + return true; +} + +static bool disc_probe_gdr8081n_e7_profile (disc *d) { + static const hlds_e7_probe_candidate candidates[] = { + {815, 0x80000000U, 5, "Probe A strict scan-guided Type4-derived: base 0x80000000, 5 windows"} + }; + size_t i; + + if (!d || dvd_get_hlds_e7_type (d -> dvd) != 81) + return true; + + hlds_e7_visible_probe_log ("GDR-8081N 0xE7 profile probe: drive is experimental; trying strict scan-guided profile only; exact-offset fallbacks were removed to avoid 10-minute false-negative loops"); + for (i = 0; i < sizeof (candidates) / sizeof (candidates[0]); i++) { + hlds_e7_visible_probe_log ("GDR-8081N 0xE7 profile probe: %s", candidates[i].label); + dvd_set_hlds_e7_runtime_profile (d -> dvd, candidates[i].type, candidates[i].base, candidates[i].windows); + disc_cache_clear (d); + if (disc_read_sector (d, 0, NULL, NULL)) { + hlds_e7_visible_probe_log ("GDR-8081N 0xE7 profile probe: selected %s", candidates[i].label); + return true; + } + hlds_e7_visible_probe_log ("GDR-8081N 0xE7 profile probe: failed %s", candidates[i].label); + } + + dvd_set_hlds_e7_runtime_profile (d -> dvd, 81, 0x80000000U, 5); + disc_cache_clear (d); + hlds_e7_visible_probe_log ("GDR-8081N 0xE7 profile probe: strict scan-guided candidate failed; run --hlds-e7-scan with --scan-dump-prefix and inspect strict/user-data fields before another seed attempt"); + return false; +} + +static bool disc_crack_seeds (disc *d) { + int i; + + /* As a Nintendo GameCube/Wii disc should not have too many keys, 20 should be enough */ + debug ("Retrieving all DVD seeds"); + if (!disc_probe_gdr8050l_e7_speed_profile (d)) + return false; + if (!disc_probe_gdr8081n_e7_profile (d)) + return false; + for (i = 0; i < 20 * 16; i += 16) { + if (!disc_read_sector (d, i, NULL, NULL)) + return false; + } + + return true; +} + + +/** + * Creates a new structure representing a Nintendo GameCube/Wii optical disc. + * @param dvd_device The CD/DVD-ROM device, in OS-dependent format (i.e.: /dev/something on Unix, x: on Windows). + * @return The newly-created structure, to be used with the other commands. + */ +disc *disc_new (char *dvd_device, u_int32_t command) { + dvd_drive *dvd; + disc *d; + + if ((dvd = dvd_drive_new (dvd_device, command))) { + d = (disc *) malloc (sizeof (disc)); + memset (d, 0, sizeof (disc)); + d -> dvd = dvd; + d -> u = unscrambler_new (); + disc_set_unscrambling (d, true); // Unscramble by default + disc_set_read_method (d, DEFAULT_READ_METHOD); + disc_cache_init (d, DISC_DEFAULT_CACHE_SIZE); + } else { + d = NULL; + } + + return (d); +} + + +int disc_media_preflight (disc *d, unsigned int timeout_ms, int *sense_key, int *asc, int *ascq) { + req_sense sense; + unsigned int elapsed = 0; + const unsigned int interval_ms = 500; + int rc; + + if (sense_key) *sense_key = 0; + if (asc) *asc = 0; + if (ascq) *ascq = 0; + if (!d || !d -> dvd) + return -1; + + for (;;) { + u_int32_t sectors = 0, sector_size = 0; + + memset (&sense, 0, sizeof (sense)); + rc = dvd_test_unit_ready (d -> dvd, &sense); + if (rc >= 0) { + /* Some optical drives and USB bridges report TEST UNIT READY=GOOD + * with an empty tray. Require a second, media-dependent command + * before allowing vendor seed/cache reads. */ + memset (&sense, 0, sizeof (sense)); + rc = dvd_read_capacity_10 (d -> dvd, §ors, §or_size, &sense); + if (rc >= 0 && sectors > 1 && sector_size == SECTOR_SIZE) + return 1; + /* A successful command with zero/invalid capacity is not proof of media. */ + if (rc >= 0) { + if (sense_key) *sense_key = 0; + if (asc) *asc = 0; + if (ascq) *ascq = 0; + return 0; + } + } + + if (sense_key) *sense_key = sense.sense_key; + if (asc) *asc = sense.asc; + if (ascq) *ascq = sense.ascq; + + /* SPC/MMC: NOT READY / MEDIUM NOT PRESENT. */ + if ((sense.sense_key & 0x0f) == 0x02 && sense.asc == 0x3a) + return 0; + + /* Retry transient becoming-ready / unit-attention states. */ + if (!(((sense.sense_key & 0x0f) == 0x02 && sense.asc == 0x04) || + ((sense.sense_key & 0x0f) == 0x06 && (sense.asc == 0x28 || sense.asc == 0x29)))) + return -1; + if (elapsed >= timeout_ms) + return -1; +#ifdef WIN32 + Sleep (interval_ms); +#else + usleep ((useconds_t) interval_ms * 1000); +#endif + elapsed += interval_ms; + } +} + +bool disc_init (disc *d, u_int32_t disctype, u_int32_t sectors_no) { + bool out; + + d -> sectors_no = 1000; // TODO + disc_detect_type (d, disctype, sectors_no); + if (d -> type != DISC_TYPE_XBOX && !disc_crack_seeds (d)) + return false; +// unscrambler_set_bruteforce (d -> u, false); // Disabling bruteforcing will allow us to detect errors more quickly + unscrambler_set_bruteforce (d -> u, true); + if (d -> type==DISC_TYPE_DVD) { + my_strdup (d -> title, "DVD"); + out = true; + } + else if (d -> type==DISC_TYPE_XBOX) { + my_strdup (d -> title, "Xbox DVD"); + d -> system_id = 'X'; + strncpy (d -> game_id, "XB", sizeof (d -> game_id)); + strncpy (d -> maker, "MS", sizeof (d -> maker)); + my_strdup (d -> version_string, "N/A"); + d -> has_update = false; + disc_set_unscrambling (d, false); + out = true; + } + else if (disc_analyze (d)) { + disc_check_update (d); + out = true; + } else { + out = false; + } + + return (out); +} + + +/** + * Frees resources used by a disc structure and destroys it. + * @param d The disc structure. + * @return NULL. + */ +void *disc_destroy (disc *d) { + disc_cache_destroy (d); + unscrambler_destroy (d -> u); + my_free (d -> version_string); + my_free (d -> title); + dvd_drive_destroy (d -> dvd); + my_free (d); + + return (NULL); +} + + +bool disc_is_xbox_unlock_drive (disc *d) { + return d && dvd_is_xbox_unlock_drive (d -> dvd); +} + +bool disc_is_xbox_challenge_drive (disc *d) { + return d && dvd_is_xbox_challenge_drive (d -> dvd); +} + +bool disc_is_xbox_vendor_unlock_drive (disc *d) { + return d && dvd_is_xbox_vendor_unlock_drive (d -> dvd); +} + +int disc_xbox_lock (disc *d) { + u_int32_t sectors = 0; + u_int32_t sector_size = 0; + + if (!d || d -> type != DISC_TYPE_XBOX) + return -1; + + if (dvd_is_xbox_vendor_unlock_drive (d -> dvd)) { + if (dvd_xbox_vendor_lock (d -> dvd) < 0) + return -1; + } + + if (dvd_read_capacity_10 (d -> dvd, §ors, §or_size, NULL) == 0 && sector_size == SECTOR_SIZE) + d -> sectors_no = sectors; + return 0; +} + + +int disc_xbox_unlock (disc *d) { + u_int32_t sectors = 0; + u_int32_t sector_size = 0; + + if (!d || d -> type != DISC_TYPE_XBOX) + return -1; + + if (dvd_is_xbox_challenge_drive (d -> dvd)) { + if (dvd_xbox_gdr8050l_unlock (d -> dvd, §ors) < 0) + return -1; + d -> sectors_no = sectors; + return 0; + } + + if (dvd_is_xbox_vendor_unlock_drive (d -> dvd)) { + if (dvd_xbox_vendor_unlock_wxripper (d -> dvd, §ors) < 0) + return -1; + d -> sectors_no = sectors; + return 0; + } + + /* Forced Xbox mode on an unknown drive keeps FriiDump's direct READ(10) + * experiment path, but no model-specific unlock is applied. */ + if (dvd_read_capacity_10 (d -> dvd, §ors, §or_size, NULL) == 0 && sector_size == SECTOR_SIZE) + d -> sectors_no = sectors; + + return 0; +} + + +int disc_xbox_read_10 (disc *d, u_int32_t sector, u_int32_t sectors, u_int8_t *buf, size_t bufsize) { + if (!d || d -> type != DISC_TYPE_XBOX || !buf) + return -1; + return dvd_read_10 (d -> dvd, sector, sectors, NULL, buf, bufsize); +} + + +int disc_xbox_read_dvd_structure (disc *d, u_int8_t format, u_int8_t layer, u_int8_t *buf, size_t bufsize) { + if (!d || d -> type != DISC_TYPE_XBOX || !buf) + return -1; + return dvd_read_dvd_structure (d -> dvd, format, layer, buf, bufsize, NULL); +} + + +int disc_xbox_read_capacity_10 (disc *d, u_int32_t *sectors, u_int32_t *sector_size) { + if (!d || d -> type != DISC_TYPE_XBOX) + return -1; + return dvd_read_capacity_10 (d -> dvd, sectors, sector_size, NULL); +} + + + +int disc_xbox_recovery_kick (disc *d, bool auth_recovery) { + if (!d || d -> type != DISC_TYPE_XBOX) + return -1; + return dvd_xbox_recovery_kick (d -> dvd, auth_recovery); +} + +int disc_refresh_volume (disc *d) { + if (!d) + return -1; + return dvd_refresh_volume (d -> dvd); +} + +int disc_lock_volume (disc *d) { + if (!d) + return -1; + return dvd_lock_volume (d -> dvd); +} + +int disc_xbox_refresh_volume (disc *d) { + if (!d || d -> type != DISC_TYPE_XBOX) + return -1; + return dvd_xbox_refresh_volume (d -> dvd); +} + +int disc_xbox_lock_volume (disc *d) { + if (!d || d -> type != DISC_TYPE_XBOX) + return -1; + return dvd_xbox_lock_volume (d -> dvd); +} + +int disc_xbox_media_cycle (disc *d) { + if (!d || d -> type != DISC_TYPE_XBOX) + return -1; + return dvd_media_cycle (d -> dvd, NULL); +} + +int disc_xbox_wait_ready (disc *d, unsigned int timeout_ms) { + if (!d || d -> type != DISC_TYPE_XBOX) + return -1; + return dvd_wait_ready (d -> dvd, timeout_ms); +} + +char *disc_get_drive_model_string (disc *d) { + return (dvd_get_model_string (d -> dvd)); +} + + +char *disc_get_device (disc *d) { + return (dvd_get_device (d -> dvd)); +} + +void *disc_get_native_handle (disc *d) { + if (!d) return NULL; + return dvd_get_native_handle (d -> dvd); +} + + +bool disc_get_drive_support_status (disc *d) { + return (dvd_get_support_status (d -> dvd)); +} + +const char *disc_get_hlds_e7_profile_name (disc *d) { + return d ? dvd_get_hlds_e7_profile_name (d -> dvd) : "none"; +} + +const char *disc_get_hlds_e7_support_tier (disc *d) { + return d ? dvd_get_hlds_e7_support_tier (d -> dvd) : "none"; +} + +const char *disc_get_hlds_e7_family (disc *d) { + return d ? dvd_get_hlds_e7_family (d -> dvd) : "none"; +} + +const char *disc_get_hlds_e7_tokens (disc *d) { + return d ? dvd_get_hlds_e7_tokens (d -> dvd) : ""; +} + +const char *disc_get_hlds_e7_record_id (disc *d) { + return d ? dvd_get_hlds_e7_record_id (d -> dvd) : ""; +} + +const char *disc_get_hlds_e7_notes (disc *d) { + return d ? dvd_get_hlds_e7_notes (d -> dvd) : ""; +} + +u_int32_t disc_get_hlds_e7_type (disc *d) { + return d ? dvd_get_hlds_e7_type (d -> dvd) : 0; +} + +u_int32_t disc_get_hlds_e7_cache_base (disc *d) { + return d ? dvd_get_hlds_e7_cache_base (d -> dvd) : 0; +} + +u_int32_t disc_get_hlds_e7_mem_blocks (disc *d) { + return d ? dvd_get_hlds_e7_mem_blocks (d -> dvd) : 0; +} + +u_int32_t disc_get_hlds_e7_static_cdb_base (disc *d) { + return d ? dvd_get_hlds_e7_static_cdb_base (d -> dvd) : 0; +} + +u_int32_t disc_get_hlds_e7_static_gate (disc *d) { + return d ? dvd_get_hlds_e7_static_gate (d -> dvd) : 0; +} + +int disc_get_hlds_e7_preferred_method (disc *d) { + return d ? dvd_get_hlds_e7_preferred_method (d -> dvd) : -1; +} + +void disc_set_speed (disc *d, u_int32_t speed) { + if (speed != -1) dvd_set_speed (d -> dvd, speed, NULL); +} + +void disc_set_streaming_speed (disc *d, u_int32_t speed) { + if (speed != -1) dvd_set_streaming (d -> dvd, speed, NULL); +} + +bool disc_stop_unit (disc *d, bool start) { + if (dvd_stop_unit (d -> dvd, start, NULL) == 0) return true; + else return false; +} + +void init_range (disc *d, u_int32_t sec_disc, u_int32_t sec_mem) { + if ((sec_disc>=1)&&(sec_disc<=100)) d->sec_disc = sec_disc; + else d->sec_disc = -1; + if ((sec_mem>=16)&&(sec_mem<=100)) d->sec_mem = sec_mem; + else d->sec_mem = -1; +} \ No newline at end of file diff --git a/libfriidump/disc.h b/libfriidump/disc.h new file mode 100644 index 0000000..345e681 --- /dev/null +++ b/libfriidump/disc.h @@ -0,0 +1,132 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef DISC_H_INCLUDED +#define DISC_H_INCLUDED + +#include "misc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + DISC_TYPE_GAMECUBE, + DISC_TYPE_WII, + DISC_TYPE_WII_DL, + DISC_TYPE_DVD, + DISC_TYPE_XBOX +} disc_type; + + +typedef enum { + DISC_REGION_PAL, + DISC_REGION_NTSC, + DISC_REGION_JAPAN, + DISC_REGION_AUSTRALIA, + DISC_REGION_FRANCE, + DISC_REGION_GERMANY, + DISC_REGION_ITALY, + DISC_REGION_SPAIN, + DISC_REGION_PAL_X, + DISC_REGION_PAL_Y, + DISC_REGION_UNKNOWN +} disc_region; + + +typedef struct disc_s disc; + + +/* Functions */ +FRIIDUMPLIB_EXPORT disc *disc_new (char *dvd_device, u_int32_t command); +FRIIDUMPLIB_EXPORT bool disc_init (disc *d, u_int32_t forced_type, u_int32_t sectors_no); +FRIIDUMPLIB_EXPORT void *disc_destroy (disc *d); +FRIIDUMPLIB_EXPORT int disc_read_sector (disc *d, u_int32_t sector_no, u_int8_t **data, u_int8_t **rawdata); +FRIIDUMPLIB_EXPORT bool disc_set_read_method (disc *d, int method); +FRIIDUMPLIB_EXPORT void disc_set_unscrambling (disc *d, bool unscramble); +FRIIDUMPLIB_EXPORT void disc_set_speed (disc *d, u_int32_t speed); +FRIIDUMPLIB_EXPORT void disc_set_streaming_speed (disc *d, u_int32_t speed); +FRIIDUMPLIB_EXPORT bool disc_stop_unit (disc *d, bool start); +FRIIDUMPLIB_EXPORT void init_range (disc *d, u_int32_t sec_disc, u_int32_t sec_mem); +FRIIDUMPLIB_EXPORT bool disc_hlds_e7_scan (disc *d, const char *json_path, const char *dump_prefix); +FRIIDUMPLIB_EXPORT bool disc_hlds_e7_subcmd_sweep (disc *d, const char *json_path, const char *dump_prefix); +FRIIDUMPLIB_EXPORT bool disc_hlds_e7_memrange_sweep (disc *d, const char *json_path, const char *dump_prefix); + +/* Getters */ +FRIIDUMPLIB_EXPORT u_int32_t disc_get_sectors_no (disc *d); +FRIIDUMPLIB_EXPORT u_int32_t disc_get_layerbreak (disc *d); +FRIIDUMPLIB_EXPORT u_int32_t disc_get_command (disc *d); +FRIIDUMPLIB_EXPORT u_int32_t disc_get_method (disc *d); +FRIIDUMPLIB_EXPORT u_int32_t disc_get_def_method (disc *d); +FRIIDUMPLIB_EXPORT u_int32_t disc_get_sec_disc (disc *d); +FRIIDUMPLIB_EXPORT u_int32_t disc_get_sec_mem (disc *d); + +FRIIDUMPLIB_EXPORT char *disc_get_type (disc *d, disc_type *dt, char **dt_s); +FRIIDUMPLIB_EXPORT char *disc_get_gameid (disc *d, char **gid_s); +FRIIDUMPLIB_EXPORT char *disc_get_region (disc *d, disc_region *dr, char **dr_s); +FRIIDUMPLIB_EXPORT char *disc_get_maker (disc *d, char **m, char **m_s); +FRIIDUMPLIB_EXPORT char *disc_get_version (disc *d, u_int8_t *v, char **v_s); +FRIIDUMPLIB_EXPORT char *disc_get_title (disc *d, char **t_s); +FRIIDUMPLIB_EXPORT bool disc_get_update (disc *d); + +FRIIDUMPLIB_EXPORT char *disc_get_drive_model_string (disc *d); +FRIIDUMPLIB_EXPORT char *disc_get_device (disc *d); +FRIIDUMPLIB_EXPORT void *disc_get_native_handle (disc *d); +FRIIDUMPLIB_EXPORT bool disc_get_drive_support_status (disc *d); +FRIIDUMPLIB_EXPORT const char *disc_get_hlds_e7_profile_name (disc *d); +FRIIDUMPLIB_EXPORT const char *disc_get_hlds_e7_support_tier (disc *d); +FRIIDUMPLIB_EXPORT const char *disc_get_hlds_e7_family (disc *d); +FRIIDUMPLIB_EXPORT const char *disc_get_hlds_e7_tokens (disc *d); +FRIIDUMPLIB_EXPORT const char *disc_get_hlds_e7_record_id (disc *d); +FRIIDUMPLIB_EXPORT const char *disc_get_hlds_e7_notes (disc *d); +FRIIDUMPLIB_EXPORT u_int32_t disc_get_hlds_e7_type (disc *d); +FRIIDUMPLIB_EXPORT u_int32_t disc_get_hlds_e7_cache_base (disc *d); +FRIIDUMPLIB_EXPORT u_int32_t disc_get_hlds_e7_mem_blocks (disc *d); +FRIIDUMPLIB_EXPORT u_int32_t disc_get_hlds_e7_static_cdb_base (disc *d); +FRIIDUMPLIB_EXPORT u_int32_t disc_get_hlds_e7_static_gate (disc *d); +FRIIDUMPLIB_EXPORT int disc_get_hlds_e7_preferred_method (disc *d); + +/* Xbox/MMC helpers used by Xbox/XGD dump layouts. These dispatch to the + * GDR-8050L challenge-table path, the GDR-3120L/Kreon-style FF 08 01 + * vendor lock-state path, or direct READ(10) for forced experiments. */ +FRIIDUMPLIB_EXPORT bool disc_is_xbox_unlock_drive (disc *d); +FRIIDUMPLIB_EXPORT bool disc_is_xbox_challenge_drive (disc *d); +FRIIDUMPLIB_EXPORT bool disc_is_xbox_vendor_unlock_drive (disc *d); +FRIIDUMPLIB_EXPORT int disc_xbox_lock (disc *d); +FRIIDUMPLIB_EXPORT int disc_xbox_unlock (disc *d); +FRIIDUMPLIB_EXPORT int disc_xbox_read_10 (disc *d, u_int32_t sector, u_int32_t sectors, u_int8_t *buf, size_t bufsize); +FRIIDUMPLIB_EXPORT int disc_xbox_read_dvd_structure (disc *d, u_int8_t format, u_int8_t layer, u_int8_t *buf, size_t bufsize); +FRIIDUMPLIB_EXPORT int disc_xbox_read_capacity_10 (disc *d, u_int32_t *sectors, u_int32_t *sector_size); +FRIIDUMPLIB_EXPORT int disc_xbox_recovery_kick (disc *d, bool auth_recovery); +FRIIDUMPLIB_EXPORT int disc_xbox_refresh_volume (disc *d); +FRIIDUMPLIB_EXPORT int disc_lock_volume (disc *d); +FRIIDUMPLIB_EXPORT int disc_refresh_volume (disc *d); +FRIIDUMPLIB_EXPORT int disc_xbox_lock_volume (disc *d); +FRIIDUMPLIB_EXPORT int disc_xbox_media_cycle (disc *d); +FRIIDUMPLIB_EXPORT int disc_xbox_wait_ready (disc *d, unsigned int timeout_ms); +FRIIDUMPLIB_EXPORT int disc_media_preflight (disc *d, unsigned int timeout_ms, int *sense_key, int *asc, int *ascq); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libfriidump/dumper.c b/libfriidump/dumper.c new file mode 100644 index 0000000..23d99be --- /dev/null +++ b/libfriidump/dumper.c @@ -0,0 +1,1489 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://www.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "misc.h" +#include +#include +#include +#include +#include +#include "constants.h" +#include "disc.h" +#include "dumper.h" +#include "xbox_ref_bridge.h" +#include "xbox_ref/xbox_ref_log.h" + +#ifndef WIN32 +#include +#include +#endif + +struct dumper_s { + disc *dsk; + char *outfile_raw; + u_int32_t start_sector_raw; + FILE *fp_raw; + char *outfile_iso; + u_int32_t start_sector_iso; + FILE *fp_iso; + char *outfile_xiso; + u_int32_t start_sector_xiso; + FILE *fp_xiso; + u_int32_t start_sector; + bool hashing; + bool flushing; + + multihash hash_raw; + multihash hash_iso; + multihash hash_xiso; + + progress_func progress; + void *progress_data; +}; + +#define DUMPER_READ_BLOCK_RETRIES 3 + +static bool dumper_read_sector_with_retry (dumper *dmp, u_int32_t sector_no, u_int8_t **isobuf, u_int8_t **rawbuf) { + int attempt; + bool need_iso, need_raw; + + if (!dmp || !dmp -> dsk) + return false; + + need_iso = dmp -> fp_iso != NULL; + need_raw = dmp -> fp_raw != NULL; + + for (attempt = 0; attempt < DUMPER_READ_BLOCK_RETRIES; attempt++) { + if (isobuf) *isobuf = NULL; + if (rawbuf) *rawbuf = NULL; + + disc_read_sector (dmp -> dsk, sector_no, isobuf, rawbuf); + if ((!need_iso || (isobuf && *isobuf)) && (!need_raw || (rawbuf && *rawbuf))) { + if (attempt > 0) + warning ("Recovered sectors %u..%u after dump-level retry %d", sector_no, sector_no + SECTORS_PER_BLOCK - 1, attempt); + return true; + } + + if (attempt + 1 < DUMPER_READ_BLOCK_RETRIES) + warning ("Dump-level read retry %d/%d for sectors %u..%u", attempt + 1, DUMPER_READ_BLOCK_RETRIES - 1, sector_no, sector_no + SECTORS_PER_BLOCK - 1); + } + + return false; +} + +#define XBOX_XISO_LEADIN_SECTORS 32 +#define XBOX_XISO_STANDARD_GAME_LBA 32 +#define XBOX_XISO_START_LBA_MAGIC 306112 +#define XBOX_XISO_DUAL_LAYER_END_LBA 1913920 + +/* DiscImageCreator-compatible Original Xbox/XGD1 2048-byte-sector layout + * constants. DIC uses XBOX_SIZE=3820880, XBOX_LAYER_BREAK=1913776, + * DVD start PSN 0x30000, and Xbox start PSN 0x60600. The virtual redump + * layout below follows those constants: video L0, middle/padding, game + * partition, middle/padding, and video L1 tail. + */ +#define XBOX_XGD1_DIC_DVD_START_PSN 0x30000 +#define XBOX_XGD1_DIC_XBOX_START_PSN 0x60600 +#define XBOX_XGD1_DIC_XBOX_SIZE 3820880 +#define XBOX_XGD1_DIC_LAYER_BREAK 1913776 +#define XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS 3431264 +#define XBOX_XGD1_FULL_REDUMP_SECTORS XBOX_XGD1_DIC_XBOX_SIZE +#define XBOX_XGD1_GAME_OUTPUT_START_LBA (XBOX_XGD1_DIC_XBOX_START_PSN - XBOX_XGD1_DIC_DVD_START_PSN) +#define XBOX_XGD1_VIDEO_L0_SECTORS 6832 +#define XBOX_XGD1_VIDEO_L1_SECTORS 160 +#define XBOX_XGD1_VIDEO_L1_OUTPUT_START_LBA (XBOX_XGD1_FULL_REDUMP_SECTORS - XBOX_XGD1_VIDEO_L1_SECTORS) +#define XBOX_XGD1_GAME_SOURCE_SECTORS (XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS - XBOX_XISO_LEADIN_SECTORS) +#define XBOX_XGD1_LAYER_THRESHOLD 2000000 +#define XBOX_METADATA_PATH_MAX 4096 + +static u_int32_t read_le32 (const u_int8_t *p) { + return ((u_int32_t) p[0]) | ((u_int32_t) p[1] << 8) | ((u_int32_t) p[2] << 16) | ((u_int32_t) p[3] << 24); +} + +static bool xbox_probe_xdfs_volume (disc *d, u_int32_t lba, u_int32_t *root_lba, u_int32_t *volume_size_raw) { + u_int8_t sector[SECTOR_SIZE]; + + memset (sector, 0, sizeof (sector)); + if (disc_xbox_read_10 (d, lba, 1, sector, sizeof (sector)) < 0) + return false; + + if (memcmp (sector, "MICROSOFT", 9) != 0) + return false; + + if (root_lba) + *root_lba = read_le32 (sector + 0x14); + if (volume_size_raw) + *volume_size_raw = read_le32 (sector + 0x1C); + + return true; +} + +static bool xbox_redump_layout_constants_match_dic (void) { + return XBOX_XGD1_FULL_REDUMP_SECTORS == XBOX_XGD1_DIC_XBOX_SIZE && + XBOX_XGD1_DIC_LAYER_BREAK == 1913776 && + XBOX_XGD1_GAME_OUTPUT_START_LBA == 0x30600 && + XBOX_XGD1_VIDEO_L1_OUTPUT_START_LBA == 3820720 && + XBOX_XGD1_VIDEO_L0_SECTORS + + (XBOX_XGD1_GAME_OUTPUT_START_LBA - XBOX_XGD1_VIDEO_L0_SECTORS) + + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS + + (XBOX_XGD1_VIDEO_L1_OUTPUT_START_LBA - + (XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS)) + + XBOX_XGD1_VIDEO_L1_SECTORS == XBOX_XGD1_FULL_REDUMP_SECTORS; +} + + +typedef struct { + bool has_pfi; + bool has_dmi; + bool pfi_written; + bool dmi_written; + u_int8_t pfi[2048]; + u_int8_t dmi[2048]; + u_int32_t locked_visible_sectors; + u_int32_t unlocked_visible_sectors; + u_int32_t output_sectors; + u_int32_t game_source_lba; + u_int32_t game_source_sectors; + u_int32_t game_leadin_read_sectors; + u_int32_t game_leadin_zero_sectors; + char pfi_path[XBOX_METADATA_PATH_MAX]; + char dmi_path[XBOX_METADATA_PATH_MAX]; + char json_path[XBOX_METADATA_PATH_MAX]; +} xbox_redump_metadata; + +static void xbox_make_related_path (const char *filename, const char *suffix, char *out, size_t out_size) { + const char *slash1, *slash2, *slash, *dot; + size_t suffix_len, base_len; + + if (!out || out_size == 0) + return; + out[0] = '\0'; + if (!filename || !suffix) + return; + + suffix_len = strlen (suffix); + if (out_size <= suffix_len + 1) + return; + + slash1 = strrchr (filename, '/'); + slash2 = strrchr (filename, '\\'); + if (slash1 && slash2) + slash = slash1 > slash2 ? slash1 : slash2; + else + slash = slash1 ? slash1 : slash2; + + dot = strrchr (filename, '.'); + if (!dot || (slash && dot < slash)) + dot = filename + strlen (filename); + + base_len = (size_t) (dot - filename); + if (base_len + suffix_len + 1 > out_size) + base_len = out_size - suffix_len - 1; + memcpy (out, filename, base_len); + out[base_len] = '\0'; + strncat (out, suffix, out_size - strlen (out) - 1); +} + +static const char *xbox_path_leaf (const char *path) { + const char *slash1, *slash2; + if (!path) + return ""; + slash1 = strrchr (path, '/'); + slash2 = strrchr (path, '\\'); + if (slash1 && slash2) + return (slash1 > slash2 ? slash1 : slash2) + 1; + if (slash1) + return slash1 + 1; + if (slash2) + return slash2 + 1; + return path; +} + +static void xbox_json_string (FILE *f, const char *s) { + fputc ('"', f); + if (s) { + while (*s) { + unsigned char c = (unsigned char) *s++; + if (c == '"' || c == '\\') { + fputc ('\\', f); + fputc (c, f); + } else if (c == '\n') { + fputs ("\\n", f); + } else if (c == '\r') { + fputs ("\\r", f); + } else if (c == '\t') { + fputs ("\\t", f); + } else if (c < 0x20) { + fprintf (f, "\\u%04x", c); + } else { + fputc (c, f); + } + } + } + fputc ('"', f); +} + +static bool xbox_write_binary_file (const char *path, const u_int8_t *data, size_t len) { + FILE *f; + if (!path || !data || len == 0) + return false; + f = fopen (path, "wb"); + if (!f) + return false; + if (fwrite (data, 1, len, f) != len) { + fclose (f); + return false; + } + return fclose (f) == 0; +} + +static void xbox_capture_pfi_dmi (disc *d, xbox_redump_metadata *meta) { + if (!d || !meta) + return; + if (!meta -> has_pfi) + meta -> has_pfi = disc_xbox_read_dvd_structure (d, 0x00, 0x00, meta -> pfi, sizeof (meta -> pfi)) >= 0; + if (!meta -> has_dmi) + meta -> has_dmi = disc_xbox_read_dvd_structure (d, 0x04, 0x00, meta -> dmi, sizeof (meta -> dmi)) >= 0; +} + +static void xbox_write_redump_metadata (dumper *dmp, xbox_redump_metadata *meta) { + FILE *json; + const char *pfi_leaf; + const char *dmi_leaf; + + if (!dmp || !meta || !dmp -> outfile_iso) + return; + + xbox_make_related_path (dmp -> outfile_iso, ".pfi.bin", meta -> pfi_path, sizeof (meta -> pfi_path)); + xbox_make_related_path (dmp -> outfile_iso, ".dmi.bin", meta -> dmi_path, sizeof (meta -> dmi_path)); + xbox_make_related_path (dmp -> outfile_iso, ".redump.json", meta -> json_path, sizeof (meta -> json_path)); + + if (meta -> has_pfi) + meta -> pfi_written = xbox_write_binary_file (meta -> pfi_path, meta -> pfi, sizeof (meta -> pfi)); + if (meta -> has_dmi) + meta -> dmi_written = xbox_write_binary_file (meta -> dmi_path, meta -> dmi, sizeof (meta -> dmi)); + + json = fopen (meta -> json_path, "wb"); + if (!json) { + warning ("Could not write Xbox redump metadata JSON"); + return; + } + + pfi_leaf = meta -> pfi_written ? xbox_path_leaf (meta -> pfi_path) : NULL; + dmi_leaf = meta -> dmi_written ? xbox_path_leaf (meta -> dmi_path) : NULL; + + fprintf (json, "{\n"); + fprintf (json, " \"format\": \"friidump-xbox-redump\",\n"); + fprintf (json, " \"version\": 1,\n"); + fprintf (json, " \"image\": {\n"); + fprintf (json, " \"file\": "); xbox_json_string (json, xbox_path_leaf (dmp -> outfile_iso)); fprintf (json, ",\n"); + fprintf (json, " \"sector_size\": %u,\n", SECTOR_SIZE); + fprintf (json, " \"sector_count\": %u,\n", meta -> output_sectors); + fprintf (json, " \"byte_count\": %llu,\n", (unsigned long long) meta -> output_sectors * SECTOR_SIZE); + fprintf (json, " \"hashes\": {\n"); + fprintf (json, " \"crc32\": "); xbox_json_string (json, dumper_get_iso_crc32 (dmp)); fprintf (json, ",\n"); + fprintf (json, " \"md5\": "); xbox_json_string (json, dumper_get_iso_md5 (dmp)); fprintf (json, ",\n"); + fprintf (json, " \"sha1\": "); xbox_json_string (json, dumper_get_iso_sha1 (dmp)); fprintf (json, ",\n"); + fprintf (json, " \"sha256\": "); xbox_json_string (json, dumper_get_iso_sha2 (dmp)); fprintf (json, "\n"); + fprintf (json, " }\n"); + fprintf (json, " },\n"); + fprintf (json, " \"drive\": {\n"); + fprintf (json, " \"model\": "); xbox_json_string (json, disc_get_drive_model_string (dmp -> dsk)); fprintf (json, ",\n"); + fprintf (json, " \"command\": %u,\n", disc_get_command (dmp -> dsk)); + fprintf (json, " \"method\": %u\n", disc_get_method (dmp -> dsk)); + fprintf (json, " },\n"); + fprintf (json, " \"layout\": {\n"); + fprintf (json, " \"layout_type\": \"original_xbox_xgd1_redump_style_2048\",\n"); + fprintf (json, " \"locked_visible_sector_count\": %u,\n", meta -> locked_visible_sectors); + fprintf (json, " \"unlocked_visible_sector_count\": %u,\n", meta -> unlocked_visible_sectors); + fprintf (json, " \"video_l0_start_lba\": 0,\n"); + fprintf (json, " \"video_l0_sector_count\": %u,\n", XBOX_XGD1_VIDEO_L0_SECTORS); + fprintf (json, " \"pregame_padding_start_lba\": %u,\n", XBOX_XGD1_VIDEO_L0_SECTORS); + fprintf (json, " \"pregame_padding_sector_count\": %u,\n", XBOX_XGD1_GAME_OUTPUT_START_LBA - XBOX_XGD1_VIDEO_L0_SECTORS); + fprintf (json, " \"game_output_start_lba\": %u,\n", XBOX_XGD1_GAME_OUTPUT_START_LBA); + fprintf (json, " \"game_leadin_sector_count\": %u,\n", XBOX_XISO_LEADIN_SECTORS); + fprintf (json, " \"game_unlocked_source_start_lba\": %u,\n", meta -> game_source_lba); + fprintf (json, " \"game_unlocked_source_sector_count\": %u,\n", meta -> game_source_sectors); + fprintf (json, " \"postgame_padding_start_lba\": %u,\n", XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS); + fprintf (json, " \"postgame_padding_sector_count\": %u,\n", XBOX_XGD1_VIDEO_L1_OUTPUT_START_LBA - (XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS)); + fprintf (json, " \"video_l1_start_lba\": %u,\n", XBOX_XGD1_VIDEO_L1_OUTPUT_START_LBA); + fprintf (json, " \"video_l1_sector_count\": %u,\n", XBOX_XGD1_VIDEO_L1_SECTORS); + fprintf (json, " \"layer_break_lba\": %u\n", XBOX_XGD1_DIC_LAYER_BREAK); + fprintf (json, " },\n"); + fprintf (json, " \"discimagecreator_reference\": {\n"); + fprintf (json, " \"xbox_size_sectors\": %u,\n", XBOX_XGD1_DIC_XBOX_SIZE); + fprintf (json, " \"xbox_layer_break_lba\": %u,\n", XBOX_XGD1_DIC_LAYER_BREAK); + fprintf (json, " \"dvd_start_psn\": %u,\n", XBOX_XGD1_DIC_DVD_START_PSN); + fprintf (json, " \"xbox_start_psn\": %u,\n", XBOX_XGD1_DIC_XBOX_START_PSN); + fprintf (json, " \"game_output_start_lba\": %u,\n", XBOX_XGD1_GAME_OUTPUT_START_LBA); + fprintf (json, " \"layout_constants_match\": %s\n", xbox_redump_layout_constants_match_dic () ? "true" : "false"); + fprintf (json, " },\n"); + fprintf (json, " \"lead_in_capture\": {\n"); + fprintf (json, " \"game_leadin_attempted\": true,\n"); + fprintf (json, " \"readable_sectors\": %u,\n", meta -> game_leadin_read_sectors); + fprintf (json, " \"zero_filled_sectors\": %u\n", meta -> game_leadin_zero_sectors); + fprintf (json, " },\n"); + fprintf (json, " \"dvd_structures\": {\n"); + fprintf (json, " \"pfi\": "); if (pfi_leaf) xbox_json_string (json, pfi_leaf); else fprintf (json, "null"); fprintf (json, ",\n"); + fprintf (json, " \"dmi\": "); if (dmi_leaf) xbox_json_string (json, dmi_leaf); else fprintf (json, "null"); fprintf (json, "\n"); + fprintf (json, " },\n"); + fprintf (json, " \"notes\": [\n"); + fprintf (json, " \"PFI and DMI are stored as separate binary READ DVD STRUCTURE captures because they are not READ(10) user-data sectors.\",\n"); + fprintf (json, " \"Pregame and postgame padding are zero-filled unless a future workflow proves readable source sectors for those ranges.\"\n"); + fprintf (json, " ]\n"); + fprintf (json, "}\n"); + fclose (json); + + if (meta -> pfi_written) { + debug ("Wrote Xbox PFI: %s", meta -> pfi_path); + } else { + warning ("Xbox PFI was not captured or could not be written"); + } + if (meta -> dmi_written) { + debug ("Wrote Xbox DMI: %s", meta -> dmi_path); + } else { + warning ("Xbox DMI was not captured or could not be written"); + } + debug ("Wrote Xbox redump metadata: %s", meta -> json_path); +} + +static bool xbox_write_iso_sectors (dumper *dmp, const u_int8_t *buf, u_int32_t sectors, u_int32_t output_lba, u_int32_t total_sectors) { + if (!dmp || !dmp -> fp_iso || !buf) + return false; + clearerr (dmp -> fp_iso); + if (fwrite (buf, SECTOR_SIZE, sectors, dmp -> fp_iso) != sectors || ferror (dmp -> fp_iso)) { + error ("fwrite() to Xbox redump ISO failed at output LBA %u", output_lba); + return false; + } + if (dmp -> hashing) + multihash_update (&(dmp -> hash_iso), (u_int8_t *) buf, (size_t) sectors * SECTOR_SIZE); + if (dmp -> flushing) + fflush (dmp -> fp_iso); + if (dmp -> progress && ((output_lba % 320) == 0 || output_lba + sectors >= total_sectors)) + dmp -> progress (false, output_lba + sectors, total_sectors, dmp -> progress_data); + return true; +} + +static bool xbox_write_zero_iso_range (dumper *dmp, u_int32_t sectors, u_int32_t output_lba, u_int32_t total_sectors, const char *label) { + u_int8_t zero[BLOCK_SIZE]; + u_int32_t done = 0; + memset (zero, 0, sizeof (zero)); + debug ("Writing Xbox %s zero-fill: output LBA %u, sectors %u", label ? label : "range", output_lba, sectors); + while (done < sectors) { + u_int32_t todo = sectors - done; + if (todo > SECTORS_PER_BLOCK) + todo = SECTORS_PER_BLOCK; + if (!xbox_write_iso_sectors (dmp, zero, todo, output_lba + done, total_sectors)) + return false; + done += todo; + } + return true; +} + +static bool xbox_dump_read10_iso_range (dumper *dmp, u_int32_t source_lba, u_int32_t sectors, u_int32_t output_lba, u_int32_t total_sectors, const char *label, bool auth_recovery, u_int32_t *current_sector) { + u_int8_t buf[BLOCK_SIZE]; + u_int8_t one[SECTOR_SIZE]; + u_int32_t done = 0; + debug ("Reading Xbox %s: source LBA %u, output LBA %u, sectors %u", label ? label : "range", source_lba, output_lba, sectors); + disc_xbox_recovery_kick (dmp -> dsk, auth_recovery); + while (done < sectors) { + u_int32_t todo = sectors - done; + int retry; + bool ok = false; + if (todo > SECTORS_PER_BLOCK) + todo = SECTORS_PER_BLOCK; + if (current_sector) + *current_sector = output_lba + done; + for (retry = 0; retry < 3 && !ok; retry++) { + if (retry > 0) + disc_xbox_recovery_kick (dmp -> dsk, auth_recovery); + memset (buf, 0, sizeof (buf)); + if (disc_xbox_read_10 (dmp -> dsk, source_lba + done, todo, buf, sizeof (buf)) >= 0) + ok = true; + } + if (!ok && todo > 1) { + /* Some Xbox-capable drives are more reliable at visible DVD-video edges + * and layer/layout boundaries with single-sector READ(10) commands. + * Fall back before declaring the whole 16-sector batch bad. */ + u_int32_t i; + bool singles_ok = true; + for (i = 0; i < todo; i++) { + bool one_ok = false; + for (retry = 0; retry < 3 && !one_ok; retry++) { + if (retry > 0) + disc_xbox_recovery_kick (dmp -> dsk, auth_recovery); + memset (one, 0, sizeof (one)); + if (disc_xbox_read_10 (dmp -> dsk, source_lba + done + i, 1, one, sizeof (one)) >= 0) + one_ok = true; + } + if (!one_ok) { + if (current_sector) + *current_sector = output_lba + done + i; + xbox_ref_log_fprintf (stderr, "\n[XBOX] READ(10) failed for %s at source LBA %u / output LBA %u\n", label ? label : "range", source_lba + done + i, output_lba + done + i); + error ("Xbox READ(10) failed for %s at source LBA %u", label ? label : "range", source_lba + done + i); + singles_ok = false; + break; + } + memcpy (buf + ((size_t) i * SECTOR_SIZE), one, SECTOR_SIZE); + } + ok = singles_ok; + } + if (!ok) { + if (current_sector) + *current_sector = output_lba + done; + xbox_ref_log_fprintf (stderr, "\n[XBOX] READ(10) failed for %s at source LBA %u / output LBA %u\n", label ? label : "range", source_lba + done, output_lba + done); + error ("Xbox READ(10) failed for %s at source LBA %u", label ? label : "range", source_lba + done); + return false; + } + if (!xbox_write_iso_sectors (dmp, buf, todo, output_lba + done, total_sectors)) + return false; + done += todo; + } + return true; +} + +static bool xbox_read_range_to_memory (disc *d, u_int32_t source_lba, u_int32_t sectors, u_int8_t *out, size_t out_size, const char *label, bool auth_recovery) { + u_int8_t one[SECTOR_SIZE]; + u_int32_t done = 0; + if (!d || !out || out_size < (size_t) sectors * SECTOR_SIZE) + return false; + debug ("Capturing Xbox %s to memory: source LBA %u, sectors %u", label ? label : "range", source_lba, sectors); + disc_xbox_recovery_kick (d, auth_recovery); + while (done < sectors) { + u_int32_t todo = sectors - done; + int retry; + bool ok = false; + if (todo > SECTORS_PER_BLOCK) + todo = SECTORS_PER_BLOCK; + for (retry = 0; retry < 3 && !ok; retry++) { + if (retry > 0) + disc_xbox_recovery_kick (d, auth_recovery); + if (disc_xbox_read_10 (d, source_lba + done, todo, out + ((size_t) done * SECTOR_SIZE), out_size - ((size_t) done * SECTOR_SIZE)) >= 0) + ok = true; + } + if (!ok && todo > 1) { + u_int32_t i; + bool singles_ok = true; + for (i = 0; i < todo; i++) { + bool one_ok = false; + for (retry = 0; retry < 3 && !one_ok; retry++) { + if (retry > 0) + disc_xbox_recovery_kick (d, auth_recovery); + memset (one, 0, sizeof (one)); + if (disc_xbox_read_10 (d, source_lba + done + i, 1, one, sizeof (one)) >= 0) + one_ok = true; + } + if (!one_ok) { + xbox_ref_log_fprintf (stderr, "\n[XBOX] READ(10) failed while capturing %s at source LBA %u\n", label ? label : "range", source_lba + done + i); + error ("Xbox READ(10) failed while capturing %s at source LBA %u", label ? label : "range", source_lba + done + i); + singles_ok = false; + break; + } + memcpy (out + ((size_t) (done + i) * SECTOR_SIZE), one, SECTOR_SIZE); + } + ok = singles_ok; + } + if (!ok) { + xbox_ref_log_fprintf (stderr, "\n[XBOX] READ(10) failed while capturing %s at source LBA %u\n", label ? label : "range", source_lba + done); + error ("Xbox READ(10) failed while capturing %s at source LBA %u", label ? label : "range", source_lba + done); + return false; + } + done += todo; + } + return true; +} + +static bool xbox_write_leadin_iso_range (dumper *dmp, u_int32_t source_lba, u_int32_t output_lba, u_int32_t total_sectors, xbox_redump_metadata *meta, const char *label, bool auth_recovery, u_int32_t *current_sector) { + u_int8_t buf[SECTOR_SIZE]; + u_int8_t zero[SECTOR_SIZE]; + u_int32_t i; + bool warned = false; + memset (zero, 0, sizeof (zero)); + debug ("Attempting Xbox %s lead-in from source LBA %u..%u", label ? label : "game", source_lba, source_lba + XBOX_XISO_LEADIN_SECTORS - 1); + disc_xbox_recovery_kick (dmp -> dsk, auth_recovery); + for (i = 0; i < XBOX_XISO_LEADIN_SECTORS; i++) { + const u_int8_t *src = buf; + if (current_sector) + *current_sector = output_lba + i; + memset (buf, 0, sizeof (buf)); + if (disc_xbox_read_10 (dmp -> dsk, source_lba + i, 1, buf, sizeof (buf)) < 0) { + if (!warned) { + warning ("Drive could not read one or more Xbox lead-in sectors; zero-filling only unreadable lead-in sectors"); + warned = true; + } + src = zero; + if (meta) meta -> game_leadin_zero_sectors++; + } else { + if (meta) meta -> game_leadin_read_sectors++; + } + if (!xbox_write_iso_sectors (dmp, src, 1, output_lba + i, total_sectors)) + return false; + } + return true; +} + +static bool xbox_get_xiso_range (disc *d, u_int32_t *source_lba, u_int32_t *source_sectors, u_int32_t *target_sectors) { + u_int32_t total_sectors, root_lba, volume_size_raw, volume_sectors; + + if (!d || !source_lba || !source_sectors || !target_sectors) + return false; + + if (disc_xbox_unlock (d) < 0) { + error ("Could not switch Xbox drive into the game/XDVDFS view"); + return false; + } + + total_sectors = disc_get_sectors_no (d); + root_lba = 0; + volume_size_raw = 0; + + /* Try the historical physical/game-partition XDVDFS location first, + * then the unlocked/XISO-visible LBA 0x20. GDR-8050L after the + * native Xbox handshake normally resolves at 0x20. + */ + if (xbox_probe_xdfs_volume (d, XBOX_XISO_START_LBA_MAGIC, &root_lba, &volume_size_raw)) { + *source_lba = XBOX_XISO_START_LBA_MAGIC; + } else if (xbox_probe_xdfs_volume (d, XBOX_XISO_STANDARD_GAME_LBA, &root_lba, &volume_size_raw)) { + *source_lba = XBOX_XISO_STANDARD_GAME_LBA; + } else { + error ("No Xbox XDVDFS volume found at LBA %u or %u", XBOX_XISO_START_LBA_MAGIC, XBOX_XISO_STANDARD_GAME_LBA); + return false; + } + + if (total_sectors > 3300000) { + /* Retail dual-layer XISO copies from the detected game/XDVDFS + * start LBA through logical LBA 1913920, then prepends 32 + * output lead-in sectors. + */ + if (*source_lba >= XBOX_XISO_DUAL_LAYER_END_LBA) { + error ("Xbox XISO source LBA %u is beyond dual-layer XISO end LBA %u", *source_lba, XBOX_XISO_DUAL_LAYER_END_LBA); + return false; + } + *source_sectors = XBOX_XISO_DUAL_LAYER_END_LBA - *source_lba; + } else { + /* For single-layer/homebrew-style media, XDVDFS VolumeSize is a + * byte count, so convert it to 2048-byte sectors. + */ + if (volume_size_raw == 0) { + error ("Xbox XDVDFS header reported a zero volume size"); + return false; + } + + volume_sectors = volume_size_raw / SECTOR_SIZE; + if (volume_sectors == 0) { + error ("Xbox XDVDFS volume size %u is smaller than one sector", volume_size_raw); + return false; + } + if (*source_lba + volume_sectors > total_sectors) { + warning ("Xbox XISO volume size extends beyond visible capacity; clipping to remaining visible sectors"); + if (total_sectors > *source_lba) + volume_sectors = total_sectors - *source_lba; + else + return false; + } + *source_sectors = volume_sectors; + } + + *target_sectors = *source_sectors + XBOX_XISO_LEADIN_SECTORS; + debug ("Xbox XISO range: source LBA %u, descriptor RootLBA %u, source sectors %u, output sectors %u", *source_lba, root_lba, *source_sectors, *target_sectors); + return true; +} + + +/** + * Tries to open the output file for writing and to find out if it contains valid data so that the dump can continue. + * @param dvd + * @param outfile + * @param[out] fp The file pointer to write to. + * @param[out] start_sector The sector to start reading from. + * @return true if the dumping can start/continue, false otherwise (for instance if outfile cannot be written to). + */ +bool dumper_set_raw_output_file (dumper *dmp, char *outfile_raw, bool resume) { + bool out; + my_off_t filesize; + FILE *fp; + + if (!outfile_raw) { + /* Raw output disabled */ + out = true; + dmp -> start_sector_raw = -1; + my_free (dmp -> outfile_raw); + dmp -> outfile_raw = NULL; + } else if (dmp -> outfile_raw) { + error ("Raw output file already defined"); + out = false; + } else if (!(fp = fopen (outfile_raw, "rb"))) { /** @todo Maybe we could not open file for permission problems */ + /* Raw output file does not exist, start from scratch */ + out = true; + dmp -> start_sector_raw = 0; + my_strdup (dmp -> outfile_raw, outfile_raw); + } else if (resume) { + /* Raw output file exists and resume was requested, so see how many dumped sectors it contains */ + my_fseek (fp, 0, SEEK_END); + filesize = my_ftell (fp); + fclose (fp); + out = true; + dmp -> start_sector_raw = (u_int32_t) (filesize / RAW_SECTOR_SIZE / SECTORS_PER_BLOCK) * SECTORS_PER_BLOCK; + debug ("Raw output can restart from sector %u", dmp -> start_sector_raw); + my_strdup (dmp -> outfile_raw, outfile_raw); + } else { + /* Raw output file exists but resume was not requested, error */ + fclose (fp); + error ("Raw output file exists, but resume was not requested."); + out = false; + dmp -> start_sector_raw = -1; + my_free (dmp -> outfile_raw); + dmp -> outfile_raw = NULL; + } + + return (out); +} + + +bool dumper_set_iso_output_file (dumper *dmp, char *outfile_iso, bool resume) { + bool out; + my_off_t filesize; + FILE *fp; + + if (!outfile_iso) { + /* ISO output disabled */ + out = true; + dmp -> start_sector_iso = -1; + my_free (dmp -> outfile_iso); + dmp -> outfile_iso = NULL; + } else if (outfile_iso[0] == '\0') { + /* Xbox/GDR-8050L auto-name placeholder; the copied reference path opens the final file. */ + out = true; + dmp -> start_sector_iso = 0; + my_strdup (dmp -> outfile_iso, outfile_iso); + } else if (dmp -> outfile_iso) { + error ("ISO output file already defined"); + out = false; + } else if (!(fp = fopen (outfile_iso, "rb"))) { /** @todo Maybe we could not open file for permission problems */ + /* Raw output file does not exist, start from scratch */ + out = true; + dmp -> start_sector_iso = 0; + my_strdup (dmp -> outfile_iso, outfile_iso); + } else if (resume) { + /* Raw output file exists and resume was requested, so see how many dumped sectors it contains */ + my_fseek (fp, 0, SEEK_END); + filesize = my_ftell (fp); + fclose (fp); + out = true; + dmp -> start_sector_iso = (u_int32_t) (filesize / SECTOR_SIZE / SECTORS_PER_BLOCK) * SECTORS_PER_BLOCK; + debug ("ISO output can restart from sector %u", dmp -> start_sector_iso); + my_strdup (dmp -> outfile_iso, outfile_iso); + } else { + /* Raw output file exists but resume was not requested, error */ + fclose (fp); + error ("ISO output file exists, but resume was not requested."); + out = false; + dmp -> start_sector_iso = -1; + my_free (dmp -> outfile_iso); + dmp -> outfile_iso = NULL; + } + + return (out); +} + + +bool dumper_set_xiso_output_file (dumper *dmp, char *outfile_xiso, bool resume) { + bool out; + my_off_t filesize; + FILE *fp; + + if (!outfile_xiso) { + out = true; + dmp -> start_sector_xiso = -1; + my_free (dmp -> outfile_xiso); + dmp -> outfile_xiso = NULL; + } else if (outfile_xiso[0] == '\0') { + /* Xbox/GDR-8050L auto-name placeholder; the copied reference path opens the final file. */ + out = true; + dmp -> start_sector_xiso = 0; + my_strdup (dmp -> outfile_xiso, outfile_xiso); + } else if (dmp -> outfile_xiso) { + error ("XISO output file already defined"); + out = false; + } else if (!(fp = fopen (outfile_xiso, "rb"))) { + out = true; + dmp -> start_sector_xiso = 0; + my_strdup (dmp -> outfile_xiso, outfile_xiso); + } else if (resume) { + my_fseek (fp, 0, SEEK_END); + filesize = my_ftell (fp); + fclose (fp); + out = true; + dmp -> start_sector_xiso = (u_int32_t) (filesize / SECTOR_SIZE / SECTORS_PER_BLOCK) * SECTORS_PER_BLOCK; + debug ("XISO output can restart from output sector %u", dmp -> start_sector_xiso); + my_strdup (dmp -> outfile_xiso, outfile_xiso); + } else { + fclose (fp); + error ("XISO output file exists, but resume was not requested."); + out = false; + dmp -> start_sector_xiso = -1; + my_free (dmp -> outfile_xiso); + dmp -> outfile_xiso = NULL; + } + + return (out); +} + + +bool dumper_prepare_xiso (dumper *dmp) { + bool out; + u_int8_t buf[SECTOR_SIZE]; + size_t r; + u_int32_t i; + + if (!dmp -> outfile_xiso) { + error ("No XISO output file defined"); + return false; + } + + dmp -> start_sector = dmp -> start_sector_xiso; + +#ifdef WIN32 + if (dmp -> outfile_xiso[0] == '\0' && disc_is_xbox_challenge_drive (dmp -> dsk)) { + if (dmp -> hashing) + multihash_init (&(dmp -> hash_xiso)); + dmp -> fp_xiso = NULL; + return true; + } +#endif + + if (dmp -> hashing) + multihash_init (&(dmp -> hash_xiso)); + + dmp -> fp_xiso = fopen (dmp -> outfile_xiso, "a+b"); + if (!dmp -> fp_xiso) + return false; + + if (dmp -> hashing) { + debug ("Calculating hashes for pre-existing XISO dump data"); + if (dmp -> start_sector > 0) { + for (i = 0; i < dmp -> start_sector && (r = fread (buf, SECTOR_SIZE, 1, dmp -> fp_xiso)) > 0; i++) + multihash_update (&(dmp -> hash_xiso), buf, SECTOR_SIZE); + MY_ASSERT (r > 0); + } + } + + if (my_fseek (dmp -> fp_xiso, dmp -> start_sector * SECTOR_SIZE, SEEK_SET) == 0 && + ftruncate (fileno (dmp -> fp_xiso), (int64_t) dmp -> start_sector * SECTOR_SIZE) == 0) { + out = true; + debug ("Writing to file \"%s\" in Xbox XISO format (fseeked() to %lld)", dmp -> outfile_xiso, my_ftell (dmp -> fp_xiso)); + } else { + out = false; + fclose (dmp -> fp_xiso); + dmp -> fp_xiso = NULL; + } + + return (out); +} + + +bool dumper_prepare (dumper *dmp) { + bool out; + u_int8_t buf[RAW_SECTOR_SIZE]; + size_t r; + u_int32_t i; + + /* Outputting to both files, resume must start from the file with the least sectors. Hopefully they will have the same number of sectors, anyway... */ + if (dmp -> outfile_raw && dmp -> outfile_iso && dmp -> start_sector_raw != dmp -> start_sector_iso) { + if (dmp -> start_sector_raw < dmp -> start_sector_iso) + dmp -> start_sector = dmp -> start_sector_raw; + else + dmp -> start_sector = dmp -> start_sector_iso; + } else if (dmp -> outfile_raw) { + dmp -> start_sector = dmp -> start_sector_raw; + } else if (dmp -> outfile_iso) { + dmp -> start_sector = dmp -> start_sector_iso; + } else { + MY_ASSERT (0); + } + +#ifdef WIN32 + if (dmp -> outfile_iso && dmp -> outfile_iso[0] == '\0' && disc_is_xbox_challenge_drive (dmp -> dsk)) { + if (dmp -> hashing) { + multihash_init (&(dmp -> hash_raw)); + multihash_init (&(dmp -> hash_iso)); + } + dmp -> fp_raw = NULL; + dmp -> fp_iso = NULL; + return true; + } +#endif + + /* Prepare hashes */ + if (dmp -> hashing) { + multihash_init (&(dmp -> hash_raw)); + multihash_init (&(dmp -> hash_iso)); + } + + /* Setup raw output file */ + if (dmp -> outfile_raw) { + dmp -> fp_raw = fopen (dmp -> outfile_raw, "a+b"); + + if (dmp -> hashing) { + debug ("Calculating hashes for pre-existing raw dump data"); + if (dmp -> start_sector > 0) { + for (i = 0; i < dmp -> start_sector && (r = fread (buf, RAW_SECTOR_SIZE, 1, dmp -> fp_raw)) > 0; i++) + multihash_update (&(dmp -> hash_raw), buf, RAW_SECTOR_SIZE); + MY_ASSERT (r > 0); + } + } + + /* Now call fseek as file will only be written, from now on */ + if (my_fseek (dmp -> fp_raw, dmp -> start_sector * RAW_SECTOR_SIZE, SEEK_SET) == 0 && + ftruncate (fileno (dmp -> fp_raw), (int64_t) dmp -> start_sector * RAW_SECTOR_SIZE) == 0) { + out = true; + debug ("Writing to file \"%s\" in raw format (fseeked() to %lld)", dmp -> outfile_raw, my_ftell (dmp -> fp_raw)); + } else { + out = false; + fclose (dmp -> fp_raw); + dmp -> fp_raw = NULL; + } + } else { + dmp -> fp_raw = NULL; + } + + /* Setup ISO output file */ + if (dmp -> outfile_iso) { + dmp -> fp_iso = fopen (dmp -> outfile_iso, "a+b"); + + if (dmp -> hashing) { + debug ("Calculating hashes for pre-existing ISO dump data"); + if (dmp -> start_sector > 0) { + for (i = 0; i < dmp -> start_sector && (r = fread (buf, SECTOR_SIZE, 1, dmp -> fp_iso)) > 0; i++) + multihash_update (&(dmp -> hash_iso), buf, SECTOR_SIZE); + MY_ASSERT (r > 0); + } + } + + if (my_fseek (dmp -> fp_iso, dmp -> start_sector * SECTOR_SIZE, SEEK_SET) == 0 && + ftruncate (fileno (dmp -> fp_iso), (int64_t) dmp -> start_sector * SECTOR_SIZE) == 0) { + out = true; + debug ("Writing to file \"%s\" in ISO format (fseeked() to %lld)", dmp -> outfile_iso, my_ftell (dmp -> fp_iso)); + } else { + out = false; + fclose (dmp -> fp_iso); + dmp -> fp_iso = NULL; + } + } else { + dmp -> fp_iso = NULL; + } + + return (out); +} + + + +static int dumper_dump_xbox_redump_iso (dumper *dmp, u_int32_t *current_sector) { + xbox_redump_metadata meta; + u_int8_t *video_l1 = NULL; + u_int32_t locked_sectors = 0, unlocked_sectors = 0, sector_size = 0; + u_int32_t game_source_lba = 0xFFFFFFFFU; + u_int32_t root_lba = 0, volume_size_raw = 0; + u_int32_t pregame_padding = XBOX_XGD1_GAME_OUTPUT_START_LBA - XBOX_XGD1_VIDEO_L0_SECTORS; + u_int32_t postgame_padding = XBOX_XGD1_VIDEO_L1_OUTPUT_START_LBA - (XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS); + bool out = false; + bool locked_view_is_xdfs; + disc_type type_id; + char *type = NULL; + + if (!dmp) + return false; + + disc_get_type (dmp -> dsk, &type_id, &type); + if (type_id != DISC_TYPE_XBOX) + return false; + + if (dmp -> fp_raw) { + error ("Xbox redump-style ISO output cannot be combined with -r/raw output in one run"); + return false; + } + + if (dmp -> start_sector != 0) { + error ("Resume is not supported for Xbox redump-style ISO reconstruction because the drive view changes mid-dump"); + return false; + } + +#ifdef WIN32 + if (disc_is_xbox_challenge_drive (dmp -> dsk)) { + int ref_status; + if (dmp -> fp_iso) { + fclose (dmp -> fp_iso); + dmp -> fp_iso = NULL; + } + if (current_sector) + *current_sector = 0; + xbox_ref_log_fprintf (stderr, "[XBOX] Dispatching GDR-8050L -i directly to copied original dumper code using FriiDump's existing drive HANDLE.\n"); + ref_status = xbox_ref_gdr8050l_dump_with_handle (disc_get_native_handle (dmp -> dsk), disc_get_device (dmp -> dsk), dmp -> outfile_iso, '1'); + return ref_status == 0; + } +#endif + + if (!dmp -> fp_iso) + return false; + + memset (&meta, 0, sizeof (meta)); + + /* Original GDR-8050L dumper order for redump-style output: + * 1. unlock/authenticate into the game view; + * 2. read XDVDFS/game metadata while that view is available; + * 3. perform a real media transition to return to the visible DVD-video view; + * 4. dump video/front/tail, unlock again, then dump game data. + * + * The video partition is not encrypted/locked; "lock" here only means the + * drive-visible DVD-video state before the Xbox game-view unlock. */ + if (disc_is_xbox_challenge_drive (dmp -> dsk)) { + xbox_ref_log_fprintf (stderr, "[XBOX] GDR-8050L flow follows original main.c/DumpXboxGameDisc option 1.\n"); + xbox_ref_log_fprintf (stderr, "[XBOX] Stage 1/6: primary UnlockDrive() handshake.\n"); + disc_xbox_wait_ready (dmp -> dsk, 30000); + if (disc_xbox_unlock (dmp -> dsk) < 0) + xbox_ref_log_fprintf (stderr, "[XBOX][WARN] Stage 1 UnlockDrive() returned failure; continuing like the reference dumper and letting later reads prove state.\n"); + xbox_ref_log_fprintf (stderr, "[XBOX] Stage 2/6: media cycle after primary handshake.\n"); + if (disc_xbox_media_cycle (dmp -> dsk) < 0) { + xbox_ref_log_fprintf (stderr, "[XBOX][FATAL] Stage 2 failed: required media-cycle failed.\n"); + return false; + } + xbox_ref_log_fprintf (stderr, "[XBOX] Stage 3/6: re-apply UnlockDrive() after media change.\n"); + if (disc_xbox_unlock (dmp -> dsk) < 0) + xbox_ref_log_fprintf (stderr, "[XBOX][WARN] Stage 3 UnlockDrive() returned failure; continuing like the reference dumper and letting GetXboxGameInfo/XDVDFS probe prove state.\n"); + + xbox_ref_log_fprintf (stderr, "[XBOX] Stage 4/6: RefreshVolume, settle, EnsureDriveReady, SetDriveSpeedMax, and volume lock before metadata probe.\n"); + disc_xbox_refresh_volume (dmp -> dsk); + disc_xbox_wait_ready (dmp -> dsk, 30000); + disc_set_speed (dmp -> dsk, 0xFFFF); + if (disc_xbox_lock_volume (dmp -> dsk) < 0) + xbox_ref_log_fprintf (stderr, "[XBOX][WARN] FSCTL_LOCK_VOLUME equivalent failed; continuing like reference dumper warning path.\n"); + disc_xbox_read_capacity_10 (dmp -> dsk, &unlocked_sectors, §or_size); + meta.unlocked_visible_sectors = unlocked_sectors; + xbox_capture_pfi_dmi (dmp -> dsk, &meta); + if (xbox_probe_xdfs_volume (dmp -> dsk, XBOX_XISO_STANDARD_GAME_LBA, &root_lba, &volume_size_raw)) { + meta.game_source_lba = XBOX_XISO_STANDARD_GAME_LBA; + meta.game_source_sectors = XBOX_XGD1_GAME_SOURCE_SECTORS; + } else if (xbox_probe_xdfs_volume (dmp -> dsk, XBOX_XISO_START_LBA_MAGIC, &root_lba, &volume_size_raw)) { + meta.game_source_lba = XBOX_XISO_START_LBA_MAGIC; + meta.game_source_sectors = XBOX_XGD1_GAME_SOURCE_SECTORS; + } + + xbox_ref_log_fprintf (stderr, "[XBOX] Stage 5/6: media cycle back to visible DVD-video view before sector 0 capture.\n"); + if (disc_xbox_media_cycle (dmp -> dsk) < 0) { + xbox_ref_log_fprintf (stderr, "[XBOX][FATAL] Stage 5 failed: could not restore visible DVD-video view.\n"); + return false; + } + disc_xbox_wait_ready (dmp -> dsk, 45000); + } else if (disc_is_xbox_vendor_unlock_drive (dmp -> dsk)) { + /* GDR-3120L/Kreon-style FF 08 01 profiles can explicitly select + * state 0 for the visible DVD-video view. */ + if (disc_xbox_lock (dmp -> dsk) < 0) + warning ("Could not set Xbox vendor lock state 0; continuing with the current visible view"); + } + + disc_xbox_read_capacity_10 (dmp -> dsk, &locked_sectors, §or_size); + meta.locked_visible_sectors = locked_sectors; + xbox_capture_pfi_dmi (dmp -> dsk, &meta); + + locked_view_is_xdfs = xbox_probe_xdfs_volume (dmp -> dsk, XBOX_XISO_STANDARD_GAME_LBA, NULL, NULL); + if (locked_view_is_xdfs && disc_is_xbox_challenge_drive (dmp -> dsk)) { + error ("Current GDR-8050L view still exposes XDVDFS at LBA 32 after media cycle; cannot capture visible DVD-video view for redump-style ISO"); + return false; + } + + if (!xbox_redump_layout_constants_match_dic ()) { + error ("Xbox redump layout constants do not match the DiscImageCreator-compatible XGD1 layout"); + return false; + } + + meta.output_sectors = XBOX_XGD1_FULL_REDUMP_SECTORS; + if (dmp -> progress) + dmp -> progress (true, 0, meta.output_sectors, dmp -> progress_data); + + debug ("Xbox redump-style layout: video L0=%u, pregame padding=%u, game view=%u, postgame padding=%u, video L1=%u", + XBOX_XGD1_VIDEO_L0_SECTORS, pregame_padding, XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS, postgame_padding, XBOX_XGD1_VIDEO_L1_SECTORS); + + video_l1 = (u_int8_t *) malloc ((size_t) XBOX_XGD1_VIDEO_L1_SECTORS * SECTOR_SIZE); + if (!video_l1) { + error ("Could not allocate Xbox VIDEO-L1 capture buffer"); + return false; + } + + /* Capture the tail of the visible DVD-video view before switching to the game view. */ + if (!xbox_read_range_to_memory (dmp -> dsk, XBOX_XGD1_VIDEO_L0_SECTORS, XBOX_XGD1_VIDEO_L1_SECTORS, + video_l1, (size_t) XBOX_XGD1_VIDEO_L1_SECTORS * SECTOR_SIZE, "VIDEO-L1", false)) + goto cleanup; + + if (!xbox_dump_read10_iso_range (dmp, 0, XBOX_XGD1_VIDEO_L0_SECTORS, 0, meta.output_sectors, "VIDEO-L0", false, current_sector)) + goto cleanup; + + if (current_sector) *current_sector = XBOX_XGD1_VIDEO_L0_SECTORS; + if (!xbox_write_zero_iso_range (dmp, pregame_padding, XBOX_XGD1_VIDEO_L0_SECTORS, meta.output_sectors, "PREGAME-PADDING")) + goto cleanup; + + xbox_ref_log_fprintf (stderr, "[XBOX] Stage 6/6: re-apply UnlockDrive() for unlocked game/XDVDFS data.\n"); + if (disc_xbox_unlock (dmp -> dsk) < 0) + xbox_ref_log_fprintf (stderr, "[XBOX][WARN] Stage 6 UnlockDrive() returned failure; continuing until the XDVDFS probe/read proves failure.\n"); + disc_xbox_refresh_volume (dmp -> dsk); + disc_xbox_wait_ready (dmp -> dsk, 30000); + disc_set_speed (dmp -> dsk, 0xFFFF); + disc_xbox_read_capacity_10 (dmp -> dsk, &unlocked_sectors, §or_size); + meta.unlocked_visible_sectors = unlocked_sectors; + xbox_capture_pfi_dmi (dmp -> dsk, &meta); + + if (xbox_probe_xdfs_volume (dmp -> dsk, XBOX_XISO_STANDARD_GAME_LBA, &root_lba, &volume_size_raw)) + game_source_lba = XBOX_XISO_STANDARD_GAME_LBA; + else if (xbox_probe_xdfs_volume (dmp -> dsk, XBOX_XISO_START_LBA_MAGIC, &root_lba, &volume_size_raw)) + game_source_lba = XBOX_XISO_START_LBA_MAGIC; + else { + error ("Could not locate Xbox XDVDFS header after unlock at LBA %u or %u", XBOX_XISO_STANDARD_GAME_LBA, XBOX_XISO_START_LBA_MAGIC); + goto cleanup; + } + + meta.game_source_lba = game_source_lba; + meta.game_source_sectors = XBOX_XGD1_GAME_SOURCE_SECTORS; + if (game_source_lba != XBOX_XISO_STANDARD_GAME_LBA && disc_is_xbox_challenge_drive (dmp -> dsk)) + warning ("Xbox XDVDFS was detected at LBA %u, not the expected GDR-8050L game-view LBA 32", game_source_lba); + + if (disc_is_xbox_challenge_drive (dmp -> dsk)) { + /* Match the original GDR-8050L dumper's option-1 redump path: the + * 32-sector XISO/game lead-in area is synthetic zero-fill, and the real + * XDVDFS data begins at unlocked source LBA 32. */ + xbox_ref_log_fprintf (stderr, "[XBOX] Writing 32-sector game lead-in as zero-fill for GDR-8050L redump flow.\n"); + meta.game_leadin_zero_sectors += XBOX_XISO_LEADIN_SECTORS; + if (current_sector) *current_sector = XBOX_XGD1_GAME_OUTPUT_START_LBA; + if (!xbox_write_zero_iso_range (dmp, XBOX_XISO_LEADIN_SECTORS, XBOX_XGD1_GAME_OUTPUT_START_LBA, meta.output_sectors, "GAME-XISO-LEADIN")) + goto cleanup; + } else { + if (!xbox_write_leadin_iso_range (dmp, + (game_source_lba >= XBOX_XISO_LEADIN_SECTORS) ? game_source_lba - XBOX_XISO_LEADIN_SECTORS : 0, + XBOX_XGD1_GAME_OUTPUT_START_LBA, meta.output_sectors, &meta, "redump game", true, current_sector)) + goto cleanup; + } + + if (!xbox_dump_read10_iso_range (dmp, game_source_lba, XBOX_XGD1_GAME_SOURCE_SECTORS, + XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XISO_LEADIN_SECTORS, meta.output_sectors, "GAME-XDVDFS", true, current_sector)) + goto cleanup; + + if (current_sector) *current_sector = XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS; + if (!xbox_write_zero_iso_range (dmp, postgame_padding, XBOX_XGD1_GAME_OUTPUT_START_LBA + XBOX_XGD1_UNLOCKED_GAME_VIEW_SECTORS, + meta.output_sectors, "POSTGAME-PADDING")) + goto cleanup; + + if (!xbox_write_iso_sectors (dmp, video_l1, XBOX_XGD1_VIDEO_L1_SECTORS, XBOX_XGD1_VIDEO_L1_OUTPUT_START_LBA, meta.output_sectors)) + goto cleanup; + + if (dmp -> hashing) + multihash_finish (&(dmp -> hash_iso)); + if (dmp -> fp_iso) { + fclose (dmp -> fp_iso); + dmp -> fp_iso = NULL; + } + + xbox_write_redump_metadata (dmp, &meta); + out = true; + +cleanup: + if (video_l1) + free (video_l1); + /* Keep current_sector at the last phase-specific LBA on failure so the + * CLI can report a useful range instead of collapsing every setup failure + * back to 0..15. */ + return out; +} + +int dumper_dump (dumper *dmp, u_int32_t *current_sector) { + bool out; + u_int8_t *rawbuf, *isobuf; + u_int32_t i, sectors_no, last_sector; + disc_type type_id; + char *type_string = NULL; +#ifdef DEBUGaa + bool no_unscrambling; +#endif + +#ifdef DEBUGaa + no_unscrambling = dd -> no_unscrambling; + if (fp_iso && no_unscrambling) { + warning ("Output to ISO format requested, ignoring no_unscrambling!"); + no_unscrambling = false; + } +#endif + + disc_get_type (dmp -> dsk, &type_id, &type_string); + if (type_id == DISC_TYPE_XBOX && dmp -> fp_iso) + return dumper_dump_xbox_redump_iso (dmp, current_sector); + + sectors_no = disc_get_sectors_no (dmp -> dsk); +#if 0 + if (dd -> start_sector != -1) { + if (dd -> start_sector >= sectors_no) { + error ("Cannot start dumping from sector %u as the inserted disc only has %u sectors\n", dd -> start_sector, sectors_no); + out = false; + } else { + warning ("Start sector forced to %u\n", dd -> start_sector); + ss = dd -> start_sector; + + if (fp_iso) + fseek (fp_iso, ss * 2048, SEEK_SET); + if (fp_raw) + fseek (fp_raw, ss * 2064, SEEK_SET); + } + } +#endif + + if (true) { + debug ("Starting dump process from sector %u...\n", dmp -> start_sector); + + /* First call to progress function */ + if (dmp -> progress) + dmp -> progress (true, dmp -> start_sector, sectors_no, dmp -> progress_data); + + last_sector=sectors_no-1; + + for (i = dmp -> start_sector, out = true; i < sectors_no && out; i++) { + if (!dumper_read_sector_with_retry (dmp, i, &isobuf, &rawbuf)) { + error ("Read failed after dump-level retries at sectors %u..%u", i, i + SECTORS_PER_BLOCK - 1); + out = false; + *(current_sector) = i; + } + + if (out && dmp -> fp_raw) { + clearerr (dmp -> fp_raw); + + if (!rawbuf) { + error ("NULL buffer"); + out = false; + *(current_sector) = i; + } + else fwrite (rawbuf, RAW_SECTOR_SIZE, 1, dmp -> fp_raw); + if (ferror (dmp -> fp_raw)) { + error ("fwrite() to raw output file failed"); + out = false; + *(current_sector) = i; + } + + if (dmp -> flushing) + fflush (dmp -> fp_raw); + + if (dmp -> hashing && out) + multihash_update (&(dmp -> hash_raw), rawbuf, RAW_SECTOR_SIZE); + } + + if (out && dmp -> fp_iso) { + clearerr (dmp -> fp_iso); + + if (!isobuf) { + error ("NULL buffer"); + out = false; + *(current_sector) = i; + } + else fwrite (isobuf, SECTOR_SIZE, 1, dmp -> fp_iso); + + if (ferror (dmp -> fp_iso)) { + error ("fwrite() to ISO output file failed"); + out = false; + *(current_sector) = i; + } + + if (dmp -> flushing) + fflush (dmp -> fp_iso); + + if (dmp -> hashing && out) + multihash_update (&(dmp -> hash_iso), isobuf, SECTOR_SIZE); + } + + if ((i % 320 == 0) || (i == last_sector)) { //speedhack + if (dmp -> progress) + dmp -> progress (false, i + 1, sectors_no, dmp -> progress_data); /* i + 1 'cause sectors range from 0 to N */ + } + } + + if (dmp -> hashing) { + multihash_finish (&(dmp -> hash_raw)); + multihash_finish (&(dmp -> hash_iso)); + } + + if (dmp -> fp_raw) + fclose (dmp -> fp_raw); + if (dmp -> fp_iso) + fclose (dmp -> fp_iso); + if (out) { + + + } + } + + return (out); +} + + +int dumper_dump_xiso (dumper *dmp, u_int32_t *current_sector) { + bool out; + u_int8_t *rawbuf, *isobuf; + u_int8_t sector_buf[SECTOR_SIZE]; + u_int32_t source_lba, source_sectors, target_sectors, i, source_sector, last_sector, leadin_source_lba; + bool leadin_fallback_warned; + + if (!dmp) { + error ("XISO dumper is not prepared"); + return false; + } + +#ifdef WIN32 + if (disc_is_xbox_challenge_drive (dmp -> dsk)) { + int ref_status; + if (dmp -> fp_xiso) { + fclose (dmp -> fp_xiso); + dmp -> fp_xiso = NULL; + } + if (current_sector) + *current_sector = 0; + xbox_ref_log_fprintf (stderr, "[XBOX] Dispatching GDR-8050L -X directly to copied original dumper code using FriiDump's existing drive HANDLE.\n"); + ref_status = xbox_ref_gdr8050l_dump_with_handle (disc_get_native_handle (dmp -> dsk), disc_get_device (dmp -> dsk), dmp -> outfile_xiso, '2'); + return ref_status == 0; + } +#endif + + if (!dmp -> fp_xiso) { + error ("XISO dumper is not prepared"); + return false; + } + + if (!xbox_get_xiso_range (dmp -> dsk, &source_lba, &source_sectors, &target_sectors)) { + if (current_sector) + *current_sector = dmp -> start_sector; + return false; + } + + if (dmp -> start_sector >= target_sectors) { + error ("Cannot resume Xbox XISO at output sector %u; target has only %u sectors", dmp -> start_sector, target_sectors); + if (current_sector) + *current_sector = dmp -> start_sector; + return false; + } + + memset (sector_buf, 0, sizeof (sector_buf)); + leadin_source_lba = (source_lba >= XBOX_XISO_LEADIN_SECTORS) ? (source_lba - XBOX_XISO_LEADIN_SECTORS) : 0; + leadin_fallback_warned = false; + out = true; + last_sector = target_sectors - 1; + + debug ("Starting Xbox XISO dump from output sector %u...", dmp -> start_sector); + debug ("Xbox XISO lead-in source LBA %u..%u will be attempted before zero-fill fallback", leadin_source_lba, leadin_source_lba + XBOX_XISO_LEADIN_SECTORS - 1); + if (dmp -> progress) + dmp -> progress (true, dmp -> start_sector, target_sectors, dmp -> progress_data); + + for (i = dmp -> start_sector; i < target_sectors && out; i++) { + if (i < XBOX_XISO_LEADIN_SECTORS) { + isobuf = NULL; + rawbuf = NULL; + if (disc_xbox_read_10 (dmp -> dsk, leadin_source_lba + i, 1, sector_buf, sizeof (sector_buf)) >= 0) { + isobuf = sector_buf; + } else { + if (!leadin_fallback_warned) { + warning ("Drive could not read one or more Xbox XISO lead-in sectors; using zero-fill fallback for unreadable lead-in sectors"); + leadin_fallback_warned = true; + } + memset (sector_buf, 0, sizeof (sector_buf)); + isobuf = sector_buf; + } + + clearerr (dmp -> fp_xiso); + fwrite (isobuf, SECTOR_SIZE, 1, dmp -> fp_xiso); + if (ferror (dmp -> fp_xiso)) { + error ("fwrite() to XISO output file failed"); + out = false; + if (current_sector) + *current_sector = i; + } + + if (dmp -> hashing && out) + multihash_update (&(dmp -> hash_xiso), isobuf, SECTOR_SIZE); + } else { + source_sector = source_lba + (i - XBOX_XISO_LEADIN_SECTORS); + isobuf = sector_buf; + rawbuf = NULL; + if (disc_xbox_read_10 (dmp -> dsk, source_sector, 1, sector_buf, sizeof (sector_buf)) < 0) { + error ("Xbox XISO READ(10) failed at source LBA %u", source_sector); + out = false; + if (current_sector) + *current_sector = i; + } else { + clearerr (dmp -> fp_xiso); + fwrite (isobuf, SECTOR_SIZE, 1, dmp -> fp_xiso); + if (ferror (dmp -> fp_xiso)) { + error ("fwrite() to XISO output file failed"); + out = false; + if (current_sector) + *current_sector = i; + } + + if (dmp -> hashing && out) + multihash_update (&(dmp -> hash_xiso), isobuf, SECTOR_SIZE); + } + } + + if (dmp -> flushing) + fflush (dmp -> fp_xiso); + + if ((i % 320 == 0) || (i == last_sector)) { + if (dmp -> progress) + dmp -> progress (false, i + 1, target_sectors, dmp -> progress_data); + } + } + + if (dmp -> hashing) + multihash_finish (&(dmp -> hash_xiso)); + + if (dmp -> fp_xiso) { + fclose (dmp -> fp_xiso); + dmp -> fp_xiso = NULL; + } + + return (out); +} + + +dumper *dumper_new (disc *d) { + dumper *dmp; + + dmp = (dumper *) malloc (sizeof (dumper)); + memset (dmp, 0, sizeof (dumper)); + dmp -> dsk = d; + dumper_set_hashing (dmp, true); + dumper_set_flushing (dmp, true); + + return (dmp); +} + + +void dumper_set_progress_callback (dumper *dmp, progress_func progress, void *progress_data) { + dmp -> progress = progress; + dmp -> progress_data = progress_data; + + return; +} + + +void dumper_set_hashing (dumper *dmp, bool h) { + dmp -> hashing = h; + debug ("Hashing %s", h ? "enabled" : "disabled"); + + return; +} + + +void dumper_set_flushing (dumper *dmp, bool f) { + dmp -> flushing = f; + debug ("Flushing %s", f ? "enabled" : "disabled"); + + return; +} + +void *dumper_destroy (dumper *dmp) { + my_free (dmp -> outfile_raw); + my_free (dmp -> outfile_iso); + my_free (dmp -> outfile_xiso); + my_free (dmp); + + return (NULL); +} + + +char *dumper_get_iso_crc32 (dumper *dmp) { + return ((dmp -> hash_iso).crc32_s); +} + + +char *dumper_get_raw_crc32 (dumper *dmp) { + return ((dmp -> hash_raw).crc32_s); +} + +char *dumper_get_xiso_crc32 (dumper *dmp) { + return ((dmp -> hash_xiso).crc32_s); +} + + +char *dumper_get_iso_md4 (dumper *dmp) { + return ((dmp -> hash_iso).md4_s); +} + + +char *dumper_get_raw_md4 (dumper *dmp) { + return ((dmp -> hash_raw).md4_s); +} + +char *dumper_get_xiso_md4 (dumper *dmp) { + return ((dmp -> hash_xiso).md4_s); +} + + +char *dumper_get_iso_md5 (dumper *dmp) { + return ((dmp -> hash_iso).md5_s); +} + + +char *dumper_get_raw_md5 (dumper *dmp) { + return ((dmp -> hash_raw).md5_s); +} + +char *dumper_get_xiso_md5 (dumper *dmp) { + return ((dmp -> hash_xiso).md5_s); +} + + +char *dumper_get_iso_ed2k (dumper *dmp) { + return ((dmp -> hash_iso).ed2k_s); +} + + +char *dumper_get_raw_ed2k (dumper *dmp) { + return ((dmp -> hash_raw).ed2k_s); +} + + +char *dumper_get_iso_sha1 (dumper *dmp) { + return ((dmp -> hash_iso).sha1_s); +} + + +char *dumper_get_raw_sha1 (dumper *dmp) { + return ((dmp -> hash_raw).sha1_s); +} + +char *dumper_get_xiso_sha1 (dumper *dmp) { + return ((dmp -> hash_xiso).sha1_s); +} + + +char *dumper_get_iso_sha2 (dumper *dmp) { + return ((dmp -> hash_iso).sha2_s); +} + + +char *dumper_get_raw_sha2 (dumper *dmp) { + return ((dmp -> hash_raw).sha2_s); +} + +char *dumper_get_xiso_sha2 (dumper *dmp) { + return ((dmp -> hash_xiso).sha2_s); +} diff --git a/libfriidump/dumper.h b/libfriidump/dumper.h new file mode 100644 index 0000000..d7835d5 --- /dev/null +++ b/libfriidump/dumper.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://www.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef DUMPER_H_INCLUDED +#define DUMPER_H_INCLUDED + +#include "misc.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct dumper_s dumper; + +typedef void (*progress_func) (bool start, u_int32_t current_sector, u_int32_t total_sectors, void *progress_data); + +FRIIDUMPLIB_EXPORT bool dumper_set_raw_output_file (dumper *dmp, char *outfile_raw, bool resume); +FRIIDUMPLIB_EXPORT bool dumper_set_iso_output_file (dumper *dmp, char *outfile_iso, bool resume); +FRIIDUMPLIB_EXPORT bool dumper_set_xiso_output_file (dumper *dmp, char *outfile_xiso, bool resume); +FRIIDUMPLIB_EXPORT bool dumper_prepare_xiso (dumper *dmp); +FRIIDUMPLIB_EXPORT bool dumper_prepare (dumper *dmp); +FRIIDUMPLIB_EXPORT int dumper_dump (dumper *dmp, u_int32_t *current_sector); +FRIIDUMPLIB_EXPORT int dumper_dump_xiso (dumper *dmp, u_int32_t *current_sector); +FRIIDUMPLIB_EXPORT dumper *dumper_new (disc *d); +FRIIDUMPLIB_EXPORT void dumper_set_progress_callback (dumper *dmp, progress_func progress, void *progress_data); +FRIIDUMPLIB_EXPORT void dumper_set_hashing (dumper *dmp, bool h); +FRIIDUMPLIB_EXPORT void dumper_set_flushing (dumper *dmp, bool f); +FRIIDUMPLIB_EXPORT void *dumper_destroy (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_iso_crc32 (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_xiso_crc32 (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_raw_crc32 (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_iso_md4 (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_xiso_md4 (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_raw_md4 (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_iso_md5 (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_xiso_md5 (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_raw_md5 (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_iso_ed2k (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_raw_ed2k (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_iso_sha1 (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_xiso_sha1 (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_raw_sha1 (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_iso_sha2 (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_xiso_sha2 (dumper *dmp); +FRIIDUMPLIB_EXPORT char *dumper_get_raw_sha2 (dumper *dmp); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libfriidump/dvd_drive.c b/libfriidump/dvd_drive.c new file mode 100644 index 0000000..036d38b --- /dev/null +++ b/libfriidump/dvd_drive.c @@ -0,0 +1,1785 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/*! \file + * \brief A class to send raw MMC commands to a CD/DVD-ROM drive. + * + * This class can be used to send raw MMC commands to a CD/DVD-ROM drive. It uses own structures and data types to represent the commands, which are + * then transformed in the proper OS-dependent structures when the command is executed, achieving portability. Currently Linux and Windows are supported, but + * all that is needed to add support to a new OS is a proper dvd_execute_cmd() function, so it should be very easy. I hope that someone can add + * compatibility with MacOS X and *BSD: libcdio is a good place to understand how it should be done :). Actally, we could have used libcdio right from the start, + * but I didn't want to add a dependency on a library that cannot be easily found in binary format for all the target OS's. + * + * This file contains code derived from the work of Kevin East (SeventhSon), kev@kev.nu, http://www.kev.nu/360/ , which, in turn, derives from work by + * a lot of other people. See his page for full details. + */ + +#include "rs.h" +#include "misc.h" +#include +#include +//#include +#include +#include +#include +#include +#include "dvd_drive.h" +#include "disc.h" +#include "sha1.h" + +#ifdef WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#endif + + +/*! \brief Timeout for MMC commands. + * + * This must be expressed in seconds (Windows uses seconds, right?). + */ +#define MMC_CMD_TIMEOUT 10 + + +/* Imported drive-specific functions */ +int vanilla_2064_dvd_dump_mem (dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf); +int vanilla_2384_dvd_dump_mem (dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf); +int hitachi_dvd_dump_mem (dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf); +int hitachi_dvd_dump_mem_type1 (dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf); +int liteon_dvd_dump_mem (dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf); +int renesas_dvd_dump_mem (dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf); + + +/*! \brief A structure that represents a CD/DVD-ROM drive. + */ +struct dvd_drive_s { + /* Device special file */ + char *device; //!< The path to the drive (i.e.: /dev/something on Unix, x: on Windows). + + /* Data about the drive */ + char *vendor; //!< The drive vendor. + char *prod_id; //!< The drive product ID. + char *prod_rev; //!< The drive product revision (Usually firmware version). + char *model_string; //!< The above three strings, joined in a single one. + u_int32_t def_method; + u_int32_t command; + u_int32_t hlds_e7_type; + u_int32_t hlds_e7_cache_base; + u_int32_t hlds_e7_mem_blocks; + u_int32_t hlds_e7_static_cdb_base; + u_int32_t hlds_e7_static_gate; + int hlds_e7_preferred_method; + const char *hlds_e7_profile_label; + const char *hlds_e7_support_tier; + const char *hlds_e7_family; + const char *hlds_e7_tokens; + const char *hlds_e7_record_id; + const char *hlds_e7_notes; + + /* Device-dependent internal memory dump function */ + /*! The intended area should start where sector data is stored upon a READ command. Here we assume that sectors are + * stored one after the other, as heuristics showed it is the case for the Hitachi MN103-based drives, but this model + * might be changed in the future, if we get support for other drives. + */ + dvd_drive_memdump_func memdump; //!< A pointer to a function that is able to dump the drive's internal memory area. + bool supported; //!< True if the drive is a supported model, false otherwise. + + + /* File descriptor & stuff used to access drive */ +#ifdef WIN32 + HANDLE fd; //!< The HANDLE to interact with the drive on Windows. +#else + int fd; //!< The file descriptor to interact with the drive on Unix. +#endif +}; + + +/** \brief Supported MMC commands. + */ +enum mmc_commands_e { + SPC_TEST_UNIT_READY = 0x00, + SPC_INQUIRY = 0x12, + SPC_MODE_SELECT_6 = 0x15, + MMC_START_STOP_UNIT = 0x1B, + MMC_READ_CAPACITY_10 = 0x25, + MMC_READ_10 = 0x28, + SPC_MODE_SENSE_10 = 0x5A, + SPC_MODE_SELECT_10 = 0x55, + MMC_READ_12 = 0xA8, + MMC_READ_DVD_STRUCTURE = 0xAD, +}; + +typedef struct { + u_int8_t s[256]; + u_int8_t i; + u_int8_t j; +} xbox_rc4_ctx; + +static void xbox_rc4_init (xbox_rc4_ctx *ctx, const u_int8_t *key, size_t keylen) { + u_int32_t i; + u_int8_t j, tmp; + + for (i = 0; i < 256; i++) + ctx -> s[i] = (u_int8_t) i; + ctx -> i = 0; + ctx -> j = 0; + + if (keylen == 0) + return; + + j = 0; + for (i = 0; i < 256; i++) { + j = (u_int8_t) (j + ctx -> s[i] + key[i % keylen]); + tmp = ctx -> s[i]; + ctx -> s[i] = ctx -> s[j]; + ctx -> s[j] = tmp; + } +} + +static void xbox_rc4_crypt (xbox_rc4_ctx *ctx, const u_int8_t *in, u_int8_t *out, size_t len) { + size_t n; + u_int8_t tmp, k; + + for (n = 0; n < len; n++) { + ctx -> i = (u_int8_t) (ctx -> i + 1); + ctx -> j = (u_int8_t) (ctx -> j + ctx -> s[ctx -> i]); + tmp = ctx -> s[ctx -> i]; + ctx -> s[ctx -> i] = ctx -> s[ctx -> j]; + ctx -> s[ctx -> j] = tmp; + k = ctx -> s[(u_int8_t) (ctx -> s[ctx -> i] + ctx -> s[ctx -> j])]; + out[n] = in[n] ^ k; + } +} + +static bool dvd_prod_has (dvd_drive *dvd, const char *needle) { + return dvd && dvd -> prod_id && needle && strstr (dvd -> prod_id, needle) != NULL; +} + +static bool dvd_vendor_is (dvd_drive *dvd, const char *vendor) { + return dvd && dvd -> vendor && vendor && strcmp (dvd -> vendor, vendor) == 0; +} + +static bool dvd_is_hlds_drive (dvd_drive *dvd) { + return dvd_vendor_is (dvd, "HL-DT-ST"); +} + +static bool dvd_prod_has_any (dvd_drive *dvd, const char **needles, size_t count) { + size_t i; + for (i = 0; i < count; i++) { + if (dvd_prod_has (dvd, needles[i])) + return true; + } + return false; +} + +static bool dvd_is_hlds_gcc4243_4244_drive (dvd_drive *dvd) { + static const char *names[] = { + "GCC-4243N", "GCC4243N", "GCC4243", + "GCC-4244N", "GCC4244N", "GCC4244" + }; + return dvd_is_hlds_drive (dvd) && dvd_prod_has_any (dvd, names, sizeof (names) / sizeof (names[0])); +} + +static bool dvd_is_hlds_gdr8050l_drive (dvd_drive *dvd) { + static const char *names[] = { + "GDR8050L", "GDR-8050L" + }; + return dvd_is_hlds_drive (dvd) && dvd_prod_has_any (dvd, names, sizeof (names) / sizeof (names[0])); +} + + +typedef struct { + const char *model; + const char *firmware; + u_int32_t type; + u_int32_t cache_base; + u_int32_t mem_blocks; + int preferred_method; + const char *label; + const char *support_tier; + const char *family; + const char *tokens; + const char *record_id; + u_int32_t static_cdb_base; + u_int32_t static_gate; + const char *notes; +} hlds_e7_profile_desc; + +static const hlds_e7_profile_desc hlds_e7_profiles[] = { + { "GCC-4241N", "A101", 21, 0x80000000U, 1, 8, "GCC-4241N A101 promoted E7 parser profile", "error_prone_supported_profile_hardening", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "stage5b_0064", 0x85cU, 0x900356b7U, "capable but error-prone; conservative Method 8 one-window validation profile" }, + { "GCC-4242N", "0J06", 22, 0x80000000U, 1, 8, "GCC-4242N 0J06 promoted E7 parser profile", "error_prone_supported_profile_hardening", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "stage5b_0066", 0x824U, 0x90038621U, "capable but error-prone; conservative Method 8 one-window validation profile" }, + { "GCC-4243N", "0000", 3, 0x80000000U, 5, 8, "GCC-4243N 0000 promoted E7 parser profile", "known_supported_profile_hardening", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "stage5b_0069", 0x884U, 0x90037929U, "known-supported GCC_424x profile hardening target" }, + { "GCC-4243N", "1.08", 3, 0x80000000U, 5, 8, "GCC-4243N 1.08 promoted E7 parser profile", "known_supported_profile_hardening", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "stage5b_0071", 0x880U, 0x90036813U, "known-supported GCC_424x profile hardening target" }, + { "GCC-4244N", "1.03", 3, 0x80000000U, 5, 8, "GCC-4244N 1.03 promoted E7 parser profile", "known_supported_profile_hardening", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "stage5b_0073", 0x88cU, 0x90037d06U, "P1 owned GCC_424x profile hardening target" }, + { "GCC-4244N", "103", 3, 0x80000000U, 5, 8, "GCC-4244N 103 promoted E7 parser profile", "known_supported_profile_hardening", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "stage5b_0076", 0x894U, 0x900386fbU, "P1 owned GCC_424x profile hardening target" }, + { "GCC4244", "B103", 3, 0x80000000U, 5, 8, "GCC-4244N B103 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "promoted_parser_signature_v23", 0x894U, 0x900386fbU, "Live INQUIRY alias for HL-DT-ST CDRW/DVD GCC4244 B103; case label B101; exact Stage5B parser signature recovered; media preflight, seed retrieval, full GameCube dump, STOP UNIT, and Redump hash match validated" }, + { "GCC-4244N", "B103", 3, 0x80000000U, 5, 8, "GCC-4244N B103 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "promoted_parser_signature_v23", 0x894U, 0x900386fbU, "B103 shares the promoted parser signature gate/CDB with 103/104; live GCC4244/B103 hardware completed a Redump-matching GameCube dump" }, + { "GCC-4244N", "104", 3, 0x80000000U, 5, 8, "GCC-4244N 104 promoted E7 parser profile", "known_supported_profile_hardening", "GCC_424x", "HL;IT;RPC;RPC_JCS3;RPC_SUFFIX", "stage5b_0078", 0x894U, 0x900386fbU, "P1 owned GCC_424x profile hardening target" }, + { "GDR-3120L", "0046", 4, 0x80000000U, 5, 8, "GDR-3120L 0046 experimental GC/Wii E7 parser profile", "experimental_gc_wii_candidate", "GDR_3120x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "stage5b_0355", 0x5b8U, 0x90025bceU, "historically Xbox/reference; allow read-only GC/Wii Method 8 experiment, not proven support until dump validates" }, + { "GDR-8050L", "0L23", 44, 0x80000000U, 1, 8, "GDR-8050L 0L23 donor/modified-firmware E7 parser profile", "donor_reference_modified_firmware_only", "GDR_8050x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "stage5b_0099", 0x5d0U, 0x90026160U, "stock firmware is Xbox-only here; GC/Wii 0xE7 path requires modified firmware with memdump support" }, + { "GDR-8082N", "0120", 4, 0x80000000U, 5, 9, "GDR-8082N 0120 promoted E7 parser profile", "known_supported_profile_hardening", "GDR_808x", "HL;IT;RPC;RPC_SUFFIX", "stage5b_0103", 0x638U, 0x900282daU, "known-supported reference profile" }, + { "GDR-8083N", "0K04", 4, 0x80000000U, 5, 9, "GDR-8083N 0K04 promoted E7 parser profile", "known_supported_profile_hardening", "GDR_808x", "HL;IT;RPC;RPC_SUFFIX", "stage5b_0104", 0x638U, 0x900291fcU, "known-supported reference profile" }, + { "GDR-8161B", "0102", 4, 0x80000000U, 5, 9, "GDR-8161B 0102 promoted E7 parser profile", "known_supported_profile_hardening", "GDR_816x", "IT;RPC;RPC_SUFFIX", "stage5b_0109", 0x5a8U, 0x90025010U, "known-supported reference profile" }, + { "GDR-8163B", "0L23", 4, 0x80000000U, 5, 9, "GDR-8163B 0L23 promoted E7 parser profile", "known_supported_profile_hardening_p0", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "stage5b_0110", 0x5e0U, 0x90024d5aU, "P0 owned profile hardening target" }, + { "GDR-8163B", "0L30", 4, 0x80000000U, 5, 8, "GDR-8163B 0L30 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "promoted_parser_signature_v23", 0x5e0U, 0x90025021U, "Germany-batch variant; Method 8 seed retrieval and full GameCube dump OK; exact Stage5B parser signature recovered" }, + { "GDR-8163B", "0L20", 4, 0x80000000U, 5, -1, "GDR-8163B 0L20 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "promoted_parser_signature_v23", 0x5e0U, 0x90024c8fU, "Germany-batch variant; exact Stage5B parser signature recovered; full GameCube dump matches Redump" }, + { "GDR-8163B", "0D20", 4, 0x80000000U, 5, -1, "GDR-8163B 0D20 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "promoted_parser_signature_v23", 0x5e0U, 0x90024ae7U, "Germany-batch variant; exact Stage5B parser signature recovered; full GameCube dump matches Redump" }, + { "GDR-8163B", "0B30", 4, 0x80000000U, 5, -1, "GDR-8163B 0B30 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "promoted_parser_signature_v23", 0x5e0U, 0x90025030U, "Germany-batch HP/OEM variant; exact Stage5B parser signature recovered; full GameCube dump matches Redump" }, + { "GDR-8163B", "0E15", 4, 0x80000000U, 5, -1, "GDR-8163B 0E15 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "promoted_parser_signature_v23", 0x5d8U, 0x900247d1U, "Germany-batch HP/OEM variant; exact Stage5B parser signature recovered with CDB base 0x5D8; full GameCube dump matches Redump" }, + { "GDR-8163B", "0M26", 4, 0x80000000U, 5, -1, "GDR-8163B 0M26 promoted E7 parser profile", "known_supported_profile_hardening_live_validated", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "promoted_parser_signature_v23", 0x5e0U, 0x90024ff7U, "Germany-batch Lenovo/OEM Malaysia variant; exact Stage5B parser signature recovered; full GameCube dump matches Redump" }, + { "GDR-8164B", "0L06", 4, 0x80000000U, 5, 9, "GDR-8164B 0L06 promoted E7 parser profile", "known_supported_profile_hardening", "GDR_816x", "HL;IT;RPC;RPC_JD4_SPACE;RPC_SUFFIX", "stage5b_0111/stage5b_0113", 0x5ccU, 0x90025bbeU, "known-supported reference profile; two firmware records agree" }, + { NULL, NULL, 0, 0, 0, -1, NULL, NULL, NULL, NULL, NULL, 0, 0, NULL } +}; + +static void hlds_normalize_model (const char *src, char *dst, size_t dst_size) { + size_t i, j; + if (!dst || dst_size == 0) + return; + dst[0] = 0; + if (!src) + return; + for (i = 0, j = 0; src[i] && j + 1 < dst_size; i++) { + unsigned char c = (unsigned char) src[i]; + if (isalnum (c)) + dst[j++] = (char) toupper (c); + } + dst[j] = 0; +} + +static bool hlds_model_matches (dvd_drive *dvd, const char *model) { + char prod_norm[64]; + char model_norm[64]; + if (!dvd || !dvd -> prod_id || !model) + return false; + hlds_normalize_model (dvd -> prod_id, prod_norm, sizeof (prod_norm)); + hlds_normalize_model (model, model_norm, sizeof (model_norm)); + return prod_norm[0] && model_norm[0] && strstr (prod_norm, model_norm) != NULL; +} + +static bool hlds_revision_matches (dvd_drive *dvd, const char *revision) { + const unsigned char *a; + const unsigned char *b; + if (!revision || !revision[0]) + return true; + if (!dvd || !dvd -> prod_rev) + return false; + a = (const unsigned char *) dvd -> prod_rev; + b = (const unsigned char *) revision; + while (*a && *b) { + if (toupper (*a) != toupper (*b)) + return false; + a++; + b++; + } + return *a == 0 && *b == 0; +} + +static const hlds_e7_profile_desc *dvd_find_hlds_e7_profile (dvd_drive *dvd) { + const hlds_e7_profile_desc *p; + if (!dvd_is_hlds_drive (dvd)) + return NULL; + for (p = hlds_e7_profiles; p -> model; p++) { + if (hlds_model_matches (dvd, p -> model) && hlds_revision_matches (dvd, p -> firmware)) + return p; + } + return NULL; +} + +static u_int32_t dvd_hlds_e7_detect_type (dvd_drive *dvd) { + const hlds_e7_profile_desc *profile = dvd_find_hlds_e7_profile (dvd); + static const char *type1[] = { + "GCC-4160N", "GCC4160N", "GCC4160", + "GCC-4240N", "GCC4240N", "GCC4240" + }; + static const char *type2_1[] = { + "GCC-4241N", "GCC4241N", "GCC4241" + }; + static const char *type2_2[] = { + "GCC-4242N", "GCC4242N", "GCC4242" + }; + static const char *gdr8081n[] = { + "GDR8081N", "GDR-8081N" + }; + static const char *type3[] = { + "GCC4244", "GCC4244N", "GCC-4244N", + "GCC4247", "GCC4247N", "GCC-4247N", + "GDR8083N", "GDR8084N", + "GCC-4243N", "GCC4243N", "GCC4243", + "GCC-4246N", "GCC4246N", "GCC4246" + }; + static const char *type4[] = { + "DU10N", "GDR8082N", "GDR8161B", "GDR8162B", + "GDR8163B", "GDR8164B", "GDR-T10N", + /* Local project keeps GDR-3120L in the same transport family for + * classification, but Xbox dumping is still routed through the explicit + * Xbox paths rather than the GC/Wii Method 8/9 readers. */ + "GDR3120L", "GDR-3120L" + }; + + if (!dvd_is_hlds_drive (dvd)) + return 0; + if (profile) + return profile -> type; + if (dvd_prod_has_any (dvd, type1, sizeof (type1) / sizeof (type1[0]))) + return 1; + if (dvd_prod_has_any (dvd, type2_1, sizeof (type2_1) / sizeof (type2_1[0]))) + return 21; + if (dvd_prod_has_any (dvd, type2_2, sizeof (type2_2) / sizeof (type2_2[0]))) + return 22; + if (dvd_prod_has_any (dvd, gdr8081n, sizeof (gdr8081n) / sizeof (gdr8081n[0]))) + return 81; + if (dvd_prod_has_any (dvd, type3, sizeof (type3) / sizeof (type3[0]))) + return 3; + if (dvd_is_hlds_gdr8050l_drive (dvd)) + return 44; + if (dvd_prod_has_any (dvd, type4, sizeof (type4) / sizeof (type4[0]))) + return 4; + /* DIC labels GSA-4163B as an Xbox swap candidate, not a normal 0xE7 + * Nintendo-disc cache dump profile, so keep it out of the profile layer. */ + return 0; +} + +static bool dvd_is_hlds_gc_wii_drive (dvd_drive *dvd) { + return dvd_hlds_e7_detect_type (dvd) != 0; +} + +static const char *dvd_hlds_e7_profile_name_from_type (u_int32_t type) { + switch (type) { + case 1: return "Type1"; + case 21: return "Type2_1 experimental"; + case 22: return "Type2_2 experimental"; + case 3: return "Type3"; + case 4: return "Type4"; + case 44: return "GDR-8050L modified 0xE7 single-window proven fallback"; + case 45: return "GDR-8050L modified 0xE7 speed-probe pending"; + case 442: return "GDR-8050L modified 0xE7 probe B 2-window"; + case 443: return "GDR-8050L modified 0xE7 probe A 3-window"; + case 445: return "GDR-8050L modified 0xE7 probe C 5-window guarded"; + case 81: return "GDR-8081N experimental 0xE7 probe"; + case 811: return "GDR-8081N probe A Type4-derived"; + case 812: return "GDR-8081N probe B single-window"; + case 813: return "GDR-8081N probe C Type1-base"; + case 814: return "GDR-8081N probe E exact-offset moving-cache candidate"; + case 815: return "GDR-8081N probe A scan-guided Type4-derived"; + default: return "none"; + } +} + +static void dvd_apply_hlds_e7_profile (dvd_drive *dvd) { + const hlds_e7_profile_desc *profile = dvd_find_hlds_e7_profile (dvd); + dvd -> hlds_e7_type = dvd_hlds_e7_detect_type (dvd); + dvd -> hlds_e7_cache_base = 0x80000000U; + dvd -> hlds_e7_mem_blocks = 5; + dvd -> hlds_e7_static_cdb_base = 0; + dvd -> hlds_e7_static_gate = 0; + dvd -> hlds_e7_preferred_method = -1; + dvd -> hlds_e7_profile_label = NULL; + dvd -> hlds_e7_support_tier = NULL; + dvd -> hlds_e7_family = NULL; + dvd -> hlds_e7_tokens = NULL; + dvd -> hlds_e7_record_id = NULL; + dvd -> hlds_e7_notes = NULL; + + switch (dvd -> hlds_e7_type) { + case 1: + /* DIC Type1: GCC-4160N/GCC-4240N cache frames begin at 0x00a13000 + * and only one 16-sector cache window is consumed per READ. */ + dvd -> hlds_e7_cache_base = 0x00a13000U; + dvd -> hlds_e7_mem_blocks = 1; + break; + case 21: + case 22: + /* DIC Type2 uses a moving 0x80000000-derived cache address. This + * branch logs/classifies it, but does not yet claim DIC parity. */ + dvd -> hlds_e7_cache_base = 0x80000000U; + dvd -> hlds_e7_mem_blocks = 1; + break; + case 81: + /* GDR-8081N is not in the confirmed DIC dump list, but local firmware + * analysis suggests an 0xE7 command surface. Start with a Type4-derived + * candidate; disc.c probes and may switch to one of the 811..814 runtime + * profiles before seed cracking continues. */ + dvd -> hlds_e7_cache_base = 0x80000000U; + dvd -> hlds_e7_mem_blocks = 5; + break; + case 44: + /* Stock GDR-8050L firmware does not expose the HIT 0xE7 memdump command. + * The local test unit is GDR-8163B hardware cross-flashed with modified + * GDR-8050L firmware where 0xE7 memdump was added. Single-window has + * completed and hash-matched Sonic, so it remains the proven fallback. + * Start in an explicit speed-probe-pending profile so the initial drive + * information does not look like the old static single-window build. + * disc.c promotes to 2/3/5 windows only after guarded validation, or + * settles back to the proven single-window profile. */ + dvd -> hlds_e7_type = 45; + dvd -> hlds_e7_cache_base = 0x80000000U; + dvd -> hlds_e7_mem_blocks = 1; + break; + case 3: + case 4: + default: + dvd -> hlds_e7_cache_base = 0x80000000U; + dvd -> hlds_e7_mem_blocks = 5; + break; + } + + if (profile) { + dvd -> hlds_e7_cache_base = profile -> cache_base; + dvd -> hlds_e7_mem_blocks = profile -> mem_blocks; + dvd -> hlds_e7_static_cdb_base = profile -> static_cdb_base; + dvd -> hlds_e7_static_gate = profile -> static_gate; + dvd -> hlds_e7_preferred_method = profile -> preferred_method; + dvd -> hlds_e7_profile_label = profile -> label; + dvd -> hlds_e7_support_tier = profile -> support_tier; + dvd -> hlds_e7_family = profile -> family; + dvd -> hlds_e7_tokens = profile -> tokens; + dvd -> hlds_e7_record_id = profile -> record_id; + dvd -> hlds_e7_notes = profile -> notes; + /* The modified GDR-8050L runtime profile still starts in the guarded + * speed-probe-pending state, but keeps its static parser evidence fields. */ + if (profile -> type == 44) { + dvd -> hlds_e7_type = 45; + dvd -> hlds_e7_mem_blocks = 1; + } + } +} + +void dvd_set_hlds_e7_runtime_profile (dvd_drive *dvd, u_int32_t type, u_int32_t cache_base, u_int32_t mem_blocks) { + if (!dvd) + return; + dvd -> hlds_e7_type = type; + dvd -> hlds_e7_cache_base = cache_base; + dvd -> hlds_e7_mem_blocks = mem_blocks; +} + +static bool dvd_is_tsst_kreon_candidate (dvd_drive *dvd) { + if (!(dvd_vendor_is (dvd, "TSSTcorp") || dvd_vendor_is (dvd, "SAMSUNG"))) + return false; + return + dvd_prod_has (dvd, "TS-H352C") || + dvd_prod_has (dvd, "TS-H353A") || + dvd_prod_has (dvd, "SH-D162C") || + dvd_prod_has (dvd, "SH-D162D") || + dvd_prod_has (dvd, "SH-D163A") || + dvd_prod_has (dvd, "SH-D163B"); +} + + +/** + * Initializes a structure representing an MMC command. + * @param mmc A pointer to the MMC command structure. + * @param buf The buffer where results of the MMC command execution provided by the drive should be stored, or NULL if no buffer will be provided. + * @param len The length of the buffer (ignored in case buf is NULL). + * @param sense A pointer to a structure which will hold the SENSE DATA got from the drive after the command has been executed, or NULL. + */ +void dvd_init_command (mmc_command *mmc, u_int8_t *buf, int len, req_sense *sense) { + memset (mmc, 0, sizeof (mmc_command)); + if (buf) + memset (buf, 0, len); + mmc -> cmdlen = 12; + mmc -> direction = buf && len > 0 ? DVD_DATA_IN : DVD_DATA_NONE; + mmc -> buffer = buf; + mmc -> buflen = buf ? len : 0; + mmc -> sense = sense; + + return; +} + + +#ifdef WIN32 + +/* Doc is under the UNIX function */ +int dvd_execute_cmd (dvd_drive *dvd, mmc_command *mmc, bool ignore_errors) { + SCSI_PASS_THROUGH_DIRECT *sptd; + unsigned char sptd_sense[sizeof (*sptd) + 18], *sense; + DWORD bytes; + int out; + + sptd = (SCSI_PASS_THROUGH_DIRECT *) sptd_sense; + sense = &sptd_sense[sizeof (*sptd)]; + + memset (sptd, 0, sizeof (sptd_sense)); + memcpy (sptd -> Cdb, mmc -> cmd, sizeof (mmc -> cmd)); + sptd -> Length = sizeof (SCSI_PASS_THROUGH_DIRECT); + sptd -> CdbLength = mmc -> cmdlen; + sptd -> SenseInfoLength = 18; + if (mmc -> direction == DVD_DATA_OUT) + sptd -> DataIn = SCSI_IOCTL_DATA_OUT; + else if (mmc -> direction == DVD_DATA_NONE) + sptd -> DataIn = SCSI_IOCTL_DATA_UNSPECIFIED; + else + sptd -> DataIn = SCSI_IOCTL_DATA_IN; + sptd -> DataBuffer = mmc -> buffer; + // Quick hack: Windows hates sptd->DataTransferLength = 1, so we set it to 2 and ignore the second byte. + if (mmc -> buflen == 1) // TODO + sptd -> DataTransferLength = 2; + else + sptd -> DataTransferLength = mmc -> buflen; + sptd -> TimeOutValue = MMC_CMD_TIMEOUT; + sptd -> SenseInfoOffset = sizeof (*sptd); + + //fprintf (stdout,"mmc->cmd[00] = %d \n",mmc->cmd[00]); + //Set streaming hack + if (mmc->cmd[00]==0xB6) { + sptd -> DataIn = SCSI_IOCTL_DATA_OUT; + sptd -> DataTransferLength = 28; + } + + { + BOOL ioctl_ok = DeviceIoControl (dvd -> fd, IOCTL_SCSI_PASS_THROUGH_DIRECT, + sptd, sizeof (*sptd) + 18, sptd, sizeof (*sptd) + 18, &bytes, NULL); + /* DeviceIoControl may succeed while the drive returns CHECK CONDITION. + * Treat any non-GOOD SCSI status as command failure. ignore_errors only + * suppresses diagnostics; it must not turn failed commands into success. */ + if (!ioctl_ok || sptd -> ScsiStatus != 0) { + out = -1; /* Failure */ + if (!ignore_errors) { + error ("Execution of MMC command failed: Win32=%lu SCSI=0x%02X", + (unsigned long) GetLastError (), sptd -> ScsiStatus); + debug ("Command was: "); + hex_and_ascii_print ("", mmc -> cmd, sizeof (mmc -> cmd)); + debug ("Sense data: %02X/%02X/%02X\n", sense[2] & 0x0F, sense[12], sense[13]); + } + } else { + out = 0; + } + } + + if (mmc -> sense) { + mmc -> sense -> sense_key = sense[2]; + mmc -> sense -> asc = sense[12]; + mmc -> sense -> ascq = sense[13]; + } + + return (out); +} + +#else + +/** + * Executes an MMC command. + * @param dvd The DVD drive the command should be exectued on. + * @param mmc The command to be executed. + * @param ignore_errors If set to true, no error will be printed if the command fails. + * @return 0 if the command was executed successfully, < 0 otherwise. + */ +int dvd_execute_cmd (dvd_drive *dvd, mmc_command *mmc, bool ignore_errors) { + int out; + struct cdrom_generic_command cgc; + struct request_sense sense; + +#if 0 + debug ("Executing MMC command: "); + hex_and_ascii_print ("", mmc -> cmd, sizeof (mmc -> cmd)); +#endif + + /* Init Linux-format MMC command */ + memset (&cgc, 0, sizeof (struct cdrom_generic_command)); + memcpy (cgc.cmd, mmc -> cmd, sizeof (mmc -> cmd)); + cgc.buffer = (unsigned char *) mmc -> buffer; + cgc.buflen = mmc -> buflen; + if (mmc -> direction == DVD_DATA_OUT) + cgc.data_direction = CGC_DATA_WRITE; + else if (mmc -> direction == DVD_DATA_NONE) + cgc.data_direction = CGC_DATA_NONE; + else + cgc.data_direction = CGC_DATA_READ; + cgc.timeout = MMC_CMD_TIMEOUT * 1000; /* Linux uses milliseconds */ + cgc.sense = &sense; + if (ioctl (dvd -> fd, CDROM_SEND_PACKET, &cgc) < 0) { + out = -1; /* Failure */ + if (!ignore_errors) { + error ("Execution of MMC command failed: %s", strerror (errno)); + debug ("Command was:"); + hex_and_ascii_print ("", cgc.cmd, sizeof (cgc.cmd)); + debug ("Sense data: %02X/%02X/%02X", sense.sense_key, sense.asc, sense.ascq); + } + } else { + out = 0; + } + + if (mmc -> sense) { + mmc -> sense -> sense_key = sense.sense_key; + mmc -> sense -> asc = sense.asc; + mmc -> sense -> ascq = sense.ascq; + } + + return (out); +} +#endif + + +/** + * Sends an INQUIRY command to the drive to retrieve drive identification strings. + * @param dvd The DVD drive the command should be exectued on. + * @return 0 if the command was executed successfully, < 0 otherwise. + */ +static int dvd_get_drive_info (dvd_drive *dvd) { + mmc_command mmc; + int out; + u_int8_t buf[36]; + char tmp[36 * 4]; + + dvd_init_command (&mmc, buf, sizeof (buf), NULL); + mmc.cmd[0] = SPC_INQUIRY; + mmc.cmd[4] = sizeof (buf); + if ((out = dvd_execute_cmd (dvd, &mmc, false)) >= 0) { + my_strndup (dvd -> vendor, buf + 8, 8); + strtrimr (dvd -> vendor); + my_strndup (dvd -> prod_id, buf + 16, 16); + strtrimr (dvd -> prod_id); + my_strndup (dvd -> prod_rev, buf + 32, 4); + strtrimr (dvd -> prod_rev); + snprintf (tmp, sizeof (tmp), "%s/%s/%s", dvd -> vendor, dvd -> prod_id, dvd -> prod_rev); + my_strdup (dvd -> model_string, tmp); + + debug ("DVD drive is \"%s\"", dvd -> model_string); + } else { + error ("Cannot identify DVD drive\n"); + } + + return (out); +} + + +/** + * Assigns the proper memory dump functions to a dvd_drive object, according to vendor, model and other parameters. Actually this scheme probably needs to + * to be improved, but it is enough for the moment. + * @param dvd The DVD drive the command should be exectued on. + */ +static void dvd_assign_functions (dvd_drive *dvd, u_int32_t command) { + dvd -> def_method = 0; + if (dvd_is_hlds_gc_wii_drive (dvd)) { + dvd_apply_hlds_e7_profile (dvd); + debug ("Hitachi-LG MN103-family 0xE7 drive detected: profile=%s tier=%s family=%s base=0x%08x windows=%u cdb=0x%03x gate=0x%08x", + dvd_get_hlds_e7_profile_name (dvd), + dvd_get_hlds_e7_support_tier (dvd), + dvd_get_hlds_e7_family (dvd), + dvd -> hlds_e7_cache_base, dvd -> hlds_e7_mem_blocks, + dvd -> hlds_e7_static_cdb_base, dvd -> hlds_e7_static_gate); + dvd -> memdump = &hitachi_dvd_dump_mem; + dvd -> command = 2; + dvd -> supported = true; + /* DIC Type1/Type3/Type4 use READ12 + HIT 0xE7 cache extraction. + * GCC-4244N is validated with Sonic Mega Collection and GCC-4243N is + * actively under test, so both remain Method 8. Type1 drives get the + * DIC-derived 0x00a13000 / one-window cache profile and also default to + * Method 8 so GCC-4160N/GCC-4240N can be tested without forcing a method. + * GDR-8050L gets a proven single-window fallback plus guarded speed probes + * for cross-flashed/modified firmware with 0xE7 memdump added; stock + * GDR-8050L firmware does not expose this GC/Wii memdump path. GDR-8081N + * is an experimental probe target and needs Method 8 so seed probing can run. + * Type2 is still classified only; leave it on the older Method 9 path. */ + if (dvd -> hlds_e7_preferred_method >= 0) + dvd -> def_method = (u_int32_t) dvd -> hlds_e7_preferred_method; + else if (dvd -> hlds_e7_type == 1 || dvd -> hlds_e7_type == 44 || dvd -> hlds_e7_type == 45 || dvd -> hlds_e7_type == 81 || dvd_is_hlds_gcc4243_4244_drive (dvd)) + dvd -> def_method = 8; + else + dvd -> def_method = 9; + + } else if (strcmp (dvd -> vendor, "LITE-ON") == 0 && ( + strcmp (dvd ->prod_id, "DVDRW LH-18A1H") == 0 || + strcmp (dvd ->prod_id, "DVDRW LH-18A1P") == 0 || + strcmp (dvd ->prod_id, "DVDRW LH-20A1H") == 0 || + strcmp (dvd ->prod_id, "DVDRW LH-20A1P") == 0 + )) { + debug ("Lite-On DVD drive detected, using Lite-On memory dump command"); + dvd -> memdump = &liteon_dvd_dump_mem; + dvd -> command = 3; + dvd -> supported = true; + dvd -> def_method = 5; + + } else if (dvd_is_tsst_kreon_candidate (dvd) || (strcmp (dvd -> vendor, "TSSTcorp") == 0 && ( + strcmp (dvd ->prod_id, "DVD-ROM SH-D162A") == 0 || + strcmp (dvd ->prod_id, "DVD-ROM SH-D162B") == 0 + ))) { + debug ("Toshiba Samsung DVD drive detected, using vanilla 2384 memory dump command"); + dvd -> memdump = &vanilla_2384_dvd_dump_mem; + dvd -> command = 1; + dvd -> supported = true; + dvd -> def_method = 0; + + } else if (strcmp (dvd -> vendor, "PLEXTOR") == 0) { + debug ("Plextor DVD drive detected, using vanilla 2064 memory dump command"); + dvd -> memdump = &vanilla_2064_dvd_dump_mem; + dvd -> command = 0; + dvd -> supported = true; + dvd -> def_method = 2; + + } else { + /* This is an unsupported drive (yet). */ + dvd -> memdump = &vanilla_2064_dvd_dump_mem; + dvd -> command = 0; + dvd -> supported = false; + } + + if (command!=-1) { + dvd -> command = command; + if (command == 0) dvd -> memdump = &vanilla_2064_dvd_dump_mem; + else if (command == 1) dvd -> memdump = &vanilla_2384_dvd_dump_mem; + else if (command == 2) dvd -> memdump = &hitachi_dvd_dump_mem; + else if (command == 3) dvd -> memdump = &liteon_dvd_dump_mem; + else if (command == 4) dvd -> memdump = &renesas_dvd_dump_mem; + } + + + //init Reed-Solomon for Lite-On + generate_gf(); + gen_poly(); + + return; +} + + +/** + * Creates a new structure representing a CD/DVD-ROM drive. + * @param device The CD/DVD-ROM device, in OS-dependent format (i.e.: /dev/something on Unix, x: on Windows). + * @return The newly-created structure, to be used with the other commands, or NULL if the drive could not be initialized. + */ +dvd_drive *dvd_drive_new (char *device, u_int32_t command) { + dvd_drive *dvd; +#ifdef WIN32 + HANDLE fd; + char dev[40]; +#else + int fd; +#endif + + /* Force the dropping of privileges: in our model, privileges are only used to execute memory dump commands, the user + must gain access to the device somehow else (i. e. get added to the "cdrom" group or similar things) */ + drop_euid (); + + debug ("Trying to open DVD device %s", device); +#ifdef WIN32 + sprintf (dev, "\\\\.\\%c:", device[0]); + if ((fd = CreateFile (dev, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) { + error ("Cannot open drive: %d", GetLastError ()); +#else + if ((fd = open (device, O_RDONLY | O_NONBLOCK)) < 0) { + perror ("Cannot open drive"); +#endif + dvd = NULL; + } else { + debug ("Opened successfully"); + drop_euid (); + dvd = (dvd_drive *) malloc (sizeof (dvd_drive)); + if (!dvd) { + fprintf (stderr, "malloc() failed\n"); + exit (100); + } + memset (dvd, 0, sizeof (dvd_drive)); + my_strdup (dvd -> device, device); + dvd -> fd = fd; + dvd_get_drive_info (dvd); + dvd_assign_functions (dvd, command); + } + + return (dvd); +} + + +/** + * Frees resources used by a DVD drive structure and destroys it. + * @param dvd The DVD drive structure to be destroyed. + * @return NULL. + */ +void *dvd_drive_destroy (dvd_drive *dvd) { + if (dvd) { +#ifdef WIN32 + CloseHandle (dvd -> fd); +#else + close (dvd -> fd); +#endif + my_free (dvd -> device); + my_free (dvd -> vendor); + my_free (dvd -> prod_id); + my_free (dvd -> prod_rev); + my_free (dvd); + } + + return (NULL); +} + + +/** + * Executes the drive-dependent function to dump the drive sector cache, and returns the dumped data. + * @param dvd The DVD drive the command should be exectued on. + * @param block_off The offset to start dumping, WRT the beginning of the sector cache. + * @param block_len The number of blocks to dump. + * @param block_size The block size to be used for dumping. + * @param buf A buffer where to store the dumped data. Note that this must be able to hold at least block_len * block_size bytes. + * @return 0 if the command was executed successfully, < 0 otherwise. + */ +int dvd_memdump (dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf) { + int out; + + /* Upgrade privileges and call actual dump functions */ + upgrade_euid (); + out = dvd -> memdump (dvd, block_off, block_len, block_size, buf); + drop_euid (); + + return (out); +} + + +/** + * Issues a READ(12) command without bothering to return the results. Uses the FUA (Force Unit Access bit) so that the requested sectors are actually read + * at the beginning of the cache and can be dumped later. + * @param dvd The DVD drive the command should be exectued on. + * @param sector The sector to be read. What will be cached is the 16-sectors block to which the sector belongs. + * @param sense A pointer to a structure which will hold the SENSE DATA got from the drive after the command has been executed. + * @return 0 if the command was executed successfully, < 0 otherwise. + */ +int dvd_read_sector_dummy (dvd_drive *dvd, u_int32_t sector, u_int32_t sectors, req_sense *sense, u_int8_t *extbuf, size_t extbufsize) { + mmc_command mmc; + int out; + u_int8_t intbuf[64 * 1024], *buf; + size_t bufsize; + + /* We need some buffer, be it provided externally or not */ + if (extbuf) { + buf = extbuf; + bufsize = extbufsize; + } else { + buf = intbuf; + bufsize = sizeof (intbuf); + } + + dvd_init_command (&mmc, buf, bufsize, sense); + mmc.cmd[0] = MMC_READ_12; + mmc.cmd[1] = 0x08; /* FUA bit set */ + mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24); /* LBA from MSB to LSB */ + mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16); + mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8); + mmc.cmd[5] = (u_int8_t) (sector & 0x000000FF); + mmc.cmd[6] = (u_int8_t) ((sectors & 0xFF000000) >> 24); /* Size from MSB to LSB */ + mmc.cmd[7] = (u_int8_t) ((sectors & 0x00FF0000) >> 16); + mmc.cmd[8] = (u_int8_t) ((sectors & 0x0000FF00) >> 8); + mmc.cmd[9] = (u_int8_t) (sectors & 0x000000FF); + out = dvd_execute_cmd (dvd, &mmc, true); /* Ignore errors! */ + + return (out); +} + + +/** + * Issues a READ(12) command using the STREAMING bit, which causes the requested 16-sector block to be read into memory, + * together with the following four. This way we will be able to dump 5 sector with a single READ request. + * + * Note the strange need for a big buffer even though we must only pass 0x10 as the transfer length, otherwise the drive will hang (!?). + * @param dvd The DVD drive the command should be exectued on. + * @param sector The sector to be read. What will be cached is the 16-sectors block to which the sector belongs, and the following 4 blocks. + * @param sense A pointer to a structure which will hold the SENSE DATA got from the drive after the command has been executed. + * @param extbuf A buffer where to store the read data, or NULL. + * @param extbufsize The size of the buffer. + * @return + */ +int dvd_read_sector_streaming (dvd_drive *dvd, u_int32_t sector, req_sense *sense, u_int8_t *extbuf, size_t extbufsize) { + mmc_command mmc; + int out; + u_int8_t intbuf[2048 * 16], *buf; + size_t bufsize; + + /* We need some buffer, be it provided externally or not */ + if (extbuf) { + buf = extbuf; + bufsize = extbufsize; + } else { + buf = intbuf; + bufsize = sizeof (intbuf); + } + + dvd_init_command (&mmc, buf, bufsize, sense); + mmc.cmd[0] = MMC_READ_12; + mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24); /* LBA from MSB to LSB */ + mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16); + mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8); + mmc.cmd[5] = (u_int8_t) (sector & 0x000000FF); + mmc.cmd[6] = 0; + mmc.cmd[7] = 0; + mmc.cmd[8] = 0; + mmc.cmd[9] = 0x10; + mmc.cmd[10] = 0x80; /* STREAMING bit set */ + out = dvd_execute_cmd (dvd, &mmc, true); /* Ignore errors! */ + + return (out); +} + + +int dvd_read_streaming (dvd_drive *dvd, u_int32_t sector, u_int32_t sectors, req_sense *sense, u_int8_t *extbuf, size_t extbufsize) { + mmc_command mmc; + int out; + u_int8_t intbuf[64 * 1024], *buf; + size_t bufsize; + + /* We need some buffer, be it provided externally or not */ + if (extbuf) { + buf = extbuf; + bufsize = extbufsize; + } else { + buf = intbuf; + bufsize = sizeof (intbuf); + } + + dvd_init_command (&mmc, buf, bufsize, sense); + mmc.cmd[0] = MMC_READ_12; + mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24); /* LBA from MSB to LSB */ + mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16); + mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8); + mmc.cmd[5] = (u_int8_t) (sector & 0x000000FF); + mmc.cmd[6] = (u_int8_t) ((sectors & 0xFF000000) >> 24); /* Size from MSB to LSB */ + mmc.cmd[7] = (u_int8_t) ((sectors & 0x00FF0000) >> 16); + mmc.cmd[8] = (u_int8_t) ((sectors & 0x0000FF00) >> 8); + mmc.cmd[9] = (u_int8_t) (sectors & 0x000000FF); + mmc.cmd[10] = 0x80; /* STREAMING bit set */ + out = dvd_execute_cmd (dvd, &mmc, true); /* Ignore errors! */ + + return (out); +} + + +int dvd_flush_cache_READ12 (dvd_drive *dvd, u_int32_t sector, req_sense *sense) { + mmc_command mmc; + int out; + u_int8_t intbuf[64], *buf; + size_t bufsize; + + buf = intbuf; + bufsize = 0; + + dvd_init_command (&mmc, buf, bufsize, sense); + mmc.cmd[0] = MMC_READ_12; + mmc.cmd[1] = 0x08; + mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24); /* LBA from MSB to LSB */ + mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16); + mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8); + mmc.cmd[5] = (u_int8_t) (sector & 0x000000FF); + out = dvd_execute_cmd (dvd, &mmc, true); + + return (out); +} + +static void dvd_sleep_ms (unsigned int ms) { +#ifdef WIN32 + Sleep (ms); +#else + usleep ((useconds_t) ms * 1000); +#endif +} + +int dvd_start_stop_unit (dvd_drive *dvd, bool start, bool load_eject, req_sense *sense) { + mmc_command mmc; + int out; + u_int8_t intbuf[64], *buf; + size_t bufsize; + + buf = intbuf; + bufsize = 0; + + dvd_init_command (&mmc, buf, bufsize, sense); + mmc.cmd[0] = 0x1B; + /* START STOP UNIT byte 4: bit 1 = LoEj, bit 0 = Start. */ + mmc.cmd[4] = (load_eject ? 0x02 : 0x00) | (start ? 0x01 : 0x00); + out = dvd_execute_cmd (dvd, &mmc, true); + + return (out); +} + +int dvd_stop_unit (dvd_drive *dvd, bool start, req_sense *sense) { + return dvd_start_stop_unit (dvd, start, false, sense); +} + +int dvd_wait_ready (dvd_drive *dvd, unsigned int timeout_ms) { + unsigned int waited = 0; + if (!dvd) + return -1; + while (waited <= timeout_ms) { + if (dvd_test_unit_ready (dvd, NULL) == 0) + return 0; + if (waited == 0) + dvd_stop_unit (dvd, true, NULL); /* START UNIT, like EnsureDriveReady() */ + dvd_sleep_ms (500); + waited += 500; + } + return -1; +} + +int dvd_media_cycle (dvd_drive *dvd, req_sense *sense) { + int i; + int out; + + /* Match the original GDR-8050L dumper's AutomateTrayCycle(): eject, wait + * long enough for the tray to extend, close, poll readiness, then settle. */ + out = dvd_start_stop_unit (dvd, false, true, sense); /* LoEj=1, Start=0: eject */ + if (out < 0) + return out; + dvd_sleep_ms (3000); + + out = dvd_start_stop_unit (dvd, true, true, sense); /* LoEj=1, Start=1: load */ + if (out < 0) + return out; + + for (i = 0; i < 90; i++) { + dvd_sleep_ms (500); + if (dvd_test_unit_ready (dvd, NULL) == 0) { + dvd_sleep_ms (1500); + return 0; + } + } + + /* Original dumper falls back to a fixed 10s settle delay if TUR never + * reports ready after tray close. */ + dvd_sleep_ms (10000); + return -1; +} + +int dvd_set_speed (dvd_drive *dvd, u_int32_t speed, req_sense *sense) { + mmc_command mmc; + int out; + u_int8_t intbuf[64], *buf; + size_t bufsize; + + buf = intbuf; + bufsize = 0; + + dvd_init_command (&mmc, buf, bufsize, sense); + mmc.cmd[0] = 0xBB; + mmc.cmd[2] = (u_int8_t) ((speed & 0x0000FF00) >> 8); + mmc.cmd[3] = (u_int8_t) (speed & 0x000000FF); + out = dvd_execute_cmd (dvd, &mmc, true); + + return (out); +} + +int dvd_get_size (dvd_drive *dvd, u_int32_t *size, req_sense *sense) { + mmc_command mmc; + int out; + u_int8_t intbuf[64], *buf; + size_t bufsize; + + buf = intbuf; + bufsize = 0x22; + + dvd_init_command (&mmc, buf, bufsize, sense); + mmc.cmd[0] = 0x52; + mmc.cmd[1] = 0x01; + mmc.cmd[5] = 0x01; + mmc.cmd[8] = 0x22; + out = dvd_execute_cmd (dvd, &mmc, true); + + *(size)=*(size) << 8 | intbuf[0x18]; + *(size)=*(size) << 8 | intbuf[0x19]; + *(size)=*(size) << 8 | intbuf[0x1a]; + *(size)=*(size) << 8 | intbuf[0x1b]; + + return (out); +} + +int dvd_get_layerbreak (dvd_drive *dvd, u_int32_t *layerbreak, req_sense *sense) { + mmc_command mmc; + int out; + u_int8_t intbuf[2052], *buf; + size_t bufsize; + + buf = intbuf; + bufsize = 2052; + + dvd_init_command (&mmc, buf, bufsize, sense); + mmc.cmd[0] = 0xad; + mmc.cmd[8] = 0x08; + mmc.cmd[9] = 0x04; + out = dvd_execute_cmd (dvd, &mmc, true); + + *(layerbreak)=*(layerbreak) << 8; + *(layerbreak)=*(layerbreak) << 8 | intbuf[0x11]; + *(layerbreak)=*(layerbreak) << 8 | intbuf[0x12]; + *(layerbreak)=*(layerbreak) << 8 | intbuf[0x13]; + if (*(layerbreak) > 0) *(layerbreak)=*(layerbreak) - 0x30000 + 1; + + return (out); +} + +int dvd_set_streaming (dvd_drive *dvd, u_int32_t speed, req_sense *sense) { +/* +DVD Decrypter-> +DeviceIoControl : \Device\CdRom5 +Command : IOCTL_SCSI_PASS_THROUGH_DIRECT +Length : 44 (0x002C) +ScsiStatus : 0 +PathId : 0 +TargedId : 0 +Lun : 0 +CdbLength : 12 (0x0C) +SenseInfoLength : 24 (0x18) +DataTransferLength : 28 (0x0000001C) +DataIn : 0 +TimeOutValue : 5000 + +CDB: +00000000 B6 00 00 00 00 00 00 00 00 00 1C 00 ........... + +Data Sent: +00000000 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF ............____ +00000010 00 00 03 E8 FF FF FF FF 00 00 03 E8 ...____... +*/ + mmc_command mmc; + int out; + u_int8_t inbuf[28], *buf; + size_t bufsize; + + buf = inbuf; + bufsize = 28; + + dvd_init_command (&mmc, buf, bufsize, sense); + mmc.cmd[00] = 0xB6; + mmc.cmd[10] = 28; + + *(buf+ 0)=0;//2 + *(buf+ 1)=0; + *(buf+ 2)=0; + *(buf+ 3)=0; + *(buf+ 4)=0; //MSB + *(buf+ 5)=0; // + *(buf+ 6)=0; // + *(buf+ 7)=0; //LSB + + *(buf+ 8)=0xff; //MSB + *(buf+ 9)=0xff; // + *(buf+10)=0xff; // + *(buf+11)=0xff; //LSB + + *(buf+12)=(u_int8_t) ((speed & 0xFF000000) >> 24); + *(buf+13)=(u_int8_t) ((speed & 0x00FF0000) >> 16); + *(buf+14)=(u_int8_t) ((speed & 0x0000FF00) >> 8); + *(buf+15)=(u_int8_t) (speed & 0x000000FF); + + *(buf+16)=(u_int8_t) ((1000 & 0xFF000000) >> 24); + *(buf+17)=(u_int8_t) ((1000 & 0x00FF0000) >> 16); + *(buf+18)=(u_int8_t) ((1000 & 0x0000FF00) >> 8); + *(buf+19)=(u_int8_t) (1000 & 0x000000FF); + + *(buf+20)=(u_int8_t) ((speed & 0xFF000000) >> 24); + *(buf+21)=(u_int8_t) ((speed & 0x00FF0000) >> 16); + *(buf+22)=(u_int8_t) ((speed & 0x0000FF00) >> 8); + *(buf+23)=(u_int8_t) (speed & 0x000000FF); + + *(buf+24)=(u_int8_t) ((1000 & 0xFF000000) >> 24); + *(buf+25)=(u_int8_t) ((1000 & 0x00FF0000) >> 16); + *(buf+26)=(u_int8_t) ((1000 & 0x0000FF00) >> 8); + *(buf+27)=(u_int8_t) (1000 & 0x000000FF); + + out = dvd_execute_cmd (dvd, &mmc, true); + + return (out); +} + + +int dvd_test_unit_ready (dvd_drive *dvd, req_sense *sense) { + mmc_command mmc; + u_int8_t intbuf[1]; + + dvd_init_command (&mmc, intbuf, 0, sense); + mmc.cmd[0] = SPC_TEST_UNIT_READY; + mmc.cmdlen = 6; + mmc.direction = DVD_DATA_NONE; + return dvd_execute_cmd (dvd, &mmc, true); +} + +int dvd_read_capacity_10 (dvd_drive *dvd, u_int32_t *sectors, u_int32_t *sector_size, req_sense *sense) { + mmc_command mmc; + u_int8_t buf[8]; + int out; + u_int32_t max_lba, block_len; + + dvd_init_command (&mmc, buf, sizeof (buf), sense); + mmc.cmd[0] = MMC_READ_CAPACITY_10; + mmc.cmdlen = 10; + out = dvd_execute_cmd (dvd, &mmc, false); + if (out >= 0) { + max_lba = ((u_int32_t) buf[0] << 24) | ((u_int32_t) buf[1] << 16) | ((u_int32_t) buf[2] << 8) | buf[3]; + block_len = ((u_int32_t) buf[4] << 24) | ((u_int32_t) buf[5] << 16) | ((u_int32_t) buf[6] << 8) | buf[7]; + if (sectors) + *sectors = max_lba + 1; + if (sector_size) + *sector_size = block_len; + } + + return out; +} + +int dvd_read_10 (dvd_drive *dvd, u_int32_t sector, u_int32_t sectors, req_sense *sense, u_int8_t *extbuf, size_t extbufsize) { + mmc_command mmc; + u_int8_t intbuf[64 * 1024], *buf; + size_t need, bufsize; + + need = (size_t) sectors * 2048; + if (extbuf) { + buf = extbuf; + bufsize = extbufsize; + } else { + buf = intbuf; + bufsize = sizeof (intbuf); + } + + if (need > bufsize) { + error ("dvd_read_10 buffer too small (%u sectors need %lu bytes)", sectors, (unsigned long) need); + return -1; + } + + dvd_init_command (&mmc, buf, (int) need, sense); + mmc.cmd[0] = MMC_READ_10; + mmc.cmdlen = 10; + mmc.cmd[2] = (u_int8_t) ((sector & 0xFF000000) >> 24); + mmc.cmd[3] = (u_int8_t) ((sector & 0x00FF0000) >> 16); + mmc.cmd[4] = (u_int8_t) ((sector & 0x0000FF00) >> 8); + mmc.cmd[5] = (u_int8_t) (sector & 0x000000FF); + mmc.cmd[7] = (u_int8_t) ((sectors & 0x0000FF00) >> 8); + mmc.cmd[8] = (u_int8_t) (sectors & 0x000000FF); + + return dvd_execute_cmd (dvd, &mmc, true); +} + +int dvd_mode_sense_10 (dvd_drive *dvd, u_int8_t page, u_int8_t *extbuf, size_t extbufsize, req_sense *sense) { + mmc_command mmc; + + if (!extbuf || extbufsize > 0xFFFF) + return -1; + + dvd_init_command (&mmc, extbuf, (int) extbufsize, sense); + mmc.cmd[0] = SPC_MODE_SENSE_10; + mmc.cmd[2] = page; + mmc.cmd[7] = (u_int8_t) ((extbufsize & 0xFF00) >> 8); + mmc.cmd[8] = (u_int8_t) (extbufsize & 0x00FF); + mmc.cmdlen = 10; + + return dvd_execute_cmd (dvd, &mmc, false); +} + +int dvd_mode_select_10 (dvd_drive *dvd, const u_int8_t *buf, size_t bufsize, req_sense *sense) { + mmc_command mmc; + u_int8_t tmp[256]; + + if (!buf || bufsize > sizeof (tmp) || bufsize > 0xFFFF) + return -1; + memset (tmp, 0, sizeof (tmp)); + memcpy (tmp, buf, bufsize); + + dvd_init_command (&mmc, tmp, (int) bufsize, sense); + mmc.direction = DVD_DATA_OUT; + mmc.cmd[0] = SPC_MODE_SELECT_10; + mmc.cmd[7] = (u_int8_t) ((bufsize & 0xFF00) >> 8); + mmc.cmd[8] = (u_int8_t) (bufsize & 0x00FF); + mmc.cmdlen = 10; + + return dvd_execute_cmd (dvd, &mmc, false); +} + +int dvd_mode_select_6 (dvd_drive *dvd, const u_int8_t *buf, size_t bufsize, req_sense *sense) { + mmc_command mmc; + u_int8_t tmp[64]; + + if (!buf || bufsize > sizeof (tmp) || bufsize > 0xFF) + return -1; + memset (tmp, 0, sizeof (tmp)); + memcpy (tmp, buf, bufsize); + + dvd_init_command (&mmc, tmp, (int) bufsize, sense); + mmc.direction = DVD_DATA_OUT; + mmc.cmd[0] = SPC_MODE_SELECT_6; + mmc.cmd[1] = 0x11; + mmc.cmd[4] = (u_int8_t) (bufsize & 0xFF); + mmc.cmdlen = 6; + + return dvd_execute_cmd (dvd, &mmc, false); +} + +int dvd_read_dvd_structure (dvd_drive *dvd, u_int8_t format, u_int8_t layer, u_int8_t *extbuf, size_t extbufsize, req_sense *sense) { + mmc_command mmc; + + if (!extbuf || extbufsize > 0xFFFF) + return -1; + + dvd_init_command (&mmc, extbuf, (int) extbufsize, sense); + mmc.cmd[0] = MMC_READ_DVD_STRUCTURE; + mmc.cmd[6] = layer; + mmc.cmd[8] = (u_int8_t) ((extbufsize & 0xFF00) >> 8); + mmc.cmd[9] = (u_int8_t) (extbufsize & 0x00FF); + mmc.cmd[11] = format; + mmc.cmdlen = 12; + + return dvd_execute_cmd (dvd, &mmc, false); +} + + +static int dvd_xbox_vendor_command (dvd_drive *dvd, u_int8_t subcommand, u_int8_t value, u_int8_t *buf, size_t bufsize, dvd_data_direction direction) { + mmc_command mmc; + + dvd_init_command (&mmc, buf, (int) bufsize, NULL); + mmc.cmd[0] = 0xFF; + mmc.cmd[1] = 0x08; + mmc.cmd[2] = 0x01; + mmc.cmd[3] = subcommand; + mmc.cmd[4] = value; + mmc.cmdlen = 10; + mmc.direction = direction; + if (direction == DVD_DATA_NONE) { + mmc.buffer = NULL; + mmc.buflen = 0; + } + return dvd_execute_cmd (dvd, &mmc, false); +} + +static bool dvd_xbox_feature_list_has (const u_int16_t *features, size_t count, u_int16_t needle) { + size_t i; + if (!features) + return false; + for (i = 0; i < count && features[i] != 0; i++) { + if (features[i] == needle) + return true; + } + return false; +} + +int dvd_xbox_vendor_get_feature_list (dvd_drive *dvd, u_int16_t *features, size_t max_features) { + u_int8_t buf[26]; + size_t i, count; + + if (!dvd || !features || max_features == 0) + return -1; + memset (features, 0, max_features * sizeof (features[0])); + memset (buf, 0, sizeof (buf)); + if (dvd_xbox_vendor_command (dvd, 0x10, 0, buf, sizeof (buf), DVD_DATA_IN) < 0) + return -1; + if ((((u_int16_t) buf[0] << 8) | buf[1]) != 0xA55A || + (((u_int16_t) buf[2] << 8) | buf[3]) != 0x5AA5) { + error ("Xbox vendor feature-list signature is invalid"); + return -1; + } + count = sizeof (buf) / 2; + if (count > max_features) + count = max_features; + for (i = 0; i < count; i++) + features[i] = ((u_int16_t) buf[i * 2] << 8) | buf[i * 2 + 1]; + return 0; +} + +int dvd_xbox_vendor_lock (dvd_drive *dvd) { + if (!dvd || !dvd_is_xbox_vendor_unlock_drive (dvd)) + return -1; + return dvd_xbox_vendor_command (dvd, 0x11, 0x00, NULL, 0, DVD_DATA_NONE); +} + +int dvd_xbox_vendor_set_error_skip (dvd_drive *dvd, bool enabled) { + if (!dvd || !dvd_is_xbox_vendor_unlock_drive (dvd)) + return -1; + return dvd_xbox_vendor_command (dvd, 0x15, enabled ? 0x01 : 0x00, NULL, 0, DVD_DATA_NONE); +} + +int dvd_xbox_vendor_unlock_wxripper (dvd_drive *dvd, u_int32_t *unlocked_sectors) { + u_int16_t features[13]; + u_int32_t sectors = 0, sector_size = 0; + + if (!dvd || !dvd_is_xbox_vendor_unlock_drive (dvd)) + return -1; + + if (dvd_xbox_vendor_get_feature_list (dvd, features, sizeof (features) / sizeof (features[0])) < 0) + return -1; + if (!dvd_xbox_feature_list_has (features, sizeof (features) / sizeof (features[0]), 0x0201) && + !dvd_xbox_feature_list_has (features, sizeof (features) / sizeof (features[0]), 0x0221)) + warning ("Xbox vendor feature list did not advertise Xbox unlock state 2/full challenge support; trying wxripper state anyway"); + + if (dvd_xbox_vendor_command (dvd, 0x11, 0x02, NULL, 0, DVD_DATA_NONE) < 0) + return -1; + + /* DiscImageCreator disables error-skip before dumping; keep that behavior so + * real read errors are visible to FriiDump unless a future option says otherwise. */ + dvd_xbox_vendor_set_error_skip (dvd, false); + + if (dvd_read_capacity_10 (dvd, §ors, §or_size, NULL) < 0) + return -1; + if (sector_size != 2048 || sectors < 1000000) { + error ("Xbox vendor unlock did not expose the expected 2048-byte view"); + return -1; + } + if (unlocked_sectors) + *unlocked_sectors = sectors; + return 0; +} + + +int dvd_xbox_recovery_kick (dvd_drive *dvd, bool auth_recovery) { + mmc_command mmc; + u_int8_t dummy[2048]; + u_int8_t auth_probe_buf[8]; + int i; + + if (!dvd) + return -1; + + /* Mirrors the proven dumper recovery cadence: after the GDR-8050L + * authentication/view switch, re-trigger the Xbox media auth command, set + * maximum speed, then issue a few harmless READ(10) probes. All commands + * deliberately ignore errors because the point is to wake/re-prime firmware + * state, not to make any one probe authoritative. + */ + if (auth_recovery) { + /* GDR-8050L / HLDS auth kick: READ DVD STRUCTURE C0 magic trigger. */ + dvd_init_command (&mmc, auth_probe_buf, sizeof (auth_probe_buf), NULL); + mmc.cmd[0] = MMC_READ_DVD_STRUCTURE; + mmc.cmd[2] = 0xFF; + mmc.cmd[3] = 0x02; + mmc.cmd[4] = 0xFD; + mmc.cmd[11] = 0xC0; + mmc.cmdlen = 12; + dvd_execute_cmd (dvd, &mmc, true); + + /* Hitachi/Kreon-style fallback auth kick: FF 08 01. */ + dvd_init_command (&mmc, NULL, 0, NULL); + mmc.cmd[0] = 0xFF; + mmc.cmd[1] = 0x08; + mmc.cmd[2] = 0x01; + mmc.cmdlen = 10; + mmc.direction = DVD_DATA_NONE; + dvd_execute_cmd (dvd, &mmc, true); + } + + dvd_set_speed (dvd, 0xFFFF, NULL); + + for (i = 0; i < 10; i++) + dvd_read_10 (dvd, 0, 1, NULL, dummy, sizeof (dummy)); + + return 0; +} + +int dvd_refresh_volume (dvd_drive *dvd) { +#ifdef WIN32 + DWORD bytesReturned = 0; + if (!dvd) + return -1; + /* Match RefreshVolume() from the reference dumper: update properties only; + * do not dismount here because that can reset drive state. This is now + * shared by Xbox and HLDS 0xE7 GC/Wii paths so Windows is less likely to + * keep stale filesystem/probe state attached to odd discs. */ + DeviceIoControl (dvd -> fd, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, &bytesReturned, NULL); + dvd_sleep_ms (1000); + return 0; +#else + (void) dvd; + return 0; +#endif +} + +int dvd_lock_volume (dvd_drive *dvd) { +#ifdef WIN32 + DWORD bytesReturned = 0; + if (!dvd) + return -1; + return DeviceIoControl (dvd -> fd, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL) ? 0 : -1; +#else + (void) dvd; + return 0; +#endif +} + +int dvd_xbox_refresh_volume (dvd_drive *dvd) { + return dvd_refresh_volume (dvd); +} + +int dvd_xbox_lock_volume (dvd_drive *dvd) { + return dvd_lock_volume (dvd); +} + +static int dvd_xbox_read_host_challenge_table (dvd_drive *dvd, u_int8_t *table, size_t table_len) { + mmc_command mmc; + int out; + + if (!table || table_len < 0x664) + return -1; + + /* GDR-8050L / Xbox READ DVD STRUCTURE format C0. */ + dvd_init_command (&mmc, table, 0x664, NULL); + mmc.cmd[0] = MMC_READ_DVD_STRUCTURE; + mmc.cmd[2] = 0xFF; + mmc.cmd[3] = 0x02; + mmc.cmd[4] = 0xFD; + mmc.cmd[5] = 0xFF; + mmc.cmd[6] = 0xFE; + mmc.cmd[8] = 0x06; + mmc.cmd[9] = 0x64; + mmc.cmd[11] = 0xC0; + mmc.cmdlen = 12; + out = dvd_execute_cmd (dvd, &mmc, true); + + /* Some Hitachi-family drives expose the same table via vendor command 0xFD. */ + if (out < 0 || table[772] != 1 || table[773] == 0) { + dvd_init_command (&mmc, table, 0x664, NULL); + mmc.cmd[0] = 0xFD; + mmc.cmd[1] = 0x01; + mmc.cmd[8] = 0x06; + mmc.cmd[9] = 0x64; + mmc.cmdlen = 12; + out = dvd_execute_cmd (dvd, &mmc, true); + } + + if (out < 0 || table[772] != 1) + return -1; + return 0; +} + +int dvd_xbox_gdr8050l_unlock (dvd_drive *dvd, u_int32_t *unlocked_sectors) { + int i, k, l; + int chalpos[24]; + u_int8_t table[0x664]; + u_int8_t restable[261]; + u_int8_t hash[0x2C]; + SHA1_HASH digest; + u_int8_t page[28]; + u_int8_t sticky[12]; + u_int32_t sectors = 0, sector_size = 0; + xbox_rc4_ctx rc4; + + if (!dvd || !dvd_is_xbox_unlock_drive (dvd)) + return -1; + + /* Step 1/2: read current capacity and the Xbox mode page. If the drive is + * already unlocked, this is harmless; the final capacity check below becomes + * the authority. */ + dvd_read_capacity_10 (dvd, §ors, §or_size, NULL); + dvd_mode_sense_10 (dvd, 0x3E, page, sizeof (page), NULL); + + /* Step 3: retrieve and decode the host challenge table. */ + if (dvd_xbox_read_host_challenge_table (dvd, table, sizeof (table)) < 0) { + error ("Cannot retrieve Xbox host challenge table"); + return -1; + } + + for (i = 0; i < 0x2C; i++) + hash[i] = table[0x4A3 + i]; + Sha1Calculate (hash, 0x2C, &digest); + + for (i = 0; i <= 260; i++) + restable[i] = table[774 + i]; + xbox_rc4_init (&rc4, digest.bytes, 7); + xbox_rc4_crypt (&rc4, restable, restable, 0xFD); + + k = 0; + for (l = 0; l <= 23; l++) { + if (restable[l * 11] == 1) { + chalpos[k++] = l; + if (k == (int) (sizeof (chalpos) / sizeof (chalpos[0]))) + break; + } + } + if (k < 2) { + error ("Xbox challenge table does not contain enough usable entries"); + return -1; + } + + /* Step 4: first host challenge. */ + memset (page, 0, sizeof (page)); + page[1] = 0x1A; + page[8] = 0x3E; + page[9] = 0x12; + page[11] = 0x01; + page[13] = 0xD1; + page[14] = 0x01; + memcpy (&page[15], &restable[1 + chalpos[k - 2] * 11], 5); + /* Match the original dumper: send the challenge and continue even if + * Windows reports a transport failure. The later XDVDFS probe is the + * authority for whether the drive actually entered the game view. */ + dvd_mode_select_10 (dvd, page, sizeof (page), NULL); + dvd_mode_sense_10 (dvd, 0x3E, page, sizeof (page), NULL); + + /* Step 6: second host challenge. */ + memset (page, 0, sizeof (page)); + page[1] = 0x1A; + page[8] = 0x3E; + page[9] = 0x12; + page[12] = 0x01; + memcpy (&page[15], &restable[1 + chalpos[k - 1] * 11], 5); + dvd_mode_select_10 (dvd, page, sizeof (page), NULL); + dvd_mode_sense_10 (dvd, 0x3E, page, sizeof (page), NULL); + + /* Step 8: unlock partition 1. */ + memset (page, 0, sizeof (page)); + page[1] = 0x1A; + page[8] = 0x3E; + page[9] = 0x12; + page[10] = 0x01; + page[11] = 0x01; + page[12] = 0x01; + page[13] = 0xD1; + page[14] = 0x01; + memcpy (&page[15], &restable[1 + chalpos[k - 1] * 11], 5); + dvd_mode_select_10 (dvd, page, sizeof (page), NULL); + + /* Step 9: sticky descrambling, mode page 0x31. */ + memset (sticky, 0, sizeof (sticky)); + sticky[4] = 0x31; + sticky[5] = 0x06; + sticky[6] = 0x01; + dvd_mode_select_6 (dvd, sticky, sizeof (sticky), NULL); + + /* Step 10: final capacity observation. The original UnlockDrive() only + * prints this verification and does not fail if the capacity has not changed + * yet. This matters for the first GDR-8050L handshake, whose purpose is to + * prime the drive before the required media-change event. */ + sectors = 0; + sector_size = 0; + if (dvd_read_capacity_10 (dvd, §ors, §or_size, NULL) == 0) { + if (unlocked_sectors) + *unlocked_sectors = sectors; + fprintf (stderr, "[XBOX] GDR-8050L handshake complete; READ CAPACITY reports %u sectors of %u bytes.\n", sectors, sector_size); + } else { + fprintf (stderr, "[XBOX] GDR-8050L handshake sent; final READ CAPACITY verify failed, continuing like original dumper.\n"); + } + + return 0; +} + +bool dvd_is_xbox_challenge_drive (dvd_drive *dvd) { + return dvd_is_hlds_drive (dvd) && dvd_prod_has (dvd, "GDR8050L"); +} + +bool dvd_is_xbox_vendor_unlock_drive (dvd_drive *dvd) { + return (dvd_is_hlds_drive (dvd) && (dvd_prod_has (dvd, "GDR3120L") || dvd_prod_has (dvd, "GDR-3120L"))) || + dvd_is_tsst_kreon_candidate (dvd); +} + +bool dvd_is_xbox_unlock_drive (dvd_drive *dvd) { + return dvd_is_xbox_challenge_drive (dvd) || dvd_is_xbox_vendor_unlock_drive (dvd); +} + +bool dvd_is_xbox_drive (dvd_drive *dvd) { + /* Autodetect only the two native Xbox profiles currently wired into the + * Xbox dump planner. Other candidate drives keep FriiDump's normal GC/Wii/DVD + * behavior unless the user explicitly forces Xbox mode with -T 4. */ + return dvd_is_xbox_challenge_drive (dvd) || + (dvd_is_hlds_drive (dvd) && (dvd_prod_has (dvd, "GDR3120L") || dvd_prod_has (dvd, "GDR-3120L"))); +} + + + +const char *dvd_get_hlds_e7_profile_name (dvd_drive *dvd) { + if (!dvd) return "none"; + if (dvd -> hlds_e7_profile_label) return dvd -> hlds_e7_profile_label; + return dvd_hlds_e7_profile_name_from_type (dvd -> hlds_e7_type); +} + +const char *dvd_get_hlds_e7_support_tier (dvd_drive *dvd) { + if (!dvd || dvd -> hlds_e7_type == 0) return "none"; + return dvd -> hlds_e7_support_tier ? dvd -> hlds_e7_support_tier : "legacy_detected"; +} + +const char *dvd_get_hlds_e7_family (dvd_drive *dvd) { + if (!dvd || dvd -> hlds_e7_type == 0) return "none"; + return dvd -> hlds_e7_family ? dvd -> hlds_e7_family : dvd_hlds_e7_profile_name_from_type (dvd -> hlds_e7_type); +} + +const char *dvd_get_hlds_e7_tokens (dvd_drive *dvd) { + if (!dvd || dvd -> hlds_e7_type == 0) return ""; + return dvd -> hlds_e7_tokens ? dvd -> hlds_e7_tokens : ""; +} + +const char *dvd_get_hlds_e7_record_id (dvd_drive *dvd) { + if (!dvd || dvd -> hlds_e7_type == 0) return ""; + return dvd -> hlds_e7_record_id ? dvd -> hlds_e7_record_id : ""; +} + +const char *dvd_get_hlds_e7_notes (dvd_drive *dvd) { + if (!dvd || dvd -> hlds_e7_type == 0) return ""; + return dvd -> hlds_e7_notes ? dvd -> hlds_e7_notes : ""; +} + +u_int32_t dvd_get_hlds_e7_static_cdb_base (dvd_drive *dvd) { + return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_static_cdb_base : 0; +} + +u_int32_t dvd_get_hlds_e7_static_gate (dvd_drive *dvd) { + return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_static_gate : 0; +} + +int dvd_get_hlds_e7_preferred_method (dvd_drive *dvd) { + return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_preferred_method : -1; +} + +u_int32_t dvd_get_hlds_e7_type (dvd_drive *dvd) { + return dvd ? dvd -> hlds_e7_type : 0; +} + +u_int32_t dvd_get_hlds_e7_cache_base (dvd_drive *dvd) { + return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_cache_base : 0; +} + +u_int32_t dvd_get_hlds_e7_mem_blocks (dvd_drive *dvd) { + return (dvd && dvd -> hlds_e7_type != 0) ? dvd -> hlds_e7_mem_blocks : 0; +} + +char *dvd_get_vendor (dvd_drive *dvd) { + return (dvd -> vendor); +} + + +char *dvd_get_product_id (dvd_drive *dvd) { + return (dvd -> prod_id); +} + + +char *dvd_get_product_revision (dvd_drive *dvd) { + return (dvd -> prod_rev); +} + + +char *dvd_get_model_string (dvd_drive *dvd) { + return (dvd -> model_string); +} + + +char *dvd_get_device (dvd_drive *dvd) { + return (dvd -> device); +} + +void *dvd_get_native_handle (dvd_drive *dvd) { + if (!dvd) return NULL; +#ifdef WIN32 + return (void *) dvd -> fd; +#else + return NULL; +#endif +} + + +bool dvd_get_support_status (dvd_drive *dvd) { + return (dvd -> supported); +} + +u_int32_t dvd_get_def_method (dvd_drive *dvd){ + return (dvd -> def_method); +} + +u_int32_t dvd_get_command (dvd_drive *dvd){ + return (dvd -> command); +} \ No newline at end of file diff --git a/libfriidump/dvd_drive.h b/libfriidump/dvd_drive.h new file mode 100644 index 0000000..2676b0a --- /dev/null +++ b/libfriidump/dvd_drive.h @@ -0,0 +1,119 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef DVD_DRIVE_H_INCLUDED +#define DVD_DRIVE_H_INCLUDED + +#include "misc.h" +#include + + +typedef struct dvd_drive_s dvd_drive; + + +typedef struct { + int sense_key; + int asc; + int ascq; +} req_sense; + + +typedef enum { + DVD_DATA_NONE = 0, + DVD_DATA_IN = 1, + DVD_DATA_OUT = 2 +} dvd_data_direction; + +typedef struct { + u_int8_t cmd[12]; + int cmdlen; + dvd_data_direction direction; + req_sense *sense; + u_int8_t *buffer; + int buflen; +} mmc_command; + + +/* Functions */ +dvd_drive *dvd_drive_new (char *device, u_int32_t command); +void *dvd_drive_destroy (dvd_drive *d); +int dvd_read_sector_dummy (dvd_drive *dvd, u_int32_t sector, u_int32_t sectors, req_sense *sense, u_int8_t *extbuf, size_t extbufsize); +int dvd_read_sector_streaming (dvd_drive *dvd, u_int32_t sector, req_sense *sense, u_int8_t *extbuf, size_t extbufsize); +int dvd_read_streaming (dvd_drive *dvd, u_int32_t sector, u_int32_t sectors, req_sense *sense, u_int8_t *extbuf, size_t extbufsize); +int dvd_flush_cache_READ12 (dvd_drive *dvd, u_int32_t sector, req_sense *sense); +int dvd_start_stop_unit (dvd_drive *dvd, bool start, bool load_eject, req_sense *sense); +int dvd_stop_unit (dvd_drive *dvd, bool start, req_sense *sense); +int dvd_media_cycle (dvd_drive *dvd, req_sense *sense); +int dvd_wait_ready (dvd_drive *dvd, unsigned int timeout_ms); +int dvd_test_unit_ready (dvd_drive *dvd, req_sense *sense); +int dvd_read_capacity_10 (dvd_drive *dvd, u_int32_t *sectors, u_int32_t *sector_size, req_sense *sense); +int dvd_read_10 (dvd_drive *dvd, u_int32_t sector, u_int32_t sectors, req_sense *sense, u_int8_t *extbuf, size_t extbufsize); +int dvd_mode_sense_10 (dvd_drive *dvd, u_int8_t page, u_int8_t *extbuf, size_t extbufsize, req_sense *sense); +int dvd_mode_select_10 (dvd_drive *dvd, const u_int8_t *buf, size_t bufsize, req_sense *sense); +int dvd_mode_select_6 (dvd_drive *dvd, const u_int8_t *buf, size_t bufsize, req_sense *sense); +int dvd_read_dvd_structure (dvd_drive *dvd, u_int8_t format, u_int8_t layer, u_int8_t *extbuf, size_t extbufsize, req_sense *sense); +int dvd_xbox_gdr8050l_unlock (dvd_drive *dvd, u_int32_t *unlocked_sectors); +int dvd_xbox_vendor_lock (dvd_drive *dvd); +int dvd_xbox_vendor_unlock_wxripper (dvd_drive *dvd, u_int32_t *unlocked_sectors); +int dvd_xbox_vendor_set_error_skip (dvd_drive *dvd, bool enabled); +int dvd_xbox_recovery_kick (dvd_drive *dvd, bool auth_recovery); +int dvd_refresh_volume (dvd_drive *dvd); +int dvd_lock_volume (dvd_drive *dvd); +int dvd_xbox_refresh_volume (dvd_drive *dvd); +int dvd_xbox_lock_volume (dvd_drive *dvd); +bool dvd_is_xbox_drive (dvd_drive *dvd); +bool dvd_is_xbox_unlock_drive (dvd_drive *dvd); +bool dvd_is_xbox_challenge_drive (dvd_drive *dvd); +bool dvd_is_xbox_vendor_unlock_drive (dvd_drive *dvd); +int dvd_set_speed (dvd_drive *dvd, u_int32_t speed, req_sense *sense); +int dvd_get_size (dvd_drive *dvd, u_int32_t *size, req_sense *sense); +int dvd_get_layerbreak (dvd_drive *dvd, u_int32_t *layerbreak, req_sense *sense); +int dvd_set_streaming (dvd_drive *dvd, u_int32_t speed, req_sense *sense); +int dvd_memdump (dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf); +char *dvd_get_vendor (dvd_drive *dvd); +char *dvd_get_product_id (dvd_drive *dvd); +char *dvd_get_product_revision (dvd_drive *dvd); +char *dvd_get_model_string (dvd_drive *dvd); +char *dvd_get_device (dvd_drive *dvd); +void *dvd_get_native_handle (dvd_drive *dvd); +bool dvd_get_support_status (dvd_drive *dvd); +u_int32_t dvd_get_def_method (dvd_drive *dvd); +u_int32_t dvd_get_command (dvd_drive *dvd); +const char *dvd_get_hlds_e7_profile_name (dvd_drive *dvd); +const char *dvd_get_hlds_e7_support_tier (dvd_drive *dvd); +const char *dvd_get_hlds_e7_family (dvd_drive *dvd); +const char *dvd_get_hlds_e7_tokens (dvd_drive *dvd); +const char *dvd_get_hlds_e7_record_id (dvd_drive *dvd); +const char *dvd_get_hlds_e7_notes (dvd_drive *dvd); +u_int32_t dvd_get_hlds_e7_type (dvd_drive *dvd); +u_int32_t dvd_get_hlds_e7_cache_base (dvd_drive *dvd); +u_int32_t dvd_get_hlds_e7_mem_blocks (dvd_drive *dvd); +u_int32_t dvd_get_hlds_e7_static_cdb_base (dvd_drive *dvd); +u_int32_t dvd_get_hlds_e7_static_gate (dvd_drive *dvd); +int dvd_get_hlds_e7_preferred_method (dvd_drive *dvd); +void dvd_set_hlds_e7_runtime_profile (dvd_drive *dvd, u_int32_t type, u_int32_t cache_base, u_int32_t mem_blocks); + +/* The following are exported for use by drive-specific functions */ +typedef int (*dvd_drive_memdump_func) (dvd_drive *dvd, u_int32_t block_off, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf); +void dvd_init_command (mmc_command *mmc, u_int8_t *buf, int len, req_sense *sense); +int dvd_execute_cmd (dvd_drive *dvd, mmc_command *mmc, bool ignore_errors); + +#endif diff --git a/libfriidump/ecma-267.c b/libfriidump/ecma-267.c new file mode 100644 index 0000000..48a23e2 --- /dev/null +++ b/libfriidump/ecma-267.c @@ -0,0 +1,109 @@ +/* +unscrambler 0.4: unscramble not standard IVs scrambled DVDs thru +bruteforce, intended for Gamecube/WII Optical Disks. + +Copyright (C) 2006 Victor Muñoz (xt5@ingenieria-inversa.cl) + +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 2 +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, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "ecma-267.h" + +/* EDC stuff */ +unsigned int edc_table[256] = { + 0x00000000, 0x80000011, 0x80000033, 0x00000022, 0x80000077, 0x00000066, 0x00000044, 0x80000055, + 0x800000FF, 0x000000EE, 0x000000CC, 0x800000DD, 0x00000088, 0x80000099, 0x800000BB, 0x000000AA, + 0x800001EF, 0x000001FE, 0x000001DC, 0x800001CD, 0x00000198, 0x80000189, 0x800001AB, 0x000001BA, + 0x00000110, 0x80000101, 0x80000123, 0x00000132, 0x80000167, 0x00000176, 0x00000154, 0x80000145, + 0x800003CF, 0x000003DE, 0x000003FC, 0x800003ED, 0x000003B8, 0x800003A9, 0x8000038B, 0x0000039A, + 0x00000330, 0x80000321, 0x80000303, 0x00000312, 0x80000347, 0x00000356, 0x00000374, 0x80000365, + 0x00000220, 0x80000231, 0x80000213, 0x00000202, 0x80000257, 0x00000246, 0x00000264, 0x80000275, + 0x800002DF, 0x000002CE, 0x000002EC, 0x800002FD, 0x000002A8, 0x800002B9, 0x8000029B, 0x0000028A, + 0x8000078F, 0x0000079E, 0x000007BC, 0x800007AD, 0x000007F8, 0x800007E9, 0x800007CB, 0x000007DA, + 0x00000770, 0x80000761, 0x80000743, 0x00000752, 0x80000707, 0x00000716, 0x00000734, 0x80000725, + 0x00000660, 0x80000671, 0x80000653, 0x00000642, 0x80000617, 0x00000606, 0x00000624, 0x80000635, + 0x8000069F, 0x0000068E, 0x000006AC, 0x800006BD, 0x000006E8, 0x800006F9, 0x800006DB, 0x000006CA, + 0x00000440, 0x80000451, 0x80000473, 0x00000462, 0x80000437, 0x00000426, 0x00000404, 0x80000415, + 0x800004BF, 0x000004AE, 0x0000048C, 0x8000049D, 0x000004C8, 0x800004D9, 0x800004FB, 0x000004EA, + 0x800005AF, 0x000005BE, 0x0000059C, 0x8000058D, 0x000005D8, 0x800005C9, 0x800005EB, 0x000005FA, + 0x00000550, 0x80000541, 0x80000563, 0x00000572, 0x80000527, 0x00000536, 0x00000514, 0x80000505, + 0x80000F0F, 0x00000F1E, 0x00000F3C, 0x80000F2D, 0x00000F78, 0x80000F69, 0x80000F4B, 0x00000F5A, + 0x00000FF0, 0x80000FE1, 0x80000FC3, 0x00000FD2, 0x80000F87, 0x00000F96, 0x00000FB4, 0x80000FA5, + 0x00000EE0, 0x80000EF1, 0x80000ED3, 0x00000EC2, 0x80000E97, 0x00000E86, 0x00000EA4, 0x80000EB5, + 0x80000E1F, 0x00000E0E, 0x00000E2C, 0x80000E3D, 0x00000E68, 0x80000E79, 0x80000E5B, 0x00000E4A, + 0x00000CC0, 0x80000CD1, 0x80000CF3, 0x00000CE2, 0x80000CB7, 0x00000CA6, 0x00000C84, 0x80000C95, + 0x80000C3F, 0x00000C2E, 0x00000C0C, 0x80000C1D, 0x00000C48, 0x80000C59, 0x80000C7B, 0x00000C6A, + 0x80000D2F, 0x00000D3E, 0x00000D1C, 0x80000D0D, 0x00000D58, 0x80000D49, 0x80000D6B, 0x00000D7A, + 0x00000DD0, 0x80000DC1, 0x80000DE3, 0x00000DF2, 0x80000DA7, 0x00000DB6, 0x00000D94, 0x80000D85, + 0x00000880, 0x80000891, 0x800008B3, 0x000008A2, 0x800008F7, 0x000008E6, 0x000008C4, 0x800008D5, + 0x8000087F, 0x0000086E, 0x0000084C, 0x8000085D, 0x00000808, 0x80000819, 0x8000083B, 0x0000082A, + 0x8000096F, 0x0000097E, 0x0000095C, 0x8000094D, 0x00000918, 0x80000909, 0x8000092B, 0x0000093A, + 0x00000990, 0x80000981, 0x800009A3, 0x000009B2, 0x800009E7, 0x000009F6, 0x000009D4, 0x800009C5, + 0x80000B4F, 0x00000B5E, 0x00000B7C, 0x80000B6D, 0x00000B38, 0x80000B29, 0x80000B0B, 0x00000B1A, + 0x00000BB0, 0x80000BA1, 0x80000B83, 0x00000B92, 0x80000BC7, 0x00000BD6, 0x00000BF4, 0x80000BE5, + 0x00000AA0, 0x80000AB1, 0x80000A93, 0x00000A82, 0x80000AD7, 0x00000AC6, 0x00000AE4, 0x80000AF5, + 0x80000A5F, 0x00000A4E, 0x00000A6C, 0x80000A7D, 0x00000A28, 0x80000A39, 0x80000A1B, 0x00000A0AL +}; + +u32 edc_calc(u32 edc, u8 *ptr, u32 len) { + while (len--) edc=edc_table[((edc>>24)^*ptr++)&0xFF]^(edc<<8); + return edc; +} + +/* end of EDC stuff */ + +/* LFSR stuff */ + +u16 ecma267_ivs[]= { + 0x0001, 0x5500, 0x0002, 0x2A00, + 0x0004, 0x5400, 0x0008, 0x2800, + 0x0010, 0x5000, 0x0020, 0x2001, + 0x0040, 0x4002, 0x0080, 0x0005 +}; + + +unsigned short LFSR; + +void LFSR_ecma_init(int iv) { + LFSR=ecma267_ivs[iv]; +} + +void LFSR_init(u16 seed) { + LFSR=seed; +} + +int LFSR_tick() { + int ret; + int n; + + ret=LFSR>>14; + + n=ret^((LFSR>>10)&1); + LFSR=((LFSR<<1)|n)&0x7FFF; + + return ret; +} + +unsigned char LFSR_byte() { + u8 ret; + int i; + + ret=0; + for(i=0; i<8; i++) ret=(ret<<1)|LFSR_tick(); + + return ret; +} + +/* end of LFSR stuff */ diff --git a/libfriidump/ecma-267.h b/libfriidump/ecma-267.h new file mode 100644 index 0000000..e63c9c8 --- /dev/null +++ b/libfriidump/ecma-267.h @@ -0,0 +1,47 @@ +/* +unscrambler 0.4: unscramble not standard IVs scrambled DVDs thru +bruteforce, intended for Gamecube/WII Optical Disks. + +Copyright (C) 2006 Victor Muñoz (xt5@ingenieria-inversa.cl) + +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 2 +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, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +typedef unsigned int u32; +typedef int s32; + +typedef unsigned short u16; +typedef short s16; + +typedef unsigned char u8; +typedef char s8; + +/* EDC stuff */ + +u32 edc_calc(u32 edc, u8 *ptr, u32 len); + +/* end of EDC stuff */ + +/* LFSR stuff */ + +void LFSR_ecma_init(int iv); + +void LFSR_init(u16 seed); + +int LFSR_tick(); + +u8 LFSR_byte(); + +/* end of LFSR stuff */ diff --git a/libfriidump/hitachi.c b/libfriidump/hitachi.c new file mode 100644 index 0000000..c799b75 --- /dev/null +++ b/libfriidump/hitachi.c @@ -0,0 +1,151 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/*! \file + * \brief Memory dump functions specific to drives based on the Hitachi MN103 chip. + * + * Drives which are supported by this set of functions are, for instance, the LG GDR-8161B, GDR-8162B, GDR-8163B and GDR-8164B. All testing has been performed + * with the latter model, so I'm not really sure about the others, but they should work ;). Please report any issues and more compatible drives! + * + * This file contains code derived from the work of Kevin East (SeventhSon), kev@kev.nu, http://www.kev.nu/360/ , which, in turn, derives from work by + * a lot of other people. See his page for full details. + */ + +#include +#include +#include "misc.h" +#include "dvd_drive.h" + +/*! \brief Memory offset at which the drive cache memory is mapped. + * + * During READ, sector data is cached beginning at this address. + */ + +#define HITACHI_MEM_BASE 0x80000000 + + +/** + * Dumps a single block (with arbitrary size) of the address space of the MN103 microcontroller within the Hitachi-LG Xbox 360 DVD drive and similar drives. + * This function is derived from the work of Kevin East (SeventhSon), kev@kev.nu, http://www.kev.nu/360/. + * @param dvd The DVD drive the command should be exectued on. + * @param offset The absolute memory offset to start dumping. + * @param block_size The block size for the dump. + * @param buf Where to place the dumped data. This must have been setup by the caller to store up to block_size bytes. + * @return < 0 if an error occurred, 0 otherwise. + */ +int hitachi_dvd_dump_memblock (dvd_drive *dvd, u_int32_t offset, u_int32_t block_size, u_int8_t *buf) { + mmc_command mmc; + int out; + + if (!buf) { + error ("NULL buffer"); + out = -1; + } else if (!block_size || block_size > 65535) { + error ("invalid block_size (valid: 1 - 65535)"); + out = -2; + } else { + dvd_init_command (&mmc, buf, block_size, NULL); + mmc.cmd[0] = 0xE7; // vendor specific command (discovered by DaveX) + mmc.cmd[1] = 0x48; // H + mmc.cmd[2] = 0x49; // I + mmc.cmd[3] = 0x54; // T + mmc.cmd[4] = 0x01; // read MCU memory sub-command + mmc.cmd[6] = (unsigned char) ((offset & 0xFF000000) >> 24); // address MSB + mmc.cmd[7] = (unsigned char) ((offset & 0x00FF0000) >> 16); // address + mmc.cmd[8] = (unsigned char) ((offset & 0x0000FF00) >> 8); // address + mmc.cmd[9] = (unsigned char) (offset & 0x000000FF); // address LSB + mmc.cmd[10] = (unsigned char) ((block_size & 0xFF00) >> 8); // length MSB + mmc.cmd[11] = (unsigned char) (block_size & 0x00FF); // length LSB + + out = dvd_execute_cmd (dvd, &mmc, false); + } + + return (out); +} + + +/** + * Dumps a given portion of the address space of the MN103 microcontroller within the Hitachi-LG Xbox 360 DVD drive. + * WARNING: it can take a while to dump a lot of data. + * @param dvd The DVD drive the command should be exectued on. + * @param offset The absolute memory offset to start dumping. + * @param block_len The number of blocks to dump. + * @param block_size The block size for the dump. + * @param buf Where to place the dumped data. This must have been setup by the caller to store up to block_size bytes. + * @return < 0 if an error occurred, 0 otherwise. + */ +int hitachi_dvd_dump_mem_generic (dvd_drive *dvd, u_int32_t offset, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf) { + u_int32_t i; + int r, out; + + if (!buf) { + error ("NULL buffer"); + out = -1; + } else { + r = -10; + for (i = 0; i < block_len; i++) { + if ((r = hitachi_dvd_dump_memblock (dvd, offset + i * block_size, block_size, buf + i * block_size)) < 0) { + error ("hitachi_dvd_read_block() failed with %d", r); + break; + } + } + out = r; + } + + return (out); +} + + +/** + * Dumps a given portion of the address space of the MN103 microcontroller within the Hitachi-LG Xbox 360 DVD drive, starting at the offset at which + * sector data are cached. + * WARNING: it can take a while to dump a lot of data. + * @param dvd The DVD drive the command should be exectued on. + * @param offset The memory offset to start dumping, relative to the cache start offset. + * @param block_len The number of blocks to dump. + * @param block_size The block size for the dump. + * @param buf Where to place the dumped data. This must have been setup by the caller to store up to block_size bytes. + * @return < 0 if an error occurred, 0 otherwise. + */ +int hitachi_dvd_dump_mem (dvd_drive *dvd, u_int32_t offset, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf) { + u_int32_t i; + u_int32_t base; + int r, out; + + if (!buf) { + error ("NULL buffer"); + out = -1; + } else { + base = dvd_get_hlds_e7_cache_base (dvd); + if (base == 0) + base = HITACHI_MEM_BASE; + r = -10; + for (i = 0; i < block_len; i++) { + if ((r = hitachi_dvd_dump_memblock (dvd, base + offset + i * block_size, block_size, buf + i * block_size)) < 0) { + error ("hitachi_dvd_read_block() failed with %d", r); + break; + } + } + out = r; + } + + return (out); +} diff --git a/libfriidump/lite-on.c b/libfriidump/lite-on.c new file mode 100644 index 0000000..6cf70fb --- /dev/null +++ b/libfriidump/lite-on.c @@ -0,0 +1,154 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "rs.h" +#include +#include +#include "misc.h" +#include "dvd_drive.h" + +/** + * Command found in Lite-On LH-18A1H; verified with LH-18A1P and LH-20A1H + * Expected to work with: DH*, DW*, LH*, SH* series + * Possibly would work with other Mediatek based drives: + * Samsung SE*, SH* series + * Some Dell drives + * Some Sony drives + * Asus DRW-1814* (DRW* series?) + */ + +//u_int8_t tmp[64*1024]; + +/** + * @param dvd The DVD drive the command should be exectued on. + * @param offset The absolute memory offset to start dumping. + * @param block_size The block size for the dump. + * @param buf Where to place the dumped data. This must have been setup by the caller to store up to block_size bytes. + * @return < 0 if an error occurred, 0 otherwise. + */ +int liteon_dvd_dump_memblock (dvd_drive *dvd, u_int32_t offset, u_int32_t block_size, u_int8_t *buf) { + mmc_command mmc; + int out; + u_int32_t raw_block_size; + u_int32_t raw_offset; + + u_int32_t src_offset; + u_int32_t dst_offset; + u_int32_t row_nr; + u_int32_t sec_nr; + u_int32_t sec_cnt; + u_int32_t first_sec_nr; + u_int8_t tmp[64*1024]; + //u_int8_t *tmp; + + raw_block_size = (block_size / 2064) * 0x950; + raw_offset = (offset / 2064) * 0x950; + + if (!buf) { + error ("NULL buffer"); + out = -1; + } else if (!block_size || raw_block_size >= 65535) { + error ("invalid raw_block_size (valid: 1 - 65534)"); + error ("raw_block_size = (block_size / 2064) * 2384"); + out = -2; + } else { + //tmp = malloc(64*1024); + dvd_init_command (&mmc, tmp, raw_block_size, NULL); //64*1024 + mmc.cmd[0] = 0x3C; // READ BUFFER + mmc.cmd[1] = 0x01; // Vendor specific - sole parameter supported by Lite-On + mmc.cmd[2] = 0x01; // == 0x02; 0xE2 = EEPROM; 0xF1 = KEYPARA; + mmc.cmd[3] = (unsigned char) ((raw_offset & 0x00FF0000) >> 16);// address MSB + mmc.cmd[4] = (unsigned char) ((raw_offset & 0x0000FF00) >> 8); // address + mmc.cmd[5] = (unsigned char) ( raw_offset & 0x000000FF); // address LSB + mmc.cmd[6] = (unsigned char) ((raw_block_size & 0x00FF0000) >> 16); // length MSB + mmc.cmd[7] = (unsigned char) ((raw_block_size & 0x0000FF00) >> 8); // length + mmc.cmd[8] = (unsigned char) ( raw_block_size & 0x000000FF); // length LSB + + out = dvd_execute_cmd (dvd, &mmc, false); + + src_offset = 0; + dst_offset = 0; + first_sec_nr = 0x55555555; + sec_cnt = 0; + while (src_offset < raw_block_size) { + sec_nr=(*(tmp+src_offset+1)<<16)+(*(tmp+src_offset+2)<<8)+(*(tmp+src_offset+3)); +//fprintf (stdout,"sec_nr: %x\n", sec_nr); + if (first_sec_nr==0x55555555) { + first_sec_nr=sec_nr; + rs_decode(tmp+src_offset, 0, 0); + } + else + if (sec_nr==first_sec_nr+sec_cnt) { + rs_decode(tmp+src_offset, 0, 0); //sector seq = ok + } + else { //sector seq broken -> corrupt + error ("sector sequence broken"); + out = -3; + *(tmp+src_offset+0)=0xff; + *(tmp+src_offset+1)=0xff; + *(tmp+src_offset+2)=0xff; + *(tmp+src_offset+3)=0xff; + break; + } + for (row_nr=0; row_nr<12; row_nr++) { + memcpy(buf+dst_offset, tmp+src_offset, 172); + dst_offset += 172; + src_offset += 182; + } + src_offset += 200; + sec_cnt += 1; + } + //free(tmp); + } + return (out); +} + + + +/** + * WARNING: it can take a while to dump a lot of data. + * @param dvd The DVD drive the command should be exectued on. + * @param offset The memory offset to start dumping, relative to the cache start offset. + * @param block_len The number of blocks to dump. + * @param block_size The block size for the dump. + * @param buf Where to place the dumped data. This must have been setup by the caller to store up to block_size bytes. + * @return < 0 if an error occurred, 0 otherwise. + */ +int liteon_dvd_dump_mem (dvd_drive *dvd, u_int32_t offset, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf) { + u_int32_t i; + int r, out; + + if (!buf) { + error ("NULL buffer"); + out = -1; + } else { + r = -10; + for (i = 0; i < block_len; i++) { + if ((r = liteon_dvd_dump_memblock (dvd, offset + i * block_size, block_size, buf + i * block_size)) < 0) { + error ("liteon_dvd_dump_memblock() failed with %d", r); + break; + } + } + out = r; + } + + return (out); +} diff --git a/libfriidump/misc.c b/libfriidump/misc.c new file mode 100644 index 0000000..b71dcf9 --- /dev/null +++ b/libfriidump/misc.c @@ -0,0 +1,316 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "misc.h" +#include "xbox_ref/xbox_ref_log.h" +#include +#include +#include +#include +#include +#include +#ifndef WIN32 +#include +#endif +#include + +/*** LOGGING STUFF ***/ +/* Uses code from the printf man page */ +static int my_vasprintf (char **dst, const char *fmt, va_list ap) { + char *p; + int n, size; + bool done; + va_list ap2; + + /* Guess we need no more than 100 bytes. */ + size = 100; + if ((p = (char *) malloc (size)) != NULL) { + do { + done = false; + va_copy (ap2, ap); + n = vsnprintf (p, size, fmt, ap2); + va_end (ap2); + + /* If that worked, return the string. */ + if (n > -1 && n < size) + done = true; + else { + /* Else try again with more space. */ + if (n > -1) /* glibc 2.1 */ + size = n + 1; /* precisely what is needed */ + else /* glibc 2.0 */ + size *= 2; /* twice the old size */ + + if ((p = (char *) realloc (p, size)) == NULL) + done = true; + } + } while (!done); + } + + /* Return -1 if memory allocation failed */ + if (!(*dst = p)) + n = -1; + + return (n); +} + + +/* Appends a print-like string to the log file, with respect to the verbosity setting */ +void _logprintf (int level, char tag[], char format[], ...) { + static bool last_nocr = false; + char *buffer, t[50]; + va_list ap; + struct timeval now; + time_t nowtt; + struct tm nowtm; + + va_start (ap, format); + my_vasprintf (&buffer, format, ap); + va_end (ap); + + /* Start of line, including time and tag */ + if (!last_nocr) { + gettimeofday (&now, NULL); + nowtt = (time_t) now.tv_sec; + if (localtime_r (&nowtt, &nowtm)) + strftime (t, 50, "%H:%M:%S", &nowtm); + if (tag) + xbox_ref_log_fprintf (stderr, "[%s/%s] ", t, tag); + else + xbox_ref_log_fprintf (stderr, "[%s] ", t); + + /* Debug level tag */ + if (level & LOG_WARNING) + xbox_ref_log_fprintf (stderr, "WARNING: "); + else if (level & LOG_ERROR) + xbox_ref_log_fprintf (stderr, "ERROR: "); + #ifdef DEBUG + else if (level & LOG_DEBUG) + xbox_ref_log_fprintf (stderr, "DEBUG: "); + #endif + } + + /* Actual message */ + xbox_ref_log_fprintf (stderr, "%s%s", buffer, level & LOG_NOCR ? "" : "\n"); + free (buffer); + + if (level & LOG_NOCR) + last_nocr = true; + else + last_nocr = false; + + return; +} +/******/ + + +/***************** TAKEN FROM TCPDUMP ****************/ + +#define ASCII_LINELENGTH 300 +#define HEXDUMP_BYTES_PER_LINE 16 +#define HEXDUMP_SHORTS_PER_LINE (HEXDUMP_BYTES_PER_LINE / 2) +#define HEXDUMP_HEXSTUFF_PER_SHORT 5 /* 4 hex digits and a space */ +#define HEXDUMP_HEXSTUFF_PER_LINE (HEXDUMP_HEXSTUFF_PER_SHORT * HEXDUMP_SHORTS_PER_LINE) + +void hex_and_ascii_print_with_offset (const char *ident, register const u_int8_t *cp, + register u_int16_t length, register u_int16_t oset) { + register u_int16_t i; + register int s1, s2; + register int nshorts; + char hexstuff[HEXDUMP_SHORTS_PER_LINE*HEXDUMP_HEXSTUFF_PER_SHORT+1], *hsp; + char asciistuff[ASCII_LINELENGTH+1], *asp; + + nshorts = length / sizeof(unsigned short); + i = 0; + hsp = hexstuff; + asp = asciistuff; + while (--nshorts >= 0) { + s1 = *cp++; + s2 = *cp++; + (void)snprintf(hsp, sizeof(hexstuff) - (hsp - hexstuff), + " %02x%02x", s1, s2); + hsp += HEXDUMP_HEXSTUFF_PER_SHORT; + *(asp++) = (isgraph(s1) ? s1 : '.'); + *(asp++) = (isgraph(s2) ? s2 : '.'); + i++; + if (i >= HEXDUMP_SHORTS_PER_LINE) { + *hsp = *asp = '\0'; + (void)printf("%s0x%04x: %-*s %s", + ident, oset, HEXDUMP_HEXSTUFF_PER_LINE, + hexstuff, asciistuff); + i = 0; + hsp = hexstuff; + asp = asciistuff; + oset += HEXDUMP_BYTES_PER_LINE; + } + } + if (length & 1) { + s1 = *cp++; + (void)snprintf(hsp, sizeof(hexstuff) - (hsp - hexstuff), + " %02x", s1); + hsp += 3; + *(asp++) = (isgraph(s1) ? s1 : '.'); + ++i; + } + if (i > 0) { + *hsp = *asp = '\0'; + debug ("%s0x%04x: %-*s %s", + ident, oset, HEXDUMP_HEXSTUFF_PER_LINE, + hexstuff, asciistuff); + } + + debug ("\n"); // Final spacing +} + +char *strtrimr (char *s) { + int i; + + for (i = strlen (s) - 1; i >= 0 && isspace (s[i]); i--) + s[i] = '\0'; + + return (s); +} + + +/*** The following was taken from the Python sources */ +#if !defined(HAVE_LARGEFILE_SUPPORT) +#error "Large file support must be enabled for this program" +#else + +/* A portable fseek() function + return 0 on success, non-zero on failure (with errno set) */ +int my_fseek (FILE *fp, my_off_t offset, int whence) { + #if defined(_MSC_VER) || defined(WIN32) + return _fseeki64(fp, (__int64) offset, whence); + #elif defined(HAVE_FSEEKO) && SIZEOF_OFF_T >= 8 + return fseeko(fp, offset, whence); + #elif defined(HAVE_FSEEK64) + return fseek64(fp, offset, whence); + #elif defined(__BEOS__) + return _fseek(fp, offset, whence); + #elif SIZEOF_FPOS_T >= 8 + /* lacking a 64-bit capable fseek(), use a 64-bit capable fsetpos() + and fgetpos() to implement fseek()*/ + fpos_t pos; + + switch (whence) { + case SEEK_END: + #ifdef MS_WINDOWS + fflush (fp); + if (_lseeki64 (fileno(fp), 0, 2) == -1) + return -1; + #else + if (fseek (fp, 0, SEEK_END) != 0) + return -1; + #endif + // fall through + case SEEK_CUR: + if (fgetpos (fp, &pos) != 0) + return -1; + offset += pos; + break; + // case SEEK_SET: break; + } + return fsetpos(fp, &offset); + #else + #error "Large file support, but no way to fseek." + #endif +} + + +/* A portable ftell() function + Return -1 on failure with errno set appropriately, current file + position on success */ +my_off_t my_ftell (FILE* fp) { + #if defined(_MSC_VER) || defined(WIN32) + return _ftelli64 (fp); + #elif defined(HAVE_FTELLO) && SIZEOF_OFF_T >= 8 + return ftello (fp); + #elif defined(HAVE_FTELL64) + return ftell64 (fp); + #elif SIZEOF_FPOS_T >= 8 + fpos_t pos; + + if (fgetpos (fp, &pos) != 0) + return -1; + + return pos; + #else + #error "Large file support, but no way to ftell." + #endif +} + +#endif + + + +/*** STUFF FOR DROPPING PRIVILEGES ***/ + +/* WARNING: I'm not sure at all that the privileges-dropping system I have implemented is secure, so don't rely too much on it. */ + +#ifndef WIN32 +#include +#endif + +/** + * Drops privileges to those of the real user (i. e. set euid to ruid). + */ +void drop_euid () { +#ifndef WIN32 + uid_t uid, euid; + + uid = getuid (); + euid = geteuid (); + if (uid != 0 && uid != euid) { +#if 1 + seteuid (uid); +#else + if (seteuid (uid) != 0) + debug ("seteuid() to uid %d failed", uid); + else + debug ("Changed euid from %d to %d", euid, uid); +#endif + } +#endif + + return; +} + + +/** + * Upgrades priviles to those of root (i. e. set euid to 0). + */ +void upgrade_euid () { +#ifndef WIN32 + if (getuid () != 0) { +#if 1 + seteuid (0); +#else + if (seteuid (0) != 0) + debug ("seteuid() to root failed"); + else + debug ("Changed euid to root"); +#endif + } +#endif + + return; +} diff --git a/libfriidump/misc.h b/libfriidump/misc.h new file mode 100644 index 0000000..56139bb --- /dev/null +++ b/libfriidump/misc.h @@ -0,0 +1,142 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef MISC_H_INCLUDED +#define MISC_H_INCLUDED + +/*** Include site configuration ***/ +#ifdef HAVE_CONFIG_H +#include +#endif +/******/ + +/*** Windows stuff ***/ +#include "win32compat.h" + +#define _GNU_SOURCE // For strndup() + +#include +#include +#include + + +/*** ASSERTIONS ***/ +#define MY_ASSERT(cond) \ + if (!(cond)) { \ + fprintf (stderr, "*** ASSERTION FAILED at " __FILE__ ":%d: " # cond "\n", __LINE__); \ + exit (9); \ + } + +/*** LOGGING STUFF ***/ +enum { + LOG_NORMAL = 1 << 0, + LOG_WARNING = 1 << 1, + LOG_ERROR = 1 << 2, +#ifdef DEBUG + LOG_DEBUG = 1 << 4, +#endif + LOG_NOCR = 1 << 15 /* Use in OR to avoid a trailing newline */ +}; + +/* Don't use this function explicitly. Use the macros below. */ +void _logprintf (int level, char tag[], char format[], ...); + +#ifdef DEBUG + #define logprintf(level, ...) _logprintf (level, (char *) __FUNCTION__, __VA_ARGS__) + + #define debug(...) logprintf(LOG_DEBUG, __VA_ARGS__) + #define debug_nocr(...) logprintf(LOG_DEBUG | LOG_NOCR, __VA_ARGS__) +#else + #define logprintf(level, ...) _logprintf (level, NULL, __VA_ARGS__) + + #define debug(...) do {;} while (0); + #define debug_nocr(...) do {;} while (0); +#endif + +#if defined (DEBUG) || defined (VERBOSE) +#define log(...) logprintf (LOG_NORMAL, __VA_ARGS__) +#define warning(...) logprintf (LOG_WARNING, __VA_ARGS__) +#define error(...) logprintf (LOG_ERROR, __VA_ARGS__) +#else +#define log(...) +#define warning(...) +#define error(...) +#endif +/******/ + +void hex_and_ascii_print_with_offset(const char *ident, register const u_int8_t *cp, + register u_int16_t length, register u_int16_t oset); +#define hex_and_ascii_print(ident, cp, length) hex_and_ascii_print_with_offset(ident, cp, length, 0) + + +#define my_strdup(dest, src) \ + if (!(dest = strdup (src))) { \ + fprintf (stderr, "strdup() failed\n"); \ + exit (101); \ + } + +#define my_strndup(dest, src, c) \ + if (!(dest = strndup ((const char *) src, c))) { \ + fprintf (stderr, "strndup() failed\n"); \ + exit (102); \ + } + +char *strtrimr (char *s); + +#define my_free(p) \ + if (p) { \ + free (p); \ + p = NULL; \ + } + + +/*** STUFF FOR PORTABLE LARGE-FILES FSEEK ***/ +#if defined(_MSC_VER) +typedef __int64 my_off_t; +#elif defined (HAVE_OFF_T) && SIZEOF_OFF_T >= 8 +typedef off_t my_off_t; +#elif defined (HAVE_FPOS_T) && SIZEOF_FPOS_T >= 8 +typedef fpos_t my_off_t; +#else +typedef u_int64_t my_off_t; +#endif + +int my_fseek (FILE *fp, my_off_t offset, int whence); +my_off_t my_ftell (FILE* fp); +/*******/ + + +/*** STUFF FOR DROPPING PRIVILEGES ***/ +FRIIDUMPLIB_EXPORT void drop_euid (); +FRIIDUMPLIB_EXPORT void upgrade_euid (); +/******/ + + +/*** STUFF FOR BOOLEAN DATA TYPE ***/ +#ifdef HAVE_STDBOOL_H +/* If using a C99 compiler, use the builtin boolean type */ +#include +#else +typedef enum {false,true} bool; +#endif +/******/ + +#endif diff --git a/libfriidump/renesas.c b/libfriidump/renesas.c new file mode 100644 index 0000000..3f6da5c --- /dev/null +++ b/libfriidump/renesas.c @@ -0,0 +1,132 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "rs.h" +#include +#include +#include "misc.h" +#include "dvd_drive.h" + +/** + * Command found in Lite-On LH-18A1H + * Expected to work with: DH*, DW*, LH*, SH* series + * Possibly would work with other Mediatek based drives: + * Samsung SE*, SH* series + * Some Dell drives + * Some Sony drives + * Asus DRW-1814* (DRW* series?) + */ + + +/** + * @param dvd The DVD drive the command should be exectued on. + * @param offset The absolute memory offset to start dumping. + * @param block_size The block size for the dump. + * @param buf Where to place the dumped data. This must have been setup by the caller to store up to block_size bytes. + * @return < 0 if an error occurred, 0 otherwise. + */ +int renesas_dvd_dump_memblock (dvd_drive *dvd, u_int32_t offset, u_int32_t block_size, u_int8_t *buf) { + mmc_command mmc; + int out; + + u_int32_t src_offset; + u_int32_t sec_nr; + u_int32_t sec_cnt; + u_int32_t first_sec_nr; + + if (!buf) { + error ("NULL buffer"); + out = -1; + } else if (!block_size || block_size > 65535) { + error ("invalid block_size (valid: 1 - 65535)"); + out = -2; + } else { + dvd_init_command (&mmc, buf, block_size, NULL); + mmc.cmd[0] = 0x3C; + mmc.cmd[1] = 0x05; + mmc.cmd[2] = (unsigned char) ((offset & 0xFF000000) >> 24); // address MSB + mmc.cmd[3] = (unsigned char) ((offset & 0x00FF0000) >> 16); // address MSB + mmc.cmd[4] = (unsigned char) ((offset & 0x0000FF00) >> 8); // address + mmc.cmd[5] = (unsigned char) ( offset & 0x000000FF); // address LSB +// mmc.cmd[6] = (unsigned char) ((block_size & 0x00FF0000) >> 16);// length MSB + mmc.cmd[6] = 0; + mmc.cmd[7] = (unsigned char) ((block_size & 0x0000FF00) >> 8); // length + mmc.cmd[8] = (unsigned char) ( block_size & 0x000000FF); // length LSB + mmc.cmd[9] = 0x44; + + out = dvd_execute_cmd (dvd, &mmc, false); + + src_offset = 0; + first_sec_nr = 0x55555555; + sec_cnt = 0; + while (src_offset < block_size) { + sec_nr=(*(buf+src_offset+1)<<16)+(*(buf+src_offset+2)<<8)+(*(buf+src_offset+3)); + if (first_sec_nr==0x55555555) { + first_sec_nr=sec_nr; + } + else + if (sec_nr!=first_sec_nr+sec_cnt) { + error ("sector sequence broken"); + out = -3; + *(buf+src_offset+0)=0xff; + *(buf+src_offset+1)=0xff; + *(buf+src_offset+2)=0xff; + *(buf+src_offset+3)=0xff; + break; + } + src_offset += 2064; + sec_cnt += 1; + } + } + return (out); +} + + + +/** + * WARNING: it can take a while to dump a lot of data. + * @param dvd The DVD drive the command should be exectued on. + * @param offset The memory offset to start dumping, relative to the cache start offset. + * @param block_len The number of blocks to dump. + * @param block_size The block size for the dump. + * @param buf Where to place the dumped data. This must have been setup by the caller to store up to block_size bytes. + * @return < 0 if an error occurred, 0 otherwise. + */ +int renesas_dvd_dump_mem (dvd_drive *dvd, u_int32_t offset, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf) { + u_int32_t i; + int r, out; + + if (!buf) { + error ("NULL buffer"); + out = -1; + } else { + r = -10; + for (i = 0; i < block_len; i++) { + if ((r = renesas_dvd_dump_memblock (dvd, offset + i * block_size, block_size, buf + i * block_size)) < 0) { + error ("renesas_dvd_dump_memblock() failed with %d", r); + break; + } + } + out = r; + } + + return (out); +} diff --git a/libfriidump/rs.c b/libfriidump/rs.c new file mode 100644 index 0000000..4534d5b --- /dev/null +++ b/libfriidump/rs.c @@ -0,0 +1,316 @@ +#include +#include +#include +#include + +#define mm 8 /* RS code over GF(2**mm) - change to suit */ +#define n 256 /* n = size of the field */ +#define nn 182 /* nn=2**mm -1 length of codeword */ +#define kk 172 /* kk = nn-2*tt */ /* Degree of g(x) = 2*tt */ + +//#define NN n-1 +//#define FCR 0 +//#define PRIM 1 +#define _NROOTS nn-kk +//#define PAD NN-nn +//#define A0 NN +//#define IPRIM 1 + +const int NN = n-1; +const int FCR = 0; +const int PRIM = 1; +const int NROOTS = nn-kk; +const int PAD = (n-1)-nn; +const int A0 = n-1; +const int IPRIM = 1; + + +#ifndef min +#define min(a,b) ((a) < (b) ? (a) : (b)) +#endif + +/**** Primitive polynomial ****/ +int pp [mm+1] = { 1, 0, 1, 1, 1, 0, 0, 0, 1}; /* 1+x^2+x^3+x^4+x^8 */ + +/* generator polynomial, tables for Galois field */ +int alpha_to[n], index_of[n], gg[nn-kk+1]; + +int b0 = 1; + +/* data[] is the info vector, bb[] is the parity vector, recd[] is the + noise corrupted received vector */ +int recd[nn], data[kk], bb[nn-kk]; + +int modnn(int x){ + while (x >= 0xff) { + x -= 0xff; + x = (x >> 0xff) + (x & 0xff); + } + return x; +} + + +void generate_gf() + { + register int i, mask ; + + mask = 1 ; + alpha_to[mm] = 0 ; + for (i=0; i>= 1 ; + for (i=mm+1; i<255; i++) + { if (alpha_to[i-1] >= mask) + alpha_to[i] = alpha_to[mm] ^ ((alpha_to[i-1]^mask)<<1) ; + else alpha_to[i] = alpha_to[i-1]<<1 ; + index_of[alpha_to[i]] = i ; + } + index_of[0] = A0 ;//-1 + } + + +void gen_poly() +/* Obtain the generator polynomial of the tt-error correcting, length */ + { + register int i, j, root; + + gg[0] = 1; + + for (i = 0,root=0*1; i < nn-kk; i++,root += 1) { + gg[i+1] = 1; + + for (j = i; j > 0; j--){ + if (gg[j] != 0) + gg[j] = gg[j-1] ^ alpha_to[modnn(index_of[gg[j]] + root)]; + else + gg[j] = gg[j-1]; + } + + gg[0] = alpha_to[modnn(index_of[gg[0]] + root)]; + } + for (i=0; i <= nn-kk; i++) { + gg[i] = index_of[gg[i]]; + } + } + + +void rs_encode(unsigned char *data, unsigned char *bb) + { + register int i,j ; + int feedback; + + for (i=0; i 0) { + /* Init lambda to be the erasure locator polynomial */ + lambda[1] = alpha_to[modnn(PRIM*(NN-1-eras_pos[0]))]; + for (i = 1; i < no_eras; i++) { + u = modnn(PRIM*(NN-1-eras_pos[i])); + for (j = i+1; j > 0; j--) { + tmp = index_of[lambda[j - 1]]; + if(tmp != A0) + lambda[j] ^= alpha_to[modnn(u + tmp)]; + } + } + } + for(i=0;i 0; j--){ + if (reg[j] != A0) { + reg[j] = modnn(reg[j] + j); + q ^= alpha_to[reg[j]]; + } + } + if (q != 0) + continue; /* Not a root */ + /* store root (index-form) and error location number */ + root[count] = i; + loc[count] = k; + /* If we've already found max possible roots, + * abort the search to save time + */ + if(++count == deg_lambda) + break; + } + + if (deg_lambda != count) { + /* + * deg(lambda) unequal to number of roots => uncorrectable + * error detected + */ + count = -1; + goto finish; + } + /* + * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo + * x**NROOTS). in index form. Also find deg(omega). + */ + deg_omega = deg_lambda-1; + for (i = 0; i <= deg_omega;i++){ + tmp = 0; + for(j=i;j >= 0; j--){ + if ((s[i - j] != A0) && (lambda[j] != A0)) + tmp ^= alpha_to[modnn(s[i - j] + lambda[j])]; + } + omega[i] = index_of[tmp]; + } + + /* + * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = + * inv(X(l))**(FCR-1) and den = lambda_pr(inv(X(l))) all in poly-form + */ + for (j = count-1; j >=0; j--) { + num1 = 0; + for (i = deg_omega; i >= 0; i--) { + if (omega[i] != A0) + num1 ^= alpha_to[modnn(omega[i] + i * root[j])]; + } + num2 = alpha_to[modnn(root[j] * (FCR - 1) + NN)]; + den = 0; + + /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */ + for (i = min(deg_lambda,NROOTS-1) & ~1; i >= 0; i -=2) { + if(lambda[i+1] != A0) + den ^= alpha_to[modnn(lambda[i+1] + i * root[j])]; + } + /* Apply error to data */ + if (num1 != 0 && loc[j] >= PAD) { + data[loc[j]-PAD] ^= alpha_to[modnn(index_of[num1] + index_of[num2] + NN - index_of[den])]; + } + } + + finish: + if(eras_pos != NULL){ + for(i=0;i +#include +#include +#include + +int modnn(int x); +void generate_gf(); +void gen_poly(); +void rs_encode(unsigned char *data, unsigned char *bb); +int rs_decode(unsigned char *data, int *eras_pos, int no_eras); diff --git a/libfriidump/unscrambler.c b/libfriidump/unscrambler.c new file mode 100644 index 0000000..fba0730 --- /dev/null +++ b/libfriidump/unscrambler.c @@ -0,0 +1,371 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/*! \file + * \brief Unscrambler for Nintendo GameCube/Wii discs. + * + * As Nintendo GameCube/Wii discs use the standars DVD-ROM scrambling algorithm, but with different, unknown, seeds, the actual seeds have to be brute-forced. + * The functions in this file take care of the brute-forcing and of the actual unscrambling of the read sectors. + * + * The code in this file has been derived from unscrambler 0.4, Copyright (C) 2006 Victor Mu�oz (xt5@ingenieria-inversa.cl), GPL v2+, + * http://www.ingenieria-inversa.cl/?lp_lang_pref=en . + */ + +#include "misc.h" +#include +#include +#include +#include +#include "constants.h" +#include "byteorder.h" +#include "ecma-267.h" +#include "unscrambler.h" + +// #define unscramblerdebug(...) debug (__VA_ARGS__); +#define unscramblerdebug(...) + +/*! \brief Size of the seeds cache (Do not touch) */ +#define MAX_SEEDS 4 + +/*! \brief Number of bytes of a sector on which the EDC is calculated */ +#define EDC_LENGTH (RAW_SECTOR_SIZE - 4) /* The EDC value is contained in the bottom 4 bytes of a frame */ + +u_int8_t disctype; + +/*! \brief A structure that represents a seed + */ +typedef struct t_seed { + int seed; //!< The seed, in numeric format. + unsigned char streamcipher[SECTOR_SIZE]; //!< The stream cipher generated from the seed through the LFSR. +} t_seed; + + +/*! \brief A structure that represents an unscrambler + */ +struct unscrambler_s { + t_seed seeds[(MAX_SEEDS + 1) * 16]; //!< The seeds cache. + bool bruteforce_seeds; //!< If true, whenever a seed for a sector is not cached, it will be found via a bruteforce attack, otherwise an error will be returned. +}; + +void unscrambler_set_disctype (u_int8_t disc_type){ + disctype = disc_type; +// fprintf (stdout,"%d",disctype); +} + +/** + * Adds a seed to the cache, calculating its streamcipher. + * @param seeds The seed cache. + * @param seed The seed to add. + * @return A structure representing the added seed, or NULL if it could not be added. + */ +static t_seed *add_seed (t_seed *seeds, unsigned short seed) { + int i; + t_seed *out; + + unscramblerdebug ("Caching seed %04x\n", seed); + + if (seeds -> seed == -2) { + out = NULL; + } else { + seeds -> seed = seed; + + LFSR_init (seed); + for (i = 0; i < SECTOR_SIZE; i++) + seeds -> streamcipher[i] = LFSR_byte (); + + out = seeds; + } + + return (out); +} + + +/** + * Tests if the specified seed is the one used for the specified sector block: the check is done comparing the generated EDC with the one at the bottom of each + * sector. Sectors are processed in blocks, as the same seed is used for 16 consecutive sectors. + * @param buf The sector. + * @param j The seed. + * @return true if the seed is correct, false otherwise. + */ +static bool test_seed (u_int8_t *buf, int j) { + int i; + u_int8_t tmp[RAW_SECTOR_SIZE]; + u_int32_t edc_calculated, edc_correct; + bool out; + + memcpy (tmp, buf, RAW_SECTOR_SIZE); + + LFSR_init (j); + for (i = 12; i < EDC_LENGTH; i++) + tmp[i] ^= LFSR_byte (); + + edc_calculated = edc_calc (0x00000000, tmp, EDC_LENGTH); + edc_correct = my_ntohl (*((u_int32_t *) (&tmp[EDC_LENGTH]))); + if (edc_calculated == edc_correct) + out = true; + else + out = false; + + return (out); +} + + +/** + * Unscramble a complete block, using an already-cached seed. + * @param seed The seed to use for the unscrambling. + * @param _bin The 16-sector block to unscramble (RAW_BLOCK_SIZE). + * @param _bout The unscrambled 16-sector block (BLOCK_SIZE). + * @return True if the unscrambling was successful, false otherwise. + */ +static bool unscramble_frame (t_seed *seed, u_int8_t *_bin, u_int8_t *_bout) { + int i, j; + u_int8_t tmp[RAW_SECTOR_SIZE], *bin, *bout; + u_int32_t *_4bin, *_4cipher, edc_calculated, edc_correct; + bool out; + + out = true; + for(j = 0; j < 16; j++) { + bin = &_bin[RAW_SECTOR_SIZE * j]; + bout = &_bout[SECTOR_SIZE * j]; + + memcpy (tmp, bin, RAW_SECTOR_SIZE); + _4bin = (u_int32_t *) &tmp[12]; /* Scrambled data begin at byte 12 */ + _4cipher = (u_int32_t *) seed -> streamcipher; + for (i = 0; i < 512; i++) /* Well, the scrambling algorithm is just a bitwise XOR... */ + _4bin[i] ^= _4cipher[i]; + + //memcpy (bout, tmp + 6, SECTOR_SIZE); // copy CPR_MAI bytes + + if (disctype==3) { //Regular + memcpy (bout, tmp + 12, SECTOR_SIZE); // DVD: copy 2048 bytes (starting from CPR_MAI) + } + else { //Nintendo + memcpy (bout, tmp + 6, SECTOR_SIZE); // Nintendo: copy 2048 bytes (up to CPR_MAI) + memcpy (&_bin[(RAW_SECTOR_SIZE * j)+2054], &tmp[2054], 6); + } + + edc_calculated = edc_calc (0x00000000, tmp, EDC_LENGTH); + edc_correct = my_ntohl (*((u_int32_t *) (&tmp[EDC_LENGTH]))); + if (edc_calculated != edc_correct) { + debug ("Bad EDC (%08x), must be %08x (sector = %d)", edc_calculated, edc_correct, j); + out = false; + } + } + + return (out); +} + + +/** + * Initializes the seed cache. + * @param u The unscrambler structure. + */ +static void unscrambler_init_seeds (unscrambler *u) { + int i, j; + + for (i = 0; i < 16; i++) { + for (j = 0; j < MAX_SEEDS; j++) + u -> seeds[i * MAX_SEEDS + j].seed = -1; + + u -> seeds[i * MAX_SEEDS + j].seed = -2; // TODO Check what this does + } + + return; +} + + +/** + * Creates a new structure representing an unscrambler. + * @return The newly-created structure, to be used with the other commands. + */ +unscrambler *unscrambler_new (void) { + unscrambler *u; + + u = (unscrambler *) malloc (sizeof (unscrambler)); + unscrambler_init_seeds (u); + u -> bruteforce_seeds = true; + + return (u); +} + + +/** + * Frees resources used by an unscrambler structure and destroys it. + * @param u The unscrambler structure. + * @return NULL. + */ +void *unscrambler_destroy (unscrambler *u) { + my_free (u); + + return (NULL); +} + + +void unscrambler_set_bruteforce (unscrambler *u, bool b) { + u -> bruteforce_seeds = b; + debug ("Seed bruteforcing %s", b ? "enabled" : "disabled"); + + return; +} + + +/** + * Unscrambles a 16-sector block. + * @param u The unscrambler structure. + * @param sector_no The number of the first sector in the block. + * @param inbuf The 16-sector block to unscramble. Each block must be RAW_SECTOR_SIZE bytes long, so that the total size is RAW_BLOCK_SIZE. + * @param outbuf The unscrambled 16-sector block. Each block will be SECTOR_SIZE bytes long, so that the total size is BLOCK_SIZE. + * @return True if the unscrambling was successful, false otherwise. + */ +bool unscrambler_unscramble_16sectors (unscrambler *u, u_int32_t sector_no, u_int8_t *inbuf, u_int8_t *outbuf) { + t_seed *seeds; + t_seed *current_seed; + int j; + bool out; + + out = true; + + seeds = &(u -> seeds[((sector_no / 16) & 0x0F) * MAX_SEEDS]); + + /* Try to find the seed used for this sector */ + current_seed = NULL; + while (!current_seed && (seeds -> seed) >= 0) { + if (test_seed (inbuf, seeds -> seed)) + current_seed = seeds; + else + seeds++; + } + + if (!current_seed && u -> bruteforce_seeds) { + /* The seed is not cached, yet. Try to find it with brute force... */ + unscramblerdebug ("Brute-forcing seed for sector %d...", sector_no); + + for (j = 0; !current_seed && j < 0x7FFF; j++) { + if (test_seed (inbuf, j)) { + if (!(current_seed = add_seed (seeds, j))) { + error ("No enough cache space for caching seed"); + out = false; + } + } + } + + if (current_seed) + unscramblerdebug ("Seed found: %04x", --j); + } + + if (current_seed) { + /* OK, somehow seed was found: unscramble frame, write it and go on */ + if (!unscramble_frame (current_seed, inbuf, outbuf)) { + error ("Error unscrambling frame %u\n", sector_no); + out = false; + } else { + out = true; + } + } else { + /* Well, we only get here if there are read errors */ + error ("Cannot find seed for frame %u", sector_no); + out = false; + } + + return (out); +} + + +/** + * Unscrambles a complete file. + * @param u The unscrambler structure. + * @param infile The input file name. + * @param outfile The output file name. + * @param progress A function to be called repeatedly during the operation, useful to report progress data/statistics. + * @param progress_data Data to be passed as-is to the progress function. + * @return True if the unscrambling was successful, false otherwise. + */ +bool unscrambler_unscramble_file (unscrambler *u, char *infile, char *outfile, unscrambler_progress_func progress, void *progress_data, u_int32_t *current_sector) { + FILE *in, *outfp; + bool out; + u_int8_t b_in[RAW_BLOCK_SIZE], b_out[BLOCK_SIZE]; + size_t r; + my_off_t filesize; + int s; + u_int32_t total_sectors; + + out = false; + if(!(in = fopen (infile ? infile : "", "rb"))) { + error ("Cannot open input file \"%s\"", infile); + } else if (!(outfp = fopen (outfile ? outfile : "", "wb"))) { + error ("Cannot open output file \"%s\"", outfile); + fclose (in); + } else { + /* Find out how many sectors we need to process */ + my_fseek (in, 0, SEEK_END); + filesize = my_ftell (in); + total_sectors = (u_int32_t) (filesize / RAW_SECTOR_SIZE); + rewind (in); + + /* First call to progress function */ + if (progress) + progress (true, 0, total_sectors, progress_data); + + s = 0, out = true; + while ((r = fread (b_in, 1, RAW_BLOCK_SIZE, in)) > 0 && out) { + if (r < RAW_BLOCK_SIZE) { + warning ("Short block read (%u bytes), padding with zeroes!", r); + memset (b_in + r, 0, sizeof (b_in) - r); + } + + if (unscrambler_unscramble_16sectors (u, s, b_in, b_out)) { + clearerr (outfp); + + if (!b_out) { + error ("NULL buffer"); + out = false; + *(current_sector) = s; + } + else fwrite (b_out, SECTOR_SIZE, SECTORS_PER_BLOCK, outfp); + if (ferror (outfp)) { + error ("fwrite() to ISO output file failed"); + out = false; + *(current_sector) = s; + } + } else { + debug ("unscrambler_unscramble_16sectors() failed"); + out = false; + *(current_sector) = s; + } + + s += 16; + + if ((s % 320 == 0) || (s == total_sectors)) { //speedhack + if (progress) + progress (false, s, total_sectors, progress_data); + } + } + + if (out) { + debug ("Image successfully unscrambled"); + } + + fclose (in); + fclose (outfp); + } + + return (out); +} diff --git a/libfriidump/unscrambler.h b/libfriidump/unscrambler.h new file mode 100644 index 0000000..4c2209f --- /dev/null +++ b/libfriidump/unscrambler.h @@ -0,0 +1,41 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef UNSCRAMBLER_H_INCLUDED +#define UNSCRAMBLER_H_INCLUDED + +#include "misc.h" +#include + +typedef struct unscrambler_s unscrambler; + +/* We want this module to be independent of the library framework, so that it can be easily recycled. Hence we just define the type of + the progress function the same format we use elsewhere */ +typedef void (*unscrambler_progress_func) (bool start, u_int32_t current_sector, u_int32_t total_sectors, void *progress_data); + +FRIIDUMPLIB_EXPORT unscrambler *unscrambler_new (void); +FRIIDUMPLIB_EXPORT void *unscrambler_destroy (unscrambler *u); +FRIIDUMPLIB_EXPORT bool unscrambler_unscramble_16sectors (unscrambler *u, u_int32_t sector_no, u_int8_t *inbuf, u_int8_t *outbuf); +FRIIDUMPLIB_EXPORT bool unscrambler_unscramble_file (unscrambler *u, char *infile, char *outfile, unscrambler_progress_func progress, void *progress_data, u_int32_t *current_sector); +FRIIDUMPLIB_EXPORT void unscrambler_set_bruteforce (unscrambler *u, bool b); +FRIIDUMPLIB_EXPORT void unscrambler_set_disctype (u_int8_t disc_type); + +#endif diff --git a/libfriidump/vanilla_2064.c b/libfriidump/vanilla_2064.c new file mode 100644 index 0000000..a95b2d3 --- /dev/null +++ b/libfriidump/vanilla_2064.c @@ -0,0 +1,131 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +//#include "rs.h" +#include +#include +#include "misc.h" +#include "dvd_drive.h" + +/** + * Command found in Lite-On LH-18A1H + * Expected to work with: DH*, DW*, LH*, SH* series + * Possibly would work with other Mediatek based drives: + * Samsung SE*, SH* series + * Some Dell drives + * Some Sony drives + * Asus DRW-1814* (DRW* series?) + */ + + +/** + * @param dvd The DVD drive the command should be exectued on. + * @param offset The absolute memory offset to start dumping. + * @param block_size The block size for the dump. + * @param buf Where to place the dumped data. This must have been setup by the caller to store up to block_size bytes. + * @return < 0 if an error occurred, 0 otherwise. + */ +int vanilla_2064_dvd_dump_memblock (dvd_drive *dvd, u_int32_t offset, u_int32_t block_size, u_int8_t *buf) { + mmc_command mmc; + int out; + + u_int32_t src_offset; + u_int32_t sec_nr; + u_int32_t sec_cnt; + u_int32_t first_sec_nr; + + if (!buf) { + error ("NULL buffer"); + out = -1; + } else if (!block_size || block_size > 65535) { + error ("invalid block_size (valid: 1 - 65535)"); + out = -2; + } else { + dvd_init_command (&mmc, buf, block_size, NULL); + mmc.cmd[0] = 0x3C; + mmc.cmd[1] = 0x02; + mmc.cmd[2] = 0x00; + mmc.cmd[3] = (unsigned char) ((offset & 0x00FF0000) >> 16); // address MSB + mmc.cmd[4] = (unsigned char) ((offset & 0x0000FF00) >> 8); // address + mmc.cmd[5] = (unsigned char) ( offset & 0x000000FF); // address LSB + mmc.cmd[6] = (unsigned char) ((block_size & 0x00FF0000) >> 16);// length MSB + mmc.cmd[7] = (unsigned char) ((block_size & 0x0000FF00) >> 8); // length + mmc.cmd[8] = (unsigned char) ( block_size & 0x000000FF); // length LSB + + out = dvd_execute_cmd (dvd, &mmc, false); + + src_offset = 0; + first_sec_nr = 0x55555555; + sec_cnt = 0; + while (src_offset < block_size) { + sec_nr=(*(buf+src_offset+1)<<16)+(*(buf+src_offset+2)<<8)+(*(buf+src_offset+3)); + if (first_sec_nr==0x55555555) { + first_sec_nr=sec_nr; + } + else + if (sec_nr!=first_sec_nr+sec_cnt) { + error ("sector sequence broken"); + out = -3; + *(buf+src_offset+0)=0xff; + *(buf+src_offset+1)=0xff; + *(buf+src_offset+2)=0xff; + *(buf+src_offset+3)=0xff; + break; + } + src_offset += 2064; + sec_cnt += 1; + } + } + + return (out); +} + + + +/** + * WARNING: it can take a while to dump a lot of data. + * @param dvd The DVD drive the command should be exectued on. + * @param offset The memory offset to start dumping, relative to the cache start offset. + * @param block_len The number of blocks to dump. + * @param block_size The block size for the dump. + * @param buf Where to place the dumped data. This must have been setup by the caller to store up to block_size bytes. + * @return < 0 if an error occurred, 0 otherwise. + */ +int vanilla_2064_dvd_dump_mem (dvd_drive *dvd, u_int32_t offset, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf) { + u_int32_t i; + int r, out; + + if (!buf) { + error ("NULL buffer"); + out = -1; + } else { + r = -10; + for (i = 0; i < block_len; i++) { + if ((r = vanilla_2064_dvd_dump_memblock (dvd, offset + i * block_size, block_size, buf + i * block_size)) < 0) { + error ("vanilla_2064_dvd_dump_memblock() failed with %d", r); + break; + } + } + out = r; + } + + return (out); +} diff --git a/libfriidump/vanilla_2384.c b/libfriidump/vanilla_2384.c new file mode 100644 index 0000000..1190d38 --- /dev/null +++ b/libfriidump/vanilla_2384.c @@ -0,0 +1,153 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +//#include "rs.h" +#include +#include +#include "misc.h" +#include "dvd_drive.h" + +/** + * Command found in Lite-On LH-18A1H + * Expected to work with: DH*, DW*, LH*, SH* series + * Possibly would work with other Mediatek based drives: + * Samsung SE*, SH* series + * Some Dell drives + * Some Sony drives + * Asus DRW-1814* (DRW* series?) + */ + +//u_int8_t tmp[64*1024]; + +/** + * @param dvd The DVD drive the command should be exectued on. + * @param offset The absolute memory offset to start dumping. + * @param block_size The block size for the dump. + * @param buf Where to place the dumped data. This must have been setup by the caller to store up to block_size bytes. + * @return < 0 if an error occurred, 0 otherwise. + */ +int vanilla_2384_dvd_dump_memblock (dvd_drive *dvd, u_int32_t offset, u_int32_t block_size, u_int8_t *buf) { + mmc_command mmc; + int out; + u_int32_t raw_block_size; + u_int32_t raw_offset; + + u_int32_t src_offset; + u_int32_t dst_offset; + u_int32_t row_nr; + u_int32_t sec_nr; + u_int32_t sec_cnt; + u_int32_t first_sec_nr; + u_int8_t tmp[64*1024]; + //u_int8_t *tmp; + + raw_block_size = (block_size / 2064) * 0x950; + raw_offset = (offset / 2064) * 0x950; + + if (!buf) { + error ("NULL buffer"); + out = -1; + } else if (!block_size || raw_block_size > 65535) { + error ("invalid raw_block_size (valid: 1 - 65535)"); + error ("raw_block_size = (block_size / 2064) * 2384"); + out = -2; + } else { + //tmp = malloc(64*1024); + dvd_init_command (&mmc, tmp, raw_block_size, NULL); + mmc.cmd[0] = 0x3C; + mmc.cmd[1] = 0x02; + mmc.cmd[2] = 0x00; + mmc.cmd[3] = (unsigned char) ((raw_offset & 0x00FF0000) >> 16);// address MSB + mmc.cmd[4] = (unsigned char) ((raw_offset & 0x0000FF00) >> 8); // address + mmc.cmd[5] = (unsigned char) ( raw_offset & 0x000000FF); // address LSB + mmc.cmd[6] = (unsigned char) ((raw_block_size & 0x00FF0000) >> 16); // length MSB + mmc.cmd[7] = (unsigned char) ((raw_block_size & 0x0000FF00) >> 8); // length + mmc.cmd[8] = (unsigned char) ( raw_block_size & 0x000000FF); // length LSB + + out = dvd_execute_cmd (dvd, &mmc, false); + + src_offset = 0; + dst_offset = 0; + first_sec_nr = 0x55555555; + sec_cnt = 0; + while (src_offset < raw_block_size) { + sec_nr=(*(tmp+src_offset+1)<<16)+(*(tmp+src_offset+2)<<8)+(*(tmp+src_offset+3)); + if (first_sec_nr==0x55555555) { + first_sec_nr=sec_nr; +// rs_decode(tmp+src_offset, 0, 0); + } + else + if (sec_nr==first_sec_nr+sec_cnt) { +// rs_decode(tmp+src_offset, 0, 0); //sector seq = ok + } + else { //sector seq broken -> corrupt + error ("sector sequence broken"); + out = -3; + *(tmp+src_offset+0)=0xff; + *(tmp+src_offset+1)=0xff; + *(tmp+src_offset+2)=0xff; + *(tmp+src_offset+3)=0xff; + break; + } + for (row_nr=0; row_nr<12; row_nr++) { + memcpy(buf+dst_offset, tmp+src_offset, 172); + dst_offset += 172; + src_offset += 182; + } + src_offset += 200; + sec_cnt += 1; + } + //free(tmp); + } + return (out); +} + + + +/** + * WARNING: it can take a while to dump a lot of data. + * @param dvd The DVD drive the command should be exectued on. + * @param offset The memory offset to start dumping, relative to the cache start offset. + * @param block_len The number of blocks to dump. + * @param block_size The block size for the dump. + * @param buf Where to place the dumped data. This must have been setup by the caller to store up to block_size bytes. + * @return < 0 if an error occurred, 0 otherwise. + */ +int vanilla_2384_dvd_dump_mem (dvd_drive *dvd, u_int32_t offset, u_int32_t block_len, u_int32_t block_size, u_int8_t *buf) { + u_int32_t i; + int r, out; + + if (!buf) { + error ("NULL buffer"); + out = -1; + } else { + r = -10; + for (i = 0; i < block_len; i++) { + if ((r = vanilla_2384_dvd_dump_memblock (dvd, offset + i * block_size, block_size, buf + i * block_size)) < 0) { + error ("vanilla_2384_dvd_dump_memblock() failed with %d", r); + break; + } + } + out = r; + } + + return (out); +} diff --git a/libfriidump/win32compat.c b/libfriidump/win32compat.c new file mode 100644 index 0000000..36ac72a --- /dev/null +++ b/libfriidump/win32compat.c @@ -0,0 +1,75 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://www.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + + +#ifdef WIN32 + +#include "win32compat.h" +#include +#include +#include +#include + + +char *strndup (const char *src, int c) { + char *dest; + + if ((dest = (char *) malloc ((c + 1) * sizeof (char)))) { + memcpy (dest, src, c); + dest[c] = '\0'; + } + + return (dest); +} + + +int ftruncate (int fd, __int64 size) { + return (_chsize_s (fd, size)); +} + + + +/* The following gettimeofday() replacement function was taken from Snort, GPLv2 */ +/**************************************************************************** + * + * Function: gettimeofday(struct timeval *, struct timezone *) + * + * Purpose: Get current time of day. + * + * Arguments: tv => Place to store the curent time of day. + * tz => Ignored. + * + * Returns: 0 => Success. + * + ****************************************************************************/ +int gettimeofday(struct timeval *tv, struct timezone *tz) { + struct _timeb tb; + + if (tv == NULL) { + return -1; + } + _ftime(&tb); + tv->tv_sec = (long) tb.time; + tv->tv_usec = ((int) tb.millitm) * 1000; + return 0; +} + +#endif diff --git a/libfriidump/win32compat.h b/libfriidump/win32compat.h new file mode 100644 index 0000000..70827a6 --- /dev/null +++ b/libfriidump/win32compat.h @@ -0,0 +1,69 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://www.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + + +/*** WINDOWS COMPATIBILITY STUFF ***/ + +#ifdef WIN32 +#include +#include + +/* Stuff to export library symbols */ +#ifdef FRIIDUMPLIB_BUILD_DLL +#ifdef FRIIDUMPLIB_EXPORTS +#define FRIIDUMPLIB_EXPORT __declspec(dllexport) /* Building the lib */ +#else +#define FRIIDUMPLIB_EXPORT __declspec(dllimport) /* Building user code */ +#endif +#else +#define FRIIDUMPLIB_EXPORT +#endif + +/* Definition of the types we use for Windows */ +#define u_int8_t BYTE +#define u_int16_t WORD +#define int32_t INT32 +#define u_int32_t DWORD +#define int64_t INT64 +#define u_int64_t ULONGLONG +typedef long suseconds_t; + +/* Modern MSVC/UCRT provides snprintf; do not macro-alias it. */ +#define strdup _strdup +#define strcasecmp lstrcmpi +#ifndef va_copy +#define va_copy(ap1, ap2) ((ap1) = (ap2)) /* MSVC compatibility */ +#endif + +/* Some functions do not exist (export them for applications, too) */ +FRIIDUMPLIB_EXPORT char *strndup (const char *src, int c); +FRIIDUMPLIB_EXPORT int ftruncate (int fd, __int64 size); +FRIIDUMPLIB_EXPORT int gettimeofday(struct timeval *tv, struct timezone *tz); + +/* Windows does have a different localtime() */ +#define localtime_r(x, y) !localtime_s (y, x) + +#else + +#define FRIIDUMPLIB_EXPORT + +#endif +/******/ diff --git a/libfriidump/xbox_ref/rc4.c b/libfriidump/xbox_ref/rc4.c new file mode 100644 index 0000000..3c56b50 --- /dev/null +++ b/libfriidump/xbox_ref/rc4.c @@ -0,0 +1,48 @@ +#include "rc4.h" + +void RC4Init(TRC4Context* rc4, const uint8_t* key, uint32_t keyLen) { + uint8_t R, T, K; + uint32_t U; + uint32_t L = keyLen; + int S; // Changed from uint8_t to int to prevent infinite loop + + rc4->I = 0; + rc4->J = 0; + + // First loop: Initialize state + for (S = 0; S < 256; S++) + rc4->D[S] = (uint8_t)S; + + R = 0; + U = 0; + + // Second loop: Key scheduling + for (S = 0; S < 256; S++) { + if (L > 0) K = key[U]; // Added check for safety + else K = 0; + + U++; + if (U >= L) U = 0; + + R = (uint8_t)(R + rc4->D[S] + K); + T = rc4->D[S]; + rc4->D[S] = rc4->D[R]; + rc4->D[R] = T; + } +} + +void rc4Decrypt(const void* InData, void* OutData, uint32_t Size, TRC4Context* ctx) { + uint32_t i = 0, j = 0, t, k; + const uint8_t* in = (const uint8_t*)InData; + uint8_t* out = (uint8_t*)OutData; + + for (k = 0; k < Size; k++) { + i = (i + 1) & 0xFF; + t = ctx->D[i]; + j = (j + t) & 0xFF; + ctx->D[i] = ctx->D[j]; + ctx->D[j] = (uint8_t)t; + t = (t + ctx->D[i]) & 0xFF; + out[k] = in[k] ^ ctx->D[t]; + } +} diff --git a/libfriidump/xbox_ref/rc4.h b/libfriidump/xbox_ref/rc4.h new file mode 100644 index 0000000..12bfd95 --- /dev/null +++ b/libfriidump/xbox_ref/rc4.h @@ -0,0 +1,15 @@ +#ifndef RC4_H +#define RC4_H + +#include + +typedef struct { + uint8_t D[256]; + uint8_t I; + uint8_t J; +} TRC4Context; + +void RC4Init(TRC4Context* rc4, const uint8_t* key, uint32_t keyLen); +void rc4Decrypt(const void* InData, void* OutData, uint32_t Size, TRC4Context* ctx); + +#endif diff --git a/libfriidump/xbox_ref/scsi_structs.h b/libfriidump/xbox_ref/scsi_structs.h new file mode 100644 index 0000000..e44923d --- /dev/null +++ b/libfriidump/xbox_ref/scsi_structs.h @@ -0,0 +1,83 @@ +#ifndef SCSI_STRUCTS_H +#define SCSI_STRUCTS_H + +#include +#include + +// Force the compiler to NOT add padding bytes +#pragma pack(push, 8) + +typedef struct _SCSI_PASS_THROUGH { + uint16_t Length; + uint8_t ScsiStatus; + uint8_t PathId; + uint8_t TargetId; + uint8_t Lun; + uint8_t CdbLength; + uint8_t SenseInfoLength; + uint8_t DataIn; + uint32_t DataTransferLength; + uint32_t TimeOutValue; + uint32_t DataBufferOffset; + uint32_t SenseInfoOffset; + uint8_t Cdb[16]; +} SCSI_PASS_THROUGH, *PSCSI_PASS_THROUGH; + +typedef struct _SCSI_PASS_THROUGH_DIRECT { + uint16_t Length; + uint8_t ScsiStatus; + uint8_t PathId; + uint8_t TargetId; + uint8_t Lun; + uint8_t CdbLength; + uint8_t SenseInfoLength; + uint8_t DataIn; + uint32_t DataTransferLength; + uint32_t TimeOutValue; + void* DataBuffer; + uint32_t SenseInfoOffset; + uint8_t Cdb[16]; +} SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT; + +typedef struct _SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER { + SCSI_PASS_THROUGH_DIRECT Spt; + uint32_t Filler; + uint8_t SenseBuf[32]; // Note: Ensure your code uses SenseBuf, not SenseBuffer +} SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, *PSCSI_PASS_THROUGH_DIRECT_WITH_BUFFER; + +#pragma pack(pop) + +// Manually define SCSI constants if headers are missing + +#ifndef IOCTL_SCSI_PASS_THROUGH_DIRECT +#define IOCTL_SCSI_PASS_THROUGH_DIRECT 0x4D014 +#endif + +#ifndef IOCTL_SCSI_PASS_THROUGH +#define IOCTL_SCSI_PASS_THROUGH 0x0004D004 +#endif + +#ifndef SCSI_IOCTL_DATA_OUT +#define SCSI_IOCTL_DATA_OUT 0 +#endif + +#ifndef SCSI_IOCTL_DATA_IN +#define SCSI_IOCTL_DATA_IN 1 +#endif + +#ifndef SCSI_IOCTL_DATA_UNSPECIFIED +#define SCSI_IOCTL_DATA_UNSPECIFIED 2 +#endif + +#ifndef FSCTL_LOCK_VOLUME +#define FSCTL_LOCK_VOLUME 0x00090018 +#endif +#ifndef FSCTL_DISMOUNT_VOLUME +#define FSCTL_DISMOUNT_VOLUME 0x00090020 +#endif + +#ifndef FSCTL_UNLOCK_VOLUME +#define FSCTL_UNLOCK_VOLUME 0x0009001c +#endif + +#endif \ No newline at end of file diff --git a/libfriidump/xbox_ref/sha1.c b/libfriidump/xbox_ref/sha1.c new file mode 100644 index 0000000..f3dd555 --- /dev/null +++ b/libfriidump/xbox_ref/sha1.c @@ -0,0 +1,95 @@ +#include "sha1.h" +#include + +#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b)))) + +static void SHA1_Transform(SHA1_CTX* ctx, const uint8_t data[64]) { + uint32_t a, b, c, d, e, i, j, t, m[80]; + + for (i = 0, j = 0; i < 16; ++i, j += 4) + m[i] = (data[j] << 24) | (data[j+1] << 16) | (data[j+2] << 8) | (data[j+3]); + for ( ; i < 80; ++i) + m[i] = ROTLEFT(m[i-3] ^ m[i-8] ^ m[i-14] ^ m[i-16], 1); + + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + e = ctx->state[4]; + + for (i = 0; i < 80; ++i) { + if (i < 20) + t = ROTLEFT(a,5) + ((b & c) | (~b & d)) + e + m[i] + 0x5A827999; + else if (i < 40) + t = ROTLEFT(a,5) + (b ^ c ^ d) + e + m[i] + 0x6ED9EBA1; + else if (i < 60) + t = ROTLEFT(a,5) + ((b & c) | (b & d) | (c & d)) + e + m[i] + 0x8F1BBCDC; + else + t = ROTLEFT(a,5) + (b ^ c ^ d) + e + m[i] + 0xCA62C1D6; + + e = d; + d = c; + c = ROTLEFT(b,30); + b = a; + a = t; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; + ctx->state[4] += e; +} + +void SHA1_Init(SHA1_CTX* ctx) { + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; + ctx->state[4] = 0xC3D2E1F0; + ctx->count[0] = ctx->count[1] = 0; +} + +void SHA1_Update(SHA1_CTX* ctx, const uint8_t* data, uint32_t len) { + uint32_t i, j; + + j = (ctx->count[0] >> 3) & 63; + if ((ctx->count[0] += len << 3) < (len << 3)) + ctx->count[1]++; + ctx->count[1] += (len >> 29); + + if ((j + len) > 63) { + memcpy(&ctx->buffer[j], data, (i = 64 - j)); + SHA1_Transform(ctx, ctx->buffer); + for ( ; i + 63 < len; i += 64) + SHA1_Transform(ctx, &data[i]); + j = 0; + } else { + i = 0; + } + memcpy(&ctx->buffer[j], &data[i], len - i); +} + +void SHA1_Final(uint8_t digest[20], SHA1_CTX* ctx) { + uint8_t finalcount[8]; + uint8_t c; + uint32_t i; + + for (i = 0; i < 8; i++) + finalcount[i] = (uint8_t)((ctx->count[(i >= 4 ? 0 : 1)] + >> ((3 - (i & 3)) * 8)) & 255); + + c = 0x80; + SHA1_Update(ctx, &c, 1); + while ((ctx->count[0] & 504) != 448) { + c = 0x00; + SHA1_Update(ctx, &c, 1); + } + + SHA1_Update(ctx, finalcount, 8); + + for (i = 0; i < 20; i++) + digest[i] = (uint8_t)((ctx->state[i>>2] >> ((3 - (i & 3)) * 8)) & 255); + + memset(ctx, 0, sizeof(*ctx)); +} diff --git a/libfriidump/xbox_ref/sha1.h b/libfriidump/xbox_ref/sha1.h new file mode 100644 index 0000000..96e033b --- /dev/null +++ b/libfriidump/xbox_ref/sha1.h @@ -0,0 +1,16 @@ +#ifndef SHA1_H +#define SHA1_H + +#include + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + uint8_t buffer[64]; +} SHA1_CTX; + +void SHA1_Init(SHA1_CTX* context); +void SHA1_Update(SHA1_CTX* context, const uint8_t* data, uint32_t len); +void SHA1_Final(uint8_t digest[20], SHA1_CTX* context); + +#endif diff --git a/libfriidump/xbox_ref/unlock.c b/libfriidump/xbox_ref/unlock.c new file mode 100644 index 0000000..1861ffd --- /dev/null +++ b/libfriidump/xbox_ref/unlock.c @@ -0,0 +1,356 @@ +#include "unlock.h" +#include "scsi_structs.h" +#include "rc4.h" +#include "sha1.h" +#include "utils.h" +#include "xbox_ref_log.h" + +#include +#include +#include +#include +#include + +#define printf xbox_ref_printf + +static uint8_t scsibuffer[2001]; +static TRC4Context rc4key; + +// Helper function to log hex dumps cleanly to the console and to the untruncated log file. +static void LogHexDump(const char* prefix, const uint8_t* data, DWORD length) { + size_t console_limit = 0; + if (!data || length == 0) return; + if (prefix && strcmp(prefix, "Received Data [Step 3: Read DVD Struct 0xAD]") == 0) + console_limit = 32; + xbox_ref_log_hexdump(prefix, data, (size_t)length, console_limit); +} + +void UnlockDrive(HANDLE AHandle) { + printf("Structure size: %zu\n", sizeof(SCSI_PASS_THROUGH_DIRECT)); + SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER SPTDW; + DWORD Size = sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER); + DWORD Returned; + BOOL Status; + int i, k, l; + int chalpos[11]; + uint8_t restable[261]; + uint8_t hash[0x2C]; + uint8_t shadigest[20]; + uint8_t shastring[20]; + + // --- STEP 1: INITIAL READ CAPACITY --- + ZeroMemory(&SPTDW, sizeof(SPTDW)); + ZeroMemory(scsibuffer, sizeof(scsibuffer)); + SPTDW.Spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + SPTDW.Spt.CdbLength = 10; + SPTDW.Spt.SenseInfoLength = 32; + SPTDW.Spt.DataIn = SCSI_IOCTL_DATA_IN; + SPTDW.Spt.DataTransferLength = 8; + SPTDW.Spt.TimeOutValue = 120; + SPTDW.Spt.DataBuffer = scsibuffer; + SPTDW.Spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf); + SPTDW.Spt.Cdb[0] = 0x25; + + LogHexDump("Sending CDB [Step 1: Init Read Cap]", SPTDW.Spt.Cdb, SPTDW.Spt.CdbLength); + Status = DeviceIoControl(AHandle, IOCTL_SCSI_PASS_THROUGH_DIRECT, &SPTDW, Size, &SPTDW, Size, &Returned, NULL); + + if (Status) { + LogHexDump("Received Data [Step 1: Init Read Cap]", scsibuffer, SPTDW.Spt.DataTransferLength); + unsigned int maxLBA = (scsibuffer[0] << 24) | + (scsibuffer[1] << 16) | + (scsibuffer[2] << 8) | + scsibuffer[3]; + printf("Initial Max LBA: %u\n", maxLBA); + PrintFormattedCapacity(scsibuffer); + } else { + printf("Fatal error: Initial capacity read failed (Error %lu).\n", GetLastError()); + return; + } + + // --- STEP 2: MODE SENSE (Page 0x3E) --- + memset(&SPTDW, 0, sizeof(SPTDW)); + memset(scsibuffer, 0, 2000); + SPTDW.Spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + SPTDW.Spt.CdbLength = 10; + SPTDW.Spt.DataIn = SCSI_IOCTL_DATA_IN; + SPTDW.Spt.DataTransferLength = 28; + SPTDW.Spt.TimeOutValue = 120; + SPTDW.Spt.DataBuffer = scsibuffer; + SPTDW.Spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf); + SPTDW.Spt.Cdb[0] = 0x5A; + SPTDW.Spt.Cdb[2] = 0x3E; + SPTDW.Spt.Cdb[8] = 0x1C; + + LogHexDump("Sending CDB [Step 2: Mode Sense]", SPTDW.Spt.Cdb, SPTDW.Spt.CdbLength); + Status = DeviceIoControl(AHandle, IOCTL_SCSI_PASS_THROUGH_DIRECT, &SPTDW, Size, &SPTDW, Size, &Returned, NULL); + if (Status) LogHexDump("Received Data [Step 2: Mode Sense]", scsibuffer, SPTDW.Spt.DataTransferLength); + + // --- STEP 3: READ DVD STRUCTURE (The Challenge Table) --- + memset(&SPTDW, 0, sizeof(SPTDW)); + memset(scsibuffer, 0, 2000); + SPTDW.Spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + SPTDW.Spt.CdbLength = 12; + SPTDW.Spt.DataIn = SCSI_IOCTL_DATA_IN; + SPTDW.Spt.DataTransferLength = 0x664; + SPTDW.Spt.TimeOutValue = 120; + SPTDW.Spt.DataBuffer = scsibuffer; + SPTDW.Spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf); + SPTDW.Spt.Cdb[0] = 0xAD; + SPTDW.Spt.Cdb[2] = 0xFF; SPTDW.Spt.Cdb[3] = 0x02; SPTDW.Spt.Cdb[4] = 0xFD; + SPTDW.Spt.Cdb[5] = 0xFF; SPTDW.Spt.Cdb[6] = 0xFE; SPTDW.Spt.Cdb[8] = 0x06; + SPTDW.Spt.Cdb[9] = 0x64; SPTDW.Spt.Cdb[11] = 0xC0; + + LogHexDump("Sending CDB [Step 3: Read DVD Struct 0xAD]", SPTDW.Spt.Cdb, SPTDW.Spt.CdbLength); + Status = DeviceIoControl(AHandle, IOCTL_SCSI_PASS_THROUGH_DIRECT, &SPTDW, Size, &SPTDW, Size, &Returned, NULL); + if (Status) LogHexDump("Received Data [Step 3: Read DVD Struct 0xAD]", scsibuffer, SPTDW.Spt.DataTransferLength); + + // Fallback Logic for Hitachi 4241N Drives + if (!Status || scsibuffer[772] != 1 || scsibuffer[773] == 0) { + printf("8050L Table Invalid. Attempting Hitachi 0xFD Fallback...\n"); + memset(&SPTDW, 0, sizeof(SPTDW)); + memset(scsibuffer, 0, 2000); + SPTDW.Spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + SPTDW.Spt.CdbLength = 12; + SPTDW.Spt.DataIn = SCSI_IOCTL_DATA_IN; + SPTDW.Spt.DataTransferLength = 0x664; + SPTDW.Spt.TimeOutValue = 120; + SPTDW.Spt.DataBuffer = scsibuffer; + SPTDW.Spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf); + + SPTDW.Spt.Cdb[0] = 0xFD; + SPTDW.Spt.Cdb[1] = 0x01; + SPTDW.Spt.Cdb[8] = 0x06; + SPTDW.Spt.Cdb[9] = 0x64; + + LogHexDump("Sending CDB [Step 3: Read DVD Struct Fallback 0xFD]", SPTDW.Spt.Cdb, SPTDW.Spt.CdbLength); + Status = DeviceIoControl(AHandle, IOCTL_SCSI_PASS_THROUGH_DIRECT, &SPTDW, Size, &SPTDW, Size, &Returned, NULL); + if (Status) LogHexDump("Received Data [Step 3: Read DVD Struct Fallback 0xFD]", scsibuffer, SPTDW.Spt.DataTransferLength); + + if (!Status || scsibuffer[772] != 1) { + printf("Fatal error: Invalid host challenge table (Both methods failed).\n"); + return; + } + printf("Success: Acquired Hitachi 0xFD Handshake Table!\n"); + } + + // --- CRYPTO: SHA1 & RC4 --- + for (i = 0; i < 0x2C; i++) hash[i] = scsibuffer[0x4A3 + i]; + { + SHA1_CTX ctx; + SHA1_Init(&ctx); + SHA1_Update(&ctx, hash, 0x2C); + SHA1_Final(shadigest, &ctx); + } + memcpy(shastring, shadigest, 7); + RC4Init(&rc4key, shastring, 7); + for (i = 0; i <= 260; i++) restable[i] = scsibuffer[774 + i]; + rc4Decrypt(restable, restable, 0xFD, &rc4key); + + k = 0; + for (l = 0; l <= 23; l++) { + if (restable[l * 11] == 1) { + chalpos[k] = l; + k++; + } + } + + if (k < 2) { + printf("Fatal error: Not enough usable challenge entries.\n"); + return; + } + + // --- STEP 4: MODE SELECT (First Challenge) --- + memset(&SPTDW, 0, sizeof(SPTDW)); + memset(scsibuffer, 0, 2000); + SPTDW.Spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + SPTDW.Spt.CdbLength = 10; + SPTDW.Spt.DataIn = SCSI_IOCTL_DATA_OUT; + SPTDW.Spt.DataTransferLength = 28; + SPTDW.Spt.TimeOutValue = 120; + SPTDW.Spt.DataBuffer = scsibuffer; + SPTDW.Spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf); + SPTDW.Spt.Cdb[0] = 0x55; SPTDW.Spt.Cdb[8] = 0x1C; + scsibuffer[1] = 0x1A; scsibuffer[8] = 0x3E; + scsibuffer[9] = 0x12; + scsibuffer[11] = 0x01; scsibuffer[13] = 0xD1; scsibuffer[14] = 0x01; + memcpy(&scsibuffer[15], &restable[1 + chalpos[k - 2] * 11], 5); + + LogHexDump("Sending CDB [Step 4: Mode Select Chal 1]", SPTDW.Spt.Cdb, SPTDW.Spt.CdbLength); + LogHexDump("Sending Data [Step 4: Mode Select Chal 1]", scsibuffer, SPTDW.Spt.DataTransferLength); + DeviceIoControl(AHandle, IOCTL_SCSI_PASS_THROUGH_DIRECT, &SPTDW, Size, &SPTDW, Size, &Returned, NULL); + + // --- STEP 5: MODE SENSE (Verify First Challenge) --- + memset(&SPTDW, 0, sizeof(SPTDW)); + memset(scsibuffer, 0, 2000); + SPTDW.Spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + SPTDW.Spt.CdbLength = 10; + SPTDW.Spt.DataIn = SCSI_IOCTL_DATA_IN; + SPTDW.Spt.DataTransferLength = 28; + SPTDW.Spt.TimeOutValue = 120; + SPTDW.Spt.DataBuffer = scsibuffer; + SPTDW.Spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf); + SPTDW.Spt.Cdb[0] = 0x5A; SPTDW.Spt.Cdb[2] = 0x3E; SPTDW.Spt.Cdb[8] = 0x1C; + + LogHexDump("Sending CDB [Step 5: Mode Sense Verify 1]", SPTDW.Spt.Cdb, SPTDW.Spt.CdbLength); + Status = DeviceIoControl(AHandle, IOCTL_SCSI_PASS_THROUGH_DIRECT, &SPTDW, Size, &SPTDW, Size, &Returned, NULL); + if (Status) LogHexDump("Received Data [Step 5: Mode Sense Verify 1]", scsibuffer, SPTDW.Spt.DataTransferLength); + + // --- STEP 6: MODE SELECT (Second Challenge) --- + memset(&SPTDW, 0, sizeof(SPTDW)); + memset(scsibuffer, 0, 2000); + SPTDW.Spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + SPTDW.Spt.CdbLength = 10; + SPTDW.Spt.DataIn = SCSI_IOCTL_DATA_OUT; + SPTDW.Spt.DataTransferLength = 28; + SPTDW.Spt.TimeOutValue = 120; + SPTDW.Spt.DataBuffer = scsibuffer; + SPTDW.Spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf); + SPTDW.Spt.Cdb[0] = 0x55; SPTDW.Spt.Cdb[8] = 0x1C; + scsibuffer[1] = 0x1A; scsibuffer[8] = 0x3E; + scsibuffer[9] = 0x12; + scsibuffer[12] = 0x01; // Marker for second challenge + memcpy(&scsibuffer[15], &restable[1 + chalpos[k - 1] * 11], 5); + + LogHexDump("Sending CDB [Step 6: Mode Select Chal 2]", SPTDW.Spt.Cdb, SPTDW.Spt.CdbLength); + LogHexDump("Sending Data [Step 6: Mode Select Chal 2]", scsibuffer, SPTDW.Spt.DataTransferLength); + DeviceIoControl(AHandle, IOCTL_SCSI_PASS_THROUGH_DIRECT, &SPTDW, Size, &SPTDW, Size, &Returned, NULL); + + // --- STEP 7: MODE SENSE (Verify Second Challenge) --- + memset(&SPTDW, 0, sizeof(SPTDW)); + memset(scsibuffer, 0, 2000); + SPTDW.Spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + SPTDW.Spt.CdbLength = 10; + SPTDW.Spt.DataIn = SCSI_IOCTL_DATA_IN; + SPTDW.Spt.DataTransferLength = 28; + SPTDW.Spt.TimeOutValue = 120; + SPTDW.Spt.DataBuffer = scsibuffer; + SPTDW.Spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf); + SPTDW.Spt.Cdb[0] = 0x5A; SPTDW.Spt.Cdb[2] = 0x3E; SPTDW.Spt.Cdb[8] = 0x1C; + + LogHexDump("Sending CDB [Step 7: Mode Sense Verify 2]", SPTDW.Spt.Cdb, SPTDW.Spt.CdbLength); + Status = DeviceIoControl(AHandle, IOCTL_SCSI_PASS_THROUGH_DIRECT, &SPTDW, Size, &SPTDW, Size, &Returned, NULL); + if (Status) LogHexDump("Received Data [Step 7: Mode Sense Verify 2]", scsibuffer, SPTDW.Spt.DataTransferLength); + + // --- STEP 8: FINAL MODE SELECT (Partition 1 Unlock) --- + memset(&SPTDW, 0, sizeof(SPTDW)); + memset(scsibuffer, 0, 2000); + SPTDW.Spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + SPTDW.Spt.CdbLength = 10; + SPTDW.Spt.DataIn = SCSI_IOCTL_DATA_OUT; + SPTDW.Spt.DataTransferLength = 28; + SPTDW.Spt.TimeOutValue = 120; + SPTDW.Spt.DataBuffer = scsibuffer; + SPTDW.Spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf); + SPTDW.Spt.Cdb[0] = 0x55; SPTDW.Spt.Cdb[8] = 0x1C; + scsibuffer[1] = 0x1A; scsibuffer[8] = 0x3E; + scsibuffer[9] = 0x12; + scsibuffer[10] = 0x01; scsibuffer[11] = 0x01; scsibuffer[12] = 0x01; + scsibuffer[13] = 0xD1; scsibuffer[14] = 0x01; + memcpy(&scsibuffer[15], &restable[1 + chalpos[k - 1] * 11], 5); + + LogHexDump("Sending CDB [Step 8: Mode Select Part1 Unlock]", SPTDW.Spt.Cdb, SPTDW.Spt.CdbLength); + LogHexDump("Sending Data [Step 8: Mode Select Part1 Unlock]", scsibuffer, SPTDW.Spt.DataTransferLength); + + Status = DeviceIoControl(AHandle, IOCTL_SCSI_PASS_THROUGH_DIRECT, &SPTDW, Size, &SPTDW, Size, &Returned, NULL); + if (Status) { + printf("Partition 1 Unlocked Successfully.\n"); + } + + // --- STEP 9: ENABLE STICKY DESCRAMBLING (Mode 0x31) --- + printf("Enabling Sticky Descrambling (Mode 0x31)...\n"); + memset(&SPTDW, 0, sizeof(SPTDW)); + memset(scsibuffer, 0, 2000); + scsibuffer[4] = 0x31; scsibuffer[5] = 0x06; scsibuffer[6] = 0x01; + SPTDW.Spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + SPTDW.Spt.CdbLength = 6; + SPTDW.Spt.DataIn = SCSI_IOCTL_DATA_OUT; + SPTDW.Spt.DataTransferLength = 12; + SPTDW.Spt.TimeOutValue = 10; + SPTDW.Spt.DataBuffer = scsibuffer; + SPTDW.Spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf); + SPTDW.Spt.Cdb[0] = 0x15; + SPTDW.Spt.Cdb[1] = 0x11; + SPTDW.Spt.Cdb[4] = 12; + + LogHexDump("Sending CDB [Step 9: Sticky Descrambling]", SPTDW.Spt.Cdb, SPTDW.Spt.CdbLength); + LogHexDump("Sending Data [Step 9: Sticky Descrambling]", scsibuffer, SPTDW.Spt.DataTransferLength); + DeviceIoControl(AHandle, IOCTL_SCSI_PASS_THROUGH_DIRECT, &SPTDW, Size, &SPTDW, Size, &Returned, NULL); + + // --- STEP 10: FINAL CAPACITY VERIFICATION --- + memset(&SPTDW, 0, sizeof(SPTDW)); + memset(scsibuffer, 0, 2000); + SPTDW.Spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + SPTDW.Spt.CdbLength = 10; + SPTDW.Spt.DataIn = SCSI_IOCTL_DATA_IN; + SPTDW.Spt.DataTransferLength = 8; + SPTDW.Spt.TimeOutValue = 120; + SPTDW.Spt.DataBuffer = scsibuffer; + SPTDW.Spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf); + SPTDW.Spt.Cdb[0] = 0x25; + + LogHexDump("Sending CDB [Step 10: Final Verify]", SPTDW.Spt.Cdb, SPTDW.Spt.CdbLength); + Status = DeviceIoControl(AHandle, IOCTL_SCSI_PASS_THROUGH_DIRECT, &SPTDW, Size, &SPTDW, Size, &Returned, NULL); + + if (Status) { + LogHexDump("Received Data [Step 10: Final Verify]", scsibuffer, SPTDW.Spt.DataTransferLength); + PrintFormattedCapacity(scsibuffer); + } +} + +// AI CODE +BOOL AuthenticateXboxMedia(HANDLE hDevice) { + SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sptdw = {0}; + DWORD bytesReturned; + + // We need a small buffer because 0xAD usually expects to return data, + // even if our custom handler just uses it as a trigger. + uint8_t dummy_buffer[8]; + + sptdw.Spt.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + sptdw.Spt.CdbLength = 12; // 0xAD is a 12-byte command + sptdw.Spt.DataIn = SCSI_IOCTL_DATA_IN; + sptdw.Spt.TimeOutValue = 10; + sptdw.Spt.DataTransferLength = sizeof(dummy_buffer); + sptdw.Spt.DataBuffer = dummy_buffer; + sptdw.Spt.SenseInfoOffset = offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, SenseBuf); + + // --- The 8050L / 4241N Hook Command --- + sptdw.Spt.Cdb[0] = 0xAD; // READ DVD STRUCTURE + sptdw.Spt.Cdb[2] = 0xFF; + sptdw.Spt.Cdb[3] = 0x02; + sptdw.Spt.Cdb[4] = 0xFD; // MAGIC BYTE: Our Gatekeeper at 0x90002670 listens for this + sptdw.Spt.Cdb[11] = 0xC0; + + return DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptdw, sizeof(sptdw), &sptdw, sizeof(sptdw), &bytesReturned, NULL); +} + +// ORIGINAL CODE +BOOL ORIGINAL_AuthenticateXboxMedia(HANDLE hDevice) { + SCSI_PASS_THROUGH_DIRECT sptd = {0}; + DWORD bytesReturned; + unsigned char cdb[10] = {0}; + + sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + sptd.CdbLength = 10; + sptd.DataIn = SCSI_IOCTL_DATA_IN; + sptd.TimeOutValue = 5; + sptd.DataTransferLength = 0; // No data transfer for this command + sptd.DataBuffer = NULL; + + // THE MAGIC LG GDR-8163B UNLOCK COMMAND + sptd.Cdb[0] = 0xFF; // Vendor Specific + sptd.Cdb[1] = 0x08; // Sub-command: Set Xbox Mode + sptd.Cdb[2] = 0x01; // Enable + // Note: Some firmwares use 0x01, some use 0xFF. Try 0x01 first. + + return DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL); +} + + +BOOL KickXboxMediaAuth(HANDLE hDevice) +{ + if (AuthenticateXboxMedia(hDevice)) + return TRUE; + + return ORIGINAL_AuthenticateXboxMedia(hDevice); +} diff --git a/libfriidump/xbox_ref/unlock.h b/libfriidump/xbox_ref/unlock.h new file mode 100644 index 0000000..7beb7dd --- /dev/null +++ b/libfriidump/xbox_ref/unlock.h @@ -0,0 +1,11 @@ +#ifndef UNLOCK_H +#define UNLOCK_H + +#include + +void UnlockDrive(HANDLE h); +BOOL AuthenticateXboxMedia(HANDLE hDevice); +BOOL ORIGINAL_AuthenticateXboxMedia(HANDLE hDevice); +BOOL KickXboxMediaAuth(HANDLE hDevice); + +#endif diff --git a/libfriidump/xbox_ref/utils.c b/libfriidump/xbox_ref/utils.c new file mode 100644 index 0000000..2e24078 --- /dev/null +++ b/libfriidump/xbox_ref/utils.c @@ -0,0 +1,3207 @@ +#include "utils.h" +#include "scsi_structs.h" +#include "xbe_cert.h" +#include "unlock.h" +#include "xbox_ref_log.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#pragma comment(lib, "advapi32.lib") + +#ifndef PROV_RSA_AES +#define PROV_RSA_AES 24 +#endif +#ifndef ALG_SID_SHA_256 +#define ALG_SID_SHA_256 12 +#endif +#ifndef CALG_SHA_256 +#define CALG_SHA_256 (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256) +#endif + +#define printf xbox_ref_printf + +#define GDR_8163B OL23 + +HANDLE OpenDrive(char driveLetter) +{ + char devicePath[16]; + snprintf(devicePath, sizeof(devicePath), "\\\\.\\%c:", driveLetter); + + HANDLE hDevice = CreateFileA(devicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + return hDevice; +} + +void CloseDrive(HANDLE hDevice) +{ + if (hDevice && hDevice != INVALID_HANDLE_VALUE) + CloseHandle(hDevice); +} + +int IsDiscPresent(HANDLE hDevice) +{ + DWORD bytesReturned; + return DeviceIoControl(hDevice, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, NULL, 0, &bytesReturned, NULL); +} + +void ControlTray(HANDLE hDevice, BOOL eject) +{ + SCSI_PASS_THROUGH_DIRECT sptd; + DWORD returned; + memset(&sptd, 0, sizeof(sptd)); + + sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + sptd.CdbLength = 6; + sptd.TimeOutValue = 10; + sptd.Cdb[0] = 0x1B; // START STOP UNIT + + if (eject) + { + printf("Software Ejecting tray...\n"); + sptd.Cdb[4] = 0x02; // Power Action: Eject + } + else + { + printf("Software Closing tray...\n"); + sptd.Cdb[4] = 0x03; // Power Action: Load + } + if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &returned, NULL)) + { + printf("Tray %s successful.\n", eject ? "eject" : "close"); + } + else + { + DWORD err = GetLastError(); + printf("Failed to %s tray. Error: %lu\n", eject ? "eject" : "close", err); + + if (err == ERROR_ACCESS_DENIED) + { + printf("Hint: Ensure no other program is locking the drive.\n"); + } + } +} + +BOOL TestUnitReady(HANDLE hDevice) +{ + SCSI_PASS_THROUGH_DIRECT sptd; + DWORD returned; + memset(&sptd, 0, sizeof(sptd)); + + sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + sptd.CdbLength = 6; + sptd.TimeOutValue = 10; + sptd.DataTransferLength = 0; + sptd.DataBuffer = NULL; + + // TEST UNIT READY. This is our practical poll for "ready/spun up". + // Many drives do not expose a literal spindle-state bit to normal host software; + // after STOP UNIT, TEST UNIT READY should fail until the unit is ready again. + sptd.Cdb[0] = 0x00; + + if (!DeviceIoControl(hDevice, + IOCTL_SCSI_PASS_THROUGH_DIRECT, + &sptd, + sizeof(sptd), + &sptd, + sizeof(sptd), + &returned, + NULL)) + { + return FALSE; + } + + return (sptd.ScsiStatus == 0); +} + +BOOL StartDriveUnit(HANDLE hDevice) +{ + SCSI_PASS_THROUGH_DIRECT sptd; + DWORD returned; + memset(&sptd, 0, sizeof(sptd)); + + sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + sptd.CdbLength = 6; + sptd.TimeOutValue = 30; + sptd.DataTransferLength = 0; + sptd.DataBuffer = NULL; + + // START STOP UNIT, START=1, LOEJ=0. + // This requests spin-up/start without ejecting/loading the tray. + sptd.Cdb[0] = 0x1B; + sptd.Cdb[4] = 0x01; + + printf("Sending SCSI START UNIT / spin-up command...\n"); + + if (DeviceIoControl(hDevice, + IOCTL_SCSI_PASS_THROUGH_DIRECT, + &sptd, + sizeof(sptd), + &sptd, + sizeof(sptd), + &returned, + NULL)) + { + printf("SCSI START UNIT / spin-up command accepted.\n"); + return TRUE; + } + + { + DWORD err = GetLastError(); + printf("[WARN] SCSI START UNIT / spin-up failed. Error: %lu\n", err); + return FALSE; + } +} + +BOOL EnsureDriveReady(HANDLE hDevice, DWORD timeoutMs) +{ + DWORD startTick = GetTickCount(); + BOOL startIssued = FALSE; + + printf("Polling drive readiness with TEST UNIT READY...\n"); + + for (;;) + { + if (TestUnitReady(hDevice)) + { + printf("Drive reports ready.\n"); + return TRUE; + } + + if (!startIssued) + { + printf("Drive is not ready/spun up yet; requesting START UNIT.\n"); + StartDriveUnit(hDevice); + startIssued = TRUE; + } + + if ((GetTickCount() - startTick) >= timeoutMs) + { + printf("[WARN] Drive did not report ready within %lu ms.\n", (unsigned long)timeoutMs); + printf(" Continuing may fail if the unit is still spun down or still reading lead-in.\n"); + return FALSE; + } + + Sleep(1000); + } +} + +BOOL StopDriveUnit(HANDLE hDevice) +{ + SCSI_PASS_THROUGH_DIRECT sptd; + DWORD returned; + memset(&sptd, 0, sizeof(sptd)); + + sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + sptd.CdbLength = 6; + sptd.TimeOutValue = 30; + sptd.DataTransferLength = 0; + sptd.DataBuffer = NULL; + + // START STOP UNIT, START=0, LOEJ=0. + // This requests a normal stop/spin-down without ejecting or loading the tray. + sptd.Cdb[0] = 0x1B; + sptd.Cdb[4] = 0x00; + + printf("Sending SCSI STOP UNIT / spin-down command...\n"); + + if (DeviceIoControl(hDevice, + IOCTL_SCSI_PASS_THROUGH_DIRECT, + &sptd, + sizeof(sptd), + &sptd, + sizeof(sptd), + &returned, + NULL)) + { + printf("SCSI STOP UNIT / spin-down successful.\n"); + return TRUE; + } + + { + DWORD err = GetLastError(); + printf("[WARN] SCSI STOP UNIT / spin-down failed. Error: %lu\n", err); + printf(" Dump output has already been finalized; this only affects drive spin state.\n"); + return FALSE; + } +} + +void AutomateTrayCycle(HANDLE hDevice) +{ + ControlTray(hDevice, TRUE); + Sleep(3000); // Give the tray time to fully extend + + // --- CLOSE --- + ControlTray(hDevice, FALSE); + printf("Waiting for disc spin-up/readiness after tray close...\n"); + if (EnsureDriveReady(hDevice, 45000)) + { + // Small settle period after readiness so the drive can finish lead-in/media-change bookkeeping. + Sleep(1500); + } + else + { + // Preserve the old conservative behavior if TEST UNIT READY polling never succeeds. + printf("[WARN] Falling back to fixed 10s post-close settle delay.\n"); + Sleep(10000); + } +} + +BOOL SetDriveSpeedMax(HANDLE hDevice) +{ + SCSI_PASS_THROUGH_DIRECT sptd = {0}; + sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + sptd.PathId = 0; + sptd.TargetId = 1; + sptd.Lun = 0; + sptd.CdbLength = 12; // 12-byte CDB for 0xBB + sptd.DataIn = SCSI_IOCTL_DATA_OUT; + sptd.TimeOutValue = 10; + sptd.DataBuffer = NULL; + sptd.DataTransferLength = 0; + + // CDB 0xBB: [0] Opcode, [2-3] Read Speed, [4-5] Write Speed + sptd.Cdb[0] = 0xBB; + sptd.Cdb[2] = 0xFF; // MSB + sptd.Cdb[3] = 0xFF; // LSB + sptd.Cdb[4] = 0xFF; // MSB + sptd.Cdb[5] = 0xFF; // LSB + + DWORD returned; + return DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, + &sptd, sizeof(sptd), &sptd, sizeof(sptd), + &returned, NULL); +} + +void ForceMediaRefresh(HANDLE hDevice) +{ + DWORD bytesReturned; + + // Lock the volume so Windows stops background polling + DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL); + + // Force the storage stack to re-read the Partition Table/Capacity + // without sending an Eject command to the hardware. + if (DeviceIoControl(hDevice, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, &bytesReturned, NULL)) + { + printf("Windows Partition Stack refreshed silently.\n"); + } + + // Explicitly dismount to kill the "Video DVD" file system driver (UDFS/ISO9660) + DeviceIoControl(hDevice, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL); + Sleep(1000); +} + +void HexDump(unsigned char *buffer, uint32_t size) +{ + for (uint32_t i = 0; i < size; i++) + { + if (i % 16 == 0) + printf("\n%04X: ", i); + printf("%02X ", buffer[i]); + } + printf("\n"); +} + +void outputdata(const uint8_t *buf, uint32_t lines) +{ + for (uint32_t j = 0; j < lines; j++) + { + for (uint32_t k = 0; k < 16; k++) + { + uint32_t idx = j * 16 + k; + if (k == 8) + printf("- "); + printf("%02X ", buf[idx]); + } + printf("\n"); + } +} + +uint8_t chksum8(const unsigned char *buff, size_t len) { + unsigned int sum = 0; + for (sum = 0; len != 0; len--) + sum += *(buff++); + return (uint8_t)sum; +} + +void FormatElapsedTime(DWORD dwMilliseconds, char *outStr) +{ + uint32_t totalSeconds = dwMilliseconds / 1000; + uint32_t hours = totalSeconds / 3600; + uint32_t minutes = (totalSeconds % 3600) / 60; + uint32_t seconds = totalSeconds % 60; + + sprintf(outStr, "%02u:%02u:%02u", hours, minutes, seconds); +} + +void PrintFormattedCapacity(unsigned char *scsibuffer) +{ + // The first 4 bytes are the Last Logical Block Address (Big Endian) + uint32_t maxLBA = (scsibuffer[0] << 24) | (scsibuffer[1] << 16) | + (scsibuffer[2] << 8) | scsibuffer[3]; + + // The next 4 bytes are the Block Length (Big Endian) + uint32_t blockLen = (scsibuffer[4] << 24) | (scsibuffer[5] << 16) | + (scsibuffer[6] << 8) | scsibuffer[7]; + + // Total bytes = (MaxLBA + 1) * BlockLen + // Use double for the math to avoid 32-bit integer overflow + double totalBytes = (double)(maxLBA + 1) * blockLen; + double totalGB = totalBytes / (1024.0 * 1024.0 * 1024.0); + + printf("--------------------------------------------\n"); + printf("Drive Capacity Details:\n"); + printf(" Total Sectors: %u\n", maxLBA + 1); + printf(" Sector Size: %u bytes\n", blockLen); + printf(" Total Size: %.2f GB\n", totalGB); + printf("--------------------------------------------\n"); +} + +void ListOpticalDrives() +{ + DWORD drives = GetLogicalDrives(); + char rootPath[] = "A:\\"; + char devicePath[] = "\\\\.\\A:"; + BYTE buffer[1024]; + + printf("%-5s %-12s %-18s %-15s %s\n", "ID", "Vendor", "Model", "Volume Label", "Status"); + printf("-------------------------------------------------------------------------------\n"); + + uint8_t driveCount = 0; + for (int i = 0; i < 26; i++) + { + if (drives & (1 << i)) + { + rootPath[0] = 'A' + i; + + if (GetDriveTypeA(rootPath) == DRIVE_CDROM) + { + devicePath[4] = 'A' + i; + + // 1. Get Hardware Info (Vendor/Model) + char vendorStr[16] = "Generic"; + char productStr[21] = "Unknown"; + + HANDLE h = CreateFileA(devicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + + if (h != INVALID_HANDLE_VALUE) + { + STORAGE_PROPERTY_QUERY query = {0}; + query.PropertyId = StorageDeviceProperty; + query.QueryType = PropertyStandardQuery; + DWORD bytes; + + if (DeviceIoControl(h, IOCTL_STORAGE_QUERY_PROPERTY, &query, sizeof(query), + buffer, sizeof(buffer), &bytes, NULL)) + { + PSTORAGE_DEVICE_DESCRIPTOR desc = (PSTORAGE_DEVICE_DESCRIPTOR)buffer; + if (desc->VendorIdOffset) + strcpy(vendorStr, (char *)(buffer + desc->VendorIdOffset)); + if (desc->ProductIdOffset) + strcpy(productStr, (char *)(buffer + desc->ProductIdOffset)); + } + CloseHandle(h); + } + + // 2. Get Volume Info (Disc Label) + char volumeName[MAX_PATH + 1] = {0}; + char statusStr[20] = "No Disc"; + + if (GetVolumeInformationA(rootPath, volumeName, sizeof(volumeName), + NULL, NULL, NULL, NULL, 0)) + { + if (strlen(volumeName) == 0) + strcpy(volumeName, "[No Label]"); + strcpy(statusStr, "Ready"); + } + + printf(" %c: %-12.12s %-18.18s %-15.15s %s\n", + rootPath[0], vendorStr, productStr, volumeName, statusStr); + driveCount++; + } + } + } + if (driveCount == 0) + printf("No optical drives found.\n"); + printf("-------------------------------------------------------------------------------\n"); + printf("Total Optical Drives Found: %u\n", driveCount); +} + +uint32_t GetTotalSectors(HANDLE hDevice) +{ + typedef struct _SCSI_PASS_THROUGH_WITH_BUFFERS + { + SCSI_PASS_THROUGH spt; + unsigned char ucDataBuf[8]; // Buffer for the 8-byte READ CAPACITY result + } SCSI_PASS_THROUGH_WITH_BUFFERS; + + SCSI_PASS_THROUGH_WITH_BUFFERS sptwb = {0}; + + sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH); + sptwb.spt.CdbLength = 10; // READ CAPACITY (10) is a 10-byte command + sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN; + sptwb.spt.DataTransferLength = 8; + sptwb.spt.TimeOutValue = 2; // 2 second timeout + sptwb.spt.DataBufferOffset = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucDataBuf); + + // CDB 0x25 = READ CAPACITY (10) + sptwb.spt.Cdb[0] = 0x25; + + DWORD bytesReturned; + if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH, + &sptwb, sizeof(sptwb), + &sptwb, sizeof(sptwb), + &bytesReturned, NULL)) + { + + // Extract Max LBA (Big Endian) from the first 4 bytes + uint32_t maxLBA = (sptwb.ucDataBuf[0] << 24) | + (sptwb.ucDataBuf[1] << 16) | + (sptwb.ucDataBuf[2] << 8) | + sptwb.ucDataBuf[3]; + + return (maxLBA + 1); + } + + return 0; // Return 0 on failure +} + +uint32_t GetXboxPhysicalSectors(HANDLE hDevice) +{ + SCSI_PASS_THROUGH_DIRECT sptd = {0}; + unsigned char buffer[2048] = {0}; + DWORD bytesReturned; + + sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + sptd.CdbLength = 12; + sptd.DataIn = SCSI_IOCTL_DATA_IN; + sptd.DataTransferLength = 2048; + sptd.TimeOutValue = 10; + sptd.DataBuffer = buffer; + + // READ DVD STRUCTURE (0xAD) + sptd.Cdb[0] = 0xAD; + sptd.Cdb[7] = 0x00; // Format: Physical Format Information + sptd.Cdb[8] = 0x08; // Allocation Length (MSB) + sptd.Cdb[9] = 0x00; // Allocation Length (LSB) + + if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL)) + { + // Bytes 13-15 of the PFI contain the End LBA of the data area + uint32_t endLba = (buffer[13] << 16) | (buffer[14] << 8) | buffer[15]; + + // For Xbox discs, we add 1 to the End LBA to get the total count + // and add the 32 sectors of lead-in padding we manually create. + return endLba + 1; + } + + // Fallback for Dual Layer if command fails + return 3431264; +} + +// Forces Windows to re-evaluate the drive without ejecting the tray +void RefreshVolume(HANDLE hDevice) +{ + DWORD bytesReturned; + printf("Refreshing Volume Stack (Quiet Mode)...\n"); + // Only update properties; do NOT dismount as it resets the GDR-8163B state. + DeviceIoControl(hDevice, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, &bytesReturned, NULL); + Sleep(1000); // Essential for the firmware to re-index after the OS check +} + +void ListDirectoryRecursive(HANDLE hDevice, uint32_t lba, uint32_t size, int level) +{ + if (size == 0 || level > 10) + return; // Prevent infinite recursion + + uint32_t sectorsToRead = (size + 2047) / 2048; + unsigned char *dirBuffer = (unsigned char *)VirtualAlloc(NULL, sectorsToRead * 2048, MEM_COMMIT, PAGE_READWRITE); + if (!dirBuffer) + return; + + if (ScsiReadSectors(hDevice, lba, (uint16_t)sectorsToRead, dirBuffer)) + { + uint32_t offset = 0; + while (offset < size) + { + XDFS_DIR_ENTRY *entry = (XDFS_DIR_ENTRY *)&dirBuffer[offset]; + + // --- SANITY CHECK 1: End of Table --- + // If FileNameLength is 0 or 0xFF, we've hit the padding/end of the list. + if (entry->FileNameLength == 0 || entry->FileNameLength == 0xFF) + break; + + // --- SANITY CHECK 2: Buffer Overflow --- + // Ensure the entry doesn't claim to exist past our allocated buffer. + if (offset + 14 + entry->FileNameLength > size) + break; + + // --- SANITY CHECK 3: Character Validation --- + // If the first character isn't a printable ASCII, it's a glitch entry. + if (entry->FileName[0] < 32 || entry->FileName[0] > 126) + break; + + // Indentation + for (int i = 0; i < level; i++) + printf(" "); + + // Branch Visual + if (entry->Attributes & 0x10) + { + printf("[DIR] "); + } + else + { + printf(" |-- "); + } + + // Print Filename safely + for (int i = 0; i < entry->FileNameLength; i++) + { + char c = entry->FileName[i]; + if (c >= 32 && c <= 126) + printf("%c", c); + else + printf("?"); // Replace glitches with a placeholder + } + + if (!(entry->Attributes & 0x10)) + { + printf(" (%u bytes)", entry->FileSize); + } + printf("\n"); + + // RECURSION: Only dive if it's a valid directory LBA + if ((entry->Attributes & 0x10) && entry->StartLBA > 0x100) + { + ListDirectoryRecursive(hDevice, entry->StartLBA, entry->FileSize, level + 1); + } + + // Move to next entry (4-byte alignment) + uint32_t nextOffset = (14 + entry->FileNameLength + 3) & ~3; + + // If the calculation gives us 0, we're stuck in an infinite loop; break. + if (nextOffset == 0) + break; + offset += nextOffset; + } + } + + VirtualFree(dirBuffer, 0, MEM_RELEASE); +} + +void ReadXboxGameDir(HANDLE hDevice) +{ + // Single buffer for the Volume Descriptor read + unsigned char *sectorBuffer = (unsigned char *)VirtualAlloc(NULL, 2048, MEM_COMMIT, PAGE_READWRITE); + if (!sectorBuffer) + return; + + // Read XDFS Volume Descriptor at Sector 0x20 + if (!ScsiReadSectors(hDevice, 0x20, 1, sectorBuffer)) + { + printf("Error: Could not read XDFS Volume Descriptor.\n"); + VirtualFree(sectorBuffer, 0, MEM_RELEASE); + return; + } + + // Map the descriptor and extract root location/size + XDFS_VOLUME_DESCRIPTOR *vol = (XDFS_VOLUME_DESCRIPTOR *)sectorBuffer; + uint32_t rootLba = vol->RootLBA; + uint32_t rootSize = vol->RootSize; + + // We no longer need this buffer once we have the Root LBA/Size + VirtualFree(sectorBuffer, 0, MEM_RELEASE); + + // Draw the recursive tree + printf("\n--- XDFS FILE SYSTEM TREE ---\n"); + + if (rootLba > 0) + { + ListDirectoryRecursive(hDevice, rootLba, rootSize, 0); + } + else + { + printf("Error: Invalid Root LBA.\n"); + } + + printf("------------------------------\n"); +} + +void SanitizeFilename(char *filename) +{ + if (!filename || filename[0] == '\0') + return; + + int readIndex = 0; + int writeIndex = 0; + int lastWasSpace = 1; // Using 1 for true to trim leading spaces + + while (filename[readIndex] != '\0') + { + unsigned char c = (unsigned char)filename[readIndex]; + + // Whitelist: Only allow Letters (isalnum) and Spaces + // This strips ! ' ? : " / \ | * < > and non-printable characters + if (isalnum(c) || c == ' ') + { + + // Collapse Multiple Spaces + if (c == ' ') + { + if (!lastWasSpace) + { + filename[writeIndex++] = ' '; + lastWasSpace = 1; + } + } + else + { + // It's a letter or number, write it normally + filename[writeIndex++] = c; + lastWasSpace = 0; + } + } + readIndex++; + } + + // Null-terminate the new shorter string + filename[writeIndex] = '\0'; + + // Remove trailing space if one exists + if (writeIndex > 0 && filename[writeIndex - 1] == ' ') + { + filename[writeIndex - 1] = '\0'; + } +} + +BOOL ScsiReadSectors(HANDLE hDevice, uint32_t lba, uint16_t count, unsigned char *buffer) +{ + SCSI_PASS_THROUGH_DIRECT sptd = {0}; + DWORD bytesReturned; + + sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + sptd.CdbLength = 10; + sptd.DataIn = 1; + sptd.DataTransferLength = count * 2048; + sptd.TimeOutValue = 30; + sptd.DataBuffer = buffer; + + sptd.Cdb[0] = 0x28; // READ(10) + sptd.Cdb[2] = (lba >> 24) & 0xFF; + sptd.Cdb[3] = (lba >> 16) & 0xFF; + sptd.Cdb[4] = (lba >> 8) & 0xFF; + sptd.Cdb[5] = lba & 0xFF; + sptd.Cdb[7] = (unsigned char)((count >> 8) & 0xFF); + sptd.Cdb[8] = (unsigned char)(count & 0xFF); + + return DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL); +} + +XboxGameInfo GetXboxGameInfo(HANDLE hDevice) +{ + XboxGameInfo info; + memset(&info, 0, sizeof(XboxGameInfo)); + unsigned char sectorBuffer[2048]; + + // Get Volume Descriptor (LBA 0x20) + if (!ScsiReadSectors(hDevice, 0x20, 1, sectorBuffer)) + return info; + + // Verify XDFS Magic "XGD2" or "MICROSOFT*XBOX*MEDIA" + if (memcmp(sectorBuffer, "MICROSOFT", 9) != 0) + { + return (XboxGameInfo){.TitleName = "Not_XDFS"}; + } + + uint32_t rootLba = *(uint32_t *)§orBuffer[0x14]; + uint32_t rootSize = *(uint32_t *)§orBuffer[0x18]; + + uint32_t rawVolumeSize = *(uint32_t *)§orBuffer[0x1C]; + // Assign to the 64-bit member (cast to ensure no weird sign extension) + info.TotalSizeBytes = (uint64_t)rawVolumeSize; + + // Read Root Directory (Scanning multiple sectors for default.xbe) + uint32_t sectorsToRead = (rootSize + 2047) / 2048; + for (uint32_t s = 0; s < sectorsToRead; s++) + { + unsigned char dirBuffer[2048]; + if (!ScsiReadSectors(hDevice, rootLba + s, 1, dirBuffer)) + break; + + uint32_t offset = 0; + while (offset < 2030) + { + uint16_t leftNode = *(uint16_t *)&dirBuffer[offset]; + if (leftNode == 0xFFFF) + break; // End of directory + + uint32_t startLba = *(uint32_t *)&dirBuffer[offset + 4]; + uint8_t nameLen = dirBuffer[offset + 13]; + char *name = (char *)&dirBuffer[offset + 14]; + + if (nameLen == 0) + break; + + // Match "default.xbe" + if (nameLen == 11 && _strnicmp(name, "default.xbe", 11) == 0) + { + unsigned char xbeHeader[2048]; + if (ScsiReadSectors(hDevice, startLba, 1, xbeHeader)) + { + + if (*(uint32_t *)xbeHeader != 0x48454258) + break; // "XBEH" + + // 4. Locate Certificate + uint32_t baseVA = *(uint32_t *)&xbeHeader[0x104]; + uint32_t certVA = *(uint32_t *)&xbeHeader[0x118]; + uint32_t fileOffset = certVA - baseVA; + + // Certificate might be in a later sector of the XBE file + uint32_t certSector = startLba + (fileOffset / 2048); + uint32_t innerOff = (fileOffset % 2048); + + unsigned char certBuffer[2048]; + if (ScsiReadSectors(hDevice, certSector, 1, certBuffer)) + { + + // Populate the Struct from the Certificate + info.TitleId = *(uint32_t *)&certBuffer[innerOff + 0x008]; + info.AllowedMedia = *(uint32_t *)&certBuffer[innerOff + 0x09C]; + info.GameRegion = *(uint32_t *)&certBuffer[innerOff + 0x0A0]; + info.GameRatings = *(uint32_t *)&certBuffer[innerOff + 0x0A4]; + info.DiscNumber = *(uint32_t *)&certBuffer[innerOff + 0x0A8]; + info.Version = *(uint32_t *)&certBuffer[innerOff + 0x0AC]; + + // Convert UTF-16 Title Name (at 0x00C) to ASCII + for (int i = 0; i < 40; i++) + { + char c = certBuffer[innerOff + 0x00C + (i * 2)]; + if (c == 0) + break; + info.TitleName[i] = c; + } + + info.Success = 1; + return info; + } + } + } + offset += (14 + nameLen + 3) & ~3; // XDFS Alignment + } + } + + return info; // Success will be 0 if we never found default.xbe or failed to read the cert +} + +void DisplayXboxGameInfo(XboxGameInfo info) +{ + if (!info.Success) + { + printf("Error: Could not retrieve Xbox game information.\n"); + return; + } + + printf("\n--- Xbox Game Information ---\n"); + printf("Title Name: %s\n", info.TitleName); + printf("Title ID: 0x%08X\n", info.TitleId); + printf("Version: %u\n", info.Version); + printf("Disc Number: %u\n", info.DiscNumber); + + // Decode Regions + printf("Regions: "); + if (info.GameRegion & XB_REGION_MANUFACTURING) + printf("[Manufacturing] "); + if (info.GameRegion & XB_REGION_US_CANADA) + printf("North America "); + if (info.GameRegion & XB_REGION_JAPAN) + printf("Japan "); + if (info.GameRegion & XB_REGION_EUROPE_AU_NZ) + printf("Europe/AU "); + if (info.GameRegion & XB_REGION_REST_OF_WORLD) + printf("Rest of World "); + + // If everything is set (0x7FFFFFFF or 0xFFFFFFFF), it's Region Free + if ((info.GameRegion & 0x7FFFFFFF) == 0x7FFFFFFF) + { + printf("(Region Free)"); + } + else if (info.GameRegion == 0) + { + printf("None (Locked)"); + } + printf("\n"); + + // Decode Media Types + printf("Allowed Media: "); + if (info.AllowedMedia & XB_MEDIA_HARD_DRIVE) + printf("HDD "); + if (info.AllowedMedia & XB_MEDIA_DVD_X2) + printf("Xbox_DVD "); + if (info.AllowedMedia & XB_MEDIA_DVD_5_RO) + printf("DVD-5 "); + if (info.AllowedMedia & XB_MEDIA_DVD_9_RO) + printf("DVD-9 "); + if (info.AllowedMedia & XB_MEDIA_CD) + printf("CD "); + if (info.AllowedMedia & XB_MEDIA_DONGLE) + printf("Memory_Unit "); + printf("\n"); + + DisplayXboxRating(info.GameRatings); + + printf("-----------------------------\n"); +} + +void DisplayXboxRating(uint32_t ratings) +{ + // ESRB (North America) - Byte 0 (Bits 0-7) + uint8_t esrb = (uint8_t)(ratings & 0xFF); + if (esrb != 0 && esrb != 0xFF) + { + printf("ESRB Rating: "); + switch (esrb) + { + case 0x01: + printf("EC (Early Childhood)\n"); + break; + case 0x02: + printf("E (Everyone)\n"); + break; + case 0x03: + printf("K-A (Kids to Adults)\n"); + break; + case 0x04: + printf("T (Teen)\n"); + break; + case 0x05: + printf("M (Mature)\n"); + break; + case 0x06: + printf("AO (Adults Only)\n"); + break; + default: + printf("RP (Rating Pending/Unrated)\n"); + break; + } + } + + // PEGI (Europe) - Byte 1 (Bits 8-15) + uint8_t pegi = (uint8_t)((ratings >> 8) & 0xFF); + if (pegi != 0 && pegi != 0xFF) + { + printf("PEGI Rating: "); + switch (pegi) + { + case 0x00: + printf("3+\n"); + break; + case 0x01: + printf("7+\n"); + break; + case 0x02: + printf("12+\n"); + break; + case 0x03: + printf("16+\n"); + break; + case 0x04: + printf("18+\n"); + break; + default: + printf("Other (0x%02X)\n", pegi); + break; + } + } + + // CERO (Japan) - Byte 2 (Bits 16-23) + uint8_t cero = (uint8_t)((ratings >> 16) & 0xFF); + if (cero != 0 && cero != 0xFF) + { + printf("CERO Rating: "); + switch (cero) + { + case 0x00: + printf("A (All Ages)\n"); + break; + case 0x01: + printf("B (12+)\n"); + break; + case 0x02: + printf("C (15+)\n"); + break; + case 0x03: + printf("D (17+)\n"); + break; + case 0x04: + printf("Z (18+ Only)\n"); + break; + default: + printf("Other (0x%02X)\n", cero); + break; + } + } + + if ((ratings & 0x00FFFFFF) == 0) + { + printf("Rating: None/Unrated\n"); + } +} + +// --- POST-DUMP VERIFICATION --- +void PrintGamePartitionHash(const char *filename) +{ + FILE *f = fopen(filename, "rb"); + __int64 fileBytes; + uint32_t startLba = START_LBA_MAGIC; + unsigned long long bytesRemaining = 0ULL; + unsigned long long totalBytesToHash = 0ULL; + unsigned long long bytesDone = 0ULL; + unsigned char *vBuf; + size_t read; + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + BYTE rgbHash[20]; + DWORD cbHash = 20; + char finalHash[41] = {0}; + DWORD startTick = 0; + DWORD lastPrintTick = 0; + DWORD nowTick = 0; + DWORD elapsedMs = 0; + DWORD etaMs = 0; + char timeStr[12] = {0}; + char etaStr[12] = {0}; + + if (!f) + return; + + if (_fseeki64(f, 0, SEEK_END) != 0) + { + fclose(f); + return; + } + fileBytes = _ftelli64(f); + if (fileBytes < 0) + { + fclose(f); + return; + } + + if ((unsigned long long)fileBytes == (unsigned long long)XGD1_FULL_REDUMP_SECTORS * 2048ULL) + { + startLba = XGD1_GAME_OUTPUT_START_LBA; + bytesRemaining = (unsigned long long)REDUMP_SECTORS * 2048ULL; + printf("[HASH] Calculating Game/XISO-region SHA-1 (Redump-style output LBA %u, %u sectors)...\n", + startLba, REDUMP_SECTORS); + } + else + { + startLba = START_LBA_MAGIC; + bytesRemaining = ((unsigned long long)fileBytes > (unsigned long long)startLba * 2048ULL) + ? ((unsigned long long)fileBytes - (unsigned long long)startLba * 2048ULL) + : 0ULL; + printf("[HASH] Calculating Game-Partition-Only SHA-1 (legacy contiguous output LBA %u)...\n", startLba); + } + + totalBytesToHash = bytesRemaining; + if (bytesRemaining == 0) + { + fclose(f); + return; + } + + if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + { + fclose(f); + return; + } + if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) + { + CryptReleaseContext(hProv, 0); + fclose(f); + return; + } + + _fseeki64(f, (__int64)startLba * 2048, SEEK_SET); + + vBuf = (unsigned char *)malloc(1024 * 1024); // 1MB buffer + if (!vBuf) + { + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + fclose(f); + return; + } + + startTick = GetTickCount(); + lastPrintTick = startTick; + + while (bytesRemaining > 0 && (read = fread(vBuf, 1, (bytesRemaining > 1024ULL * 1024ULL) ? 1024 * 1024 : (size_t)bytesRemaining, f)) > 0) + { + CryptHashData(hHash, vBuf, (DWORD)read, 0); + bytesRemaining -= read; + bytesDone += (unsigned long long)read; + + nowTick = GetTickCount(); + if (bytesDone >= totalBytesToHash || (nowTick - lastPrintTick) >= 1000) + { + double percent = ((double)bytesDone / (double)totalBytesToHash) * 100.0; + double mbDone = (double)bytesDone / (1024.0 * 1024.0); + double speed = 0.0; + elapsedMs = nowTick - startTick; + if (elapsedMs > 0) + speed = mbDone / ((double)elapsedMs / 1000.0); + etaMs = (bytesDone > 0 && elapsedMs > 0) + ? (DWORD)(((double)elapsedMs / (double)bytesDone) * (double)(totalBytesToHash - bytesDone)) + : 0; + FormatElapsedTime(elapsedMs, timeStr); + FormatElapsedTime(etaMs, etaStr); + xbox_ref_console_printf("\r[HASH] Game/XISO-region: %5.1f%% | %.1f MB | Speed: %.2f MB/s | Time: %s | ETA: %s ", + percent, + mbDone, + speed, + timeStr, + etaStr); + fflush(stdout); + lastPrintTick = nowTick; + } + } + + CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0); + for (int i = 0; i < 20; i++) + sprintf(&finalHash[i * 2], "%02x", rgbHash[i]); + + elapsedMs = GetTickCount() - startTick; + FormatElapsedTime(elapsedMs, timeStr); + if (totalBytesToHash > 0) + xbox_ref_console_printf("\r[HASH] Game/XISO-region: 100.0%% | %.1f MB | Time: %s \n", + (double)totalBytesToHash / (1024.0 * 1024.0), + timeStr); + printf("Game/XISO-region SHA-1: %s\n", finalHash); + printf("[OK] Game/XISO-region SHA-1 complete in %s.\n", timeStr); + + free(vBuf); + CryptDestroyHash(hHash); + CryptReleaseContext(hProv, 0); + fclose(f); +} + +void GetMediaID(HANDLE hDevice, char *outMediaId) +{ + SCSI_PASS_THROUGH_DIRECT sptd = {0}; + unsigned char buffer[2048] = {0}; + DWORD bytesReturned; + + sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + sptd.CdbLength = 12; + sptd.DataIn = SCSI_IOCTL_DATA_IN; + sptd.DataTransferLength = 2048; + sptd.TimeOutValue = 5; + sptd.DataBuffer = buffer; + + sptd.Cdb[0] = 0xAD; // READ DVD STRUCTURE + sptd.Cdb[7] = 0x04; // Format: Disc Manufacturing Information (DMI) + sptd.Cdb[8] = 0x08; + sptd.Cdb[9] = 0x00; + + if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL)) + { + // The Media ID is typically 32 bytes starting at offset 4 in the DMI + // Offset 8 is where "MS11..." usually starts on Xbox discs + // We'll grab 16 characters to be safe + int writePos = 0; + for (int i = 8; i < 24; i++) + { + // Only add alphanumeric characters to keep the filename clean + if (isalnum(buffer[i])) + { + outMediaId[writePos++] = buffer[i]; + } + } + outMediaId[writePos] = '\0'; // Null terminate the string + } + else + { + strcpy(outMediaId, "UNKNOWN_ID"); + } +} + +void GetDiscMetadata(HANDLE hDevice, uint32_t *totalSectors, bool *isDualLayer, XboxGameInfo *gameInfo) +{ + SCSI_PASS_THROUGH_DIRECT sptd = {0}; + unsigned char buffer[2048] = {0}; + DWORD bytesReturned; + + sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + sptd.CdbLength = 12; + sptd.DataIn = SCSI_IOCTL_DATA_IN; + sptd.DataTransferLength = 2048; + sptd.TimeOutValue = 10; + sptd.DataBuffer = buffer; + + sptd.Cdb[0] = 0xAD; // READ DVD STRUCTURE + sptd.Cdb[7] = 0x00; // Physical Format Information + sptd.Cdb[8] = 0x08; // 2048 bytes + sptd.Cdb[9] = 0x00; + + if (DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd), &bytesReturned, NULL)) + { + + // Byte 12: bits 5-6 (Number of Layers) + // 0x20 = 00100000 (Two layers), 0x00 = 00000000 (One layer) + unsigned char layerInfo = (buffer[12] >> 5) & 0x03; + *isDualLayer = (layerInfo > 0); + + // Bytes 13-15: End LBA + uint32_t endLba = (buffer[13] << 16) | (buffer[14] << 8) | buffer[15]; + + // XBOX SANITY CHECK + // If the drive reports a value much larger than a standard Xbox Dual Layer (3.4M sectors) + // it means the drive is reporting the raw DVD-9 limit. We must cap it. + if (endLba > ((uint32_t)REDUMP_SECTORS - 1)) + { + printf("[!] Drive reported Raw DVD-9 geometry. Normalizing to Xbox Dual Layer...\n"); + if (gameInfo->TotalSizeBytes < LAYER_BREAK) + { + printf("[!] Info: Game partition size is smaller than expected for a Dual Layer disc.\n"); + } + *totalSectors = REDUMP_SECTORS; + *isDualLayer = true; + } + else + { + printf("[!] Drive reported Raw DVD-5 geometry. Normalizing to Xbox Single Layer...\n"); + *totalSectors = endLba + 1; + *isDualLayer = (endLba > (uint32_t)LAYER_THRESHOLD); // Standard threshold for SL vs DL + } + } + else + { + // Fallback safety + *isDualLayer = true; + *totalSectors = REDUMP_SECTORS; + printf("Media Info: Could not read PFI. Defaulting to Dual Layer.\n"); + } +} + +uint32_t GetGamePartitionSize(HANDLE hDevice, uint32_t totalDiscSectors, XDFS_VOLUME_DESCRIPTOR *vol) +{ + uint32_t sectorsToRead = 0; + if (totalDiscSectors > 3300000) + { + // DUAL LAYER (XGD2) Calculation: + // LBA 1,913,920 is the physical end of the usable XDFS area on retail DVD-9s. + uint32_t xgd2EndLba = 1913920; + sectorsToRead = xgd2EndLba - vol->RootLBA; + } + else + { + // SINGLE LAYER (XGD1 / Homebrew) Calculation: + // On single layer discs, the header's VolumeSize is trustworthy. + sectorsToRead = vol->VolumeSize / 2048; + } + return sectorsToRead; +} + +static BOOL ProbeXboxVolumeAt(HANDLE hDevice, uint32_t lba) +{ + unsigned char sector[2048] = {0}; + return ScsiReadSectors(hDevice, lba, 1, sector) && memcmp(sector, "MICROSOFT", 9) == 0; +} + +static uint32_t DetectXboxVolumeStart(HANDLE hDevice) +{ + if (ProbeXboxVolumeAt(hDevice, START_LBA_MAGIC)) + return START_LBA_MAGIC; + + if (ProbeXboxVolumeAt(hDevice, 0x20)) + return 0x20; + + return 0xFFFFFFFFu; +} + +static void RecoveryKick(HANDLE hDevice, BOOL authRecovery) +{ + unsigned char dummy[2048] = {0}; + + if (authRecovery) + KickXboxMediaAuth(hDevice); + + SetDriveSpeedMax(hDevice); + + for (int i = 0; i < 10; i++) + { + ScsiReadSectors(hDevice, 0, 1, dummy); + Sleep(50); + } +} + + +static void GetDirectoryForPath(const char *filename, char *outDir, DWORD outDirSize) +{ + DWORD len; + char fullPath[MAX_PATH]; + char *filePart = NULL; + + if (!outDir || outDirSize == 0) + return; + + outDir[0] = '\0'; + + if (!filename || filename[0] == '\0') + { + GetCurrentDirectoryA(outDirSize, outDir); + return; + } + + len = GetFullPathNameA(filename, (DWORD)sizeof(fullPath), fullPath, &filePart); + if (len == 0 || len >= sizeof(fullPath)) + { + GetCurrentDirectoryA(outDirSize, outDir); + return; + } + + if (filePart && filePart > fullPath) + { + size_t dirLen = (size_t)(filePart - fullPath); + if (dirLen >= outDirSize) + dirLen = outDirSize - 1; + memcpy(outDir, fullPath, dirLen); + outDir[dirLen] = '\0'; + } + else + { + GetCurrentDirectoryA(outDirSize, outDir); + } +} + +static BOOL FileExistsAndSize(const char *filename, unsigned long long *sizeOut) +{ + WIN32_FILE_ATTRIBUTE_DATA fad; + + if (sizeOut) + *sizeOut = 0ULL; + + if (!filename || !GetFileAttributesExA(filename, GetFileExInfoStandard, &fad)) + return FALSE; + + if (fad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + return FALSE; + + if (sizeOut) + { + ULARGE_INTEGER size; + size.HighPart = fad.nFileSizeHigh; + size.LowPart = fad.nFileSizeLow; + *sizeOut = size.QuadPart; + } + + return TRUE; +} + +static BOOL CheckOutputFreeSpace(const char *filename, unsigned long long expectedBytes, const char *label) +{ + char dir[MAX_PATH]; + ULARGE_INTEGER freeToCaller; + ULARGE_INTEGER totalBytes; + ULARGE_INTEGER totalFree; + unsigned long long existingBytes = 0ULL; + unsigned long long effectiveFree; + unsigned long long margin; + + if (expectedBytes == 0) + return TRUE; + + GetDirectoryForPath(filename, dir, (DWORD)sizeof(dir)); + + if (!GetDiskFreeSpaceExA(dir[0] ? dir : NULL, &freeToCaller, &totalBytes, &totalFree)) + { + DWORD err = GetLastError(); + printf("\n[WARN] Could not check free space for output path '%s' (GetDiskFreeSpaceEx error %lu).\n", filename, err); + printf(" Continuing, but write errors will still be caught during the dump.\n"); + return TRUE; + } + + FileExistsAndSize(filename, &existingBytes); + + // If overwriting an existing output on the same volume, its current bytes can be + // reclaimed by fopen(..., "wb"). This avoids rejecting a valid replacement run. + effectiveFree = freeToCaller.QuadPart + existingBytes; + + // Add a small safety margin for sidecar/profile files and filesystem metadata. + // Keep this modest so overwriting an existing full raw ISO still passes. + margin = 64ULL * 1024ULL * 1024ULL; + + printf("[%s] Free-space preflight for '%s':\n", label ? label : "OUTPUT", filename); + printf(" Required output bytes: %llu\n", expectedBytes); + printf(" Safety margin: %llu\n", margin); + printf(" Free to caller: %llu\n", (unsigned long long)freeToCaller.QuadPart); + if (existingBytes) + printf(" Existing output bytes: %llu (counted as reclaimable overwrite space)\n", existingBytes); + printf(" Effective available: %llu\n", effectiveFree); + + if (effectiveFree < expectedBytes + margin) + { + printf("\n[FATAL] Not enough free disk space for %s.\n", label ? label : "output"); + printf(" Required + margin: %llu bytes\n", expectedBytes + margin); + printf(" Effective free: %llu bytes\n", effectiveFree); + printf(" Free space can change while dumping; free extra space and rerun.\n"); + return FALSE; + } + + return TRUE; +} + +static BOOL WriteOutputBytes(FILE *outFile, + const void *data, + size_t bytesToWrite, + const char *phaseName, + uint32_t sourceLba, + uint32_t outputLba) +{ + size_t written; + + if (!outFile || !data || bytesToWrite == 0) + return bytesToWrite == 0; + + written = fwrite(data, 1, bytesToWrite, outFile); + if (written != bytesToWrite) + { + printf("\n[FATAL] Output write failed during %s range.\n", phaseName ? phaseName : "dump"); + printf(" Source LBA: %u | Output LBA: %u\n", sourceLba, outputLba); + printf(" Requested: %llu bytes\n", (unsigned long long)bytesToWrite); + printf(" Written: %llu bytes\n", (unsigned long long)written); + if (errno) + printf(" errno: %d (%s)\n", errno, strerror(errno)); + printf(" This commonly means another program consumed free space after preflight,\n"); + printf(" the destination volume filled up, or the destination became unavailable.\n"); + return FALSE; + } + + if (ferror(outFile)) + { + printf("\n[FATAL] Output stream error during %s range at output LBA %u.\n", + phaseName ? phaseName : "dump", outputLba); + if (errno) + printf(" errno: %d (%s)\n", errno, strerror(errno)); + return FALSE; + } + + return TRUE; +} + +static BOOL FlushAndCommitOutput(FILE *outFile, const char *label) +{ + int fd; + + if (!outFile) + return FALSE; + + if (fflush(outFile) != 0) + { + printf("\n[FATAL] fflush failed for %s output.\n", label ? label : "dump"); + if (errno) + printf(" errno: %d (%s)\n", errno, strerror(errno)); + return FALSE; + } + + fd = _fileno(outFile); + if (fd >= 0 && _commit(fd) != 0) + { + printf("\n[FATAL] _commit failed for %s output. The OS may not have accepted all buffered data.\n", + label ? label : "dump"); + if (errno) + printf(" errno: %d (%s)\n", errno, strerror(errno)); + return FALSE; + } + + return TRUE; +} + +static BOOL DumpSectorRangeWithRetry(HANDLE hDevice, + FILE *outFile, + HCRYPTHASH hHash, + uint32_t sourceStartLba, + uint32_t sectorsToRead, + uint32_t outputBaseLba, + const char *phaseName, + BOOL authRecovery) +{ + const uint32_t batchSize = 32; + unsigned char *buffer = NULL; + uint32_t sectorsDone = 0; + DWORD startTime = GetTickCount(); + char timeStr[12] = {0}; + char etaStr[12] = {0}; + + if (sectorsToRead == 0) + return TRUE; + + buffer = (unsigned char *)VirtualAlloc(NULL, batchSize * 2048, MEM_COMMIT, PAGE_READWRITE); + if (!buffer) + { + printf("\n[FATAL] Could not allocate dump buffer for %s range.\n", phaseName); + return FALSE; + } + + printf("\n--- STARTING %s RANGE ---\n", phaseName); + printf("Source LBA: %u | Output LBA: %u | Sectors: %u\n", sourceStartLba, outputBaseLba, sectorsToRead); + + RecoveryKick(hDevice, authRecovery); + + while (sectorsDone < sectorsToRead) + { + uint32_t currentLba = sourceStartLba + sectorsDone; + const char *currentLayerStr = "L0"; + uint32_t burstLimit = batchSize; + uint32_t toRead; + BOOL success = FALSE; + + if ((outputBaseLba + sectorsDone) >= LAYER_BREAK) + currentLayerStr = "L1"; + + // Layer-boundary safety: do not let one READ(10) span the Xbox layer break. + if (sectorsDone == 0 || (outputBaseLba + sectorsDone) == LAYER_BREAK) + { + burstLimit = 1; + } + else if ((outputBaseLba + sectorsDone) < LAYER_BREAK && + (outputBaseLba + sectorsDone + batchSize) > LAYER_BREAK) + { + burstLimit = LAYER_BREAK - (outputBaseLba + sectorsDone); + } + + toRead = (sectorsToRead - sectorsDone > burstLimit) ? burstLimit : (sectorsToRead - sectorsDone); + + if ((outputBaseLba + sectorsDone) == LAYER_BREAK) + { + printf("\n[INFO] Redump/XGD1 output layer break at LBA %u. Reducing burst size to 1 sector for safety.\n", LAYER_BREAK); + } + + for (int retry = 0; retry <= MAX_RETRIES; retry++) + { + if (ScsiReadSectors(hDevice, currentLba, (uint16_t)toRead, buffer)) + { + if (!WriteOutputBytes(outFile, buffer, (size_t)toRead * 2048U, phaseName, currentLba, outputBaseLba + sectorsDone)) + { + VirtualFree(buffer, 0, MEM_RELEASE); + return FALSE; + } + CryptHashData(hHash, buffer, toRead * 2048, 0); + sectorsDone += toRead; + success = TRUE; + break; + } + + RecoveryKick(hDevice, authRecovery); + Sleep(500); + } + + if (!success) + { + unsigned char *smallBuffer = NULL; + printf("\n[!] Batch failed in %s range at source LBA %u. Recovering sectors individually.\n", phaseName, currentLba); + + smallBuffer = (unsigned char *)VirtualAlloc(NULL, 2048, MEM_COMMIT, PAGE_READWRITE); + if (!smallBuffer) + { + printf("\n[FATAL] Could not allocate single-sector recovery buffer.\n"); + VirtualFree(buffer, 0, MEM_RELEASE); + return FALSE; + } + + for (uint32_t i = 0; i < toRead; i++) + { + BOOL sectorSuccess = FALSE; + + for (int sRetry = 0; sRetry <= MAX_RETRIES; sRetry++) + { + if (ScsiReadSectors(hDevice, currentLba + i, 1, smallBuffer)) + { + if (!WriteOutputBytes(outFile, smallBuffer, 2048, phaseName, currentLba + i, outputBaseLba + sectorsDone)) + { + VirtualFree(smallBuffer, 0, MEM_RELEASE); + VirtualFree(buffer, 0, MEM_RELEASE); + return FALSE; + } + CryptHashData(hHash, smallBuffer, 2048, 0); + sectorsDone++; + sectorSuccess = TRUE; + break; + } + + RecoveryKick(hDevice, authRecovery); + Sleep(500); + } + + if (!sectorSuccess) + { + printf("\n[FATAL] Unrecoverable %s sector at source LBA %u. Output hash is invalid.\n", phaseName, currentLba + i); + VirtualFree(smallBuffer, 0, MEM_RELEASE); + VirtualFree(buffer, 0, MEM_RELEASE); + return FALSE; + } + } + + VirtualFree(smallBuffer, 0, MEM_RELEASE); + } + + if (sectorsDone > 0) + { + DWORD elapsedMs = GetTickCount() - startTime; + uint32_t sectorsLeft = sectorsToRead - sectorsDone; + DWORD etaMs = (DWORD)(((double)elapsedMs / sectorsDone) * sectorsLeft); + float percent = ((float)sectorsDone / sectorsToRead) * 100.0f; + float mbDone = (float)(outputBaseLba + sectorsDone) * 2048 / 1024 / 1024; + float speed = (elapsedMs > 0) ? (((float)sectorsDone * 2048 / 1024 / 1024) / (elapsedMs / 1000.0f)) : 0.0f; + + FormatElapsedTime(elapsedMs, timeStr); + FormatElapsedTime(etaMs, etaStr); + + xbox_ref_console_printf("\rProgress [%s/%s]: %3.1f%% | %.1f MB | Speed: %.2f MB/s | sourceLba: %u | outputLba: %u | Time: %s | ETA: %s ", + phaseName, currentLayerStr, percent, mbDone, speed, currentLba, outputBaseLba + sectorsDone, timeStr, etaStr); + fflush(stdout); + } + } + + printf("\n[OK] Completed %s range.\n", phaseName); + VirtualFree(buffer, 0, MEM_RELEASE); + return TRUE; +} + + + + +static BOOL WriteZeroSectorsOutput(FILE *outFile, + HCRYPTHASH hHash, + uint32_t sectorCount, + uint32_t outputBaseLba, + const char *phaseName) +{ + const uint32_t batchSectors = 32; + unsigned char *zeroBuffer = NULL; + uint32_t sectorsDone = 0; + DWORD startTime = GetTickCount(); + char timeStr[12] = {0}; + char etaStr[12] = {0}; + + if (sectorCount == 0) + return TRUE; + + zeroBuffer = (unsigned char *)VirtualAlloc(NULL, batchSectors * 2048, MEM_COMMIT, PAGE_READWRITE); + if (!zeroBuffer) + { + printf("\n[FATAL] Could not allocate zero-fill buffer for %s range.\n", phaseName ? phaseName : "padding"); + return FALSE; + } + memset(zeroBuffer, 0, batchSectors * 2048); + + printf("\n--- STARTING %s RANGE ---\n", phaseName ? phaseName : "ZERO"); + printf("Output LBA: %u | Sectors: %u | Fill: synthetic zero-fill (not drive-captured)\n", outputBaseLba, sectorCount); + + while (sectorsDone < sectorCount) + { + uint32_t toWrite = (sectorCount - sectorsDone > batchSectors) ? batchSectors : (sectorCount - sectorsDone); + uint32_t outputLba = outputBaseLba + sectorsDone; + + if (!WriteOutputBytes(outFile, zeroBuffer, (size_t)toWrite * 2048U, phaseName ? phaseName : "ZERO", 0, outputLba)) + { + VirtualFree(zeroBuffer, 0, MEM_RELEASE); + return FALSE; + } + if (hHash) + CryptHashData(hHash, zeroBuffer, toWrite * 2048, 0); + + sectorsDone += toWrite; + + if (sectorsDone > 0) + { + DWORD elapsedMs = GetTickCount() - startTime; + uint32_t sectorsLeft = sectorCount - sectorsDone; + DWORD etaMs = (DWORD)(((double)elapsedMs / sectorsDone) * sectorsLeft); + float percent = ((float)sectorsDone / sectorCount) * 100.0f; + float mbDone = (float)(outputBaseLba + sectorsDone) * 2048 / 1024 / 1024; + float speed = (elapsedMs > 0) ? (((float)sectorsDone * 2048 / 1024 / 1024) / (elapsedMs / 1000.0f)) : 0.0f; + + FormatElapsedTime(elapsedMs, timeStr); + FormatElapsedTime(etaMs, etaStr); + xbox_ref_console_printf("\rProgress [%s]: %3.1f%% | %.1f MB | Speed: %.2f MB/s | outputLba: %u | Time: %s | ETA: %s ", + phaseName ? phaseName : "ZERO", percent, mbDone, speed, outputBaseLba + sectorsDone, timeStr, etaStr); + fflush(stdout); + } + } + + printf("\n[OK] Completed %s range.\n", phaseName ? phaseName : "ZERO"); + VirtualFree(zeroBuffer, 0, MEM_RELEASE); + return TRUE; +} + +static BOOL ReadSectorsToMemory(HANDLE hDevice, + uint32_t sourceStartLba, + uint32_t sectorCount, + unsigned char *outBuffer, + const char *phaseName) +{ + const uint32_t batchSectors = 32; + uint32_t sectorsDone = 0; + + if (sectorCount == 0) + return TRUE; + if (!outBuffer) + return FALSE; + + printf("[RAW] Capturing %s to memory: source LBA %u..%u (%u sectors).\n", + phaseName ? phaseName : "sector range", + sourceStartLba, + sourceStartLba + sectorCount - 1, + sectorCount); + + while (sectorsDone < sectorCount) + { + uint32_t toRead = (sectorCount - sectorsDone > batchSectors) ? batchSectors : (sectorCount - sectorsDone); + uint32_t currentLba = sourceStartLba + sectorsDone; + BOOL success = FALSE; + + for (int retry = 0; retry <= MAX_RETRIES; retry++) + { + if (ScsiReadSectors(hDevice, currentLba, (uint16_t)toRead, outBuffer + ((size_t)sectorsDone * 2048U))) + { + sectorsDone += toRead; + success = TRUE; + break; + } + Sleep(500); + } + + if (!success) + { + printf("\n[FATAL] Could not capture %s at source LBA %u.\n", phaseName ? phaseName : "sector range", currentLba); + return FALSE; + } + } + + return TRUE; +} + +static BOOL WriteMemorySectorsOutput(FILE *outFile, + HCRYPTHASH hHash, + const unsigned char *buffer, + uint32_t sectorCount, + uint32_t outputBaseLba, + const char *phaseName) +{ + const uint32_t batchSectors = 32; + uint32_t sectorsDone = 0; + + if (sectorCount == 0) + return TRUE; + if (!buffer) + return FALSE; + + printf("\n--- STARTING %s RANGE ---\n", phaseName ? phaseName : "MEMORY"); + printf("Output LBA: %u | Sectors: %u | Source: captured memory\n", outputBaseLba, sectorCount); + + while (sectorsDone < sectorCount) + { + uint32_t toWrite = (sectorCount - sectorsDone > batchSectors) ? batchSectors : (sectorCount - sectorsDone); + uint32_t outputLba = outputBaseLba + sectorsDone; + const unsigned char *src = buffer + ((size_t)sectorsDone * 2048U); + + if (!WriteOutputBytes(outFile, src, (size_t)toWrite * 2048U, phaseName ? phaseName : "MEMORY", 0, outputLba)) + return FALSE; + if (hHash) + CryptHashData(hHash, src, toWrite * 2048, 0); + + sectorsDone += toWrite; + } + + printf("[OK] Completed %s range.\n", phaseName ? phaseName : "MEMORY"); + return TRUE; +} + +typedef struct _XboxDvdSidecarCapture +{ + BOOL hasLockedCapacity; + BOOL hasLockedModeSense3E; + BOOL hasUnlockedCapacity; + BOOL hasUnlockedModeSense3E; + BOOL hasAdC0; + BOOL hasPfi; + BOOL hasDmi; + + unsigned char lockedCapacity[8]; + unsigned char lockedModeSense3E[28]; + unsigned char unlockedCapacity[8]; + unsigned char unlockedModeSense3E[28]; + + unsigned char adC0[0x664]; + unsigned char pfi[2048]; + unsigned char dmi[2048]; +} XboxDvdSidecarCapture; + +static void StripKnownExtension(const char *filename, char *outBase, size_t outBaseSize) +{ + char *dot; + char *slash1; + char *slash2; + char *slash; + + if (!outBase || outBaseSize == 0) + return; + + outBase[0] = '\0'; + if (!filename) + return; + + strncpy(outBase, filename, outBaseSize - 1); + outBase[outBaseSize - 1] = '\0'; + + dot = strrchr(outBase, '.'); + slash1 = strrchr(outBase, '\\'); + slash2 = strrchr(outBase, '/'); + slash = slash1 > slash2 ? slash1 : slash2; + + if (dot && (!slash || dot > slash)) + *dot = '\0'; +} + +static void MakeSidecarPath(const char *filename, const char *suffix, char *outPath, size_t outPathSize) +{ + char base[MAX_PATH]; + + if (!outPath || outPathSize == 0) + return; + + StripKnownExtension(filename, base, sizeof(base)); + snprintf(outPath, outPathSize, "%s%s", base, suffix); + outPath[outPathSize - 1] = '\0'; +} + +static BOOL WriteBinaryFile(const char *path, const unsigned char *data, size_t len) +{ + FILE *f; + + if (!path || !data || len == 0) + return FALSE; + + f = fopen(path, "wb"); + if (!f) + return FALSE; + + if (fwrite(data, 1, len, f) != len) + { + fclose(f); + return FALSE; + } + + fclose(f); + return TRUE; +} + +static void JsonWriteEscapedString(FILE *f, const char *s) +{ + fputc('"', f); + if (s) + { + while (*s) + { + unsigned char c = (unsigned char)*s++; + if (c == '"' || c == '\\') + { + fputc('\\', f); + fputc(c, f); + } + else if (c == '\n') + { + fputs("\\n", f); + } + else if (c == '\r') + { + fputs("\\r", f); + } + else if (c == '\t') + { + fputs("\\t", f); + } + else if (c < 0x20) + { + fprintf(f, "\\u%04x", c); + } + else + { + fputc(c, f); + } + } + } + fputc('"', f); +} + +static void JsonWriteHexString(FILE *f, const unsigned char *data, size_t len) +{ + fputc('"', f); + if (data) + { + for (size_t i = 0; i < len; i++) + fprintf(f, "%02X", data[i]); + } + fputc('"', f); +} + + +static const char *PathLeaf(const char *path) +{ + const char *slash1; + const char *slash2; + + if (!path) + return ""; + + slash1 = strrchr(path, '\\'); + slash2 = strrchr(path, '/'); + + if (slash1 && slash2) + return (slash1 > slash2 ? slash1 : slash2) + 1; + if (slash1) + return slash1 + 1; + if (slash2) + return slash2 + 1; + return path; +} + +static void TrimTrailingSpaces(char *s) +{ + size_t len; + + if (!s) + return; + + len = strlen(s); + while (len > 0 && (s[len - 1] == ' ' || s[len - 1] == '\t')) + { + s[len - 1] = '\0'; + len--; + } +} + +static void CopyBounded(char *dst, size_t dstSize, const char *src, size_t srcLen) +{ + size_t n; + + if (!dst || dstSize == 0) + return; + + dst[0] = '\0'; + if (!src) + return; + + n = srcLen; + if (n >= dstSize) + n = dstSize - 1; + + memcpy(dst, src, n); + dst[n] = '\0'; +} + +static void ExtractMediaProfileNames(const char *isoFilename, + char *titleHint, + size_t titleHintSize, + char *mediaId, + size_t mediaIdSize) +{ + char base[MAX_PATH]; + const char *leaf; + const char *openBracket; + const char *closeBracket; + + if (titleHint && titleHintSize > 0) + titleHint[0] = '\0'; + if (mediaId && mediaIdSize > 0) + mediaId[0] = '\0'; + + if (!isoFilename) + return; + + StripKnownExtension(isoFilename, base, sizeof(base)); + leaf = PathLeaf(base); + + openBracket = strrchr(leaf, '['); + closeBracket = openBracket ? strchr(openBracket, ']') : NULL; + + if (openBracket && closeBracket && closeBracket > openBracket) + { + CopyBounded(titleHint, titleHintSize, leaf, (size_t)(openBracket - leaf)); + CopyBounded(mediaId, mediaIdSize, openBracket + 1, (size_t)(closeBracket - openBracket - 1)); + } + else + { + CopyBounded(titleHint, titleHintSize, leaf, strlen(leaf)); + } + + TrimTrailingSpaces(titleHint); +} + +static void JsonWriteValidationWarnings(FILE *f, const XboxDvdSidecarCapture *cap, BOOL payloadFilesPresent, BOOL redumpStyleZeroFilledPadding) +{ + BOOL wrote = FALSE; + + fprintf(f, "["); + +#define WRITE_WARNING(w) do { \ + if (wrote) fprintf(f, ", "); \ + JsonWriteEscapedString(f, (w)); \ + wrote = TRUE; \ + } while (0) + + if (!cap || !cap->hasLockedCapacity) + WRITE_WARNING("missing_locked_read_capacity_10"); + if (!cap || !cap->hasLockedModeSense3E) + WRITE_WARNING("missing_locked_mode_sense_3e"); + if (!cap || !cap->hasUnlockedCapacity) + WRITE_WARNING("missing_unlocked_read_capacity_10"); + if (!cap || !cap->hasUnlockedModeSense3E) + WRITE_WARNING("missing_unlocked_mode_sense_3e"); + if (!cap || !cap->hasAdC0) + WRITE_WARNING("missing_ad_c0_payload"); + if (!cap || !cap->hasPfi) + WRITE_WARNING("missing_pfi_payload"); + if (!cap || !cap->hasDmi) + WRITE_WARNING("missing_dmi_payload"); + if (!payloadFilesPresent) + WRITE_WARNING("payload_files_not_fully_present"); + if (redumpStyleZeroFilledPadding) + WRITE_WARNING("redump_style_padding_zero_filled_pending_hardware_capture"); + +#undef WRITE_WARNING + + fprintf(f, "]"); +} + +static BOOL ScsiDataInCommand(HANDLE hDevice, + const unsigned char *cdb, + BYTE cdbLen, + DWORD dataLen, + unsigned char *buffer) +{ + SCSI_PASS_THROUGH_DIRECT sptd; + DWORD bytesReturned = 0; + + if (!hDevice || !cdb || !buffer || dataLen == 0 || cdbLen == 0 || cdbLen > 16) + return FALSE; + + memset(&sptd, 0, sizeof(sptd)); + memset(buffer, 0, dataLen); + + sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); + sptd.CdbLength = cdbLen; + sptd.DataIn = SCSI_IOCTL_DATA_IN; + sptd.DataTransferLength = dataLen; + sptd.TimeOutValue = 30; + sptd.DataBuffer = buffer; + memcpy(sptd.Cdb, cdb, cdbLen); + + return DeviceIoControl(hDevice, + IOCTL_SCSI_PASS_THROUGH_DIRECT, + &sptd, + sizeof(sptd), + &sptd, + sizeof(sptd), + &bytesReturned, + NULL); +} + +static BOOL CaptureReadCapacity10(HANDLE hDevice, unsigned char out8[8]) +{ + static const unsigned char cdb[10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + return ScsiDataInCommand(hDevice, cdb, 10, 8, out8); +} + +static BOOL CaptureModeSense3E(HANDLE hDevice, unsigned char out28[28]) +{ + static const unsigned char cdb[10] = {0x5A, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00}; + return ScsiDataInCommand(hDevice, cdb, 10, 28, out28); +} + +static BOOL CaptureReadDvdStructureXboxC0(HANDLE hDevice, unsigned char out1664[0x664]) +{ + static const unsigned char cdb[12] = {0xAD, 0x00, 0xFF, 0x02, 0xFD, 0xFF, 0xFE, 0x00, 0x06, 0x64, 0x00, 0xC0}; + return ScsiDataInCommand(hDevice, cdb, 12, 0x664, out1664); +} + +static BOOL CaptureReadDvdStructurePfi(HANDLE hDevice, unsigned char out2048[2048]) +{ + static const unsigned char cdb[12] = {0xAD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00}; + return ScsiDataInCommand(hDevice, cdb, 12, 2048, out2048); +} + +static BOOL CaptureReadDvdStructureDmi(HANDLE hDevice, unsigned char out2048[2048]) +{ + static const unsigned char cdb[12] = {0xAD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x00, 0x00, 0x00}; + return ScsiDataInCommand(hDevice, cdb, 12, 2048, out2048); +} + +static void CaptureLockedSidecarState(HANDLE hDevice, XboxDvdSidecarCapture *cap) +{ + if (!cap) + return; + + cap->hasLockedCapacity = CaptureReadCapacity10(hDevice, cap->lockedCapacity); + cap->hasLockedModeSense3E = CaptureModeSense3E(hDevice, cap->lockedModeSense3E); + + printf("[META] Locked READ CAPACITY: %s\n", cap->hasLockedCapacity ? "captured" : "failed"); + printf("[META] Locked MODE SENSE 0x3E: %s\n", cap->hasLockedModeSense3E ? "captured" : "failed"); +} + +static void CaptureUnlockedSidecarState(HANDLE hDevice, XboxDvdSidecarCapture *cap) +{ + if (!cap) + return; + + cap->hasUnlockedCapacity = CaptureReadCapacity10(hDevice, cap->unlockedCapacity); + cap->hasUnlockedModeSense3E = CaptureModeSense3E(hDevice, cap->unlockedModeSense3E); + cap->hasAdC0 = CaptureReadDvdStructureXboxC0(hDevice, cap->adC0); + cap->hasPfi = CaptureReadDvdStructurePfi(hDevice, cap->pfi); + cap->hasDmi = CaptureReadDvdStructureDmi(hDevice, cap->dmi); + + printf("[META] Unlocked READ CAPACITY: %s\n", cap->hasUnlockedCapacity ? "captured" : "failed"); + printf("[META] Unlocked MODE SENSE 0x3E: %s\n", cap->hasUnlockedModeSense3E ? "captured" : "failed"); + printf("[META] READ DVD STRUCTURE Xbox C0 block: %s\n", cap->hasAdC0 ? "captured" : "failed"); + printf("[META] READ DVD STRUCTURE PFI: %s\n", cap->hasPfi ? "captured" : "failed"); + printf("[META] READ DVD STRUCTURE DMI: %s\n", cap->hasDmi ? "captured" : "failed"); +} + +static uint32_t CapacitySectorsFromReadCapacity10(const unsigned char data[8]) +{ + uint32_t maxLba; + + if (!data) + return 0; + + maxLba = ((uint32_t)data[0] << 24) | + ((uint32_t)data[1] << 16) | + ((uint32_t)data[2] << 8) | + ((uint32_t)data[3]); + return maxLba + 1; +} + + +static uint32_t NormalizeRawIsoTargetSectors(uint32_t reportedSectors, BOOL isDualLayer) +{ + // Option 1 targets a Redump-style reconstructed 2048-byte-sector image. + // The GDR-8050L's unlocked READ CAPACITY reports the game/XISO view length + // (3,431,264 sectors), while the full Original Xbox/XGD1 reconstructed image + // is larger (3,820,880 sectors) because it also includes video L0/L1 and + // padding around the game region. + if (isDualLayer || reportedSectors > LAYER_THRESHOLD) + return XGD1_FULL_REDUMP_SECTORS; + + return reportedSectors; +} + +static unsigned long long GetFileSizeBytes64(const char *filename) +{ + FILE *f; + __int64 pos; + + if (!filename) + return 0ULL; + + f = fopen(filename, "rb"); + if (!f) + return 0ULL; + + if (_fseeki64(f, 0, SEEK_END) != 0) + { + fclose(f); + return 0ULL; + } + + pos = _ftelli64(f); + fclose(f); + + if (pos < 0) + return 0ULL; + + return (unsigned long long)pos; +} + +static BOOL VerifyOutputByteCount(const char *filename, unsigned long long expectedBytes, const char *label) +{ + unsigned long long actualBytes = GetFileSizeBytes64(filename); + + if (expectedBytes == 0) + return TRUE; + + if (actualBytes != expectedBytes) + { + printf("\n[FATAL] %s byte-count mismatch.\n", label ? label : "Output"); + printf(" Expected: %llu bytes\n", expectedBytes); + printf(" Actual: %llu bytes\n", actualBytes); + printf(" Refusing to mark this dump complete.\n"); + return FALSE; + } + + printf("[OK] %s byte count verified: %llu bytes.\n", label ? label : "Output", actualBytes); + return TRUE; +} + +static void JsonWriteNull(FILE *f) +{ + fprintf(f, "null"); +} + +static void HexBytesToString(const BYTE *bytes, DWORD byteCount, char *outHex, size_t outHexSize) +{ + DWORD i; + + if (!outHex || outHexSize == 0) + return; + + outHex[0] = '\0'; + if (!bytes || outHexSize < ((size_t)byteCount * 2U + 1U)) + return; + + for (i = 0; i < byteCount; i++) + sprintf(&outHex[i * 2], "%02x", bytes[i]); +} + +static DWORD Crc32Update(DWORD crc, const unsigned char *buf, size_t len) +{ + static DWORD table[256]; + static BOOL tableReady = FALSE; + size_t i; + + if (!tableReady) + { + DWORD n; + for (n = 0; n < 256; n++) + { + DWORD c = n; + int k; + for (k = 0; k < 8; k++) + c = (c & 1U) ? (0xEDB88320U ^ (c >> 1)) : (c >> 1); + table[n] = c; + } + tableReady = TRUE; + } + + for (i = 0; i < len; i++) + crc = table[(crc ^ buf[i]) & 0xFFU] ^ (crc >> 8); + + return crc; +} + +static BOOL CalculateFileHashes(const char *filename, + char *outCrc32, + size_t outCrc32Size, + char *outMd5, + size_t outMd5Size, + char *outSha1, + size_t outSha1Size, + char *outSha256, + size_t outSha256Size) +{ + FILE *f; + unsigned char *buf; + HCRYPTPROV hProv = 0; + HCRYPTHASH hMd5 = 0; + HCRYPTHASH hSha1 = 0; + HCRYPTHASH hSha256 = 0; + DWORD crc = 0xFFFFFFFFU; + BOOL ok = FALSE; + size_t readBytes; + unsigned long long totalBytes = 0ULL; + unsigned long long doneBytes = 0ULL; + DWORD startTick = 0; + DWORD lastPrintTick = 0; + DWORD nowTick = 0; + DWORD elapsedMs = 0; + DWORD etaMs = 0; + char timeStr[12] = {0}; + char etaStr[12] = {0}; + + if (outCrc32 && outCrc32Size) outCrc32[0] = '\0'; + if (outMd5 && outMd5Size) outMd5[0] = '\0'; + if (outSha1 && outSha1Size) outSha1[0] = '\0'; + if (outSha256 && outSha256Size) outSha256[0] = '\0'; + + if (!filename) + return FALSE; + + totalBytes = GetFileSizeBytes64(filename); + + f = fopen(filename, "rb"); + if (!f) + return FALSE; + + buf = (unsigned char *)malloc(1024 * 1024); + if (!buf) + { + fclose(f); + return FALSE; + } + + if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT) && + !CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + goto cleanup; + if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hMd5)) + goto cleanup; + if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hSha1)) + goto cleanup; + if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hSha256)) + goto cleanup; + + startTick = GetTickCount(); + lastPrintTick = startTick; + printf("[HASH] Calculating full-file CRC32/MD5/SHA-1/SHA-256 for %s (%llu bytes)...\n", + filename, + totalBytes); + + while ((readBytes = fread(buf, 1, 1024 * 1024, f)) > 0) + { + crc = Crc32Update(crc, buf, readBytes); + if (!CryptHashData(hMd5, buf, (DWORD)readBytes, 0)) + goto cleanup; + if (!CryptHashData(hSha1, buf, (DWORD)readBytes, 0)) + goto cleanup; + if (!CryptHashData(hSha256, buf, (DWORD)readBytes, 0)) + goto cleanup; + + doneBytes += (unsigned long long)readBytes; + nowTick = GetTickCount(); + if (totalBytes > 0 && (doneBytes >= totalBytes || (nowTick - lastPrintTick) >= 1000)) + { + double percent = ((double)doneBytes / (double)totalBytes) * 100.0; + double mbDone = (double)doneBytes / (1024.0 * 1024.0); + double speed = 0.0; + unsigned long long bytesLeft = totalBytes - doneBytes; + + elapsedMs = nowTick - startTick; + if (elapsedMs > 0) + speed = mbDone / ((double)elapsedMs / 1000.0); + etaMs = (doneBytes > 0 && elapsedMs > 0) + ? (DWORD)(((double)elapsedMs / (double)doneBytes) * (double)bytesLeft) + : 0; + FormatElapsedTime(elapsedMs, timeStr); + FormatElapsedTime(etaMs, etaStr); + xbox_ref_console_printf("\r[HASH] Full-file: %5.1f%% | %.1f MB | Speed: %.2f MB/s | Time: %s | ETA: %s ", + percent, + mbDone, + speed, + timeStr, + etaStr); + fflush(stdout); + lastPrintTick = nowTick; + } + } + + if (ferror(f)) + goto cleanup; + + elapsedMs = GetTickCount() - startTick; + FormatElapsedTime(elapsedMs, timeStr); + if (totalBytes > 0) + xbox_ref_console_printf("\r[HASH] Full-file: 100.0%% | %.1f MB | Time: %s \n", + (double)totalBytes / (1024.0 * 1024.0), + timeStr); + printf("[OK] Full-file CRC32/MD5/SHA-1/SHA-256 complete in %s.\n", timeStr); + + crc ^= 0xFFFFFFFFU; + if (outCrc32 && outCrc32Size >= 9) + sprintf(outCrc32, "%08x", crc); + + if (outMd5 && outMd5Size >= 33) + { + BYTE md5Bytes[16]; + DWORD md5Len = sizeof(md5Bytes); + if (!CryptGetHashParam(hMd5, HP_HASHVAL, md5Bytes, &md5Len, 0)) + goto cleanup; + HexBytesToString(md5Bytes, md5Len, outMd5, outMd5Size); + } + + if (outSha1 && outSha1Size >= 41) + { + BYTE sha1Bytes[20]; + DWORD sha1Len = sizeof(sha1Bytes); + if (!CryptGetHashParam(hSha1, HP_HASHVAL, sha1Bytes, &sha1Len, 0)) + goto cleanup; + HexBytesToString(sha1Bytes, sha1Len, outSha1, outSha1Size); + } + + if (outSha256 && outSha256Size >= 65) + { + BYTE sha256Bytes[32]; + DWORD sha256Len = sizeof(sha256Bytes); + if (!CryptGetHashParam(hSha256, HP_HASHVAL, sha256Bytes, &sha256Len, 0)) + goto cleanup; + HexBytesToString(sha256Bytes, sha256Len, outSha256, outSha256Size); + } + + ok = TRUE; + +cleanup: + if (!ok && startTick) + { + elapsedMs = GetTickCount() - startTick; + FormatElapsedTime(elapsedMs, timeStr); + printf("\n[WARN] Full-file CRC32/MD5/SHA-1/SHA-256 calculation failed after %s.\n", timeStr); + } + if (hSha256) CryptDestroyHash(hSha256); + if (hSha1) CryptDestroyHash(hSha1); + if (hMd5) CryptDestroyHash(hMd5); + if (hProv) CryptReleaseContext(hProv, 0); + free(buf); + fclose(f); + return ok; +} + +static void WritePressedDvdRomWriteMediaState(FILE *json, const char *isoFilename, uint32_t totalDiscSectors) +{ + if (!json) + return; + + fprintf(json, " \"write_media_state\": {\n"); + fprintf(json, " \"media_class\": \"pressed_dvd_rom\",\n"); + fprintf(json, " \"writable\": false,\n"); + fprintf(json, " \"erasable\": false,\n"); + fprintf(json, " \"finalized\": true,\n"); + + fprintf(json, " \"backing_image\": {\n"); + fprintf(json, " \"file\": "); JsonWriteEscapedString(json, isoFilename ? isoFilename : ""); fprintf(json, ",\n"); + fprintf(json, " \"sector_size\": 2048,\n"); + fprintf(json, " \"initial_sector_count\": %u,\n", totalDiscSectors); + fprintf(json, " \"max_sector_count\": %u,\n", totalDiscSectors); + fprintf(json, " \"growth_policy\": \"fixed_read_only\"\n"); + fprintf(json, " },\n"); + + fprintf(json, " \"sessions\": [\n"); + fprintf(json, " {\n"); + fprintf(json, " \"session_number\": 1,\n"); + fprintf(json, " \"state\": \"closed\",\n"); + fprintf(json, " \"first_track_number\": 1,\n"); + fprintf(json, " \"last_track_number\": 1\n"); + fprintf(json, " }\n"); + fprintf(json, " ],\n"); + + fprintf(json, " \"tracks\": [\n"); + fprintf(json, " {\n"); + fprintf(json, " \"track_number\": 1,\n"); + fprintf(json, " \"state\": \"complete\",\n"); + fprintf(json, " \"mode\": \"data\",\n"); + fprintf(json, " \"packet_or_track_mode\": \"pressed_read_only\",\n"); + fprintf(json, " \"start_lba\": 0,\n"); + fprintf(json, " \"next_writable_lba\": "); JsonWriteNull(json); fprintf(json, ",\n"); + fprintf(json, " \"free_blocks\": 0,\n"); + fprintf(json, " \"written_blocks\": %u\n", totalDiscSectors); + fprintf(json, " }\n"); + fprintf(json, " ],\n"); + + fprintf(json, " \"unwritten_read_policy\": \"not_applicable_read_only_media\",\n"); + fprintf(json, " \"flush_policy\": \"read_only_noop\"\n"); + fprintf(json, " },\n"); +} + +static BOOL WriteXboxDvdMediaProfileFile(const char *isoFilename, + const XboxDvdSidecarCapture *cap, + uint32_t totalDiscSectors, + BOOL isDualLayer, + uint32_t videoSectors, + uint32_t gameSourceLba, + uint32_t gameSectors, + const char *isoSha1, + const char *isoMd5, + const char *isoCrc32, + const char *isoSha256, + const char *adC0Path, + const char *pfiPath, + const char *dmiPath, + BOOL payloadFilesPresent) +{ + char profilePath[MAX_PATH]; + char titleHint[256]; + char mediaId[64]; + BOOL redumpStyle; + FILE *json; + + if (!isoFilename || !cap) + return FALSE; + + MakeSidecarPath(isoFilename, ".media.json", profilePath, sizeof(profilePath)); + ExtractMediaProfileNames(isoFilename, titleHint, sizeof(titleHint), mediaId, sizeof(mediaId)); + redumpStyle = (totalDiscSectors == XGD1_FULL_REDUMP_SECTORS && gameSectors == REDUMP_SECTORS); + + json = fopen(profilePath, "wb"); + if (!json) + return FALSE; + + fprintf(json, "{\n"); + fprintf(json, " \"format\": \"xdvd-media-profile\",\n"); + fprintf(json, " \"version\": 1,\n"); + fprintf(json, " \"media_id\": "); JsonWriteEscapedString(json, mediaId); fprintf(json, ",\n"); + fprintf(json, " \"title_hint\": "); JsonWriteEscapedString(json, titleHint); fprintf(json, ",\n"); + fprintf(json, " \"image\": {\n"); + fprintf(json, " \"file\": "); JsonWriteEscapedString(json, isoFilename); fprintf(json, ",\n"); + fprintf(json, " \"sector_size\": 2048,\n"); + fprintf(json, " \"sector_count\": %u,\n", totalDiscSectors); + fprintf(json, " \"byte_count\": %llu,\n", (unsigned long long)totalDiscSectors * 2048ULL); + fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n"); + fprintf(json, " \"hashes\": {\n"); + fprintf(json, " \"crc32\": "); JsonWriteEscapedString(json, isoCrc32 ? isoCrc32 : ""); fprintf(json, ",\n"); + fprintf(json, " \"md5\": "); JsonWriteEscapedString(json, isoMd5 ? isoMd5 : ""); fprintf(json, ",\n"); + fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n"); + fprintf(json, " \"sha256\": "); JsonWriteEscapedString(json, isoSha256 ? isoSha256 : ""); fprintf(json, "\n"); + fprintf(json, " }\n"); + fprintf(json, " },\n"); + + { + uint32_t gameOutputLba = redumpStyle ? XGD1_GAME_OUTPUT_START_LBA : videoSectors; + + fprintf(json, " \"layout\": {\n"); + fprintf(json, " \"layout_type\": "); JsonWriteEscapedString(json, redumpStyle ? "original_xbox_xgd1_redump_style_2048" : "contiguous_logical_dump"); fprintf(json, ",\n"); + fprintf(json, " \"layers\": %u,\n", isDualLayer ? 2U : 1U); + fprintf(json, " \"layer_break_lba\": %u,\n", isDualLayer ? LAYER_BREAK : 0U); + fprintf(json, " \"video_l0_start_lba\": 0,\n"); + fprintf(json, " \"video_l0_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : videoSectors); + fprintf(json, " \"pregame_padding_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : 0U); + fprintf(json, " \"pregame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) : 0U); + fprintf(json, " \"game_output_start_lba\": %u,\n", gameOutputLba); + fprintf(json, " \"game_unlocked_source_start_lba\": %u,\n", gameSourceLba); + fprintf(json, " \"game_unlocked_source_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_SOURCE_SECTORS : gameSectors); + fprintf(json, " \"game_xiso_leadin_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U); + fprintf(json, " \"xdfs_volume_lba_within_game_region\": 32,\n"); + fprintf(json, " \"game_sector_count\": %u,\n", gameSectors); + fprintf(json, " \"postgame_padding_start_lba\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS) : 0U); + fprintf(json, " \"postgame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS)) : 0U); + fprintf(json, " \"video_l1_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_OUTPUT_START_LBA : 0U); + fprintf(json, " \"video_l1_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_SECTORS : 0U); + fprintf(json, " \"legacy_contiguous_visible_sector_count\": %u,\n", videoSectors); + fprintf(json, " \"drive_locked_visible_sector_count\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0); + fprintf(json, " \"drive_reported_unlocked_sector_count\": %u,\n", cap->hasUnlockedCapacity ? CapacitySectorsFromReadCapacity10(cap->unlockedCapacity) : 0); + fprintf(json, " \"reconstructed_output_sector_count\": %u\n", totalDiscSectors); + fprintf(json, " },\n"); + } + + fprintf(json, " \"reconstruction\": {\n"); + fprintf(json, " \"is_reconstructed_layout\": %s,\n", redumpStyle ? "true" : "false"); + fprintf(json, " \"filler_policy\": "); JsonWriteEscapedString(json, redumpStyle ? "zero_fill_until_drive_can_read_filler" : "not_applicable"); fprintf(json, ",\n"); + fprintf(json, " \"filler_verified_from_disc\": false,\n"); + fprintf(json, " \"filler_byte_value\": %s,\n", redumpStyle ? "0" : "null"); + fprintf(json, " \"pending_hardware_capture\": %s,\n", redumpStyle ? "true" : "false"); + fprintf(json, " \"filler_ranges\": [\n"); + if (redumpStyle) + { + fprintf(json, " {\n"); + fprintf(json, " \"name\": \"pregame_padding\",\n"); + fprintf(json, " \"start_lba\": %u,\n", XGD1_VIDEO_L0_SECTORS); + fprintf(json, " \"sector_count\": %u,\n", XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS); + fprintf(json, " \"source\": \"synthetic_zero_fill\"\n"); + fprintf(json, " },\n"); + fprintf(json, " {\n"); + fprintf(json, " \"name\": \"postgame_padding\",\n"); + fprintf(json, " \"start_lba\": %u,\n", XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS); + fprintf(json, " \"sector_count\": %u,\n", XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS)); + fprintf(json, " \"source\": \"synthetic_zero_fill\"\n"); + fprintf(json, " }\n"); + } + fprintf(json, " ],\n"); + fprintf(json, " \"note\": \"Padding/filler ranges are intentionally zero-filled placeholders until a drive/workflow capable of reading those regions is available.\"\n"); + fprintf(json, " },\n"); + + fprintf(json, " \"dvd_structures\": {\n"); + fprintf(json, " \"ad_c0\": "); JsonWriteEscapedString(json, cap->hasAdC0 ? adC0Path : ""); fprintf(json, ",\n"); + fprintf(json, " \"pfi\": "); JsonWriteEscapedString(json, cap->hasPfi ? pfiPath : ""); fprintf(json, ",\n"); + fprintf(json, " \"dmi\": "); JsonWriteEscapedString(json, cap->hasDmi ? dmiPath : ""); fprintf(json, "\n"); + fprintf(json, " },\n"); + + fprintf(json, " \"non_lba_physical_metadata\": {\n"); + fprintf(json, " \"pfi_storage\": \"sidecar_bin\",\n"); + fprintf(json, " \"dmi_storage\": \"sidecar_bin\",\n"); + fprintf(json, " \"lead_in_storage\": \"not_in_iso_stream\",\n"); + fprintf(json, " \"lead_out_storage\": \"not_in_iso_stream\",\n"); + fprintf(json, " \"note\": \"A standard 2048-byte-sector ISO contains READ(10) user-data sectors only. PFI/DMI/lead-in/lead-out are represented by sidecar/profile metadata.\"\n"); + fprintf(json, " },\n"); + + fprintf(json, " \"drive_state_observations\": {\n"); + fprintf(json, " \"locked\": {\n"); + fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasLockedCapacity ? cap->lockedCapacity : NULL, cap->hasLockedCapacity ? 8 : 0); fprintf(json, ",\n"); + fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasLockedModeSense3E ? cap->lockedModeSense3E : NULL, cap->hasLockedModeSense3E ? 28 : 0); fprintf(json, "\n"); + fprintf(json, " },\n"); + fprintf(json, " \"unlocked\": {\n"); + fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasUnlockedCapacity ? cap->unlockedCapacity : NULL, cap->hasUnlockedCapacity ? 8 : 0); fprintf(json, ",\n"); + fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasUnlockedModeSense3E ? cap->unlockedModeSense3E : NULL, cap->hasUnlockedModeSense3E ? 28 : 0); fprintf(json, "\n"); + fprintf(json, " }\n"); + fprintf(json, " },\n"); + + WritePressedDvdRomWriteMediaState(json, isoFilename, totalDiscSectors); + + fprintf(json, " \"validation\": {\n"); + fprintf(json, " \"byte_count_matches_sector_count\": true,\n"); + fprintf(json, " \"payload_files_present\": %s,\n", payloadFilesPresent ? "true" : "false"); + fprintf(json, " \"warnings\": "); JsonWriteValidationWarnings(json, cap, payloadFilesPresent, redumpStyle); fprintf(json, "\n"); + fprintf(json, " }\n"); + fprintf(json, "}\n"); + + fclose(json); + printf("[META] Wrote media profile: %s\n", profilePath); + return TRUE; +} + +static BOOL WriteXboxDvdSidecarFiles(const char *isoFilename, + const XboxDvdSidecarCapture *cap, + uint32_t totalDiscSectors, + BOOL isDualLayer, + uint32_t videoSectors, + uint32_t gameSourceLba, + uint32_t gameSectors, + const char *isoSha1, + const char *isoMd5, + const char *isoCrc32, + const char *isoSha256) +{ + char jsonPath[MAX_PATH]; + char adC0Path[MAX_PATH]; + char pfiPath[MAX_PATH]; + char dmiPath[MAX_PATH]; + FILE *json; + BOOL ok = TRUE; + BOOL payloadFilesPresent = FALSE; + BOOL redumpStyle = FALSE; + + if (!isoFilename || !cap) + return FALSE; + + MakeSidecarPath(isoFilename, ".xdvd.json", jsonPath, sizeof(jsonPath)); + MakeSidecarPath(isoFilename, ".ad_c0.bin", adC0Path, sizeof(adC0Path)); + MakeSidecarPath(isoFilename, ".pfi.bin", pfiPath, sizeof(pfiPath)); + MakeSidecarPath(isoFilename, ".dmi.bin", dmiPath, sizeof(dmiPath)); + + if (cap->hasAdC0 && !WriteBinaryFile(adC0Path, cap->adC0, 0x664)) + ok = FALSE; + if (cap->hasPfi && !WriteBinaryFile(pfiPath, cap->pfi, 2048)) + ok = FALSE; + if (cap->hasDmi && !WriteBinaryFile(dmiPath, cap->dmi, 2048)) + ok = FALSE; + + payloadFilesPresent = cap->hasAdC0 && cap->hasPfi && cap->hasDmi && ok; + redumpStyle = (totalDiscSectors == XGD1_FULL_REDUMP_SECTORS && gameSectors == REDUMP_SECTORS); + + json = fopen(jsonPath, "wb"); + if (!json) + return FALSE; + + fprintf(json, "{\n"); + fprintf(json, " \"format\": \"xdvd-sidecar\",\n"); + fprintf(json, " \"version\": 1,\n"); + fprintf(json, " \"image\": {\n"); + fprintf(json, " \"file\": "); JsonWriteEscapedString(json, isoFilename); fprintf(json, ",\n"); + fprintf(json, " \"sector_size\": 2048,\n"); + fprintf(json, " \"sector_count\": %u,\n", totalDiscSectors); + fprintf(json, " \"byte_count\": %llu,\n", (unsigned long long)totalDiscSectors * 2048ULL); + fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n"); + fprintf(json, " \"hashes\": {\n"); + fprintf(json, " \"crc32\": "); JsonWriteEscapedString(json, isoCrc32 ? isoCrc32 : ""); fprintf(json, ",\n"); + fprintf(json, " \"md5\": "); JsonWriteEscapedString(json, isoMd5 ? isoMd5 : ""); fprintf(json, ",\n"); + fprintf(json, " \"sha1\": "); JsonWriteEscapedString(json, isoSha1 ? isoSha1 : ""); fprintf(json, ",\n"); + fprintf(json, " \"sha256\": "); JsonWriteEscapedString(json, isoSha256 ? isoSha256 : ""); fprintf(json, "\n"); + fprintf(json, " },\n"); + { + uint32_t gameOutputLba = redumpStyle ? XGD1_GAME_OUTPUT_START_LBA : videoSectors; + + fprintf(json, " \"layout\": {\n"); + fprintf(json, " \"layout_type\": "); JsonWriteEscapedString(json, redumpStyle ? "original_xbox_xgd1_redump_style_2048" : "contiguous_logical_dump"); fprintf(json, ",\n"); + fprintf(json, " \"legacy_contiguous_visible_start_lba\": 0,\n"); + fprintf(json, " \"legacy_contiguous_visible_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_OUTPUT_START_LBA : videoSectors); + fprintf(json, " \"drive_locked_visible_sector_count\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0); + fprintf(json, " \"video_l0_start_lba\": 0,\n"); + fprintf(json, " \"video_l0_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : videoSectors); + fprintf(json, " \"pregame_padding_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L0_SECTORS : 0U); + fprintf(json, " \"pregame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS) : 0U); + fprintf(json, " \"game_output_start_lba\": %u,\n", gameOutputLba); + fprintf(json, " \"game_unlocked_source_start_lba\": %u,\n", gameSourceLba); + fprintf(json, " \"game_unlocked_source_sector_count\": %u,\n", redumpStyle ? XGD1_GAME_SOURCE_SECTORS : gameSectors); + fprintf(json, " \"game_xiso_leadin_sector_count\": %u,\n", redumpStyle ? XGD1_XISO_LEADIN_SECTORS : 0U); + fprintf(json, " \"xdfs_volume_lba_within_game_region\": 32,\n"); + fprintf(json, " \"game_sector_count\": %u,\n", gameSectors); + fprintf(json, " \"postgame_padding_start_lba\": %u,\n", redumpStyle ? (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS) : 0U); + fprintf(json, " \"postgame_padding_sector_count\": %u,\n", redumpStyle ? (XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS)) : 0U); + fprintf(json, " \"video_l1_start_lba\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_OUTPUT_START_LBA : 0U); + fprintf(json, " \"video_l1_sector_count\": %u,\n", redumpStyle ? XGD1_VIDEO_L1_SECTORS : 0U); + fprintf(json, " \"layer_break_lba\": %u\n", LAYER_BREAK); + fprintf(json, " }\n"); + } + fprintf(json, " },\n"); + + fprintf(json, " \"disc\": {\n"); + fprintf(json, " \"layers\": %u,\n", isDualLayer ? 2U : 1U); + fprintf(json, " \"reconstructed_output_sectors\": %u,\n", totalDiscSectors); + fprintf(json, " \"reconstructed_output_byte_count\": %llu,\n", (unsigned long long)totalDiscSectors * 2048ULL); + fprintf(json, " \"drive_reported_unlocked_sectors\": %u,\n", cap->hasUnlockedCapacity ? CapacitySectorsFromReadCapacity10(cap->unlockedCapacity) : 0); + fprintf(json, " \"drive_reported_locked_sectors\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0); + fprintf(json, " \"unlocked_game_view_sectors\": %u\n", gameSectors); + fprintf(json, " },\n"); + + fprintf(json, " \"drive_states\": {\n"); + fprintf(json, " \"locked\": {\n"); + fprintf(json, " \"read_capacity_10_cdb_hex\": \"25000000000000000000\",\n"); + fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasLockedCapacity ? cap->lockedCapacity : NULL, cap->hasLockedCapacity ? 8 : 0); fprintf(json, ",\n"); + fprintf(json, " \"visible_sector_count\": %u,\n", cap->hasLockedCapacity ? CapacitySectorsFromReadCapacity10(cap->lockedCapacity) : 0); + fprintf(json, " \"mode_sense_3e_cdb_hex\": \"5A003E00000000001C00\",\n"); + fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasLockedModeSense3E ? cap->lockedModeSense3E : NULL, cap->hasLockedModeSense3E ? 28 : 0); fprintf(json, "\n"); + fprintf(json, " },\n"); + fprintf(json, " \"unlocked\": {\n"); + fprintf(json, " \"read_capacity_10_cdb_hex\": \"25000000000000000000\",\n"); + fprintf(json, " \"read_capacity_10_hex\": "); JsonWriteHexString(json, cap->hasUnlockedCapacity ? cap->unlockedCapacity : NULL, cap->hasUnlockedCapacity ? 8 : 0); fprintf(json, ",\n"); + fprintf(json, " \"visible_sector_count\": %u,\n", cap->hasUnlockedCapacity ? CapacitySectorsFromReadCapacity10(cap->unlockedCapacity) : 0); + fprintf(json, " \"mode_sense_3e_cdb_hex\": \"5A003E00000000001C00\",\n"); + fprintf(json, " \"mode_sense_3e_hex\": "); JsonWriteHexString(json, cap->hasUnlockedModeSense3E ? cap->unlockedModeSense3E : NULL, cap->hasUnlockedModeSense3E ? 28 : 0); fprintf(json, "\n"); + fprintf(json, " }\n"); + fprintf(json, " },\n"); + + fprintf(json, " \"scsi_responses\": [\n"); + fprintf(json, " {\n"); + fprintf(json, " \"name\": \"read_dvd_structure_xbox_control_block\",\n"); + fprintf(json, " \"cdb_hex\": \"AD00FF02FDFFFE00066400C0\",\n"); + fprintf(json, " \"data_in\": true,\n"); + fprintf(json, " \"data_len\": 1636,\n"); + fprintf(json, " \"response_file\": "); JsonWriteEscapedString(json, cap->hasAdC0 ? adC0Path : ""); fprintf(json, ",\n"); + fprintf(json, " \"captured\": %s\n", cap->hasAdC0 ? "true" : "false"); + fprintf(json, " },\n"); + fprintf(json, " {\n"); + fprintf(json, " \"name\": \"read_dvd_structure_pfi\",\n"); + fprintf(json, " \"cdb_hex\": \"AD0000000000000008000000\",\n"); + fprintf(json, " \"data_in\": true,\n"); + fprintf(json, " \"data_len\": 2048,\n"); + fprintf(json, " \"response_file\": "); JsonWriteEscapedString(json, cap->hasPfi ? pfiPath : ""); fprintf(json, ",\n"); + fprintf(json, " \"captured\": %s\n", cap->hasPfi ? "true" : "false"); + fprintf(json, " },\n"); + fprintf(json, " {\n"); + fprintf(json, " \"name\": \"read_dvd_structure_dmi\",\n"); + fprintf(json, " \"cdb_hex\": \"AD0000000000000408000000\",\n"); + fprintf(json, " \"data_in\": true,\n"); + fprintf(json, " \"data_len\": 2048,\n"); + fprintf(json, " \"response_file\": "); JsonWriteEscapedString(json, cap->hasDmi ? dmiPath : ""); fprintf(json, ",\n"); + fprintf(json, " \"captured\": %s\n", cap->hasDmi ? "true" : "false"); + fprintf(json, " }\n"); + fprintf(json, " ],\n"); + + fprintf(json, " \"auth\": {\n"); + fprintf(json, " \"requires_media_transition\": true,\n"); + fprintf(json, " \"mode_page\": \"0x3E\",\n"); + fprintf(json, " \"challenge_table_source\": \"read_dvd_structure_xbox_control_block\",\n"); + fprintf(json, " \"challenge_table_response_offset\": 774,\n"); + fprintf(json, " \"challenge_table_hash_offset\": 1187,\n"); + fprintf(json, " \"challenge_table_hash_length\": 44\n"); + fprintf(json, " }\n"); + fprintf(json, "}\n"); + + fclose(json); + + if (!WriteXboxDvdMediaProfileFile(isoFilename, + cap, + totalDiscSectors, + isDualLayer, + videoSectors, + gameSourceLba, + gameSectors, + isoSha1, + isoMd5, + isoCrc32, + isoSha256, + adC0Path, + pfiPath, + dmiPath, + payloadFilesPresent)) + { + ok = FALSE; + printf("[WARN] Failed to write XDVD media profile.\n"); + } + + printf("[META] Wrote XDVD sidecar: %s\n", jsonPath); + if (cap->hasAdC0) printf("[META] Wrote Xbox control block: %s\n", adC0Path); + if (cap->hasPfi) printf("[META] Wrote PFI: %s\n", pfiPath); + if (cap->hasDmi) printf("[META] Wrote DMI: %s\n", dmiPath); + + return ok; +} + +void DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uint32_t totalDiscSectors, bool isDualLayer, bool EjectOnSuccess) +{ + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + FILE *outFile = NULL; + BYTE rgbHash[20]; + char sha1String[41] = {0}; + char md5String[33] = {0}; + char crc32String[9] = {0}; + char fileSha1String[41] = {0}; + char fileSha256String[65] = {0}; + BOOL dumpOk = FALSE; + XboxDvdSidecarCapture sidecarCapture; + BOOL rawSidecarAvailable = FALSE; + uint32_t rawVideoSectors = START_LBA_MAGIC; + uint32_t rawGameSourceLba = 0xFFFFFFFFu; + uint32_t rawGameSectors = 0; + uint32_t rawTargetSectors = 0; + unsigned long long expectedOutputBytes = 0ULL; + const char *outputLabel = "Output"; + DWORD operationStartTick = GetTickCount(); + DWORD cbHash = 20; + char operationTimeStr[12] = {0}; + + memset(&sidecarCapture, 0, sizeof(sidecarCapture)); + + if (totalDiscSectors == 0) + totalDiscSectors = GetTotalSectors(hDevice); + if (totalDiscSectors == 0) + totalDiscSectors = REDUMP_SECTORS; + + rawTargetSectors = NormalizeRawIsoTargetSectors(totalDiscSectors, isDualLayer); + + if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + return; + if (!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) + { + CryptReleaseContext(hProv, 0); + return; + } + + EnsureDriveReady(hDevice, 30000); + SetDriveSpeedMax(hDevice); + + if (xisoFormat == '1') + { + DWORD bytesReturned; + uint32_t gameSourceLba = 0xFFFFFFFFu; + uint32_t videoSectors = START_LBA_MAGIC; + uint32_t gameSectors = 0; + BOOL lockedViewIsAlreadyXdfs = FALSE; + + outputLabel = "RAW ISO"; + expectedOutputBytes = (unsigned long long)rawTargetSectors * 2048ULL; + + if (rawTargetSectors != totalDiscSectors) + { + printf("[RAW] Drive-reported unlocked sectors %u normalized to Redump-style output target %u.\n", + totalDiscSectors, rawTargetSectors); + } + + printf("[RAW] Full-disc target: %u sectors (%llu bytes).\n", + rawTargetSectors, + expectedOutputBytes); + if (rawTargetSectors > LAYER_BREAK) + { + printf("[RAW] Expected Redump/XGD1 layer break at output LBA %u.\n", LAYER_BREAK); + } + printf("[RAW] Media-transition-preserving mode is enabled.\n"); + + if (!CheckOutputFreeSpace(filename, expectedOutputBytes, outputLabel)) + goto cleanup; + + outFile = fopen(filename, "wb"); + if (!outFile) + { + printf("\n[FATAL] Could not create output file '%s'.\n", filename); + if (errno) + printf(" errno: %d (%s)\n", errno, strerror(errno)); + goto cleanup; + } + + // Raw mode must capture the visible/video view first. The caller normally reaches this + // point after the drive has already been authenticated for metadata, so reset the drive + // state with a real media transition before reading LBA 0. + DeviceIoControl(hDevice, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL); + + printf("[RAW] Cycling tray to restore locked/video view before dumping sector 0.\n"); + AutomateTrayCycle(hDevice); + RefreshVolume(hDevice); + SetDriveSpeedMax(hDevice); + + DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &bytesReturned, NULL); + + CaptureLockedSidecarState(hDevice, &sidecarCapture); + + lockedViewIsAlreadyXdfs = ProbeXboxVolumeAt(hDevice, 0x20); + + if (lockedViewIsAlreadyXdfs && rawTargetSectors > START_LBA_MAGIC) + { + printf("\n[FATAL] Option 1 requires a full raw/video-front source image.\n"); + printf(" This source exposes XDFS at LBA 0x20 after the media-reset step,\n"); + printf(" which looks like an XISO/game-partition view, not a full raw disc view.\n"); + printf(" Use option 2 for this source, or mount/create a 7.29 GiB Redump-style option-1 ISO.\n"); + goto cleanup; + } + + if (rawTargetSectors <= START_LBA_MAGIC) + { + printf("[RAW] Non-retail-sized source; dumping visible LBA 0..%u directly.\n", + rawTargetSectors - 1); + rawVideoSectors = rawTargetSectors; + rawGameSourceLba = 0; + rawGameSectors = 0; + CaptureUnlockedSidecarState(hDevice, &sidecarCapture); + rawSidecarAvailable = TRUE; + dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, rawTargetSectors, 0, "RAW", FALSE); + } + else if (rawTargetSectors == XGD1_FULL_REDUMP_SECTORS) + { + unsigned char *videoL1Buffer = NULL; + uint32_t detectedXdfsLba; + uint32_t pregamePaddingSectors = XGD1_GAME_OUTPUT_START_LBA - XGD1_VIDEO_L0_SECTORS; + uint32_t postgamePaddingSectors = XGD1_VIDEO_L1_OUTPUT_START_LBA - (XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS); + + printf("[RAW] Using Original Xbox/XGD1 Redump-style 2048-byte-sector layout.\n"); + printf("[RAW] Layout: VIDEO_L0=%u sectors, pregame zero-fill padding=%u sectors, game/XISO=%u sectors, postgame zero-fill padding=%u sectors, VIDEO_L1=%u sectors.\n", + XGD1_VIDEO_L0_SECTORS, + pregamePaddingSectors, + REDUMP_SECTORS, + postgamePaddingSectors, + XGD1_VIDEO_L1_SECTORS); + printf("[RAW] Note: filler/padding ranges are synthetic zero-fill placeholders until readable from hardware.\n"); + printf("[RAW] Note: PFI/DMI/lead-in/lead-out are MMC/physical metadata, not READ(10) user-data sectors; they are emitted in sidecar/profile files.\n"); + + videoL1Buffer = (unsigned char *)VirtualAlloc(NULL, XGD1_VIDEO_L1_SECTORS * 2048U, MEM_COMMIT, PAGE_READWRITE); + if (!videoL1Buffer) + { + printf("\n[FATAL] Could not allocate VIDEO_L1 capture buffer.\n"); + goto cleanup; + } + + // The locked-visible Xbox video ISO is 6,992 sectors. In the Redump-style + // image, its L0 portion is placed at the beginning and its L1 tail is placed + // at the end of the reconstructed image. Capture the L1 tail while the drive + // is still in the locked/video state, before authenticating for the game view. + if (!ReadSectorsToMemory(hDevice, + XGD1_VIDEO_L0_SECTORS, + XGD1_VIDEO_L1_SECTORS, + videoL1Buffer, + "VIDEO-L1 tail")) + { + VirtualFree(videoL1Buffer, 0, MEM_RELEASE); + goto cleanup; + } + + printf("[RAW] Writing video L0 from locked source LBA 0..%u.\n", XGD1_VIDEO_L0_SECTORS - 1); + if (!DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, XGD1_VIDEO_L0_SECTORS, 0, "VIDEO-L0", FALSE)) + { + VirtualFree(videoL1Buffer, 0, MEM_RELEASE); + goto cleanup; + } + + if (!WriteZeroSectorsOutput(outFile, hHash, pregamePaddingSectors, XGD1_VIDEO_L0_SECTORS, "PREGAME-PAD")) + { + VirtualFree(videoL1Buffer, 0, MEM_RELEASE); + goto cleanup; + } + + printf("[RAW] Re-applying full Xbox handshake for unlocked game/XISO view.\n"); + UnlockDrive(hDevice); + RefreshVolume(hDevice); + Sleep(2000); + EnsureDriveReady(hDevice, 30000); + SetDriveSpeedMax(hDevice); + KickXboxMediaAuth(hDevice); + RecoveryKick(hDevice, TRUE); + CaptureUnlockedSidecarState(hDevice, &sidecarCapture); + rawSidecarAvailable = TRUE; + + detectedXdfsLba = DetectXboxVolumeStart(hDevice); + if (detectedXdfsLba == 0xFFFFFFFFu) + { + printf("\n[FATAL] Could not locate XDFS after unlock at LBA 0x20 or 0x%X.\n", START_LBA_MAGIC); + VirtualFree(videoL1Buffer, 0, MEM_RELEASE); + goto cleanup; + } + if (detectedXdfsLba != 0x20) + { + printf("[WARN] XDFS was detected at unlocked source LBA %u, not the expected XISO header LBA 32.\n", detectedXdfsLba); + } + + // Build the Redump-style XISO/game region using the same proven + // convention as option 2: the first 32 sectors are synthetic XISO + // lead-in/padding, and the real XDFS volume begins at source LBA 32. + // Do not read unlocked source LBA 0..31 here; on the 8050L path those + // LBAs are not the XDFS header sectors we want in the rebuilt image. + gameSourceLba = XGD1_GAME_SOURCE_START_LBA; + gameSectors = REDUMP_SECTORS; + rawVideoSectors = XGD1_GAME_OUTPUT_START_LBA; + rawGameSourceLba = gameSourceLba; + rawGameSectors = gameSectors; + + printf("[RAW] Writing %u-sector XISO lead-in/padding at output LBA %u.\n", + XGD1_XISO_LEADIN_SECTORS, XGD1_GAME_OUTPUT_START_LBA); + if (!WriteZeroSectorsOutput(outFile, hHash, XGD1_XISO_LEADIN_SECTORS, XGD1_GAME_OUTPUT_START_LBA, "GAME-XISO-LEADIN")) + { + VirtualFree(videoL1Buffer, 0, MEM_RELEASE); + goto cleanup; + } + + printf("[RAW] Writing unlocked XDFS/game data from source LBA %u for %u sectors at output LBA %u.\n", + XGD1_GAME_SOURCE_START_LBA, + XGD1_GAME_SOURCE_SECTORS, + XGD1_GAME_OUTPUT_START_LBA + XGD1_XISO_LEADIN_SECTORS); + if (!DumpSectorRangeWithRetry(hDevice, + outFile, + hHash, + XGD1_GAME_SOURCE_START_LBA, + XGD1_GAME_SOURCE_SECTORS, + XGD1_GAME_OUTPUT_START_LBA + XGD1_XISO_LEADIN_SECTORS, + "GAME-XISO", + TRUE)) + { + VirtualFree(videoL1Buffer, 0, MEM_RELEASE); + goto cleanup; + } + + if (!WriteZeroSectorsOutput(outFile, hHash, postgamePaddingSectors, XGD1_GAME_OUTPUT_START_LBA + REDUMP_SECTORS, "POSTGAME-PAD")) + { + VirtualFree(videoL1Buffer, 0, MEM_RELEASE); + goto cleanup; + } + + if (!WriteMemorySectorsOutput(outFile, hHash, videoL1Buffer, XGD1_VIDEO_L1_SECTORS, XGD1_VIDEO_L1_OUTPUT_START_LBA, "VIDEO-L1")) + { + VirtualFree(videoL1Buffer, 0, MEM_RELEASE); + goto cleanup; + } + + VirtualFree(videoL1Buffer, 0, MEM_RELEASE); + dumpOk = TRUE; + } + else + { + if (videoSectors > rawTargetSectors) + videoSectors = rawTargetSectors; + rawVideoSectors = videoSectors; + + printf("[RAW] Dumping contiguous visible/video area first: source LBA 0..%u.\n", videoSectors - 1); + if (!DumpSectorRangeWithRetry(hDevice, outFile, hHash, 0, videoSectors, 0, "VIDEO", FALSE)) + goto cleanup; + + printf("[RAW] Re-applying full Xbox handshake after media transition for hidden game/data area.\n"); + UnlockDrive(hDevice); + RefreshVolume(hDevice); + Sleep(2000); + EnsureDriveReady(hDevice, 30000); + SetDriveSpeedMax(hDevice); + KickXboxMediaAuth(hDevice); + RecoveryKick(hDevice, TRUE); + CaptureUnlockedSidecarState(hDevice, &sidecarCapture); + rawSidecarAvailable = TRUE; + + gameSourceLba = DetectXboxVolumeStart(hDevice); + if (gameSourceLba == 0xFFFFFFFFu) + { + printf("\n[FATAL] Could not locate XDFS after unlock at LBA 0x20 or 0x%X.\n", START_LBA_MAGIC); + goto cleanup; + } + + gameSectors = rawTargetSectors - videoSectors; + rawGameSourceLba = gameSourceLba; + rawGameSectors = gameSectors; + printf("[RAW] Appending hidden game/data area from unlocked source LBA %u for %u sectors.\n", + gameSourceLba, gameSectors); + dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, gameSourceLba, gameSectors, videoSectors, "GAME", TRUE); + } + } + else if (xisoFormat == '2') + { + uint32_t startLba = 0; + uint32_t sectorsToRead = 0; + unsigned char *sectorBuffer = (unsigned char *)VirtualAlloc(NULL, 2048, MEM_COMMIT, PAGE_READWRITE); + XDFS_VOLUME_DESCRIPTOR *vol = NULL; + uint32_t xgd2EndLba = 1913920; + unsigned char zeroSector[2048] = {0}; + + if (!sectorBuffer) + goto cleanup; + + SetDriveSpeedMax(hDevice); + KickXboxMediaAuth(hDevice); + RecoveryKick(hDevice, TRUE); + + vol = (XDFS_VOLUME_DESCRIPTOR *)sectorBuffer; + + if (ScsiReadSectors(hDevice, START_LBA_MAGIC, 1, sectorBuffer) && memcmp(vol->Identifier, "MICROSOFT", 9) == 0) + { + startLba = START_LBA_MAGIC; + printf("[INFO] XGD2 Game Partition identified at LBA %u\n", startLba); + } + else if (ScsiReadSectors(hDevice, 0x20, 1, sectorBuffer) && memcmp(vol->Identifier, "MICROSOFT", 9) == 0) + { + startLba = 0x20; + printf("[INFO] Standard Game Partition identified at LBA 32\n"); + } + else + { + printf("[ERROR] No Xbox Game Partition found. Disc may be non-standard.\n"); + VirtualFree(sectorBuffer, 0, MEM_RELEASE); + goto cleanup; + } + + if (isDualLayer || totalDiscSectors > 3300000) + { + sectorsToRead = xgd2EndLba - startLba; + printf("[INFO] Dual Layer disc detected. Calculating span across layers...\n"); + } + else + { + sectorsToRead = vol->VolumeSize / 2048; + printf("[INFO] Single Layer disc detected. Using header-reported size.\n"); + } + + outputLabel = "XISO"; + expectedOutputBytes = ((unsigned long long)sectorsToRead + 32ULL) * 2048ULL; + + printf("[SUCCESS] Final XISO target: %u sectors plus 32-sector lead-in (%llu bytes).\n", + sectorsToRead, + expectedOutputBytes); + + if (!CheckOutputFreeSpace(filename, expectedOutputBytes, outputLabel)) + { + VirtualFree(sectorBuffer, 0, MEM_RELEASE); + goto cleanup; + } + + outFile = fopen(filename, "wb"); + if (!outFile) + { + printf("\n[FATAL] Could not create output file '%s'.\n", filename); + if (errno) + printf(" errno: %d (%s)\n", errno, strerror(errno)); + VirtualFree(sectorBuffer, 0, MEM_RELEASE); + goto cleanup; + } + + VirtualFree(sectorBuffer, 0, MEM_RELEASE); + + printf("Writing 64KB XISO lead-in padding...\n"); + for (int p = 0; p < 32; p++) + { + if (!WriteOutputBytes(outFile, zeroSector, 2048, "XISO-PAD", 0, (uint32_t)p)) + goto cleanup; + CryptHashData(hHash, zeroSector, 2048, 0); + } + + dumpOk = DumpSectorRangeWithRetry(hDevice, outFile, hHash, startLba, sectorsToRead, 32, "XISO", TRUE); + } + else + { + printf("[ERROR] Unsupported dump mode '%c'.\n", xisoFormat); + goto cleanup; + } + + if (!dumpOk) + goto cleanup; + + if (!FlushAndCommitOutput(outFile, outputLabel)) + { + dumpOk = FALSE; + goto cleanup; + } + + if (fclose(outFile) != 0) + { + printf("\n[FATAL] fclose failed for %s output.\n", outputLabel ? outputLabel : "dump"); + if (errno) + printf(" errno: %d (%s)\n", errno, strerror(errno)); + outFile = NULL; + dumpOk = FALSE; + goto cleanup; + } + outFile = NULL; + + if (!VerifyOutputByteCount(filename, expectedOutputBytes, outputLabel)) + { + dumpOk = FALSE; + goto cleanup; + } + + FormatElapsedTime(GetTickCount() - operationStartTick, operationTimeStr); + printf("\nDump/write phase complete at elapsed %s. Finalizing hashes...\n", operationTimeStr); + + if (CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0)) + { + HexBytesToString(rgbHash, cbHash, sha1String, sizeof(sha1String)); + } + + if (CalculateFileHashes(filename, crc32String, sizeof(crc32String), md5String, sizeof(md5String), fileSha1String, sizeof(fileSha1String), fileSha256String, sizeof(fileSha256String))) + { + if (fileSha1String[0] && sha1String[0] && strcmp(fileSha1String, sha1String) != 0) + { + printf("\n[WARN] Streaming SHA-1 differs from file SHA-1. Using file SHA-1 in metadata.\n"); + printf(" Streaming SHA-1: %s\n", sha1String); + printf(" File SHA-1: %s\n", fileSha1String); + } + if (fileSha1String[0]) + strcpy(sha1String, fileSha1String); + } + else + { + printf("\n[WARN] Could not calculate CRC32/MD5/SHA-1/SHA-256 from finalized output file.\n"); + } + + if (xisoFormat == '1') + { + printf("Final RAW ISO Sector Count: %u\n", rawTargetSectors); + printf("Final RAW ISO Byte Count: %llu\n", (unsigned long long)rawTargetSectors * 2048ULL); + printf("CRC32: %s\n", crc32String); + printf("MD5: %s\n", md5String); + printf("SHA-1: %s\n", sha1String); + printf("SHA-256: %s\n", fileSha256String); + PrintGamePartitionHash(filename); + if (rawSidecarAvailable) + { + if (!WriteXboxDvdSidecarFiles(filename, + &sidecarCapture, + rawTargetSectors, + isDualLayer, + rawVideoSectors, + rawGameSourceLba, + rawGameSectors, + sha1String, + md5String, + crc32String, + fileSha256String)) + { + printf("[WARN] Failed to write one or more XDVD sidecar metadata files.\n"); + } + } + else + { + printf("[WARN] XDVD sidecar metadata was not captured for this raw dump.\n"); + } + } + else + { + printf("Final XISO Byte Count: see progress target above.\n"); + printf("CRC32: %s\n", crc32String); + printf("MD5: %s\n", md5String); + printf("SHA-1: %s\n", sha1String); + printf("SHA-256: %s\n", fileSha256String); + } + FormatElapsedTime(GetTickCount() - operationStartTick, operationTimeStr); + printf("\nOperation Complete! Total elapsed: %s\n", operationTimeStr); + + StopDriveUnit(hDevice); + + if (EjectOnSuccess) + ControlTray(hDevice, TRUE); + +cleanup: + if (outFile) + fclose(outFile); + if (hHash) + CryptDestroyHash(hHash); + if (hProv) + CryptReleaseContext(hProv, 0); +} diff --git a/libfriidump/xbox_ref/utils.h b/libfriidump/xbox_ref/utils.h new file mode 100644 index 0000000..b7d2cf9 --- /dev/null +++ b/libfriidump/xbox_ref/utils.h @@ -0,0 +1,143 @@ +#ifndef UTILS_H +#define UTILS_H + +#include +#include +#include "xbe_cert.h" + +#define SECTORS_PER_READ 128 // 128 * 2048 = 256KB (The 8163B has a 256KB hardware cache.) +#define MAX_RETRIES 3 // How many times to retry a failed block + +// Xbox DVD layout constants +// +// REDUMP_SECTORS is the unlocked game/XISO view length reported by the +// GDR-8050L after successful authentication. It is not the full Redump-style +// reconstructed disc-image length. +#define REDUMP_SECTORS 3431264 + +// Full Original Xbox/XGD1 Redump-style 2048-byte sector image length. +// This includes video L0, pre-game padding, the full unlocked game/XISO view, +// post-game padding, and video L1. Non-LBA structures such as PFI/DMI/lead-in +// remain sidecar/profile data, not bytes inside the ISO stream. +#define XGD1_FULL_REDUMP_SECTORS 3820880 +#define XGD1_GAME_OUTPUT_START_LBA 198144 +#define XGD1_XISO_LEADIN_SECTORS 32 +#define XGD1_GAME_SOURCE_START_LBA 32 +#define XGD1_GAME_SOURCE_SECTORS (REDUMP_SECTORS - XGD1_XISO_LEADIN_SECTORS) +#define XGD1_VIDEO_TOTAL_SECTORS 6992 +#define XGD1_VIDEO_L0_SECTORS 6832 +#define XGD1_VIDEO_L1_SECTORS 160 +#define XGD1_VIDEO_L1_OUTPUT_START_LBA (XGD1_FULL_REDUMP_SECTORS - XGD1_VIDEO_L1_SECTORS) + +// Redump/XGD1 physical layer-break from the Redump reference page. +// This is metadata for the reconstructed 2048-byte-sector ISO image. +#define XGD1_REDUMP_LAYER_BREAK_LBA 1913776 + +// Compatibility alias used by existing code paths that refer to the output +// layer break. Do not confuse this with the GDR-8050L unlocked game-view +// sector count (3431264). +#define LAYER_BREAK XGD1_REDUMP_LAYER_BREAK_LBA +#define LAYER_THRESHOLD 2000000 + +// Legacy contiguous/hybrid layout start used by earlier experiments. +#define START_LBA_MAGIC 306112 + +// Handle cases where stdbool.h isn't available +#ifndef __cplusplus +typedef int bool; +#define true 1 +#define false 0 +#endif + +#pragma pack(push, 1) +typedef struct _XDFS_SUPERBLOCK +{ + char Magic[20]; // Should be "Microsoft Xbox Disk" + uint32_t RootDirSector; // Sector where the root directory starts + uint32_t RootDirSize; // Size of the root directory in bytes + FILETIME ImageTime; // Creation time + char Unused[0x7C8]; // Padding to 2048 bytes +} XDFS_SUPERBLOCK; + +typedef struct _XDFS_DIRENTRY +{ + uint16_t LeftEntry; // Offset to left node (it's a B-Tree) + uint16_t RightEntry; // Offset to right node + uint32_t Sector; // Starting sector of the file/dir + uint32_t Size; // Size of the file/dir + uint8_t Attributes; // Directory, Hidden, etc. + uint8_t NameLength; // Length of name + char Name[1]; // The name (variable length) +} XDFS_DIRENTRY; +#pragma pack(pop) + +#pragma pack(push, 1) // Ensure no padding is added by the compiler +typedef struct +{ + // Offset: 0x00 (0) + char Identifier[20]; // "MICROSOFT*XBOX*MEDIA" + + // Offset: 0x14 (20) + uint32_t RootLBA; // LBA of the Root Directory Table (Relative to Partition Start) + + // Offset: 0x18 (24) + uint32_t RootSize; // Size of the Root Directory Table in bytes + + // Offset: 0x1C (28) + uint32_t VolumeSize; // Total Number of Sectors in this Partition (The 2.11GB value) + + // Offset: 0x20 (32) + uint64_t VolumeTimestamp; // 64-bit Windows FileTime + + // Offset: 0x28 (40) + // The remainder of the 2048-byte sector is reserved/padding (2048 - 40 = 2008) + uint8_t Reserved[2008]; +} XDFS_VOLUME_DESCRIPTOR; +#pragma pack(pop) + +// XDFS Directory Entry Structure +typedef struct +{ + uint16_t LeftChild; // Offset / 4 + uint16_t RightChild; // Offset / 4 + uint32_t StartLBA; // File Data LBA + uint32_t FileSize; // File Size in Bytes + uint8_t Attributes; + uint8_t FileNameLength; + char FileName[1]; // Variable length +} XDFS_DIR_ENTRY; + +HANDLE OpenDrive(char driveLetter); +void CloseDrive(HANDLE hDevice); +int IsDiscPresent(HANDLE hDevice); +void ControlTray(HANDLE hDevice, BOOL eject); +BOOL TestUnitReady(HANDLE hDevice); +BOOL StartDriveUnit(HANDLE hDevice); +BOOL EnsureDriveReady(HANDLE hDevice, DWORD timeoutMs); +BOOL StopDriveUnit(HANDLE hDevice); +void AutomateTrayCycle(HANDLE hDevice); +BOOL SetDriveSpeedMax(HANDLE hDevice); +void ForceMediaRefresh(HANDLE hDevice); +void HexDump(unsigned char* buffer, uint32_t size); +void outputdata(const uint8_t* buf, uint32_t lines); +uint8_t chksum8(const unsigned char *buff, size_t len); +void FormatElapsedTime(DWORD dwMilliseconds, char* outStr); +void PrintFormattedCapacity(unsigned char* scsibuffer); +void ListOpticalDrives(void); +uint32_t GetTotalSectors(HANDLE hDevice); +uint32_t GetXboxPhysicalSectors(HANDLE hDevice); +void RefreshVolume(HANDLE hDevice); +void ListDirectoryRecursive(HANDLE hDevice, uint32_t lba, uint32_t size, int level); +void ReadXboxGameDir(HANDLE hDevice); +void SanitizeFilename(char* filename); +BOOL ScsiReadSectors(HANDLE hDevice, uint32_t lba, uint16_t count, unsigned char* buffer); +XboxGameInfo GetXboxGameInfo(HANDLE hDevice); +void DisplayXboxGameInfo(XboxGameInfo info); +void DisplayXboxRating(uint32_t ratings); +void GetMediaID(HANDLE hDevice, char* outMediaId); +void PrintGamePartitionHash(const char* filename); +void GetDiscMetadata(HANDLE hDevice, uint32_t* totalSectors, bool* isDualLayer, XboxGameInfo *gameInfo); +uint32_t GetGamePartitionSize(HANDLE hDevice, uint32_t totalDiscSectors, XDFS_VOLUME_DESCRIPTOR *vol); +void DumpXboxGameDisc(HANDLE hDevice, const char *filename, char xisoFormat, uint32_t totalDiscSectors, bool isDualLayer, bool EjectOnSuccess); + +#endif diff --git a/libfriidump/xbox_ref/xbe_cert.h b/libfriidump/xbox_ref/xbe_cert.h new file mode 100644 index 0000000..b326462 --- /dev/null +++ b/libfriidump/xbox_ref/xbe_cert.h @@ -0,0 +1,44 @@ +#ifndef XBE_CERT_H +#define XBE_CERT_H + +#include + +// --- XBE Certificate Bit Flags --- + +typedef enum { + XB_REGION_US_CANADA = 0x00000001, + XB_REGION_JAPAN = 0x00000002, + XB_REGION_EUROPE_AU_NZ = 0x00000004, + XB_REGION_REST_OF_WORLD = 0x00000008, + XB_REGION_MANUFACTURING = 0x80000000U // 'U' suffix forces Unsigned +} XboxRegion; + +typedef enum { + XB_MEDIA_HARD_DRIVE = 0x00000001, + XB_MEDIA_DVD_X2 = 0x00000002, + XB_MEDIA_DVD_CD = 0x00000004, + XB_MEDIA_CD = 0x00000008, + XB_MEDIA_DVD_5_RO = 0x00000010, + XB_MEDIA_DVD_9_RO = 0x00000020, + XB_MEDIA_DVD_5_RW = 0x00000040, + XB_MEDIA_DVD_9_RW = 0x00000080, + XB_MEDIA_DONGLE = 0x00000100, + XB_MEDIA_NON_SECURE_HARD_DRIVE = 0x40000000, + XB_MEDIA_NON_SECURE_USER_MASK = 0x80000000U +} XboxMedia; + +// --- The Data Struct --- + +typedef struct { + uint32_t TitleId; // Offset 0x008 + char TitleName[41]; // Offset 0x00C (Converted to ASCII) + uint32_t AllowedMedia; // Offset 0x09C + uint32_t GameRegion; // Offset 0x0A0 + uint32_t GameRatings; // Offset 0x0A4 + uint32_t DiscNumber; // Offset 0x0A8 + uint32_t Version; // Offset 0x0AC + uint64_t TotalSizeBytes; // Offset 0x01C + int Success; // Helper to check if read worked +} XboxGameInfo; + +#endif \ No newline at end of file diff --git a/libfriidump/xbox_ref/xbox_ref_log.c b/libfriidump/xbox_ref/xbox_ref_log.c new file mode 100644 index 0000000..6a515ca --- /dev/null +++ b/libfriidump/xbox_ref/xbox_ref_log.c @@ -0,0 +1,237 @@ +#include "xbox_ref_log.h" + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#ifndef MAX_PATH +#define MAX_PATH 260 +#endif + +static FILE *g_xbox_ref_log = NULL; +static char g_xbox_ref_log_path[MAX_PATH * 2] = {0}; +static char g_xbox_ref_temp_path[MAX_PATH * 2] = {0}; +static int g_xbox_ref_using_temp = 0; + +static void xbox_ref_build_log_path(const char *target_filename, char drive_letter, char *out, size_t out_size, int *is_temp) +{ + if (!out || out_size == 0) + return; + + out[0] = '\0'; + if (is_temp) + *is_temp = 0; + + if (target_filename && target_filename[0]) { + snprintf(out, out_size, "%s.log", target_filename); + return; + } + + if (drive_letter && isalpha((unsigned char)drive_letter)) + snprintf(out, out_size, "friidump_%c.log", (char)tolower((unsigned char)drive_letter)); + else + snprintf(out, out_size, "friidump.log"); + + if (is_temp) + *is_temp = 1; +} + +static int xbox_ref_open_path(const char *path, int append) +{ + if (!path || !path[0]) + return 0; + + g_xbox_ref_log = fopen(path, append ? "ab" : "wb"); + if (!g_xbox_ref_log) + return 0; + + strncpy(g_xbox_ref_log_path, path, sizeof(g_xbox_ref_log_path) - 1); + g_xbox_ref_log_path[sizeof(g_xbox_ref_log_path) - 1] = '\0'; + return 1; +} + +int xbox_ref_log_open_for_target(const char *target_filename, char drive_letter) +{ + char path[MAX_PATH * 2]; + int is_temp = 0; + + xbox_ref_build_log_path(target_filename, drive_letter, path, sizeof(path), &is_temp); + + if (g_xbox_ref_log && strcmp(g_xbox_ref_log_path, path) == 0) + return 1; + + xbox_ref_log_close(); + + if (!xbox_ref_open_path(path, 0)) { + fprintf(stderr, "[FRIIDUMP][WARN] Could not create log file '%s'. Continuing without file logging.\n", path[0] ? path : "(none)"); + return 0; + } + + g_xbox_ref_using_temp = is_temp; + if (is_temp) { + strncpy(g_xbox_ref_temp_path, path, sizeof(g_xbox_ref_temp_path) - 1); + g_xbox_ref_temp_path[sizeof(g_xbox_ref_temp_path) - 1] = '\0'; + } else { + g_xbox_ref_temp_path[0] = '\0'; + } + + xbox_ref_printf("[FRIIDUMP] Log file: %s\n", g_xbox_ref_log_path); + return 1; +} + +int xbox_ref_log_retarget(const char *target_filename) +{ + char final_path[MAX_PATH * 2]; + int dummy = 0; + + if (!target_filename || !target_filename[0]) + return 0; + + xbox_ref_build_log_path(target_filename, 0, final_path, sizeof(final_path), &dummy); + if (!final_path[0]) + return 0; + + if (strcmp(g_xbox_ref_log_path, final_path) == 0) + return 1; + + if (!g_xbox_ref_log) { + return xbox_ref_open_path(final_path, 0); + } + + xbox_ref_printf("[FRIIDUMP] Retargeting log file to: %s\n", final_path); + fclose(g_xbox_ref_log); + g_xbox_ref_log = NULL; + + if (g_xbox_ref_using_temp && g_xbox_ref_temp_path[0]) { + remove(final_path); + if (rename(g_xbox_ref_temp_path, final_path) == 0) { + g_xbox_ref_using_temp = 0; + g_xbox_ref_temp_path[0] = '\0'; + return xbox_ref_open_path(final_path, 1); + } + } + + g_xbox_ref_using_temp = 0; + g_xbox_ref_temp_path[0] = '\0'; + return xbox_ref_open_path(final_path, 1); +} + +void xbox_ref_log_close(void) +{ + if (g_xbox_ref_log) { + fflush(g_xbox_ref_log); + fclose(g_xbox_ref_log); + } + g_xbox_ref_log = NULL; + g_xbox_ref_log_path[0] = '\0'; + g_xbox_ref_temp_path[0] = '\0'; + g_xbox_ref_using_temp = 0; +} + +int xbox_ref_log_is_open(void) +{ + return g_xbox_ref_log != NULL; +} + +const char *xbox_ref_log_path(void) +{ + return g_xbox_ref_log_path[0] ? g_xbox_ref_log_path : NULL; +} + +int xbox_ref_printf(const char *fmt, ...) +{ + va_list ap; + va_list ap2; + int out; + + va_start(ap, fmt); + va_copy(ap2, ap); + out = vprintf(fmt, ap); + fflush(stdout); + if (g_xbox_ref_log) { + vfprintf(g_xbox_ref_log, fmt, ap2); + fflush(g_xbox_ref_log); + } + va_end(ap2); + va_end(ap); + return out; +} + +int xbox_ref_log_fprintf(FILE *stream, const char *fmt, ...) +{ + va_list ap; + va_list ap2; + int out; + + if (!stream) + stream = stdout; + + va_start(ap, fmt); + va_copy(ap2, ap); + out = vfprintf(stream, fmt, ap); + fflush(stream); + if (g_xbox_ref_log) { + vfprintf(g_xbox_ref_log, fmt, ap2); + fflush(g_xbox_ref_log); + } + va_end(ap2); + va_end(ap); + return out; +} + +int xbox_ref_console_printf(const char *fmt, ...) +{ + va_list ap; + int out; + + va_start(ap, fmt); + out = vprintf(fmt, ap); + fflush(stdout); + va_end(ap); + return out; +} + +static void xbox_ref_hexdump_to_file(FILE *stream, const char *prefix, const uint8_t *data, size_t length, size_t limit, int truncated) +{ + size_t i; + size_t shown = (limit && limit < length) ? limit : length; + + if (!stream || !prefix) + return; + + if (truncated) + fprintf(stream, "\n--- %s (%lu bytes; console showing first %lu bytes, full data in log) ---\n", + prefix, (unsigned long)length, (unsigned long)shown); + else + fprintf(stream, "\n--- %s (%lu bytes) ---\n", prefix, (unsigned long)length); + + for (i = 0; i < shown; i++) { + fprintf(stream, "%02X ", data ? data[i] : 0); + if ((i + 1) % 16 == 0) + fprintf(stream, "\n"); + } + if (shown % 16 != 0) + fprintf(stream, "\n"); + if (truncated) + fprintf(stream, "... [truncated on console; full %lu bytes written to log]\n", (unsigned long)length); + fprintf(stream, "---------------------------\n"); + fflush(stream); +} + +void xbox_ref_log_hexdump(const char *prefix, const uint8_t *data, size_t length, size_t console_limit) +{ + int console_truncated = (console_limit > 0 && console_limit < length); + + xbox_ref_hexdump_to_file(stdout, prefix, data, length, console_limit, console_truncated); + + if (g_xbox_ref_log) { + /* The log is intentionally never truncated. */ + xbox_ref_hexdump_to_file(g_xbox_ref_log, prefix, data, length, 0, 0); + } +} diff --git a/libfriidump/xbox_ref/xbox_ref_log.h b/libfriidump/xbox_ref/xbox_ref_log.h new file mode 100644 index 0000000..44f95d4 --- /dev/null +++ b/libfriidump/xbox_ref/xbox_ref_log.h @@ -0,0 +1,26 @@ +#ifndef XBOX_REF_LOG_H +#define XBOX_REF_LOG_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int xbox_ref_log_open_for_target(const char *target_filename, char drive_letter); +int xbox_ref_log_retarget(const char *target_filename); +void xbox_ref_log_close(void); +int xbox_ref_log_is_open(void); +const char *xbox_ref_log_path(void); +int xbox_ref_printf(const char *fmt, ...); +int xbox_ref_log_fprintf(FILE *stream, const char *fmt, ...); +int xbox_ref_console_printf(const char *fmt, ...); +void xbox_ref_log_hexdump(const char *prefix, const uint8_t *data, size_t length, size_t console_limit); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libfriidump/xbox_ref/xbox_ref_sha1.c b/libfriidump/xbox_ref/xbox_ref_sha1.c new file mode 100644 index 0000000..f3dd555 --- /dev/null +++ b/libfriidump/xbox_ref/xbox_ref_sha1.c @@ -0,0 +1,95 @@ +#include "sha1.h" +#include + +#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b)))) + +static void SHA1_Transform(SHA1_CTX* ctx, const uint8_t data[64]) { + uint32_t a, b, c, d, e, i, j, t, m[80]; + + for (i = 0, j = 0; i < 16; ++i, j += 4) + m[i] = (data[j] << 24) | (data[j+1] << 16) | (data[j+2] << 8) | (data[j+3]); + for ( ; i < 80; ++i) + m[i] = ROTLEFT(m[i-3] ^ m[i-8] ^ m[i-14] ^ m[i-16], 1); + + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + e = ctx->state[4]; + + for (i = 0; i < 80; ++i) { + if (i < 20) + t = ROTLEFT(a,5) + ((b & c) | (~b & d)) + e + m[i] + 0x5A827999; + else if (i < 40) + t = ROTLEFT(a,5) + (b ^ c ^ d) + e + m[i] + 0x6ED9EBA1; + else if (i < 60) + t = ROTLEFT(a,5) + ((b & c) | (b & d) | (c & d)) + e + m[i] + 0x8F1BBCDC; + else + t = ROTLEFT(a,5) + (b ^ c ^ d) + e + m[i] + 0xCA62C1D6; + + e = d; + d = c; + c = ROTLEFT(b,30); + b = a; + a = t; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; + ctx->state[4] += e; +} + +void SHA1_Init(SHA1_CTX* ctx) { + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; + ctx->state[4] = 0xC3D2E1F0; + ctx->count[0] = ctx->count[1] = 0; +} + +void SHA1_Update(SHA1_CTX* ctx, const uint8_t* data, uint32_t len) { + uint32_t i, j; + + j = (ctx->count[0] >> 3) & 63; + if ((ctx->count[0] += len << 3) < (len << 3)) + ctx->count[1]++; + ctx->count[1] += (len >> 29); + + if ((j + len) > 63) { + memcpy(&ctx->buffer[j], data, (i = 64 - j)); + SHA1_Transform(ctx, ctx->buffer); + for ( ; i + 63 < len; i += 64) + SHA1_Transform(ctx, &data[i]); + j = 0; + } else { + i = 0; + } + memcpy(&ctx->buffer[j], &data[i], len - i); +} + +void SHA1_Final(uint8_t digest[20], SHA1_CTX* ctx) { + uint8_t finalcount[8]; + uint8_t c; + uint32_t i; + + for (i = 0; i < 8; i++) + finalcount[i] = (uint8_t)((ctx->count[(i >= 4 ? 0 : 1)] + >> ((3 - (i & 3)) * 8)) & 255); + + c = 0x80; + SHA1_Update(ctx, &c, 1); + while ((ctx->count[0] & 504) != 448) { + c = 0x00; + SHA1_Update(ctx, &c, 1); + } + + SHA1_Update(ctx, finalcount, 8); + + for (i = 0; i < 20; i++) + digest[i] = (uint8_t)((ctx->state[i>>2] >> ((3 - (i & 3)) * 8)) & 255); + + memset(ctx, 0, sizeof(*ctx)); +} diff --git a/libfriidump/xbox_ref_bridge.c b/libfriidump/xbox_ref_bridge.c new file mode 100644 index 0000000..a120c28 --- /dev/null +++ b/libfriidump/xbox_ref_bridge.c @@ -0,0 +1,202 @@ +#include "xbox_ref_bridge.h" + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#define _WIN32_WINNT 0x0500 +#include +#include +#include +#include +#include "xbox_ref/utils.h" +#include "xbox_ref/unlock.h" +#include "xbox_ref/xbox_ref_log.h" + +#define printf xbox_ref_printf + +static char xbox_ref_drive_letter_from_device(const char *device) +{ + if (!device || !device[0]) return 0; + if (device[0] && device[1] == ':') return device[0]; + if (device[0] == '\\' && device[1] == '\\' && device[2] == '.' && device[3] == '\\' && device[4] && device[5] == ':') return device[4]; + return device[0]; +} + +static int xbox_ref_gdr8050l_dump_core(HANDLE hDevice, const char *device, const char *filename, char mode, BOOL ownsHandle) +{ + char driveLetter; + XboxGameInfo game; + BOOL isDualLayer = FALSE; + bool isDualLayerBool = false; + char mediaId[33] = {0}; + char finalFilename[MAX_PATH] = {0}; + BOOL filenameOverride; + uint32_t totalSectors = 0; + DWORD dwBytesReturned = 0; + BOOL volumeLocked = FALSE; + BOOL ok = FALSE; + + filenameOverride = (filename && filename[0]); + + if (mode != '1' && mode != '2') { + xbox_ref_log_open_for_target(filename, 0); + printf("[XBOX-REF] Fatal: unsupported Xbox reference dump mode '%c'.\n", mode); + xbox_ref_log_close(); + return 1; + } + + driveLetter = xbox_ref_drive_letter_from_device(device); + if (!driveLetter) { + xbox_ref_log_open_for_target(filename, 0); + printf("[XBOX-REF] Fatal: could not parse drive letter from device '%s'.\n", device ? device : "(null)"); + xbox_ref_log_close(); + return 1; + } + + xbox_ref_log_open_for_target(filename, driveLetter); + printf("[XBOX-REF] Using copied original GDR-8050L dumper flow for drive %c:.\n", driveLetter); + printf("[XBOX-REF] Handle mode: %s.\n", ownsHandle ? "opened by xbox_ref wrapper" : "using FriiDump's existing drive HANDLE"); + printf("[XBOX-REF] Preserving reference timing sleeps: tray 3000ms, ready-settle 1500ms/10000ms fallback, RefreshVolume 1000ms + post-refresh 2000ms.\n"); + + if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL) { + printf("[XBOX-REF] Fatal: invalid drive handle for %c:.\n", driveLetter); + goto cleanup; + } + + printf("Opening drive %c: for Atomic Handshake...\n", driveLetter); + EnsureDriveReady(hDevice, 30000); + UnlockDrive(hDevice); + + AutomateTrayCycle(hDevice); + + printf("Re-applying 8050L Handshake after Media Change...\n"); + UnlockDrive(hDevice); + + RefreshVolume(hDevice); + Sleep(2000); + EnsureDriveReady(hDevice, 30000); + + printf("Setting drive to maximum performance mode...\n"); + SetDriveSpeedMax(hDevice); + + if (!DeviceIoControl(hDevice, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0, &dwBytesReturned, NULL)) { + printf("[!] Warning: Could not lock volume. Close any open folders pointing to the drive.\n"); + printf("[XBOX-REF] The reference dumper treats this as fatal before XBE/title probing.\n"); + goto cleanup; + } + volumeLocked = TRUE; + + printf("Searching for Xbox Game Signature (Raw SCSI Scan)...\n"); + game = GetXboxGameInfo(hDevice); + printf("Game Title Raw: %s\n", game.TitleName); + ReadXboxGameDir(hDevice); + + totalSectors = GetXboxPhysicalSectors(hDevice); + isDualLayerBool = false; + GetDiscMetadata(hDevice, &totalSectors, &isDualLayerBool, &game); + isDualLayer = isDualLayerBool ? TRUE : FALSE; + GetMediaID(hDevice, mediaId); + + if (game.Success) { + char titleCopy[sizeof(game.TitleName)]; + memset(titleCopy, 0, sizeof(titleCopy)); + strncpy(titleCopy, game.TitleName, sizeof(titleCopy) - 1); + SanitizeFilename(titleCopy); + if (filenameOverride) { + strncpy(finalFilename, filename, sizeof(finalFilename) - 1); + finalFilename[sizeof(finalFilename) - 1] = '\0'; + printf("[XBOX-REF] Output filename override: %s\n", finalFilename); + } else { + snprintf(finalFilename, sizeof(finalFilename), "%s[%s].%s", + titleCopy[0] ? titleCopy : "XboxDisc", + mediaId[0] ? mediaId : "UNKNOWN_ID", + (mode == '1') ? "iso" : "xiso"); + printf("[XBOX-REF] XBE/DMI-derived output filename: %s\n", finalFilename); + xbox_ref_log_retarget(finalFilename); + } + DisplayXboxGameInfo(game); + printf("\n--- DISC INFORMATION ---\n"); + printf("Physical Sectors: %u\n", totalSectors); + printf("Layers: %d\n", isDualLayer ? 2 : 1); + printf("Media ID: %s\n", mediaId); + printf("Target File: %s\n", finalFilename); + printf("-------------------------------\n\n"); + } else { + if (filenameOverride) { + strncpy(finalFilename, filename, sizeof(finalFilename) - 1); + finalFilename[sizeof(finalFilename) - 1] = '\0'; + printf("Error: Could not retrieve Xbox game information; using override filename %s.\n", finalFilename); + } else { + printf("Error: Could not retrieve Xbox game information and no output filename override was supplied.\n"); + printf(" Rerun with -i or -X if metadata probing cannot complete.\n"); + goto cleanup; + } + } + + if (totalSectors != (uint32_t)(REDUMP_SECTORS)) { + printf("[WARNING] Unlocked drive-reported sectors: %u does not match expected Xbox game-view sectors: %u\n", totalSectors, (uint32_t)(REDUMP_SECTORS)); + printf("-Homebrew games may have different sizes.\n\n"); + } + + if (totalSectors == 0) { + printf("Error: Could not determine total sectors for dumping.\n"); + goto cleanup; + } + + printf("Starting dump with copied DumpXboxGameDisc() mode %c...\n", mode); + DumpXboxGameDisc(hDevice, finalFilename, mode, totalSectors, isDualLayerBool, FALSE); + ok = TRUE; + +cleanup: + if (volumeLocked) + DeviceIoControl(hDevice, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0, &dwBytesReturned, NULL); + if (ownsHandle && hDevice != INVALID_HANDLE_VALUE && hDevice != NULL) { + printf("Issuing STOP UNIT / spin-down after dump attempt... "); + if (StopDriveUnit(hDevice)) printf("OK\n"); else printf("FAILED\n"); + CloseDrive(hDevice); + } else { + printf("[XBOX-REF] Returning to FriiDump; final STOP UNIT cleanup will use the same handle.\n"); + } + printf("[XBOX-REF] Log file complete: %s\n", xbox_ref_log_path() ? xbox_ref_log_path() : "(unavailable)"); + xbox_ref_log_close(); + return ok ? 0 : 1; +} + +int xbox_ref_gdr8050l_dump_with_handle(void *native_handle, const char *device, const char *filename, char mode) +{ + return xbox_ref_gdr8050l_dump_core((HANDLE)native_handle, device, filename, mode, FALSE); +} + +int xbox_ref_gdr8050l_dump(const char *device, const char *filename, char mode) +{ + char driveLetter; + HANDLE hDevice; + + driveLetter = xbox_ref_drive_letter_from_device(device); + if (!driveLetter) { + xbox_ref_log_open_for_target(filename, 0); + printf("[XBOX-REF] Fatal: could not parse drive letter from device '%s'.\n", device ? device : "(null)"); + xbox_ref_log_close(); + return 1; + } + + hDevice = OpenDrive(driveLetter); + if (hDevice == INVALID_HANDLE_VALUE) { + xbox_ref_log_open_for_target(filename, driveLetter); + printf("[XBOX-REF] Fatal: Cannot open drive %c:. Run as Administrator.\n", driveLetter); + xbox_ref_log_close(); + return 1; + } + + return xbox_ref_gdr8050l_dump_core(hDevice, device, filename, mode, TRUE); +} +#else +int xbox_ref_gdr8050l_dump_with_handle(void *native_handle, const char *device, const char *filename, char mode) +{ + (void)native_handle; (void)device; (void)filename; (void)mode; return 1; +} + +int xbox_ref_gdr8050l_dump(const char *device, const char *filename, char mode) +{ + (void)device; (void)filename; (void)mode; return 1; +} +#endif diff --git a/libfriidump/xbox_ref_bridge.h b/libfriidump/xbox_ref_bridge.h new file mode 100644 index 0000000..6da040b --- /dev/null +++ b/libfriidump/xbox_ref_bridge.h @@ -0,0 +1,15 @@ +#ifndef XBOX_REF_BRIDGE_H_INCLUDED +#define XBOX_REF_BRIDGE_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + +int xbox_ref_gdr8050l_dump_with_handle(void *native_handle, const char *device, const char *filename, char mode); +int xbox_ref_gdr8050l_dump(const char *device, const char *filename, char mode); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libmultihash/CMakeLists.txt b/libmultihash/CMakeLists.txt new file mode 100644 index 0000000..89f0fc7 --- /dev/null +++ b/libmultihash/CMakeLists.txt @@ -0,0 +1,79 @@ +# Create a library called "Hello" which includes the source file "hello.cxx". +# The extension is already found. Any number of sources could be listed here. +add_library ( + multihashlib + ${libmultihash_type} + #SHARED + #STATIC + + crc32.c + edonkey.c + md4.c + md5.c + multihash.c + sha1.c + sha2.c + crc32.h + edonkey.h + md4.h + md5.h + multihash.h + sha1.h + sha2.h +) + +set_target_properties (multihashlib PROPERTIES OUTPUT_NAME "multihash") + +# Before making a release, the LTVERSION string should be modified. +# The string is of the form CURRENT:REVISION:AGE. +# +# CURRENT (C) +# The most recent interface number that this library implements. +# +# REVISION (R) +# The implementation number that this library implements. +# +# AGE (A) +# The difference between the newest and oldest interfaces that this +# library implements. In other works, the library implements all the +# interface numbers in the range from number 'CURRENT - AGE' to +# 'CURRENT'. +# +# This means that: +# +# - If interfaces have been changed or added, but binary compatibility has +# been preserved, change to C+1:0:A+1 +# +# - If binary compatibility has been broken (eg removed or changed +# interfaces) change to C+1:0:0 +# +# - If the interface is the same as the previous version, change to C:R+1:A +# +set_target_properties (multihashlib PROPERTIES SOVERSION 1.0.0) + + +#get_target_property(libhash_type multihashlib TYPE) +if (WIN32) + if (libhash_type STREQUAL "SHARED") +# MESSAGE ("Building libmultihash DLL") + ADD_DEFINITIONS (-DMULTIHASH_BUILD_DLL) + + set_target_properties (multihashlib PROPERTIES DEFINE_SYMBOL MULTIHASH_EXPORTS) + + install ( + TARGETS multihashlib + RUNTIME DESTINATION / + #ARCHIVE DESTINATION lib + ) + endif (libhash_type STREQUAL "SHARED") +else (WIN32) + # Install stuff, only if a shared library is being built + if (libhash_type STREQUAL "SHARED") + install ( + TARGETS multihashlib + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib/static + ) + endif (libhash_type STREQUAL "SHARED") +endif (WIN32) + diff --git a/libmultihash/crc32.c b/libmultihash/crc32.c new file mode 100644 index 0000000..7bf6894 --- /dev/null +++ b/libmultihash/crc32.c @@ -0,0 +1,120 @@ +/* + * Copyright (c) 1995, Edward B. Hamrick + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that + * + * (i) the above copyright notice and the text in this "C" comment block + * appear in all copies of the software and related documentation, and + * + * (ii) any modifications to this source file must be sent, via e-mail + * to the copyright owner (currently hamrick@primenet.com) within + * 30 days of such modification. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL EDWARD B. HAMRICK BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "crc32.h" + +/* This is a pre-computed table to make crc computations efficient */ +static unsigned long crctable[] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, + 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, + 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, + 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, + 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, + 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, + 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, + 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, + 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, + 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, + 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, + 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, + 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, + 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, + 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, + 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, + 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, + 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, + 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, + 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, + 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, + 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, + 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, + 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, + 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, + 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, + 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL +}; + +/* + * This CRC algorithm is the same as that used in zip. Normally it + * should be initialized with 0xffffffff, and the final CRC stored + * should be crc ^ 0xffffffff. + * + * It implements the polynomial: + * + * x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1 + */ + +unsigned long CrcUpdate( /* returns updated crc */ + unsigned long crc, /* starting crc */ + unsigned char *buffer, /* buffer to use to update crc */ + long length /* length of buffer */ +) +{ + long i; + + for (i=0; i> 8); + } + + return crc; +} diff --git a/libmultihash/crc32.h b/libmultihash/crc32.h new file mode 100644 index 0000000..d1cf7f1 --- /dev/null +++ b/libmultihash/crc32.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 1995, Edward B. Hamrick + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that + * + * (i) the above copyright notice and the text in this "C" comment block + * appear in all copies of the software and related documentation, and + * + * (ii) any modifications to this source file must be sent, via e-mail + * to the copyright owner (currently hamrick@primenet.com) within + * 30 days of such modification. + * + * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, + * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY + * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + * + * IN NO EVENT SHALL EDWARD B. HAMRICK BE LIABLE FOR ANY SPECIAL, INCIDENTAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF + * THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This CRC algorithm is the same as that used in zip. Normally it + * should be initialized with 0xffffffff, and the final CRC stored + * should be crc ^ 0xffffffff. + * + * It implements the polynomial: + * + * x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1 + */ + +#ifndef __CRC_H +#define __CRC_H + +#ifdef __cplusplus +extern "C" { +#endif + +unsigned long CrcUpdate( /* returns updated crc */ + unsigned long crc, /* starting crc */ + unsigned char *buffer, /* buffer to use to update crc */ + long length /* length of buffer */ +); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libmultihash/edonkey.c b/libmultihash/edonkey.c new file mode 100644 index 0000000..44beba5 --- /dev/null +++ b/libmultihash/edonkey.c @@ -0,0 +1,83 @@ +/*************************************************************************** + * Copyright (C) 2007 by SukkoPera * + * sukkopera@sukkology.net * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/* As Wikipedia (http://en.wikipedia.org/wiki/Ed2k_link) says: + * The ed2k hash function is a MD4 root hash of a MD4 hash list, and gives a + * different result than simply MD4: The file data is divided into full + * chunks of 9728000 bytes plus a remainder chunk, and a separate 128-bit + * MD4 checksum is computed for each. The ed2k hash is computed by + * concatenating the chunks' MD4 checksums in order and hashing the result + *using MD4. + */ + +#include +#include "edonkey.h" +#include "md4.h" + +void ed2khash_starts (ed2khash_context *ctx) { + md4_starts (&(ctx -> md4cur)); + md4_starts (&(ctx -> md4final)); + ctx -> bytes_processed = 0; + ctx -> chunks = 0; + + return; +} + + +void ed2khash_update (ed2khash_context *ctx, unsigned char *input, int ilen) { + unsigned long x; + + while (ilen > 0) { + if (ctx -> bytes_processed + ilen >= ED2KHASH_CHUNKSIZE) + x = ED2KHASH_CHUNKSIZE - ctx -> bytes_processed; + else + x = ilen; + + md4_update (&(ctx -> md4cur), input, x); + + if ((ctx -> bytes_processed += x) % ED2KHASH_CHUNKSIZE == 0) { + /* End of a chunk, save current MD4 and start a new one */ + md4_finish (&(ctx -> md4cur), ctx -> lastmd4); + md4_starts (&(ctx -> md4cur)); + ctx -> bytes_processed = 0; + ctx -> chunks++; + + md4_update (&(ctx -> md4final), ctx -> lastmd4, MD4_DIGESTSIZE); + } + ilen -= x; + input += x; + } + + return; +} + + +void ed2khash_finish (ed2khash_context *ctx, unsigned char *output) { + if (ctx -> chunks > 0) { + md4_finish (&(ctx -> md4cur), ctx -> lastmd4); + md4_update (&(ctx -> md4final), ctx -> lastmd4, MD4_DIGESTSIZE); + md4_finish (&(ctx -> md4final), output); + } else { + /* If we have a single chunk, use its MD4 straight away */ + md4_finish (&(ctx -> md4cur), output); + } + + return; +} diff --git a/libmultihash/edonkey.h b/libmultihash/edonkey.h new file mode 100644 index 0000000..3d9d0a1 --- /dev/null +++ b/libmultihash/edonkey.h @@ -0,0 +1,36 @@ +/*************************************************************************** + * Copyright (C) 2007 by SukkoPera * + * sukkopera@sukkology.net * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "md4.h" + +#define ED2KHASH_CHUNKSIZE 9728000 +#define MD4_DIGESTSIZE 16 + +typedef struct { + unsigned long bytes_processed; + unsigned int chunks; + md4_context md4cur; + md4_context md4final; + unsigned char lastmd4[MD4_DIGESTSIZE]; +} ed2khash_context; + +void ed2khash_starts (ed2khash_context *ctx); +void ed2khash_update (ed2khash_context *ctx, unsigned char *input, int ilen); +void ed2khash_finish (ed2khash_context *ctx, unsigned char *output); diff --git a/libmultihash/md4.c b/libmultihash/md4.c new file mode 100644 index 0000000..3b2eda0 --- /dev/null +++ b/libmultihash/md4.c @@ -0,0 +1,441 @@ +/* + * RFC 1186/1320 compliant MD4 implementation + * + * Copyright (C) 2006-2007 Christophe Devine + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License, version 2.1 as published by the Free Software Foundation. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +/* + * The MD4 algorithm was designed by Ron Rivest in 1990. + * + * http://www.ietf.org/rfc/rfc1186.txt + * http://www.ietf.org/rfc/rfc1320.txt + */ + +#ifndef _CRT_SECURE_NO_DEPRECATE +#define _CRT_SECURE_NO_DEPRECATE 1 +#endif + +#include +#include + +#include "md4.h" + +/* + * 32-bit integer manipulation macros (little endian) + */ +#ifndef GET_UINT32_LE +#define GET_UINT32_LE(n,b,i) \ +{ \ + (n) = ( (unsigned long) (b)[(i) ] ) \ + | ( (unsigned long) (b)[(i) + 1] << 8 ) \ + | ( (unsigned long) (b)[(i) + 2] << 16 ) \ + | ( (unsigned long) (b)[(i) + 3] << 24 ); \ +} +#endif + +#ifndef PUT_UINT32_LE +#define PUT_UINT32_LE(n,b,i) \ +{ \ + (b)[(i) ] = (unsigned char) ( (n) ); \ + (b)[(i) + 1] = (unsigned char) ( (n) >> 8 ); \ + (b)[(i) + 2] = (unsigned char) ( (n) >> 16 ); \ + (b)[(i) + 3] = (unsigned char) ( (n) >> 24 ); \ +} +#endif + +/* + * MD4 context setup + */ +void md4_starts( md4_context *ctx ) +{ + ctx->total[0] = 0; + ctx->total[1] = 0; + + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; +} + +static void md4_process( md4_context *ctx, unsigned char data[64] ) +{ + unsigned long X[16], A, B, C, D; + + GET_UINT32_LE( X[ 0], data, 0 ); + GET_UINT32_LE( X[ 1], data, 4 ); + GET_UINT32_LE( X[ 2], data, 8 ); + GET_UINT32_LE( X[ 3], data, 12 ); + GET_UINT32_LE( X[ 4], data, 16 ); + GET_UINT32_LE( X[ 5], data, 20 ); + GET_UINT32_LE( X[ 6], data, 24 ); + GET_UINT32_LE( X[ 7], data, 28 ); + GET_UINT32_LE( X[ 8], data, 32 ); + GET_UINT32_LE( X[ 9], data, 36 ); + GET_UINT32_LE( X[10], data, 40 ); + GET_UINT32_LE( X[11], data, 44 ); + GET_UINT32_LE( X[12], data, 48 ); + GET_UINT32_LE( X[13], data, 52 ); + GET_UINT32_LE( X[14], data, 56 ); + GET_UINT32_LE( X[15], data, 60 ); + +#define S(x,n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) + + A = ctx->state[0]; + B = ctx->state[1]; + C = ctx->state[2]; + D = ctx->state[3]; + +#define F(x, y, z) ((x & y) | ((~x) & z)) +#define P(a,b,c,d,x,s) { a += F(b,c,d) + x; a = S(a,s); } + + P( A, B, C, D, X[ 0], 3 ); + P( D, A, B, C, X[ 1], 7 ); + P( C, D, A, B, X[ 2], 11 ); + P( B, C, D, A, X[ 3], 19 ); + P( A, B, C, D, X[ 4], 3 ); + P( D, A, B, C, X[ 5], 7 ); + P( C, D, A, B, X[ 6], 11 ); + P( B, C, D, A, X[ 7], 19 ); + P( A, B, C, D, X[ 8], 3 ); + P( D, A, B, C, X[ 9], 7 ); + P( C, D, A, B, X[10], 11 ); + P( B, C, D, A, X[11], 19 ); + P( A, B, C, D, X[12], 3 ); + P( D, A, B, C, X[13], 7 ); + P( C, D, A, B, X[14], 11 ); + P( B, C, D, A, X[15], 19 ); + +#undef P +#undef F + +#define F(x,y,z) ((x & y) | (x & z) | (y & z)) +#define P(a,b,c,d,x,s) { a += F(b,c,d) + x + 0x5A827999; a = S(a,s); } + + P( A, B, C, D, X[ 0], 3 ); + P( D, A, B, C, X[ 4], 5 ); + P( C, D, A, B, X[ 8], 9 ); + P( B, C, D, A, X[12], 13 ); + P( A, B, C, D, X[ 1], 3 ); + P( D, A, B, C, X[ 5], 5 ); + P( C, D, A, B, X[ 9], 9 ); + P( B, C, D, A, X[13], 13 ); + P( A, B, C, D, X[ 2], 3 ); + P( D, A, B, C, X[ 6], 5 ); + P( C, D, A, B, X[10], 9 ); + P( B, C, D, A, X[14], 13 ); + P( A, B, C, D, X[ 3], 3 ); + P( D, A, B, C, X[ 7], 5 ); + P( C, D, A, B, X[11], 9 ); + P( B, C, D, A, X[15], 13 ); + +#undef P +#undef F + +#define F(x,y,z) (x ^ y ^ z) +#define P(a,b,c,d,x,s) { a += F(b,c,d) + x + 0x6ED9EBA1; a = S(a,s); } + + P( A, B, C, D, X[ 0], 3 ); + P( D, A, B, C, X[ 8], 9 ); + P( C, D, A, B, X[ 4], 11 ); + P( B, C, D, A, X[12], 15 ); + P( A, B, C, D, X[ 2], 3 ); + P( D, A, B, C, X[10], 9 ); + P( C, D, A, B, X[ 6], 11 ); + P( B, C, D, A, X[14], 15 ); + P( A, B, C, D, X[ 1], 3 ); + P( D, A, B, C, X[ 9], 9 ); + P( C, D, A, B, X[ 5], 11 ); + P( B, C, D, A, X[13], 15 ); + P( A, B, C, D, X[ 3], 3 ); + P( D, A, B, C, X[11], 9 ); + P( C, D, A, B, X[ 7], 11 ); + P( B, C, D, A, X[15], 15 ); + +#undef F +#undef P + + ctx->state[0] += A; + ctx->state[1] += B; + ctx->state[2] += C; + ctx->state[3] += D; +} + +/* + * MD4 process buffer + */ +void md4_update( md4_context *ctx, unsigned char *input, int ilen ) +{ + int fill; + unsigned long left; + + if( ilen <= 0 ) + return; + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += ilen; + ctx->total[0] &= 0xFFFFFFFF; + + if( ctx->total[0] < (unsigned long) ilen ) + ctx->total[1]++; + + if( left && ilen >= fill ) + { + memcpy( (void *) (ctx->buffer + left), + (void *) input, fill ); + md4_process( ctx, ctx->buffer ); + input += fill; + ilen -= fill; + left = 0; + } + + while( ilen >= 64 ) + { + md4_process( ctx, input ); + input += 64; + ilen -= 64; + } + + if( ilen > 0 ) + { + memcpy( (void *) (ctx->buffer + left), + (void *) input, ilen ); + } +} + +static const unsigned char md4_padding[64] = +{ + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* + * MD4 final digest + */ +void md4_finish( md4_context *ctx, unsigned char *output ) +{ + unsigned long last, padn; + unsigned long high, low; + unsigned char msglen[8]; + + high = ( ctx->total[0] >> 29 ) + | ( ctx->total[1] << 3 ); + low = ( ctx->total[0] << 3 ); + + PUT_UINT32_LE( low, msglen, 0 ); + PUT_UINT32_LE( high, msglen, 4 ); + + last = ctx->total[0] & 0x3F; + padn = ( last < 56 ) ? ( 56 - last ) : ( 120 - last ); + + md4_update( ctx, (unsigned char *) md4_padding, padn ); + md4_update( ctx, msglen, 8 ); + + PUT_UINT32_LE( ctx->state[0], output, 0 ); + PUT_UINT32_LE( ctx->state[1], output, 4 ); + PUT_UINT32_LE( ctx->state[2], output, 8 ); + PUT_UINT32_LE( ctx->state[3], output, 12 ); +} + +/* + * Output = MD4( input buffer ) + */ +void md4( unsigned char *input, int ilen, + unsigned char *output ) +{ + md4_context ctx; + + md4_starts( &ctx ); + md4_update( &ctx, input, ilen ); + md4_finish( &ctx, output ); + + memset( &ctx, 0, sizeof( md4_context ) ); +} + +/* + * Output = MD4( file contents ) + */ +int md4_file( char *path, unsigned char *output ) +{ + FILE *f; + size_t n; + md4_context ctx; + unsigned char buf[1024]; + + if( ( f = fopen( path, "rb" ) ) == NULL ) + return( 1 ); + + md4_starts( &ctx ); + + while( ( n = fread( buf, 1, sizeof( buf ), f ) ) > 0 ) + md4_update( &ctx, buf, (int) n ); + + md4_finish( &ctx, output ); + + memset( &ctx, 0, sizeof( md4_context ) ); + + if( ferror( f ) != 0 ) + { + fclose( f ); + return( 2 ); + } + + fclose( f ); + return( 0 ); +} + +/* + * MD4 HMAC context setup + */ +void md4_hmac_starts( md4_context *ctx, + unsigned char *key, int keylen ) +{ + int i; + + memset( ctx->ipad, 0x36, 64 ); + memset( ctx->opad, 0x5C, 64 ); + + for( i = 0; i < keylen; i++ ) + { + if( i >= 64 ) break; + + ctx->ipad[i] ^= key[i]; + ctx->opad[i] ^= key[i]; + } + + md4_starts( ctx ); + md4_update( ctx, ctx->ipad, 64 ); +} + +/* + * MD4 HMAC process buffer + */ +void md4_hmac_update( md4_context *ctx, + unsigned char *input, int ilen ) +{ + md4_update( ctx, input, ilen ); +} + +/* + * MD4 HMAC final digest + */ +void md4_hmac_finish( md4_context *ctx, unsigned char *output ) +{ + unsigned char tmpbuf[16]; + + md4_finish( ctx, tmpbuf ); + md4_starts( ctx ); + md4_update( ctx, ctx->opad, 64 ); + md4_update( ctx, tmpbuf, 16 ); + md4_finish( ctx, output ); + + memset( tmpbuf, 0, sizeof( tmpbuf ) ); +} + +/* + * Output = HMAC-MD4( hmac key, input buffer ) + */ +void md4_hmac( unsigned char *key, int keylen, + unsigned char *input, int ilen, + unsigned char *output ) +{ + md4_context ctx; + + md4_hmac_starts( &ctx, key, keylen ); + md4_hmac_update( &ctx, input, ilen ); + md4_hmac_finish( &ctx, output ); + + memset( &ctx, 0, sizeof( md4_context ) ); +} + +static const char _md4_src[] = "_md4_src"; + +#if defined(SELF_TEST) +/* + * RFC 1320 test vectors + */ +static const char md4_test_str[7][81] = +{ + { "" }, + { "a" }, + { "abc" }, + { "message digest" }, + { "abcdefghijklmnopqrstuvwxyz" }, + { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" }, + { "12345678901234567890123456789012345678901234567890123456789012" \ + "345678901234567890" } +}; + +static const unsigned char md4_test_sum[7][16] = +{ + { 0x31, 0xD6, 0xCF, 0xE0, 0xD1, 0x6A, 0xE9, 0x31, + 0xB7, 0x3C, 0x59, 0xD7, 0xE0, 0xC0, 0x89, 0xC0 }, + { 0xBD, 0xE5, 0x2C, 0xB3, 0x1D, 0xE3, 0x3E, 0x46, + 0x24, 0x5E, 0x05, 0xFB, 0xDB, 0xD6, 0xFB, 0x24 }, + { 0xA4, 0x48, 0x01, 0x7A, 0xAF, 0x21, 0xD8, 0x52, + 0x5F, 0xC1, 0x0A, 0xE8, 0x7A, 0xA6, 0x72, 0x9D }, + { 0xD9, 0x13, 0x0A, 0x81, 0x64, 0x54, 0x9F, 0xE8, + 0x18, 0x87, 0x48, 0x06, 0xE1, 0xC7, 0x01, 0x4B }, + { 0xD7, 0x9E, 0x1C, 0x30, 0x8A, 0xA5, 0xBB, 0xCD, + 0xEE, 0xA8, 0xED, 0x63, 0xDF, 0x41, 0x2D, 0xA9 }, + { 0x04, 0x3F, 0x85, 0x82, 0xF2, 0x41, 0xDB, 0x35, + 0x1C, 0xE6, 0x27, 0xE1, 0x53, 0xE7, 0xF0, 0xE4 }, + { 0xE3, 0x3B, 0x4D, 0xDC, 0x9C, 0x38, 0xF2, 0x19, + 0x9C, 0x3E, 0x7B, 0x16, 0x4F, 0xCC, 0x05, 0x36 } +}; + +/* + * Checkup routine + */ +int md4_self_test( int verbose ) +{ + int i; + unsigned char md4sum[16]; + + for( i = 0; i < 7; i++ ) + { + if( verbose != 0 ) + printf( " MD4 test #%d: ", i + 1 ); + + md4( (unsigned char *) md4_test_str[i], + strlen( md4_test_str[i] ), md4sum ); + + if( memcmp( md4sum, md4_test_sum[i], 16 ) != 0 ) + { + if( verbose != 0 ) + printf( "failed\n" ); + + return( 1 ); + } + + if( verbose != 0 ) + printf( "passed\n" ); + } + + if( verbose != 0 ) + printf( "\n" ); + + return( 0 ); +} +#else +int md4_self_test( int verbose ) +{ + return( 0 ); +} +#endif diff --git a/libmultihash/md4.h b/libmultihash/md4.h new file mode 100644 index 0000000..c0cb343 --- /dev/null +++ b/libmultihash/md4.h @@ -0,0 +1,121 @@ +/** + * \file md4.h + */ +#ifndef _MD4_H +#define _MD4_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief MD4 context structure + */ +typedef struct +{ + unsigned long total[2]; /*!< number of bytes processed */ + unsigned long state[4]; /*!< intermediate digest state */ + unsigned char buffer[64]; /*!< data block being processed */ + unsigned char ipad[64]; /*!< HMAC: inner padding */ + unsigned char opad[64]; /*!< HMAC: outer padding */ +} +md4_context; + +/** + * \brief MD4 context setup + * + * \param ctx context to be initialized + */ +void md4_starts( md4_context *ctx ); + +/** + * \brief MD4 process buffer + * + * \param ctx MD4 context + * \param input buffer holding the data + * \param ilen length of the input data + */ +void md4_update( md4_context *ctx, unsigned char *input, int ilen ); + +/** + * \brief MD4 final digest + * + * \param ctx MD4 context + * \param output MD4 checksum result + */ +void md4_finish( md4_context *ctx, unsigned char *output ); + +/** + * \brief Output = MD4( input buffer ) + * + * \param input buffer holding the data + * \param ilen length of the input data + * \param output MD4 checksum result + */ +void md4( unsigned char *input, int ilen, + unsigned char *output ); + +/** + * \brief Output = MD4( file contents ) + * + * \param path input file name + * \param output MD4 checksum result + * + * \return 0 if successful, 1 if fopen failed, + * or 2 if fread failed + */ +int md4_file( char *path, unsigned char *output ); + +/** + * \brief MD4 HMAC context setup + * + * \param ctx HMAC context to be initialized + * \param key HMAC secret key + * \param keylen length of the HMAC key + */ +void md4_hmac_starts( md4_context *ctx, + unsigned char *key, int keylen ); + +/** + * \brief MD4 HMAC process buffer + * + * \param ctx HMAC context + * \param input buffer holding the data + * \param ilen length of the input data + */ +void md4_hmac_update( md4_context *ctx, + unsigned char *input, int ilen ); + +/** + * \brief MD4 HMAC final digest + * + * \param ctx HMAC context + * \param output MD4 HMAC checksum result + */ +void md4_hmac_finish( md4_context *ctx, unsigned char *output ); + +/** + * \brief Output = HMAC-MD4( hmac key, input buffer ) + * + * \param key HMAC secret key + * \param keylen length of the HMAC key + * \param input buffer holding the data + * \param ilen length of the input data + * \param output HMAC-MD4 result + */ +void md4_hmac( unsigned char *key, int keylen, + unsigned char *input, int ilen, + unsigned char *output ); + +/** + * \brief Checkup routine + * + * \return 0 if successful, or 1 if the test failed + */ +int md4_self_test( int verbose ); + +#ifdef __cplusplus +} +#endif + +#endif /* md4.h */ diff --git a/libmultihash/md5.c b/libmultihash/md5.c new file mode 100644 index 0000000..543e27f --- /dev/null +++ b/libmultihash/md5.c @@ -0,0 +1,335 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WjCryptLib_Md5 +// +// Implementation of MD5 hash function. Originally written by Alexander Peslyak. Modified by WaterJuice retaining +// Public Domain license. +// +// This is free and unencumbered software released into the public domain - June 2013 waterjuice.org +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// IMPORTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "md5.h" +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// INTERNAL FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// F, G, H, I +// +// The basic MD5 functions. F and G are optimised compared to their RFC 1321 definitions for architectures that lack +// an AND-NOT instruction, just like in Colin Plumb's implementation. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define F( x, y, z ) ( (z) ^ ((x) & ((y) ^ (z))) ) +#define G( x, y, z ) ( (y) ^ ((z) & ((x) ^ (y))) ) +#define H( x, y, z ) ( (x) ^ (y) ^ (z) ) +#define I( x, y, z ) ( (y) ^ ((x) | ~(z)) ) + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// STEP +// +// The MD5 transformation for all four rounds. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define STEP( f, a, b, c, d, x, t, s ) \ + (a) += f((b), (c), (d)) + (x) + (t); \ + (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ + (a) += (b); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// TransformFunction +// +// This processes one or more 64-byte data blocks, but does NOT update the bit counters. There are no alignment +// requirements. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static +void* + TransformFunction + ( + Md5Context* ctx, + void const* data, + uintmax_t size + ) +{ + uint8_t* ptr; + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; + uint32_t saved_a; + uint32_t saved_b; + uint32_t saved_c; + uint32_t saved_d; + + #define GET(n) (ctx->block[(n)]) + #define SET(n) (ctx->block[(n)] = \ + ((uint32_t)ptr[(n)*4 + 0] << 0 ) \ + | ((uint32_t)ptr[(n)*4 + 1] << 8 ) \ + | ((uint32_t)ptr[(n)*4 + 2] << 16) \ + | ((uint32_t)ptr[(n)*4 + 3] << 24) ) + + ptr = (uint8_t*)data; + + a = ctx->a; + b = ctx->b; + c = ctx->c; + d = ctx->d; + + do + { + saved_a = a; + saved_b = b; + saved_c = c; + saved_d = d; + + // Round 1 + STEP( F, a, b, c, d, SET(0), 0xd76aa478, 7 ) + STEP( F, d, a, b, c, SET(1), 0xe8c7b756, 12 ) + STEP( F, c, d, a, b, SET(2), 0x242070db, 17 ) + STEP( F, b, c, d, a, SET(3), 0xc1bdceee, 22 ) + STEP( F, a, b, c, d, SET(4), 0xf57c0faf, 7 ) + STEP( F, d, a, b, c, SET(5), 0x4787c62a, 12 ) + STEP( F, c, d, a, b, SET(6), 0xa8304613, 17 ) + STEP( F, b, c, d, a, SET(7), 0xfd469501, 22 ) + STEP( F, a, b, c, d, SET(8 ), 0x698098d8, 7 ) + STEP( F, d, a, b, c, SET(9 ), 0x8b44f7af, 12 ) + STEP( F, c, d, a, b, SET(10 ), 0xffff5bb1, 17 ) + STEP( F, b, c, d, a, SET(11 ), 0x895cd7be, 22 ) + STEP( F, a, b, c, d, SET(12 ), 0x6b901122, 7 ) + STEP( F, d, a, b, c, SET(13 ), 0xfd987193, 12 ) + STEP( F, c, d, a, b, SET(14 ), 0xa679438e, 17 ) + STEP( F, b, c, d, a, SET(15 ), 0x49b40821, 22 ) + + // Round 2 + STEP( G, a, b, c, d, GET(1), 0xf61e2562, 5 ) + STEP( G, d, a, b, c, GET(6), 0xc040b340, 9 ) + STEP( G, c, d, a, b, GET(11), 0x265e5a51, 14 ) + STEP( G, b, c, d, a, GET(0), 0xe9b6c7aa, 20 ) + STEP( G, a, b, c, d, GET(5), 0xd62f105d, 5 ) + STEP( G, d, a, b, c, GET(10), 0x02441453, 9 ) + STEP( G, c, d, a, b, GET(15), 0xd8a1e681, 14 ) + STEP( G, b, c, d, a, GET(4), 0xe7d3fbc8, 20 ) + STEP( G, a, b, c, d, GET(9), 0x21e1cde6, 5 ) + STEP( G, d, a, b, c, GET(14), 0xc33707d6, 9 ) + STEP( G, c, d, a, b, GET(3), 0xf4d50d87, 14 ) + STEP( G, b, c, d, a, GET(8), 0x455a14ed, 20 ) + STEP( G, a, b, c, d, GET(13), 0xa9e3e905, 5 ) + STEP( G, d, a, b, c, GET(2), 0xfcefa3f8, 9 ) + STEP( G, c, d, a, b, GET(7), 0x676f02d9, 14 ) + STEP( G, b, c, d, a, GET(12), 0x8d2a4c8a, 20 ) + + // Round 3 + STEP( H, a, b, c, d, GET(5), 0xfffa3942, 4 ) + STEP( H, d, a, b, c, GET(8), 0x8771f681, 11 ) + STEP( H, c, d, a, b, GET(11), 0x6d9d6122, 16 ) + STEP( H, b, c, d, a, GET(14), 0xfde5380c, 23 ) + STEP( H, a, b, c, d, GET(1), 0xa4beea44, 4 ) + STEP( H, d, a, b, c, GET(4), 0x4bdecfa9, 11 ) + STEP( H, c, d, a, b, GET(7), 0xf6bb4b60, 16 ) + STEP( H, b, c, d, a, GET(10), 0xbebfbc70, 23 ) + STEP( H, a, b, c, d, GET(13), 0x289b7ec6, 4 ) + STEP( H, d, a, b, c, GET(0), 0xeaa127fa, 11 ) + STEP( H, c, d, a, b, GET(3), 0xd4ef3085, 16 ) + STEP( H, b, c, d, a, GET(6), 0x04881d05, 23 ) + STEP( H, a, b, c, d, GET(9), 0xd9d4d039, 4 ) + STEP( H, d, a, b, c, GET(12), 0xe6db99e5, 11 ) + STEP( H, c, d, a, b, GET(15), 0x1fa27cf8, 16 ) + STEP( H, b, c, d, a, GET(2), 0xc4ac5665, 23 ) + + // Round 4 + STEP( I, a, b, c, d, GET(0), 0xf4292244, 6 ) + STEP( I, d, a, b, c, GET(7), 0x432aff97, 10 ) + STEP( I, c, d, a, b, GET(14), 0xab9423a7, 15 ) + STEP( I, b, c, d, a, GET(5), 0xfc93a039, 21 ) + STEP( I, a, b, c, d, GET(12), 0x655b59c3, 6 ) + STEP( I, d, a, b, c, GET(3), 0x8f0ccc92, 10 ) + STEP( I, c, d, a, b, GET(10), 0xffeff47d, 15 ) + STEP( I, b, c, d, a, GET(1), 0x85845dd1, 21 ) + STEP( I, a, b, c, d, GET(8), 0x6fa87e4f, 6 ) + STEP( I, d, a, b, c, GET(15), 0xfe2ce6e0, 10 ) + STEP( I, c, d, a, b, GET(6), 0xa3014314, 15 ) + STEP( I, b, c, d, a, GET(13), 0x4e0811a1, 21 ) + STEP( I, a, b, c, d, GET(4), 0xf7537e82, 6 ) + STEP( I, d, a, b, c, GET(11), 0xbd3af235, 10 ) + STEP( I, c, d, a, b, GET(2), 0x2ad7d2bb, 15 ) + STEP( I, b, c, d, a, GET(9), 0xeb86d391, 21 ) + + a += saved_a; + b += saved_b; + c += saved_c; + d += saved_d; + + ptr += 64; + } while( size -= 64 ); + + ctx->a = a; + ctx->b = b; + ctx->c = c; + ctx->d = d; + + #undef GET + #undef SET + + return ptr; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// EXPORTED FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Initialise +// +// Initialises an MD5 Context. Use this to initialise/reset a context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Md5Initialise + ( + Md5Context* Context // [out] + ) +{ + Context->a = 0x67452301; + Context->b = 0xefcdab89; + Context->c = 0x98badcfe; + Context->d = 0x10325476; + + Context->lo = 0; + Context->hi = 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Update +// +// Adds data to the MD5 context. This will process the data and update the internal state of the context. Keep on +// calling this function until all the data has been added. Then call Md5Finalise to calculate the hash. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Md5Update + ( + Md5Context* Context, // [in out] + void const* Buffer, // [in] + uint32_t BufferSize // [in] + ) +{ + uint32_t saved_lo; + uint32_t used; + uint32_t free; + + saved_lo = Context->lo; + if( (Context->lo = (saved_lo + BufferSize) & 0x1fffffff) < saved_lo ) + { + Context->hi++; + } + Context->hi += (uint32_t)( BufferSize >> 29 ); + + used = saved_lo & 0x3f; + + if( used ) + { + free = 64 - used; + + if( BufferSize < free ) + { + memcpy( &Context->buffer[used], Buffer, BufferSize ); + return; + } + + memcpy( &Context->buffer[used], Buffer, free ); + Buffer = (uint8_t*)Buffer + free; + BufferSize -= free; + TransformFunction(Context, Context->buffer, 64); + } + + if( BufferSize >= 64 ) + { + Buffer = TransformFunction( Context, Buffer, BufferSize & ~(unsigned long)0x3f ); + BufferSize &= 0x3f; + } + + memcpy( Context->buffer, Buffer, BufferSize ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Finalise +// +// Performs the final calculation of the hash and returns the digest (16 byte buffer containing 128bit hash). After +// calling this, Md5Initialised must be used to reuse the context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Md5Finalise + ( + Md5Context* Context, // [in out] + MD5_HASH* Digest // [in] + ) +{ + uint32_t used; + uint32_t free; + + used = Context->lo & 0x3f; + + Context->buffer[used++] = 0x80; + + free = 64 - used; + + if(free < 8) + { + memset( &Context->buffer[used], 0, free ); + TransformFunction( Context, Context->buffer, 64 ); + used = 0; + free = 64; + } + + memset( &Context->buffer[used], 0, free - 8 ); + + Context->lo <<= 3; + Context->buffer[56] = (uint8_t)( Context->lo ); + Context->buffer[57] = (uint8_t)( Context->lo >> 8 ); + Context->buffer[58] = (uint8_t)( Context->lo >> 16 ); + Context->buffer[59] = (uint8_t)( Context->lo >> 24 ); + Context->buffer[60] = (uint8_t)( Context->hi ); + Context->buffer[61] = (uint8_t)( Context->hi >> 8 ); + Context->buffer[62] = (uint8_t)( Context->hi >> 16 ); + Context->buffer[63] = (uint8_t)( Context->hi >> 24 ); + + TransformFunction( Context, Context->buffer, 64 ); + + Digest->bytes[0] = (uint8_t)( Context->a ); + Digest->bytes[1] = (uint8_t)( Context->a >> 8 ); + Digest->bytes[2] = (uint8_t)( Context->a >> 16 ); + Digest->bytes[3] = (uint8_t)( Context->a >> 24 ); + Digest->bytes[4] = (uint8_t)( Context->b ); + Digest->bytes[5] = (uint8_t)( Context->b >> 8 ); + Digest->bytes[6] = (uint8_t)( Context->b >> 16 ); + Digest->bytes[7] = (uint8_t)( Context->b >> 24 ); + Digest->bytes[8] = (uint8_t)( Context->c ); + Digest->bytes[9] = (uint8_t)( Context->c >> 8 ); + Digest->bytes[10] = (uint8_t)( Context->c >> 16 ); + Digest->bytes[11] = (uint8_t)( Context->c >> 24 ); + Digest->bytes[12] = (uint8_t)( Context->d ); + Digest->bytes[13] = (uint8_t)( Context->d >> 8 ); + Digest->bytes[14] = (uint8_t)( Context->d >> 16 ); + Digest->bytes[15] = (uint8_t)( Context->d >> 24 ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Calculate +// +// Combines Md5Initialise, Md5Update, and Md5Finalise into one function. Calculates the MD5 hash of the buffer. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Md5Calculate + ( + void const* Buffer, // [in] + uint32_t BufferSize, // [in] + MD5_HASH* Digest // [in] + ) +{ + Md5Context context; + + Md5Initialise( &context ); + Md5Update( &context, Buffer, BufferSize ); + Md5Finalise( &context, Digest ); +} diff --git a/libmultihash/md5.h b/libmultihash/md5.h new file mode 100644 index 0000000..457b3f2 --- /dev/null +++ b/libmultihash/md5.h @@ -0,0 +1,99 @@ +#ifndef __MD5_H__ +#define __MD5_H__ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WjCryptLib_Md5 +// +// Implementation of MD5 hash function. Originally written by Alexander Peslyak. Modified by WaterJuice retaining +// Public Domain license. +// +// This is free and unencumbered software released into the public domain - June 2013 waterjuice.org +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// IMPORTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// TYPES +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Md5Context - This must be initialised using Md5Initialised. Do not modify the contents of this structure directly. +typedef struct +{ + uint32_t lo; + uint32_t hi; + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; + uint8_t buffer[64]; + uint32_t block[16]; +} Md5Context; + +#define MD5_HASH_SIZE ( 128 / 8 ) + +typedef struct +{ + uint8_t bytes [MD5_HASH_SIZE]; +} MD5_HASH; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Initialise +// +// Initialises an MD5 Context. Use this to initialise/reset a context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Md5Initialise + ( + Md5Context* Context // [out] + ); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Update +// +// Adds data to the MD5 context. This will process the data and update the internal state of the context. Keep on +// calling this function until all the data has been added. Then call Md5Finalise to calculate the hash. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Md5Update + ( + Md5Context* Context, // [in out] + void const* Buffer, // [in] + uint32_t BufferSize // [in] + ); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Finalise +// +// Performs the final calculation of the hash and returns the digest (16 byte buffer containing 128bit hash). After +// calling this, Md5Initialised must be used to reuse the context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Md5Finalise + ( + Md5Context* Context, // [in out] + MD5_HASH* Digest // [in] + ); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Md5Calculate +// +// Combines Md5Initialise, Md5Update, and Md5Finalise into one function. Calculates the MD5 hash of the buffer. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Md5Calculate + ( + void const* Buffer, // [in] + uint32_t BufferSize, // [in] + MD5_HASH* Digest // [in] + ); +#endif /* __MD5_H__ */ diff --git a/libmultihash/multihash.c b/libmultihash/multihash.c new file mode 100644 index 0000000..9666dfe --- /dev/null +++ b/libmultihash/multihash.c @@ -0,0 +1,151 @@ +/*************************************************************************** + * Copyright (C) 2007 by SukkoPera * + * sukkopera@sukkology.net * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "multihash.h" +#include + +#define READBUF_SIZE 8192 + + +void multihash_init (multihash *mh) { +#ifdef USE_CRC32 + (mh -> crc32_s)[0] = '\0'; + mh -> crc32 = 0xffffffff; +#endif +#ifdef USE_MD4 + (mh -> md4_s)[0] = '\0'; + md4_starts (&(mh -> md4)); +#endif +#ifdef USE_MD5 + (mh -> md5_s)[0] = '\0'; + Md5Initialise (&(mh -> md5)); +#endif +#ifdef USE_ED2K + (mh -> ed2k_s)[0] = '\0'; + ed2khash_starts (&(mh -> ed2k)); +#endif +#ifdef USE_SHA1 + (mh -> sha1_s)[0] = '\0'; + Sha1Initialise (&(mh -> sha1)); +#endif +#ifdef USE_SHA2 + (mh -> sha2_s)[0] = '\0'; + Sha256Initialise (&(mh -> sha2)); +#endif + + return; +} + + +void multihash_update (multihash *mh, unsigned char *data, int bytes) { +#ifdef USE_CRC32 + mh -> crc32 = CrcUpdate (mh -> crc32, data, bytes); +#endif +#ifdef USE_MD4 + md4_update (&(mh -> md4), data, bytes); +#endif +#ifdef USE_MD5 + Md5Update (&(mh -> md5), data, bytes); +#endif +#ifdef USE_ED2K + ed2khash_update (&(mh -> ed2k), data, bytes); +#endif +#ifdef USE_SHA1 + Sha1Update (&(mh -> sha1), data, bytes); +#endif +#ifdef USE_SHA2 + Sha256Update (&(mh -> sha2), data, bytes); +#endif + + return; +} + + +void multihash_finish (multihash *mh) { + unsigned char buf[MAX_DIGESTSIZE]; + int bytes; + +#ifdef USE_CRC32 + mh -> crc32 ^= 0xffffffff; + snprintf (mh -> crc32_s, LEN_CRC32 + 1, "%08x", mh -> crc32); +#endif +#ifdef USE_MD4 + md4_finish (&(mh -> md4), buf); + for (bytes = 0; bytes < LEN_MD4 / 2; bytes++) + sprintf (mh -> md4_s + 2*bytes, "%02x", buf[bytes]); + (mh -> md4_s)[LEN_MD4] = '\0'; +#endif +#ifdef USE_MD5 + /* MD5_HASH struct that receives the finalised MD5 hash */ + MD5_HASH md5_hash; + Md5Finalise (&(mh -> md5), &md5_hash); + /* copy the final MD5 hash as hex string to the multihash struct */ + for (bytes = 0; bytes < LEN_MD5 / 2; bytes++) + sprintf (mh -> md5_s + 2*bytes, "%02x", md5_hash.bytes[bytes]); + (mh -> md5_s)[LEN_MD5] = '\0'; +#endif +#ifdef USE_ED2K + ed2khash_finish (&(mh -> ed2k), buf); + for (bytes = 0; bytes < LEN_ED2K / 2; bytes++) + sprintf (mh -> ed2k_s + 2*bytes, "%02x", buf[bytes]); + (mh -> ed2k_s)[LEN_ED2K] = '\0'; +#endif +#ifdef USE_SHA1 + /* SHA1_HASH struct that receives the finalised sha1 hash */ + SHA1_HASH sha1_hash; + Sha1Finalise (&(mh -> sha1), &sha1_hash); + /* copy the final sha1 hash as hex string to the multihash struct */ + for (bytes = 0; bytes < LEN_SHA1 / 2; bytes++) + sprintf (mh -> sha1_s + 2*bytes, "%02x", sha1_hash.bytes[bytes]); + (mh -> sha1_s)[LEN_SHA1] = '\0'; +#endif +#ifdef USE_SHA2 + /* SHA256_HASH struct that receives the finalised sha2-256 hash */ + SHA256_HASH sha2_hash; + Sha256Finalise (&(mh -> sha2), &sha2_hash); + /* copy the final sha2 hash as hex string to the multihash struct */ + for (bytes = 0; bytes < LEN_SHA2 / 2; bytes++) + sprintf (mh -> sha2_s + 2*bytes, "%02x", sha2_hash.bytes[bytes]); + (mh -> sha2_s)[LEN_SHA2] = '\0'; +#endif + + return; +} + + +int multihash_file (multihash *mh, char *filename) { + FILE *fp; + int bytes, out; + unsigned char data[READBUF_SIZE]; + + multihash_init (mh); + if ((fp = fopen (filename, "r"))) { + while ((bytes = fread (data, 1, READBUF_SIZE, fp)) != 0) + multihash_update (mh, data, bytes); + multihash_finish (mh); + fclose (fp); + out = 0; + } else { + /* Cannot open file */ + out = -1; + } + + return (out); +} diff --git a/libmultihash/multihash.h b/libmultihash/multihash.h new file mode 100644 index 0000000..7fda8ce --- /dev/null +++ b/libmultihash/multihash.h @@ -0,0 +1,136 @@ +/*************************************************************************** + * Copyright (C) 2007 by SukkoPera * + * sukkopera@sukkology.net * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef MULTIHASH_H_INCLUDED +#define MULTIHASH_H_INCLUDED + +#ifdef WIN32 +#include + +/* Definition of the types we use for Windows */ +#define u_int8_t BYTE +#define u_int16_t WORD +#define u_int32_t DWORD + +/* Modern MSVC/UCRT provides snprintf; do not macro-alias it. */ + +/* Stuff to export library symbols */ +#ifdef MULTIHASH_BUILD_DLL +#ifdef MULTIHASH_EXPORTS +#define MULTIHASH_EXPORT __declspec(dllexport) /* Building the lib */ +#else +#define MULTIHASH_EXPORT __declspec(dllimport) /* Building user code */ +#endif +#else +#define MULTIHASH_EXPORT +#endif + +#else /* !WIN32 */ + +#define MULTIHASH_EXPORT + +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +#define USE_CRC32 +#define USE_MD4 +#define USE_MD5 +#define USE_ED2K +#define USE_SHA1 +#define USE_SHA2 + +#ifdef USE_CRC32 +#include +#include "crc32.h" +#define LEN_CRC32 8 +#endif + +#ifdef USE_MD4 +#include "md4.h" +#define LEN_MD4 32 +#define MD4_DIGESTSIZE 16 +#endif + +#ifdef USE_MD5 +#include "md5.h" +#define LEN_MD5 32 +#endif + +#ifdef USE_ED2K +#include "edonkey.h" +#define LEN_ED2K 32 +#endif + +#ifdef USE_SHA1 +#include "sha1.h" +#define LEN_SHA1 40 +#endif + +#ifdef USE_SHA2 +#include "sha2.h" +#define LEN_SHA2 64 +#endif + +/* This must be as long as the longest hash (in bytes) */ +#define MAX_DIGESTSIZE 32 + +typedef struct { +#ifdef USE_CRC32 + u_int32_t crc32; + char crc32_s[LEN_CRC32 + 1]; +#endif +#ifdef USE_MD4 + md4_context md4; + char md4_s[LEN_MD4 + 1]; +#endif +#ifdef USE_MD5 + Md5Context md5; + char md5_s[LEN_MD5 + 1]; +#endif +#ifdef USE_ED2K + ed2khash_context ed2k; + char ed2k_s[LEN_ED2K + 1]; +#endif +#ifdef USE_SHA1 + Sha1Context sha1; + char sha1_s[LEN_SHA1 + 1]; +#endif +#ifdef USE_SHA2 + Sha256Context sha2; + char sha2_s[LEN_SHA2 + 1]; +#endif +} multihash; + + +/* Prototypes */ +MULTIHASH_EXPORT void multihash_init (multihash *mh); +MULTIHASH_EXPORT void multihash_update (multihash *mh, unsigned char *data, int bytes); +MULTIHASH_EXPORT void multihash_finish (multihash *mh); +MULTIHASH_EXPORT int multihash_file (multihash *mh, char *filename); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libmultihash/sha1.c b/libmultihash/sha1.c new file mode 100644 index 0000000..9015ead --- /dev/null +++ b/libmultihash/sha1.c @@ -0,0 +1,269 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WjCryptLib_Sha1 +// +// Implementation of SHA1 hash function. +// Original author: Steve Reid +// Contributions by: James H. Brown , Saul Kravitz , +// and Ralph Giles +// Modified by WaterJuice retaining Public Domain license. +// +// This is free and unencumbered software released into the public domain - June 2013 waterjuice.org +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// IMPORTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "sha1.h" +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// DEFINES +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Decide whether to use the Little-Endian shortcut. If the shortcut is not used then the code will work correctly +// on either big or little endian, however if we do know it is a little endian architecture we can speed it up a bit. +// Note, there are TWO places where USE_LITTLE_ENDIAN_SHORTCUT is used. They MUST be paired together. +#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && ( __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ ) + // gcc defines __BYTE_ORDER__ so if it says its little endian we can use that. + #define USE_LITTLE_ENDIAN_SHORTCUT +#elif defined( _WIN32 ) + // Windows is always little endian so we can use that. + #define USE_LITTLE_ENDIAN_SHORTCUT +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// TYPES +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef union +{ + uint8_t c [64]; + uint32_t l [16]; +} CHAR64LONG16; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// INTERNAL FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Endian neutral macro for loading 32 bit value from 4 byte array (in big endian form). +#define LOAD32H(x, y) \ + { x = ((uint32_t)((y)[0] & 255)<<24) | \ + ((uint32_t)((y)[1] & 255)<<16) | \ + ((uint32_t)((y)[2] & 255)<<8) | \ + ((uint32_t)((y)[3] & 255)); } + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +// blk0() and blk() perform the initial expand. +#ifdef USE_LITTLE_ENDIAN_SHORTCUT + #define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) | (rol(block->l[i],8)&0x00FF00FF)) +#else + #define blk0(i) block->l[i] +#endif + +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15] ^ block->l[(i+8)&15] ^ block->l[(i+2)&15] ^ block->l[i&15],1)) + +// (R0+R1), R2, R3, R4 are the different operations used in SHA1 +#define R0(v,w,x,y,z,i) z += ((w&(x^y))^y) + blk0(i)+ 0x5A827999 + rol(v,5); w=rol(w,30); +#define R1(v,w,x,y,z,i) z += ((w&(x^y))^y) + blk(i) + 0x5A827999 + rol(v,5); w=rol(w,30); +#define R2(v,w,x,y,z,i) z += (w^x^y) + blk(i) + 0x6ED9EBA1 + rol(v,5); w=rol(w,30); +#define R3(v,w,x,y,z,i) z += (((w|x)&y)|(w&x)) + blk(i) + 0x8F1BBCDC + rol(v,5); w=rol(w,30); +#define R4(v,w,x,y,z,i) z += (w^x^y) + blk(i) + 0xCA62C1D6 + rol(v,5); w=rol(w,30); + +// Loads the 128 bits from ByteArray into WordArray, treating ByteArray as big endian data +#ifdef USE_LITTLE_ENDIAN_SHORTCUT + #define Load128BitsAsWords( WordArray, ByteArray ) \ + memcpy( WordArray, ByteArray, 64 ) +#else + #define Load128BitsAsWords( WordArray, ByteArray ) \ + { \ + uint32_t i; \ + for( i=0; i<16; i++ ) \ + { \ + LOAD32H( (WordArray)[i], (ByteArray)+(i*4) ); \ + } \ + } +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// TransformFunction +// +// Hash a single 512-bit block. This is the core of the algorithm +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static +void + TransformFunction + ( + uint32_t state[5], + uint8_t const buffer[64] + ) +{ + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; + uint32_t e; + uint8_t workspace[64]; + CHAR64LONG16* block = (CHAR64LONG16*) workspace; + + Load128BitsAsWords( block->l, buffer ); + + // Copy context->state[] to working vars + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + // 4 rounds of 20 operations each. Loop unrolled. + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + // Add the working vars back into context.state[] + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha1Initialise +// +// Initialises an SHA1 Context. Use this to initialise/reset a context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha1Initialise + ( + Sha1Context* Context // [out] + ) +{ + // SHA1 initialisation constants + Context->State[0] = 0x67452301; + Context->State[1] = 0xEFCDAB89; + Context->State[2] = 0x98BADCFE; + Context->State[3] = 0x10325476; + Context->State[4] = 0xC3D2E1F0; + Context->Count[0] = 0; + Context->Count[1] = 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha1Update +// +// Adds data to the SHA1 context. This will process the data and update the internal state of the context. Keep on +// calling this function until all the data has been added. Then call Sha1Finalise to calculate the hash. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha1Update + ( + Sha1Context* Context, // [in out] + void const* Buffer, // [in] + uint32_t BufferSize // [in] + ) +{ + uint32_t i; + uint32_t j; + + j = (Context->Count[0] >> 3) & 63; + if( (Context->Count[0] += BufferSize << 3) < (BufferSize << 3) ) + { + Context->Count[1]++; + } + + Context->Count[1] += (BufferSize >> 29); + if( (j + BufferSize) > 63 ) + { + i = 64 - j; + memcpy( &Context->Buffer[j], Buffer, i ); + TransformFunction(Context->State, Context->Buffer); + for( ; i + 63 < BufferSize; i += 64 ) + { + TransformFunction(Context->State, (uint8_t*)Buffer + i); + } + j = 0; + } + else + { + i = 0; + } + + memcpy( &Context->Buffer[j], &((uint8_t*)Buffer)[i], BufferSize - i ); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha1Finalise +// +// Performs the final calculation of the hash and returns the digest (20 byte buffer containing 160bit hash). After +// calling this, Sha1Initialised must be used to reuse the context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha1Finalise + ( + Sha1Context* Context, // [in out] + SHA1_HASH* Digest // [in] + ) +{ + uint32_t i; + uint8_t finalcount[8]; + + for( i=0; i<8; i++ ) + { + finalcount[i] = (unsigned char)((Context->Count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); // Endian independent + } + Sha1Update( Context, (uint8_t*)"\x80", 1 ); + while( (Context->Count[0] & 504) != 448 ) + { + Sha1Update( Context, (uint8_t*)"\0", 1 ); + } + + Sha1Update( Context, finalcount, 8 ); // Should cause a Sha1TransformFunction() + for( i=0; ibytes[i] = (uint8_t)((Context->State[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha1Calculate +// +// Combines Sha1Initialise, Sha1Update, and Sha1Finalise into one function. Calculates the SHA1 hash of the buffer. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha1Calculate + ( + void const* Buffer, // [in] + uint32_t BufferSize, // [in] + SHA1_HASH* Digest // [in] + ) +{ + Sha1Context context; + + Sha1Initialise( &context ); + Sha1Update( &context, Buffer, BufferSize ); + Sha1Finalise( &context, Digest ); +} diff --git a/libmultihash/sha1.h b/libmultihash/sha1.h new file mode 100644 index 0000000..e768191 --- /dev/null +++ b/libmultihash/sha1.h @@ -0,0 +1,99 @@ +#ifndef __SHA1_H__ +#define __SHA1_H__ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WjCryptLib_Sha1 +// +// Implementation of SHA1 hash function. +// Original author: Steve Reid +// Contributions by: James H. Brown , Saul Kravitz , +// and Ralph Giles +// Modified by WaterJuice retaining Public Domain license. +// +// This is free and unencumbered software released into the public domain - June 2013 waterjuice.org +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// IMPORTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// TYPES +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Sha1Context - This must be initialised using Sha1Initialised. Do not modify the contents of this structure directly. +typedef struct +{ + uint32_t State[5]; + uint32_t Count[2]; + uint8_t Buffer[64]; +} Sha1Context; + +#define SHA1_HASH_SIZE ( 160 / 8 ) + +typedef struct +{ + uint8_t bytes [SHA1_HASH_SIZE]; +} SHA1_HASH; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha1Initialise +// +// Initialises an SHA1 Context. Use this to initialise/reset a context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha1Initialise + ( + Sha1Context* Context // [out] + ); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha1Update +// +// Adds data to the SHA1 context. This will process the data and update the internal state of the context. Keep on +// calling this function until all the data has been added. Then call Sha1Finalise to calculate the hash. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha1Update + ( + Sha1Context* Context, // [in out] + void const* Buffer, // [in] + uint32_t BufferSize // [in] + ); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha1Finalise +// +// Performs the final calculation of the hash and returns the digest (20 byte buffer containing 160bit hash). After +// calling this, Sha1Initialised must be used to reuse the context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha1Finalise + ( + Sha1Context* Context, // [in out] + SHA1_HASH* Digest // [in] + ); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha1Calculate +// +// Combines Sha1Initialise, Sha1Update, and Sha1Finalise into one function. Calculates the SHA1 hash of the buffer. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha1Calculate + ( + void const* Buffer, // [in] + uint32_t BufferSize, // [in] + SHA1_HASH* Digest // [in] + ); + +#endif /* __SHA1_H__ */ diff --git a/libmultihash/sha2.c b/libmultihash/sha2.c new file mode 100644 index 0000000..c2ca4ef --- /dev/null +++ b/libmultihash/sha2.c @@ -0,0 +1,295 @@ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WjCryptLib_Sha256 +// +// Implementation of SHA256 hash function. +// Original author: Tom St Denis, tomstdenis@gmail.com, http://libtom.org +// Modified by WaterJuice retaining Public Domain license. +// +// This is free and unencumbered software released into the public domain - June 2013 waterjuice.org +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// IMPORTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "sha2.h" +#include + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MACROS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#define ror(value, bits) (((value) >> (bits)) | ((value) << (32 - (bits)))) + +#define MIN(x, y) ( ((x)<(y))?(x):(y) ) + +#define STORE32H(x, y) \ + { (y)[0] = (uint8_t)(((x)>>24)&255); (y)[1] = (uint8_t)(((x)>>16)&255); \ + (y)[2] = (uint8_t)(((x)>>8)&255); (y)[3] = (uint8_t)((x)&255); } + +#define LOAD32H(x, y) \ + { x = ((uint32_t)((y)[0] & 255)<<24) | \ + ((uint32_t)((y)[1] & 255)<<16) | \ + ((uint32_t)((y)[2] & 255)<<8) | \ + ((uint32_t)((y)[3] & 255)); } + +#define STORE64H(x, y) \ + { (y)[0] = (uint8_t)(((x)>>56)&255); (y)[1] = (uint8_t)(((x)>>48)&255); \ + (y)[2] = (uint8_t)(((x)>>40)&255); (y)[3] = (uint8_t)(((x)>>32)&255); \ + (y)[4] = (uint8_t)(((x)>>24)&255); (y)[5] = (uint8_t)(((x)>>16)&255); \ + (y)[6] = (uint8_t)(((x)>>8)&255); (y)[7] = (uint8_t)((x)&255); } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// CONSTANTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// The K array +static const uint32_t K[64] = { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL, + 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL, + 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, + 0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, + 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, 0x983e5152UL, + 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL, + 0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, + 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL, + 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, 0x19a4c116UL, 0x1e376c08UL, + 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, + 0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL +}; + +#define BLOCK_SIZE 64 + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// INTERNAL FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Various logical functions +#define Ch( x, y, z ) (z ^ (x & (y ^ z))) +#define Maj( x, y, z ) (((x | y) & z) | (x & y)) +#define S( x, n ) ror((x),(n)) +#define R( x, n ) (((x)&0xFFFFFFFFUL)>>(n)) +#define Sigma0( x ) (S(x, 2) ^ S(x, 13) ^ S(x, 22)) +#define Sigma1( x ) (S(x, 6) ^ S(x, 11) ^ S(x, 25)) +#define Gamma0( x ) (S(x, 7) ^ S(x, 18) ^ R(x, 3)) +#define Gamma1( x ) (S(x, 17) ^ S(x, 19) ^ R(x, 10)) + +#define Sha256Round( a, b, c, d, e, f, g, h, i ) \ + t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \ + t1 = Sigma0(a) + Maj(a, b, c); \ + d += t0; \ + h = t0 + t1; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// TransformFunction +// +// Compress 512-bits +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static +void + TransformFunction + ( + Sha256Context* Context, + uint8_t const* Buffer + ) +{ + uint32_t S[8]; + uint32_t W[64]; + uint32_t t0; + uint32_t t1; + uint32_t t; + int i; + + // Copy state into S + for( i=0; i<8; i++ ) + { + S[i] = Context->state[i]; + } + + // Copy the state into 512-bits into W[0..15] + for( i=0; i<16; i++ ) + { + LOAD32H( W[i], Buffer + (4*i) ); + } + + // Fill W[16..63] + for( i=16; i<64; i++ ) + { + W[i] = Gamma1( W[i-2]) + W[i-7] + Gamma0( W[i-15] ) + W[i-16]; + } + + // Compress + for( i=0; i<64; i++ ) + { + Sha256Round( S[0], S[1], S[2], S[3], S[4], S[5], S[6], S[7], i ); + t = S[7]; + S[7] = S[6]; + S[6] = S[5]; + S[5] = S[4]; + S[4] = S[3]; + S[3] = S[2]; + S[2] = S[1]; + S[1] = S[0]; + S[0] = t; + } + + // Feedback + for( i=0; i<8; i++ ) + { + Context->state[i] = Context->state[i] + S[i]; + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Initialise +// +// Initialises a SHA256 Context. Use this to initialise/reset a context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha256Initialise + ( + Sha256Context* Context // [out] + ) +{ + Context->curlen = 0; + Context->length = 0; + Context->state[0] = 0x6A09E667UL; + Context->state[1] = 0xBB67AE85UL; + Context->state[2] = 0x3C6EF372UL; + Context->state[3] = 0xA54FF53AUL; + Context->state[4] = 0x510E527FUL; + Context->state[5] = 0x9B05688CUL; + Context->state[6] = 0x1F83D9ABUL; + Context->state[7] = 0x5BE0CD19UL; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Update +// +// Adds data to the SHA256 context. This will process the data and update the internal state of the context. Keep on +// calling this function until all the data has been added. Then call Sha256Finalise to calculate the hash. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha256Update + ( + Sha256Context* Context, // [in out] + void const* Buffer, // [in] + uint32_t BufferSize // [in] + ) +{ + uint32_t n; + + if( Context->curlen > sizeof(Context->buf) ) + { + return; + } + + while( BufferSize > 0 ) + { + if( Context->curlen == 0 && BufferSize >= BLOCK_SIZE ) + { + TransformFunction( Context, (uint8_t*)Buffer ); + Context->length += BLOCK_SIZE * 8; + Buffer = (uint8_t*)Buffer + BLOCK_SIZE; + BufferSize -= BLOCK_SIZE; + } + else + { + n = MIN( BufferSize, (BLOCK_SIZE - Context->curlen) ); + memcpy( Context->buf + Context->curlen, Buffer, (size_t)n ); + Context->curlen += n; + Buffer = (uint8_t*)Buffer + n; + BufferSize -= n; + if( Context->curlen == BLOCK_SIZE ) + { + TransformFunction( Context, Context->buf ); + Context->length += 8*BLOCK_SIZE; + Context->curlen = 0; + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Finalise +// +// Performs the final calculation of the hash and returns the digest (32 byte buffer containing 256bit hash). After +// calling this, Sha256Initialised must be used to reuse the context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha256Finalise + ( + Sha256Context* Context, // [in out] + SHA256_HASH* Digest // [out] + ) +{ + int i; + + if( Context->curlen >= sizeof(Context->buf) ) + { + return; + } + + // Increase the length of the message + Context->length += Context->curlen * 8; + + // Append the '1' bit + Context->buf[Context->curlen++] = (uint8_t)0x80; + + // if the length is currently above 56 bytes we append zeros + // then compress. Then we can fall back to padding zeros and length + // encoding like normal. + if( Context->curlen > 56 ) + { + while( Context->curlen < 64 ) + { + Context->buf[Context->curlen++] = (uint8_t)0; + } + TransformFunction(Context, Context->buf); + Context->curlen = 0; + } + + // Pad up to 56 bytes of zeroes + while( Context->curlen < 56 ) + { + Context->buf[Context->curlen++] = (uint8_t)0; + } + + // Store length + STORE64H( Context->length, Context->buf+56 ); + TransformFunction( Context, Context->buf ); + + // Copy output + for( i=0; i<8; i++ ) + { + STORE32H( Context->state[i], Digest->bytes+(4*i) ); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Calculate +// +// Combines Sha256Initialise, Sha256Update, and Sha256Finalise into one function. Calculates the SHA256 hash of the +// buffer. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha256Calculate + ( + void const* Buffer, // [in] + uint32_t BufferSize, // [in] + SHA256_HASH* Digest // [in] + ) +{ + Sha256Context context; + + Sha256Initialise( &context ); + Sha256Update( &context, Buffer, BufferSize ); + Sha256Finalise( &context, Digest ); +} + diff --git a/libmultihash/sha2.h b/libmultihash/sha2.h new file mode 100644 index 0000000..e8d28fe --- /dev/null +++ b/libmultihash/sha2.h @@ -0,0 +1,92 @@ +#ifndef __SHA2_H__ +#define __SHA2_H__ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// WjCryptLib_Sha256 +// +// Implementation of SHA256 hash function. +// Original author: Tom St Denis, tomstdenis@gmail.com, http://libtom.org +// Modified by WaterJuice retaining Public Domain license. +// +// This is free and unencumbered software released into the public domain - June 2013 waterjuice.org +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// IMPORTS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include + +typedef struct +{ + uint64_t length; + uint32_t state[8]; + uint32_t curlen; + uint8_t buf[64]; +} Sha256Context; + +#define SHA256_HASH_SIZE ( 256 / 8 ) + +typedef struct +{ + uint8_t bytes [SHA256_HASH_SIZE]; +} SHA256_HASH; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Initialise +// +// Initialises a SHA256 Context. Use this to initialise/reset a context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha256Initialise + ( + Sha256Context* Context // [out] + ); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Update +// +// Adds data to the SHA256 context. This will process the data and update the internal state of the context. Keep on +// calling this function until all the data has been added. Then call Sha256Finalise to calculate the hash. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha256Update + ( + Sha256Context* Context, // [in out] + void const* Buffer, // [in] + uint32_t BufferSize // [in] + ); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Finalise +// +// Performs the final calculation of the hash and returns the digest (32 byte buffer containing 256bit hash). After +// calling this, Sha256Initialised must be used to reuse the context. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha256Finalise + ( + Sha256Context* Context, // [in out] + SHA256_HASH* Digest // [out] + ); + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Sha256Calculate +// +// Combines Sha256Initialise, Sha256Update, and Sha256Finalise into one function. Calculates the SHA256 hash of the +// buffer. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +void + Sha256Calculate + ( + void const* Buffer, // [in] + uint32_t BufferSize, // [in] + SHA256_HASH* Digest // [in] + ); +#endif /* __SHA2_H__ */ diff --git a/msvc32_friidump.rsp b/msvc32_friidump.rsp new file mode 100644 index 0000000..6c92172 --- /dev/null +++ b/msvc32_friidump.rsp @@ -0,0 +1,46 @@ +/nologo +/O2 +/MD +/TC +/DWIN32 +/DHAVE_CONFIG_H +/D_CRT_SECURE_NO_DEPRECATE +/D_CRT_NONSTDC_NO_DEPRECATE +/D_CRT_SECURE_NO_WARNINGS +/I. +/Ilibfriidump +/Ilibmultihash +/Isrc +/Ilibfriidump/xbox_ref +/Fobuild-msvc32\ +/Fefriidump.exe +libmultihash\crc32.c +libmultihash\edonkey.c +libmultihash\md4.c +libmultihash\md5.c +libmultihash\multihash.c +libmultihash\sha1.c +libmultihash\sha2.c +libfriidump\brickblocker.c +libfriidump\disc.c +libfriidump\dumper.c +libfriidump\dvd_drive.c +libfriidump\ecma-267.c +libfriidump\hitachi.c +libfriidump\lite-on.c +libfriidump\misc.c +libfriidump\renesas.c +libfriidump\rs.c +libfriidump\unscrambler.c +libfriidump\vanilla_2064.c +libfriidump\vanilla_2384.c +libfriidump\win32compat.c +libfriidump/xbox_ref_bridge.c +libfriidump/xbox_ref/utils.c +libfriidump/xbox_ref/unlock.c +libfriidump/xbox_ref/rc4.c +libfriidump/xbox_ref/xbox_ref_sha1.c +libfriidump/xbox_ref/xbox_ref_log.c +src\getopt-win32.c +src\getopt_long-win32.c +src\friidump.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..1ee59db --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,50 @@ +# Make sure the compiler can find include files from our Hello library. +include_directories ( + ${FriiDump_SOURCE_DIR}/libfriidump +) + +# Make sure the linker can find the Hello library once it is built. +link_directories ( + ${FriiDump_BINARY_DIR}/libfriidump +) + +# Add executable called "helloDemo" that is built from the source files +# "demo.cxx" and "demo_b.cxx". The extensions are automatically found. +if (WIN32) + set( + additional_sources + + getopt-win32.h + getopt-win32.c + getopt_long-win32.c + ) +endif (WIN32) + +add_executable ( + friidump + + friidump.c + ${additional_sources} +) + +# Link the executable to the Hello library. +target_link_libraries ( + friidump + + friidumplib +) + +if (WIN32) + # We only want a version number on Windows (Is it any use?) + set_target_properties (friidump PROPERTIES VERSION 0.2) + + install( + TARGETS friidump + RUNTIME DESTINATION / + ) +else (WIN32) + install( + TARGETS friidump + RUNTIME DESTINATION bin + ) +endif (WIN32) \ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..48ecb75 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,11 @@ +mylibdir = $(top_builddir)/libfriidump +myincludedir = $(top_srcdir)/libfriidump + +bin_PROGRAMS = friidump +friidump_SOURCES = config.h.cmake friidump.c +friidump_LDADD = $(mylibdir)/libfriidump.la +friidump_LDFLAGS = $(all_libraries) + +# noinst_HEADERS = +INCLUDES = $(all_includes) -I$(myincludedir) + diff --git a/src/Makefile.in b/src/Makefile.in new file mode 100644 index 0000000..901d5ff --- /dev/null +++ b/src/Makefile.in @@ -0,0 +1,455 @@ +# Makefile.in generated by automake 1.9.6 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +top_builddir = .. +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +INSTALL = @INSTALL@ +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +bin_PROGRAMS = friidump$(EXEEXT) +subdir = src +DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) +PROGRAMS = $(bin_PROGRAMS) +am_friidump_OBJECTS = friidump.$(OBJEXT) +friidump_OBJECTS = $(am_friidump_OBJECTS) +friidump_DEPENDENCIES = $(mylibdir)/libfriidump.la +DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) --tag=CC --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +CCLD = $(CC) +LINK = $(LIBTOOL) --tag=CC --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +SOURCES = $(friidump_SOURCES) +DIST_SOURCES = $(friidump_SOURCES) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMDEP_FALSE = @AMDEP_FALSE@ +AMDEP_TRUE = @AMDEP_TRUE@ +AMTAR = @AMTAR@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO = @ECHO@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +F77 = @F77@ +FFLAGS = @FFLAGS@ +GREP = @GREP@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_F77 = @ac_ct_F77@ +am__fastdepCC_FALSE = @am__fastdepCC_FALSE@ +am__fastdepCC_TRUE = @am__fastdepCC_TRUE@ +am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@ +am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +mylibdir = $(top_builddir)/libfriidump +myincludedir = $(top_srcdir)/libfriidump +friidump_SOURCES = config.h.cmake friidump.c +friidump_LDADD = $(mylibdir)/libfriidump.la +friidump_LDFLAGS = $(all_libraries) + +# noinst_HEADERS = +INCLUDES = $(all_includes) -I$(myincludedir) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + test -z "$(bindir)" || $(mkdir_p) "$(DESTDIR)$(bindir)" + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + p1=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + if test -f $$p \ + || test -f $$p1 \ + ; then \ + f=`echo "$$p1" | sed 's,^.*/,,;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(binPROGRAMS_INSTALL) '$$p' '$(DESTDIR)$(bindir)/$$f'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) --mode=install $(binPROGRAMS_INSTALL) "$$p" "$(DESTDIR)$(bindir)/$$f" || exit 1; \ + else :; fi; \ + done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + f=`echo "$$p" | sed 's,^.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/'`; \ + echo " rm -f '$(DESTDIR)$(bindir)/$$f'"; \ + rm -f "$(DESTDIR)$(bindir)/$$f"; \ + done + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; for p in $$list; do \ + f=`echo $$p|sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f $$p $$f"; \ + rm -f $$p $$f ; \ + done +friidump$(EXEEXT): $(friidump_OBJECTS) $(friidump_DEPENDENCIES) + @rm -f friidump$(EXEEXT) + $(LINK) $(friidump_LDFLAGS) $(friidump_OBJECTS) $(friidump_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/friidump.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c $< + +.c.obj: +@am__fastdepCC_TRUE@ if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(COMPILE) -c `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ if $(LTCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ +@am__fastdepCC_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Plo"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool +uninstall-info-am: + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \ + list='$(DISTFILES)'; for file in $$list; do \ + case $$file in \ + $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \ + $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \ + esac; \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test "$$dir" != "$$file" && test "$$dir" != "."; then \ + dir="/$$dir"; \ + $(mkdir_p) "$(distdir)$$dir"; \ + else \ + dir=''; \ + fi; \ + if test -d $$d/$$file; then \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(mkdir_p) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-libtool distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: + +install-exec-am: install-binPROGRAMS + +install-info: install-info-am + +install-man: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS uninstall-info-am + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-binPROGRAMS \ + clean-generic clean-libtool ctags distclean distclean-compile \ + distclean-generic distclean-libtool distclean-tags distdir dvi \ + dvi-am html html-am info info-am install install-am \ + install-binPROGRAMS install-data install-data-am install-exec \ + install-exec-am install-info install-info-am install-man \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags uninstall uninstall-am \ + uninstall-binPROGRAMS uninstall-info-am + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/friidump.c b/src/friidump.c new file mode 100644 index 0000000..f8b5292 --- /dev/null +++ b/src/friidump.c @@ -0,0 +1,1381 @@ +/*************************************************************************** + * Copyright (C) 2007 by Arep * + * Support is provided through the forums at * + * http://wii.console-tribe.com * + * * + * 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 2 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "misc.h" +#include +#include +#include +#include +#include "disc.h" +#include "dumper.h" +#include "unscrambler.h" +#include "xbox_ref/xbox_ref_log.h" + +#define printf xbox_ref_printf +#define fprintf xbox_ref_log_fprintf + +#define USECS_PER_SEC 1000000 + + +static const char *friidump_requested_output_target(void); + +typedef struct { + bool active; + bool dump_attempted; + bool seed_ok; + bool have_seed_duration; + double seed_duration; + bool dump_ok; + bool stop_attempted; + bool stop_ok; + char model[256]; + char disc_type[64]; + char game_id[64]; + char title[256]; + char output[512]; + u_int32_t command; + int method; + u_int32_t sectors; + u_int32_t fail_sector; + u_int32_t hlds_type; + u_int32_t cache_base; + u_int32_t mem_blocks; + const char *profile; +} friidump_validation_summary; + +static friidump_validation_summary g_validation_summary; + +static void friidump_summary_copy(char *dst, size_t dst_size, const char *src) { + if (!dst || dst_size == 0) + return; + if (!src) + src = ""; + snprintf(dst, dst_size, "%s", src); +} + +static void friidump_summary_reset(void) { + memset(&g_validation_summary, 0, sizeof(g_validation_summary)); + g_validation_summary.fail_sector = 0xFFFFFFFFU; +} + +static void friidump_format_hms(double seconds, char *buf, size_t buf_size) { + long total, hours, minutes, secs; + if (!buf || buf_size == 0) + return; + if (seconds < 0.0) + seconds = 0.0; + total = (long) (seconds + 0.5); + hours = total / 3600; + minutes = (total / 60) % 60; + secs = total % 60; + snprintf(buf, buf_size, "%02ld:%02ld:%02ld", hours, minutes, secs); +} + + +static void friidump_summary_begin(disc *d) { + const char *target; + if (!d || disc_get_hlds_e7_type(d) == 0) + return; + friidump_summary_reset(); + g_validation_summary.active = true; + friidump_summary_copy(g_validation_summary.model, sizeof(g_validation_summary.model), disc_get_drive_model_string(d)); + g_validation_summary.command = disc_get_command(d); + g_validation_summary.method = disc_get_method(d); + g_validation_summary.hlds_type = disc_get_hlds_e7_type(d); + g_validation_summary.profile = disc_get_hlds_e7_profile_name(d); + g_validation_summary.cache_base = disc_get_hlds_e7_cache_base(d); + g_validation_summary.mem_blocks = disc_get_hlds_e7_mem_blocks(d); + target = friidump_requested_output_target(); + friidump_summary_copy(g_validation_summary.output, sizeof(g_validation_summary.output), target ? target : "(none)"); +} + +static void friidump_print_validation_summary(double duration, bool have_duration) { + double mib_total, mib_per_hour; + if (!g_validation_summary.active) + return; + mib_total = (double) g_validation_summary.sectors * 2048.0 / 1024.0 / 1024.0; + mib_per_hour = (have_duration && duration > 0.0) ? (mib_total / duration * 3600.0) : 0.0; + fprintf(stderr, + "\nHLDS 0xE7 validation summary:\n" + " Model.............: %s\n" + " Profile...........: %s\n" + " Command/method....: %u / %d\n" + " Cache base........: 0x%08x\n" + " Memory windows....: %u\n" + " Disc type.........: %s\n" + " Game ID...........: %s\n" + " Title.............: %s\n" + " Output............: %s\n" + " Seed read.........: %s\n" + " Dump status.......: %s\n" + " STOP UNIT.........: %s\n", + g_validation_summary.model[0] ? g_validation_summary.model : "(unknown)", + g_validation_summary.profile ? g_validation_summary.profile : "(unknown)", + g_validation_summary.command, g_validation_summary.method, + g_validation_summary.cache_base, g_validation_summary.mem_blocks, + g_validation_summary.disc_type[0] ? g_validation_summary.disc_type : "(unknown)", + g_validation_summary.game_id[0] ? g_validation_summary.game_id : "(unknown)", + g_validation_summary.title[0] ? g_validation_summary.title : "(unknown)", + g_validation_summary.output[0] ? g_validation_summary.output : "(none)", + g_validation_summary.seed_ok ? "OK" : "FAILED/NOT REACHED", + g_validation_summary.dump_attempted ? (g_validation_summary.dump_ok ? "OK" : "FAILED") : "NOT ATTEMPTED", + g_validation_summary.stop_attempted ? (g_validation_summary.stop_ok ? "OK" : "FAILED") : "NOT ATTEMPTED"); + if (g_validation_summary.have_seed_duration) { + char seed_buf[32]; + friidump_format_hms(g_validation_summary.seed_duration, seed_buf, sizeof(seed_buf)); + fprintf(stderr, " Seed elapsed......: %s (%.2f seconds)\n", seed_buf, g_validation_summary.seed_duration); + } + if (g_validation_summary.dump_attempted && !g_validation_summary.dump_ok && g_validation_summary.fail_sector != 0xFFFFFFFFU) + fprintf(stderr, " Failure sector....: %u..%u\n", g_validation_summary.fail_sector, g_validation_summary.fail_sector + 15); + if (g_validation_summary.sectors) + fprintf(stderr, " Sectors...........: %u\n", g_validation_summary.sectors); + if (have_duration) + fprintf(stderr, " Duration..........: %.2f seconds\n", duration); + if (have_duration && mib_per_hour > 0.0) + fprintf(stderr, " Observed average..: %.2f MiB/h over %.2f MiB ISO payload\n", mib_per_hour, mib_total); + if (g_validation_summary.dump_ok) + fprintf(stderr, " Hash compare......: compare printed hashes against known-good target for this disc\n"); +} + + +/* Name of package */ +#define PACKAGE "friidump" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "arep@no.net" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "FriiDump" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "0.5.3.5" + + +#ifdef WIN32 + +#include "getopt-win32.h" + + +#else +#include +#include +#endif + + +/* Struct for program options */ +struct { + char *device; + bool autodump; + bool gui; + char *raw_in; + char *raw_out; + char *iso_out; + char *xiso_out; + bool iso_requested; + bool xiso_requested; + bool xbox_filename_override; + bool xbox_auto_filename; + bool resume; + int dump_method; + u_int32_t command; + u_int32_t start_sector; + u_int32_t sectors_no; + u_int32_t speed; + u_int32_t disctype; + u_int32_t sec_disc; + u_int32_t sec_mem; + bool no_hashing; + bool no_unscrambling; + bool no_flushing; + bool stop_unit; + bool allmethods; + bool hlds_e7_scan; + bool hlds_e7_subcmd_sweep; + bool hlds_e7_memrange_sweep; + char *hlds_e7_scan_log; + char *hlds_e7_scan_dump_prefix; + char *hlds_profile_report; +} options; + + +/* Struct for progress data */ +typedef struct { + struct timeval start_time; + struct timeval end_time; + double mb_total; + double mb_total_real; + u_int32_t sectors_skipped; +} progstats; + + +static char friidump_drive_letter_from_device(const char *device) { + if (!device || !device[0]) return 0; + if (device[0] && device[1] == ':') return device[0]; + if (device[0] == '\\' && device[1] == '\\' && device[2] == '.' && device[3] == '\\' && device[4] && device[5] == ':') return device[4]; + return device[0]; +} + + +static const char *friidump_requested_output_target(void) { + if (options.iso_out && options.iso_out[0]) return options.iso_out; + if (options.xiso_out && options.xiso_out[0]) return options.xiso_out; + if (options.raw_out && options.raw_out[0]) return options.raw_out; + return NULL; +} + + +static void friidump_json_string(FILE *f, const char *s) { + const unsigned char *p; + fputc('"', f); + if (s) { + for (p = (const unsigned char *) s; *p; p++) { + switch (*p) { + case '\\': fputs("\\\\", f); break; + case '"': fputs("\\\"", f); break; + case '\b': fputs("\\b", f); break; + case '\f': fputs("\\f", f); break; + case '\n': fputs("\\n", f); break; + case '\r': fputs("\\r", f); break; + case '\t': fputs("\\t", f); break; + default: + if (*p < 0x20) { + char tmp[8]; + snprintf(tmp, sizeof(tmp), "\\u%04x", (unsigned int) *p); + fputs(tmp, f); + } else { + fputc(*p, f); + } + break; + } + } + } + fputc('"', f); +} + +static void friidump_json_kv_string(FILE *f, const char *key, const char *value, bool comma) { + fputs(" ", f); + friidump_json_string(f, key); + fputs(": ", f); + friidump_json_string(f, value ? value : ""); + fputs(comma ? ",\n" : "\n", f); +} + +static void friidump_json_kv_u32_hex(FILE *f, const char *key, u_int32_t value, bool comma) { + char tmp[32]; + snprintf(tmp, sizeof(tmp), "0x%08x", value); + friidump_json_kv_string(f, key, value ? tmp : "", comma); +} + +static void friidump_json_kv_int(FILE *f, const char *key, int value, bool comma) { + char tmp[32]; + fputs(" ", f); + friidump_json_string(f, key); + snprintf(tmp, sizeof(tmp), ": %d%s\n", value, comma ? "," : ""); + fputs(tmp, f); +} + +static void friidump_json_kv_bool(FILE *f, const char *key, bool value, bool comma) { + fputs(" ", f); + friidump_json_string(f, key); + fputs(value ? ": true" : ": false", f); + fputs(comma ? ",\n" : "\n", f); +} + +static bool friidump_write_hlds_profile_report(disc *d, const char *path) { + FILE *f; + if (!d || !path || !path[0]) + return false; + f = fopen(path, "wb"); + if (!f) { + fprintf(stderr, "WARNING: could not write HLDS profile report: %s\n", path); + return false; + } + fputs("{\n", f); + friidump_json_kv_string(f, "schema", "friidump_hlds_profile_report_v1", true); + friidump_json_kv_string(f, "drive_model", disc_get_drive_model_string(d), true); + friidump_json_kv_string(f, "profile_name", disc_get_hlds_e7_profile_name(d), true); + friidump_json_kv_string(f, "support_tier", disc_get_hlds_e7_support_tier(d), true); + friidump_json_kv_string(f, "family", disc_get_hlds_e7_family(d), true); + friidump_json_kv_string(f, "tokens", disc_get_hlds_e7_tokens(d), true); + friidump_json_kv_string(f, "stage5b_record_id", disc_get_hlds_e7_record_id(d), true); + friidump_json_kv_u32_hex(f, "static_e7_cdb_base", disc_get_hlds_e7_static_cdb_base(d), true); + friidump_json_kv_u32_hex(f, "static_e7_gate", disc_get_hlds_e7_static_gate(d), true); + friidump_json_kv_int(f, "runtime_e7_type", (int) disc_get_hlds_e7_type(d), true); + friidump_json_kv_u32_hex(f, "runtime_cache_base", disc_get_hlds_e7_cache_base(d), true); + friidump_json_kv_int(f, "runtime_mem_windows", (int) disc_get_hlds_e7_mem_blocks(d), true); + friidump_json_kv_int(f, "preferred_method", disc_get_hlds_e7_preferred_method(d), true); + friidump_json_kv_int(f, "selected_method", (int) disc_get_method(d), true); + friidump_json_kv_int(f, "selected_command", (int) disc_get_command(d), true); + friidump_json_kv_bool(f, "live_safe_from_static_only", false, true); + friidump_json_kv_string(f, "safety_note", "Static firmware addresses are evidence only; FriiDump does not use them as host-side commands and does not emit flash/write/update CDBs.", true); + friidump_json_kv_string(f, "notes", disc_get_hlds_e7_notes(d), false); + fputs("}\n", f); + fclose(f); + return true; +} + + +static void friidump_retarget_log_from_options(void) { + const char *target = friidump_requested_output_target(); + if (target) { + xbox_ref_log_retarget(target); + } else if (options.device && options.device[0]) { + xbox_ref_log_open_for_target(NULL, friidump_drive_letter_from_device(options.device)); + } +} + + +void progress_for_guis (bool start, u_int32_t sectors_done, u_int32_t total_sectors, progstats *stats) { + int perc; + double elapsed, mb_done, mb_done_real, mb_hour, seconds_left; + struct timeval now; + time_t eta; + struct tm etatm; + char buf[50]; + + if (start) { + gettimeofday (&(stats -> start_time), NULL); + stats -> mb_total = (double) total_sectors * 2064 / 1024 / 1024; + stats -> mb_total_real = (double) (total_sectors - sectors_done) * 2064 / 1024 / 1024; + stats -> sectors_skipped = sectors_done; + } else { + perc = (int) (100.0 * sectors_done / total_sectors); + gettimeofday (&now, NULL); + elapsed = difftime (now.tv_sec, (stats -> start_time).tv_sec); + mb_done = (double) sectors_done * 2064 / 1024 / 1024; + mb_done_real = (double) (sectors_done - stats -> sectors_skipped) * 2064 / 1024 / 1024; + mb_hour = mb_done_real / elapsed * 60 * 60; + seconds_left = stats -> mb_total_real / mb_hour * 60 * 60; + eta = (time_t) ((stats -> start_time).tv_sec + seconds_left); + if (localtime_r (&eta, &etatm)) + strftime (buf, 50, "%d/%m/%Y %H:%M:%S", &etatm); + else + sprintf (buf, "N/A"); + + /* This is the only thing we print to stdout, so that other programs can easily capture and parse our output */ + fprintf (stdout, "%d%%|%u/%u sectors|%.2lf/%.0lf MB|%.0lf/%.0lf seconds|%.2lf MB/h|%s\n", + perc, sectors_done, total_sectors, mb_done, stats -> mb_total, elapsed, seconds_left, mb_hour, buf); + fflush (stdout); + } + + /* Save return time, in case this will be the last call */ + gettimeofday (&(stats -> end_time), NULL); + + return; +} + + +void progress (bool start, u_int32_t sectors_done, u_int32_t total_sectors, progstats *stats) { + int perc, i; + double elapsed, mb_done, mb_done_real, mb_hour, seconds_left; + struct timeval now; + time_t eta; + struct tm etatm; + char buf[50]; + + if (start) { + gettimeofday (&(stats -> start_time), NULL); + stats -> mb_total = (double) total_sectors * 2064 / 1024 / 1024; + stats -> mb_total_real = (double) (total_sectors - sectors_done) * 2064 / 1024 / 1024; + stats -> sectors_skipped = sectors_done; + } else { + perc = (int) (100.0 * sectors_done / total_sectors); + gettimeofday (&now, NULL); + elapsed = difftime (now.tv_sec, (stats -> start_time).tv_sec); + mb_done = (double) sectors_done * 2064 / 1024 / 1024; + mb_done_real = (double) (sectors_done - stats -> sectors_skipped) * 2064 / 1024 / 1024; + mb_hour = mb_done_real / elapsed * 60 * 60; + seconds_left = stats -> mb_total_real / mb_hour * 60 * 60; + eta = (time_t) ((stats -> start_time).tv_sec + seconds_left); + if (localtime_r (&eta, &etatm)) + strftime (buf, 50, "%d/%m/%Y %H:%M:%S", &etatm); + else + sprintf (buf, "N/A"); + + fprintf (stdout, "\r%3d%% ", perc); + fprintf (stdout, "|"); + for (i = 0; i < 100 / 3; i++) { + if (i == perc / 3) + fprintf (stdout, "*"); + else + fprintf (stdout, "-"); + } + fprintf (stdout, "| "); + fprintf (stdout, "%.2lf MB/h, ETA: %s", mb_hour, buf); + fflush (stdout); + } + + if (sectors_done == total_sectors) + printf ("\n"); + + /* Save return time, in case this will be the last call */ + gettimeofday (&(stats -> end_time), NULL); + + return; +} + + + +void welcome (void) { + /* Welcome text */ + fprintf (stderr, + "FriiDump " PACKAGE_VERSION " - Copyright (C) 2007 Arep\n" + "This software comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it\n" + "under certain conditions; see COPYING for details.\n" + "\n" + "Official support forum: http://wii.console-tribe.com\n" + "\n" + "Forum for this UNOFFICIAL VERSION: http://forum.redump.org\n" + "\n" + ); + fflush (stderr); + + return; +} + + +void help (void) { + /* 80 cols guide: + * |-------------------------------------------------------------------------------| + */ + fprintf (stderr, "\n" + "Available command line options:\n" + "\n" + " -h, --help Show this help\n" + " -a, --autodump Dump the disc to an ISO file with an\n" + " automatically-generated name, resuming the dump\n" + " if possible\n" + " -g, --gui Use more verbose output that can be easily\n" + " parsed by a GUI frontend\n" + " -d, --device Dump disc from device \n" + " -p, --stop Instruct device to stop disc rotation\n" + " -D, --dvd Force standard DVD-ROM mode for any drive\n" + " (equivalent to -T 3; uses FriiDump original\n" + " DVD/raw/ISO paths, not Xbox mode)\n" + " -c, --command Force memory dump command:\n" + " 0 - vanilla 2064\n" + " 1 - vanilla 2384\n" + " 2 - Hitachi\n" + " 3 - Lite-On\n" + " 4 - Renesas\n" + " -x, --speed Set streaming speed (1, 24, 32, 64, etc.,\n" + " where 1 = 150 KiB/s and so on)\n" + " -T, --type Force disc type:\n" + " 0 - GameCube\n" + " 1 - Wii\n" + " 2 - Wii_DL\n" + " 3 - DVD\n" + " 4 - Xbox/XGD 2048-byte-sector mode\n" + " Native profiles: GDR-8050L and GDR-3120L.\n" + " Other drives keep normal FriiDump behavior\n" + " unless Xbox mode is explicitly forced.\n" + " -S, --size Force disc size\n" + " -r, --raw Output to file in raw format (2064-byte\n" + " sectors)\n" + " -i, --iso[=] Output to file in ISO format (2048-byte\n" + " sectors). For Xbox/GDR-8050L, omitting \n" + " derives Title[MediaID].iso from the XBE/DMI;\n" + " providing is an explicit override. For\n" + " Xbox/XGD this reconstructs the redump-style\n" + " XGD1 layout and writes .pfi.bin, .dmi.bin, and\n" + " .redump.json metadata when possible\n" + " -X, --xiso[=] Output Xbox/XGD game partition as XISO (.xiso).\n" + " For Xbox/GDR-8050L, omitting derives\n" + " Title[MediaID].xiso from the XBE/DMI; providing\n" + " is an explicit override. Attempts to read\n" + " the 32-sector game lead-in from drive-readable\n" + " sectors and zero-fills only unreadable sectors.\n" + " -u, --unscramble Convert (unscramble) raw image contained in\n" + " to ISO format\n" + " -H, --nohash Do not compute CRC32/MD5/SHA-1/SHA-256 hashes\n" + " for generated files\n" + " -s, --resume Resume partial dump\n" + " - General -----------------------------------\n" + " -0, --method0[=,] Use dumping method 0 (Optional argument\n" + " specifies how many sectors to request from disc\n" + " and read from cache at a time. Values should be\n" + " separated with a comma. Default 16,16)\n" + " - Non-Streaming -----------------------------\n" + " -1, --method1[=,] Use dumping method 1 (Default 16,16)\n" + " -2, --method2[=,] Use dumping method 2 (Default 16,16)\n" + " -3, --method3[=,] Use dumping method 3 (Default 16,16)\n" + " - Streaming ---------------------------------\n" + " -4, --method4[=,] Use dumping method 4 (Default 27,27)\n" + " -5, --method5[=,] Use dumping method 5 (Default 27,27)\n" + " -6, --method6[=,] Use dumping method 6 (Default 27,27)\n" + " - Hitachi -----------------------------------\n" + " -7, --method7 Use dumping method 7 (Read and dump 5 blocks\n" + " at a time, using streaming read)\n" + " -8, --method8 Use dumping method 8 (Read and dump 5 blocks\n" + " at a time, using streaming read, using DMA)\n" + " -9, --method9 Use dumping method 9 (Read and dump 5 blocks\n" + " at a time, using streaming read, using DMA and\n" + " some speed tricks)\n" + " --hlds-e7-scan Probe HLDS HIT 0xE7 cache/memdump bases only;\n" + " writes JSON and does not crack seeds or dump data\n" + " --hlds-e7-subcmd-sweep Probe HIT 0xE7 subcommands/address candidates only;\n" + " writes JSON and does not crack seeds or dump data\n" + " --hlds-e7-memrange-sweep Sweep wider HIT 0xE7 subcmd 0x01 address ranges;\n" + " writes JSON and does not crack seeds or dump data\n" + " --scan-log JSON output path for --hlds-e7-scan\n" + " --scan-dump-prefix Optional raw 0xE7 window dump prefix for --hlds-e7-scan\n" + " --hlds-profile-report Write selected HLDS profile/evidence JSON\n" + " -A, --allmethods Try all known command/method combinations until\n" + " one works. Reopens the drive for each command so\n" + " command-specific vendor handlers are rebound.\n" +#ifdef DEBUG + " -n, --donottunscramble Do not try unscrambling to check EDC. Only\n" + " useful for testing the raw performance of the\n" + " different methods\n" + " -f, --donottflush Do not call fflush() after every fwrite()\n" +#endif + ); + + return; +} + + +bool optparse (int argc, char **argv) { + bool out; + char *result = NULL; + int c; + int option_index = 0; + static struct option long_options[] = { + {"help", 0, 0, 'h'}, //0 - no_argument + {"autodump", 0, 0, 'a'}, + {"gui", 0, 0, 'g'}, + {"device", 1, 0, 'd'}, //1 - required_argument + {"raw", 1, 0, 'r'}, + {"iso", 2, 0, 'i'}, + {"xiso", 2, 0, 'X'}, + {"unscramble", 1, 0, 'u'}, + {"nohash", 0, 0, 'H'}, + {"resume", 0, 0, 's'}, + {"method0", 2, 0, '0'}, //2 - optional_argument + {"method1", 2, 0, '1'}, + {"method2", 2, 0, '2'}, + {"method3", 2, 0, '3'}, + {"method4", 2, 0, '4'}, + {"method5", 2, 0, '5'}, + {"method6", 2, 0, '6'}, + {"method7", 0, 0, '7'}, + {"method8", 0, 0, '8'}, + {"method9", 0, 0, '9'}, + {"stop", 0, 0, 'p'}, + {"dvd", 0, 0, 'D'}, + {"command", 1, 0, 'c'}, + {"startsector", 1, 0, 't'}, + {"size", 1, 0, 'S'}, + {"speed", 1, 0, 'x'}, + {"type", 1, 0, 'T'}, + {"allmethods", 0, 0, 'A'}, + {"hlds-e7-scan", 0, 0, 1000}, + {"hlds-e7-subcmd-sweep", 0, 0, 1003}, + {"hlds-e7-memrange-sweep", 0, 0, 1004}, + {"scan-log", 1, 0, 1001}, + {"scan-dump-prefix", 1, 0, 1002}, + {"hlds-profile-report", 1, 0, 1005}, +#ifdef DEBUG + /* We don't want newbies to generate and put into circulation bad dumps, so this options are disabled for releases */ + {"donottunscramble", 0, 0, 'n'}, + {"donottflush", 0, 0, 'f'}, +#endif + {0, 0, 0, 0} + }; + + if (argc == 1) { + help (); + exit (1); + } + + /* Init options to default values */ + options.device = NULL; + options.autodump = false; + options.gui = false; + options.raw_in = NULL; + options.raw_out = NULL; + options.iso_out = NULL; + options.xiso_out = NULL; + options.iso_requested = false; + options.xiso_requested = false; + options.xbox_filename_override = false; + options.xbox_auto_filename = false; + options.no_hashing = false; + options.resume = false; + options.dump_method = -1; + options.command = -1; + options.start_sector = -1; + options.sectors_no = -1; + options.speed = -1; + options.disctype = -1; + options.sec_disc = -1; + options.sec_mem = -1; + options.no_unscrambling = false; + options.no_flushing = false; + options.stop_unit = false; + options.allmethods = false; + options.hlds_e7_scan = false; + options.hlds_e7_subcmd_sweep = false; + options.hlds_e7_memrange_sweep = false; + options.hlds_e7_scan_log = NULL; + options.hlds_e7_scan_dump_prefix = NULL; + options.hlds_profile_report = NULL; + + do { +#ifdef DEBUG + c = getopt_long (argc, argv, "hpagd:r:i::X::u:Hs0::1::2::3::4::5::6::789Dc:t:S:x:T:Anf", long_options, &option_index); +#else + c = getopt_long (argc, argv, "hpagd:r:i::X::u:Hs0::1::2::3::4::5::6::789Dc:t:S:x:T:A", long_options, &option_index); +#endif + + switch (c) { + case 'h': + help (); + exit (1); + break; + case 'p': + options.stop_unit = true; + break; + case 'a': + options.autodump = true; + options.resume = true; + break; + case 'g': + options.gui = true; + break; + case 'd': + my_strdup (options.device, optarg); + break; + case 'r': + my_strdup (options.raw_out, optarg); + break; + case 'i': + options.iso_requested = true; + /* Preserve the old `-i file.iso` syntax even though Xbox now also + * supports bare `-i` for XBE/DMI-derived names. */ + if (!optarg && optind < argc && argv[optind] && argv[optind][0] != '-') + optarg = argv[optind++]; + if (optarg) { + my_strdup (options.iso_out, optarg); + options.xbox_filename_override = true; + } else { + options.xbox_auto_filename = true; + } + break; + case 'X': + options.xiso_requested = true; + /* Preserve the old `-X file.xiso` syntax while allowing bare `-X`. */ + if (!optarg && optind < argc && argv[optind] && argv[optind][0] != '-') + optarg = argv[optind++]; + if (optarg) { + my_strdup (options.xiso_out, optarg); + options.xbox_filename_override = true; + } else { + options.xbox_auto_filename = true; + } + break; + case 'u': + my_strdup (options.raw_in, optarg); + break; + case 'H': + options.no_hashing = true; + break; + case 's': + options.resume = true; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + options.dump_method = c - '0'; + if (optarg) { + result = strtok(optarg, ","); + result = strtok(NULL, ","); + options.sec_disc = atol(strpbrk(optarg,"1234567890")); + if (result) options.sec_mem = atol(result); + else { + help (); + exit (1); + } + } + break; + case '7': + case '8': + case '9': + options.dump_method = c - '0'; + break; + case 'D': + options.disctype = DISC_TYPE_DVD; + unscrambler_set_disctype (DISC_TYPE_DVD); + break; + case 'c': + options.command = atol (optarg); + if (options.command > 4) { + help (); + exit (1); + }; + break; + case 't': + options.start_sector = atol (optarg); + break; + case 'S': + options.sectors_no = atol (optarg); + break; + case 'x': + options.speed = atol (optarg); + break; + case 'T': + options.disctype = atol (optarg); + if (options.disctype > 4) { + help (); + exit (1); + }; + if (options.disctype <= DISC_TYPE_DVD) + unscrambler_set_disctype (options.disctype); + break; + case 'A': + options.allmethods = true; + options.resume = true; + break; +#ifdef DEBUG + case 'n': + options.no_unscrambling = true; + break; + case 'f': + options.no_flushing = true; + break; +#endif + case 1000: + options.hlds_e7_scan = true; + break; + case 1003: + options.hlds_e7_subcmd_sweep = true; + break; + case 1004: + options.hlds_e7_memrange_sweep = true; + break; + case 1001: + my_strdup (options.hlds_e7_scan_log, optarg); + break; + case 1002: + my_strdup (options.hlds_e7_scan_dump_prefix, optarg); + break; + case 1005: + my_strdup (options.hlds_profile_report, optarg); + break; + case -1: + break; + default: +// fprintf (stderr, "?? getopt returned character code 0%o ??\n", c); + exit (7); + break; + } + } while (c != -1); + + if (optind < argc) { + /* Command-line arguments remaining. Ignore them, warning the user. */ + fprintf (stderr, "WARNING: Extra parameters ignored\n"); + } + + /* Sanity checks... */ + out = false; + if (!options.device && !options.raw_in) { + fprintf (stderr, "No operation specified. Please use the -d or -u options.\n"); + } else if (options.raw_in && options.raw_out) { + fprintf (stderr, + "Are you sure you want to convert a raw image to another raw image? ;)\n" + "Take a look at the -i and -a options!\n" + ); + } else if (options.autodump && (options.raw_out || options.iso_requested || options.xiso_requested)) { + fprintf (stderr, "The -r, -i and -X options cannot be used together with -a.\n"); + } else if (options.xiso_requested && (options.raw_out || options.iso_requested)) { + fprintf (stderr, "The -X/--xiso option is a separate Xbox output mode and cannot be combined with -r or -i.\n"); + } else { + /* Specified options seem to make sense */ + out = true; + } + + return (out); +} + +int dologic (disc *d, progstats *stats) { + disc_type type_id; + char *type, *game_id, *region, *maker_id, *maker, *version, *title, tmp[0x03E0 + 4 + 1]; + bool drive_supported; + bool xbox_forced; + bool xbox_output_requested; + bool dump_attempted; + int out; + dumper *dmp; + u_int32_t current_sector = 0; + + xbox_forced = (options.disctype == DISC_TYPE_XBOX); + xbox_output_requested = xbox_forced || options.xiso_requested; + dump_attempted = false; + + + + if (options.stop_unit) { //stop rotation, if requested + fprintf (stderr, "Issuing STOP command... %s\n", (disc_stop_unit (d, false)) ? "OK" : "Failed"); + exit (1); + } + else disc_stop_unit(d, true); //else start rotation + + drive_supported = disc_get_drive_support_status (d); + fprintf (stderr, + "\n" + "Drive information:\n" + "----------------------------------------------------------------------\n" + "Drive model........: %s\n" + "Supported..........: %s\n", disc_get_drive_model_string (d), drive_supported ? "Yes" : "No" + ); + + if (xbox_output_requested && !disc_is_xbox_unlock_drive (d)) { + fprintf (stderr, + "Xbox/XGD output is limited to the supported Xbox unlock profiles " + "currently wired into this branch: GDR-8050L, GDR-3120L, " + "and known Samsung/Kreon-style vendor-unlock drives.\n" + "Refusing to fall back to FriiDump GC/Wii methods for Xbox mode on this drive.\n"); + return false; + } + + /* Xbox/XGD (-T 4 or -X) is a direct MMC/SCSI READ(10) path. + * Do not let the detected GC/Wii vendor memdump method (for example + * Hitachi command 2 / method 9 on GDR-8050L) drive sector reads. */ + if (xbox_output_requested && options.dump_method == -1) + options.dump_method = 10; + + init_range(d, options.sec_disc, options.sec_mem); + + if (!(disc_set_read_method (d, options.dump_method))) + exit (2); + + if (xbox_output_requested && disc_get_method(d) == 10) { + fprintf (stderr, "Command............: Xbox direct MMC/SCSI path\n"); + fprintf (stderr, "Method.............: 10 (Xbox READ(10))\n"); + } else { + if (options.command!=-1) fprintf (stderr, + "Command............: %d (forced)\n", disc_get_command(d)); + else fprintf (stderr, + "Command............: %d\n", disc_get_command(d)); + if (disc_get_def_method(d)!=disc_get_method(d)) fprintf (stderr, + "Method.............: %d (forced)\n", disc_get_method(d)); + else fprintf (stderr, + "Method.............: %d\n", disc_get_method(d)); + if (disc_get_hlds_e7_type (d) != 0) { + fprintf (stderr, "HLDS 0xE7 profile..: %s\n", disc_get_hlds_e7_profile_name (d)); + fprintf (stderr, "HLDS support tier..: %s\n", disc_get_hlds_e7_support_tier (d)); + fprintf (stderr, "HLDS family........: %s\n", disc_get_hlds_e7_family (d)); + fprintf (stderr, "HLDS E7 tokens.....: %s\n", disc_get_hlds_e7_tokens (d)); + fprintf (stderr, "HLDS Stage5B row...: %s\n", disc_get_hlds_e7_record_id (d)); + fprintf (stderr, "Static CDB/gate....: 0x%03x / 0x%08x\n", disc_get_hlds_e7_static_cdb_base (d), disc_get_hlds_e7_static_gate (d)); + fprintf (stderr, "Cache base.........: 0x%08x\n", disc_get_hlds_e7_cache_base (d)); + fprintf (stderr, "Memory windows.....: %u\n", disc_get_hlds_e7_mem_blocks (d)); + if (disc_get_hlds_e7_notes (d) && disc_get_hlds_e7_notes (d)[0]) + fprintf (stderr, "HLDS notes.........: %s\n", disc_get_hlds_e7_notes (d)); + } + } + if (options.hlds_profile_report) { + if (friidump_write_hlds_profile_report(d, options.hlds_profile_report)) + fprintf(stderr, "HLDS profile report: %s\n", options.hlds_profile_report); + } + options.dump_method=disc_get_method(d); + friidump_summary_begin(d); + if ((options.dump_method==0) + || (options.dump_method==1) || (options.dump_method==2) || (options.dump_method==3) + || (options.dump_method==4) || (options.dump_method==5) || (options.dump_method==6) + ){ + fprintf (stderr, + "Requested sectors..: %d\n", disc_get_sec_disc(d)); + fprintf (stderr, + "Expected sectors...: %d\n", disc_get_sec_mem(d)); + } + + fprintf (stderr, "\nPress Ctrl+C at any time to terminate\n"); + + //set speed for 1st time + if (options.speed != -1) disc_set_speed(d, options.speed * 177); + if (options.speed != -1) disc_set_streaming_speed(d, options.speed * 177); +// disc_set_speed(d, 0xffff); + + /* Windows may attach filesystem/autoplay polling to odd GC/Wii discs and + * steal the drive during the HLDS 0xE7 seed phase, especially on Type1 + * GCC-4160N/GCC-4240N. Reuse the same volume guard mechanism that the + * Xbox path already uses, but apply it before disc_init()/seed reads for + * all HLDS 0xE7 GC/Wii profiles. Failure is warning-only because Explorer + * may already have a transient handle; the read path itself remains the + * authority. */ + if (!xbox_output_requested && disc_get_hlds_e7_type (d) != 0) { + if (disc_get_hlds_e7_type (d) == 44 || disc_get_hlds_e7_type (d) == 45) { + fprintf (stderr, + "\nGDR-8050L modified-firmware warning:\n" + " This GC/Wii 0xE7 path assumes a cross-flashed or modified GDR-8050L firmware with 0xE7 memdump support added.\n" + " Stock GDR-8050L firmware is still supported for Xbox ripping, but it is not expected to dump GC/Wii discs through this path.\n"); + } + fprintf (stderr, + "\nWindows AutoPlay warning:\n" + " HLDS 0xE7 GC/Wii seed reads are sensitive to Windows polling.\n" + " Disable AutoPlay for this drive/media and close File Explorer or any \"insert a disc\" dialogs before dumping.\n" + " FriiDump will try to lock the volume, but AutoPlay can still interfere before or during seed retrieval.\n"); + fprintf (stderr, "Applying Windows volume lock guard for HLDS 0xE7 seed reads... "); + disc_refresh_volume (d); + if (disc_lock_volume (d) < 0) + fprintf (stderr, "Warning: failed; disable AutoPlay, close File Explorer/AutoPlay dialogs for this drive, then rerun.\n"); + else + fprintf (stderr, "OK\n"); + } + + if (options.hlds_e7_scan) { + out = disc_hlds_e7_scan (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix); + fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 scan... "); + fprintf (stderr, "%s\n", disc_stop_unit (d, false) ? "OK" : "Failed"); + return out; + } + + if (options.hlds_e7_subcmd_sweep) { + out = disc_hlds_e7_subcmd_sweep (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix); + fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 subcommand sweep... "); + fprintf (stderr, "%s\n", disc_stop_unit (d, false) ? "OK" : "Failed"); + return out; + } + + if (options.hlds_e7_memrange_sweep) { + out = disc_hlds_e7_memrange_sweep (d, options.hlds_e7_scan_log, options.hlds_e7_scan_dump_prefix); + fprintf (stderr, "Issuing STOP UNIT / spin-down after HLDS 0xE7 memory-range sweep... "); + fprintf (stderr, "%s\n", disc_stop_unit (d, false) ? "OK" : "Failed"); + return out; + } + + { + int media_rc, media_sense_key, media_asc, media_ascq; + fprintf (stderr, "\nChecking for ready media before disc seed retrieval... "); + media_rc = disc_media_preflight (d, 15000, &media_sense_key, &media_asc, &media_ascq); + if (media_rc <= 0) { + if (media_rc == 0) + fprintf (stderr, "No readable disc present (sense %02X/%02X/%02X). Insert a disc, wait for spin-up, and retry.\n", media_sense_key, media_asc, media_ascq); + else + fprintf (stderr, "Drive/media not ready (sense %02X/%02X/%02X). Wait for spin-up, close AutoPlay/File Explorer, and retry.\n", media_sense_key, media_asc, media_ascq); + return false; + } + fprintf (stderr, "OK\n"); + + time_t seed_start, seed_end; + double seed_elapsed; + char seed_elapsed_buf[32]; + + if (xbox_output_requested) + fprintf (stderr, "\nInitializing Xbox/XGD disc state... "); + else + fprintf (stderr, "\nRetrieving disc seeds, this might take a while... "); + + seed_start = time(NULL); + if (!disc_init (d, options.disctype, options.sectors_no)) { + seed_end = time(NULL); + seed_elapsed = difftime(seed_end, seed_start); + friidump_format_hms(seed_elapsed, seed_elapsed_buf, sizeof(seed_elapsed_buf)); + if (!xbox_output_requested) + fprintf (stderr, "[Elapsed:%s] ", seed_elapsed_buf); + if (g_validation_summary.active) { + g_validation_summary.have_seed_duration = true; + g_validation_summary.seed_duration = seed_elapsed; + } + fprintf (stderr, "Failed\n"); + out = false; + } else { + seed_end = time(NULL); + seed_elapsed = difftime(seed_end, seed_start); + friidump_format_hms(seed_elapsed, seed_elapsed_buf, sizeof(seed_elapsed_buf)); + if (!xbox_output_requested) + fprintf (stderr, "[Elapsed:%s] ", seed_elapsed_buf); + fprintf (stderr, "OK\n"); + if (g_validation_summary.active) { + g_validation_summary.seed_ok = true; + g_validation_summary.have_seed_duration = true; + g_validation_summary.seed_duration = seed_elapsed; + } + disc_get_type (d, &type_id, &type); + disc_get_gameid (d, &game_id); + disc_get_region (d, NULL, ®ion); + disc_get_maker (d, &maker_id, &maker); + disc_get_version (d, NULL, &version); + disc_get_title (d, &title); + if (g_validation_summary.active) { + friidump_summary_copy(g_validation_summary.disc_type, sizeof(g_validation_summary.disc_type), type); + friidump_summary_copy(g_validation_summary.game_id, sizeof(g_validation_summary.game_id), game_id); + friidump_summary_copy(g_validation_summary.title, sizeof(g_validation_summary.title), title); + g_validation_summary.sectors = disc_get_sectors_no(d); + } + fprintf (stderr, + "\n" + "Disc information:\n" + "----------------------------------------------------------------------\n"); + + if (options.disctype!=-1) fprintf (stderr, + "Disc type..........: %s (forced)\n", type); + else fprintf (stderr, + "Disc type..........: %s\n", type); + if (options.sectors_no!=-1) fprintf (stderr, + "Disc size..........: %d (forced)\n", disc_get_sectors_no(d)); + else fprintf (stderr, + "Disc size..........: %d\n", disc_get_sectors_no(d)); + + if (disc_get_layerbreak(d)>0 && type_id==DISC_TYPE_DVD) fprintf (stderr, + "Layer break........: %d\n", disc_get_layerbreak(d)); + + if ((type_id==DISC_TYPE_GAMECUBE) || (type_id==DISC_TYPE_WII) || (type_id==DISC_TYPE_WII_DL)) fprintf (stderr, + "Game ID............: %s\n" + "Region.............: %s\n" + "Maker..............: %s - %s\n" + "Version............: %s\n" + "Game title.........: %s\n", game_id, region, maker_id, maker, version, title + ); + + if (type_id == DISC_TYPE_WII || type_id == DISC_TYPE_WII_DL) + fprintf (stderr, "Contains update....: %s\n" , disc_get_update (d) ? "Yes" : "No"); + fprintf (stderr, "\n"); + + disc_set_unscrambling (d, !options.no_unscrambling); + + if (type_id <= DISC_TYPE_DVD) + unscrambler_set_disctype (type_id); + + if (options.autodump) { + snprintf (tmp, sizeof (tmp), "%s.iso", title); + my_strdup (options.iso_out, tmp); + options.iso_requested = true; + } + + /* Xbox/GDR-8050L supports default XBE/DMI-derived names. + * Use an empty-string placeholder so the copied reference path can + * derive Title[MediaID].iso/.xiso after the first unlock/XBE probe. */ + if (type_id == DISC_TYPE_XBOX && options.iso_requested && !options.iso_out) + my_strdup (options.iso_out, ""); + if (type_id == DISC_TYPE_XBOX && options.xiso_requested && !options.xiso_out) + my_strdup (options.xiso_out, ""); + + //set speed 2nd time after rotation is started and some sectors read + if (options.speed != -1) disc_set_streaming_speed(d, options.speed * 177); + if (options.speed != -1) disc_set_speed(d, options.speed * 177); + + /* If at least an output file was specified, proceed dumping, otherwise stop here */ + if (options.xiso_requested) { + if (type_id != DISC_TYPE_XBOX) { + fprintf (stderr, "Xbox XISO output requires Xbox/XGD disc type. Use -T 4 or an Xbox-capable drive/disc.\n"); + out = false; + } else { + fprintf (stderr, options.xiso_out && options.xiso_out[0] ? "Writing to file \"%s\" in Xbox XISO format\n\n" : "Writing Xbox XISO using XBE-derived filename (override with -X )\n\n", (options.xiso_out && options.xiso_out[0]) ? options.xiso_out : ""); + + dmp = dumper_new (d); + dumper_set_hashing (dmp, !options.no_hashing); + dumper_set_flushing (dmp, !options.no_flushing); + + if (!dumper_set_xiso_output_file (dmp, options.xiso_out, options.resume)) { + fprintf (stderr, "Cannot setup Xbox XISO output file\n"); + } else if (!dumper_prepare_xiso (dmp)) { + fprintf (stderr, "Cannot prepare Xbox XISO dumper"); + } else { + if (options.gui) + dumper_set_progress_callback (dmp, (progress_func) progress_for_guis, stats); + else + dumper_set_progress_callback (dmp, (progress_func) progress, stats); + + dump_attempted = true; + if (g_validation_summary.active) + g_validation_summary.dump_attempted = true; + if (dumper_dump_xiso (dmp, ¤t_sector)) { + fprintf (stderr, "Xbox XISO dump completed successfully!\n"); + if (!options.no_hashing && !(type_id == DISC_TYPE_XBOX && disc_is_xbox_challenge_drive (d))) + fprintf (stderr, + "XISO image hashes:\n" + "CRC32...: %s\n" + "MD5.....: %s\n" + "SHA-1...: %s\n" + "SHA-256.: %s\n", + dumper_get_xiso_crc32 (dmp), dumper_get_xiso_md5 (dmp), + dumper_get_xiso_sha1 (dmp), dumper_get_xiso_sha2 (dmp) + ); + out = true; + if (g_validation_summary.active) + g_validation_summary.dump_ok = true; + } else { + fprintf (stderr, "\nXbox XISO dump failed at output sector: %u\n", current_sector); + out = false; + if (g_validation_summary.active) { + g_validation_summary.dump_ok = false; + g_validation_summary.fail_sector = current_sector; + } + } + } + + dmp = dumper_destroy (dmp); + } + } else if (options.raw_out || options.iso_requested) { + if (type_id == DISC_TYPE_XBOX && options.raw_out) + fprintf (stderr, "Xbox/XGD output does not support -r/raw together with the redump-style ISO path. Use -i or -X.\n"); + else if (options.raw_out) + fprintf (stderr, "Writing to file \"%s\" in raw format\n", options.raw_out); + if (options.iso_out) { + if (type_id == DISC_TYPE_XBOX) + fprintf (stderr, options.iso_out && options.iso_out[0] ? "Writing to file \"%s\" in Xbox/XGD redump-style ISO format\n" : "Writing Xbox/XGD redump-style ISO using XBE-derived filename (override with -i )\n", (options.iso_out && options.iso_out[0]) ? options.iso_out : ""); + else + fprintf (stderr, "Writing to file \"%s\" in ISO format\n", options.iso_out); + } + fprintf (stderr, "\n"); + + dmp = dumper_new (d); + + dumper_set_hashing (dmp, !options.no_hashing); + dumper_set_flushing (dmp, !options.no_flushing); + + if (!dumper_set_raw_output_file (dmp, options.raw_out, options.resume)) { + fprintf (stderr, "Cannot setup raw output file\n"); + } else if (!dumper_set_iso_output_file (dmp, options.iso_out, options.resume)) { + fprintf (stderr, "Cannot setup ISO output file\n"); + } else if (!dumper_prepare (dmp)) { + fprintf (stderr, "Cannot prepare dumper"); + } else { +// fprintf (stderr, "Starting dump process from sector %u...\n", dmp -> start_sector); +// opdd.start_sector = options.start_sector; + + if (options.gui) + dumper_set_progress_callback (dmp, (progress_func) progress_for_guis, stats); + else + dumper_set_progress_callback (dmp, (progress_func) progress, stats); + + dump_attempted = true; + if (g_validation_summary.active) + g_validation_summary.dump_attempted = true; + if (dumper_dump (dmp, ¤t_sector)) { + fprintf (stderr, "Dump completed successfully!\n"); + if (!options.no_hashing && options.raw_out) + fprintf (stderr, + "Raw image hashes:\n" + "CRC32...: %s\n" + //"MD4.....: %s\n" + "MD5.....: %s\n" + "SHA-1...: %s\n" + "SHA-256.: %s\n" + /*"ED2K....: %s\n"*/, + dumper_get_raw_crc32 (dmp), /*dumper_get_raw_md4 (dmp),*/ dumper_get_raw_md5 (dmp), + dumper_get_raw_sha1 (dmp), dumper_get_raw_sha2 (dmp)/*, dumper_get_raw_ed2k (dmp)*/ + ); + if (!options.no_hashing && options.iso_out && !(type_id == DISC_TYPE_XBOX && disc_is_xbox_challenge_drive (d))) + fprintf (stderr, + "ISO image hashes:\n" + "CRC32...: %s\n" + //"MD4.....: %s\n" + "MD5.....: %s\n" + "SHA-1...: %s\n" + "SHA-256.: %s\n" + /*"ED2K....: %s\n"*/, + dumper_get_iso_crc32 (dmp), /*dumper_get_iso_md4 (dmp),*/ dumper_get_iso_md5 (dmp), + dumper_get_iso_sha1 (dmp), dumper_get_iso_sha2 (dmp)/*, dumper_get_iso_ed2k (dmp)*/ + ); + + out = true; + if (g_validation_summary.active) + g_validation_summary.dump_ok = true; + } else { + if (g_validation_summary.active) { + g_validation_summary.dump_ok = false; + g_validation_summary.fail_sector = current_sector; + } + if (current_sector == 0xFFFFFFFFU) + fprintf (stderr, "\nXbox reference dumper failed; see the Xbox-specific message above.\n"); + else + fprintf (stderr, "\nDump failed at sectors: %u..%u\n", current_sector, current_sector+15); + out = false; + //disc_stop_unit (d, 0); + } + } + + dmp = dumper_destroy (dmp); + } else { + fprintf (stderr, "No output file for dumping specified, please take a look at the -i, -r, -X and -a options\n"); + } + } + } + if (dump_attempted) { + bool stop_ok = disc_stop_unit (d, false); + fprintf (stderr, "\nIssuing STOP UNIT / spin-down after dump attempt... %s\n", stop_ok ? "OK" : "Failed"); + if (g_validation_summary.active) { + g_validation_summary.stop_attempted = true; + g_validation_summary.stop_ok = stop_ok; + } + } + + return out; +} + + +static int try_all_methods (progstats *stats) { + u_int32_t saved_command; + int saved_method; + u_int32_t command; + int method; + int out; + disc *d; + + saved_command = options.command; + saved_method = options.dump_method; + out = false; + + fprintf (stderr, "Trying all command/method combinations... This will take a LOOOONG time and generate an insanely long console output :p\n"); + + for (command = 0; command <= 4 && !out; command++) { + for (method = 0; method <= 10 && !out; method++) { + options.command = command; + options.dump_method = method; + memset (stats, 0, sizeof (*stats)); + + fprintf (stderr, "\nTrying with command %u, method %d\n", command, method); + fprintf (stderr, "Initializing DVD drive... "); + + d = disc_new (options.device, options.command); + if (!d) { + fprintf (stderr, "Failed\n"); +#ifndef WIN32 + fprintf (stderr, + "Probably you do not have access to the DVD device. Ask the administrator\n" + "to add you to the proper group, or use 'sudo'.\n" + ); +#endif + continue; + } + + fprintf (stderr, "OK\n"); + out = dologic (d, stats); + d = disc_destroy (d); + + if (out) + fprintf (stderr, "Command %u and method %d combination worked!\n", command, method); + } + } + + if (!out) { + options.command = saved_command; + options.dump_method = saved_method; + } + + return out; +} + +int main (int argc, char *argv[]) { + disc *d; + progstats stats; + double duration; + suseconds_t us; + int out, ret; + unscrambler *u; + unscrambler_progress_func pfunc; + u_int32_t current_sector; + + /* First of all... */ + drop_euid (); + xbox_ref_log_open_for_target(NULL, 0); + + welcome (); + + memset (&stats, 0, sizeof (stats)); + d = NULL; + out = false; + ret = EXIT_FAILURE; + if (optparse (argc, argv)) { + friidump_retarget_log_from_options(); + if (options.device) { + if (options.allmethods) { + out = try_all_methods (&stats); + } else { + /* Dump DVD to file */ + fprintf (stderr, "Initializing DVD drive... "); + + if (!(d = disc_new (options.device, options.command))) { + fprintf (stderr, "Failed\n"); +#ifndef WIN32 + fprintf (stderr, + "Probably you do not have access to the DVD device. Ask the administrator\n" + "to add you to the proper group, or use 'sudo'.\n" + ); +#endif + } else { + fprintf (stderr, "OK\n"); + out = dologic (d, &stats); + d = disc_destroy (d); + } + } + } else if (options.raw_in) { + /* Convert raw image to ISO format */ + u = unscrambler_new (); + + if (options.gui) + pfunc = (unscrambler_progress_func) progress_for_guis; + else + pfunc = (unscrambler_progress_func) progress; + + if ((out = unscrambler_unscramble_file (u, options.raw_in, options.iso_out, pfunc, &stats, ¤t_sector))) + fprintf (stderr, "Unscrambling completed successfully!\n"); + else + fprintf (stderr, "\nUnscrambling failed at sectors: %u..%u\n", current_sector, current_sector+15); + + u = unscrambler_destroy (u); + } else { + MY_ASSERT (0); + } + + if (out) { + duration = stats.end_time.tv_sec - stats.start_time.tv_sec; + if (stats.end_time.tv_usec >= stats.start_time.tv_usec) { + us = stats.end_time.tv_usec - stats.start_time.tv_usec; + } else { + if (duration > 0) + duration--; + us = USECS_PER_SEC + stats.end_time.tv_usec - stats.start_time.tv_usec; + } + duration += ((double) us / (double) USECS_PER_SEC); + if (duration < 0) + duration = 0; + fprintf (stderr, "Operation took %.2f seconds\n", duration); + friidump_print_validation_summary(duration, true); + + ret = EXIT_SUCCESS; + } else { + friidump_print_validation_summary(0.0, false); + ret = EXIT_FAILURE; + } + + my_free (options.device); + my_free (options.iso_out); + my_free (options.xiso_out); + my_free (options.raw_out); + my_free (options.raw_in); + my_free (options.hlds_profile_report); + } + + if (xbox_ref_log_path()) + fprintf (stderr, "Log file complete: %s\n", xbox_ref_log_path()); + xbox_ref_log_close(); + + return (ret); +} diff --git a/src/getopt-win32.c b/src/getopt-win32.c new file mode 100644 index 0000000..1380eaa --- /dev/null +++ b/src/getopt-win32.c @@ -0,0 +1,130 @@ +/* $Id$ */ +/* +** Copyright (C) 2002 Martin Roesch +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License Version 2 as +** published by the Free Software Foundation. You may not use, modify or +** distribute this program under any other version of the GNU General +** Public License. +** +** 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, write to the Free Software +** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#ifdef SNORT_GETOPT + +#include /* for EOF */ +#include /* for strchr() */ + +#include "getopt.h" + +/* static (global) variables that are specified as exported by getopt() */ +char *optarg = NULL; /* pointer to the start of the option argument */ +int optind = 1; /* number of the next argv[] to be evaluated */ +int opterr = 1; /* non-zero if a question mark should be returned + when a non-valid option character is detected */ +int optopt; + +int getopt(int argc, char *argv[], char *opstring) +{ + static char *pIndexPosition = NULL; /* place inside current argv string */ + char *pArgString = NULL; /* where to start from next */ + char *pOptString; /* the string in our program */ + + + if (pIndexPosition != NULL) { + /* we last left off inside an argv string */ + if (*(++pIndexPosition)) { + /* there is more to come in the most recent argv */ + pArgString = pIndexPosition; + } + } + + if (pArgString == NULL) { + /* we didn't leave off in the middle of an argv string */ + if (optind >= argc) { + /* more command-line arguments than the argument count */ + pIndexPosition = NULL; /* not in the middle of anything */ + return EOF; /* used up all command-line arguments */ + } + + /*--------------------------------------------------------------------- + * If the next argv[] is not an option, there can be no more options. + *-------------------------------------------------------------------*/ + pArgString = argv[optind++]; /* set this to the next argument ptr */ + + if (('/' != *pArgString) && /* doesn't start with a slash or a dash? */ + ('-' != *pArgString)) { + --optind; /* point to current arg once we're done */ + optarg = NULL; /* no argument follows the option */ + pIndexPosition = NULL; /* not in the middle of anything */ + return EOF; /* used up all the command-line flags */ + } + + /* check for special end-of-flags markers */ + if ((strcmp(pArgString, "-") == 0) || + (strcmp(pArgString, "--") == 0)) { + optarg = NULL; /* no argument follows the option */ + pIndexPosition = NULL; /* not in the middle of anything */ + return EOF; /* encountered the special flag */ + } + + pArgString++; /* look past the / or - */ + } + + if (':' == *pArgString) { /* is it a colon? */ + /*--------------------------------------------------------------------- + * Rare case: if opterr is non-zero, return a question mark; + * otherwise, just return the colon we're on. + *-------------------------------------------------------------------*/ + return (opterr ? (int)'?' : (int)':'); + } + else if ((pOptString = strchr(opstring, *pArgString)) == 0) { + /*--------------------------------------------------------------------- + * The letter on the command-line wasn't any good. + *-------------------------------------------------------------------*/ + optarg = NULL; /* no argument follows the option */ + pIndexPosition = NULL; /* not in the middle of anything */ + return (opterr ? (int)'?' : (int)*pArgString); + } + else { + /*--------------------------------------------------------------------- + * The letter on the command-line matches one we expect to see + *-------------------------------------------------------------------*/ + if (':' == _next_char(pOptString)) { /* is the next letter a colon? */ + /* It is a colon. Look for an argument string. */ + if ('\0' != _next_char(pArgString)) { /* argument in this argv? */ + optarg = &pArgString[1]; /* Yes, it is */ + } + else { + /*------------------------------------------------------------- + * The argument string must be in the next argv. + * But, what if there is none (bad input from the user)? + * In that case, return the letter, and optarg as NULL. + *-----------------------------------------------------------*/ + if (optind < argc) + optarg = argv[optind++]; + else { + optarg = NULL; + return (opterr ? (int)'?' : (int)*pArgString); + } + } + + pIndexPosition = NULL; /* not in the middle of anything */ + } + else { + /* it's not a colon, so just return the letter */ + optarg = NULL; /* no argument follows the option */ + pIndexPosition = pArgString; /* point to the letter we're on */ + } + return (int)*pArgString; /* return the letter that matched */ + } +} + +#endif diff --git a/src/getopt-win32.h b/src/getopt-win32.h new file mode 100644 index 0000000..e08af95 --- /dev/null +++ b/src/getopt-win32.h @@ -0,0 +1,36 @@ +/* $Id$ */ +/* +** Copyright (C) 2002 Martin Roesch +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU General Public License Version 2 as +** published by the Free Software Foundation. You may not use, modify or +** distribute this program under any other version of the GNU General +** Public License. +** +** 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, write to the Free Software +** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#ifndef _SNORT_GETOPT_H_ +#define _SNORT_GETOPT_H_ + +#ifdef SNORT_GETOPT +#define _next_char(string) (char)(*(string+1)) + +extern char * optarg; +extern int optind; + +int getopt(int, char**, char*); + +#else +#include "getopt1.h" +#endif + +#endif /* _SNORT_GETOPT_H_ */ diff --git a/src/getopt1.h b/src/getopt1.h new file mode 100644 index 0000000..7c2552a --- /dev/null +++ b/src/getopt1.h @@ -0,0 +1,130 @@ +/* Declarations for getopt. + Copyright (C) 1989, 1990, 1991, 1992, 1993 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + it under the terms of the GNU General Public License Version 2 as + published by the Free Software Foundation. You may not use, modify or + distribute this program under any other version of the GNU General + Public License. + + 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. */ + +#ifndef _GETOPT1_H +#define _GETOPT1_H 1 + +#ifndef HAVE_GETOPT_LONG + +#ifdef __cplusplus +extern "C" { +#endif + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int opterr; + +/* Set to an option character which was unrecognized. */ + +extern int optopt; + +/* Describe the long-named options requested by the application. + The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector + of `struct option' terminated by an element containing a name which is + zero. + + The field `has_arg' is: + no_argument (or 0) if the option does not take an argument, + required_argument (or 1) if the option requires an argument, + optional_argument (or 2) if the option takes an optional argument. + + If the field `flag' is not NULL, it points to a variable that is set + to the value given in the field `val' when the option is found, but + left unchanged if the option is not found. + + To have a long-named option do something other than set an `int' to + a compiled-in constant, such as set a value from `optarg', set the + option's `flag' field to zero and its `val' field to a nonzero + value (the equivalent single-letter option character, if there is + one). For long options that have a zero `flag' field, `getopt' + returns the contents of the `val' field. */ + +struct option +{ +#if __STDC__ + const char *name; +#else + char *name; +#endif + /* has_arg can't be an enum because some compilers complain about + type mismatches in all the code that assumes it is an int. */ + int has_arg; + int *flag; + int val; +}; + +/* Names for the values of the `has_arg' field of `struct option'. */ + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +#if __STDC__ + +#ifndef HAVE_GETOPT +extern int getopt (int argc, char *const *argv, const char *optstring); +#endif + +extern int getopt_long (int argc, char *const *argv, const char *shortopts, + const struct option *longopts, int *longind); +extern int getopt_long_only (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind); + +/* Internal only. Users should not call this directly. */ +extern int _getopt_internal (int argc, char *const *argv, + const char *shortopts, + const struct option *longopts, int *longind, + int long_only); +#else /* not __STDC__ */ + +#ifndef HAVE_GETOPT +extern int getopt (); +#endif +extern int getopt_long (); +extern int getopt_long_only (); + +extern int _getopt_internal (); +#endif /* not __STDC__ */ + +#ifdef __cplusplus +} +#endif + +#endif /* HAVE_GETOPT_LONG */ + +#endif /* _GETOPT_H */ diff --git a/src/getopt_long-win32.c b/src/getopt_long-win32.c new file mode 100644 index 0000000..991363e --- /dev/null +++ b/src/getopt_long-win32.c @@ -0,0 +1,727 @@ +/* Getopt for GNU. + NOTE: getopt is now part of the C library, so if you don't know what + "Keep this file name-space clean" means, talk to roland@gnu.ai.mit.edu + before changing it! + + Copyright (C) 1987, 88, 89, 90, 91, 92, 93, 94 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + it under the terms of the GNU General Public License Version 2 as + published by the Free Software Foundation. You may not use, modify or + distribute this program under any other version of the GNU General + Public License. + + 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. */ + +/* This tells Alpha OSF/1 not to define a getopt prototype in . + Ditto for AIX 3.2 and . */ +#ifndef _NO_PROTO +#define _NO_PROTO +#endif + +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#if defined (emacs) || defined (CONFIG_BROKETS) +/* We use instead of "config.h" so that a compilation + using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h + (which it would do because it found this file in $srcdir). */ +#include +#else +#include "config.h" +#endif +#endif + + +#ifndef __STDC__ +/* This is a separate conditional since some stdc systems + reject `defined (const)'. */ +#ifndef const +#define const +#endif +#endif + + +#ifdef HAVE_STRING_H +#include +#endif + +/* Comment out all this code if we are using the GNU C Library, and are not + actually compiling the library itself. This code is part of the GNU C + Library, but also included in many other GNU distributions. Compiling + and linking in this code is a waste when using the GNU C library + (especially if it is a shared library). Rather than having every GNU + program understand `configure --with-gnu-libc' and omit the object files, + it is simpler to just do this in the source for each such file. */ + +#if defined (_LIBC) || !defined (__GNU_LIBRARY__) + + +/* This needs to come after some library #include + to get __GNU_LIBRARY__ defined. */ +#ifdef __GNU_LIBRARY__ +/* Don't include stdlib.h for non-GNU C libraries because some of them + contain conflicting prototypes for getopt. */ +#include +#endif /* GNU C library. */ + +/* This version of `getopt' appears to the caller like standard Unix `getopt' + but it behaves differently for the user, since it allows the user + to intersperse the options with the other arguments. + + As `getopt' works, it permutes the elements of ARGV so that, + when it is done, all the options precede everything else. Thus + all application programs are extended to handle flexible argument order. + + Setting the environment variable POSIXLY_CORRECT disables permutation. + Then the behavior is completely standard. + + GNU application programs can use a third alternative mode in which + they can distinguish the relative order of options and other arguments. */ + +#include "getopt1.h" + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +char *optarg = NULL; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* XXX 1003.2 says this must be 1 before any call. */ +int optind = 0; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int optopt = '?'; + +/* Describe how to deal with options that follow non-option ARGV-elements. + + If the caller did not specify anything, + the default is REQUIRE_ORDER if the environment variable + POSIXLY_CORRECT is defined, PERMUTE otherwise. + + REQUIRE_ORDER means don't recognize them as options; + stop option processing when the first non-option is seen. + This is what Unix does. + This mode of operation is selected by either setting the environment + variable POSIXLY_CORRECT, or using `+' as the first character + of the list of option characters. + + PERMUTE is the default. We permute the contents of ARGV as we scan, + so that eventually all the non-options are at the end. This allows options + to be given in any order, even with programs that were not written to + expect this. + + RETURN_IN_ORDER is an option available to programs that were written + to expect options and other ARGV-elements in any order and that care about + the ordering of the two. We describe each non-option ARGV-element + as if it were the argument of an option with character code 1. + Using `-' as the first character of the list of option characters + selects this mode of operation. + + The special argument `--' forces an end of option-scanning regardless + of the value of `ordering'. In the case of RETURN_IN_ORDER, only + `--' can cause `getopt' to return EOF with `optind' != ARGC. */ + +static enum +{ + REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER +} ordering; + +/* Value of POSIXLY_CORRECT environment variable. */ +static char *posixly_correct; + +#ifdef __GNU_LIBRARY__ +/* We want to avoid inclusion of string.h with non-GNU libraries + because there are many ways it can cause trouble. + On some systems, it contains special magic macros that don't work + in GCC. */ +#include +#define my_index strchr +#else + +/* Avoid depending on library functions or files + whose names are inconsistent. */ + +#if !defined(_WIN32) +char *getenv (); +#endif + +static char * +my_index (str, chr) + const char *str; + int chr; +{ + while (*str) + { + if (*str == chr) + return (char *) str; + str++; + } + return 0; +} + +/* If using GCC, we can safely declare strlen this way. + If not using GCC, it is ok not to declare it. */ +#ifdef __GNUC__ +/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h. + That was relevant to code that was here before. */ +#ifndef __STDC__ +/* gcc with -traditional declares the built-in strlen to return int, + and has done so at least since version 2.4.5. -- rms. */ +extern int strlen (const char *); +#endif /* not __STDC__ */ +#endif /* __GNUC__ */ + +#endif /* not __GNU_LIBRARY__ */ + +/* Handle permutation of arguments. */ + +/* Describe the part of ARGV that contains non-options that have + been skipped. `first_nonopt' is the index in ARGV of the first of them; + `last_nonopt' is the index after the last of them. */ + +static int first_nonopt; +static int last_nonopt; + +/* Exchange two adjacent subsequences of ARGV. + One subsequence is elements [first_nonopt,last_nonopt) + which contains all the non-options that have been skipped so far. + The other is elements [last_nonopt,optind), which contains all + the options processed since those non-options were skipped. + + `first_nonopt' and `last_nonopt' are relocated so that they describe + the new indices of the non-options in ARGV after they are moved. */ + +static void +exchange (argv) + char **argv; +{ + int bottom = first_nonopt; + int middle = last_nonopt; + int top = optind; + char *tem; + + /* Exchange the shorter segment with the far end of the longer segment. + That puts the shorter segment into the right place. + It leaves the longer segment in the right place overall, + but it consists of two parts that need to be swapped next. */ + + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + /* Bottom segment is the short one. */ + int len = middle - bottom; + register int i; + + /* Swap it with the top part of the top segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + } + /* Exclude the moved bottom segment from further swapping. */ + top -= len; + } + else + { + /* Top segment is the short one. */ + int len = top - middle; + register int i; + + /* Swap it with the bottom part of the bottom segment. */ + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + } + /* Exclude the moved top segment from further swapping. */ + bottom += len; + } + } + + /* Update records for the slots the non-options now occupy. */ + + first_nonopt += (optind - last_nonopt); + last_nonopt = optind; +} + +/* Initialize the internal data when the first call is made. */ + +static const char * +_getopt_initialize (optstring) + const char *optstring; +{ + /* Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + first_nonopt = last_nonopt = optind = 1; + + nextchar = NULL; + + posixly_correct = getenv ("POSIXLY_CORRECT"); + + /* Determine how to handle the ordering of options and nonoptions. */ + + if (optstring[0] == '-') + { + ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + ordering = REQUIRE_ORDER; + ++optstring; + } + else if (posixly_correct != NULL) + ordering = REQUIRE_ORDER; + else + ordering = PERMUTE; + + return optstring; +} + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `getopt' finds another option character, it returns that character, + updating `optind' and `nextchar' so that the next call to `getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `getopt' returns `EOF'. + Then `optind' is the index in ARGV of the first ARGV-element + that is not an option. (The ARGV-elements have been permuted + so that those that are not options now come last.) + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `optarg'. Two colons mean an option that + wants an optional arg; if there is text in the current ARGV-element, + it is returned in `optarg', otherwise `optarg' is set to zero. + + If OPTSTRING starts with `-' or `+', it requests different methods of + handling the non-option ARGV-elements. + See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above. + + Long-named options begin with `--' instead of `-'. + Their names may be abbreviated as long as the abbreviation is unique + or is an exact match for some defined option. If they have an + argument, it follows the option name in the same ARGV-element, separated + from the option name by a `=', or else the in next ARGV-element. + When `getopt' finds a long-named option, it returns 0 if that option's + `flag' field is nonzero, the value of the option's `val' field + if the `flag' field is zero. + + The elements of ARGV aren't really const, because we permute them. + But we pretend they're const in the prototype to be compatible + with other systems. + + LONGOPTS is a vector of `struct option' terminated by an + element containing a name which is zero. + + LONGIND returns the index in LONGOPT of the long-named option found. + It is only valid when a long-named option has been found by the most + recent call. + + If LONG_ONLY is nonzero, '-' as well as '--' can introduce + long-named options. */ + +int +_getopt_internal (argc, argv, optstring, longopts, longind, long_only) + int argc; + char *const *argv; + const char *optstring; + const struct option *longopts; + int *longind; + int long_only; +{ + optarg = NULL; + + if (optind == 0) + optstring = _getopt_initialize (optstring); + + if (nextchar == NULL || *nextchar == '\0') + { + /* Advance to the next ARGV-element. */ + if (ordering == PERMUTE) + { + /* If we have just processed some options following some non-options, + exchange them so that the options come first. */ + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (last_nonopt != optind) + first_nonopt = optind; + + /* Skip any additional non-options + and extend the range of non-options previously skipped. */ + while (optind < argc + && (argv[optind][0] != '-' || argv[optind][1] == '\0')) + optind++; + last_nonopt = optind; + } + + /* The special ARGV-element `--' means premature end of options. + Skip it like a null option, + then exchange with previous non-options as if it were an option, + then skip everything else like a non-option. */ + if (optind != argc && !strcmp (argv[optind], "--")) + { + optind++; + + if (first_nonopt != last_nonopt && last_nonopt != optind) + exchange ((char **) argv); + else if (first_nonopt == last_nonopt) + first_nonopt = optind; + last_nonopt = argc; + + optind = argc; + } + + /* If we have done all the ARGV-elements, stop the scan + and back over any non-options that we skipped and permuted. */ + if (optind == argc) + { + /* Set the next-arg-index to point at the non-options + that we previously skipped, so the caller will digest them. */ + if (first_nonopt != last_nonopt) + optind = first_nonopt; + return EOF; + } + + /* If we have come to a non-option and did not permute it, + either stop the scan or describe it to the caller and pass it by. */ + if ((argv[optind][0] != '-' || argv[optind][1] == '\0')) + { + if (ordering == REQUIRE_ORDER) + return EOF; + optarg = argv[optind++]; + return 1; + } + + /* We have found another option-ARGV-element. + Skip the initial punctuation. */ + nextchar = (argv[optind] + 1 + + (longopts != NULL && argv[optind][1] == '-')); + } + + /* Decode the current option-ARGV-element. */ + + /* Check whether the ARGV-element is a long option. + + If long_only and the ARGV-element has the form "-f", where f is + a valid short option, don't consider it an abbreviated form of + a long option that starts with f. Otherwise there would be no + way to give the -f short option. + + On the other hand, if there's a long option "fubar" and + the ARGV-element is "-fu", do consider that an abbreviation of + the long option, just like "--fu", and not "-f" with arg "u". + + This distinction seems to be the most useful approach. */ + if (longopts != NULL + && (argv[optind][1] == '-' + || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1]))))) + { + char *nameend; + const struct option *p; + const struct option *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound; + int option_index; + + for (nameend = nextchar; *nameend && *nameend != '='; nameend++) + /* Do nothing. */ ; + + /* Test all long options for either exact match + or abbreviated matches. */ + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp (p->name, nextchar, nameend - nextchar)) + { + if (nameend - nextchar == (int) strlen (p->name)) + { + /* Exact match found. */ + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + /* First nonexact match found. */ + pfound = p; + indfound = option_index; + } + else + /* Second or later nonexact match found. */ + ambig = 1; + } + + if (ambig && !exact) + { + if (opterr) + fprintf (stderr, "%s: option `%s' is ambiguous\n", + argv[0], argv[optind]); + nextchar += strlen (nextchar); + optind++; + return '?'; + } + + if (pfound != NULL) + { + option_index = indfound; + optind++; + if (*nameend) + { + /* Don't test has_arg with >, because some C compilers don't + allow it to be used on enums. */ + if (pfound->has_arg) + optarg = nameend + 1; + else + { + if (opterr) + { + if (argv[optind - 1][1] == '-') + /* --option */ + fprintf (stderr, + "%s: option `--%s' doesn't allow an argument\n", + argv[0], pfound->name); + else + /* +option or -option */ + fprintf (stderr, + "%s: option `%c%s' doesn't allow an argument\n", + argv[0], argv[optind - 1][0], pfound->name); + } + nextchar += strlen (nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (optind < argc) + optarg = argv[optind++]; + else + { + if (opterr) + fprintf (stderr, "%s: option `%s' requires an argument\n", + argv[0], argv[optind - 1]); + nextchar += strlen (nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + + nextchar += strlen (nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + + /* Can't find it as a long option. If this is not getopt_long_only, + or the option starts with '--' or is not a valid short + option, then it's an error. + Otherwise interpret it as a short option. */ + if (!long_only || argv[optind][1] == '-' + || my_index (optstring, *nextchar) == NULL) + { + if (opterr) + { + if (argv[optind][1] == '-') + /* --option */ + fprintf (stderr, "%s: unrecognized option `--%s'\n", + argv[0], nextchar); + else + /* +option or -option */ + fprintf (stderr, "%s: unrecognized option `%c%s'\n", + argv[0], argv[optind][0], nextchar); + } + nextchar = (char *) ""; + optind++; + return '?'; + } + } + + /* Look at and handle the next short option-character. */ + { + char c = *nextchar++; + char *temp = my_index (optstring, c); + + /* Increment `optind' when we start to process its last character. */ + if (*nextchar == '\0') + ++optind; + + if (temp == NULL || c == ':') + { + if (opterr) + { + if (posixly_correct) + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, "%s: illegal option -- %c\n", argv[0], c); + else + fprintf (stderr, "%s: invalid option -- %c\n", argv[0], c); + } + optopt = c; + return '?'; + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + /* This is an option that accepts an argument optionally. */ + if (*nextchar != '\0') + { + optarg = nextchar; + optind++; + } + else + optarg = NULL; + nextchar = NULL; + } + else + { + /* This is an option that requires an argument. */ + if (*nextchar != '\0') + { + optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + optind++; + } + else if (optind == argc) + { + if (opterr) + { + /* 1003.2 specifies the format of this message. */ + fprintf (stderr, "%s: option requires an argument -- %c\n", + argv[0], c); + } + optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + /* We already incremented `optind' once; + increment it again when taking next ARGV-elt as argument. */ + optarg = argv[optind++]; + nextchar = NULL; + } + } + return c; + } +} + + +int +getopt (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + return _getopt_internal (argc, argv, optstring, + (const struct option *) 0, + (int *) 0, + 0); +} + +/* getopt_long and getopt_long_only entry points for GNU getopt. + Copyright (C) 1987, 88, 89, 90, 91, 92, 1993 + Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify it + it under the terms of the GNU General Public License Version 2 as + published by the Free Software Foundation. You may not use, modify or + distribute this program under any other version of the GNU General + Public License. + + 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. */ + +int +getopt_long (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 0); +} + +/* Like getopt_long, but '-' as well as '--' can indicate a long option. + If an option that starts with '-' (not '--') doesn't match a long option, + but does match a short option, it is parsed as a short option + instead. */ + +int +getopt_long_only (argc, argv, options, long_options, opt_index) + int argc; + char *const *argv; + const char *options; + const struct option *long_options; + int *opt_index; +{ + return _getopt_internal (argc, argv, options, long_options, opt_index, 1); +} + + +#endif /* _LIBC or not __GNU_LIBRARY__. */