Mercurial > mplayer.hg
diff stream/stream_bd.c @ 31836:dcd515ac5f6c
Add support for bd:// streams as a test for a part of the AACS algorithm.
Patch by cRTrn13 <crtrn13-at-gmail.com> with some minor fixes by me.
author | reimar |
---|---|
date | Tue, 03 Aug 2010 16:26:50 +0000 |
parents | |
children | af68430bf5de |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/stream/stream_bd.c Tue Aug 03 16:26:50 2010 +0000 @@ -0,0 +1,356 @@ +/* + * Bluray stream playback + * by cRTrn13 <crtrn13-at-gmail.com> 2009 + * + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <limits.h> +#include "libavutil/common.h" +#include "libavutil/aes.h" +#include "libavutil/sha.h" +#include "libmpdemux/demuxer.h" +#include "libavutil/intreadwrite.h" +#include "m_struct.h" +#include "m_option.h" +#include "stream.h" + +static const int BD_UNIT_SIZE = 6144; +static const char *BD_UKF_PATH = "/%s/AACS/Unit_Key_RO.inf"; +static const char *BD_M2TS_PATH = "/%s/BDMV/STREAM/%05d.m2ts"; + +static const char *DEFAULT_BD_DEVICE = "/mnt/bd"; + +static const uint8_t BD_CBC_IV[] = { + 0x0b, 0xa0, 0xf8, 0xdd, 0xfe, 0xa6, 0x1f, 0xb3, + 0xd8, 0xdf, 0x9f, 0x56, 0x6a, 0x05, 0x0f, 0x78 +}; + +static const struct stream_priv_s { + int title; + char *device; +} stream_priv_dflts = { + 0, + NULL +}; + +// Format: bd://[title][</mntlocation>] +// --> e.g.: bd://117/media/THE_MUMMY/ +// --> or bd://152 +// instead of directly, mount location can also be gotten through -dvd-device +#define ST_OFF(f) M_ST_OFF(struct stream_priv_s,f) +static const m_option_t stream_opts_fields[] = { + { "hostname", ST_OFF(title), CONF_TYPE_INT, M_OPT_RANGE, 0, 99999, NULL}, + { "filename", ST_OFF(device), CONF_TYPE_STRING, 0, 0 ,0, NULL}, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; + +static const struct m_struct_st stream_opts = { + "bd", + sizeof(struct stream_priv_s), + &stream_priv_dflts, + stream_opts_fields +}; + +typedef union { + uint64_t u64[2]; + uint8_t u8[16]; +} key; + +struct uks { + int count; + key *keys; +}; + +struct bd_priv { + key vuk; + key iv; + int title; + const char *device; + FILE *title_file; + struct AVAES *aescbc; + struct AVAES *aeseed; + off_t pos; + struct uks uks; +}; + +static void bd_stream_close(stream_t *s) +{ + struct bd_priv *bd = s->priv; + fclose(bd->title_file); + av_free(bd->aescbc); + av_free(bd->aeseed); + free(bd->uks.keys); + free(bd); +} + +static int bd_stream_seek(stream_t *s, off_t pos) +{ + struct bd_priv *bd = s->priv; + + // must seek to start of unit + pos -= pos % BD_UNIT_SIZE; + bd->pos = pos; + s->pos = pos; + + return 1; +} + +static int bd_get_uks(struct bd_priv *bd) +{ + unsigned char *buf; + size_t file_size; + int pos; + int i, j; + struct AVAES *a; + struct AVSHA *asha; + FILE *file; + char filename[PATH_MAX]; + uint8_t discid[20]; + char *home; + int vukfound = 0; + + snprintf(filename, sizeof(filename), BD_UKF_PATH, bd->device); + file = fopen(filename, "rb"); + if (!file) { + mp_msg(MSGT_OPEN, MSGL_ERR, + "Cannot open file %s to get UK and DiscID\n", filename); + return 0; + } + fseek(file, 0, SEEK_END); + file_size = ftell(file); + if (file_size > 10 * 1024* 1024) { + mp_msg(MSGT_OPEN, MSGL_ERR, "File %s too large\n", filename); + fclose(file); + return 0; + } + rewind(file); + buf = av_malloc(file_size); + fread(buf, 1, file_size, file); + fclose(file); + + // get discid from file + asha = av_malloc(av_sha_size); + av_sha_init(asha, 160); + av_sha_update(asha, buf, file_size); + av_sha_final(asha, discid); + av_free(asha); + + // look up discid in KEYDB.cfg to get VUK + home = getenv("HOME"); + snprintf(filename, sizeof(filename), "%s/.dvdcss/KEYDB.cfg", home); + file = fopen(filename, "r"); + if (!file) { + mp_msg(MSGT_OPEN,MSGL_ERR, + "Cannot open VUK database file %s\n", filename); + av_free(buf); + return 0; + } + while (!feof(file)) { + char line[1024]; + uint8_t id[20]; + char d[200]; + char *vst; + unsigned int byte; + + fgets(line, sizeof(line), file); + // file is built up this way: + // DISCID = title | V | VUK + // or + // DISCID = title | key-pair + // key-pair = V | VUK + // or D | Date + // or M | M-key??? + // or I | I-Key + // can be followed by ; and comment + + //This means: first string up to whitespace is discid + sscanf(line, "%40s", d); + for (i = 0; i < 20; ++i) { + if (sscanf(&d[i*2], "%2x", &byte) != 1) break; + id[i] = byte; + } + if (memcmp(id, discid, 20) != 0) + continue; + mp_msg(MSGT_OPEN, MSGL_V, "KeyDB found Entry for DiscID:\n%s\n", line); + + vst = strstr(line, "| V |"); + if (vst == 0) break; + sscanf(&vst[6], "%32s", d); + for (i = 0; i < 16; i++) { + if (sscanf(&d[i*2], "%2x", &byte) != 1) break; + bd->vuk.u8[i] = byte; + } + vukfound = 1; + } + fclose(file); + if (!vukfound) { + mp_msg(MSGT_OPEN, MSGL_ERR, + "No Volume Unique Key (VUK) found for this Disc: "); + for (j = 0; j < 20; j++) mp_msg(MSGT_OPEN, MSGL_ERR, "%02x", discid[j]); + mp_msg(MSGT_OPEN, MSGL_ERR, "\n"); + return 0; + } + + pos = AV_RB32(buf); + if (pos < file_size) { + int key_pos = pos + 48; + int max_count = (file_size - key_pos - 16) / 48; + bd->uks.count = AV_RB16(&buf[pos]); + if (max_count < bd->uks.count) { + mp_msg(MSGT_OPEN, MSGL_ERR, + "File to small for key count %i, using %i\n", + bd->uks.count, max_count); + bd->uks.count = max_count; + } + bd->uks.keys = calloc(bd->uks.count, sizeof(key)); + + a = av_malloc(av_aes_size); + j = av_aes_init(a, bd->vuk.u8, 128, 1); + + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_BD_DISCID="); + for (j = 0; j < 20; j++) mp_msg(MSGT_IDENTIFY, MSGL_INFO, "%02x", discid[j]); + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "\n"); + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_BD_VUK="); + for (j = 0; j < 20; j++) mp_msg(MSGT_IDENTIFY, MSGL_INFO, "%02x", discid[j]); + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "\n"); + + for (i = 0; i < bd->uks.count; i++) { + av_aes_crypt(a, bd->uks.keys[i].u8, &buf[key_pos], 1, NULL, 1); // decrypt unit key + key_pos += 48; + } + + av_free(a); + } + + av_free(buf); + return 1; +} + +static off_t bd_read(struct bd_priv *bd, uint8_t *buf, int len) +{ + int read_len; + len &= ~15; + if (!len) + return 0; + + fseek(bd->title_file, bd->pos, SEEK_SET); + + read_len = fread(buf, 1, len, bd->title_file); + if (read_len != len) + return -1; + + if (bd->pos % BD_UNIT_SIZE) { + // decrypt in place + av_aes_crypt(bd->aescbc, buf, buf, len / 16, bd->iv.u8, 1); + } else { + // reset aes context as at start of unit + key enc_seed; + int i; + memcpy(bd->iv.u8, BD_CBC_IV, sizeof(bd->iv.u8)); + + // set up AES key from uk and seed + av_aes_init(bd->aeseed, bd->uks.keys[0].u8, 128, 0); + + // perform encryption of first 16 bytes of unit (seed) + av_aes_crypt(bd->aeseed, enc_seed.u8, buf, 1, NULL, 0); + + // perform xor + for (i = 0; i < 16; i++) + enc_seed.u8[i] ^= buf[i]; + + // set uk AES-CBC key from enc_seed and BD_CBC_IV + av_aes_init(bd->aescbc, enc_seed.u8, 128, 1); + + // decrypt + av_aes_crypt(bd->aescbc, &buf[16], &buf[16], (len - 16) / 16, bd->iv.u8, 1); + } + + bd->pos += read_len; + + return read_len; +} + +static int bd_stream_fill_buffer(stream_t *s, char *buf, int len) +{ + int read_len; + struct bd_priv *bd = s->priv; + + read_len = bd_read(bd, buf, len); + + s->pos = bd->pos; + + return read_len; +} + +static int bd_stream_open(stream_t *s, int mode, void* opts, int* file_format) +{ + char filename[PATH_MAX]; + + struct stream_priv_s* p = opts; + struct bd_priv *bd = calloc(1, sizeof(*bd)); + + if (p->device) + bd->device = p->device; + else if (dvd_device) + bd->device = dvd_device; + else + bd->device = DEFAULT_BD_DEVICE; + + s->sector_size = BD_UNIT_SIZE; + s->flags = STREAM_READ | MP_STREAM_SEEK; + s->fill_buffer = bd_stream_fill_buffer; + s->seek = bd_stream_seek; + s->close = bd_stream_close; + s->start_pos = 0; + s->priv = bd; + s->type = STREAMTYPE_BD; + s->url = strdup("bd://"); + + bd->pos = 0; + bd->title = p->title; + + // get and decrypt unit keys + if (!bd_get_uks(bd)) + return STREAM_ERROR; + + bd->aescbc = av_malloc(av_aes_size); + bd->aeseed = av_malloc(av_aes_size); + + snprintf(filename, sizeof(filename), BD_M2TS_PATH, bd->device, bd->title); + mp_msg(MSGT_OPEN, MSGL_STATUS, "Opening %s\n", filename); + bd->title_file = fopen(filename, "rb"); + if (!bd->title_file) + return STREAM_ERROR; + fseek(bd->title_file, 0, SEEK_END); + s->end_pos = ftell(bd->title_file); + rewind(bd->title_file); + + return STREAM_OK; +} + +const stream_info_t stream_info_bd = { + "Bluray", + "bd", + "cRTrn13", + "", + bd_stream_open, + { "bd", NULL }, + &stream_opts, + 1 +};