/* * This file is part of libbluray * Copyright (C) 2010 William Hahne * Copyright (C) 2014 Petri Hintukainen * * 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 "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 #include 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; }