/*
* This file is part of libbluray
* Copyright (C) 2014 VideoLAN
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* .
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include "dec.h"
#include "enc_info.h"
#include "aacs.h"
#include "bdplus.h"
#include "file/file.h"
#include "util/logging.h"
#include "util/macro.h"
#include "util/strutl.h"
#include
struct bd_dec {
int use_menus;
BD_AACS *aacs;
BD_BDPLUS *bdplus;
};
/*
* stream
*/
typedef struct {
BD_FILE_H *fp;
BD_AACS *aacs;
BD_BDPLUS_ST *bdplus;
} DEC_STREAM;
static int64_t _stream_read(BD_FILE_H *fp, uint8_t *buf, int64_t size)
{
DEC_STREAM *st = (DEC_STREAM *)fp->internal;
int64_t result;
if (size != 6144) {
BD_DEBUG(DBG_CRIT, "read size != unit size\n");
return 0;
}
result = st->fp->read(st->fp, buf, size);
if (result <= 0) {
return result;
}
if (st->aacs) {
if (libaacs_decrypt_unit(st->aacs, buf)) {
/* failure is detected from TP header */
}
}
if (st->bdplus) {
if (libbdplus_fixup(st->bdplus, buf, (int)size) < 0) {
/* there's no way to verify if the stream was decoded correctly */
}
}
return result;
}
static int64_t _stream_seek(BD_FILE_H *fp, int64_t offset, int32_t origin)
{
DEC_STREAM *st = (DEC_STREAM *)fp->internal;
int64_t result = st->fp->seek(st->fp, offset, origin);
if (result >= 0 && st->bdplus) {
libbdplus_seek(st->bdplus, st->fp->tell(st->fp));
}
return result;
}
static int64_t _stream_tell(BD_FILE_H *fp)
{
DEC_STREAM *st = (DEC_STREAM *)fp->internal;
return st->fp->tell(st->fp);
}
static void _stream_close(BD_FILE_H *fp)
{
DEC_STREAM *st = (DEC_STREAM *)fp->internal;
if (st->bdplus) {
libbdplus_m2ts_close(&st->bdplus);
}
st->fp->close(st->fp);
X_FREE(fp->internal);
X_FREE(fp);
}
BD_FILE_H *dec_open_stream(BD_DEC *dec, BD_FILE_H *fp, uint32_t clip_id)
{
DEC_STREAM *st;
BD_FILE_H *p = calloc(1, sizeof(BD_FILE_H));
if (!p) {
return NULL;
}
st = calloc(1, sizeof(DEC_STREAM));
if (!st) {
X_FREE(p);
return NULL;
}
st->fp = fp;
if (dec->bdplus) {
st->bdplus = libbdplus_m2ts(dec->bdplus, clip_id, 0);
}
if (dec->aacs) {
st->aacs = dec->aacs;
if (!dec->use_menus) {
/* There won't be title events --> need to manually reset AACS CPS */
libaacs_select_title(dec->aacs, 0xffff);
}
}
p->internal = st;
p->read = _stream_read;
p->seek = _stream_seek;
p->tell = _stream_tell;
p->close = _stream_close;
return p;
}
/*
*
*/
/*
*
*/
static int _bdrom_have_file(void *p, const char *dir, const char *file)
{
struct dec_dev *dev = (struct dec_dev *)p;
BD_FILE_H *fp;
char *path;
path = str_printf("%s" DIR_SEP "%s", dir, file);
if (!path) {
return 0;
}
fp = dev->pf_file_open_bdrom(dev->file_open_bdrom_handle, path);
X_FREE(path);
if (fp) {
file_close(fp);
return 1;
}
return 0;
}
static int _libaacs_init(BD_DEC *dec, struct dec_dev *dev,
BD_ENC_INFO *i, const char *keyfile_path)
{
int result;
const uint8_t *disc_id;
if (!dec->aacs) {
return 0;
}
result = libaacs_open(dec->aacs, dev->device, dev->file_open_vfs_handle, (void*)dev->pf_file_open_vfs, keyfile_path);
i->aacs_error_code = result;
i->aacs_handled = !result;
i->aacs_mkbv = libaacs_get_mkbv(dec->aacs);
disc_id = libaacs_get_aacs_data(dec->aacs, BD_AACS_DISC_ID);
if (disc_id) {
memcpy(i->disc_id, disc_id, 20);
}
if (result) {
BD_DEBUG(DBG_BLURAY | DBG_CRIT, "aacs_open() failed: %d!\n", result);
libaacs_unload(&dec->aacs);
return 0;
}
BD_DEBUG(DBG_BLURAY, "Opened libaacs\n");
return 1;
}
static int _libbdplus_init(BD_DEC *dec, struct dec_dev *dev,
BD_ENC_INFO *i,
void *regs, void *psr_read, void *psr_write)
{
if (!dec->bdplus) {
return 0;
}
const uint8_t *vid = libaacs_get_aacs_data(dec->aacs, BD_AACS_MEDIA_VID);
const uint8_t *mk = libaacs_get_aacs_data(dec->aacs, BD_AACS_MEDIA_KEY);
if (!vid) {
BD_DEBUG(DBG_BLURAY | DBG_CRIT, "BD+ initialization failed (no AACS ?)\n");
libbdplus_unload(&dec->bdplus);
return 0;
}
if (libbdplus_init(dec->bdplus, dev->root, dev->device, dev->file_open_bdrom_handle, (void*)dev->pf_file_open_bdrom, vid, mk)) {
BD_DEBUG(DBG_BLURAY | DBG_CRIT, "bdplus_init() failed\n");
i->bdplus_handled = 0;
libbdplus_unload(&dec->bdplus);
return 0;
}
BD_DEBUG(DBG_BLURAY, "libbdplus initialized\n");
/* map player memory regions */
libbdplus_mmap(dec->bdplus, 0, regs);
libbdplus_mmap(dec->bdplus, 1, (void*)((uint8_t *)regs + sizeof(uint32_t) * 128));
/* connect registers */
libbdplus_psr(dec->bdplus, regs, psr_read, psr_write);
i->bdplus_gen = libbdplus_get_gen(dec->bdplus);
i->bdplus_date = libbdplus_get_date(dec->bdplus);
i->bdplus_handled = 1;
if (i->bdplus_date == 0) {
// libmmbd -> no menu support
BD_DEBUG(DBG_BLURAY | DBG_CRIT, "WARNING: using libmmbd for BD+. On-disc menus will not work.\n");
//i->no_menu_support = 1;
}
return 1;
}
static int _dec_detect(struct dec_dev *dev, BD_ENC_INFO *i)
{
/* Check for AACS */
i->aacs_detected = libaacs_required((void*)dev, _bdrom_have_file);
if (!i->aacs_detected) {
/* No AACS (=> no BD+) */
return 0;
}
/* check for BD+ */
i->bdplus_detected = libbdplus_required((void*)dev, _bdrom_have_file);
return 1;
}
static void _dec_load(BD_DEC *dec, BD_ENC_INFO *i)
{
int force_mmbd_aacs = 0;
if (i->bdplus_detected) {
/* load BD+ library and check BD+ library type. libmmbd doesn't work with libaacs */
dec->bdplus = libbdplus_load();
force_mmbd_aacs = dec->bdplus && libbdplus_is_mmbd(dec->bdplus);
}
/* load AACS library */
dec->aacs = libaacs_load(force_mmbd_aacs);
i->libaacs_detected = !!dec->aacs;
i->libbdplus_detected = !!dec->bdplus;
}
/*
*
*/
BD_DEC *dec_init(struct dec_dev *dev, BD_ENC_INFO *enc_info,
const char *keyfile_path,
void *regs, void *psr_read, void *psr_write)
{
BD_DEC *dec = NULL;
memset(enc_info, 0, sizeof(*enc_info));
/* detect AACS/BD+ */
if (!_dec_detect(dev, enc_info)) {
return NULL;
}
dec = calloc(1, sizeof(BD_DEC));
if (!dec) {
return NULL;
}
/* load compatible libraries */
_dec_load(dec, enc_info);
/* init decoding libraries */
/* BD+ won't help unless AACS works ... */
if (_libaacs_init(dec, dev, enc_info, keyfile_path)) {
_libbdplus_init(dec, dev, enc_info, regs, psr_read, psr_write);
}
if (!enc_info->aacs_handled) {
/* AACS failed, clean up */
dec_close(&dec);
}
/* BD+ failure may be non-fatal (not all titles in disc use BD+).
* Keep working AACS decoder even if BD+ init failed
*/
return dec;
}
void dec_close(BD_DEC **pp)
{
if (pp && *pp) {
BD_DEC *p = *pp;
libaacs_unload(&p->aacs);
libbdplus_unload(&p->bdplus);
X_FREE(*pp);
}
}
/*
*
*/
const uint8_t *dec_data(BD_DEC *dec, int type)
{
const uint8_t *ret = NULL;
if (type >= 0x1000) {
if (dec->bdplus) {
ret = libbdplus_get_data(dec->bdplus, type);
}
} else {
if (dec->aacs) {
ret = libaacs_get_aacs_data(dec->aacs, type);
}
}
return ret;
}
const uint8_t *dec_disc_id(BD_DEC *dec)
{
return dec_data(dec, BD_AACS_DISC_ID);
}
void dec_start(BD_DEC *dec, uint32_t num_titles)
{
if (num_titles == 0) {
dec->use_menus = 1;
if (dec->bdplus) {
libbdplus_start(dec->bdplus);
libbdplus_event(dec->bdplus, 0x110, 0xffff, 0);
}
} else {
if (dec->bdplus) {
libbdplus_event(dec->bdplus, 0xffffffff, num_titles, 0);
}
}
}
void dec_title(BD_DEC *dec, uint32_t title)
{
if (dec->aacs) {
libaacs_select_title(dec->aacs, title);
}
if (dec->bdplus) {
libbdplus_event(dec->bdplus, 0x110, title, 0);
}
}
void dec_application(BD_DEC *dec, uint32_t data)
{
if (dec->bdplus) {
libbdplus_event(dec->bdplus, 0x210, data, 0);
}
}