Skip to content

Commit

Permalink
Add XGD support for Kreon Drives (#181)
Browse files Browse the repository at this point in the history
* Add Support for dumping Xbox Security Sectors

This allows for dumping Xbox Security Sectors from a Kreon Drive.
Currently there are a couple points I would like to revist:

    - Currently always dumps security sectors when dumping with a
      "KREON V1.00" drive, should only dump when XGD game detected

* Add Kreon "Set Lock State" Command

This adds the command to allow for locking/unlocking the drive in order
to read the disc contents.

* Unlock the Drive and Dump the XGD

* Zero out L1 Middle

* Dump L1 Video at end

With this commit the SHA of my ISO matched DIC on Linux, however it
didn't match what I get from DIC on Windows (which is what is in redump)

* Add Support for XDG2/3

Add support for XBox 360

* Fix Typos and Xbox 360 Dump Support

* Cleanup and XGD3 support

* Make variables const

* Refactor, move code into main loop

* Fix issue where skip regions overlap

* Refactoring and style

* Proper Kreon lock/unlock

* Try unlock Kreon at start

* Fix libata issue

libata checks for minimum CDB size based on opcode. In this case it was
(0xFF >> 5) & 7 which was 7 which libata used to index into an array of
minimum sizes and the minimum in this case was 10. By switching from CDB
6 to CDB 10 we can now send Kreon specific commands to the drive when
connected via SATA on Linux.

* Don't check capacity when locking

* Cleanup no-longer needed code, refactoring

* Fix state not being updated for skip regions

Fixes an issue where state was not being updated when we write zeroes to
the iso which would cause issues during refine.

Some refactoring as well

* Fix refine/verify not having skip regions or lock sector set

* Clean up security sector with ssv1/ssv2 fixes

Note: SSV2 fixes have been added, however as 0800 support is not
currently present this code should not be needed at this point
in time.

* Fix PFI not hashing correctly

This fixes an issue where we modify the PFI in order to make the prints
output the correct values. Now instead of modifying the structure itself
we just store the value and use it for logging.

* Fix Style issues

- switch to CDB10 Struct fro KREON Lock command
- switch from post- to pre-increment
- switch from std::array<uint32_t, 2> to std::pair<uint32_t, uint32_t>
- remove unneeded clean_ss variable
- switch to using endian_swap instead of manually creating xgd_type data

* Use struct for xgd_get_type, rename .raw_ss to .security

* Pull Xbox SS dump loop out of cmd.ixx

This pulls the loop out of cmd.ixx when dumping the security sector from
an XGD

* Do not store cleaned SS, clean when refining

This updates the behavior so that while we clean the security sectors
for use they are not saved as a file. When refining we clean the loaded
SS in order to make sure it matches (unless forcing).

* Add initial XGD1 and XGD2 Security Sector structs

Adds currently known fields for XGD1 and XGD2 security sectors for
potential future use.

---------

Co-authored-by: Deterous <138427222+Deterous@users.noreply.github.com>
  • Loading branch information
tbejos and Deterous authored Nov 3, 2024
1 parent c2f0653 commit fc82107
Show file tree
Hide file tree
Showing 5 changed files with 502 additions and 7 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ target_sources(redumper
"utils/misc.ixx"
"utils/signal.ixx"
"utils/strings.ixx"
"utils/xbox.ixx"
"debug.ixx"
"dump.ixx"
"drive.ixx"
Expand Down
233 changes: 226 additions & 7 deletions dvd/dvd_dump.ixx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module;
#include <functional>
#include <map>
#include <set>
#include <utility>
#include "throw_line.hh"

export module dvd.dump;
Expand All @@ -26,6 +27,7 @@ import utils.logger;
import utils.misc;
import utils.signal;
import utils.strings;
import utils.xbox;



Expand Down Expand Up @@ -374,9 +376,19 @@ export bool redumper_dump_dvd(Context &ctx, const Options &options, DumpMode dum
if(dump_mode == DumpMode::DUMP)
image_check_overwrite(options);

SPTD::Status status;

// unlock drive if Kreon firmware detected so we can identify XGD later
if(ctx.drive_config.vendor_specific.starts_with("KREON V1.00"))
{
status = cmd_kreon_set_lock_state(*ctx.sptd, KREON_LockState::WXRIPPER);
if(status.status_code)
LOG("warning: failed to unlock Kreon drive, SCSI ({})", SPTD::StatusMessage(status));
}

// get sectors count
uint32_t sector_last, block_length;
auto status = cmd_read_capacity(*ctx.sptd, sector_last, block_length, false, 0, false);
status = cmd_read_capacity(*ctx.sptd, sector_last, block_length, false, 0, false);
if(status.status_code)
throw_line("failed to read capacity, SCSI ({})", SPTD::StatusMessage(status));
if(block_length != FORM1_DATA_SIZE)
Expand All @@ -386,6 +398,12 @@ export bool redumper_dump_dvd(Context &ctx, const Options &options, DumpMode dum
auto readable_formats = get_readable_formats(*ctx.sptd, profile_is_bluray(ctx.current_profile));

bool trim_to_filesystem_size = false;

bool is_xbox = false;
std::vector<std::pair<uint32_t, uint32_t>> xbox_skip_ranges;
uint32_t xbox_lock_sector = 0;
uint32_t xbox_l1_video_shift = 0;

if(readable_formats.find(READ_DISC_STRUCTURE_Format::PHYSICAL) != readable_formats.end())
{
// function call changes rom flag if discrepancy is detected
Expand All @@ -412,11 +430,133 @@ export bool redumper_dump_dvd(Context &ctx, const Options &options, DumpMode dum
physical_sectors_count += get_layer_length(layer_descriptor);
}

// Kreon drives return incorrect sectors count
if(physical_sectors_count != sectors_count)
{
// Kreon PFI sector count is only for Video portion when XGD present
if(ctx.drive_config.vendor_specific.starts_with("KREON V1.00"))
is_xbox = true;
else
{
LOG("warning: READ_CAPACITY / PHYSICAL sectors count mismatch, using PHYSICAL");
sectors_count = physical_sectors_count;
}
}
}

uint32_t xbox_layer0_end_sector = 0;
if(is_xbox)
{
std::vector<uint8_t> security_sector(0x800);

bool complete_ss = xbox_get_security_sector(*ctx.sptd, security_sector);
if(!complete_ss)
LOG("warning: could not get complete security sector, attempting to continue");

auto security_sector_fn = image_prefix + ".security";
// store security sector
if(dump_mode == DumpMode::DUMP)
write_vector(security_sector_fn, security_sector);

// validate security sector
XGD_Type xgd_type = get_xgd_type((READ_DVD_STRUCTURE_LayerDescriptor &)security_sector[0]);
if(xgd_type == XGD_Type::UNKNOWN)
{
LOG("warning: READ_CAPACITY / PHYSICAL sectors count mismatch, using PHYSICAL");
sectors_count = physical_sectors_count;
LOG("warning: Kreon Drive with malformed XGD detected, reverting to normal DVD mode");
LOG("");
is_xbox = false;
}

if(is_xbox && !physical_structures.empty())
{
LOG("Kreon Drive with XGD{} detected", (uint8_t)xgd_type);
LOG("");

clean_xbox_security_sector(security_sector);

if(dump_mode == DumpMode::REFINE && !options.force_refine)
{
// if not dumping, compare security sector to stored to make sure it's the same disc
if(!std::filesystem::exists(security_sector_fn))
{
throw_line("disc / file security sector doesn't match, refining from a different disc?");
}
else
{
auto refined_security_sector = read_vector(security_sector_fn);
clean_xbox_security_sector(refined_security_sector);

if(refined_security_sector != security_sector)
throw_line("disc / file security sector doesn't match, refining from a different disc?");
}
}

auto &structure = physical_structures.front();

if(structure.size() < sizeof(CMD_ParameterListHeader) + sizeof(READ_DVD_STRUCTURE_LayerDescriptor))
throw_line("invalid layer descriptor size (layer: 0)");

auto &pfi_layer_descriptor = (READ_DVD_STRUCTURE_LayerDescriptor &)structure[sizeof(CMD_ParameterListHeader)];

int32_t lba_first = sign_extend<24>(endian_swap(pfi_layer_descriptor.data_start_sector));
int32_t layer0_last = sign_extend<24>(endian_swap(pfi_layer_descriptor.layer0_end_sector));

uint32_t l1_video_start = layer0_last + 1 - lba_first;
uint32_t l1_video_length = get_layer_length(pfi_layer_descriptor) - l1_video_start;

auto &ss_layer_descriptor = (READ_DVD_STRUCTURE_LayerDescriptor &)security_sector[0];

int32_t ss_lba_first = sign_extend<24>(endian_swap(ss_layer_descriptor.data_start_sector));
int32_t ss_layer0_last = sign_extend<24>(endian_swap(ss_layer_descriptor.layer0_end_sector));

uint32_t l1_padding_length = ss_lba_first - layer0_last - 1;
if(xgd_type == XGD_Type::XGD3)
l1_padding_length += 4096;

// extract security sector ranges
bool is_xgd1 = (xgd_type == XGD_Type::XGD1);

const auto media_specific_offset = offsetof(READ_DVD_STRUCTURE_LayerDescriptor, media_specific);
uint8_t num_ss_regions = ss_layer_descriptor.media_specific[1632 - media_specific_offset];
// partial pre-compute of conversion to Layer 1
const uint32_t layer1_offset = (ss_layer0_last * 2) - 0x30000 + 1;

for(int ss_pos = 1633 - media_specific_offset, i = 0; i < num_ss_regions; ss_pos += 9, ++i)
{
uint32_t start_psn = ((uint32_t)ss_layer_descriptor.media_specific[ss_pos + 3] << 16) | ((uint32_t)ss_layer_descriptor.media_specific[ss_pos + 4] << 8)
| (uint32_t)ss_layer_descriptor.media_specific[ss_pos + 5];
uint32_t end_psn = ((uint32_t)ss_layer_descriptor.media_specific[ss_pos + 6] << 16) | ((uint32_t)ss_layer_descriptor.media_specific[ss_pos + 7] << 8)
| (uint32_t)ss_layer_descriptor.media_specific[ss_pos + 8];
if((i < 8 && is_xgd1) || (i == 0 && !is_xgd1))
{
// Layer 0
xbox_skip_ranges.push_back({ start_psn - 0x30000, end_psn - 0x30000 });
}
else if((i < 16 && is_xgd1) || (i == 3 && !is_xgd1))
{
// Layer 1
xbox_skip_ranges.push_back({ layer1_offset - (start_psn ^ 0xFFFFFF), layer1_offset - (end_psn ^ 0xFFFFFF) });
}
}

// append L1 padding to ranges
xbox_skip_ranges.push_back({ sectors_count, sectors_count + l1_padding_length - 1 });

// sort the skip ranges
std::sort(xbox_skip_ranges.begin(), xbox_skip_ranges.end(), [](const std::pair<uint32_t, uint32_t> &a, const std::pair<uint32_t, uint32_t> &b) { return a.first < b.first; });

// add L1 padding to sectors count
sectors_count += l1_padding_length;

// must relock drive to read L1 video
xbox_lock_sector = sectors_count;
xbox_l1_video_shift = xbox_lock_sector - l1_video_start;

// add L1 video to sectors count
sectors_count += l1_video_length;

// store true layer0_last from SS, so that disc structure logging is correct
xbox_layer0_end_sector = ss_layer_descriptor.layer0_end_sector;
}
}

Expand All @@ -428,7 +568,7 @@ export bool redumper_dump_dvd(Context &ctx, const Options &options, DumpMode dum
for(uint32_t i = 0; i < physical_structures.size(); ++i)
{
std::vector<uint8_t> structure;
cmd_read_disc_structure(*ctx.sptd, structure, 0, 0, i, READ_DISC_STRUCTURE_Format::MANUFACTURER, 0);
status = cmd_read_disc_structure(*ctx.sptd, structure, 0, 0, i, READ_DISC_STRUCTURE_Format::MANUFACTURER, 0);
if(status.status_code)
throw_line("failed to read disc manufacturer structure, SCSI ({})", SPTD::StatusMessage(status));

Expand Down Expand Up @@ -502,7 +642,13 @@ export bool redumper_dump_dvd(Context &ctx, const Options &options, DumpMode dum
for(uint32_t i = 0; i < physical_structures.size(); ++i)
{
auto const &structure = physical_structures[i];
print_physical_structure((READ_DVD_STRUCTURE_LayerDescriptor &)structure[sizeof(CMD_ParameterListHeader)], i);
auto &pfi_layer_descriptor = (READ_DVD_STRUCTURE_LayerDescriptor &)structure[sizeof(CMD_ParameterListHeader)];

// overwrite physical structure with true layer0_last from SS, so that disc structure logging is correct
if(is_xbox)
pfi_layer_descriptor.layer0_end_sector = xbox_layer0_end_sector;

print_physical_structure(pfi_layer_descriptor, i);
}
LOG("");

Expand Down Expand Up @@ -557,7 +703,7 @@ export bool redumper_dump_dvd(Context &ctx, const Options &options, DumpMode dum
if(dump_mode == DumpMode::DUMP && readable_formats.find(READ_DISC_STRUCTURE_Format::COPYRIGHT) != readable_formats.end())
{
std::vector<uint8_t> copyright;
auto status = cmd_read_disc_structure(*ctx.sptd, copyright, 0, 0, 0, READ_DISC_STRUCTURE_Format::COPYRIGHT, 0);
status = cmd_read_disc_structure(*ctx.sptd, copyright, 0, 0, 0, READ_DISC_STRUCTURE_Format::COPYRIGHT, 0);
if(!status.status_code)
{
strip_response_header(copyright);
Expand Down Expand Up @@ -605,12 +751,72 @@ export bool redumper_dump_dvd(Context &ctx, const Options &options, DumpMode dum

SignalINT signal;

uint8_t skip_range_idx = 0;
bool kreon_locked = false;
for(uint32_t s = 0; s < sectors_count;)
{
bool increment = true;

uint32_t sectors_to_read = std::min(sectors_at_once, sectors_count - s);

if(is_xbox && !kreon_locked)
{
// skip xbox security sector ranges and L1 filler range
if(skip_range_idx < xbox_skip_ranges.size())
{
if(xbox_skip_ranges[skip_range_idx].first <= s && s <= xbox_skip_ranges[skip_range_idx].second + 1)
{
if(s == xbox_skip_ranges[skip_range_idx].second + 1)
{
if(options.verbose)
LOG_R("skipped sectors: {}-{}", xbox_skip_ranges[skip_range_idx].first, xbox_skip_ranges[skip_range_idx].second);

++skip_range_idx;
// skip any overlapping ranges we have already completed
while(skip_range_idx < xbox_skip_ranges.size() && s >= xbox_skip_ranges[skip_range_idx].second + 1)
++skip_range_idx;

// if still in a security sector range do not allow later read to happen
if(skip_range_idx < xbox_skip_ranges.size() && xbox_skip_ranges[skip_range_idx].first <= s)
continue;
}
else
{
// skip at most to the end of the security sector range
sectors_to_read = std::min(sectors_to_read, xbox_skip_ranges[skip_range_idx].second + 1 - s);
progress_output(s, sectors_count, errors_scsi);

std::vector<uint8_t> zeroes(sectors_to_read * FORM1_DATA_SIZE);
write_entry(fs_iso, zeroes.data(), FORM1_DATA_SIZE, s, sectors_to_read, 0);
std::fill(file_state.begin(), file_state.end(), State::SUCCESS);
write_entry(fs_state, (uint8_t *)file_state.data(), sizeof(State), s, sectors_to_read, 0);

rom_entry.update(zeroes.data(), sectors_to_read * FORM1_DATA_SIZE);

s += sectors_to_read;
continue;
}
}
else
{
sectors_to_read = std::min(sectors_to_read, xbox_skip_ranges[skip_range_idx].first - s);
}
}

// check if Kreon drive needs locking
if(s < xbox_lock_sector && s + sectors_to_read >= xbox_lock_sector)
sectors_to_read = std::min(sectors_to_read, xbox_lock_sector - s);
else if(s == xbox_lock_sector)
{
status = cmd_kreon_set_lock_state(*ctx.sptd, KREON_LockState::LOCKED);
if(status.status_code)
throw_line("failed to set lock state, SCSI ({})", SPTD::StatusMessage(status));
if(options.verbose)
LOG_R("locked kreon drive at sector: {}", s);
kreon_locked = true;
}
}

bool read = false;
if(dump_mode == DumpMode::DUMP)
{
Expand All @@ -634,7 +840,12 @@ export bool redumper_dump_dvd(Context &ctx, const Options &options, DumpMode dum
progress_output(s, sectors_count, errors_scsi);

std::vector<uint8_t> drive_data(sectors_at_once * FORM1_DATA_SIZE);
auto status = cmd_read(*ctx.sptd, drive_data.data(), FORM1_DATA_SIZE, s, sectors_to_read, dump_mode == DumpMode::REFINE && refine_counter);

uint32_t dump_sector = s;
if(kreon_locked)
dump_sector -= xbox_l1_video_shift;

status = cmd_read(*ctx.sptd, drive_data.data(), FORM1_DATA_SIZE, dump_sector, sectors_to_read, dump_mode == DumpMode::REFINE && refine_counter);

if(status.status_code)
{
Expand Down Expand Up @@ -735,6 +946,14 @@ export bool redumper_dump_dvd(Context &ctx, const Options &options, DumpMode dum
s += sectors_to_read;
}

if(is_xbox)
{
// re-unlock drive before returning
status = cmd_kreon_set_lock_state(*ctx.sptd, KREON_LockState::WXRIPPER);
if(status.status_code)
LOG("warning: failed to unlock drive at end of dump, SCSI ({})", SPTD::StatusMessage(status));
}

if(!signal.interrupt())
{
progress_output(sectors_count, sectors_count, errors_scsi);
Expand Down
38 changes: 38 additions & 0 deletions scsi/cmd.ixx
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,44 @@ SPTD::Status cmd_get_configuration(SPTD &sptd)
}


export SPTD::Status cmd_kreon_get_security_sector(SPTD &sptd, std::vector<uint8_t> &response_data, uint8_t ss_val)
{
// AD 00 FF 02 FD FF FE 00 08 00 xx C0
CDB12_ReadDiscStructure cdb = {};
cdb.operation_code = (uint8_t)CDB_OperationCode::READ_DISC_STRUCTURE;
*(uint32_t *)cdb.address = endian_swap<uint32_t>(0xFF02FDFF);
cdb.layer_number = 0xFE;
*(uint16_t *)cdb.allocation_length = endian_swap<uint16_t>((uint16_t)response_data.size());
cdb.reserved2 = ss_val;
cdb.control = 0xC0;

return sptd.sendCommand(&cdb, sizeof(cdb), response_data.data(), response_data.size());
}


export SPTD::Status cmd_kreon_set_lock_state(SPTD &sptd, KREON_LockState lock_state)
{
// FF 08 01 01 (Legacy)
// FF 08 01 11 xx
bool is_legacy = (lock_state == KREON_LockState::LEGACY);
CDB10_KREON_SetLockState cdb = {};
cdb.operation_code = 0xFF;
cdb.unknown1 = 0x08;
cdb.unknown2 = 0x01;
if(is_legacy)
{
cdb.lock_mode = 0x01;
}
else
{
cdb.lock_mode = 0x11;
cdb.extended = (uint8_t)lock_state;
}

return sptd.sendCommand(&cdb, sizeof(cdb), nullptr, 0);
}


export SPTD::Status cmd_start_stop_unit(SPTD &sptd, uint8_t load_eject, uint8_t start)
{
CDB6_StartStopUnit cdb = {};
Expand Down
Loading

0 comments on commit fc82107

Please sign in to comment.