/*********************************************************************** Copyright 2002 Ben Rudiak-Gould. 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, or visit . ***********************************************************************/ #include "DVDSubber.h" #include "../include/dvdsynth-device.h" #include template static inline T Min(T a, T b) { return a static inline T Max(T a, T b) { return a>24; dst[1] = val>>16; dst[2] = val>>8; dst[3] = val; } unsigned crc_ccitt(const unsigned char *buf, unsigned len) { const unsigned poly = 0x11021; static unsigned short lookup[256]; unsigned int r; unsigned int i; if (lookup[1] == 0) { unsigned int j,k; for (j = 0; j < 256; ++j) { unsigned int temp = j << 8; for (k = 0; k < 8; ++k) { unsigned int hibit = temp & 32768; temp <<= 1; if (hibit) temp ^= poly; } lookup[j] = temp; } } r = 0; for (i = 0; i < len; ++i) { r = (r << 8) ^ lookup[((r >> 8) ^ buf[i]) & 255]; } return r & 65535; } int check_udf_descriptor(const unsigned char* buf, int type) { if (buf[0]+buf[1]*256 != type) { return -1; } int checksum = 0; for (int i=0; i<16; ++i) { checksum += buf[i]; } if ((checksum - buf[4]*2) & 255) { return -3; } int crc = crc_ccitt(buf+16, buf[10]+buf[11]*256); if (buf[8]+buf[9]*256 != crc) { return -4; } return 0; } unsigned DVDSubberKernel::Hard2Soft(unsigned sector) { unsigned increase = 0; SubpictureInfo* si = subpic_inf; for (int i=0; ihard_lba > sector) break; increase += si->num_sectors; si = si->next; } return sector+increase; } unsigned DVDSubberKernel::Soft2Hard(unsigned sector) { SubpictureInfo* si = subpic_inf; for (int i=0; ihard_lba) { break; } else if (sector < si->hard_lba + si->num_sectors) { return si->hard_lba; } else { sector -= si->num_sectors; } } return sector; } void DVDSubberKernel::GenerateSubpictureSector(SubpictureInfo* si, unsigned subpic_rba, unsigned char* buffer) { g_callbacks->MemCpy(&buffer[4], saved_scr, 6); if (saved_scr_lba != si->hard_lba - 1) { SenseData sense; if (SCSIRESULT_SUCCESS == ReadHardSectors(si->hard_lba - 1, 1, buffer, &sense)) { g_callbacks->MemCpy(saved_scr, &buffer[4], 6); } else { g_callbacks->MemCpy(&buffer[4], saved_scr, 6); } saved_scr_lba = si->hard_lba - 1; } Put4(&buffer[0], 0x000001BA); Put4(&buffer[10], 0x0189C3F8); Put4(&buffer[14], 0x000001BD); Put4(&buffer[18], 0x07EC8180); size_t filled_in; if (subpic_rba == 0) { buffer[22] = 5; unsigned ptm = si->ptm; buffer[23] = ((ptm >> 29) & 0x0E) | 0x21; buffer[24] = ptm >> 22; buffer[25] = (ptm >> 14) | 1; buffer[26] = ptm >> 7; buffer[27] = ptm * 2 + 1; buffer[28] = 0x20 + disc_info->titlesets[title].number_of_existing_subpicture_tracks; filled_in = 29 + g_callbacks->ReadFile(data_file_handle, si->file_offset, 0, Min(2019U, unsigned(si->data_len)), buffer + 29); } else { Put4(&buffer[20], 0x81000020 + disc_info->titlesets[title].number_of_existing_subpicture_tracks); unsigned subpic_data_offset = subpic_rba * 2024 - 5; unsigned subpic_data_len = Min(2024U, unsigned(si->data_len - subpic_data_offset)); filled_in = 24 + g_callbacks->ReadFile(data_file_handle, si->file_offset + subpic_data_offset, 0, subpic_data_len, buffer + 24); } if (filled_in < 2048) { buffer[18] = (filled_in - 20) >> 8; buffer[19] = (filled_in - 20); if (filled_in < 2048-6) { Put4(&buffer[filled_in+2], (2048-6)-filled_in); Put4(&buffer[filled_in], 0x000001BE); g_callbacks->MemSet(buffer+filled_in+6, -1, (2048-6)-filled_in); } else { int stuffing = 2048-filled_in; buffer[13] += stuffing; for (int i=filled_in-1; i>=14; --i) { buffer[i+stuffing] = buffer[i]; buffer[i] = 255; } } } } unsigned GetICBStartLBA(const unsigned char* buffer, unsigned buf_lba) { // x86-specific! unsigned partition_begin = buf_lba - *(unsigned*)(buffer+12); unsigned l_ea = *(unsigned*)(buffer+168); if (l_ea > 2048-176-8) { return 0; } return partition_begin + *(unsigned*)(buffer+176+l_ea+4); } void SetICBLengthInBlocks(const unsigned char* buffer, unsigned length) { // x86-specific! *(unsigned*)(buffer+56) = length << 11; *(unsigned*)(buffer+60) = length >> 21; *(unsigned*)(buffer+64) = length; *(unsigned*)(buffer+68) = 0; unsigned l_ea = *(unsigned*)(buffer+168); *(unsigned*)(buffer+176+l_ea) = length << 11; } void RecalcCRC(unsigned char* buffer) { buffer[4] -= buffer[8]+buffer[9]; *(unsigned short*)(buffer+8) = crc_ccitt(buffer+16, *(unsigned short*)(buffer+10)); buffer[4] += buffer[8]+buffer[9]; } void SetICBStartLBA(unsigned char* buffer, unsigned buf_lba, unsigned new_lba) { // x86-specific! unsigned partition_begin = buf_lba - *(unsigned*)(buffer+12); unsigned l_ea = *(unsigned*)(buffer+168); *(unsigned*)(buffer+176+l_ea+4) = new_lba - partition_begin; } void DVDSubberKernel::PatchHardSector(unsigned hard_lba, unsigned char* buffer) { for (int ts = disc_info->num_title_sets-1; ts >= 0; --ts) { TitleSetInfo& tsi = disc_info->titlesets[ts]; if (hard_lba >= tsi.vts_ifo_sector) { if (hard_lba >= tsi.vts_bup_sector) { PatchVTSIFO(ts, tsi, hard_lba - tsi.vts_bup_sector, buffer); } else if (hard_lba >= tsi.vts_vobs_sector) { if (Get4(buffer) == 0x000001BA) { g_callbacks->MemCpy(saved_scr, &buffer[4], 6); saved_scr_lba = hard_lba; // FIXME: should I check for pack header stuffing? switch (Get4(buffer+14)) { case 0x000001BB: { TranslateRelativeSector(&buffer[0x02D], tsi.vts_vobs_sector, false); TranslateRelativeSector(&buffer[0x40B], tsi.vts_vobs_sector, false); TranslateRelativeSector(&buffer[0x42D], hard_lba, true); for (int refframe=0; refframe<4; ++refframe) { TranslateRelativeSector(&buffer[0x40F+refframe*4], hard_lba, false); } for (int angle=0; angle<9; ++angle) { TranslateRelativeSector(&buffer[0x069+angle*4], hard_lba, false); TranslateRelativeSector(&buffer[0x4BB+angle*4], hard_lba, true); } for (int various=0; various<42; ++various) { TranslateRelativeSector(&buffer[0x4F1+various*4], hard_lba, false); } Put4(&buffer[0x5A9 + 4 * tsi.number_of_existing_subpicture_tracks], 0x7FFFFFFF); break; } case 0x000001BD: { // The R2 Cagliostro DVD has four subtitle tracks, but // only two are mentioned in the INF file. (The other // two seem to be copies of the first two.) To prevent // added subtitles from mixing with existing undocumented // subtitles, I convert them into padding blocks here. unsigned char substream_id = buffer[23 + buffer[22]]; if (substream_id == 0x20 + tsi.number_of_existing_subpicture_tracks) { Put4(buffer+14, 0x000001BE); g_callbacks->MemSet(buffer+20, 0xFF, 2048-20); } break; } } } } else if (hard_lba >= tsi.vtsm_vobs_sector) { // no action } else { PatchVTSIFO(ts, tsi, hard_lba - tsi.vts_ifo_sector, buffer); } return; } } if (hard_lba >= disc_info->vmg_bup_sector) { PatchVMGIFO(hard_lba - disc_info->vmg_bup_sector, buffer); } else if (hard_lba >= disc_info->vmg_vobs_sector) { // no action } else if (hard_lba >= disc_info->vmg_ifo_sector) { PatchVMGIFO(hard_lba - disc_info->vmg_ifo_sector, buffer); } else { if (check_udf_descriptor(buffer, 261) >= 0) { // file entry unsigned start_lba = GetICBStartLBA(buffer, hard_lba); if (start_lba) { if (start_lba < disc_info->titlesets[title].vts_vobs_sector) { // no action } else if (start_lba < disc_info->titlesets[title].vts_bup_sector) { // need to juggle the sizes: try to figure out which VTS_##_# file this is unsigned last_digit_minus_1 = (start_lba - disc_info->titlesets[title].vts_vobs_sector) / 0x7F000; // round down if (last_digit_minus_1 > 8) last_digit_minus_1 = 8; // does this make sense? unsigned new_start_lba = disc_info->titlesets[title].vts_vobs_sector + last_digit_minus_1 * 0x7FFFF; unsigned length = 0x7FFFF; unsigned soft_end = Hard2Soft(disc_info->titlesets[title].vts_bup_sector); if (new_start_lba > soft_end) new_start_lba = soft_end; if (new_start_lba + length > soft_end) length = soft_end - new_start_lba; SetICBLengthInBlocks(buffer, length); SetICBStartLBA(buffer, hard_lba, new_start_lba); } else { start_lba += Hard2Soft(disc_info->titlesets[title].vts_bup_sector) - disc_info->titlesets[title].vts_bup_sector; SetICBStartLBA(buffer, hard_lba, start_lba); } RecalcCRC(buffer); } } else if (check_udf_descriptor(buffer, 5) >= 0) { // partition descriptor Put4(buffer+192, Hard2Soft(Get4(buffer+192))); RecalcCRC(buffer); } } } void DVDSubberKernel::TranslateRelativeSector(unsigned char* pos, unsigned hard_sector, bool and_size) { unsigned rel = Get4(pos); unsigned offset = rel & 0x7FFFFFFF; if (offset >= 0x3FFFFFFF) return; unsigned backward = rel & 0x80000000; unsigned hard_sector_2 = backward ? hard_sector-offset : hard_sector + offset; unsigned soft_sector_2 = Hard2Soft(hard_sector_2); int soft_rel = soft_sector_2 - Hard2Soft(hard_sector); Put4(pos, soft_rel >= 0 ? soft_rel : (0x80000000 - soft_rel)); if (and_size) { unsigned size = pos[4]*256 + pos[5]; if (size != 0 && size != 0xFFFF) { unsigned soft_size = Hard2Soft(hard_sector_2 + size) - soft_sector_2; pos[4] = soft_size >> 8; pos[5] = soft_size; } } } void DVDSubberKernel::UpdateSectorTable(unsigned offset, unsigned end_offset, unsigned record_min, unsigned record_size, unsigned base_lba, unsigned ifo_sector, unsigned char* buffer) { record_min *= 4; record_size *= 4; int buf_offset; unsigned record_pos; if (offset < ifo_sector*2048) { buf_offset = 0; record_pos = (ifo_sector*2048 - offset) % record_size; } else { buf_offset = (offset - ifo_sector*2048); record_pos = 0; } int buf_end_offset = end_offset - ifo_sector*2048; if (buf_end_offset > 2048) { buf_end_offset = 2048; } for (int i = buf_offset; i < buf_end_offset; i += 4) { if (record_pos >= record_min) { TranslateRelativeSector(&buffer[i], base_lba, false); } record_pos += 4; if (record_pos >= record_size) record_pos -= record_size; } } void DVDSubberKernel::PatchVMGIFO(unsigned ifo_sector, unsigned char* buffer) { UpdateSectorTable(disc_info->vmg_tt_srpt_offset, disc_info->vmg_tt_srpt_end_offset, 2, 3, disc_info->vmg_ifo_sector, ifo_sector, buffer); // FIXME: VMG copy of subpicture info } void DVDSubberKernel::PatchVTSIFO(unsigned ts, const TitleSetInfo& tsi, unsigned ifo_sector, unsigned char* buffer) { if (ts != title) return; if (ifo_sector == 0) { TranslateRelativeSector(&buffer[4], tsi.vts_ifo_sector, false); // FIXME: hack unsigned tracks = tsi.number_of_existing_subpicture_tracks; buffer[0x255] = tracks + 1; g_callbacks->MemSet(buffer + 0x256 + tracks*6, 0, 6); // buffer[0x256+tracks*6] = 1; // buffer[0x256+tracks*6+2] = 'j'; // buffer[0x256+tracks*6+3] = 'a'; } else { for (unsigned pgc = 0; pgc < tsi.num_pgcs; ++pgc) { UpdateSectorTable( tsi.pgcs[pgc].cell_playback_info_table_offset, tsi.pgcs[pgc].cell_playback_info_table_end_offset, 2, 6, tsi.vts_vobs_sector, ifo_sector, buffer); int ss_offset = (tsi.pgcs[pgc].subpicture_status_offset + 4 * tsi.number_of_existing_subpicture_tracks) - (ifo_sector * 2048); if (ss_offset >= 0 && ss_offset <= 2044) { Put4(&buffer[ss_offset], 0x80000000 + 0x01010101 * tsi.number_of_existing_subpicture_tracks); } for (int pal = 0; pal < palette_size; ++pal) { int pal_offset = tsi.pgcs[pgc].subpicture_status_offset - ifo_sector * 2048 + 0x88 + 4*15 - 4*pal; if (pal_offset >= 0 && pal_offset <= 2044) { Put4(&buffer[pal_offset], palette[pal]); } } } for (unsigned tmap = 0; tmap < tsi.num_tmaps; ++tmap) { UpdateSectorTable( tsi.tmaps[tmap].offset, tsi.tmaps[tmap].end_offset, 0, 1, tsi.vts_vobs_sector, ifo_sector, buffer); } UpdateSectorTable(tsi.vts_c_adt_offset, tsi.vts_c_adt_end_offset, 1, 3, tsi.vts_vobs_sector, ifo_sector, buffer); UpdateSectorTable(tsi.vts_vobu_admap_offset, tsi.vts_vobu_admap_end_offset, 0, 1, tsi.vts_vobs_sector, ifo_sector, buffer); } } // returns true if hard sector range, false if subtitle bool DVDSubberKernel::FindLBASource(unsigned soft_lba, unsigned num_blocks, unsigned* run_length, unsigned* hard_lba, SubpictureInfo** psi, unsigned* subpicture_rba) { unsigned earlier_soft_sectors = 0; SubpictureInfo* si = subpic_inf; for (int subpic = 0; subpic < num_subpics; ++subpic, si = si->next) { unsigned subpic_start = si->hard_lba + earlier_soft_sectors; if (subpic_start > soft_lba) { *run_length = Min(subpic_start-soft_lba, num_blocks); *hard_lba = soft_lba - earlier_soft_sectors; return true; } unsigned subpic_end = subpic_start + si->num_sectors; if (subpic_end > soft_lba) { *run_length = Min(subpic_end-soft_lba, num_blocks); *psi = si; *subpicture_rba = soft_lba - subpic_start; return false; } earlier_soft_sectors += si->num_sectors; } *run_length = num_blocks; *hard_lba = soft_lba - earlier_soft_sectors; return true; } scsi_result_t DVDSubberKernel::ReadHardSectors(unsigned lba, unsigned count, unsigned char* buffer, SenseData* sense) { static unsigned char cdb[10] = { 0x28, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; Put4(&cdb[5], count); // order matters here... Put4(&cdb[2], lba); unsigned long buflen = count * 2048; return child->ScsiCommand(child, cdb, 10, buffer, &buflen, 1, sense); } scsi_result_t DVDSubberKernel::ReadSectors(unsigned soft_lba, unsigned num_soft_sectors, unsigned char* buffer, SenseData* sense) { //printf("\nrange: %06X - %06X\n", soft_lba, soft_lba + num_soft_sectors); // first find the range of hard sectors to read unsigned pos = 0; unsigned first_hard_sector = 0, num_hard_sectors = 0; while (pos < num_soft_sectors) { unsigned run_length, hard_lba, subpic_rba; SubpictureInfo* si; if (FindLBASource(soft_lba + pos, num_soft_sectors - pos, &run_length, &hard_lba, &si, &subpic_rba)) { // hard if (num_hard_sectors == 0) { first_hard_sector = hard_lba; } num_hard_sectors += run_length; } pos += run_length; } //printf(" hard: %06X - %06X\n", first_hard_sector, first_hard_sector + num_hard_sectors); // now read them unsigned char* hardp = buffer + (num_soft_sectors - num_hard_sectors) * 2048; scsi_result_t result = ReadHardSectors(first_hard_sector, num_hard_sectors, hardp, sense); if (result != SCSIRESULT_SUCCESS) return result; // now go back and put everything into place pos = 0; unsigned char* p = buffer; while (pos < num_soft_sectors) { unsigned run_length, hard_lba, subpic_rba; SubpictureInfo* si; bool hard = FindLBASource(soft_lba + pos, num_soft_sectors - pos, &run_length, &hard_lba, &si, &subpic_rba); for (unsigned i=0; iMemCpy(p, hardp, 2048); } PatchHardSector(hard_lba, p); hardp += 2048; ++hard_lba; } else { GenerateSubpictureSector(si, subpic_rba, p); ++subpic_rba; } p += 2048; } pos += run_length; } return SCSIRESULT_SUCCESS; } scsi_result_t ScsiCommand(DvsDeviceKernel* _self, const unsigned char* cdb, int cdblen, unsigned char* buffer, unsigned long* pbuflen, int inout, SenseData* sense) { DVDSubberKernel* self = (DVDSubberKernel*)_self; if (self->state == stateActive || cdb[0] == 0x12) { unsigned char newcdb[16]; if (self->num_subpics > 0) { switch (cdb[0]) { case SCSIOP_READ: { unsigned first_sector = Get4(&cdb[2]); unsigned num_sectors = cdb[7]*256+cdb[8]; return self->ReadSectors(first_sector, num_sectors, buffer, sense); } case SCSIOP_READ_CAPACITY: { scsi_result_t result = self->child->ScsiCommand(self->child, cdb, cdblen, buffer, pbuflen, inout, sense); if (result == SCSIRESULT_SUCCESS && *pbuflen >= 4) { Put4(buffer, self->Hard2Soft(Get4(buffer))); } return result; } case SCSIOP_REPORT_KEY: g_callbacks->MemCpy(newcdb, cdb, cdblen); Put4(newcdb+2, self->Soft2Hard(Get4(newcdb+2))); cdb = newcdb; break; } } return self->child->ScsiCommand(self->child, cdb, cdblen, buffer, pbuflen, inout, sense); } else if (self->state == stateUpdating) { return MAKE_SCSIRESULT_ERROR(0x23A00); // MEDIUM NOT PRESENT } else /*if (self->state == stateUpdated)*/ { self->state = stateActive; return MAKE_SCSIRESULT_ERROR(0x62800); // NOT READY TO READY CHANGE, MEDIUM MAY HAVE CHANGED } } extern "C" dvs_scsi_func* __cdecl GetDispatchFunc() { return ScsiCommand; } extern "C" void* __cdecl SetState(DVDSubberKernel* self, int state) { self->state = state; return 0; } extern "C" void __cdecl DvdsynthDriverInit(DvsDockingBayKernelGlobal* callbacks) { g_callbacks = callbacks; } extern "C" unsigned __stdcall DLLEntry(void*, unsigned, void*) { return 1; }