/*
* This file is part of libbluray
* Copyright (C) 2010 William Hahne
* Copyright (C) 2014 Petri Hintukainen <phintuka@users.sourceforge.net>
*
* 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
* <http://www.gnu.org/licenses/>.
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include "bdjo_parse.h"
#include "bdjo_data.h"
#include "disc/disc.h"
#include "bdnav/bdmv_parse.h"
#include "file/file.h"
#include "util/bits.h"
#include "util/logging.h"
#include "util/macro.h"
#include <stdlib.h>
#include <string.h>
static char *_read_string(BITSTREAM* bs, uint32_t length)
{
char *out = malloc(length + 1);
if (out) {
bs_read_string(bs, out, length);
} else {
BD_DEBUG(DBG_BDJ | DBG_CRIT, "Out of memory\n");
bs_skip(bs, length * 8);
}
return out;
}
/* BDJO_TERMINAL_INFO */
static int _parse_terminal_info(BITSTREAM* bs, BDJO_TERMINAL_INFO *p)
{
bs_skip(bs, 32); // skip length
bs_read_string(bs, p->default_font, 5);
p->initial_havi_config_id = bs_read(bs, 4);
p->menu_call_mask = bs_read(bs, 1);
p->title_search_mask = bs_read(bs, 1);
bs_skip(bs, 34); // skip padding
return 1;
}
/* BDJO_APP_CACHE_ITEM */
static void _parse_app_cache_item(BITSTREAM* bs, BDJO_APP_CACHE_ITEM *p)
{
p->type = bs_read(bs, 8);
bs_read_string(bs, p->ref_to_name, 5);
bs_read_string(bs, p->lang_code, 3);
bs_skip(bs, 24); // skip padding
}
/* BDJO_APP_CACHE_INFO */
static void _clean_app_cache_info(BDJO_APP_CACHE_INFO *p)
{
if (p) {
X_FREE(p->item);
}
}
static int _parse_app_cache_info(BITSTREAM* bs, BDJO_APP_CACHE_INFO *p)
{
unsigned ii;
bs_skip(bs, 32); // skip length
p->num_item = bs_read(bs, 8);
bs_skip(bs, 8); // skip padding
p->item = calloc(p->num_item, sizeof(BDJO_APP_CACHE_ITEM));
if (!p->item) {
BD_DEBUG(DBG_BDJ | DBG_CRIT, "Out of memory\n");
return -1;
}
for (ii = 0; ii < p->num_item; ii++) {
_parse_app_cache_item(bs, &p->item[ii]);
}
return 1;
}
/* BDJO_ACCESSIBLE_PLAYLISTS */
static void _clean_accessible_playlists(BDJO_ACCESSIBLE_PLAYLISTS *p)
{
if (p) {
X_FREE(p->pl);
}
}
static int _parse_accessible_playlists(BITSTREAM* bs, BDJO_ACCESSIBLE_PLAYLISTS *p)
{
unsigned ii;
bs_skip(bs, 32); // skip length
p->num_pl = bs_read(bs, 11);
p->access_to_all_flag = bs_read(bs, 1);
p->autostart_first_playlist_flag = bs_read(bs, 1);
bs_skip(bs, 19); // skip padding
p->pl = calloc(p->num_pl, sizeof(BDJO_PLAYLIST));
if (!p->pl) {
BD_DEBUG(DBG_BDJ | DBG_CRIT, "Out of memory\n");
return -1;
}
for (ii = 0; ii < p->num_pl; ii++) {
bs_read_string(bs, p->pl[ii].name, 5);
bs_skip(bs, 8); // skip padding
}
return 1;
}
/* BDJO_APP_PROFILE */
static void _parse_app_profile(BITSTREAM *bs, BDJO_APP_PROFILE *p)
{
p->profile_number = bs_read(bs, 16);
p->major_version = bs_read(bs, 8);
p->minor_version = bs_read(bs, 8);
p->micro_version = bs_read(bs, 8);
bs_skip(bs, 8);
}
/* BDJO_APP_NAME */
static void _clean_app_name(BDJO_APP_NAME *p)
{
if (p) {
X_FREE(p->name);
}
}
static int _count_app_strings(BITSTREAM *bs, uint16_t data_length, uint16_t prefix_bytes, const char *type)
{
int count = 0;
uint32_t bytes_read = 0;
int64_t pos = bs_pos(bs) >> 3;
while (bytes_read < data_length) {
bs_skip(bs, prefix_bytes * 8);
uint8_t length = bs_read(bs, 8);
bs_skip(bs, 8 * length);
bytes_read += prefix_bytes + 1 + length;
count++;
}
// seek back
if (bytes_read) {
if (bs_seek_byte(bs, pos) < 0) {
return -1;
}
}
if (bytes_read != data_length) {
BD_DEBUG(DBG_BDJ | DBG_CRIT, "data size mismatch (%d/%d), skipping %s\n", bytes_read, data_length, type);
count = 0;
}
return count;
}
static int _parse_app_name(BITSTREAM *bs, BDJO_APP_NAME *p)
{
bs_read_string(bs, p->lang, 3);
uint32_t length = bs_read(bs, 8);
p->name = _read_string(bs, length);
return p->name ? 1 : -1;
}
/* BDJO_APP_PARAM */
static void _clean_app_param(BDJO_APP_PARAM *p)
{
if (p) {
X_FREE(p->param);
}
}
static int _parse_app_param(BITSTREAM *bs, BDJO_APP_PARAM *p)
{
uint32_t length = bs_read(bs, 8);
p->param = _read_string(bs, length);
return p->param ? 1 : -1;
}
/* BDJO_APP */
static void _clean_bdjo_app(BDJO_APP *p)
{
if (p) {
unsigned ii;
for (ii = 0; ii < p->num_name; ii++) {
_clean_app_name(&p->name[ii]);
}
for (ii = 0; ii < p->num_param; ii++) {
_clean_app_param(&p->param[ii]);
}
X_FREE(p->profile);
X_FREE(p->name);
X_FREE(p->icon_locator);
X_FREE(p->base_dir);
X_FREE(p->classpath_extension);
X_FREE(p->initial_class);
X_FREE(p->param);
}
}
static char *_read_app_string(BITSTREAM *bs)
{
char *result;
uint8_t length = bs_read(bs, 8);
result = _read_string(bs, length);
// word align
if (!(length & 1))
bs_skip(bs, 8);
return result;
}
static int _parse_app_names(BITSTREAM *bs, BDJO_APP *p)
{
unsigned ii;
int r;
uint32_t data_length = bs_read(bs, 16);
r = _count_app_strings(bs, data_length, 3, "names");
if (r < 0) {
return -1;
}
p->num_name = r;
if (data_length == 0) return 1;
if (p->num_name) {
p->name = calloc(p->num_name, sizeof(BDJO_APP_NAME));
if (!p->name) {
BD_DEBUG(DBG_BDJ | DBG_CRIT, "Out of memory\n");
return -1;
}
for (ii = 0; ii < p->num_name; ii++) {
if (_parse_app_name(bs, &p->name[ii]) < 0) {
return -1;
}
}
} else {
// ignore invalid data (seek over chunk)
bs_skip(bs, data_length*8);
}
// word align
bs_skip(bs, 8 * (data_length & 1));
return 1;
}
static int _parse_app_params(BITSTREAM *bs, BDJO_APP *p)
{
unsigned ii;
int r;
uint32_t data_length = bs_read(bs, 8);
r = _count_app_strings(bs, data_length, 0, "params");
if (r < 0) {
return -1;
}
p->num_param = r;
if (p->num_param) {
p->param = calloc(p->num_param, sizeof(BDJO_APP_PARAM));
if (!p->param) {
BD_DEBUG(DBG_BDJ | DBG_CRIT, "Out of memory\n");
return -1;
}
for (ii = 0; ii < p->num_param; ii++) {
if (_parse_app_param(bs, &p->param[ii]) < 0) {
return -1;
}
}
} else {
// ignore invalid data (seek over chunk)
bs_skip(bs, data_length*8);
}
// word align
if (!(data_length & 1))
bs_skip(bs, 8);
return 1;
}
static int _parse_bdjo_app(BITSTREAM *bs, BDJO_APP *p)
{
unsigned ii;
p->control_code = bs_read(bs, 8);
p->type = bs_read(bs, 4);
bs_skip(bs, 4);
p->org_id = bs_read(bs, 32);
p->app_id = bs_read(bs, 16);
bs_skip(bs, 80); // skip sescriptor tag and length
/* application descriptor */
p->num_profile = bs_read(bs, 4);
bs_skip(bs, 12); // skip padding
p->profile = calloc(p->num_profile, sizeof(BDJO_APP_PROFILE));
if (!p->profile) {
BD_DEBUG(DBG_BDJ | DBG_CRIT, "Out of memory\n");
return -1;
}
for (ii = 0; ii < p->num_profile; ii++) {
_parse_app_profile(bs, &p->profile[ii]);
}
p->priority = bs_read(bs, 8);
p->binding = bs_read(bs, 2);
p->visibility = bs_read(bs, 2);
bs_skip(bs, 4);
if (_parse_app_names(bs, p) < 0) {
return -1;
}
p->icon_locator = _read_app_string(bs);
if (!p->icon_locator) {
return -1;
}
p->icon_flags = bs_read(bs, 16);
p->base_dir = _read_app_string(bs);
if (!p->base_dir) {
return -1;
}
p->classpath_extension = _read_app_string(bs);
if (!p->classpath_extension) {
return -1;
}
p->initial_class = _read_app_string(bs);
if (!p->initial_class) {
return -1;
}
if (_parse_app_params(bs, p) < 0) {
return -1;
}
return 1;
}
/* BDJO_APP_MANAGEMENT_TABLE */
static void _clean_app_management_table(BDJO_APP_MANAGEMENT_TABLE *p)
{
if (p) {
unsigned ii;
for (ii = 0; ii < p->num_app; ii++) {
_clean_bdjo_app(&p->app[ii]);
}
X_FREE(p->app);
}
}
static int _parse_app_management_table(BITSTREAM *bs, BDJO_APP_MANAGEMENT_TABLE *p)
{
unsigned ii;
bs_skip(bs, 32); // length
p->num_app = bs_read(bs, 8);
bs_skip(bs, 8); // skip padding
p->app = calloc(p->num_app, sizeof(BDJO_APP));
if (!p->app) {
BD_DEBUG(DBG_BDJ | DBG_CRIT, "Out of memory\n");
return -1;
}
for (ii = 0; ii < p->num_app; ii++) {
/* TODO: if parsing of application data fails, ignore that app but parse others */
if (_parse_bdjo_app(bs, &p->app[ii]) < 0) {
return -1;
}
}
return 1;
}
/* BDJO_KEY_INTEREST_TABLE */
static int _parse_key_interest_table(BITSTREAM *bs, BDJO_KEY_INTEREST_TABLE *p)
{
p->vk_play = bs_read(bs, 1);
p->vk_stop = bs_read(bs, 1);
p->vk_ffw = bs_read(bs, 1);
p->vk_rew = bs_read(bs, 1);
p->vk_track_next = bs_read(bs, 1);
p->vk_track_prev = bs_read(bs, 1);
p->vk_pause = bs_read(bs, 1);
p->vk_still_off = bs_read(bs, 1);
p->vk_sec_audio_ena_dis = bs_read(bs, 1);
p->vk_sec_video_ena_dis = bs_read(bs, 1);
p->pg_textst_ena_dis = bs_read(bs, 1);
bs_skip(bs, 21);
return 1;
}
/* BDJO_FILE_ACCESS_INFO */
static void _clean_file_access_info(BDJO_FILE_ACCESS_INFO *p)
{
if (p) {
X_FREE(p->path);
}
}
static int _parse_file_access_info(BITSTREAM *bs, BDJO_FILE_ACCESS_INFO *p)
{
uint16_t file_access_length = bs_read(bs, 16);
p->path = _read_string(bs, file_access_length);
return p->path ? 1 : -1;
}
/* BDJO */
static void _clean_bdjo(BDJO *p)
{
if (p) {
_clean_app_cache_info(&p->app_cache_info);
_clean_accessible_playlists(&p->accessible_playlists);
_clean_app_management_table(&p->app_table);
_clean_file_access_info(&p->file_access_info);
}
}
#define BDJO_SIG1 ('B' << 24 | 'D' << 16 | 'J' << 8 | 'O')
static int _parse_header(BITSTREAM *bs, uint32_t *bdjo_version)
{
if (!bdmv_parse_header(bs, BDJO_SIG1, bdjo_version)) {
return -1;
}
BD_DEBUG(DBG_BDJ, "[bdj] BDJO > Version: %.4s\n", (const char *)bdjo_version);
// skip address table
bs_skip(bs, 8*40);
return 1;
}
static BDJO *_bdjo_parse(BD_FILE_H *fp)
{
BITSTREAM bs;
BDJO *p;
if (bs_init(&bs, fp) < 0) {
BD_DEBUG(DBG_BDJ, "?????.bdjo: read error\n");
return NULL;
}
p = calloc(1, sizeof(BDJO));
if (!p) {
BD_DEBUG(DBG_BDJ | DBG_CRIT, "Out of memory\n");
return NULL;
}
if (_parse_header(&bs, &p->bdjo_version) < 0 ||
_parse_terminal_info(&bs, &p->terminal_info) < 0 ||
_parse_app_cache_info(&bs, &p->app_cache_info) < 0 ||
_parse_accessible_playlists(&bs, &p->accessible_playlists) < 0 ||
_parse_app_management_table(&bs, &p->app_table) < 0 ||
_parse_key_interest_table(&bs, &p->key_interest_table) < 0 ||
_parse_file_access_info(&bs, &p->file_access_info) < 0) {
bdjo_free(&p);
}
return p;
}
/*
*
*/
void bdjo_free(BDJO **pp)
{
if (pp && *pp) {
_clean_bdjo(*pp);
X_FREE(*pp);
}
}
BDJO *bdjo_parse(const char *path)
{
BD_FILE_H *fp;
BDJO *bdjo;
fp = file_open(path, "rb");
if (!fp) {
BD_DEBUG(DBG_BDJ | DBG_CRIT, "Failed to open bdjo file (%s)\n", path);
return NULL;
}
bdjo = _bdjo_parse(fp);
file_close(fp);
return bdjo;
}
static BDJO *_bdjo_get(BD_DISC *disc, const char *dir, const char *file)
{
BD_FILE_H *fp;
BDJO *bdjo;
fp = disc_open_file(disc, dir, file);
if (!fp) {
return NULL;
}
bdjo = _bdjo_parse(fp);
file_close(fp);
return bdjo;
}
BDJO *bdjo_get(BD_DISC *disc, const char *file)
{
BDJO *bdjo;
bdjo = _bdjo_get(disc, "BDMV" DIR_SEP "BDJO", file);
if (bdjo) {
return bdjo;
}
/* if failed, try backup file */
bdjo = _bdjo_get(disc, "BDMV" DIR_SEP "BACKUP" DIR_SEP "BDJO", file);
return bdjo;
}