|
|
/*
|
|
|
* Copyright (C) 2000, 2001 H<>an Hjort
|
|
|
* Copyright (C) 2001 Rich Wareham <richwareham@users.sourceforge.net>
|
|
|
* 2002-2004 the dvdnav project
|
|
|
*
|
|
|
* This file is part of libdvdnav, a DVD navigation library. It is modified
|
|
|
* from a file originally part of the Ogle DVD player.
|
|
|
*
|
|
|
* libdvdnav 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.
|
|
|
*
|
|
|
* libdvdnav 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 this program; if not, write to the Free Software
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
|
*
|
|
|
* $Id: vm.c,v 1.32 2004/12/20 19:27:20 mroi Exp $
|
|
|
*
|
|
|
*/
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
#include "config.h"
|
|
|
#endif
|
|
|
|
|
|
#include <stdio.h>
|
|
|
#include <string.h>
|
|
|
#include <stdlib.h>
|
|
|
#include <unistd.h>
|
|
|
#include <inttypes.h>
|
|
|
#include <assert.h>
|
|
|
#include <sys/types.h>
|
|
|
#include <sys/stat.h>
|
|
|
#include <fcntl.h>
|
|
|
#include "dvdread.h"
|
|
|
|
|
|
#include "dvdnav_internal.h"
|
|
|
|
|
|
#ifdef _MSC_VER
|
|
|
#include <io.h> /* read() */
|
|
|
#endif /* _MSC_VER */
|
|
|
|
|
|
/*
|
|
|
#define STRICT
|
|
|
*/
|
|
|
|
|
|
/* Local prototypes */
|
|
|
|
|
|
/* get_XYZ returns a value.
|
|
|
* set_XYZ sets state using passed parameters.
|
|
|
* returns success/failure.
|
|
|
*/
|
|
|
|
|
|
/* Play */
|
|
|
static link_t play_PGC(vm_t *vm);
|
|
|
static link_t play_PGC_PG(vm_t *vm, int pgN);
|
|
|
static link_t play_PGC_post(vm_t *vm);
|
|
|
static link_t play_PG(vm_t *vm);
|
|
|
static link_t play_Cell(vm_t *vm);
|
|
|
static link_t play_Cell_post(vm_t *vm);
|
|
|
|
|
|
/* Process link - returns 1 if a hop has been performed */
|
|
|
static int process_command(vm_t *vm,link_t link_values);
|
|
|
|
|
|
/* Set */
|
|
|
static int set_TT(vm_t *vm, int tt);
|
|
|
static int set_PTT(vm_t *vm, int tt, int ptt);
|
|
|
static int set_VTS_TT(vm_t *vm, int vtsN, int vts_ttn);
|
|
|
static int set_VTS_PTT(vm_t *vm, int vtsN, int vts_ttn, int part);
|
|
|
static int set_FP_PGC(vm_t *vm);
|
|
|
static int set_MENU(vm_t *vm, int menu);
|
|
|
static int set_PGCN(vm_t *vm, int pgcN);
|
|
|
static int set_PGN(vm_t *vm); /* Set PGN based on (vm->state).CellN */
|
|
|
static void set_RSMinfo(vm_t *vm, int cellN, int blockN);
|
|
|
|
|
|
/* Get */
|
|
|
static int get_TT(vm_t *vm, int vtsN, int vts_ttn);
|
|
|
static int get_ID(vm_t *vm, int id);
|
|
|
static int get_PGCN(vm_t *vm);
|
|
|
|
|
|
static pgcit_t* get_MENU_PGCIT(vm_t *vm, ifo_handle_t *h, uint16_t lang);
|
|
|
static pgcit_t* get_PGCIT(vm_t *vm);
|
|
|
|
|
|
|
|
|
/* Helper functions */
|
|
|
|
|
|
#ifdef TRACE
|
|
|
static void vm_print_current_domain_state(vm_t *vm) {
|
|
|
switch((vm->state).domain) {
|
|
|
case VTS_DOMAIN:
|
|
|
fprintf(MSG_OUT, "libdvdnav: Video Title Domain: -\n");
|
|
|
break;
|
|
|
|
|
|
case VTSM_DOMAIN:
|
|
|
fprintf(MSG_OUT, "libdvdnav: Video Title Menu Domain: -\n");
|
|
|
break;
|
|
|
|
|
|
case VMGM_DOMAIN:
|
|
|
fprintf(MSG_OUT, "libdvdnav: Video Manager Menu Domain: -\n");
|
|
|
break;
|
|
|
|
|
|
case FP_DOMAIN:
|
|
|
fprintf(MSG_OUT, "libdvdnav: First Play Domain: -\n");
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
fprintf(MSG_OUT, "libdvdnav: Unknown Domain: -\n");
|
|
|
break;
|
|
|
}
|
|
|
fprintf(MSG_OUT, "libdvdnav: VTS:%d PGC:%d PG:%u CELL:%u BLOCK:%u VTS_TTN:%u TTN:%u TT_PGCN:%u\n",
|
|
|
(vm->state).vtsN,
|
|
|
get_PGCN(vm),
|
|
|
(vm->state).pgN,
|
|
|
(vm->state).cellN,
|
|
|
(vm->state).blockN,
|
|
|
(vm->state).VTS_TTN_REG,
|
|
|
(vm->state).TTN_REG,
|
|
|
(vm->state).TT_PGCN_REG);
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
static void dvd_read_name(char *name, const char *device) {
|
|
|
/* Because we are compiling with _FILE_OFFSET_BITS=64
|
|
|
* all off_t are 64bit.
|
|
|
*/
|
|
|
off_t off;
|
|
|
int fd, i;
|
|
|
uint8_t data[DVD_VIDEO_LB_LEN];
|
|
|
|
|
|
/* Read DVD name */
|
|
|
fd = open(device, O_RDONLY);
|
|
|
if (fd > 0) {
|
|
|
off = lseek( fd, 32 * (off_t) DVD_VIDEO_LB_LEN, SEEK_SET );
|
|
|
if( off == ( 32 * (off_t) DVD_VIDEO_LB_LEN ) ) {
|
|
|
off = read( fd, data, DVD_VIDEO_LB_LEN );
|
|
|
close(fd);
|
|
|
if (off == ( (off_t) DVD_VIDEO_LB_LEN )) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: DVD Title: ");
|
|
|
for(i=25; i < 73; i++ ) {
|
|
|
if((data[i] == 0)) break;
|
|
|
if((data[i] > 32) && (data[i] < 127)) {
|
|
|
fprintf(MSG_OUT, "%c", data[i]);
|
|
|
} else {
|
|
|
fprintf(MSG_OUT, " ");
|
|
|
}
|
|
|
}
|
|
|
strncpy(name, &data[25], 48);
|
|
|
name[48] = 0;
|
|
|
fprintf(MSG_OUT, "\nlibdvdnav: DVD Serial Number: ");
|
|
|
for(i=73; i < 89; i++ ) {
|
|
|
if((data[i] == 0)) break;
|
|
|
if((data[i] > 32) && (data[i] < 127)) {
|
|
|
fprintf(MSG_OUT, "%c", data[i]);
|
|
|
} else {
|
|
|
fprintf(MSG_OUT, " ");
|
|
|
}
|
|
|
}
|
|
|
fprintf(MSG_OUT, "\nlibdvdnav: DVD Title (Alternative): ");
|
|
|
for(i=89; i < 128; i++ ) {
|
|
|
if((data[i] == 0)) break;
|
|
|
if((data[i] > 32) && (data[i] < 127)) {
|
|
|
fprintf(MSG_OUT, "%c", data[i]);
|
|
|
} else {
|
|
|
fprintf(MSG_OUT, " ");
|
|
|
}
|
|
|
}
|
|
|
fprintf(MSG_OUT, "\n");
|
|
|
} else {
|
|
|
fprintf(MSG_OUT, "libdvdnav: Can't read name block. Probably not a DVD-ROM device.\n");
|
|
|
}
|
|
|
} else {
|
|
|
fprintf(MSG_OUT, "libdvdnav: Can't seek to block %u\n", 32 );
|
|
|
}
|
|
|
close(fd);
|
|
|
} else {
|
|
|
fprintf(MSG_OUT, "NAME OPEN FAILED\n");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
static int ifoOpenNewVTSI(vm_t *vm, dvd_reader_t *dvd, int vtsN) {
|
|
|
if((vm->state).vtsN == vtsN) {
|
|
|
return 1; /* We alread have it */
|
|
|
}
|
|
|
|
|
|
if(vm->vtsi != NULL)
|
|
|
ifoClose(vm->vtsi);
|
|
|
|
|
|
vm->vtsi = ifoOpenVTSI(dvd, vtsN);
|
|
|
if(vm->vtsi == NULL) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: ifoOpenVTSI failed\n");
|
|
|
return 0;
|
|
|
}
|
|
|
if(!ifoRead_VTS_PTT_SRPT(vm->vtsi)) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: ifoRead_VTS_PTT_SRPT failed\n");
|
|
|
return 0;
|
|
|
}
|
|
|
if(!ifoRead_PGCIT(vm->vtsi)) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: ifoRead_PGCIT failed\n");
|
|
|
return 0;
|
|
|
}
|
|
|
if(!ifoRead_PGCI_UT(vm->vtsi)) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: ifoRead_PGCI_UT failed\n");
|
|
|
return 0;
|
|
|
}
|
|
|
if(!ifoRead_VOBU_ADMAP(vm->vtsi)) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: ifoRead_VOBU_ADMAP vtsi failed\n");
|
|
|
return 0;
|
|
|
}
|
|
|
if(!ifoRead_TITLE_VOBU_ADMAP(vm->vtsi)) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: ifoRead_TITLE_VOBU_ADMAP vtsi failed\n");
|
|
|
return 0;
|
|
|
}
|
|
|
(vm->state).vtsN = vtsN;
|
|
|
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
|
|
|
/* Initialisation & Destruction */
|
|
|
|
|
|
vm_t* vm_new_vm() {
|
|
|
return (vm_t*)calloc(sizeof(vm_t), sizeof(char));
|
|
|
}
|
|
|
|
|
|
void vm_free_vm(vm_t *vm) {
|
|
|
vm_stop(vm);
|
|
|
free(vm);
|
|
|
}
|
|
|
|
|
|
|
|
|
/* IFO Access */
|
|
|
|
|
|
ifo_handle_t *vm_get_vmgi(vm_t *vm) {
|
|
|
return vm->vmgi;
|
|
|
}
|
|
|
|
|
|
ifo_handle_t *vm_get_vtsi(vm_t *vm) {
|
|
|
return vm->vtsi;
|
|
|
}
|
|
|
|
|
|
|
|
|
/* Reader Access */
|
|
|
|
|
|
dvd_reader_t *vm_get_dvd_reader(vm_t *vm) {
|
|
|
return vm->dvd;
|
|
|
}
|
|
|
|
|
|
|
|
|
/* Basic Handling */
|
|
|
|
|
|
int vm_start(vm_t *vm) {
|
|
|
/* Set pgc to FP (First Play) pgc */
|
|
|
set_FP_PGC(vm);
|
|
|
process_command(vm, play_PGC(vm));
|
|
|
return !vm->stopped;
|
|
|
}
|
|
|
|
|
|
void vm_stop(vm_t *vm) {
|
|
|
if(vm->vmgi) {
|
|
|
ifoClose(vm->vmgi);
|
|
|
vm->vmgi=NULL;
|
|
|
}
|
|
|
if(vm->vtsi) {
|
|
|
ifoClose(vm->vtsi);
|
|
|
vm->vtsi=NULL;
|
|
|
}
|
|
|
if(vm->dvd) {
|
|
|
if (!vm->openedDvd)
|
|
|
DVDClose(vm->dvd);
|
|
|
vm->dvd=NULL;
|
|
|
}
|
|
|
vm->stopped = 1;
|
|
|
}
|
|
|
|
|
|
int vm_reset(vm_t *vm, const char *dvdroot) {
|
|
|
/* Setup State */
|
|
|
memset((vm->state).registers.SPRM, 0, sizeof((vm->state).registers.SPRM));
|
|
|
memset((vm->state).registers.GPRM, 0, sizeof((vm->state).registers.GPRM));
|
|
|
memset((vm->state).registers.GPRM_mode, 0, sizeof((vm->state).registers.GPRM_mode));
|
|
|
memset((vm->state).registers.GPRM_mode, 0, sizeof((vm->state).registers.GPRM_mode));
|
|
|
memset((vm->state).registers.GPRM_time, 0, sizeof((vm->state).registers.GPRM_time));
|
|
|
(vm->state).registers.SPRM[0] = ('e'<<8)|'n'; /* Player Menu Languange code */
|
|
|
(vm->state).AST_REG = 15; /* 15 why? */
|
|
|
(vm->state).SPST_REG = 62; /* 62 why? */
|
|
|
(vm->state).AGL_REG = 1;
|
|
|
(vm->state).TTN_REG = 1;
|
|
|
(vm->state).VTS_TTN_REG = 1;
|
|
|
/* (vm->state).TT_PGCN_REG = 0 */
|
|
|
(vm->state).PTTN_REG = 1;
|
|
|
(vm->state).HL_BTNN_REG = 1 << 10;
|
|
|
(vm->state).PTL_REG = 15; /* Parental Level */
|
|
|
(vm->state).registers.SPRM[12] = ('U'<<8)|'S'; /* Parental Management Country Code */
|
|
|
(vm->state).registers.SPRM[16] = ('e'<<8)|'n'; /* Initial Language Code for Audio */
|
|
|
(vm->state).registers.SPRM[18] = ('e'<<8)|'n'; /* Initial Language Code for Spu */
|
|
|
(vm->state).registers.SPRM[20] = 0x1; /* Player Regional Code Mask. Region free! */
|
|
|
(vm->state).registers.SPRM[14] = 0x100; /* Try Pan&Scan */
|
|
|
|
|
|
(vm->state).pgN = 0;
|
|
|
(vm->state).cellN = 0;
|
|
|
(vm->state).cell_restart = 0;
|
|
|
|
|
|
(vm->state).domain = FP_DOMAIN;
|
|
|
(vm->state).rsm_vtsN = 0;
|
|
|
(vm->state).rsm_cellN = 0;
|
|
|
(vm->state).rsm_blockN = 0;
|
|
|
|
|
|
(vm->state).vtsN = -1;
|
|
|
|
|
|
if (vm->dvd && dvdroot) {
|
|
|
/* a new dvd device has been requested */
|
|
|
vm_stop(vm);
|
|
|
}
|
|
|
|
|
|
if (!vm->dvd) {
|
|
|
if (vm->openedDvd) /*JMP we use the dvd opened by copy*/
|
|
|
vm->dvd=vm->openedDvd;
|
|
|
else
|
|
|
vm->dvd = DVDOpen(dvdroot);
|
|
|
if(!vm->dvd) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: vm: faild to open/read the DVD\n");
|
|
|
return 0;
|
|
|
}
|
|
|
dvd_read_name(vm->dvd_name, dvdroot);
|
|
|
vm->map = remap_loadmap(vm->dvd_name);
|
|
|
vm->vmgi = ifoOpenVMGI(vm->dvd);
|
|
|
if(!vm->vmgi) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: vm: faild to read VIDEO_TS.IFO\n");
|
|
|
return 0;
|
|
|
}
|
|
|
if(!ifoRead_FP_PGC(vm->vmgi)) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_FP_PGC failed\n");
|
|
|
return 0;
|
|
|
}
|
|
|
if(!ifoRead_TT_SRPT(vm->vmgi)) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_TT_SRPT failed\n");
|
|
|
return 0;
|
|
|
}
|
|
|
if(!ifoRead_PGCI_UT(vm->vmgi)) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_PGCI_UT failed\n");
|
|
|
return 0;
|
|
|
}
|
|
|
if(!ifoRead_PTL_MAIT(vm->vmgi)) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_PTL_MAIT failed\n");
|
|
|
/* return 0; Not really used for now.. */
|
|
|
}
|
|
|
if(!ifoRead_VTS_ATRT(vm->vmgi)) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_VTS_ATRT failed\n");
|
|
|
/* return 0; Not really used for now.. */
|
|
|
}
|
|
|
if(!ifoRead_VOBU_ADMAP(vm->vmgi)) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: vm: ifoRead_VOBU_ADMAP vgmi failed\n");
|
|
|
/* return 0; Not really used for now.. */
|
|
|
}
|
|
|
/* ifoRead_TXTDT_MGI(vmgi); Not implemented yet */
|
|
|
}
|
|
|
if (vm->vmgi) {
|
|
|
int i, mask;
|
|
|
fprintf(MSG_OUT, "libdvdnav: DVD disk reports itself with Region mask 0x%08x. Regions:",
|
|
|
vm->vmgi->vmgi_mat->vmg_category);
|
|
|
for (i = 1, mask = 1; i <= 8; i++, mask <<= 1)
|
|
|
if (((vm->vmgi->vmgi_mat->vmg_category >> 16) & mask) == 0)
|
|
|
fprintf(MSG_OUT, " %d", i);
|
|
|
fprintf(MSG_OUT, "\n");
|
|
|
}
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
|
|
|
/* copying and merging */
|
|
|
|
|
|
vm_t *vm_new_copy(vm_t *source) {
|
|
|
vm_t *target = vm_new_vm();
|
|
|
int vtsN;
|
|
|
int pgcN = get_PGCN(source);
|
|
|
int pgN = (source->state).pgN;
|
|
|
|
|
|
assert(pgcN);
|
|
|
|
|
|
memcpy(target, source, sizeof(vm_t));
|
|
|
|
|
|
/* open a new vtsi handle, because the copy might switch to another VTS */
|
|
|
target->vtsi = NULL;
|
|
|
vtsN = (target->state).vtsN;
|
|
|
if (vtsN > 0) {
|
|
|
(target->state).vtsN = 0;
|
|
|
if (!ifoOpenNewVTSI(target, target->dvd, vtsN))
|
|
|
assert(0);
|
|
|
|
|
|
/* restore pgc pointer into the new vtsi */
|
|
|
if (!set_PGCN(target, pgcN))
|
|
|
assert(0);
|
|
|
(target->state).pgN = pgN;
|
|
|
}
|
|
|
|
|
|
return target;
|
|
|
}
|
|
|
|
|
|
void vm_merge(vm_t *target, vm_t *source) {
|
|
|
if(target->vtsi)
|
|
|
ifoClose(target->vtsi);
|
|
|
memcpy(target, source, sizeof(vm_t));
|
|
|
memset(source, 0, sizeof(vm_t));
|
|
|
}
|
|
|
|
|
|
void vm_free_copy(vm_t *vm) {
|
|
|
if(vm->vtsi)
|
|
|
ifoClose(vm->vtsi);
|
|
|
free(vm);
|
|
|
}
|
|
|
|
|
|
|
|
|
/* regular playback */
|
|
|
|
|
|
void vm_position_get(vm_t *vm, vm_position_t *position) {
|
|
|
position->button = (vm->state).HL_BTNN_REG >> 10;
|
|
|
position->vts = (vm->state).vtsN;
|
|
|
position->domain = (vm->state).domain;
|
|
|
position->spu_channel = (vm->state).SPST_REG;
|
|
|
position->audio_channel = (vm->state).AST_REG;
|
|
|
position->angle_channel = (vm->state).AGL_REG;
|
|
|
position->hop_channel = vm->hop_channel; /* Increases by one on each hop */
|
|
|
position->cell = (vm->state).cellN;
|
|
|
position->cell_restart = (vm->state).cell_restart;
|
|
|
position->cell_start = (vm->state).pgc->cell_playback[(vm->state).cellN - 1].first_sector;
|
|
|
position->still = (vm->state).pgc->cell_playback[(vm->state).cellN - 1].still_time;
|
|
|
position->block = (vm->state).blockN;
|
|
|
|
|
|
/* handle PGC stills at PGC end */
|
|
|
if ((vm->state).cellN == (vm->state).pgc->nr_of_cells)
|
|
|
position->still += (vm->state).pgc->still_time;
|
|
|
/* still already determined */
|
|
|
if (position->still)
|
|
|
return;
|
|
|
/* This is a rough fix for some strange still situations on some strange DVDs.
|
|
|
* There are discs (like the German "Back to the Future" RC2) where the only
|
|
|
* indication of a still is a cell playback time higher than the time the frames
|
|
|
* in this cell actually take to play (like 1 frame with 1 minute playback time).
|
|
|
* On the said BTTF disc, for these cells last_sector and last_vobu_start_sector
|
|
|
* are equal and the cells are very short, so we abuse these conditions to
|
|
|
* detect such discs. I consider these discs broken, so the fix is somewhat
|
|
|
* broken, too. */
|
|
|
if (((vm->state).pgc->cell_playback[(vm->state).cellN - 1].last_sector ==
|
|
|
(vm->state).pgc->cell_playback[(vm->state).cellN - 1].last_vobu_start_sector) &&
|
|
|
((vm->state).pgc->cell_playback[(vm->state).cellN - 1].last_sector -
|
|
|
(vm->state).pgc->cell_playback[(vm->state).cellN - 1].first_sector < 1024)) {
|
|
|
int time;
|
|
|
int size = (vm->state).pgc->cell_playback[(vm->state).cellN - 1].last_sector -
|
|
|
(vm->state).pgc->cell_playback[(vm->state).cellN - 1].first_sector;
|
|
|
time = ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.hour >> 4 ) * 36000;
|
|
|
time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.hour & 0x0f) * 3600;
|
|
|
time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.minute >> 4 ) * 600;
|
|
|
time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.minute & 0x0f) * 60;
|
|
|
time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.second >> 4 ) * 10;
|
|
|
time += ((vm->state).pgc->cell_playback[(vm->state).cellN - 1].playback_time.second & 0x0f) * 1;
|
|
|
if (!time || size / time > 30)
|
|
|
/* datarate is too high, it might be a very short, but regular cell */
|
|
|
return;
|
|
|
if (time > 0xff) time = 0xff;
|
|
|
position->still = time;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void vm_get_next_cell(vm_t *vm) {
|
|
|
process_command(vm, play_Cell_post(vm));
|
|
|
}
|
|
|
|
|
|
|
|
|
/* Jumping */
|
|
|
|
|
|
int vm_jump_pg(vm_t *vm, int pg) {
|
|
|
(vm->state).pgN = pg;
|
|
|
process_command(vm, play_PG(vm));
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
int vm_jump_cell_block(vm_t *vm, int cell, int block) {
|
|
|
(vm->state).cellN = cell;
|
|
|
process_command(vm, play_Cell(vm));
|
|
|
/* play_Cell can jump to a different cell in case of angles */
|
|
|
if ((vm->state).cellN == cell)
|
|
|
(vm->state).blockN = block;
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
int vm_jump_title_part(vm_t *vm, int title, int part) {
|
|
|
link_t link;
|
|
|
|
|
|
if(!set_PTT(vm, title, part))
|
|
|
return 0;
|
|
|
/* Some DVDs do not want us to jump directly into a title and have
|
|
|
* PGC pre commands taking us back to some menu. Since we do not like that,
|
|
|
* we do not execute PGC pre commands that would do a jump. */
|
|
|
/* process_command(vm, play_PGC_PG(vm, (vm->state).pgN)); */
|
|
|
link = play_PGC_PG(vm, (vm->state).pgN);
|
|
|
if (link.command != PlayThis)
|
|
|
/* jump occured -> ignore it and play the PG anyway */
|
|
|
process_command(vm, play_PG(vm));
|
|
|
else
|
|
|
process_command(vm, link);
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
int vm_jump_top_pg(vm_t *vm) {
|
|
|
process_command(vm, play_PG(vm));
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
int vm_jump_next_pg(vm_t *vm) {
|
|
|
if((vm->state).pgN >= (vm->state).pgc->nr_of_programs) {
|
|
|
/* last program -> move to TailPGC */
|
|
|
process_command(vm, play_PGC_post(vm));
|
|
|
return 1;
|
|
|
} else {
|
|
|
vm_jump_pg(vm, (vm->state).pgN + 1);
|
|
|
return 1;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int vm_jump_prev_pg(vm_t *vm) {
|
|
|
if ((vm->state).pgN <= 1) {
|
|
|
/* first program -> move to last program of previous PGC */
|
|
|
if ((vm->state).pgc->prev_pgc_nr && set_PGCN(vm, (vm->state).pgc->prev_pgc_nr)) {
|
|
|
process_command(vm, play_PGC(vm));
|
|
|
vm_jump_pg(vm, (vm->state).pgc->nr_of_programs);
|
|
|
return 1;
|
|
|
}
|
|
|
return 0;
|
|
|
} else {
|
|
|
vm_jump_pg(vm, (vm->state).pgN - 1);
|
|
|
return 1;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int vm_jump_up(vm_t *vm) {
|
|
|
if((vm->state).pgc->goup_pgc_nr && set_PGCN(vm, (vm->state).pgc->goup_pgc_nr)) {
|
|
|
process_command(vm, play_PGC(vm));
|
|
|
return 1;
|
|
|
}
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
int vm_jump_menu(vm_t *vm, DVDMenuID_t menuid) {
|
|
|
domain_t old_domain = (vm->state).domain;
|
|
|
|
|
|
switch ((vm->state).domain) {
|
|
|
case VTS_DOMAIN:
|
|
|
set_RSMinfo(vm, 0, (vm->state).blockN);
|
|
|
/* FALL THROUGH */
|
|
|
case VTSM_DOMAIN:
|
|
|
case VMGM_DOMAIN:
|
|
|
switch(menuid) {
|
|
|
case DVD_MENU_Title:
|
|
|
case DVD_MENU_Escape:
|
|
|
(vm->state).domain = VMGM_DOMAIN;
|
|
|
break;
|
|
|
case DVD_MENU_Root:
|
|
|
case DVD_MENU_Subpicture:
|
|
|
case DVD_MENU_Audio:
|
|
|
case DVD_MENU_Angle:
|
|
|
case DVD_MENU_Part:
|
|
|
(vm->state).domain = VTSM_DOMAIN;
|
|
|
break;
|
|
|
}
|
|
|
if(get_PGCIT(vm) && set_MENU(vm, menuid)) {
|
|
|
process_command(vm, play_PGC(vm));
|
|
|
return 1; /* Jump */
|
|
|
} else {
|
|
|
(vm->state).domain = old_domain;
|
|
|
}
|
|
|
break;
|
|
|
case FP_DOMAIN: /* FIXME XXX $$$ What should we do here? */
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
int vm_jump_resume(vm_t *vm) {
|
|
|
link_t link_values = { LinkRSM, 0, 0, 0 };
|
|
|
|
|
|
if (!(vm->state).rsm_vtsN) /* Do we have resume info? */
|
|
|
return 0;
|
|
|
if (!process_command(vm, link_values))
|
|
|
return 0;
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
int vm_exec_cmd(vm_t *vm, vm_cmd_t *cmd) {
|
|
|
link_t link_values;
|
|
|
|
|
|
if(vmEval_CMD(cmd, 1, &(vm->state).registers, &link_values))
|
|
|
return process_command(vm, link_values);
|
|
|
else
|
|
|
return 0; /* It updated some state thats all... */
|
|
|
}
|
|
|
|
|
|
|
|
|
/* getting information */
|
|
|
|
|
|
int vm_get_current_menu(vm_t *vm, int *menuid) {
|
|
|
pgcit_t* pgcit;
|
|
|
int pgcn;
|
|
|
pgcn = (vm->state).pgcN;
|
|
|
pgcit = get_PGCIT(vm);
|
|
|
*menuid = pgcit->pgci_srp[pgcn - 1].entry_id & 0xf ;
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
int vm_get_current_title_part(vm_t *vm, int *title_result, int *part_result) {
|
|
|
vts_ptt_srpt_t *vts_ptt_srpt;
|
|
|
int title, part = 0, vts_ttn;
|
|
|
int found;
|
|
|
int16_t pgcN, pgN;
|
|
|
|
|
|
vts_ptt_srpt = vm->vtsi->vts_ptt_srpt;
|
|
|
pgcN = get_PGCN(vm);
|
|
|
pgN = vm->state.pgN;
|
|
|
|
|
|
found = 0;
|
|
|
for (vts_ttn = 0; (vts_ttn < vts_ptt_srpt->nr_of_srpts) && !found; vts_ttn++) {
|
|
|
for (part = 0; (part < vts_ptt_srpt->title[vts_ttn].nr_of_ptts) && !found; part++) {
|
|
|
if (vts_ptt_srpt->title[vts_ttn].ptt[part].pgcn == pgcN) {
|
|
|
if (vts_ptt_srpt->title[vts_ttn].ptt[part].pgn == pgN) {
|
|
|
found = 1;
|
|
|
break;
|
|
|
}
|
|
|
if (part > 0 && vts_ptt_srpt->title[vts_ttn].ptt[part].pgn > pgN &&
|
|
|
vts_ptt_srpt->title[vts_ttn].ptt[part - 1].pgn < pgN) {
|
|
|
part--;
|
|
|
found = 1;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
if (found) break;
|
|
|
}
|
|
|
vts_ttn++;
|
|
|
part++;
|
|
|
|
|
|
if (!found) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: chapter NOT FOUND!\n");
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
title = get_TT(vm, vm->state.vtsN, vts_ttn);
|
|
|
|
|
|
#ifdef TRACE
|
|
|
if (title) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: ************ this chapter FOUND!\n");
|
|
|
fprintf(MSG_OUT, "libdvdnav: VTS_PTT_SRPT - Title %3i part %3i: PGC: %3i PG: %3i\n",
|
|
|
title, part,
|
|
|
vts_ptt_srpt->title[vts_ttn-1].ptt[part-1].pgcn ,
|
|
|
vts_ptt_srpt->title[vts_ttn-1].ptt[part-1].pgn );
|
|
|
}
|
|
|
#endif
|
|
|
*title_result = title;
|
|
|
*part_result = part;
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
/* Return the substream id for 'logical' audio stream audioN.
|
|
|
* 0 <= audioN < 8
|
|
|
*/
|
|
|
int vm_get_audio_stream(vm_t *vm, int audioN) {
|
|
|
int streamN = -1;
|
|
|
|
|
|
if((vm->state).domain != VTS_DOMAIN)
|
|
|
audioN = 0;
|
|
|
|
|
|
if(audioN < 8) {
|
|
|
/* Is there any control info for this logical stream */
|
|
|
if((vm->state).pgc->audio_control[audioN] & (1<<15)) {
|
|
|
streamN = ((vm->state).pgc->audio_control[audioN] >> 8) & 0x07;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if((vm->state).domain != VTS_DOMAIN && streamN == -1)
|
|
|
streamN = 0;
|
|
|
|
|
|
/* FIXME: Should also check in vtsi/vmgi status what kind of stream
|
|
|
* it is (ac3/lpcm/dts/sdds...) to find the right (sub)stream id */
|
|
|
return streamN;
|
|
|
}
|
|
|
|
|
|
/* Return the substream id for 'logical' subpicture stream subpN and given mode.
|
|
|
* 0 <= subpN < 32
|
|
|
* mode == 0 - widescreen
|
|
|
* mode == 1 - letterbox
|
|
|
* mode == 2 - pan&scan
|
|
|
*/
|
|
|
int vm_get_subp_stream(vm_t *vm, int subpN, int mode) {
|
|
|
int streamN = -1;
|
|
|
int source_aspect = vm_get_video_aspect(vm);
|
|
|
|
|
|
if((vm->state).domain != VTS_DOMAIN)
|
|
|
subpN = 0;
|
|
|
|
|
|
if(subpN < 32) { /* a valid logical stream */
|
|
|
/* Is this logical stream present */
|
|
|
if((vm->state).pgc->subp_control[subpN] & (1<<31)) {
|
|
|
if(source_aspect == 0) /* 4:3 */
|
|
|
streamN = ((vm->state).pgc->subp_control[subpN] >> 24) & 0x1f;
|
|
|
if(source_aspect == 3) /* 16:9 */
|
|
|
switch (mode) {
|
|
|
case 0:
|
|
|
streamN = ((vm->state).pgc->subp_control[subpN] >> 16) & 0x1f;
|
|
|
break;
|
|
|
case 1:
|
|
|
streamN = ((vm->state).pgc->subp_control[subpN] >> 8) & 0x1f;
|
|
|
break;
|
|
|
case 2:
|
|
|
streamN = (vm->state).pgc->subp_control[subpN] & 0x1f;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if((vm->state).domain != VTS_DOMAIN && streamN == -1)
|
|
|
streamN = 0;
|
|
|
|
|
|
/* FIXME: Should also check in vtsi/vmgi status what kind of stream it is. */
|
|
|
return streamN;
|
|
|
}
|
|
|
|
|
|
int vm_get_audio_active_stream(vm_t *vm) {
|
|
|
int audioN;
|
|
|
int streamN;
|
|
|
audioN = (vm->state).AST_REG ;
|
|
|
streamN = vm_get_audio_stream(vm, audioN);
|
|
|
|
|
|
/* If no such stream, then select the first one that exists. */
|
|
|
if(streamN == -1) {
|
|
|
for(audioN = 0; audioN < 8; audioN++) {
|
|
|
if((vm->state).pgc->audio_control[audioN] & (1<<15)) {
|
|
|
if ((streamN = vm_get_audio_stream(vm, audioN)) >= 0)
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return streamN;
|
|
|
}
|
|
|
|
|
|
int vm_get_subp_active_stream(vm_t *vm, int mode) {
|
|
|
int subpN;
|
|
|
int streamN;
|
|
|
subpN = (vm->state).SPST_REG & ~0x40;
|
|
|
streamN = vm_get_subp_stream(vm, subpN, mode);
|
|
|
|
|
|
/* If no such stream, then select the first one that exists. */
|
|
|
if(streamN == -1) {
|
|
|
for(subpN = 0; subpN < 32; subpN++) {
|
|
|
if((vm->state).pgc->subp_control[subpN] & (1<<31)) {
|
|
|
if ((streamN = vm_get_subp_stream(vm, subpN, mode)) >= 0)
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if((vm->state).domain == VTS_DOMAIN && !((vm->state).SPST_REG & 0x40))
|
|
|
/* Bit 7 set means hide, and only let Forced display show */
|
|
|
return (streamN | 0x80);
|
|
|
else
|
|
|
return streamN;
|
|
|
}
|
|
|
|
|
|
void vm_get_angle_info(vm_t *vm, int *current, int *num_avail) {
|
|
|
*num_avail = 1;
|
|
|
*current = 1;
|
|
|
|
|
|
if((vm->state).domain == VTS_DOMAIN) {
|
|
|
title_info_t *title;
|
|
|
/* TTN_REG does not allways point to the correct title.. */
|
|
|
if((vm->state).TTN_REG > vm->vmgi->tt_srpt->nr_of_srpts)
|
|
|
return;
|
|
|
title = &vm->vmgi->tt_srpt->title[(vm->state).TTN_REG - 1];
|
|
|
if(title->title_set_nr != (vm->state).vtsN ||
|
|
|
title->vts_ttn != (vm->state).VTS_TTN_REG)
|
|
|
return;
|
|
|
*num_avail = title->nr_of_angles;
|
|
|
*current = (vm->state).AGL_REG;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#if 0
|
|
|
/* currently unused */
|
|
|
void vm_get_audio_info(vm_t *vm, int *current, int *num_avail) {
|
|
|
switch ((vm->state).domain) {
|
|
|
case VTS_DOMAIN:
|
|
|
*num_avail = vm->vtsi->vtsi_mat->nr_of_vts_audio_streams;
|
|
|
*current = (vm->state).AST_REG;
|
|
|
break;
|
|
|
case VTSM_DOMAIN:
|
|
|
*num_avail = vm->vtsi->vtsi_mat->nr_of_vtsm_audio_streams; /* 1 */
|
|
|
*current = 1;
|
|
|
break;
|
|
|
case VMGM_DOMAIN:
|
|
|
case FP_DOMAIN:
|
|
|
*num_avail = vm->vmgi->vmgi_mat->nr_of_vmgm_audio_streams; /* 1 */
|
|
|
*current = 1;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* currently unused */
|
|
|
void vm_get_subp_info(vm_t *vm, int *current, int *num_avail) {
|
|
|
switch ((vm->state).domain) {
|
|
|
case VTS_DOMAIN:
|
|
|
*num_avail = vm->vtsi->vtsi_mat->nr_of_vts_subp_streams;
|
|
|
*current = (vm->state).SPST_REG;
|
|
|
break;
|
|
|
case VTSM_DOMAIN:
|
|
|
*num_avail = vm->vtsi->vtsi_mat->nr_of_vtsm_subp_streams; /* 1 */
|
|
|
*current = 0x41;
|
|
|
break;
|
|
|
case VMGM_DOMAIN:
|
|
|
case FP_DOMAIN:
|
|
|
*num_avail = vm->vmgi->vmgi_mat->nr_of_vmgm_subp_streams; /* 1 */
|
|
|
*current = 0x41;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* currently unused */
|
|
|
void vm_get_video_res(vm_t *vm, int *width, int *height) {
|
|
|
video_attr_t attr = vm_get_video_attr(vm);
|
|
|
|
|
|
if(attr.video_format != 0)
|
|
|
*height = 576;
|
|
|
else
|
|
|
*height = 480;
|
|
|
switch(attr.picture_size) {
|
|
|
case 0:
|
|
|
*width = 720;
|
|
|
break;
|
|
|
case 1:
|
|
|
*width = 704;
|
|
|
break;
|
|
|
case 2:
|
|
|
*width = 352;
|
|
|
break;
|
|
|
case 3:
|
|
|
*width = 352;
|
|
|
*height /= 2;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
int vm_get_video_aspect(vm_t *vm) {
|
|
|
int aspect = vm_get_video_attr(vm).display_aspect_ratio;
|
|
|
|
|
|
assert(aspect == 0 || aspect == 3);
|
|
|
(vm->state).registers.SPRM[14] &= ~(0x3 << 10);
|
|
|
(vm->state).registers.SPRM[14] |= aspect << 10;
|
|
|
|
|
|
return aspect;
|
|
|
}
|
|
|
|
|
|
int vm_get_video_scale_permission(vm_t *vm) {
|
|
|
return vm_get_video_attr(vm).permitted_df;
|
|
|
}
|
|
|
|
|
|
video_attr_t vm_get_video_attr(vm_t *vm) {
|
|
|
switch ((vm->state).domain) {
|
|
|
case VTS_DOMAIN:
|
|
|
return vm->vtsi->vtsi_mat->vts_video_attr;
|
|
|
case VTSM_DOMAIN:
|
|
|
return vm->vtsi->vtsi_mat->vtsm_video_attr;
|
|
|
case VMGM_DOMAIN:
|
|
|
case FP_DOMAIN:
|
|
|
return vm->vmgi->vmgi_mat->vmgm_video_attr;
|
|
|
default:
|
|
|
abort();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
audio_attr_t vm_get_audio_attr(vm_t *vm, int streamN) {
|
|
|
switch ((vm->state).domain) {
|
|
|
case VTS_DOMAIN:
|
|
|
return vm->vtsi->vtsi_mat->vts_audio_attr[streamN];
|
|
|
case VTSM_DOMAIN:
|
|
|
return vm->vtsi->vtsi_mat->vtsm_audio_attr;
|
|
|
case VMGM_DOMAIN:
|
|
|
case FP_DOMAIN:
|
|
|
return vm->vmgi->vmgi_mat->vmgm_audio_attr;
|
|
|
default:
|
|
|
abort();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
subp_attr_t vm_get_subp_attr(vm_t *vm, int streamN) {
|
|
|
switch ((vm->state).domain) {
|
|
|
case VTS_DOMAIN:
|
|
|
return vm->vtsi->vtsi_mat->vts_subp_attr[streamN];
|
|
|
case VTSM_DOMAIN:
|
|
|
return vm->vtsi->vtsi_mat->vtsm_subp_attr;
|
|
|
case VMGM_DOMAIN:
|
|
|
case FP_DOMAIN:
|
|
|
return vm->vmgi->vmgi_mat->vmgm_subp_attr;
|
|
|
default:
|
|
|
abort();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/* Playback control */
|
|
|
|
|
|
static link_t play_PGC(vm_t *vm) {
|
|
|
link_t link_values;
|
|
|
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: play_PGC:");
|
|
|
if((vm->state).domain != FP_DOMAIN) {
|
|
|
fprintf(MSG_OUT, " (vm->state).pgcN (%i)\n", get_PGCN(vm));
|
|
|
} else {
|
|
|
fprintf(MSG_OUT, " first_play_pgc\n");
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
/* This must be set before the pre-commands are executed because they
|
|
|
* might contain a CallSS that will save resume state */
|
|
|
|
|
|
/* FIXME: This may be only a temporary fix for something... */
|
|
|
(vm->state).pgN = 1;
|
|
|
(vm->state).cellN = 0;
|
|
|
(vm->state).blockN = 0;
|
|
|
|
|
|
/* eval -> updates the state and returns either
|
|
|
- some kind of jump (Jump(TT/SS/VTS_TTN/CallSS/link C/PG/PGC/PTTN)
|
|
|
- just play video i.e first PG
|
|
|
(This is what happens if you fall of the end of the pre_cmds)
|
|
|
- or an error (are there more cases?) */
|
|
|
if((vm->state).pgc->command_tbl && (vm->state).pgc->command_tbl->nr_of_pre) {
|
|
|
if(vmEval_CMD((vm->state).pgc->command_tbl->pre_cmds,
|
|
|
(vm->state).pgc->command_tbl->nr_of_pre,
|
|
|
&(vm->state).registers, &link_values)) {
|
|
|
/* link_values contains the 'jump' return value */
|
|
|
return link_values;
|
|
|
} else {
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: PGC pre commands didn't do a Jump, Link or Call\n");
|
|
|
#endif
|
|
|
}
|
|
|
}
|
|
|
return play_PG(vm);
|
|
|
}
|
|
|
|
|
|
static link_t play_PGC_PG(vm_t *vm, int pgN) {
|
|
|
link_t link_values;
|
|
|
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: play_PGC_PG:");
|
|
|
if((vm->state).domain != FP_DOMAIN) {
|
|
|
fprintf(MSG_OUT, " (vm->state).pgcN (%i)\n", get_PGCN(vm));
|
|
|
} else {
|
|
|
fprintf(MSG_OUT, " first_play_pgc\n");
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
/* This must be set before the pre-commands are executed because they
|
|
|
* might contain a CallSS that will save resume state */
|
|
|
|
|
|
/* FIXME: This may be only a temporary fix for something... */
|
|
|
(vm->state).pgN = pgN;
|
|
|
(vm->state).cellN = 0;
|
|
|
(vm->state).blockN = 0;
|
|
|
|
|
|
/* eval -> updates the state and returns either
|
|
|
- some kind of jump (Jump(TT/SS/VTS_TTN/CallSS/link C/PG/PGC/PTTN)
|
|
|
- just play video i.e first PG
|
|
|
(This is what happens if you fall of the end of the pre_cmds)
|
|
|
- or an error (are there more cases?) */
|
|
|
if((vm->state).pgc->command_tbl && (vm->state).pgc->command_tbl->nr_of_pre) {
|
|
|
if(vmEval_CMD((vm->state).pgc->command_tbl->pre_cmds,
|
|
|
(vm->state).pgc->command_tbl->nr_of_pre,
|
|
|
&(vm->state).registers, &link_values)) {
|
|
|
/* link_values contains the 'jump' return value */
|
|
|
return link_values;
|
|
|
} else {
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: PGC pre commands didn't do a Jump, Link or Call\n");
|
|
|
#endif
|
|
|
}
|
|
|
}
|
|
|
return play_PG(vm);
|
|
|
}
|
|
|
|
|
|
static link_t play_PGC_post(vm_t *vm) {
|
|
|
link_t link_values;
|
|
|
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: play_PGC_post:\n");
|
|
|
#endif
|
|
|
|
|
|
/* eval -> updates the state and returns either
|
|
|
- some kind of jump (Jump(TT/SS/VTS_TTN/CallSS/link C/PG/PGC/PTTN)
|
|
|
- just go to next PGC
|
|
|
(This is what happens if you fall of the end of the post_cmds)
|
|
|
- or an error (are there more cases?) */
|
|
|
if((vm->state).pgc->command_tbl && (vm->state).pgc->command_tbl->nr_of_post &&
|
|
|
vmEval_CMD((vm->state).pgc->command_tbl->post_cmds,
|
|
|
(vm->state).pgc->command_tbl->nr_of_post,
|
|
|
&(vm->state).registers, &link_values)) {
|
|
|
return link_values;
|
|
|
}
|
|
|
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: ** Fell of the end of the pgc, continuing in NextPGC\n");
|
|
|
#endif
|
|
|
/* Should end up in the STOP_DOMAIN if next_pgc is 0. */
|
|
|
if(!set_PGCN(vm, (vm->state).pgc->next_pgc_nr)) {
|
|
|
link_values.command = Exit;
|
|
|
return link_values;
|
|
|
}
|
|
|
return play_PGC(vm);
|
|
|
}
|
|
|
|
|
|
static link_t play_PG(vm_t *vm) {
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: play_PG: (vm->state).pgN (%i)\n", (vm->state).pgN);
|
|
|
#endif
|
|
|
|
|
|
assert((vm->state).pgN > 0);
|
|
|
if((vm->state).pgN > (vm->state).pgc->nr_of_programs) {
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: play_PG: (vm->state).pgN (%i) > pgc->nr_of_programs (%i)\n",
|
|
|
(vm->state).pgN, (vm->state).pgc->nr_of_programs );
|
|
|
#endif
|
|
|
assert((vm->state).pgN == (vm->state).pgc->nr_of_programs + 1);
|
|
|
return play_PGC_post(vm);
|
|
|
}
|
|
|
|
|
|
(vm->state).cellN = (vm->state).pgc->program_map[(vm->state).pgN - 1];
|
|
|
|
|
|
return play_Cell(vm);
|
|
|
}
|
|
|
|
|
|
static link_t play_Cell(vm_t *vm) {
|
|
|
static const link_t play_this = {PlayThis, /* Block in Cell */ 0, 0, 0};
|
|
|
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: play_Cell: (vm->state).cellN (%i)\n", (vm->state).cellN);
|
|
|
#endif
|
|
|
|
|
|
assert((vm->state).cellN > 0);
|
|
|
if((vm->state).cellN > (vm->state).pgc->nr_of_cells) {
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: (vm->state).cellN (%i) > pgc->nr_of_cells (%i)\n",
|
|
|
(vm->state).cellN, (vm->state).pgc->nr_of_cells );
|
|
|
#endif
|
|
|
assert((vm->state).cellN == (vm->state).pgc->nr_of_cells + 1);
|
|
|
return play_PGC_post(vm);
|
|
|
}
|
|
|
|
|
|
/* Multi angle/Interleaved */
|
|
|
switch((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode) {
|
|
|
case 0: /* Normal */
|
|
|
assert((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type == 0);
|
|
|
break;
|
|
|
case 1: /* The first cell in the block */
|
|
|
switch((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type) {
|
|
|
case 0: /* Not part of a block */
|
|
|
assert(0);
|
|
|
break;
|
|
|
case 1: /* Angle block */
|
|
|
/* Loop and check each cell instead? So we don't get outside the block? */
|
|
|
(vm->state).cellN += (vm->state).AGL_REG - 1;
|
|
|
#ifdef STRICT
|
|
|
assert((vm->state).cellN <= (vm->state).pgc->nr_of_cells);
|
|
|
assert((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode != 0);
|
|
|
assert((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type == 1);
|
|
|
#else
|
|
|
if (!((vm->state).cellN <= (vm->state).pgc->nr_of_cells) ||
|
|
|
!((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode != 0) ||
|
|
|
!((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type == 1)) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: Invalid angle block\n");
|
|
|
(vm->state).cellN -= (vm->state).AGL_REG - 1;
|
|
|
}
|
|
|
#endif
|
|
|
break;
|
|
|
case 2: /* ?? */
|
|
|
case 3: /* ?? */
|
|
|
default:
|
|
|
fprintf(MSG_OUT, "libdvdnav: Invalid? Cell block_mode (%d), block_type (%d)\n",
|
|
|
(vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode,
|
|
|
(vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type);
|
|
|
assert(0);
|
|
|
}
|
|
|
break;
|
|
|
case 2: /* Cell in the block */
|
|
|
case 3: /* Last cell in the block */
|
|
|
/* These might perhaps happen for RSM or LinkC commands? */
|
|
|
default:
|
|
|
fprintf(MSG_OUT, "libdvdnav: Cell is in block but did not enter at first cell!\n");
|
|
|
}
|
|
|
|
|
|
/* Updates (vm->state).pgN and PTTN_REG */
|
|
|
if(!set_PGN(vm)) {
|
|
|
/* Should not happen */
|
|
|
assert(0);
|
|
|
return play_PGC_post(vm);
|
|
|
}
|
|
|
(vm->state).cell_restart++;
|
|
|
(vm->state).blockN = 0;
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: Cell should restart here\n");
|
|
|
#endif
|
|
|
return play_this;
|
|
|
}
|
|
|
|
|
|
static link_t play_Cell_post(vm_t *vm) {
|
|
|
cell_playback_t *cell;
|
|
|
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: play_Cell_post: (vm->state).cellN (%i)\n", (vm->state).cellN);
|
|
|
#endif
|
|
|
|
|
|
cell = &(vm->state).pgc->cell_playback[(vm->state).cellN - 1];
|
|
|
|
|
|
/* Still time is already taken care of before we get called. */
|
|
|
|
|
|
/* Deal with a Cell command, if any */
|
|
|
if(cell->cell_cmd_nr != 0) {
|
|
|
link_t link_values;
|
|
|
|
|
|
/* These asserts are now not needed.
|
|
|
* Some DVDs have no cell commands listed in the PGC,
|
|
|
* but the Cell itself points to a cell command that does not exist.
|
|
|
* For this situation, just ignore the cell command and continue.
|
|
|
*
|
|
|
* assert((vm->state).pgc->command_tbl != NULL);
|
|
|
* assert((vm->state).pgc->command_tbl->nr_of_cell >= cell->cell_cmd_nr);
|
|
|
*/
|
|
|
|
|
|
if ((vm->state).pgc->command_tbl != NULL &&
|
|
|
(vm->state).pgc->command_tbl->nr_of_cell >= cell->cell_cmd_nr) {
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: Cell command present, executing\n");
|
|
|
#endif
|
|
|
if(vmEval_CMD(&(vm->state).pgc->command_tbl->cell_cmds[cell->cell_cmd_nr - 1], 1,
|
|
|
&(vm->state).registers, &link_values)) {
|
|
|
return link_values;
|
|
|
} else {
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: Cell command didn't do a Jump, Link or Call\n");
|
|
|
#endif
|
|
|
}
|
|
|
} else {
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: Invalid Cell command\n");
|
|
|
#endif
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* Where to continue after playing the cell... */
|
|
|
/* Multi angle/Interleaved */
|
|
|
switch((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode) {
|
|
|
case 0: /* Normal */
|
|
|
assert((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type == 0);
|
|
|
(vm->state).cellN++;
|
|
|
break;
|
|
|
case 1: /* The first cell in the block */
|
|
|
case 2: /* A cell in the block */
|
|
|
case 3: /* The last cell in the block */
|
|
|
default:
|
|
|
switch((vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type) {
|
|
|
case 0: /* Not part of a block */
|
|
|
assert(0);
|
|
|
break;
|
|
|
case 1: /* Angle block */
|
|
|
/* Skip the 'other' angles */
|
|
|
(vm->state).cellN++;
|
|
|
while((vm->state).cellN <= (vm->state).pgc->nr_of_cells &&
|
|
|
(vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode >= 2) {
|
|
|
(vm->state).cellN++;
|
|
|
}
|
|
|
break;
|
|
|
case 2: /* ?? */
|
|
|
case 3: /* ?? */
|
|
|
default:
|
|
|
fprintf(MSG_OUT, "libdvdnav: Invalid? Cell block_mode (%d), block_type (%d)\n",
|
|
|
(vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_mode,
|
|
|
(vm->state).pgc->cell_playback[(vm->state).cellN - 1].block_type);
|
|
|
assert(0);
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
/* Figure out the correct pgN for the new cell */
|
|
|
if(!set_PGN(vm)) {
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: last cell in this PGC\n");
|
|
|
#endif
|
|
|
return play_PGC_post(vm);
|
|
|
}
|
|
|
return play_Cell(vm);
|
|
|
}
|
|
|
|
|
|
|
|
|
/* link processing */
|
|
|
|
|
|
static int process_command(vm_t *vm, link_t link_values) {
|
|
|
|
|
|
while(link_values.command != PlayThis) {
|
|
|
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: Before printout starts:\n");
|
|
|
vm_print_link(link_values);
|
|
|
fprintf(MSG_OUT, "libdvdnav: Link values %i %i %i %i\n", link_values.command,
|
|
|
link_values.data1, link_values.data2, link_values.data3);
|
|
|
vm_print_current_domain_state(vm);
|
|
|
fprintf(MSG_OUT, "libdvdnav: Before printout ends.\n");
|
|
|
#endif
|
|
|
|
|
|
switch(link_values.command) {
|
|
|
case LinkNoLink:
|
|
|
/* BUTTON number:data1 */
|
|
|
if(link_values.data1 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data1 << 10;
|
|
|
return 0; /* no actual jump */
|
|
|
|
|
|
case LinkTopC:
|
|
|
/* Restart playing from the beginning of the current Cell. */
|
|
|
/* BUTTON number:data1 */
|
|
|
if(link_values.data1 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data1 << 10;
|
|
|
link_values = play_Cell(vm);
|
|
|
break;
|
|
|
case LinkNextC:
|
|
|
/* Link to Next Cell */
|
|
|
/* BUTTON number:data1 */
|
|
|
if(link_values.data1 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data1 << 10;
|
|
|
(vm->state).cellN += 1;
|
|
|
link_values = play_Cell(vm);
|
|
|
break;
|
|
|
case LinkPrevC:
|
|
|
/* Link to Previous Cell */
|
|
|
/* BUTTON number:data1 */
|
|
|
if(link_values.data1 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data1 << 10;
|
|
|
assert((vm->state).cellN > 1);
|
|
|
(vm->state).cellN -= 1;
|
|
|
link_values = play_Cell(vm);
|
|
|
break;
|
|
|
|
|
|
case LinkTopPG:
|
|
|
/* Link to Top of current Program */
|
|
|
/* BUTTON number:data1 */
|
|
|
if(link_values.data1 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data1 << 10;
|
|
|
link_values = play_PG(vm);
|
|
|
break;
|
|
|
case LinkNextPG:
|
|
|
/* Link to Next Program */
|
|
|
/* BUTTON number:data1 */
|
|
|
if(link_values.data1 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data1 << 10;
|
|
|
(vm->state).pgN += 1;
|
|
|
link_values = play_PG(vm);
|
|
|
break;
|
|
|
case LinkPrevPG:
|
|
|
/* Link to Previous Program */
|
|
|
/* BUTTON number:data1 */
|
|
|
if(link_values.data1 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data1 << 10;
|
|
|
assert((vm->state).pgN > 1);
|
|
|
(vm->state).pgN -= 1;
|
|
|
link_values = play_PG(vm);
|
|
|
break;
|
|
|
|
|
|
case LinkTopPGC:
|
|
|
/* Restart playing from beginning of current Program Chain */
|
|
|
/* BUTTON number:data1 */
|
|
|
if(link_values.data1 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data1 << 10;
|
|
|
link_values = play_PGC(vm);
|
|
|
break;
|
|
|
case LinkNextPGC:
|
|
|
/* Link to Next Program Chain */
|
|
|
/* BUTTON number:data1 */
|
|
|
if(link_values.data1 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data1 << 10;
|
|
|
assert((vm->state).pgc->next_pgc_nr != 0);
|
|
|
if(set_PGCN(vm, (vm->state).pgc->next_pgc_nr))
|
|
|
link_values = play_PGC(vm);
|
|
|
else
|
|
|
link_values.command = Exit;
|
|
|
break;
|
|
|
case LinkPrevPGC:
|
|
|
/* Link to Previous Program Chain */
|
|
|
/* BUTTON number:data1 */
|
|
|
if(link_values.data1 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data1 << 10;
|
|
|
assert((vm->state).pgc->prev_pgc_nr != 0);
|
|
|
if(set_PGCN(vm, (vm->state).pgc->prev_pgc_nr))
|
|
|
link_values = play_PGC(vm);
|
|
|
else
|
|
|
link_values.command = Exit;
|
|
|
break;
|
|
|
case LinkGoUpPGC:
|
|
|
/* Link to GoUp Program Chain */
|
|
|
/* BUTTON number:data1 */
|
|
|
if(link_values.data1 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data1 << 10;
|
|
|
assert((vm->state).pgc->goup_pgc_nr != 0);
|
|
|
if(set_PGCN(vm, (vm->state).pgc->goup_pgc_nr))
|
|
|
link_values = play_PGC(vm);
|
|
|
else
|
|
|
link_values.command = Exit;
|
|
|
break;
|
|
|
case LinkTailPGC:
|
|
|
/* Link to Tail of Program Chain */
|
|
|
/* BUTTON number:data1 */
|
|
|
if(link_values.data1 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data1 << 10;
|
|
|
link_values = play_PGC_post(vm);
|
|
|
break;
|
|
|
|
|
|
case LinkRSM:
|
|
|
{
|
|
|
/* Link to Resume point */
|
|
|
int i;
|
|
|
|
|
|
/* Check and see if there is any rsm info!! */
|
|
|
if (!(vm->state).rsm_vtsN) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: trying to resume without any resume info set\n");
|
|
|
link_values.command = Exit;
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
(vm->state).domain = VTS_DOMAIN;
|
|
|
if (!ifoOpenNewVTSI(vm, vm->dvd, (vm->state).rsm_vtsN))
|
|
|
assert(0);
|
|
|
set_PGCN(vm, (vm->state).rsm_pgcN);
|
|
|
|
|
|
/* These should never be set in SystemSpace and/or MenuSpace */
|
|
|
/* (vm->state).TTN_REG = rsm_tt; ?? */
|
|
|
/* (vm->state).TT_PGCN_REG = (vm->state).rsm_pgcN; ?? */
|
|
|
for(i = 0; i < 5; i++) {
|
|
|
(vm->state).registers.SPRM[4 + i] = (vm->state).rsm_regs[i];
|
|
|
}
|
|
|
|
|
|
if(link_values.data1 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data1 << 10;
|
|
|
|
|
|
if((vm->state).rsm_cellN == 0) {
|
|
|
assert((vm->state).cellN); /* Checking if this ever happens */
|
|
|
(vm->state).pgN = 1;
|
|
|
link_values = play_PG(vm);
|
|
|
} else {
|
|
|
/* (vm->state).pgN = ?? this gets the right value in set_PGN() below */
|
|
|
(vm->state).cellN = (vm->state).rsm_cellN;
|
|
|
link_values.command = PlayThis;
|
|
|
link_values.data1 = (vm->state).rsm_blockN & 0xffff;
|
|
|
link_values.data2 = (vm->state).rsm_blockN >> 16;
|
|
|
if(!set_PGN(vm)) {
|
|
|
/* Were at the end of the PGC, should not happen for a RSM */
|
|
|
assert(0);
|
|
|
link_values.command = LinkTailPGC;
|
|
|
link_values.data1 = 0; /* No button */
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
case LinkPGCN:
|
|
|
/* Link to Program Chain Number:data1 */
|
|
|
if(!set_PGCN(vm, link_values.data1))
|
|
|
assert(0);
|
|
|
link_values = play_PGC(vm);
|
|
|
break;
|
|
|
case LinkPTTN:
|
|
|
/* Link to Part of current Title Number:data1 */
|
|
|
/* BUTTON number:data2 */
|
|
|
/* PGC Pre-Commands are not executed */
|
|
|
assert((vm->state).domain == VTS_DOMAIN);
|
|
|
if(link_values.data2 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data2 << 10;
|
|
|
if(!set_VTS_PTT(vm, (vm->state).vtsN, (vm->state).VTS_TTN_REG, link_values.data1))
|
|
|
assert(0);
|
|
|
link_values = play_PG(vm);
|
|
|
break;
|
|
|
case LinkPGN:
|
|
|
/* Link to Program Number:data1 */
|
|
|
/* BUTTON number:data2 */
|
|
|
if(link_values.data2 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data2 << 10;
|
|
|
/* Update any other state, PTTN perhaps? */
|
|
|
(vm->state).pgN = link_values.data1;
|
|
|
link_values = play_PG(vm);
|
|
|
break;
|
|
|
case LinkCN:
|
|
|
/* Link to Cell Number:data1 */
|
|
|
/* BUTTON number:data2 */
|
|
|
if(link_values.data2 != 0)
|
|
|
(vm->state).HL_BTNN_REG = link_values.data2 << 10;
|
|
|
/* Update any other state, pgN, PTTN perhaps? */
|
|
|
(vm->state).cellN = link_values.data1;
|
|
|
link_values = play_Cell(vm);
|
|
|
break;
|
|
|
|
|
|
case Exit:
|
|
|
vm->stopped = 1;
|
|
|
return 0;
|
|
|
|
|
|
case JumpTT:
|
|
|
/* Jump to VTS Title Domain */
|
|
|
/* Only allowed from the First Play domain(PGC) */
|
|
|
/* or the Video Manager domain (VMG) */
|
|
|
/* Stop SPRM9 Timer */
|
|
|
/* Set SPRM1 and SPRM2 */
|
|
|
assert((vm->state).domain == VMGM_DOMAIN || (vm->state).domain == FP_DOMAIN); /* ?? */
|
|
|
if(set_TT(vm, link_values.data1))
|
|
|
link_values = play_PGC(vm);
|
|
|
else
|
|
|
link_values.command = Exit;
|
|
|
break;
|
|
|
case JumpVTS_TT:
|
|
|
/* Jump to Title:data1 in same VTS Title Domain */
|
|
|
/* Only allowed from the VTS Menu Domain(VTSM) */
|
|
|
/* or the Video Title Set Domain(VTS) */
|
|
|
/* Stop SPRM9 Timer */
|
|
|
/* Set SPRM1 and SPRM2 */
|
|
|
assert((vm->state).domain == VTSM_DOMAIN || (vm->state).domain == VTS_DOMAIN); /* ?? */
|
|
|
if(!set_VTS_TT(vm, (vm->state).vtsN, link_values.data1))
|
|
|
assert(0);
|
|
|
link_values = play_PGC(vm);
|
|
|
break;
|
|
|
case JumpVTS_PTT:
|
|
|
/* Jump to Part:data2 of Title:data1 in same VTS Title Domain */
|
|
|
/* Only allowed from the VTS Menu Domain(VTSM) */
|
|
|
/* or the Video Title Set Domain(VTS) */
|
|
|
/* Stop SPRM9 Timer */
|
|
|
/* Set SPRM1 and SPRM2 */
|
|
|
assert((vm->state).domain == VTSM_DOMAIN || (vm->state).domain == VTS_DOMAIN); /* ?? */
|
|
|
if(!set_VTS_PTT(vm, (vm->state).vtsN, link_values.data1, link_values.data2))
|
|
|
assert(0);
|
|
|
link_values = play_PGC_PG(vm, (vm->state).pgN);
|
|
|
break;
|
|
|
|
|
|
case JumpSS_FP:
|
|
|
/* Jump to First Play Domain */
|
|
|
/* Only allowed from the VTS Menu Domain(VTSM) */
|
|
|
/* or the Video Manager domain (VMG) */
|
|
|
/* Stop SPRM9 Timer and any GPRM counters */
|
|
|
assert((vm->state).domain == VMGM_DOMAIN || (vm->state).domain == VTSM_DOMAIN); /* ?? */
|
|
|
if (!set_FP_PGC(vm))
|
|
|
assert(0);
|
|
|
link_values = play_PGC(vm);
|
|
|
break;
|
|
|
case JumpSS_VMGM_MENU:
|
|
|
/* Jump to Video Manger domain - Title Menu:data1 or any PGC in VMG */
|
|
|
/* Allowed from anywhere except the VTS Title domain */
|
|
|
/* Stop SPRM9 Timer and any GPRM counters */
|
|
|
assert((vm->state).domain != VTS_DOMAIN); /* ?? */
|
|
|
(vm->state).domain = VMGM_DOMAIN;
|
|
|
if(!set_MENU(vm, link_values.data1))
|
|
|
assert(0);
|
|
|
link_values = play_PGC(vm);
|
|
|
break;
|
|
|
case JumpSS_VTSM:
|
|
|
/* Jump to a menu in Video Title domain, */
|
|
|
/* or to a Menu is the current VTS */
|
|
|
/* Stop SPRM9 Timer and any GPRM counters */
|
|
|
/* ifoOpenNewVTSI:data1 */
|
|
|
/* VTS_TTN_REG:data2 */
|
|
|
/* get_MENU:data3 */
|
|
|
if(link_values.data1 != 0) {
|
|
|
if (link_values.data1 != (vm->state).vtsN) {
|
|
|
/* the normal case */
|
|
|
assert((vm->state).domain == VMGM_DOMAIN || (vm->state).domain == FP_DOMAIN); /* ?? */
|
|
|
(vm->state).domain = VTSM_DOMAIN;
|
|
|
if (!ifoOpenNewVTSI(vm, vm->dvd, link_values.data1)) /* Also sets (vm->state).vtsN */
|
|
|
assert(0);
|
|
|
} else {
|
|
|
/* This happens on some discs like "Captain Scarlet & the Mysterons" or
|
|
|
* the German RC2 of "Anatomie" in VTSM. */
|
|
|
assert((vm->state).domain == VTSM_DOMAIN ||
|
|
|
(vm->state).domain == VMGM_DOMAIN || (vm->state).domain == FP_DOMAIN); /* ?? */
|
|
|
(vm->state).domain = VTSM_DOMAIN;
|
|
|
}
|
|
|
} else {
|
|
|
/* This happens on 'The Fifth Element' region 2. */
|
|
|
assert((vm->state).domain == VTSM_DOMAIN);
|
|
|
}
|
|
|
/* I don't know what title is supposed to be used for. */
|
|
|
/* Alien or Aliens has this != 1, I think. */
|
|
|
/* assert(link_values.data2 == 1); */
|
|
|
(vm->state).VTS_TTN_REG = link_values.data2;
|
|
|
/* TTN_REG (SPRM4), VTS_TTN_REG (SPRM5), TT_PGCN_REG (SPRM6) are linked, */
|
|
|
/* so if one changes, the others must change to match it. */
|
|
|
(vm->state).TTN_REG = get_TT(vm, (vm->state).vtsN, (vm->state).VTS_TTN_REG);
|
|
|
if(!set_MENU(vm, link_values.data3))
|
|
|
assert(0);
|
|
|
link_values = play_PGC(vm);
|
|
|
break;
|
|
|
case JumpSS_VMGM_PGC:
|
|
|
/* set_PGCN:data1 */
|
|
|
/* Stop SPRM9 Timer and any GPRM counters */
|
|
|
assert((vm->state).domain != VTS_DOMAIN); /* ?? */
|
|
|
(vm->state).domain = VMGM_DOMAIN;
|
|
|
if(!set_PGCN(vm, link_values.data1))
|
|
|
assert(0);
|
|
|
link_values = play_PGC(vm);
|
|
|
break;
|
|
|
|
|
|
case CallSS_FP:
|
|
|
/* set_RSMinfo:data1 */
|
|
|
assert((vm->state).domain == VTS_DOMAIN); /* ?? */
|
|
|
/* Must be called before domain is changed */
|
|
|
set_RSMinfo(vm, link_values.data1, /* We dont have block info */ 0);
|
|
|
set_FP_PGC(vm);
|
|
|
link_values = play_PGC(vm);
|
|
|
break;
|
|
|
case CallSS_VMGM_MENU:
|
|
|
/* set_MENU:data1 */
|
|
|
/* set_RSMinfo:data2 */
|
|
|
assert((vm->state).domain == VTS_DOMAIN); /* ?? */
|
|
|
/* Must be called before domain is changed */
|
|
|
set_RSMinfo(vm, link_values.data2, /* We dont have block info */ 0);
|
|
|
(vm->state).domain = VMGM_DOMAIN;
|
|
|
if(!set_MENU(vm, link_values.data1))
|
|
|
assert(0);
|
|
|
link_values = play_PGC(vm);
|
|
|
break;
|
|
|
case CallSS_VTSM:
|
|
|
/* set_MENU:data1 */
|
|
|
/* set_RSMinfo:data2 */
|
|
|
assert((vm->state).domain == VTS_DOMAIN); /* ?? */
|
|
|
/* Must be called before domain is changed */
|
|
|
set_RSMinfo(vm, link_values.data2, /* We dont have block info */ 0);
|
|
|
(vm->state).domain = VTSM_DOMAIN;
|
|
|
if(!set_MENU(vm, link_values.data1))
|
|
|
assert(0);
|
|
|
link_values = play_PGC(vm);
|
|
|
break;
|
|
|
case CallSS_VMGM_PGC:
|
|
|
/* set_PGC:data1 */
|
|
|
/* set_RSMinfo:data2 */
|
|
|
assert((vm->state).domain == VTS_DOMAIN); /* ?? */
|
|
|
/* Must be called before domain is changed */
|
|
|
set_RSMinfo(vm, link_values.data2, /* We dont have block info */ 0);
|
|
|
(vm->state).domain = VMGM_DOMAIN;
|
|
|
if(!set_PGCN(vm, link_values.data1))
|
|
|
assert(0);
|
|
|
link_values = play_PGC(vm);
|
|
|
break;
|
|
|
case PlayThis:
|
|
|
/* Should never happen. */
|
|
|
assert(0);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: After printout starts:\n");
|
|
|
vm_print_current_domain_state(vm);
|
|
|
fprintf(MSG_OUT, "libdvdnav: After printout ends.\n");
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
(vm->state).blockN = link_values.data1 | (link_values.data2 << 16);
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
|
|
|
/* Set functions */
|
|
|
|
|
|
static int set_TT(vm_t *vm, int tt) {
|
|
|
return set_PTT(vm, tt, 1);
|
|
|
}
|
|
|
|
|
|
static int set_PTT(vm_t *vm, int tt, int ptt) {
|
|
|
assert(tt <= vm->vmgi->tt_srpt->nr_of_srpts);
|
|
|
return set_VTS_PTT(vm, vm->vmgi->tt_srpt->title[tt - 1].title_set_nr,
|
|
|
vm->vmgi->tt_srpt->title[tt - 1].vts_ttn, ptt);
|
|
|
}
|
|
|
|
|
|
static int set_VTS_TT(vm_t *vm, int vtsN, int vts_ttn) {
|
|
|
return set_VTS_PTT(vm, vtsN, vts_ttn, 1);
|
|
|
}
|
|
|
|
|
|
static int set_VTS_PTT(vm_t *vm, int vtsN, int vts_ttn, int part) {
|
|
|
int pgcN, pgN, res;
|
|
|
|
|
|
(vm->state).domain = VTS_DOMAIN;
|
|
|
|
|
|
if (vtsN != (vm->state).vtsN)
|
|
|
if (!ifoOpenNewVTSI(vm, vm->dvd, vtsN)) /* Also sets (vm->state).vtsN */
|
|
|
return 0;
|
|
|
|
|
|
if ((vts_ttn < 1) || (vts_ttn > vm->vtsi->vts_ptt_srpt->nr_of_srpts) ||
|
|
|
(part < 1) || (part > vm->vtsi->vts_ptt_srpt->title[vts_ttn - 1].nr_of_ptts) ) {
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
pgcN = vm->vtsi->vts_ptt_srpt->title[vts_ttn - 1].ptt[part - 1].pgcn;
|
|
|
pgN = vm->vtsi->vts_ptt_srpt->title[vts_ttn - 1].ptt[part - 1].pgn;
|
|
|
|
|
|
(vm->state).TT_PGCN_REG = pgcN;
|
|
|
(vm->state).PTTN_REG = part;
|
|
|
(vm->state).TTN_REG = get_TT(vm, vtsN, vts_ttn);
|
|
|
assert( (vm->state.TTN_REG) != 0 );
|
|
|
(vm->state).VTS_TTN_REG = vts_ttn;
|
|
|
(vm->state).vtsN = vtsN; /* Not sure about this one. We can get to it easily from TTN_REG */
|
|
|
/* Any other registers? */
|
|
|
|
|
|
res = set_PGCN(vm, pgcN); /* This clobber's state.pgN (sets it to 1), but we don't want clobbering here. */
|
|
|
(vm->state).pgN = pgN;
|
|
|
return res;
|
|
|
}
|
|
|
|
|
|
static int set_FP_PGC(vm_t *vm) {
|
|
|
(vm->state).domain = FP_DOMAIN;
|
|
|
if (!vm->vmgi->first_play_pgc) {
|
|
|
return set_PGCN(vm, 1);
|
|
|
}
|
|
|
(vm->state).pgc = vm->vmgi->first_play_pgc;
|
|
|
(vm->state).pgcN = vm->vmgi->vmgi_mat->first_play_pgc;
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
|
|
|
static int set_MENU(vm_t *vm, int menu) {
|
|
|
assert((vm->state).domain == VMGM_DOMAIN || (vm->state).domain == VTSM_DOMAIN);
|
|
|
return set_PGCN(vm, get_ID(vm, menu));
|
|
|
}
|
|
|
|
|
|
static int set_PGCN(vm_t *vm, int pgcN) {
|
|
|
pgcit_t *pgcit;
|
|
|
|
|
|
pgcit = get_PGCIT(vm);
|
|
|
assert(pgcit != NULL); /* ?? Make this return -1 instead */
|
|
|
|
|
|
if(pgcN < 1 || pgcN > pgcit->nr_of_pgci_srp) {
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: ** No such pgcN = %d\n", pgcN);
|
|
|
#endif
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
(vm->state).pgc = pgcit->pgci_srp[pgcN - 1].pgc;
|
|
|
(vm->state).pgcN = pgcN;
|
|
|
(vm->state).pgN = 1;
|
|
|
|
|
|
if((vm->state).domain == VTS_DOMAIN)
|
|
|
(vm->state).TT_PGCN_REG = pgcN;
|
|
|
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
/* Figure out the correct pgN from the cell and update (vm->state). */
|
|
|
static int set_PGN(vm_t *vm) {
|
|
|
int new_pgN = 0;
|
|
|
|
|
|
while(new_pgN < (vm->state).pgc->nr_of_programs
|
|
|
&& (vm->state).cellN >= (vm->state).pgc->program_map[new_pgN])
|
|
|
new_pgN++;
|
|
|
|
|
|
if(new_pgN == (vm->state).pgc->nr_of_programs) /* We are at the last program */
|
|
|
if((vm->state).cellN > (vm->state).pgc->nr_of_cells)
|
|
|
return 0; /* We are past the last cell */
|
|
|
|
|
|
(vm->state).pgN = new_pgN;
|
|
|
|
|
|
if((vm->state).domain == VTS_DOMAIN) {
|
|
|
playback_type_t *pb_ty;
|
|
|
if((vm->state).TTN_REG > vm->vmgi->tt_srpt->nr_of_srpts)
|
|
|
return 0; /* ?? */
|
|
|
pb_ty = &vm->vmgi->tt_srpt->title[(vm->state).TTN_REG - 1].pb_ty;
|
|
|
if(pb_ty->multi_or_random_pgc_title == /* One_Sequential_PGC_Title */ 0) {
|
|
|
int dummy, part;
|
|
|
vm_get_current_title_part(vm, &dummy, &part);
|
|
|
(vm->state).PTTN_REG = part;
|
|
|
} else {
|
|
|
/* FIXME: Handle RANDOM or SHUFFLE titles. */
|
|
|
fprintf(MSG_OUT, "libdvdnav: RANDOM or SHUFFLE titles are NOT handled yet.\n");
|
|
|
}
|
|
|
}
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
/* Must be called before domain is changed (set_PGCN()) */
|
|
|
static void set_RSMinfo(vm_t *vm, int cellN, int blockN) {
|
|
|
int i;
|
|
|
|
|
|
if(cellN) {
|
|
|
(vm->state).rsm_cellN = cellN;
|
|
|
(vm->state).rsm_blockN = blockN;
|
|
|
} else {
|
|
|
(vm->state).rsm_cellN = (vm->state).cellN;
|
|
|
(vm->state).rsm_blockN = blockN;
|
|
|
}
|
|
|
(vm->state).rsm_vtsN = (vm->state).vtsN;
|
|
|
(vm->state).rsm_pgcN = get_PGCN(vm);
|
|
|
|
|
|
/* assert((vm->state).rsm_pgcN == (vm->state).TT_PGCN_REG); for VTS_DOMAIN */
|
|
|
|
|
|
for(i = 0; i < 5; i++) {
|
|
|
(vm->state).rsm_regs[i] = (vm->state).registers.SPRM[4 + i];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/* Get functions */
|
|
|
|
|
|
/* Searches the TT tables, to find the current TT.
|
|
|
* returns the current TT.
|
|
|
* returns 0 if not found.
|
|
|
*/
|
|
|
static int get_TT(vm_t *vm, int vtsN, int vts_ttn) {
|
|
|
int i;
|
|
|
int tt=0;
|
|
|
|
|
|
for(i = 1; i <= vm->vmgi->tt_srpt->nr_of_srpts; i++) {
|
|
|
if( vm->vmgi->tt_srpt->title[i - 1].title_set_nr == vtsN &&
|
|
|
vm->vmgi->tt_srpt->title[i - 1].vts_ttn == vts_ttn) {
|
|
|
tt=i;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
return tt;
|
|
|
}
|
|
|
|
|
|
/* Search for entry_id match of the PGC Category in the current VTS PGCIT table.
|
|
|
* Return pgcN based on entry_id match.
|
|
|
*/
|
|
|
static int get_ID(vm_t *vm, int id) {
|
|
|
int pgcN, i;
|
|
|
pgcit_t *pgcit;
|
|
|
|
|
|
/* Relies on state to get the correct pgcit. */
|
|
|
pgcit = get_PGCIT(vm);
|
|
|
assert(pgcit != NULL);
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: ** Searching for menu (0x%x) entry PGC\n", id);
|
|
|
#endif
|
|
|
|
|
|
/* Force high bit set. */
|
|
|
id |=0x80;
|
|
|
|
|
|
/* Get menu/title */
|
|
|
for(i = 0; i < pgcit->nr_of_pgci_srp; i++) {
|
|
|
if( (pgcit->pgci_srp[i].entry_id) == id) {
|
|
|
pgcN = i + 1;
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: Found menu.\n");
|
|
|
#endif
|
|
|
return pgcN;
|
|
|
}
|
|
|
}
|
|
|
#ifdef TRACE
|
|
|
fprintf(MSG_OUT, "libdvdnav: ** No such id/menu (0x%02x) entry PGC\n", id & 0x7f);
|
|
|
for(i = 0; i < pgcit->nr_of_pgci_srp; i++) {
|
|
|
if ( (pgcit->pgci_srp[i].entry_id & 0x80) == 0x80) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: Available menus: 0x%x\n",
|
|
|
pgcit->pgci_srp[i].entry_id & 0x7f);
|
|
|
}
|
|
|
}
|
|
|
#endif
|
|
|
return 0; /* error */
|
|
|
}
|
|
|
|
|
|
/* FIXME: we have a pgcN member in the vm's state now, so this should be obsolete */
|
|
|
static int get_PGCN(vm_t *vm) {
|
|
|
pgcit_t *pgcit;
|
|
|
int pgcN = 1;
|
|
|
|
|
|
pgcit = get_PGCIT(vm);
|
|
|
|
|
|
if (pgcit) {
|
|
|
while(pgcN <= pgcit->nr_of_pgci_srp) {
|
|
|
if(pgcit->pgci_srp[pgcN - 1].pgc == (vm->state).pgc) {
|
|
|
assert((vm->state).pgcN == pgcN);
|
|
|
return pgcN;
|
|
|
}
|
|
|
pgcN++;
|
|
|
}
|
|
|
}
|
|
|
fprintf(MSG_OUT, "libdvdnav: get_PGCN failed. Was trying to find pgcN in domain %d\n",
|
|
|
(vm->state).domain);
|
|
|
return 0; /* error */
|
|
|
}
|
|
|
|
|
|
static pgcit_t* get_MENU_PGCIT(vm_t *vm, ifo_handle_t *h, uint16_t lang) {
|
|
|
int i;
|
|
|
|
|
|
if(h == NULL || h->pgci_ut == NULL) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: *** pgci_ut handle is NULL ***\n");
|
|
|
return NULL; /* error? */
|
|
|
}
|
|
|
|
|
|
i = 0;
|
|
|
while(i < h->pgci_ut->nr_of_lus
|
|
|
&& h->pgci_ut->lu[i].lang_code != lang)
|
|
|
i++;
|
|
|
if(i == h->pgci_ut->nr_of_lus) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: Language '%c%c' not found, using '%c%c' instead\n",
|
|
|
(char)(lang >> 8), (char)(lang & 0xff),
|
|
|
(char)(h->pgci_ut->lu[0].lang_code >> 8),
|
|
|
(char)(h->pgci_ut->lu[0].lang_code & 0xff));
|
|
|
fprintf(MSG_OUT, "libdvdnav: Menu Languages available: ");
|
|
|
for(i = 0; i < h->pgci_ut->nr_of_lus; i++) {
|
|
|
fprintf(MSG_OUT, "%c%c ",
|
|
|
(char)(h->pgci_ut->lu[i].lang_code >> 8),
|
|
|
(char)(h->pgci_ut->lu[i].lang_code & 0xff));
|
|
|
}
|
|
|
fprintf(MSG_OUT, "\n");
|
|
|
i = 0; /* error? */
|
|
|
}
|
|
|
|
|
|
return h->pgci_ut->lu[i].pgcit;
|
|
|
}
|
|
|
|
|
|
/* Uses state to decide what to return */
|
|
|
static pgcit_t* get_PGCIT(vm_t *vm) {
|
|
|
pgcit_t *pgcit;
|
|
|
|
|
|
switch ((vm->state).domain) {
|
|
|
case VTS_DOMAIN:
|
|
|
pgcit = vm->vtsi->vts_pgcit;
|
|
|
break;
|
|
|
case VTSM_DOMAIN:
|
|
|
pgcit = get_MENU_PGCIT(vm, vm->vtsi, (vm->state).registers.SPRM[0]);
|
|
|
break;
|
|
|
case VMGM_DOMAIN:
|
|
|
case FP_DOMAIN:
|
|
|
pgcit = get_MENU_PGCIT(vm, vm->vmgi, (vm->state).registers.SPRM[0]);
|
|
|
break;
|
|
|
default:
|
|
|
abort();
|
|
|
}
|
|
|
|
|
|
return pgcit;
|
|
|
}
|
|
|
|
|
|
|
|
|
/* Debug functions */
|
|
|
|
|
|
#ifdef TRACE
|
|
|
void vm_position_print(vm_t *vm, vm_position_t *position) {
|
|
|
fprintf(MSG_OUT, "libdvdnav: But=%x Spu=%x Aud=%x Ang=%x Hop=%x vts=%x dom=%x cell=%x cell_restart=%x cell_start=%x still=%x block=%x\n",
|
|
|
position->button,
|
|
|
position->spu_channel,
|
|
|
position->audio_channel,
|
|
|
position->angle_channel,
|
|
|
position->hop_channel,
|
|
|
position->vts,
|
|
|
position->domain,
|
|
|
position->cell,
|
|
|
position->cell_restart,
|
|
|
position->cell_start,
|
|
|
position->still,
|
|
|
position->block);
|
|
|
}
|
|
|
#endif
|
|
|
|