Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add XGD support for Kreon Drives #181

Merged
merged 29 commits into from
Nov 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a9c5b63
Add Support for dumping Xbox Security Sectors
tbejos Aug 2, 2024
4dc507a
Add Kreon "Set Lock State" Command
tbejos Aug 2, 2024
8713cd4
Unlock the Drive and Dump the XGD
tbejos Aug 2, 2024
b16c0c9
Zero out L1 Middle
tbejos Aug 5, 2024
95cb887
Dump L1 Video at end
tbejos Aug 5, 2024
b250aa4
Add Support for XDG2/3
tbejos Aug 5, 2024
7bf77e5
Fix Typos and Xbox 360 Dump Support
tbejos Aug 6, 2024
554d521
Cleanup and XGD3 support
tbejos Aug 7, 2024
091a376
Make variables const
tbejos Aug 9, 2024
dbb9caa
Refactor, move code into main loop
Deterous Aug 9, 2024
2e72289
Fix issue where skip regions overlap
tbejos Aug 11, 2024
e3d3ea0
Merge pull request #2 from Deterous/main
tbejos Aug 11, 2024
0ac21a7
Refactoring and style
tbejos Aug 12, 2024
2b93df0
Proper Kreon lock/unlock
Deterous Aug 13, 2024
09999fb
Try unlock Kreon at start
Deterous Aug 13, 2024
b169b21
Fix libata issue
tbejos Aug 16, 2024
174fefd
Don't check capacity when locking
Deterous Aug 16, 2024
f6a21fb
Merge pull request #3 from Deterous/main
tbejos Aug 16, 2024
3f8177c
Cleanup no-longer needed code, refactoring
tbejos Aug 16, 2024
51f1363
Fix state not being updated for skip regions
tbejos Aug 17, 2024
9361f67
Fix refine/verify not having skip regions or lock sector set
tbejos Aug 18, 2024
1faf9d1
Clean up security sector with ssv1/ssv2 fixes
tbejos Aug 18, 2024
6e527ae
Fix PFI not hashing correctly
tbejos Aug 18, 2024
e65c19a
Fix Style issues
tbejos Aug 18, 2024
11971f9
Use struct for xgd_get_type, rename .raw_ss to .security
tbejos Aug 19, 2024
dd81526
Pull Xbox SS dump loop out of cmd.ixx
tbejos Aug 23, 2024
5e2f986
Do not store cleaned SS, clean when refining
tbejos Oct 20, 2024
ad95833
Add initial XGD1 and XGD2 Security Sector structs
tbejos Oct 20, 2024
a3e4c7f
Merge remote-tracking branch 'upstream/main'
tbejos Oct 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
superg marked this conversation as resolved.
Show resolved Hide resolved
}

// 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;
superg marked this conversation as resolved.
Show resolved Hide resolved
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);
superg marked this conversation as resolved.
Show resolved Hide resolved

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);
tbejos marked this conversation as resolved.
Show resolved Hide resolved
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));
}
superg marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -447,6 +447,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());
superg marked this conversation as resolved.
Show resolved Hide resolved
cdb.reserved2 = ss_val;
cdb.control = 0xC0;
superg marked this conversation as resolved.
Show resolved Hide resolved

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