Log in

View Full Version : first attempt at cmd line iso decrypter based on libcdio/udf and dvdcss


Pages : [1] 2 3 4

spotter
18th October 2010, 04:10
So, I've had an issue with recovering DVDs.

I've written about this before, my methodology in linux is

1) lsdvd - to authenticate the disk
2) ddrescue to recover all of the DVD (sometimes requires getting a second DVD from the library, also possibly scratched)

this results in an image with CSS scrambled sectors.

this image seems playable in anything using libdvdread/nav/dvdcss (i.e. anything in Linux, and VLC on windows) if I pass the ISO to it.

However, that's not so good. I can remove the scrambled sectors if I mount this iso w/ daemon tools in windows and use dvd decrypter, though it complains a bit. so I decided it be nice to try and write a program in Linux that could do it, and what I have below is my 3rd attempt (my first attempt worked beautifully, except didn't know when to have dvdcss try to see if key needed to change, need to do it at the start of each VOB it seems, and my second attempt at just reading 1 2k block and calling dvdcss_seek() after each block was a horrible idea due to the way dvdcss works)

So what's the issue, the below code, is my attempt at making it use libcdio/udf to find the starting block for each VOB file. Unfortunately, libcdio/udf hide that information from the programmer. so a bit stuck. A hackish idea was to mount loopback mount the image and use the FIBMAP ioctl to find the blocks, but that requires root, which I don't want to do).

any good ideas on how to get this block information?

#include <stdio.h>
#include <unistd.h>
#include <dvdcss/dvdcss.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cdio/cdio.h>
#include <cdio/udf.h>
#include <vector>
#include <algorithm>
#include <string.h>
#include <ctype.h>

using namespace std;

#define _FILE_OFFSET_BITS 64
#define _LARGEFILE64_SOURCE 1
#define _LARGEFILE_SOURCE 1

vector<int> start_blocks;

#define MAX 1000

static void add_block(const udf_dirent_t *p_udf_dirent)
{
char *psz_fname=(char *) udf_get_filename(p_udf_dirent);
int len = strlen(psz_fname);
for(int i=0; i < len; i++) {
psz_fname[i] = tolower(psz_fname[i]);
}
if (! strcmp(psz_fname + (len-3), "vob")) {
printf("matched on name %s\n", psz_fname);
//TODO: start_blocks.push_back(p_udf_dirent->i_loc);
} else {
printf("didn't match on name %s\n", psz_fname);
}
}

void find_start_blocks(udf_t *p_udf, udf_dirent_t *p_udf_dirent)
{
if (!p_udf_dirent)
return;

while(udf_readdir(p_udf_dirent)) {
if (udf_is_dir(p_udf_dirent)) {
udf_dirent_t *p_udf_dirent2 = udf_opendir(p_udf_dirent);
if (p_udf_dirent) {
find_start_blocks(p_udf, p_udf_dirent2);
}
} else {
add_block(p_udf_dirent);
}
}
}

int main(int argc, char *argv[])
{
dvdcss_t input;
int fd;

struct stat stat_buf;
int total_blocks;
int pos = 0;

udf_t *p_udf;
udf_dirent_t *p_udf_root;

char buffer[MAX*DVDCSS_BLOCK_SIZE];

if (argc != 3) {
printf("usage:\n\t %s <input iso> <output iso>\n", argv[0]);
return 1;
}

/* figure out how big the ISO image is */
if (stat(argv[1], &stat_buf) < 0) {
perror("failed to stat input iso");
return 1;
}

total_blocks = stat_buf.st_blocks / 4;
start_blocks.push_back(total_blocks);

/* find locations where have to rekey CSS */
if ((p_udf = udf_open(argv[1])) == NULL) {
fprintf(stderr, "couldn't open %s as UDF\n", argv[1]);
return 1;
}

if (!(p_udf_root = udf_get_root(p_udf, true, 0))) {
fprintf(stderr, "couldn't find / in %s\n", argv[1]);
return 1;
}

find_start_blocks(p_udf, p_udf_root);

sort(start_blocks.begin(), start_blocks.end());

/* prep CSS */
if (!(input = dvdcss_open(argv[1]))) {
fprintf(stderr, "dvdcss_open failed\n");
return 1;
}

/* prep output file */
if ((fd = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) < 0) {
printf("failed to open output file\n");
return 1;
}

/* if not scrambled skip! */
if (! dvdcss_is_scrambled(input)) {
printf("dvd isn't scrambled\n");
return 0;
}

return 0;

while (! start_blocks.empty()) {
int len = MAX;
int blocks_read;

int end = start_blocks[0];
start_blocks.erase(start_blocks.begin());

// don't dvdcss_seek first time around or last element (last block of dvd
if (pos && !start_blocks.empty()) {
if ( dvdcss_seek(input, pos, DVDCSS_SEEK_KEY) < 0) {
fprintf(stderr, "failed to seek to %d: %s\n", pos, dvdcss_error(input));
}
}

while (pos <= end) {
if (pos + len > end) {
len = end - pos;
}

if ((blocks_read = dvdcss_read(input, buffer, len, DVDCSS_READ_DECRYPT)) != len) {
fprintf(stderr, "didn't read %d blocks! read %d blocks\n", len, blocks_read);
}
pos += blocks_read;

if (write(fd, buffer, 2048 * blocks_read) != 2048 * blocks_read) {
fprintf(stderr, "write didn't write enough\n");
return 0;
}
}
}
}

spotter
18th October 2010, 19:16
so, here's some progress. added this function to libcdio's libudf, including an appropriate prototype to the exported headers


uint32_t udf_get_start_block(const udf_dirent_t *p_udf_dirent)
{
udf_t *p_udf = p_udf_dirent->p_udf;
uint32_t i_max_size;
lba_t i_start = offset_to_lba(p_udf_dirent, p_udf->i_position, &i_start,
&i_max_size);
return i_start;
}


code is then changed to what I include below, seems to work mostly, but returning an iso a bit difference size, need to investigate why. should be same size.


#include <stdio.h>
#include <unistd.h>
#include <dvdcss/dvdcss.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cdio/cdio.h>
#include <cdio/udf.h>
#include <vector>
#include <algorithm>
#include <string.h>
#include <ctype.h>

using namespace std;

#define _FILE_OFFSET_BITS 64
#define _LARGEFILE64_SOURCE 1
#define _LARGEFILE_SOURCE 1

vector<int> start_blocks;

#define MAX 1000

static void add_block(const udf_dirent_t *p_udf_dirent)
{
char *psz_fname=(char *) udf_get_filename(p_udf_dirent);
int len = strlen(psz_fname);

unsigned int start;

for(int i=0; i < len; i++) {
psz_fname[i] = tolower(psz_fname[i]);
}
if (! strcmp(psz_fname + (len-3), "vob")) {
start = udf_get_start_block(p_udf_dirent);
start_blocks.push_back(start);
printf("start block for %s = %u\n", psz_fname, start);
} else {
printf("didn't match on name %s\n", psz_fname);
}
}

void find_start_blocks(udf_t *p_udf, udf_dirent_t *p_udf_dirent)
{
if (!p_udf_dirent)
return;

while(udf_readdir(p_udf_dirent)) {
if (udf_is_dir(p_udf_dirent)) {
udf_dirent_t *p_udf_dirent2 = udf_opendir(p_udf_dirent);
if (p_udf_dirent) {
find_start_blocks(p_udf, p_udf_dirent2);
}
} else {
add_block(p_udf_dirent);
}
}
}

int main(int argc, char *argv[])
{
dvdcss_t input;
int fd;

struct stat stat_buf;
int total_blocks;
unsigned int pos = 0;

udf_t *p_udf;
udf_dirent_t *p_udf_root;

char buffer[MAX*DVDCSS_BLOCK_SIZE];

int loop = 1;

if (argc != 3) {
printf("usage:\n\t %s <input iso> <output iso>\n", argv[0]);
return 1;
}

/* figure out how big the ISO image is */
if (stat(argv[1], &stat_buf) < 0) {
perror("failed to stat input iso");
return 1;
}

total_blocks = stat_buf.st_blocks / 4;
// start_blocks.push_back(total_blocks);

/* find locations where have to rekey CSS */
if ((p_udf = udf_open(argv[1])) == NULL) {
fprintf(stderr, "couldn't open %s as UDF\n", argv[1]);
return 1;
}

if (!(p_udf_root = udf_get_root(p_udf, true, 0))) {
fprintf(stderr, "couldn't find / in %s\n", argv[1]);
return 1;
}

find_start_blocks(p_udf, p_udf_root);

sort(start_blocks.begin(), start_blocks.end());

/* for(vector<int>::iterator it = start_blocks.begin(); it != start_blocks.end(); it++) {
printf("end pos = %d\n", *it);
} */

/* prep CSS */
if (!(input = dvdcss_open(argv[1]))) {
fprintf(stderr, "dvdcss_open failed\n");
return 1;
}

/* prep output file */
if ((fd = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) < 0) {
printf("failed to open output file\n");
return 1;
}

/* if not scrambled skip! */
if (! dvdcss_is_scrambled(input)) {
printf("dvd isn't scrambled\n");
return 0;
}

while (! start_blocks.empty()) {
int len = MAX;
int blocks_read;

int end = start_blocks[0];
// start_blocks.pop_back();
start_blocks.erase(start_blocks.begin());

printf("current position = %u, next sync point at %u\n", pos, end);

// don't dvdcss_seek first time around or last element (last block of dvd
if (pos) {
printf("syncing at position %u\n", pos);
if ( dvdcss_seek(input, pos, DVDCSS_SEEK_KEY) < 0) {
fprintf(stderr, "failed to seek to %d: %s\n", pos, dvdcss_error(input));
}
} else {
printf("not seeking as at beginning, no sync points yet\n");
}

while (pos < end) {
printf("current pos = %u\n", pos);
if (pos + len > end) {
printf("going to a read shorter then %d as end is near\n", len);
len = end - pos;
}
printf("going to read %d\n", len);

if ((blocks_read = dvdcss_read(input, buffer, len, DVDCSS_READ_DECRYPT)) != len) {
if (start_blocks.empty()) {
loop = 0;
}
fprintf(stderr, "didn't read %d blocks! read %d blocks\n", len, blocks_read);
}
printf("read %d\n", blocks_read);
pos += blocks_read;

if (write(fd, buffer, 2048 * blocks_read) != 2048 * blocks_read) {
fprintf(stderr, "write didn't write enough\n");
return 0;
}
}
}
}

spotter
18th October 2010, 20:30
this version works on my test iso (mad men season 1 disc 1), it seems dvdcss_read() doesn't like being called before a dvdcss_seek() on a VOB file, so use plain read before we get to the first VOB.

on my machine takes about 5 minutes to read/decode/write a ~7GB iso w/ css scrambled sectors (or about 36 MB/s in total IO as to same disk)


#include <stdio.h>
#include <unistd.h>
#include <dvdcss/dvdcss.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cdio/cdio.h>
#include <cdio/udf.h>
#include <vector>
#include <algorithm>
#include <string.h>
#include <ctype.h>

using namespace std;

#define _FILE_OFFSET_BITS 64
#define _LARGEFILE64_SOURCE 1
#define _LARGEFILE_SOURCE 1

vector<int> start_blocks;

#define MAX 1000

static void add_block(const udf_dirent_t *p_udf_dirent)
{
char *psz_fname=(char *) udf_get_filename(p_udf_dirent);
int len = strlen(psz_fname);

unsigned int start;

for(int i=0; i < len; i++) {
psz_fname[i] = tolower(psz_fname[i]);
}
if (! strcmp(psz_fname + (len-3), "vob")) {
start = udf_get_start_block(p_udf_dirent);
start_blocks.push_back(start);
printf("start block for %s = %u\n", psz_fname, start);
} else {
printf("didn't match on name %s\n", psz_fname);
}
}

void find_start_blocks(udf_t *p_udf, udf_dirent_t *p_udf_dirent)
{
if (!p_udf_dirent)
return;

while(udf_readdir(p_udf_dirent)) {
if (udf_is_dir(p_udf_dirent)) {
udf_dirent_t *p_udf_dirent2 = udf_opendir(p_udf_dirent);
if (p_udf_dirent) {
find_start_blocks(p_udf, p_udf_dirent2);
}
} else {
add_block(p_udf_dirent);
}
}
}

int main(int argc, char *argv[])
{
dvdcss_t input;
int fd;
int fd_in;

struct stat stat_buf;
int total_blocks;
unsigned int pos = 0;

udf_t *p_udf;
udf_dirent_t *p_udf_root;

char * buffer;
unsigned int preamble;

if (!(buffer = (char *) malloc(MAX*DVDCSS_BLOCK_SIZE))) {
printf("failed to allocate space for buffer\n");
return 0;
}

if (argc != 3) {
printf("usage:\n\t %s <input iso> <output iso>\n", argv[0]);
return 1;
}

/* figure out how big the ISO image is */
if (stat(argv[1], &stat_buf) < 0) {
perror("failed to stat input iso");
return 1;
}

total_blocks = stat_buf.st_size / 2048;
if (stat_buf.st_size != (long long) total_blocks * 2048) {
printf("partial block?????\n");
return 1;
}
printf("total_blocks = %d\n", total_blocks);
start_blocks.push_back(total_blocks);

/* find locations where have to rekey CSS */
if ((p_udf = udf_open(argv[1])) == NULL) {
fprintf(stderr, "couldn't open %s as UDF\n", argv[1]);
return 1;
}

if (!(p_udf_root = udf_get_root(p_udf, true, 0))) {
fprintf(stderr, "couldn't find / in %s\n", argv[1]);
return 1;
}

find_start_blocks(p_udf, p_udf_root);

sort(start_blocks.begin(), start_blocks.end());

/* for(vector<int>::iterator it = start_blocks.begin(); it != start_blocks.end(); it++) {
printf("end pos = %d\n", *it);
} */

/* prep CSS */
if (!(input = dvdcss_open(argv[1]))) {
fprintf(stderr, "dvdcss_open failed\n");
return 1;
}

/* prep output file */
if ((fd = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) < 0) {
printf("failed to open output file\n");
return 1;
}

if ((fd_in = open(argv[1], O_RDONLY)) < 0) {
printf("failed to open output file\n");
return 1;
}

printf("fd_in = %d\n", fd_in);

/* if not scrambled skip! */
if (! dvdcss_is_scrambled(input)) {
printf("dvd isn't scrambled\n");
return 0;
}

preamble = start_blocks[0];
start_blocks.erase(start_blocks.begin());

if (preamble > MAX) {
printf("preamble is too big (%u)\n", preamble);
return 0;
}

pos = read(fd_in, buffer, preamble*2048);
if (pos != preamble * 2048) {
printf("didn't read enough (%u vs %d)\n", preamble*2048, pos);
return 0;
}

if (write(fd, buffer, pos) != pos) {
printf("didn't write enough\n");
return 0;
}

pos = pos / 2048;

while (! start_blocks.empty()) {
int len = MAX;
int blocks_read;

int end = start_blocks[0];
// start_blocks.pop_back();
start_blocks.erase(start_blocks.begin());

printf("current position = %u, next sync point at %u\n", pos, end);

// don't dvdcss_seek first time around or last element (last block of dvd
printf("syncing at position %u\n", pos);
if ( dvdcss_seek(input, pos, DVDCSS_SEEK_KEY) < 0) {
fprintf(stderr, "failed to seek to %d: %s\n", pos, dvdcss_error(input));
/* } else {
printf("not seeking as at beginning, no sync points yet\n"); */
}

while (pos < end) {
//printf("current pos = %u\n", pos);
if (pos + len > end) {
//printf("going to a read shorter then %d as end is near\n", len);
len = end - pos;
}
//printf("going to read %d\n", len);

if ((blocks_read = dvdcss_read(input, buffer, len, DVDCSS_READ_DECRYPT)) != len) {
fprintf(stderr, "didn't read %d blocks! read %d blocks\n", len, blocks_read);
}
//printf("read %d\n", blocks_read);
pos += blocks_read;

if (write(fd, buffer, 2048 * blocks_read) != 2048 * blocks_read) {
fprintf(stderr, "write didn't write enough\n");
return 0;
}
}
}
}

spotter
19th October 2010, 07:58
and here's a version that does all the writing in place (i.e. just overwrites the blocks that are scrambled by CSS after descrambling them).

caveat: it seems much slower on machine (though probably also because the reads/writes were from different file systems, albiet on same block device)

this version can also chew up and destroy your iso images, so comes with no warranty that it works, only tested on one iso image so far.


#define _FILE_OFFSET_BITS 64
#define _LARGEFILE64_SOURCE 1
#define _LARGEFILE_SOURCE 1

#include <stdio.h>
#include <unistd.h>
#include <dvdcss/dvdcss.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cdio/cdio.h>
#include <cdio/udf.h>
#include <vector>
#include <algorithm>
#include <string.h>
#include <ctype.h>

using namespace std;

vector<int> start_blocks;

#define MAX 2000

static void add_block(const udf_dirent_t *p_udf_dirent)
{
char *psz_fname=(char *) udf_get_filename(p_udf_dirent);
int len = strlen(psz_fname);

unsigned int start;

for(int i=0; i < len; i++) {
psz_fname[i] = tolower(psz_fname[i]);
}
if (! strcmp(psz_fname + (len-3), "vob")) {
start = udf_get_start_block(p_udf_dirent);
start_blocks.push_back(start);
printf("start block for %s = %u\n", psz_fname, start);
} else {
printf("didn't match on name %s\n", psz_fname);
}
}

void find_start_blocks(udf_t *p_udf, udf_dirent_t *p_udf_dirent)
{
if (!p_udf_dirent)
return;

while(udf_readdir(p_udf_dirent)) {
if (udf_is_dir(p_udf_dirent)) {
udf_dirent_t *p_udf_dirent2 = udf_opendir(p_udf_dirent);
if (p_udf_dirent) {
find_start_blocks(p_udf, p_udf_dirent2);
}
} else {
add_block(p_udf_dirent);
}
}
}

int main(int argc, char *argv[])
{
dvdcss_t input;
int fd;

struct stat stat_buf;
int total_blocks;
unsigned long long pos = 0;

udf_t *p_udf;
udf_dirent_t *p_udf_root;

char * buffer;
unsigned long long preamble;

if (!(buffer = (char *) malloc(MAX*DVDCSS_BLOCK_SIZE))) {
printf("failed to allocate space for buffer\n");
return 0;
}

if (argc != 2) {
printf("usage:\n\t %s <input iso>\n", argv[0]);
return 1;
}

/* figure out how big the ISO image is */
if (stat(argv[1], &stat_buf) < 0) {
perror("failed to stat input iso");
return 1;
}

total_blocks = stat_buf.st_size / DVDCSS_BLOCK_SIZE;
if (stat_buf.st_size != (long long) total_blocks * DVDCSS_BLOCK_SIZE) {
printf("partial block?????\n");
return 1;
}
printf("total_blocks = %d\n", total_blocks);
start_blocks.push_back(total_blocks);

/* find locations where have to rekey CSS */
if ((p_udf = udf_open(argv[1])) == NULL) {
fprintf(stderr, "couldn't open %s as UDF\n", argv[1]);
return 1;
}

if (!(p_udf_root = udf_get_root(p_udf, true, 0))) {
fprintf(stderr, "couldn't find / in %s\n", argv[1]);
return 1;
}

find_start_blocks(p_udf, p_udf_root);

sort(start_blocks.begin(), start_blocks.end());

/* for(vector<int>::iterator it = start_blocks.begin(); it != start_blocks.end(); it++) {
printf("end pos = %d\n", *it);
} */

/* prep CSS */
if (!(input = dvdcss_open(argv[1]))) {
fprintf(stderr, "dvdcss_open failed\n");
return 1;
}

if ((fd = open(argv[1], O_RDWR)) < 0) {
printf("failed to open input/output file\n");
return 1;
}

/* if not scrambled skip! */
if (! dvdcss_is_scrambled(input)) {
printf("dvd isn't scrambled\n");
return 0;
}

preamble = start_blocks[0];
start_blocks.erase(start_blocks.begin());

lseek64(fd, preamble*DVDCSS_BLOCK_SIZE, SEEK_SET);

pos = preamble;

while (! start_blocks.empty()) {
int len = MAX;
int blocks_read;

int end = start_blocks[0];
start_blocks.erase(start_blocks.begin());

printf("syncing at position = %llu, next sync point at %u\n", pos, end);

if ( dvdcss_seek(input, pos, DVDCSS_SEEK_KEY) < 0) {
fprintf(stderr, "failed to seek to %llu: %s\n", pos, dvdcss_error(input));
}

while (pos < end) {
int read_size;
char * tmp_buffer;
int reseek;

if (pos + len > end) {
len = end - pos;
}

read_size = len * DVDCSS_BLOCK_SIZE;

if ((blocks_read = read(fd, buffer, read_size)) != read_size) {
printf("short read, not handled yet\n");
return 1;
}

tmp_buffer = buffer;
reseek = 0;

for(int index = 0; index < len; index++) {
if( ((uint8_t*)tmp_buffer)[0x14] & 0x30 ) {
if (dvdcss_seek(input, pos+index, DVDCSS_NOFLAGS) < 0) {
fprintf(stderr, "failed to seek to %llu (index %d): %s\n", pos+index, index, dvdcss_error(input));
return 1;
}
if (dvdcss_read(input, tmp_buffer, 1, DVDCSS_READ_DECRYPT) != 1) {
fprintf(stderr, "dvdcss_read failed\n");
return 1;
}
lseek64(fd, (pos+index) * DVDCSS_BLOCK_SIZE, SEEK_SET);
if (write(fd, tmp_buffer, DVDCSS_BLOCK_SIZE) != DVDCSS_BLOCK_SIZE)
{
printf("failed to write block correctly\n");
return 1;
}
reseek = 1;
}
tmp_buffer = tmp_buffer + DVDCSS_BLOCK_SIZE;
}

pos += len;
if (reseek) {
lseek64(fd, pos * DVDCSS_BLOCK_SIZE, SEEK_SET);
}
}
}
}

spotter
19th October 2010, 18:04
so this seems to work, gives a mostly identical copy to what dvd decrypter outputs.

it seems to differ in non VOB files though. My guess is that it dvd decrypter is making the disk region free and removing rce protection. As that's not the goal of this program, it doesn't do that. Though when I get home later today I will try running my decrypted iso through dvd decrypter and see if it offers to remove the region/rce protection as well and results in a the same disk image as when it works on the encrypted version.

SledgeHammer_999
19th October 2010, 20:48
Shouldn't you implement it using libdvdnav/read(can't remember which) instead? As far as I know, libdvdcss can't handle protection schemes that purposedly corrupt the files/filesystem, like Arcos, directly. If I understand correctly dvdnav/read reads the IFO file and skips the corrupted sectors according to it.

This has the potential to be an excellent open-source and free Dvd decrypter...
I will be following this.

JohnAStebbins
19th October 2010, 21:44
Should you implement it using libdvdnav/read(can't remember which) instead? As far as I know, libdvdcss can't handle protection schemes that purposedly corrupt the files/filesystem, like Arcos, directly. If I understand correctly dvdnav/read reads the IFO file and skips the corrupted sectors according to it.

This has the potential to be an excellent open-source and free Dvd decrypter...
I will be following this.
I've thought about doing this, but libdvdnav doesn't have a way to query where a block came from after reading it. So once you have the data, there's no way to know where you need to write it to the image you are creating. You would have to add an api to libdvdnav to retrieve this information.

Then there's the problem of deciding when you have all the necessary data. Reading it in the order libdvdnav gives it means you are skipping over things. Some of those things are valid data that can be accessed if you start with different initial conditions. You have to essentially rip a title multiple times with all variations of initial conditions (angles, multiple entry points, etc.). To make that efficient, you would have to keep a bitmap of blocks already visited and a cache of all nav commands for each block so you don't have to re-read everything multiple times.

That's about as far as I got in thinking about it before deciding to move on to something more interesting :D

spotter
20th October 2010, 05:53
Shouldn't you implement it using libdvdnav/read(can't remember which) instead? As far as I know, libdvdcss can't handle protection schemes that purposedly corrupt the files/filesystem, like Arcos, directly. If I understand correctly dvdnav/read reads the IFO file and skips the corrupted sectors according to it.

This has the potential to be an excellent open-source and free Dvd decrypter...
I will be following this.

in this program, all I'm doing is

1) rekey the css key at the start of each VOB.

2) whenever there's a block that is scrambled with CSS, descramble it and right it back out.

the idea here is that between lsdvd/ddrescue we don't care if the disk has corrupted sectors, ddrescue will just skip over those) and then my program will descramble the css scrambled sectors.

right now, an issue I have is how to deal with RCE type protection. It's not the biggest deal in the world (VLC handles these decrypted images just fine when mounted), but would be nice. my gut feeling is that it would be a second program that knows how to parse the IFO structure and "zero out" the commands that make up RCE, but as I have no clue about RCE at this point in time, unsure how to proceed in regards to it.

spotter
20th October 2010, 06:07
though on this subject, in doing my hexdiff's of my copy against a copy produced by dvd decrypter, I noticed some value that was fe being changed to 00

according to this

http://en.wikipedia.org/wiki/DVD_region_code#Circumvention

that makes perfect sense. wondering if i'd edit them what that would do to RCE. My initial hope was that if I don't try to play with the regionness of the DVD, RCE type protections would just work fine and ignore it as the image is still region coded. That seemed to fail.

spotter
20th October 2010, 06:27
just hexedited the region code, didn't help (somewhat as expected).

spotter
20th October 2010, 06:32
wondering if my code is buggy. acc to dvdcss_read() it should be safe to run on IFO files and the like, but I'm wondering if they lied. going to change my program to make a record of every start/end block and only do css descrambling on the VOB files.

spotter
20th October 2010, 18:52
here's version 5, which is mostly a test (doesn't do any write out) to figure out which non vob blocks have the css bit set, just coded up while on a conference call at work. will test it when I get home

#define _FILE_OFFSET_BITS 64
#define _LARGEFILE64_SOURCE 1
#define _LARGEFILE_SOURCE 1

#include <stdio.h>
#include <unistd.h>
#include <dvdcss/dvdcss.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cdio/cdio.h>
#include <cdio/udf.h>
#include <vector>
#include <map>
#include <algorithm>
#include <string.h>
#include <ctype.h>

using namespace std;

#define MAX 2000
#define CEILING(x, y) ((x+(y-1))/y) //ripped from libudf

vector<unsigned long long> start_blocks;
map<unsigned long long, unsigned long long> end_blocks;

static void add_block(const udf_dirent_t *p_udf_dirent)
{
char *psz_fname=(char *) udf_get_filename(p_udf_dirent);
int len = strlen(psz_fname);
unsigned long long file_length;

unsigned long long start;

start = udf_get_start_block(p_udf_dirent);
//-1 as start block is included in count of blocks
end_blocks[start] = start - 1 + CEILING(udf_get_file_length(p_udf_dirent), DVDCSS_BLOCK_SIZE);
printf("%s: %llu->%llu\n", psz_fname, start, end_blocks[start]);

for(int i=0; i < len; i++) {
psz_fname[i] = tolower(psz_fname[i]);
}

if (! strcmp(psz_fname + (len-3), "vob")) {
start_blocks.push_back(start);
}
}

void find_start_blocks(udf_t *p_udf, udf_dirent_t *p_udf_dirent)
{
if (!p_udf_dirent)
return;

while(udf_readdir(p_udf_dirent)) {
if (udf_is_dir(p_udf_dirent)) {
udf_dirent_t *p_udf_dirent2 = udf_opendir(p_udf_dirent);
if (p_udf_dirent) {
find_start_blocks(p_udf, p_udf_dirent2);
}
} else {
add_block(p_udf_dirent);
}
}
}

int main(int argc, char *argv[])
{
dvdcss_t input;
int fd;

struct stat stat_buf;
int total_blocks;
unsigned long long pos = 0;

udf_t *p_udf;
udf_dirent_t *p_udf_root;

char * buffer;
unsigned long long preamble;

unsigned long long count = 0;

if (!(buffer = (char *) malloc(MAX*DVDCSS_BLOCK_SIZE))) {
printf("failed to allocate space for buffer\n");
return 0;
}

if (argc != 2) {
printf("usage:\n\t %s <input iso>\n", argv[0]);
return 1;
}

/* figure out how big the ISO image is */
if (stat(argv[1], &stat_buf) < 0) {
perror("failed to stat input iso");
return 1;
}

total_blocks = stat_buf.st_size / DVDCSS_BLOCK_SIZE;
if (stat_buf.st_size != (long long) total_blocks * DVDCSS_BLOCK_SIZE) {
printf("partial block?????\n");
return 1;
}
printf("total_blocks = %d\n", total_blocks);
start_blocks.push_back(total_blocks);

/* find locations where have to rekey CSS */
if ((p_udf = udf_open(argv[1])) == NULL) {
fprintf(stderr, "couldn't open %s as UDF\n", argv[1]);
return 1;
}

if (!(p_udf_root = udf_get_root(p_udf, true, 0))) {
fprintf(stderr, "couldn't find / in %s\n", argv[1]);
return 1;
}

find_start_blocks(p_udf, p_udf_root);

sort(start_blocks.begin(), start_blocks.end());

/* prep CSS */
if (!(input = dvdcss_open(argv[1]))) {
fprintf(stderr, "dvdcss_open failed\n");
return 1;
}

// if ((fd = open(argv[1], O_RDWR)) < 0) {
if ((fd = open(argv[1], O_RDONLY)) < 0) {
printf("failed to open input/output file\n");
return 1;
}

/* if not scrambled skip! */
if (! dvdcss_is_scrambled(input)) {
printf("dvd isn't scrambled\n");
return 0;
}

preamble = start_blocks[0];
start_blocks.erase(start_blocks.begin());

lseek64(fd, preamble*DVDCSS_BLOCK_SIZE, SEEK_SET);

pos = preamble;

while (! start_blocks.empty()) {
int len = MAX;
int blocks_read;

int cur = pos;
int end = start_blocks[0];
start_blocks.erase(start_blocks.begin());

//printf("syncing at position = %llu, next sync point at %u\n", pos, end);

if ( dvdcss_seek(input, pos, DVDCSS_SEEK_KEY) < 0) {
fprintf(stderr, "failed to seek to %llu: %s\n", pos, dvdcss_error(input));
}

while (pos < end) {
int read_size;
char * tmp_buffer;
int reseek;

if (pos + len > end) {
len = end - pos;
}

read_size = len * DVDCSS_BLOCK_SIZE;

if ((blocks_read = read(fd, buffer, read_size)) != read_size) {
printf("short read, not handled yet\n");
return 1;
}

tmp_buffer = buffer;
reseek = 0;

for(int index = 0; index < len; index++) {
char block[2048];

bcopy(tmp_buffer, block, 2048);

if( ((uint8_t*)tmp_buffer)[0x14] & 0x30 ) {
int skip=0;
if (pos + index > end_blocks[cur]) {
printf("should skipping decode of supposed encrypted block (%llu) as not within VOB\n", pos+index);
skip=1;
//continue;
}
count++;
if (dvdcss_seek(input, pos+index, DVDCSS_NOFLAGS) < 0) {
fprintf(stderr, "failed to seek to %llu (index %d): %s\n", pos+index, index, dvdcss_error(input));
return 1;
}
if (dvdcss_read(input, tmp_buffer, 1, DVDCSS_READ_DECRYPT) != 1) {
fprintf(stderr, "dvdcss_read failed\n");
return 1;
}
if (skip) {
printf("testing block we would skip\n");
if( ((uint8_t*)tmp_buffer)[0x14] & 0x30 ) {
printf("dvdcss \"decoded\" block still has bit set\n");
if (!memcmp(tmp_buffer, block, 2048)) {
printf("dvdcss \"decoded\" block didn't change!\n");
} else {
printf("dvdcss \"decoded\" block did change!\n");
}
} else {
printf("dvdcss \"decoded\" block got bit removed!\n");
}
}
/* lseek64(fd, (pos+index) * DVDCSS_BLOCK_SIZE, SEEK_SET);
if (write(fd, tmp_buffer, DVDCSS_BLOCK_SIZE) != DVDCSS_BLOCK_SIZE)
{
printf("failed to write block correctly\n");
return 1;
} */
reseek = 1;
}
tmp_buffer = tmp_buffer + DVDCSS_BLOCK_SIZE;
}

pos += len;
if (reseek) {
lseek64(fd, pos * DVDCSS_BLOCK_SIZE, SEEK_SET);
}
}
}
printf("descrambled %llu blocks\n", count);
printf("\n");

return 0;
}

spotter
21st October 2010, 00:45
ok, so I was wrong above, the decryption worked perfectly. I just had it mounted in windows and Daemon Tools was set to be region free so RCE kicked it (as it should).

set it to region 1 and everything was great.

spotter
21st October 2010, 05:32
here's my current version for now, seems to work pretty well.

what did I learn here? it seems that Gilmore Girls Season 1 Disk 2 has VTS_#_0.VOB files of 0 length (and hence only 1 block) in the middle of an area that would be contiguous with another VOB.

#define _FILE_OFFSET_BITS 64
#define _LARGEFILE64_SOURCE 1
#define _LARGEFILE_SOURCE 1

#include <stdio.h>
#include <unistd.h>
#include <dvdcss/dvdcss.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cdio/cdio.h>
#include <cdio/udf.h>
#include <vector>
#include <map>
#include <algorithm>
#include <string.h>
#include <ctype.h>
#include <errno.h>

using namespace std;

#define MAX 2000
#define CEILING(x, y) ((x+(y-1))/y) //ripped from libudf

vector<unsigned long long> start_blocks;
map<unsigned long long, unsigned long long> end_blocks;
map<unsigned long long, char *> start_map;

ssize_t my_write(int fd, const void * buf, size_t count)
{
int ret;
size_t my_count = count;
char * my_buf = (char *) buf;

while (my_count) {
if (my_count != count) {
printf("looped in my_write, last write = %d\n", ret);
}
if ((ret = write(fd, my_buf, my_count)) == -1) {
if (errno != EINTR) {
perror("write failed!");
goto out;
}
} else {
my_count -= ret;
my_buf += ret;
}
}

ret = count;
out:
return ret;
}

static void add_block(const udf_dirent_t *p_udf_dirent)
{
char *psz_fname=(char *) udf_get_filename(p_udf_dirent);
char *name = strdup(psz_fname);
int len = strlen(psz_fname);
unsigned long long file_length;
unsigned long long start;
unsigned long long blocks;

start = udf_get_start_block(p_udf_dirent);
blocks = CEILING(udf_get_file_length(p_udf_dirent), DVDCSS_BLOCK_SIZE);

start_map[start] = name;

if (blocks == 0) {
//file length of 0 would result in a blocks of 0, and don't want
//to subtract one from it.
end_blocks[start] = start;
} else {
//-1 as start block is included in count of blocks
end_blocks[start] = start - 1 + CEILING(udf_get_file_length(p_udf_dirent), DVDCSS_BLOCK_SIZE);
}
printf("%s: %llu->%llu\n", psz_fname, start, end_blocks[start]);

for(int i=0; i < len; i++) {
psz_fname[i] = tolower(psz_fname[i]);
}

if (! strcmp(psz_fname + (len-3), "vob")) {
if (blocks) {
// if (find(start_blocks.begin(), start_blocks.end(), start) == start_blocks.end()) {
start_blocks.push_back(start);
}
}
}

void find_start_blocks(udf_t *p_udf, udf_dirent_t *p_udf_dirent)
{
if (!p_udf_dirent)
return;

while(udf_readdir(p_udf_dirent)) {
if (udf_is_dir(p_udf_dirent)) {
udf_dirent_t *p_udf_dirent2 = udf_opendir(p_udf_dirent);
if (p_udf_dirent) {
find_start_blocks(p_udf, p_udf_dirent2);
}
} else {
add_block(p_udf_dirent);
}
}
}

int main(int argc, char *argv[])
{
dvdcss_t input;
int fd;

off64_t disc_len;
int total_blocks;
unsigned long long pos = 0;

udf_t *p_udf;
udf_dirent_t *p_udf_root;

char * buffer;
unsigned long long preamble;

unsigned long long count = 0;

if (!(buffer = (char *) malloc(MAX*DVDCSS_BLOCK_SIZE))) {
printf("failed to allocate space for buffer\n");
return 0;
}

if (argc < 2) {
printf("usage:\n\t %s <input iso>\n", argv[0]);
return 1;
}

if ((fd = open(argv[1], O_RDWR)) < 0) {
printf("failed to open input/output file\n");
return 1;
}

/* figure out how big the ISO image is */
if ((disc_len = lseek64(fd, 0, SEEK_END)) < 0) {
perror("lseek64 failed");
return 1;
}

total_blocks = disc_len / DVDCSS_BLOCK_SIZE;
if (disc_len != (long long) total_blocks * DVDCSS_BLOCK_SIZE) {
printf("partial block?????\n");
return 1;
}
printf("total_blocks = %d\n", total_blocks);
start_blocks.push_back(total_blocks);

/* find locations where have to rekey CSS */
if ((p_udf = udf_open(argv[1])) == NULL) {
fprintf(stderr, "couldn't open %s as UDF\n", argv[1]);
return 1;
}

if (!(p_udf_root = udf_get_root(p_udf, true, 0))) {
fprintf(stderr, "couldn't find / in %s\n", argv[1]);
return 1;
}

find_start_blocks(p_udf, p_udf_root);

sort(start_blocks.begin(), start_blocks.end());

for(vector<unsigned long long>::iterator it = start_blocks.begin(); it != start_blocks.end(); it++) {
printf("end pos = %llu\n", *it);
}

for(map<unsigned long long, char *>::iterator p = start_map.begin(); p != start_map.end(); p++) {
printf("%s : %llu\n", p->second, p->first);
}

if (argc == 3 && !strcmp(argv[2], "-test")) {
return 0;
}

/* prep CSS */
if (!(input = dvdcss_open(argv[1]))) {
fprintf(stderr, "dvdcss_open failed\n");
return 1;
}

/* if not scrambled skip! */
/* this doesn't do anything on iso input */
if (! dvdcss_is_scrambled(input)) {
printf("dvd isn't scrambled\n");
return 0;
}

preamble = start_blocks[0];
start_blocks.erase(start_blocks.begin());

lseek64(fd, preamble*DVDCSS_BLOCK_SIZE, SEEK_SET);

pos = preamble;

while (! start_blocks.empty()) {
int len = MAX;
int blocks_read;

int cur = pos;
int end = start_blocks[0];
start_blocks.erase(start_blocks.begin());

//printf("syncing at position = %llu, next sync point at %u\n", pos, end);

if ( dvdcss_seek(input, pos, DVDCSS_SEEK_KEY) < 0) {
fprintf(stderr, "failed to seek to %llu: %s\n", pos, dvdcss_error(input));
}

while (pos < end) {
int read_size;
char * tmp_buffer;
int reseek;

if (pos + len > end) {
len = end - pos;
}

read_size = len * DVDCSS_BLOCK_SIZE;

if ((blocks_read = read(fd, buffer, read_size)) != read_size) {
printf("short read, not handled yet\n");
return 1;
}

tmp_buffer = buffer;
reseek = 0;

for(int index = 0; index < len; index++) {
char block[2048];

if( ((uint8_t*)tmp_buffer)[0x14] & 0x30 ) {
int skip=0;
if (pos + index > end_blocks[cur]) {
printf("should skipping decode of supposed encrypted block (%llu) as not within VOB\n", pos+index);
bcopy(tmp_buffer, block, 2048);
skip=1;
}
count++;
if (dvdcss_seek(input, pos+index, DVDCSS_NOFLAGS) < 0) {
fprintf(stderr, "failed to seek to %llu (index %d): %s\n", pos+index, index, dvdcss_error(input));
return 1;
}
if (dvdcss_read(input, tmp_buffer, 1, DVDCSS_READ_DECRYPT) != 1) {
fprintf(stderr, "dvdcss_read failed\n");
return 1;
}
if (skip) {
printf("testing block we are skipping anyways\n");
if( ((uint8_t*)tmp_buffer)[0x14] & 0x30 ) {
printf("dvdcss \"decoded\" block still has bit set\n");
if (!memcmp(tmp_buffer, block, 2048)) {
printf("dvdcss \"decoded\" block didn't change!\n");
} else {
printf("dvdcss \"decoded\" block changed!\n");
}
} else {
printf("dvdcss \"decoded\" block got bit removed!\n");
}
} else {
lseek64(fd, (pos+index) * DVDCSS_BLOCK_SIZE, SEEK_SET);
if (my_write(fd, tmp_buffer, DVDCSS_BLOCK_SIZE) < 0) {
return 1;
}
reseek = 1;
}
}
tmp_buffer = tmp_buffer + DVDCSS_BLOCK_SIZE;
}

pos += len;
if (reseek) {
lseek64(fd, pos * DVDCSS_BLOCK_SIZE, SEEK_SET);
}
}
}
printf("descrambled %llu blocks\n", count);
printf("\n");

return 0;
}

sl1pkn07
21st October 2010, 21:58
run on linux?

spotter
21st October 2010, 22:24
that's the point.

spotter
22nd October 2010, 03:33
so I just tried my methodology on a DVD from castle season 2 (2nd to be precise). This set seems to be protected by a number of schemes, bad sectors, crazy IFOs, crazy VOBs (i.e. that reference same blocks on the disc). My methodology of

1) authenticating the drive (lsdvd. though in this case lsdvd fails too as can't parse the IFOs, so resorted to vlc)
2) ripping the disc with ddrescue (takes a while, as slow to iterate through the bad sectors)
3) decoding the vob with my tool

seems to result in a DVD that is fully playable.

setarip_old
22nd October 2010, 04:36
@spotter

Hi! seems to result in a DVD that is fully playable.

I'm just a curious bystander on this one. Does your project yield a full DVD "package", including menus, or is it "movie only"?

spotter
22nd October 2010, 04:54
@spotter

Hi!

I'm just a curious bystander on this one. Does your project yield a full DVD "package", including menus, or is it "movie only"?



all it does is make an ISO image that is exactly the same as the initial one but without any encryption. so all their protection methods are still in place (RCE...), except doesn't make a difference for any player that can actually play a DVD.

This issue was because if I make an ISO image with the CSS scrambled sectors still embeded within it, I can't mount it with programs like daemon tools and have it play as many programs see that they can't authenticate the drive/disc so skip doing the deCSS. With this, no need, as the CSS is removed.

setarip_old
22nd October 2010, 06:46
the idea here is that between lsdvd/ddrescue we don't care if the disk has corrupted sectors, ddrescue will just skip over those) and then my program will descramble the css scrambled sectors.Although I'd be delighted to hear of your complete success, as I've suggested to other Linux users at Doom9, who previously attempted the same thing you are pursuing (FULL DISC DVD backup), if the only things involved in the world of DVD protection were CSS and bad sectoring, there'd be no need for the ongoing updates and modifications to several commercial DVD "ripping" programs.

To get a better sense of what I'm referring to, you might visit the "DVDFab" and "Slysoft"/AnyDVD sites and forums to see which DVDs required
the authors to modify their programs to deal with protection schemes that go well beyond simple CSS and simple bad sectoring.

By all means keep us apprised of your progress ;>}

spotter
22nd October 2010, 07:02
if you can point me to DVDs (region 1) that have been known to be problematic I can try taking them out of the library and seeing what happens. I'm not trying to play any games with IFOs (not even playing with region coding), so unsure why my method wouldn't work. (now, if you'd extract it to files, then you might have a problem, especially with it blowing up way beyond what can fit on a DVD due to all these shared blocks).

I can tell you that I see people complain about Castle, and I can tell you my program handled it without a problem. Now, I also know very little about any of the specs that make up DVDs (UDF, through dvd), so this is mostly a hack, but the main idea is sound I think. 1) grab a copy w/ css scrambled sectors 2) descramble those sectors.

there's nothing any DVD mastering process can do to stop step 1 as long as they can be played on a computer. they can make it more painful by having invalid sectors, but ddrescue just skips over them. now life is more difficult if your DVD is scratched and has valid sectors that are unreadable, how do you know if you got it all or not, but that's not a problem i'm dealing with at the moment.

step 2 is the hackish part, as I'm making 2 assumptions

a) can always rekey the css key by calling dvdcss_seek() to the first block of a vob file (and in fact, this seems to be how libdvdread works, so if the mastering process could change this, it might break all linux players)
b) that vob files are contiguous on the medium. namely if the file start at block X and is Y blocks long, I can decrypt any scrambled sectors from X through X+Y-1 with the key I got.

if either of these assumptions fail, my program will fail. But I haven't see anything that indicates they do fail.

spotter
22nd October 2010, 07:15
so put iron man 2 on hold at the library, hopefully get it early next week. that's a DVD that seemed to cause problems. let's see if my naive approach works on it as well.

setarip_old
22nd October 2010, 08:25
You might want to try some of these (culled from the websites I had suggested you check out):

"Dora the Explorer: Super Silly Fiesta", Region 2 and 4 (UK & Australia)
"New Moon", R1, US
"Precious", R1, US
"Angel & Demons (rental)", Japan
"Insanity Workout"
and "One on One with Tony Horton" (US)
"Haunting in Connecticut", R1, US
"Transporter 3", R2, Germany
"Underbelly -
a tale of two cities", R4, Australia
"Baader Meinhof Komplex", R2, Germany
"The Closet", UK
"Prince of Persia The Sands of Time"
"Region 1 DVD of Leap Year"
"Leap Year" (US)
"Alice in Wonderland"
"Iron Man 2" (US)
"Shutter Island"

spotter
22nd October 2010, 13:41
well, won't be able to get any non region 1 disc, but iron man 2 shood come soon, will also put leap year, shutter island, prince of persia...on hold. (in some ways better than iron man 2, as more willing to sit trough a complete watching, as never seen).

But as I said, I have no idea how they could impact my methodology beyond making it take longer, as I never even look in any files expecting a certain structure (i.e. IFO).

beandog
22nd October 2010, 17:31
Although I'd be delighted to hear of your complete success, as I've suggested to other Linux users at Doom9, who previously attempted the same thing you are pursuing (FULL DISC DVD backup), if the only things involved in the world of DVD protection were CSS and bad sectoring, there'd be no need for the ongoing updates and modifications to several commercial DVD "ripping" programs.

Am I missing something? Backing up a full DVD isn't hard. vobcopy comes to mind.

spotter
22nd October 2010, 18:06
that could possibly work, but notice this from castle season 2


root@nas:/# df -h /mnt
Filesystem Size Used Avail Use% Mounted on
/dev/loop0 7.2G 7.2G 0 100% /mnt


vs


root@nas:/# du -hsc /mnt
21G /mnt
21G total


i.e. my version doesn't care that the file system appears to have 21GB of data on it, I only operate on 7+GB that are actually in the image.

vobcopy will just copy everything, it has no idea of DVD security. I have no idea of DVD security either, but as operate only on the blocks (have very little concept of the file system), I'm left with the same size as the original (in fact, just overwrite the css scrambled blocks w/ the decss unscrambled versions)

spotter
22nd October 2010, 20:17
so if I were a movie studio, how would I try to attack my program? I'd try to create invalid VOBs that cover areas that include valid IFOs and construct the IFO in such a way that if one tries to descramble it with CSS it gets corrupted.

don't know if this is possible, but seems a good way to mess things up.

nevragain
23rd October 2010, 03:43
Aside from the method you describe bad sectors (that the IFO instruct the player to skip over) there are two more I have heard described.
Using an invalid disc label (characters not permitted).

The use of a corrupted file system.

I believe that the corruption related to UDF based on the theory that hardware dvd players would see only the ISO file system.

This corruption may have involved fake files too large to fit on the disc or the use of fake files over the limit permitted in UDF.

This Netflix list also included some dvd with extra copy protection.
http://www.netflix.com/FAQ?p_faqid=2462

spotter
24th October 2010, 00:29
yes, but corrupted file system doesn't matter to me, as I don't really need to look at the file system beyond to see start of VOBs. With that said, I have the ability to only view the ISO file system as well. With that said, on castle DVD, just mounted it in linux as both iso9660 and udf and it sees same messed upness.

and in worse case, all I'd have to do to get around the way I say the studios could attack this is to have the ability to create the file system manually.

i.e. in the worse case where I don't know which vob blocks are good and which ifo blocks are good. all I have to do is is recreate a file system with the same overlapped corruption (how I'd do this, don't know, but not that difficult I'd imagine)

1) read all IFO related blocks - i.e. assume they are all valid
2) read all VOB related blocks - i.e. also assume they are all valid

3) create corrupted file system with VOB files sharing the blocks as appropriate
4) add all the IFO related blocks and the files to the file system (assumption being that these are minimal in size and hence even if share blocks, doesn't matter much)

this approach really discounts the file system and just looks at the disc as a raw block device.

spotter
24th October 2010, 00:55
basically, with my experience now, making a full image copy of any DVD should be easy for software like dvdfab/anydvd and should need no changes over time. only thing that they should have to adapt to is if one 1) wants a file system rip (i.e. not an image) or 2) wants a specific title from the dvd. could be wrong, but playing castle which has numerous protections without a problem using my method.

though, in regards to title, I think it should also be fairly straightforward. namely, just present the DVD menu to the user, and play through it till you get to the title the user wants and then just record those vm registers and play it through the same and record all the correct blocks and create the correct IFO for it. (doubt I'd ever do this, at last without help, as not what I'm aiming at).

marshalleq
24th October 2010, 21:54
I think this all sounds very compelling. Presumably once you'd made a descrambled ISO, you could run some DVD fab over it anyway?

Expand it to support blu ray then we'll all be very happy ;)

But please, keep up the great work!

spotter
24th October 2010, 22:22
1) yes dvdfab works over the decrypted DVD if you want a file system rip tested

2) re blueray, I doubt file system structural protections are what they are dealing with, isn't it mostly bd+ these days \

3) running into crashes when running against udf file system on some DVDs (30 Rock and Community to be percise), I easily get the same information with the iso9660 file system so going to try with it. so there will probably be a v6 soon.

spotter
24th October 2010, 23:03
version 6, works based on iso9660 file system (also doesn't require any patches to libcdio)


#define _FILE_OFFSET_BITS 64
#define _LARGEFILE64_SOURCE 1
#define _LARGEFILE_SOURCE 1

#include <stdio.h>
#include <unistd.h>
#include <dvdcss/dvdcss.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cdio/cdio.h>
#include <cdio/iso9660.h>
#include <vector>
#include <map>
#include <algorithm>
#include <string.h>
#include <ctype.h>
#include <errno.h>

using namespace std;

#define MAX 2000
#define CEILING(x, y) ((x+(y-1))/y) //ripped from libudf

vector<unsigned long long> start_blocks;
map<unsigned long long, unsigned long long> end_blocks;
map<unsigned long long, char *> start_map;

ssize_t my_write(int fd, const void * buf, size_t count)
{
int ret;
size_t my_count = count;
char * my_buf = (char *) buf;

while (my_count) {
if (my_count != count) {
printf("looped in my_write, last write = %d\n", ret);
}
if ((ret = write(fd, my_buf, my_count)) == -1) {
if (errno != EINTR) {
perror("write failed!");
goto out;
}
} else {
my_count -= ret;
my_buf += ret;
}
}

ret = count;
out:
return ret;
}

void find_start_blocks(iso9660_t *p_iso)
{
CdioList_t *p_entlist;
CdioListNode_t *p_entnode;

if (!p_iso)
return;

p_entlist = iso9660_ifs_readdir (p_iso, "/video_ts/");

if (p_entlist) {
_CDIO_LIST_FOREACH (p_entnode, p_entlist)
{
unsigned long long start;
unsigned long long blocks;

char filename[4096];
int len;

iso9660_stat_t *p_statbuf =
(iso9660_stat_t *) _cdio_list_node_data (p_entnode);
iso9660_name_translate(p_statbuf->filename, filename);
len = strlen(filename);

if ( ! (2 == p_statbuf->type) ) {
if (! strcmp(filename + (len-3), "vob")) {
start = p_statbuf->lsn;
blocks = CEILING(p_statbuf->size, DVDCSS_BLOCK_SIZE);

start_map[start] = strdup(filename);

if (blocks == 0) {
//file length of 0 would result in a blocks of 0, and don't want
//to subtract one from it.
end_blocks[start] = start;
} else {
//-1 as start block is included in count of blocks
end_blocks[start] = start - 1 + blocks;
}
printf("%s: %llu->%llu (%llu blocks)\n", filename, start, end_blocks[start], blocks);
if (blocks) {
if (find(start_blocks.begin(), start_blocks.end(), start) == start_blocks.end()) {
start_blocks.push_back(start);
}
}
}
}
}

_cdio_list_free (p_entlist, true);
}
}

int main(int argc, char *argv[])
{
dvdcss_t input;
int fd;
int flags = O_RDWR;

off64_t disc_len;
int total_blocks;
unsigned long long pos = 0;

iso9660_t * p_iso;

char * buffer;
unsigned long long preamble;

unsigned long long count = 0;

if (argc == 3 && !strcmp(argv[2], "-test")) {
flags = O_RDONLY;
}

if (!(buffer = (char *) malloc(MAX*DVDCSS_BLOCK_SIZE))) {
printf("failed to allocate space for buffer\n");
return 0;
}

if (argc < 2) {
printf("usage:\n\t %s <input iso>\n", argv[0]);
return 1;
}

if ((fd = open(argv[1], flags)) < 0) {
printf("failed to open input/output file\n");
return 1;
}

/* figure out how big the ISO image is */
if ((disc_len = lseek64(fd, 0, SEEK_END)) < 0) {
perror("lseek64 failed");
return 1;
}

total_blocks = disc_len / DVDCSS_BLOCK_SIZE;
if (disc_len != (long long) total_blocks * DVDCSS_BLOCK_SIZE) {
printf("partial block?????\n");
return 1;
}
printf("total_blocks = %d\n", total_blocks);
start_blocks.push_back(total_blocks);

/* find locations where have to rekey CSS */
if ((p_iso = iso9660_open(argv[1])) == NULL) {
fprintf(stderr, "couldn't open %s as UDF\n", argv[1]);
return 1;
}

find_start_blocks(p_iso);

sort(start_blocks.begin(), start_blocks.end());

for(vector<unsigned long long>::iterator it = start_blocks.begin(); it != start_blocks.end(); it++) {
printf("end pos = %llu\n", *it);
}

for(map<unsigned long long, char *>::iterator p = start_map.begin(); p != start_map.end(); p++) {
printf("%s : %llu\n", p->second, p->first);
}

if (argc == 3 && !strcmp(argv[2], "-test")) {
return 0;
}

/* prep CSS */
if (!(input = dvdcss_open(argv[1]))) {
fprintf(stderr, "dvdcss_open failed\n");
return 1;
}

/* if not scrambled skip! */
/* this doesn't do anything on iso input */
if (! dvdcss_is_scrambled(input)) {
printf("dvd isn't scrambled\n");
return 0;
}

preamble = start_blocks[0];
start_blocks.erase(start_blocks.begin());

lseek64(fd, preamble*DVDCSS_BLOCK_SIZE, SEEK_SET);

pos = preamble;

while (! start_blocks.empty()) {
int len = MAX;
int blocks_read;

int cur = pos;
int end = start_blocks[0];

int seeked = DVDCSS_NOFLAGS;

start_blocks.erase(start_blocks.begin());

//printf("syncing at position = %llu, next sync point at %u\n", pos, end);

if ( dvdcss_seek(input, pos, DVDCSS_SEEK_KEY) < 0) {
fprintf(stderr, "failed to seek to %llu: %s\n", pos, dvdcss_error(input));
seeked = DVDCSS_SEEK_KEY;
}

while (pos < end) {
int read_size;
char * tmp_buffer;
int reseek;

if (pos + len > end) {
len = end - pos;
}

read_size = len * DVDCSS_BLOCK_SIZE;

if ((blocks_read = read(fd, buffer, read_size)) != read_size) {
printf("short read, not handled yet\n");
return 1;
}

tmp_buffer = buffer;
reseek = 0;

for(int index = 0; index < len; index++) {
char block[2048];

if( ((uint8_t*)tmp_buffer)[0x14] & 0x30 ) {
int skip=0;
if (pos + index > end_blocks[cur]) {
printf("should skipping decode of supposed encrypted block (%llu) as not within VOB\n", pos+index);
bcopy(tmp_buffer, block, 2048);
skip=1;
}
count++;
if (dvdcss_seek(input, pos+index, seeked) < 0) {
fprintf(stderr, "failed to seek to %llu (index %d): %s\n", pos+index, index, dvdcss_error(input));
return 1;
}
seeked = DVDCSS_NOFLAGS;

if (dvdcss_read(input, tmp_buffer, 1, DVDCSS_READ_DECRYPT) != 1) {
fprintf(stderr, "dvdcss_read failed\n");
return 1;
}
if (skip) {
printf("testing block we are skipping anyways\n");
if( ((uint8_t*)tmp_buffer)[0x14] & 0x30 ) {
printf("dvdcss \"decoded\" block still has bit set\n");
if (!memcmp(tmp_buffer, block, 2048)) {
printf("dvdcss \"decoded\" block didn't change!\n");
} else {
printf("dvdcss \"decoded\" block changed!\n");
}
} else {
printf("dvdcss \"decoded\" block got bit removed!\n");
}
} else {
lseek64(fd, (pos+index) * DVDCSS_BLOCK_SIZE, SEEK_SET);
if (my_write(fd, tmp_buffer, DVDCSS_BLOCK_SIZE) < 0) {
return 1;
}
reseek = 1;
}
}
tmp_buffer = tmp_buffer + DVDCSS_BLOCK_SIZE;
}

pos += len;
if (reseek) {
lseek64(fd, pos * DVDCSS_BLOCK_SIZE, SEEK_SET);
}
}
}
printf("descrambled %llu blocks\n", count);
printf("\n");

return 0;
}

setarip_old
24th October 2010, 23:11
@spotter

A few days ago, I provided you with a list of 15 or 16 DVD titles that initially were not "rippable" by either AnyDVD or DVDFab - and required those programs to be modified in order to successfully backup those titles as full DVD "packages" (not simply "movie-only").

Have you as yet tried to make full DVD "packages" (.ISO image files or file format, including .IFO, .BUPs, and .VOBs) with any of these DVDs?

If so, how successful were you?

spotter
24th October 2010, 23:38
Iron Man 2 (2 disc set) is on hold at the library and in transfer to my local library, assuming it's not scratched, I'll try it out. can only put a limited # on hold so can't test that many so quickly :)

I'm just a lone person doing this for fun (and my local setup)

setarip_old
24th October 2010, 23:49
I'm just a lone person doing this for fun (and my local setup)Understood - I just want you to be aware of what's out there ;>}

spotter
25th October 2010, 23:54
have the new alice in wonderland, testing now.


update1:
- lsdvd complained (as it does for a bunch of disks "Please send bug report - no VTS_TMAPT ?? " but that doesn't impact me. only use lsdvd to authenticate the disk/drive. though it lists 99 titles! (not that I care about that either)
- ddrescue is chugging along. some invalid sectors at the beginning of the disk, but have read about 2GB so far without much issue.

update2:
- ddrescue did its thing, they really corrupt the FS


root@nas:/raid/data/image/recover/DVDs/aiw# df -h /mnt
Filesystem Size Used Avail Use% Mounted on
/dev/loop0 6.8G 6.8G 0 100% /mnt
root@nas:/raid/data/image/recover/DVDs/aiw# du -hsc /mnt
60G /mnt
60G total


i.e. ~7GB of disk size, but files claim 60G.

update3: descrambled 769256 blocks

seems to work without a problem.

setarip_old
26th October 2010, 02:30
seems to work without a problem.Full DVD with menus and extras, or "movie only"?

spotter
26th October 2010, 02:33
full DVD image with messed up file system as well and what ever extra region protection is included. i.e. I don't modify the image at all besides to unscramble the css sectors.

Don't do any tricks, just act like a DVD player, just read all blocks (including bad blocks, but as IFO will never let the dvd player read those blocks it doesn't matter).

Honestly, if we wanted to edi the IFOs to remove those protections, in my scheme it wouldn't be that hard if one knew how, but I don't and have no need to remove extra region protection either.

setarip_old
26th October 2010, 04:33
Sound interesting.

Have you actually burned a (D/L) disc and played it as a full DVD (with functioning menus and extras, as well as movie) on both a standalone DVD player and a PC software DVD player?

spotter
26th October 2010, 04:38
No, just mounted with daemon tools in windows. Don't have a standalone player. Also, for DL layer media, I think layer break issues might be important? I don't have the knowledge to calculate that.

setarip_old
26th October 2010, 05:21
Sorry, I'm a bit unclear about what you've accomplished thusfar.

You've created an .ISO image file that you've mounted with Daemon Tools, correct?

Once it was mounted, were you able to play it properly as a full DVD (with functioning menus and extras, as well as movie) on a PC software DVD player?

If so, this would be a major accomplishment...

spotter
26th October 2010, 05:49
yes to your Q.

lsdvd authenticates the drive/disc - without doing the disk, the dvd disk prevents some sectors from being even able to be read (the css scrambled sectors? unsure really)

ddrescue does a bitexact copy of the disk with the css encrypted sectors scrambled as they are on disc. so now one has an iso image that will play in vlc (if one open the ISO as a file, though VLC's iso support is crappy in my experience).

one can't just mount this iso w/ css scrambled sectors with daemon tools mostly because daemon tools doesn't act like a drive that one can authenticate and most tools assume that any disc in a drive that they can't authenticate to doesn't have css scrambled sectors so won't try to descramble css sectors

my tool just descrambled the css scrambled sectors in the image produced by ddrescue. pretty simple tool.

because I don't really pay attention to the file system, file system corruption doesn't matter to me (such as Alice in Wonderland that looks like it has 60GB of content, in my produced image it also looks like it has 60GB of content) All I need to know from the file system is where it claims the start/end of each vob is. now, this is something they can try to attack, but might break real players for all I know.

I don't think its that big of an accomplishment, it's a pretty naive approach. I think most tools just try to be too smart to create iso images. For example, DVDFab rips the movie to the file system and then creates an iso out of that rip. AnyDVD might do the same. That requires figuring out the titles and only ripping the real titles and editing the IFOs to fix things up and remove things like region protection. I don't need to (or want to) do any of that.

setarip_old
26th October 2010, 07:33
Please clarify. I asked you TWO questions:

1) You've created an .ISO image file that you've mounted with Daemon Tools, correct?

2) Once it was mounted, were you able to play it properly as a full DVD (with functioning menus and extras, as well as movie) on a PC software DVD player?

You responded:yes to your Q.This appears to be a response to my first question.

So would you please specifically answer my second question:

Once it was mounted, were you actually able to successfully play it properly as a full DVD (with functioning menus and extras, as well as movie - not simply as a file/"movie-only") on a PC software DVD player?

Thank you.

spotter
26th October 2010, 07:39
Yes to both. Mplayer classic.

setarip_old
26th October 2010, 07:49
Again, thank you - And best of luck with the other DVDs I listed ;>}

spotter
26th October 2010, 15:21
worked with windows media player 12 in windows 7 as well.

also, I'm not claiming any perfection in my code, very easily it could be buggy, but I think the methodology is sound.

setarip_old
26th October 2010, 17:54
Can you convert your process to run under Windows, so that it can be explored by the masses, including me?

spotter
26th October 2010, 18:33
it should run under cygwin just fine once. It's only library dependencies are libdvdcss and libcdio, and libcdio is probably already available on cygwin and libdvdcss should build easily.

ddrescue is available in cygwin and lsdvd is a simple program so assume it would build as well (though can probably fake it out by just having vlc play the disc for a moment before you ddrescue it)

spotter
26th October 2010, 18:44
the way I build on linux is g++ `pkg-config --cflags --libs libdvdcss libudf libiso9660` <cpp file> (for latest version don't really need the libudf) for those that want to build it