/* 
 * Copyright (C) 2000 Rich Wareham <richwareham@users.sourceforge.net>
 * 
 * This file is part of libdvdnav, a DVD navigation library.
 * 
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
 *
 * $Id: dvdnav.c,v 1.63 2004/01/31 17:12:58 jcdutton Exp $
 *
 */

/*
 * Ötvös Attila (Attila, Otvos) created it with the libdvdnav-0.1.10.
 * Useful links:
 * SourceForge.net: DVD tools for Linux
 * http://sourceforge.net/projects/dvd/
 */

#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include "mp_msg.h"
#include "../libmpdemux/stream.h"
#include "help_mp.h"
#include "osdep/timer.h"
#include "input/input.h"
#include "stream.h"
#include "stream_dvdnav.h"
#include "libvo/video_out.h"
#include "spudec.h"
#include "m_option.h"
#include "m_struct.h"

#define TRACE	// enable trace messages print, usage: -dvdnav-trace options
//#define LOG_DEBUG

int dvd_nav_skip_opening = 0;
int dvd_nav_still = 0;
int dvdnav_menutype = 0;
int dvdnav_continue_play = 0;
int dvdnav_go_title = 0; 
int dvdnav_go_part = 0;
int dvdnav_go_menu = 0;
int dvdnav_go_audio = 0;
int dvdnav_go_spu = 0;
int dvdnav_trace = 0;
char* dvdmenu_lang = NULL;

extern char* dvd_device;
extern int audio_id;
extern int dvdsub_id;
extern char* audio_lang;
extern char* dvdsub_lang;

#ifdef HAVE_NEW_GUI
int	dvdnav_window_width=0;
int	dvdnav_window_height=0;
int	dvdnav_window_orig_width=0;
int	dvdnav_window_orig_height=0;
int	dvdnav_mouse_x=0;
int	dvdnav_mouse_y=0;
int	dvdnav_mouse_orig_x=0;
int	dvdnav_mouse_orig_y=0;
int	dvdnav_mouse_button=0;
int	dvdnav_mouse_set=0;
#endif

off_t	eventnum=0;


static struct stream_priv_s {
  int track;
  char* device;
} stream_priv_dflts = {
  1,
  NULL
};

#define ST_OFF(f) M_ST_OFF(struct stream_priv_s,f)
/// URL definition
static m_option_t stream_opts_fields[] = {
  { NULL, NULL, 0, 0, 0, 0,  NULL }
};
static struct m_struct_st stream_opts = {
  "dvd",
  sizeof(struct stream_priv_s),
  &stream_priv_dflts,
  stream_opts_fields
};


#ifndef _MSC_VER 
#include <sys/param.h>
#include <sys/fcntl.h>
#else
#ifndef MAXPATHLEN
#define MAXPATHLEN 255
#endif
#endif /* _MSC_VER */


// ####################################### decoder.c start ##########################################
uint32_t vm_getbits(vm_command_t *command, int32_t start, int32_t count) {
  uint64_t result = 0;
  uint64_t bit_mask = 0;
  uint64_t examining = 0;
  int32_t  bits;
  
  if (count == 0) return 0;

  if ( ((start - count) < -1) ||
       (count > 32) ||
       (start > 63) ||
       (count < 0) ||
       (start < 0) ) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_BadCallToVMGetbits);
    abort();
  }
  /* all ones, please */
  bit_mask = ~bit_mask;
  bit_mask >>= 63 - start;
  bits = start + 1 - count;
  examining = ((bit_mask >> bits) << bits );
  command->examined |= examining;
  result = (command->instruction & bit_mask) >> bits;
  return (uint32_t) result;
}

static uint16_t get_GPRM(registers_t* registers, uint8_t reg) {
  if (registers->GPRM_mode[reg] & 0x01) {
    struct timeval current_time, time_offset;
    uint16_t result;
    /* Counter mode */
    /* mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Getting counter %d\n",reg);*/
    gettimeofday(&current_time, NULL);
    time_offset.tv_sec = current_time.tv_sec - registers->GPRM_time[reg].tv_sec;
    time_offset.tv_usec = current_time.tv_usec - registers->GPRM_time[reg].tv_usec;
    if (time_offset.tv_usec < 0) { 
      time_offset.tv_sec--; 
      time_offset.tv_usec += 1000000;
    }
    result = (uint16_t) (time_offset.tv_sec & 0xffff);
    registers->GPRM[reg]=result;
    return result; 

  } else {
    /* Register mode */
    return registers->GPRM[reg];
  }
  
}

static void set_GPRM(registers_t* registers, uint8_t reg, uint16_t value) {
  if (registers->GPRM_mode[reg] & 0x01) {
    struct timeval current_time;
    /* Counter mode */
    /* mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Setting counter %d\n",reg); */
    gettimeofday(&current_time, NULL);
    registers->GPRM_time[reg] = current_time;
    registers->GPRM_time[reg].tv_sec -= value;
  }
  registers->GPRM[reg] = value;
}

/* Eval register code, can either be system or general register.
   SXXX_XXXX, where S is 1 if it is system register. */
static uint16_t eval_reg(vm_command_t* command, uint8_t reg) {
  if(reg & 0x80) {
    if ((reg & 0x1f) == 20) {
      mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_SuspectedRCERegionProtection);
    }
    return command->registers->SPRM[reg & 0x1f]; /*  FIXME max 24 not 32 */
  } else {
    return get_GPRM(command->registers, reg & 0x0f) ;
  }
}

/* Eval register or immediate data.
   AAAA_AAAA BBBB_BBBB, if immediate use all 16 bits for data else use
   lower eight bits for the system or general purpose register. */
static uint16_t eval_reg_or_data(vm_command_t* command, int32_t imm, int32_t start) {
  if(imm) { /*  immediate */
    return vm_getbits(command, start, 16);
  } else {
    return eval_reg(command, vm_getbits(command, (start - 8), 8));
  }
}

/* Eval register or immediate data.
   xBBB_BBBB, if immediate use all 7 bits for data else use
   lower four bits for the general purpose register number. */
/* Evaluates gprm or data depending on bit, data is in byte n */
static uint16_t eval_reg_or_data_2(vm_command_t* command, 
				   int32_t imm, int32_t start) {
  if(imm) /* immediate */
    return vm_getbits(command, (start - 1), 7);
  else
    return get_GPRM(command->registers, (vm_getbits(command, (start - 4), 4)) );
}


/* Compare data using operation, return result from comparison. 
   Helper function for the different if functions. */
static int32_t eval_compare(uint8_t operation, uint16_t data1, uint16_t data2) {
  switch(operation) {
    case 1:
      return data1 & data2;
    case 2:
      return data1 == data2;
    case 3:
      return data1 != data2;
    case 4:
      return data1 >= data2;
    case 5:
      return data1 >  data2;
    case 6:
      return data1 <= data2;
    case 7:
      return data1 <  data2;
  }
  mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_EvalCompareInvalidComparisonCode);
  return 0;
}


/* Evaluate if version 1.
   Has comparison data in byte 3 and 4-5 (immediate or register) */
static int32_t eval_if_version_1(vm_command_t* command) {
  uint8_t op = vm_getbits(command, 54, 3);
  if(op) {
    return eval_compare(op, eval_reg(command, vm_getbits(command, 39, 8)), 
                            eval_reg_or_data(command, vm_getbits(command, 55, 1), 31));
  }
  return 1;
}

/* Evaluate if version 2.
   This version only compares register which are in byte 6 and 7 */
static int32_t eval_if_version_2(vm_command_t* command) {
  uint8_t op = vm_getbits(command, 54, 3);
  if(op) {
    return eval_compare(op, eval_reg(command, vm_getbits(command, 15, 8)), 
                            eval_reg(command, vm_getbits(command, 7, 8)));
  }
  return 1;
}

/* Evaluate if version 3.
   Has comparison data in byte 2 and 6-7 (immediate or register) */
static int32_t eval_if_version_3(vm_command_t* command) {
  uint8_t op = vm_getbits(command, 54, 3);
  if(op) {
    return eval_compare(op, eval_reg(command, vm_getbits(command, 47, 8)), 
                            eval_reg_or_data(command, vm_getbits(command, 55, 1), 15));
  }
  return 1;
}

/* Evaluate if version 4.
   Has comparison data in byte 1 and 4-5 (immediate or register) 
   The register in byte 1 is only the lowe nibble (4 bits) */
static int32_t eval_if_version_4(vm_command_t* command) {
  uint8_t op = vm_getbits(command, 54, 3);
  if(op) {
    return eval_compare(op, eval_reg(command, vm_getbits(command, 51, 4)), 
                            eval_reg_or_data(command, vm_getbits(command, 55, 1), 31));
  }
  return 1;
}

/* Evaluate special instruction.... returns the new row/line number,
   0 if no new row and 256 if Break. */
static int32_t eval_special_instruction(vm_command_t* command, int32_t cond) {
  int32_t line, level;
  
  switch(vm_getbits(command, 51, 4)) {
    case 0: /*  NOP */
      line = 0;
      return cond ? line : 0;
    case 1: /*  Goto line */
      line = vm_getbits(command, 7, 8);
      return cond ? line : 0;
    case 2: /*  Break */
      /*  max number of rows < 256, so we will end this set */
      line = 256;
      return cond ? 256 : 0;
    case 3: /*  Set temporary parental level and goto */
      line = vm_getbits(command, 7, 8); 
      level = vm_getbits(command, 11, 4);
      if(cond) {
	/*  This always succeeds now, if we want real parental protection */
	/*  we need to ask the user and have passwords and stuff. */
	command->registers->SPRM[13] = level;
      }
      return cond ? line : 0;
  }
  return 0;
}

/* Evaluate link by subinstruction.
   Return 1 if link, or 0 if no link
   Actual link instruction is in return_values parameter */
static int32_t eval_link_subins(vm_command_t* command, int32_t cond, link_t *return_values) {
  uint16_t button = vm_getbits(command, 15, 6);
  uint8_t  linkop = vm_getbits(command, 4, 5);
  
  if(linkop > 0x10)
    return 0;    /*  Unknown Link by Sub-Instruction command */

  /*  Assumes that the link_cmd_t enum has the same values as the LinkSIns codes */
  return_values->command = linkop;
  return_values->data1 = button;
  return cond;
}


/* Evaluate link instruction.
   Return 1 if link, or 0 if no link
   Actual link instruction is in return_values parameter */
static int32_t eval_link_instruction(vm_command_t* command, int32_t cond, link_t *return_values) {
  uint8_t op = vm_getbits(command, 51, 4);
  
  switch(op) {
    case 1:
	return eval_link_subins(command, cond, return_values);
    case 4:
	return_values->command = LinkPGCN;
	return_values->data1   = vm_getbits(command, 14, 15);
	return cond;
    case 5:
	return_values->command = LinkPTTN;
	return_values->data1 = vm_getbits(command, 9, 10);
	return_values->data2 = vm_getbits(command, 15, 6);
	return cond;
    case 6:
	return_values->command = LinkPGN;
	return_values->data1 = vm_getbits(command, 6, 7);
	return_values->data2 = vm_getbits(command, 15, 6);
	return cond;
    case 7:
	return_values->command = LinkCN;
	return_values->data1 = vm_getbits(command, 7, 8);
	return_values->data2 = vm_getbits(command, 15, 6);
	return cond;
  }
  return 0;
}


/* Evaluate a jump instruction.
   returns 1 if jump or 0 if no jump
   actual jump instruction is in return_values parameter */
static int32_t eval_jump_instruction(vm_command_t* command, int32_t cond, link_t *return_values) {
  
  switch(vm_getbits(command, 51, 4)) {
    case 1:
      return_values->command = Exit;
      return cond;
    case 2:
      return_values->command = JumpTT;
      return_values->data1 = vm_getbits(command, 22, 7);
      return cond;
    case 3:
      return_values->command = JumpVTS_TT;
      return_values->data1 = vm_getbits(command, 22, 7);
      return cond;
    case 5:
      return_values->command = JumpVTS_PTT;
      return_values->data1 = vm_getbits(command, 22, 7);
      return_values->data2 = vm_getbits(command, 41, 10);
      return cond;
    case 6:
      switch(vm_getbits(command, 23, 2)) {
        case 0:
          return_values->command = JumpSS_FP;
          return cond;
        case 1:
          return_values->command = JumpSS_VMGM_MENU;
          return_values->data1 =  vm_getbits(command, 19, 4);
          return cond;
        case 2:
          return_values->command = JumpSS_VTSM;
          return_values->data1 =  vm_getbits(command, 31, 8);
          return_values->data2 =  vm_getbits(command, 39, 8);
          return_values->data3 =  vm_getbits(command, 19, 4);
          return cond;
        case 3:
          return_values->command = JumpSS_VMGM_PGC;
          return_values->data1 =  vm_getbits(command, 46, 15);
          return cond;
        }
      break;
    case 8:
      switch(vm_getbits(command, 23, 2)) {
        case 0:
          return_values->command = CallSS_FP;
          return_values->data1 = vm_getbits(command, 31, 8);
          return cond;
        case 1:
          return_values->command = CallSS_VMGM_MENU;
          return_values->data1 = vm_getbits(command, 19, 4);
          return_values->data2 = vm_getbits(command, 31, 8);
          return cond;
        case 2:
          return_values->command = CallSS_VTSM;
          return_values->data1 = vm_getbits(command, 19, 4);
          return_values->data2 = vm_getbits(command, 31, 8);
          return cond;
        case 3:
          return_values->command = CallSS_VMGM_PGC;
          return_values->data1 = vm_getbits(command, 46, 15);
          return_values->data2 = vm_getbits(command, 31, 8);
          return cond;
      }
      break;
  }
  return 0;
}

/* Evaluate a set sytem register instruction 
   May contain a link so return the same as eval_link */
static int32_t eval_system_set(vm_command_t* command, int32_t cond, link_t *return_values) {
  int32_t i;
  uint16_t data, data2;
  
  switch(vm_getbits(command, 59, 4)) {
    case 1: /*  Set system reg 1 &| 2 &| 3 (Audio, Subp. Angle) */
      for(i = 1; i <= 3; i++) {
        if(vm_getbits(command, 63 - ((2 + i)*8), 1)) {
          data = eval_reg_or_data_2(command, vm_getbits(command, 60, 1), (47 - (i*8)));
          if(cond) {
            command->registers->SPRM[i] = data;
          }
        }
      }
      break;
    case 2: /*  Set system reg 9 & 10 (Navigation timer, Title PGC number) */
      data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 47);
      data2 = vm_getbits(command, 23, 8); /*  ?? size */
      if(cond) {
	command->registers->SPRM[9] = data; /*  time */
	command->registers->SPRM[10] = data2; /*  pgcN */
      }
      break;
    case 3: /*  Mode: Counter / Register + Set */
      data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 47);
      data2 = vm_getbits(command, 19, 4);
      if(vm_getbits(command, 23, 1)) {
	command->registers->GPRM_mode[data2] |= 1; /* Set bit 0 */
      } else {
	command->registers->GPRM_mode[data2] &= ~ 0x01; /* Reset bit 0 */
      }
      if(cond) {
        set_GPRM(command->registers, data2, data);
      }
      break;
    case 6: /*  Set system reg 8 (Highlighted button) */
      data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 31); /*  Not system reg!! */
      if(cond) {
	command->registers->SPRM[8] = data;
      }
      break;
  }
  if(vm_getbits(command, 51, 4)) {
    return eval_link_instruction(command, cond, return_values);
  }
  return 0;
}


/* Evaluate set operation
   Sets the register given to the value indicated by op and data.
   For the swap case the contents of reg is stored in reg2.
*/
static void eval_set_op(vm_command_t* command, int32_t op, int32_t reg, int32_t reg2, int32_t data) {
  const int32_t shortmax = 0xffff;
  int32_t     tmp; 
  switch(op) {
    case 1:
      set_GPRM(command->registers, reg, data);
      break;
    case 2: /* SPECIAL CASE - SWAP! */
      set_GPRM(command->registers, reg2, get_GPRM(command->registers, reg));
      set_GPRM(command->registers, reg, data);
      break;
    case 3:
      tmp = get_GPRM(command->registers, reg) + data;
      if(tmp > shortmax) tmp = shortmax;
      set_GPRM(command->registers, reg, (uint16_t)tmp);
      break;
    case 4:
      tmp = get_GPRM(command->registers, reg) - data;
      if(tmp < 0) tmp = 0;
      set_GPRM(command->registers, reg, (uint16_t)tmp);
      break;
    case 5:
      tmp = get_GPRM(command->registers, reg) * data;
      if(tmp > shortmax) tmp = shortmax;
      set_GPRM(command->registers, reg, (uint16_t)tmp);
      break;
    case 6:
      if (data != 0) {
        set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) / data) );
      } else {
        set_GPRM(command->registers, reg, 0xffff); /* Avoid that divide by zero! */
      }
      break;
    case 7:
      if (data != 0) {
        set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) % data) );
      } else {
        set_GPRM(command->registers, reg, 0xffff); /* Avoid that divide by zero! */
      }
      break;
    case 8: /* SPECIAL CASE - RND! Return numbers between 1 and data. */
      set_GPRM(command->registers, reg, 1 + ((uint16_t) ((float) data * rand()/(RAND_MAX+1.0))) );
      break;
    case 9:
      set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) & data) );
      break;
    case 10:
      set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) | data) );
      break;
    case 11:
      set_GPRM(command->registers, reg, (get_GPRM(command->registers, reg) ^ data) );
      break;
  }
}

/* Evaluate set instruction, combined with either Link or Compare. */
static void eval_set_version_1(vm_command_t* command, int32_t cond) {
  uint8_t  op   = vm_getbits(command, 59, 4);
  uint8_t  reg  = vm_getbits(command, 35, 4); /* FIXME: This is different from vmcmd.c!!! */
  uint8_t  reg2 = vm_getbits(command, 19, 4);
  uint16_t data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 31);

  if(cond) {
    eval_set_op(command, op, reg, reg2, data);
  }
}


/* Evaluate set instruction, combined with both Link and Compare. */
static void eval_set_version_2(vm_command_t* command, int32_t cond) {
  uint8_t  op   = vm_getbits(command, 59, 4);
  uint8_t  reg  = vm_getbits(command, 51, 4);
  uint8_t  reg2 = vm_getbits(command, 35, 4); /* FIXME: This is different from vmcmd.c!!! */
  uint16_t data = eval_reg_or_data(command, vm_getbits(command, 60, 1), 47);

  if(cond) {
    eval_set_op(command, op, reg, reg2, data);
  }
}


/* Evaluate a command
   returns row number of goto, 0 if no goto, -1 if link.
   Link command in return_values */
static int32_t eval_command(uint8_t *bytes, registers_t* registers, link_t *return_values) {
  int32_t cond, res = 0;
  vm_command_t command;
  command.instruction =( (uint64_t) bytes[0] << 56 ) |
        ( (uint64_t) bytes[1] << 48 ) |
        ( (uint64_t) bytes[2] << 40 ) |
        ( (uint64_t) bytes[3] << 32 ) |
        ( (uint64_t) bytes[4] << 24 ) |
        ( (uint64_t) bytes[5] << 16 ) |
        ( (uint64_t) bytes[6] <<  8 ) |
          (uint64_t) bytes[7] ;
  command.examined = 0;
  command.registers = registers;
  memset(return_values, 0, sizeof(link_t));

  switch(vm_getbits(&command, 63, 3)) { /* three first old_bits */
    case 0: /*  Special instructions */
      cond = eval_if_version_1(&command);
      res = eval_special_instruction(&command, cond);
      if(res == -1) {
	mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_UnknownInstrution);
	abort();
      }
      break;
    case 1: /*  Link/jump instructions */
      if(vm_getbits(&command, 60, 1)) {
        cond = eval_if_version_2(&command);
        res = eval_jump_instruction(&command, cond, return_values);
      } else {
        cond = eval_if_version_1(&command);
        res = eval_link_instruction(&command, cond, return_values);
      }
      if(res)
	res = -1;
      break;
    case 2: /*  System set instructions */
      cond = eval_if_version_2(&command);
      res = eval_system_set(&command, cond, return_values);
      if(res)
	res = -1;
      break;
    case 3: /*  Set instructions, either Compare or Link may be used */
      cond = eval_if_version_3(&command);
      eval_set_version_1(&command, cond);
      if(vm_getbits(&command, 51, 4)) {
	res = eval_link_instruction(&command, cond, return_values);
      }
      if(res)
	res = -1;
      break;
    case 4: /*  Set, Compare -> Link Sub-Instruction */
      eval_set_version_2(&command, /*True*/ 1);
      cond = eval_if_version_4(&command);
      res = eval_link_subins(&command, cond, return_values);
      if(res)
	res = -1;
      break;
    case 5: /*  Compare -> (Set and Link Sub-Instruction) */
      /* FIXME: These are wrong. Need to be updated from vmcmd.c */
      cond = eval_if_version_4(&command);
      eval_set_version_2(&command, cond);
      res = eval_link_subins(&command, cond, return_values);
      if(res)
	res = -1;
      break;
    case 6: /*  Compare -> Set, allways Link Sub-Instruction */
      /* FIXME: These are wrong. Need to be updated from vmcmd.c */
      cond = eval_if_version_4(&command);
      eval_set_version_2(&command, cond);
      res = eval_link_subins(&command, /*True*/ 1, return_values);
      if(res)
	res = -1;
      break;
    default: /* Unknown command */
       mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_WarningUnknownComman, vm_getbits(&command, 63, 3));
      abort();
  }
  /*  Check if there are bits not yet examined */

  if(command.instruction & ~ command.examined) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_DecoderCWarningUnknownBits);
    mp_msg(MSGT_CPLAYER,MSGL_INFO," %08llx", (command.instruction & ~ command.examined) );
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "]\n");
  }

  return res;
}

/* Evaluate a set of commands in the given register set (which is modified) */
int32_t vmEval_CMD(vm_cmd_t commands[], int32_t num_commands, 
	       registers_t *registers, link_t *return_values) {
  int32_t i = 0;
  int32_t total = 0;
  
#ifdef USE_MPDVDNAV_TRACE
  if (dvdnav_trace) {
  /*  DEBUG */
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Registers before transaction\n");
  vm_print_registers( registers );
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Full list of commands to execute\n");
  for(i = 0; i < num_commands; i++)
    vm_print_cmd(i, &commands[i]);
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: --------------------------------------------\n");
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Single stepping commands\n");
  }
#endif

  i = 0; 
  while(i < num_commands && total < 100000) {
    int32_t line;
    
#ifdef USE_MPDVDNAV_TRACE
    if (dvdnav_trace) vm_print_cmd(i, &commands[i]);
#endif

    line = eval_command(&commands[i].bytes[0], registers, return_values);
    if (line < 0) { /*  Link command */
#ifdef USE_MPDVDNAV_TRACE
      if (dvdnav_trace) {
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Registers after transaction\n");
      vm_print_registers( registers );
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: eval: Doing Link/Jump/Call\n"); 
      }
#endif
      return 1;
    }
    
    if (line > 0) /*  Goto command */
      i = line - 1;
    else /*  Just continue on the next line */
      i++;

    total++;
  }
  
  memset(return_values, 0, sizeof(link_t));
#ifdef USE_MPDVDNAV_TRACE
  if (dvdnav_trace) {
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Registers after transaction\n");
  vm_print_registers( registers );
  }
#endif
  return 0;
}

#ifdef USE_MPDVDNAV_TRACE

static char *linkcmd2str(link_cmd_t cmd) {
  switch(cmd) {
  case LinkNoLink:
    return "LinkNoLink";
  case LinkTopC:
    return "LinkTopC";
  case LinkNextC:
    return "LinkNextC";
  case LinkPrevC:
    return "LinkPrevC";
  case LinkTopPG:
    return "LinkTopPG";
  case LinkNextPG:
    return "LinkNextPG";
  case LinkPrevPG:
    return "LinkPrevPG";
  case LinkTopPGC:
    return "LinkTopPGC";
  case LinkNextPGC:
    return "LinkNextPGC";
  case LinkPrevPGC:
    return "LinkPrevPGC";
  case LinkGoUpPGC:
    return "LinkGoUpPGC";
  case LinkTailPGC:
    return "LinkTailPGC";
  case LinkRSM:
    return "LinkRSM";
  case LinkPGCN:
    return "LinkPGCN";
  case LinkPTTN:
    return "LinkPTTN";
  case LinkPGN:
    return "LinkPGN";
  case LinkCN:
    return "LinkCN";
  case Exit:
    return "Exit";
  case JumpTT:
    return "JumpTT";
  case JumpVTS_TT:
    return "JumpVTS_TT";
  case JumpVTS_PTT:
    return "JumpVTS_PTT";
  case JumpSS_FP:
    return "JumpSS_FP";
  case JumpSS_VMGM_MENU:
    return "JumpSS_VMGM_MENU";
  case JumpSS_VTSM:
    return "JumpSS_VTSM";
  case JumpSS_VMGM_PGC:
    return "JumpSS_VMGM_PGC";
  case CallSS_FP:
    return "CallSS_FP";
  case CallSS_VMGM_MENU:
    return "CallSS_VMGM_MENU";
  case CallSS_VTSM:
    return "CallSS_VTSM";
  case CallSS_VMGM_PGC:
    return "CallSS_VMGM_PGC";
  case PlayThis:
    return "PlayThis";
  }
  return "*** (bug)";
}

void vm_print_link(link_t value) {
  char *cmd = linkcmd2str(value.command);
    
  switch(value.command) {
  case LinkNoLink:
  case LinkTopC:
  case LinkNextC:
  case LinkPrevC:
  case LinkTopPG:
  case LinkNextPG:
  case LinkPrevPG:
  case LinkTopPGC:
  case LinkNextPGC:
  case LinkPrevPGC:
  case LinkGoUpPGC:
  case LinkTailPGC:
  case LinkRSM:
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_DecoderCButton, cmd, value.data1);
    break;
  case LinkPGCN:
  case JumpTT:
  case JumpVTS_TT:
  case JumpSS_VMGM_MENU: /*  == 2 -> Title Menu */
  case JumpSS_VMGM_PGC:
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: %s %d\n", cmd, value.data1);
    break;
  case LinkPTTN:
  case LinkPGN:
  case LinkCN:
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_DecoderCButton2, cmd, value.data1, value.data2);
    break;
  case Exit:
  case JumpSS_FP:
  case PlayThis: /*  Humm.. should we have this at all.. */
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: %s\n", cmd);
    break;
  case JumpVTS_PTT:
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: %s %d:%d\n", cmd, value.data1, value.data2);
    break;
  case JumpSS_VTSM:
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_DecoderCVtsTitleMenu,
	    cmd, value.data1, value.data2, value.data3);
    break;
  case CallSS_FP:
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_DecoderCResumeCell, cmd, value.data1);
    break;
  case CallSS_VMGM_MENU: /*  == 2 -> Title Menu */
  case CallSS_VTSM:
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_DecoderCResumeCell2, cmd, value.data1, value.data2);
    break;
  case CallSS_VMGM_PGC:
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_DecoderCResumeCell2, cmd, value.data1, value.data2);
    break;
  }
 }

void vm_print_registers( registers_t *registers ) {
  int32_t i;
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav:    #   ");
  for(i = 0; i < 24; i++)
    mp_msg(MSGT_CPLAYER,MSGL_INFO, " %2d |", i);
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "\nlibdvdnav: SRPMS: ");
  for(i = 0; i < 24; i++)
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "%04x|", registers->SPRM[i]);
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "\nlibdvdnav: GRPMS: ");
  for(i = 0; i < 16; i++)
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "%04x|", get_GPRM(registers, i) );
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "\nlibdvdnav: Gmode: ");
  for(i = 0; i < 16; i++)
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "%04x|", registers->GPRM_mode[i]);
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "\nlibdvdnav: Gtime: ");
  for(i = 0; i < 16; i++)
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "%04lx|", registers->GPRM_time[i].tv_sec & 0xffff);
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "\n");
}
#endif

// ######################################## decoder.c end ###########################################

// ######################################## remap.c start ###########################################
struct block_s {
    int domain;
    int title;
    int program;
    unsigned long start_block;
    unsigned long end_block;
};

struct remap_s {
    char *title;
    int maxblocks;
    int nblocks;
    int debug;
    struct block_s *blocks;
};

static remap_t* remap_new( char *title) {
    remap_t *map = malloc( sizeof(remap_t));
    map->title = strdup(title);
    map->maxblocks = 0;
    map->nblocks = 0;
    map->blocks = NULL;
    map->debug = 0;
    return map;
}

static int compare_block( block_t *a, block_t *b) {
    /* returns -1 if a precedes b, 1 if a follows b, and 0 if a and b overlap */
    if (a->domain < b->domain) {
	return -1;
    } else if (a->domain > b->domain) {
	return 1;
    }

    if (a->title < b->title) {
	return -1;
    } else if (a->title > b->title) {
	return 1;
    }

    if (a->program < b->program) {
	return -1;
    } else if (a->program > b->program) {
	return 1;
    }

    if (a->end_block < b->start_block) {
	return -1;
    } else if (a->start_block > b->end_block) {
	/*
	 * if a->start_block == b->end_block then the two regions
	 * aren't strictly overlapping, but they should be merged 
	 * anyway since there are zero blocks between them
	 */
	return 1;
    }

    return 0;
}

static block_t *findblock( remap_t *map, block_t *key) {
    int lb = 0;
    int ub = map->nblocks - 1;
    int mid;
    int res;

    while (lb <= ub) {
	mid = lb + (ub - lb)/2;
	res = compare_block( key, &map->blocks[mid]);
	if (res < 0) {
	    ub = mid-1;
	} else if (res > 0) {
	    lb = mid+1;
	} else {
	    return &map->blocks[mid];
	}
    }
    return NULL;
}

static void mergeblock( block_t *b, block_t tmp) {
    if (tmp.start_block < b->start_block) b->start_block = tmp.start_block;
    if (tmp.end_block > b->end_block) b->end_block = tmp.end_block;
}

static void remap_add_node( remap_t *map, block_t block) {
    block_t *b;
    int n;
    b = findblock( map, &block);
    if (b) {
	/* overlaps an existing block */
	mergeblock( b, block);
    } else {
        /* new block */
	if (map->nblocks >= map->maxblocks) {
	    map->maxblocks += 20;
	    map->blocks = realloc( map->blocks, sizeof( block_t)*map->maxblocks);
	}
	n = map->nblocks++;
	while (n > 0 && compare_block( &block, &map->blocks[ n-1]) < 0) {
	    map->blocks[ n] = map->blocks[ n-1];
	    n--;
	}
	map->blocks[ n] = block;
    }
}

static int parseblock(char *buf, int *dom, int *tt, int *pg, 
		      unsigned long *start, unsigned long *end) {
    long tmp;
    char *tok;
    char *epos;
    char *marker[]={"domain", "title", "program", "start", "end"};
    int st = 0;
    tok = strtok( buf, " ");
    while (st < 5) {
        if (strcmp(tok, marker[st])) return -st-1000;
        tok = strtok( NULL, " ");
        if (!tok) return -st-2000;
        tmp = strtol( tok, &epos, 0);
        if (*epos != 0 && *epos != ',') return -st-3000;
        switch (st) {
	    case 0:
		*dom = (int)tmp;
		break;
	    case 1:
		*tt = (int)tmp;
		break;
	    case 2:
		*pg = (int)tmp;
		break;
	    case 3:
		*start = tmp;
		break;
	    case 4:
		*end = tmp;
		break;
	} 
	st++;
        tok = strtok( NULL, " ");
    }
    return st;
}

remap_t* remap_loadmap( char *title) {
    char buf[160];
    char fname[MAXPATHLEN];
    char *home;
    int res;
    FILE *fp;
    block_t tmp;
    remap_t *map;

    /* Build the map filename */
    home = getenv("HOME"); 
#ifdef WIN32
    if(!home) home = getenv("USERPROFILE");
    if(!home) home = getenv("HOMEPATH");
    if(!home) home = (char*)get_path("");
#endif
    assert(home);
    strncpy(fname, home, sizeof(fname));
    strncat(fname, "/.dvdnav/", sizeof(fname));
    strncat(fname, title, sizeof(fname));
    strncat(fname, ".map", sizeof(fname));

    /* Open the map file */
    fp = fopen( fname, "r");
    if (!fp) {
        mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_UnableToFindMapFile, fname);
	return NULL;
    }

    /* Load the map file */
    map = remap_new( title);
    while (fgets( buf, sizeof(buf), fp) != NULL) {
        if (buf[0] == '\n' || buf[0] == '#' || buf[0] == 0) continue;
        if (strncasecmp( buf, "debug", 5) == 0) {
	    map->debug = 1;
	} else {
	    res = parseblock( buf, 
		&tmp.domain, &tmp.title, &tmp.program, &tmp.start_block, &tmp.end_block);
	    if (res != 5) {
    		mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_IgnoringMapLine, res, buf);
		continue;
	    }
	    remap_add_node( map, tmp);
	}
    }

    if (map->nblocks == 0 && map->debug == 0) return NULL;
    return map;
}

unsigned long remap_block( 
	remap_t *map, int domain, int title, int program, 
	unsigned long cblock, unsigned long offset) 
{
    block_t key;
    block_t *b;

    if (map->debug) {
	mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_RemapInfo,
	    map->title, domain, title, program, cblock, cblock+offset);
    }

    key.domain = domain;
    key.title = title;
    key.program = program;
    key.start_block = key.end_block = cblock + offset;
    b = findblock( map, &key);
    
    if (b) {
       if (map->debug) {
	   mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_RedirectedTo, b->end_block);
       }
       return b->end_block - cblock;
    }
    return offset;
}
// ######################################### remap.c end ############################################

// ####################################### vmcmd.c start ############################################
/*  freebsd compatibility */
#ifndef PRIu8
#define PRIu8 "d"
#endif

/*  freebsd compatibility */
#ifndef PRIu16
#define PRIu16 "d"
#endif

static const char *cmp_op_table[] = {
  NULL, "&", "==", "!=", ">=", ">", "<=", "<"
};
static const char *set_op_table[] = {
  NULL, "=", "<->", "+=", "-=", "*=", "/=", "%=", "rnd", "&=", "|=", "^="
};

static const char *link_table[] = {
  "LinkNoLink",  "LinkTopC",    "LinkNextC",   "LinkPrevC",
  NULL,          "LinkTopPG",   "LinkNextPG",  "LinkPrevPG",
  NULL,          "LinkTopPGC",  "LinkNextPGC", "LinkPrevPGC",
  "LinkGoUpPGC", "LinkTailPGC", NULL,          NULL,
  "RSM"
};

static const char *system_reg_table[] = {
  "Menu Description Language Code",
  "Audio Stream Number",
  "Sub-picture Stream Number",
  "Angle Number",
  "Title Track Number",
  "VTS Title Track Number",
  "VTS PGC Number",
  "PTT Number for One_Sequential_PGC_Title",
  "Highlighted Button Number",
  "Navigation Timer",
  "Title PGC Number for Navigation Timer",
  "Audio Mixing Mode for Karaoke",
  "Country Code for Parental Management",
  "Parental Level",
  "Player Configurations for Video",
  "Player Configurations for Audio",
  "Initial Language Code for Audio",
  "Initial Language Code Extension for Audio",
  "Initial Language Code for Sub-picture",
  "Initial Language Code Extension for Sub-picture",
  "Player Regional Code",
  "Reserved 21",
  "Reserved 22",
  "Reserved 23"
};

static const char *system_reg_abbr_table[] = {
  NULL,
  "ASTN",
  "SPSTN",
  "AGLN",
  "TTN",
  "VTS_TTN",
  "TT_PGCN",
  "PTTN",
  "HL_BTNN",
  "NVTMR",
  "NV_PGCN",
  NULL,
  "CC_PLT",
  "PLT",
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
};

static void print_system_reg(uint16_t reg) {
  if(reg < sizeof(system_reg_abbr_table) / sizeof(char *))
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "%s (SRPM:%d)", system_reg_table[reg], reg);
  else
    mp_msg(MSGT_CPLAYER,MSGL_INFO, " WARNING: Unknown system register ( reg=%d ) ", reg);
}

static void print_g_reg(uint8_t reg) {
    if(reg < 16)
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "g[%" PRIu8 "]", reg);
    else
      mp_msg(MSGT_CPLAYER,MSGL_INFO, " WARNING: Unknown general register ");
}

static void print_reg(uint8_t reg) {
  if(reg & 0x80)
    print_system_reg(reg & 0x7f);
  else
    print_g_reg(reg & 0x7f);
}

static void print_cmp_op(uint8_t op) {
  if(op < sizeof(cmp_op_table) / sizeof(char *) && cmp_op_table[op] != NULL)
    mp_msg(MSGT_CPLAYER,MSGL_INFO, " %s ", cmp_op_table[op]);
  else
    mp_msg(MSGT_CPLAYER,MSGL_INFO, " WARNING: Unknown compare op ");
}

static void print_set_op(uint8_t op) {
  if(op < sizeof(set_op_table) / sizeof(char *) && set_op_table[op] != NULL)
    mp_msg(MSGT_CPLAYER,MSGL_INFO, " %s ", set_op_table[op]);
  else
    mp_msg(MSGT_CPLAYER,MSGL_INFO, " WARNING: Unknown set op ");
}

static void print_reg_or_data(vm_command_t* command, int immediate, int start) {
  if(immediate) {
    uint32_t i = vm_getbits(command, start, 16);
    
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "0x%x", i);
    if(isprint(i & 0xff) && isprint((i>>8) & 0xff))
      mp_msg(MSGT_CPLAYER,MSGL_INFO, " (\"%c%c\")", (char)((i>>8) & 0xff), (char)(i & 0xff));
  } else {
    print_reg(vm_getbits(command, start - 8, 8));
  }
}

static void print_reg_or_data_2(vm_command_t* command, int immediate, int start) {
  if(immediate)
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "0x%x", vm_getbits(command, start - 1, 7));
  else
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "g[%" PRIu8 "]", vm_getbits(command, start - 4, 4));
}

static void print_reg_or_data_3(vm_command_t* command, int immediate, int start) {
  if(immediate) {
    uint32_t i = vm_getbits(command, start, 16);
    
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "0x%x", i);
    if(isprint(i & 0xff) && isprint((i>>8) & 0xff))
      mp_msg(MSGT_CPLAYER,MSGL_INFO, " (\"%c%c\")", (char)((i>>8) & 0xff), (char)(i & 0xff));
  } else {
    print_reg(vm_getbits(command, start, 8));
  }
}


static void print_if_version_1(vm_command_t* command) {
  uint8_t op = vm_getbits(command, 54, 3);
  
  if(op) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "if (");
    print_g_reg(vm_getbits(command,39,8));
    print_cmp_op(op);
    print_reg_or_data(command, vm_getbits(command, 55,1), 31);
    mp_msg(MSGT_CPLAYER,MSGL_INFO, ") ");
  }
}

static void print_if_version_2(vm_command_t* command) {
  uint8_t op = vm_getbits(command, 54, 3);
  
  if(op) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "if (");
    print_reg(vm_getbits(command, 15, 8));
    print_cmp_op(op);
    print_reg(vm_getbits(command, 7, 8));
    mp_msg(MSGT_CPLAYER,MSGL_INFO, ") ");
  }
}

static void print_if_version_3(vm_command_t* command) {
  uint8_t op = vm_getbits(command, 54, 3);
  
  if(op) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "if (");
    print_g_reg(vm_getbits(command, 43, 4));
    print_cmp_op(op);
    print_reg_or_data(command, vm_getbits(command, 55, 1), 15);
    mp_msg(MSGT_CPLAYER,MSGL_INFO, ") ");
  }
}

static void print_if_version_4(vm_command_t* command) {
  uint8_t op = vm_getbits(command, 54, 3);

  if(op) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "if (");
    print_g_reg(vm_getbits(command, 51, 4));
    print_cmp_op(op);
    print_reg_or_data(command, vm_getbits(command, 55, 1), 31);
    mp_msg(MSGT_CPLAYER,MSGL_INFO, ") ");
  }
}

static void print_if_version_5(vm_command_t* command) {
  uint8_t op = vm_getbits(command, 54, 3);
  int set_immediate = vm_getbits(command, 60, 1);
  
  if(op) {
    if (set_immediate) {
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "if (");
      print_g_reg(vm_getbits(command, 31, 8));
      print_cmp_op(op);
      print_reg(vm_getbits(command, 23, 8));
      mp_msg(MSGT_CPLAYER,MSGL_INFO, ") ");
    } else {
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "if (");
      print_g_reg(vm_getbits(command, 39, 8));
      print_cmp_op(op);
      print_reg_or_data(command, vm_getbits(command, 55, 1), 31);
      mp_msg(MSGT_CPLAYER,MSGL_INFO, ") ");
    }
  }
}

static void print_special_instruction(vm_command_t* command) {
  uint8_t op = vm_getbits(command, 51, 4);
  
  switch(op) {
    case 0: /*  NOP */
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "Nop");
      break;
    case 1: /*  Goto line */
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "Goto %" PRIu8, vm_getbits(command, 7, 8));
      break;
    case 2: /*  Break */
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "Break");
      break;
    case 3: /*  Parental level */
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "SetTmpPML %" PRIu8 ", Goto %" PRIu8, 
	      vm_getbits(command, 11, 4), vm_getbits(command, 7, 8));
      break;
    default:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "WARNING: Unknown special instruction (%i)", 
	      vm_getbits(command, 51, 4));
  }
}

static void print_linksub_instruction(vm_command_t* command) {
  uint32_t linkop = vm_getbits(command, 7, 8);
  uint32_t button = vm_getbits(command, 15, 6);
  
  if(linkop < sizeof(link_table)/sizeof(char *) && link_table[linkop] != NULL)
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "%s (button %" PRIu8 ")", link_table[linkop], button);
  else
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "WARNING: Unknown linksub instruction (%i)", linkop);
}

static void print_link_instruction(vm_command_t* command, int optional) {
  uint8_t op = vm_getbits(command, 51, 4);
  
  if(optional && op)
    mp_msg(MSGT_CPLAYER,MSGL_INFO, ", ");
  
  switch(op) {
    case 0:
      if(!optional)
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "WARNING: NOP (link)!");
      break;
    case 1:
      print_linksub_instruction(command);
      break;
    case 4:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "LinkPGCN %" PRIu16, vm_getbits(command, 14, 15));
      break;
    case 5:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "LinkPTT %" PRIu16 " (button %" PRIu8 ")", 
	      vm_getbits(command, 9, 10), vm_getbits(command, 15, 6));
      break;
    case 6:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "LinkPGN %" PRIu8 " (button %" PRIu8 ")", 
	      vm_getbits(command, 6, 7), vm_getbits(command, 15, 6));
      break;
    case 7:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "LinkCN %" PRIu8 " (button %" PRIu8 ")", 
	      vm_getbits(command, 7, 8), vm_getbits(command, 15, 6));
      break;
    default:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "WARNING: Unknown link instruction");
  }
}

static void print_jump_instruction(vm_command_t* command) {
  switch(vm_getbits(command, 51, 4)) {
    case 1:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "Exit");
      break;
    case 2:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "JumpTT %" PRIu8, vm_getbits(command, 22, 7));
      break;
    case 3:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "JumpVTS_TT %" PRIu8, vm_getbits(command, 22, 7));
      break;
    case 5:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "JumpVTS_PTT %" PRIu8 ":%" PRIu16, 
	      vm_getbits(command, 22, 7), vm_getbits(command, 41, 10));
      break;
    case 6:
      switch(vm_getbits(command, 23, 2)) {
        case 0:
          mp_msg(MSGT_CPLAYER,MSGL_INFO, "JumpSS FP");
          break;
        case 1:
          mp_msg(MSGT_CPLAYER,MSGL_INFO, "JumpSS VMGM (menu %" PRIu8 ")", vm_getbits(command, 19, 4));
          break;
        case 2:
          mp_msg(MSGT_CPLAYER,MSGL_INFO, "JumpSS VTSM (vts %" PRIu8 ", title %" PRIu8 
		  ", menu %" PRIu8 ")", vm_getbits(command, 30, 7), vm_getbits(command, 38, 7), vm_getbits(command, 19, 4));
          break;
        case 3:
          mp_msg(MSGT_CPLAYER,MSGL_INFO, "JumpSS VMGM (pgc %" PRIu8 ")", vm_getbits(command, 46, 15));
          break;
        }
      break;
    case 8:
      switch(vm_getbits(command, 23, 2)) {
        case 0:
          mp_msg(MSGT_CPLAYER,MSGL_INFO, "CallSS FP (rsm_cell %" PRIu8 ")",
              vm_getbits(command, 31, 8));
          break;
        case 1:
          mp_msg(MSGT_CPLAYER,MSGL_INFO, "CallSS VMGM (menu %" PRIu8 
		  ", rsm_cell %" PRIu8 ")", vm_getbits(command, 19, 4), vm_getbits(command, 31, 8));
          break;
        case 2:
          mp_msg(MSGT_CPLAYER,MSGL_INFO, "CallSS VTSM (menu %" PRIu8 
		  ", rsm_cell %" PRIu8 ")", vm_getbits(command, 19, 4), vm_getbits(command, 31, 8));
          break;
        case 3:
          mp_msg(MSGT_CPLAYER,MSGL_INFO, "CallSS VMGM (pgc %" PRIu8 ", rsm_cell %" PRIu8 ")", 
		  vm_getbits(command, 46, 15), vm_getbits(command, 31, 8));
          break;
      }
      break;
    default:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "WARNING: Unknown Jump/Call instruction");
  }
}

static void print_system_set(vm_command_t* command) {
  int i;
/* FIXME: What about SPRM11 ? Karaoke */
/*        Surely there must be some system set command for that ? */
  
  switch(vm_getbits(command, 59, 4)) {
    case 1: /*  Set system reg 1 &| 2 &| 3 (Audio, Subp. Angle) */
      for(i = 1; i <= 3; i++) {
        if(vm_getbits(command, 47 - (i*8), 1)) {
          print_system_reg(i);
          mp_msg(MSGT_CPLAYER,MSGL_INFO, " = ");
          print_reg_or_data_2(command, vm_getbits(command, 60, 1), 47 - (i*8) );
          mp_msg(MSGT_CPLAYER,MSGL_INFO, " ");
        }
      }
      break;
    case 2: /*  Set system reg 9 & 10 (Navigation timer, Title PGC number) */
      print_system_reg(9);
      mp_msg(MSGT_CPLAYER,MSGL_INFO, " = ");
      print_reg_or_data(command, vm_getbits(command, 60, 1), 47);
      mp_msg(MSGT_CPLAYER,MSGL_INFO, " ");
      print_system_reg(10);
      mp_msg(MSGT_CPLAYER,MSGL_INFO, " = %" PRIu16, vm_getbits(command, 30, 15)); /*  ?? */
      break;
    case 3: /*  Mode: Counter / Register + Set */
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "SetMode ");
      if(vm_getbits(command, 23, 1))
	mp_msg(MSGT_CPLAYER,MSGL_INFO, "Counter ");
      else
	mp_msg(MSGT_CPLAYER,MSGL_INFO, "Register ");
      print_g_reg(vm_getbits(command, 19, 4));
      print_set_op(0x1); /*  '=' */
      print_reg_or_data(command, vm_getbits(command, 60, 1), 47);
      break;
    case 6: /*  Set system reg 8 (Highlighted button) */
      print_system_reg(8);
      if(vm_getbits(command, 60, 1)) /*  immediate */
        mp_msg(MSGT_CPLAYER,MSGL_INFO, " = 0x%x (button no %d)", vm_getbits(command, 31, 16), vm_getbits(command, 31, 6));
      else
        mp_msg(MSGT_CPLAYER,MSGL_INFO, " = g[%" PRIu8 "]", vm_getbits(command, 19, 4));
      break;
    default:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "WARNING: Unknown system set instruction (%i)", 
	      vm_getbits(command, 59, 4));
  }
}

static void print_set_version_1(vm_command_t* command) {
  uint8_t set_op = vm_getbits(command, 59, 4);
  
  if(set_op) {
    print_g_reg(vm_getbits(command, 35, 4));
    print_set_op(set_op);
    print_reg_or_data(command, vm_getbits(command, 60, 1), 31);
  } else {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "NOP");
  }
}

static void print_set_version_2(vm_command_t* command) {
  uint8_t set_op = vm_getbits(command, 59, 4);
  
  if(set_op) {
    print_g_reg(vm_getbits(command, 51, 4));
    print_set_op(set_op);
    print_reg_or_data(command, vm_getbits(command, 60, 1), 47);
  } else {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "NOP");
  }
}

static void print_set_version_3(vm_command_t* command) {
  uint8_t set_op = vm_getbits(command, 59, 4);
  
  if(set_op) {
    print_g_reg(vm_getbits(command, 51, 4));
    print_set_op(set_op);
    print_reg_or_data_3(command, vm_getbits(command, 60, 1), 47);
  } else {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "NOP");
  }
}


void vm_print_mnemonic(vm_cmd_t *vm_command)  {
  vm_command_t command;
  command.instruction =( (uint64_t) vm_command->bytes[0] << 56 ) |
        ( (uint64_t) vm_command->bytes[1] << 48 ) |
        ( (uint64_t) vm_command->bytes[2] << 40 ) |
        ( (uint64_t) vm_command->bytes[3] << 32 ) |
        ( (uint64_t) vm_command->bytes[4] << 24 ) |
        ( (uint64_t) vm_command->bytes[5] << 16 ) |
        ( (uint64_t) vm_command->bytes[6] <<  8 ) |
          (uint64_t) vm_command->bytes[7] ;
  command.examined = 0; 

  switch(vm_getbits(&command,63,3)) { /* three first bits */
    case 0: /*  Special instructions */
      print_if_version_1(&command);
      print_special_instruction(&command);
      break;
    case 1: /*  Jump/Call or Link instructions */
      if(vm_getbits(&command,60,1)) {
        print_if_version_2(&command);
        print_jump_instruction(&command);
      } else {
        print_if_version_1(&command);
        print_link_instruction(&command, 0); /*  must be pressent */
      }
      break;
    case 2: /*  Set System Parameters instructions */
      print_if_version_2(&command);
      print_system_set(&command);
      print_link_instruction(&command, 1); /*  either 'if' or 'link' */
      break;
    case 3: /*  Set General Parameters instructions */
      print_if_version_3(&command);
      print_set_version_1(&command);
      print_link_instruction(&command, 1); /*  either 'if' or 'link' */
      break;
    case 4: /*  Set, Compare -> LinkSub instructions */
      print_set_version_2(&command);
      mp_msg(MSGT_CPLAYER,MSGL_INFO, ", ");
      print_if_version_4(&command);
      print_linksub_instruction(&command);
      break;
    case 5: /*  Compare -> (Set and LinkSub) instructions */
      print_if_version_5(&command);
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "{ ");
      print_set_version_3(&command);
      mp_msg(MSGT_CPLAYER,MSGL_INFO, ", ");
      print_linksub_instruction(&command);
      mp_msg(MSGT_CPLAYER,MSGL_INFO, " }");
      break;
    case 6: /*  Compare -> Set, always LinkSub instructions */
      print_if_version_5(&command);
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "{ ");
      print_set_version_3(&command);
      mp_msg(MSGT_CPLAYER,MSGL_INFO, " } ");
      print_linksub_instruction(&command);
      break;
    default:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "WARNING: Unknown instruction type (%i)", vm_getbits(&command, 63, 3));
  }
  /*  Check if there still are bits set that were not examined */
  
  if(command.instruction & ~ command.examined) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, " libdvdnav: vmcmd.c: [WARNING, unknown bits:");
    mp_msg(MSGT_CPLAYER,MSGL_INFO, " %08llx", (command.instruction & ~ command.examined) );
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "]");
  }
}

void vm_print_cmd(int row, vm_cmd_t *vm_command) {
  int i;

  mp_msg(MSGT_CPLAYER,MSGL_INFO, "(%03d) ", row + 1);
  for(i = 0; i < 8; i++)
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "%02x ", vm_command->bytes[i]);
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "| ");

  vm_print_mnemonic(vm_command);
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "\n");
}

// ######################################## vmcmd.c end #############################################

// ######################################### vm.c start #############################################
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 USE_MPDVDNAV_TRACE
static void vm_print_current_domain_state(vm_t *vm) {
  switch((vm->state).domain) {
    case VTS_DOMAIN:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Video Title Domain: -\n");
      break;

    case VTSM_DOMAIN:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Video Title Menu Domain: -\n");
      break;

    case VMGM_DOMAIN:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Video Manager Menu Domain: -\n");
      break;

    case FP_DOMAIN: 
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: First Play Domain: -\n");
      break;

    default:
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Unknown Domain: -\n");
      break;
  }
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "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

#ifdef USE_MPDVDNAV
int vm_get_PGCN(vm_t *vm) { return get_PGCN(vm); }
#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 )) {
	  mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCDVDTitle);
          for(i=25; i < 73; i++ ) {
            if((data[i] == 0)) break;
            if((data[i] > 32) && (data[i] < 127)) {
	  mp_msg(MSGT_CPLAYER,MSGL_INFO, "%c", data[i]);
            } else {
	      mp_msg(MSGT_CPLAYER,MSGL_INFO, " ");
            }
          }
          strncpy(name, &data[25], 48);
          name[48] = 0;
	  mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCDVDSerialNumber);
          for(i=73; i < 89; i++ ) {
            if((data[i] == 0)) break;
            if((data[i] > 32) && (data[i] < 127)) {
	      mp_msg(MSGT_CPLAYER,MSGL_INFO, "%c", data[i]);
            } else {
	      mp_msg(MSGT_CPLAYER,MSGL_INFO, " ");
            } 
          }
	  mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCDVDTitleAlternative);
          for(i=89; i < 128; i++ ) {
            if((data[i] == 0)) break;
            if((data[i] > 32) && (data[i] < 127)) {
	      mp_msg(MSGT_CPLAYER,MSGL_INFO, "%c", data[i]);
            } else {
	      mp_msg(MSGT_CPLAYER,MSGL_INFO, " ");
            }
          }
	  mp_msg(MSGT_CPLAYER,MSGL_INFO, "\n");
        } else {
	  mp_msg(MSGT_CPLAYER,MSGL_INFO, MSGTR_LIBMPDVDNAV_VmCCantReadNameBlock);
        }
      } else {
        mp_msg(MSGT_CPLAYER,MSGL_INFO, MSGTR_LIBMPDVDNAV_VmCCantSeekToBlock);
      }
      close(fd);
    } else {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, MSGTR_LIBMPDVDNAV_VmCNameOpenFailed);
  }
}

static void ifoOpenNewVTSI(vm_t *vm, dvd_reader_t *dvd, int vtsN) {
  if((vm->state).vtsN == vtsN) {
    return; /*  We alread have it */
  }
  
  if(vm->vtsi != NULL)
    ifoClose(vm->vtsi);
  
  vm->vtsi = ifoOpenVTSI(dvd, vtsN);
  if(vm->vtsi == NULL) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCIfoOpenVTSIFailed);
    assert(0);
  }
  if(!ifoRead_VTS_PTT_SRPT(vm->vtsi)) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCIfoReadVTSPTTSRPTFailed);
    assert(0);
  }
  if(!ifoRead_PGCIT(vm->vtsi)) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCIfoReadPGCITFailed);
    assert(0);
  }
  if(!ifoRead_PGCI_UT(vm->vtsi)) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCIfoReadPGCIUTFailed);
    assert(0);
  }
  if(!ifoRead_VOBU_ADMAP(vm->vtsi)) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCIfoReadVOBUADMAPVtsiFailed);
    assert(0);
  }
  if(!ifoRead_TITLE_VOBU_ADMAP(vm->vtsi)) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCIfoReadTITLEVOBUADMAPVtsiFailed);
    assert(0);
  }
  (vm->state).vtsN = vtsN;
}


/* 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 */

void vm_start(vm_t *vm) {
  /* Set pgc to FP (First Play) pgc */
  set_FP_PGC(vm);
  process_command(vm, play_PGC(vm));
}

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) {
    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) {
    vm->dvd = DVDOpen(dvdroot);
    if(!vm->dvd) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCVmFaildToOpenReadTheDVD);
      return 0;
    }
    dvd_read_name(vm->dvd_name, dvdroot);
    vm->map  = remap_loadmap(vm->dvd_name);
    vm->vmgi = ifoOpenVMGI(vm->dvd);
    if(!vm->vmgi) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCVmFaildToReadVIDEOTSIFO);
      return 0;
    }
    if(!ifoRead_FP_PGC(vm->vmgi)) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCVmIfoReadFPPGCFailed);
      return 0;
    }
    if(!ifoRead_TT_SRPT(vm->vmgi)) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCVmIfoReadTTSRPTFailed);
      return 0;
    }
    if(!ifoRead_PGCI_UT(vm->vmgi)) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCVmIfoReadPGCIUTFailed);
      return 0;
    }
    if(!ifoRead_PTL_MAIT(vm->vmgi)) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCVmIfoReadPRLMAITFailed);
      /* return 0; Not really used for now.. */
    }
    if(!ifoRead_VTS_ATRT(vm->vmgi)) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCVmIfoReadVTSATRTFailed);
      /* return 0; Not really used for now.. */
    }
    if(!ifoRead_VOBU_ADMAP(vm->vmgi)) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCVmIfoReadVOBUADMAPVgmiFailed);
      /* return 0; Not really used for now.. */
    }
    /* ifoRead_TXTDT_MGI(vmgi); Not implemented yet */
  }
  if (vm->vmgi) {
    int i, mask;
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCDVDDiskReportsItselfWithRegionMask,
      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)
	mp_msg(MSGT_CPLAYER,MSGL_INFO, " %d", i);
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "\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;
    ifoOpenNewVTSI(target, target->dvd, vtsN);
  
    /* 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) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCChapterNotFound);
    return 0;
  }

  title = get_TT(vm, vm->state.vtsN, vts_ttn);

#ifdef USE_MPDVDNAV_TRACE
  if (dvdnav_trace) {
  if (title) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: ************ this chapter FOUND!\n");
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "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 */ 
#ifdef USE_MPDVDKIT
    if( (vm->state).pgc->audio_control[audioN].present) {
      streamN = (vm->state).pgc->audio_control[audioN].s_audio;  
    }
#else
    if((vm->state).pgc->audio_control[audioN] & (1<<15)) {
      streamN = ((vm->state).pgc->audio_control[audioN] >> 8) & 0x07;  
    }
#endif
  }
  
  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 */ 
#ifdef USE_MPDVDKIT
    if((vm->state).pgc->subp_control[subpN].present) {
      if(source_aspect == 0) /* 4:3 */	     
	streamN = (vm->state).pgc->subp_control[subpN].s_4p3;  
      if(source_aspect == 3) /* 16:9 */
        switch (mode) {
	case 0:
	  streamN = (vm->state).pgc->subp_control[subpN].s_wide;
	  break;
	case 1:
	  streamN = (vm->state).pgc->subp_control[subpN].s_lbox;
	  break;
	case 2:
	  streamN = (vm->state).pgc->subp_control[subpN].s_panscan;
	}
    }
#else
    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;
	}
    }
#endif
  }
  
 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++) {
#ifdef USE_MPDVDKIT
      if((vm->state).pgc->audio_control[audioN].present) {
#else
      if((vm->state).pgc->audio_control[audioN] & (1<<15)) {
#endif
        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++) {
#ifdef USE_MPDVDKIT
      if((vm->state).pgc->subp_control[subpN].present) {
#else
      if((vm->state).pgc->subp_control[subpN] & (1<<31)) {
#endif
        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

#if 0
int vm_get_audio_id(vm_t *vm, int streamN) {
  switch ((vm->state).domain) {
  case VTS_DOMAIN:
    return vm->vtsi->vtsi_mat->vts_audio_attr[streamN].s_audio;
  case VTSM_DOMAIN:
    return vm->vtsi->vtsi_mat->vtsm_audio_attr.s_audio;
  case VMGM_DOMAIN:
  case FP_DOMAIN:
    return vm->vmgi->vmgi_mat->vmgm_audio_attr.s_audio;;
  default:
    abort();
  }
}
#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 USE_MPDVDNAV_TRACE
  if (dvdnav_trace) {
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: play_PGC:");
  if((vm->state).domain != FP_DOMAIN) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, " (vm->state).pgcN (%i)\n", get_PGCN(vm));
  } else {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, " 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 USE_MPDVDNAV_TRACE
  if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "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 USE_MPDVDNAV_TRACE
  if (dvdnav_trace) {
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: play_PGC_PG:");
  if((vm->state).domain != FP_DOMAIN) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, " (vm->state).pgcN (%i)\n", get_PGCN(vm));
  } else {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, " 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 USE_MPDVDNAV_TRACE
  if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "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 USE_MPDVDNAV_TRACE
    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "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 USE_MPDVDNAV_TRACE
    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "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. */
#ifndef USE_MPDVDNAV
  if(!set_PGCN(vm, (vm->state).pgc->next_pgc_nr)) {
    link_values.command = Exit;
    return link_values;
  }
#endif
  return play_PGC(vm);
}

static link_t play_PG(vm_t *vm) {
#ifdef USE_MPDVDNAV_TRACE
    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "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 USE_MPDVDNAV_TRACE
    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "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 USE_MPDVDNAV_TRACE
  if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "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 USE_MPDVDNAV_TRACE
  if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "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)) {
	mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCInvalidAngleBlock);
	(vm->state).cellN -= (vm->state).AGL_REG - 1;
      }
#endif
      break;
    case 2: /*  ?? */
    case 3: /*  ?? */
    default:
      mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCInvalidBlockMode,
	      (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:
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCCellIsInBlockButDidNotEnter);
  }
  
  /* 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 USE_MPDVDNAV_TRACE
  if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Cell should restart here\n");
#endif
  return play_this;
}

static link_t play_Cell_post(vm_t *vm) {
  cell_playback_t *cell;
  
#ifdef USE_MPDVDNAV_TRACE
  if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "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 USE_MPDVDNAV_TRACE
      if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "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 USE_MPDVDNAV_TRACE
  if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Cell command didn't do a Jump, Link or Call\n");
#endif
      }
    } else {
#ifdef USE_MPDVDNAV_TRACE
  if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "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:
      mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCInvalidBlockMode,
	      (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 USE_MPDVDNAV_TRACE
  if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "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 USE_MPDVDNAV_TRACE
  if (dvdnav_trace) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Before printout starts:\n");
    vm_print_link(link_values);
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "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);
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "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))
	assert(0);
      link_values = play_PGC(vm);
      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))
	assert(0);
      link_values = play_PGC(vm);
      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))
	assert(0);
      link_values = play_PGC(vm);
      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) {
          mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCTryingToResumeWithoutAnyResume);
	  link_values.command = Exit;
	  break;
	}
	
	(vm->state).domain = VTS_DOMAIN;
	ifoOpenNewVTSI(vm, vm->dvd, (vm->state).rsm_vtsN);
	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 righ value in set_PGN() below */
	  (vm->state).cellN = (vm->state).rsm_cellN;
	  link_values.command = PlayThis;
	  link_values.data1 = (vm->state).rsm_blockN;
	  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))
	assert(0);
      link_values = play_PGC(vm);
      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;
	  ifoOpenNewVTSI(vm, vm->dvd, link_values.data1);  /* Also sets (vm->state).vtsN */
	} 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 USE_MPDVDNAV_TRACE
  if (dvdnav_trace) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: After printout starts:\n");
    vm_print_current_domain_state(vm);
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: After printout ends.\n");
    }
#endif
    
  }
  (vm->state).blockN = link_values.data1;
  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)
    ifoOpenNewVTSI(vm, vm->dvd, vtsN);  /* Also sets (vm->state).vtsN */
  
  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;
  (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 USE_MPDVDNAV_TRACE
  if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "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. */
      mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCRandomOrShuffleTitlesAreNotHandledYet);
    }
  }
  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 USE_MPDVDNAV_TRACE
  if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "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 USE_MPDVDNAV_TRACE
  if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Found menu.\n");
#endif
      return pgcN;
    }
  }
#ifdef USE_MPDVDNAV_TRACE
  if (dvdnav_trace) {
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "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) {
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "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++;
    }
  }
  mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCGetPGCNFailed,
         (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) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCPgciUtHandleIsNULL);
    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) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCLanguageNotFound,
	    (char)(lang >> 8), (char)(lang & 0xff),
 	    (char)(h->pgci_ut->lu[0].lang_code >> 8),
	    (char)(h->pgci_ut->lu[0].lang_code & 0xff));
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_VmCMenuLanguagesAvailable);
    for(i = 0; i < h->pgci_ut->nr_of_lus; i++) {
      mp_msg(MSGT_CPLAYER,MSGL_INFO, "%c%c ",
 	    (char)(h->pgci_ut->lu[i].lang_code >> 8),
	    (char)(h->pgci_ut->lu[i].lang_code & 0xff));
    }
    mp_msg(MSGT_CPLAYER,MSGL_INFO, "\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 USE_MPDVDNAV_TRACE
void vm_position_print(vm_t *vm, vm_position_t *position) {
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "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

// ########################################## vm.c end ##############################################

// ####################################### dvdnav.c start ###########################################
static dvdnav_status_t dvdnav_clear(dvdnav_t * this) {
  /* clear everything except file, vm, mutex, readahead */

  if (this->file) DVDCloseFile(this->file);
  this->file = NULL;

  memset(&this->pci,0,sizeof(this->pci));
  memset(&this->dsi,0,sizeof(this->dsi));
  this->last_cmd_nav_lbn = SRI_END_OF_CELL;

  /* Set initial values of flags */
  this->position_current.still = 0;
  this->skip_still = 0;
  this->sync_wait = 0;
  this->sync_wait_skip = 0;
  this->spu_clut_changed = 0;
  this->started = 0;

  dvdnav_read_cache_clear(this->cache);
  
  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_open(dvdnav_t** dest, const char *path) {
  dvdnav_t *this;
  struct timeval time;
  
  /* Create a new structure */
  mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_Using, DVDNAVSVERSION);

  (*dest) = NULL;
  this = (dvdnav_t*)malloc(sizeof(dvdnav_t));
  if(!this)
    return DVDNAV_STATUS_ERR;
  memset(this, 0, (sizeof(dvdnav_t) ) ); /* Make sure this structure is clean */
 
  pthread_mutex_init(&this->vm_lock, NULL);

  /* Initialise the VM */
  this->vm = vm_new_vm();
  if(!this->vm) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ErrorInitialisingTheDVDVM);
    pthread_mutex_destroy(&this->vm_lock);
    free(this);
    return DVDNAV_STATUS_ERR;
  }
  if(!vm_reset(this->vm, path)) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ErrorStartingTheVM);
    pthread_mutex_destroy(&this->vm_lock);
    vm_free_vm(this->vm);
    free(this);
    return DVDNAV_STATUS_ERR;
  }

  /* Set the path. FIXME: Is a deep copy 'right' */
  strncpy(this->path, path, MAX_PATH_LEN);

  /* Pre-open and close a file so that the CSS-keys are cached. */
  this->file = DVDOpenFile(vm_get_dvd_reader(this->vm), 0, DVD_READ_MENU_VOBS);
    
  /* Start the read-ahead cache. */
  this->cache = dvdnav_read_cache_new(this);

  /* Seed the random numbers. So that the DVD VM Command rand()
   * gives a different start value each time a DVD is played. */
  gettimeofday(&time, NULL);
  srand(time.tv_usec);
 
  dvdnav_clear(this);
 
  (*dest) = this;
  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_close(dvdnav_t *this) {

#ifdef LOG_DEBUG
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: close:called\n");
#endif

  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  if (this->file) {
    DVDCloseFile(this->file);
#ifdef LOG_DEBUG
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: close:file closing\n");
#endif
    this->file = NULL;
  }

  /* Free the VM */
  if(this->vm)
    vm_free_vm(this->vm);

  pthread_mutex_destroy(&this->vm_lock);

  /* We leave the final freeing of the entire structure to the cache,
   * because we don't know, if there are still buffers out in the wild,
   * that must return first. */
  if(this->cache)
    dvdnav_read_cache_free(this->cache);
  else
    free(this);
  
  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_reset(dvdnav_t *this) {
  dvdnav_status_t result;

#ifdef LOG_DEBUG
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: reset:called\n");
#endif

  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  pthread_mutex_lock(&this->vm_lock); 

#ifdef LOG_DEBUG
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: reseting vm\n");
#endif
  if(!vm_reset(this->vm, NULL)) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ErrorRestartingTheVM);
    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_ERR;
  }
#ifdef LOG_DEBUG
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: clearing dvdnav\n");
#endif
  result = dvdnav_clear(this);

  pthread_mutex_unlock(&this->vm_lock); 
  return result;
}

dvdnav_status_t dvdnav_path(dvdnav_t *this, const char** path) {

  if(!this || !path) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  (*path) = this->path;

  return DVDNAV_STATUS_OK;
}

const char* dvdnav_err_to_string(dvdnav_t *this) {
  
  if(!this)
    return "Hey! You gave me a NULL pointer you naughty person!";
  
  return this->err_str;
}

/* converts a dvd_time_t to PTS ticks */
static int64_t dvdnav_convert_time(dvd_time_t *time) {
  int64_t result;
  int64_t frames;
  
  result  = (time->hour    >> 4  ) * 10 * 60 * 60 * 90000;
  result += (time->hour    & 0x0f)      * 60 * 60 * 90000;
  result += (time->minute  >> 4  )      * 10 * 60 * 90000;
  result += (time->minute  & 0x0f)           * 60 * 90000;
  result += (time->second  >> 4  )           * 10 * 90000;
  result += (time->second  & 0x0f)                * 90000;
  
  frames  = ((time->frame_u & 0x30) >> 4) * 10;
  frames += ((time->frame_u & 0x0f)     )     ;
  
  if (time->frame_u & 0x80)
    result += frames * 3000;
  else
    result += frames * 3600;
  
  return result;
}

/*
 * Returns 1 if block contains NAV packet, 0 otherwise.
 * Processes said NAV packet if present.
 *
 * Most of the code in here is copied from xine's MPEG demuxer
 * so any bugs which are found in that should be corrected here also.
 */
static int32_t dvdnav_decode_packet(dvdnav_t *this, uint8_t *p, dsi_t *nav_dsi, pci_t *nav_pci) {
  int32_t        bMpeg1 = 0;
  uint32_t       nHeaderLen;
  uint32_t       nPacketLen;
  uint32_t       nStreamID;

  if (p[3] == 0xBA) { /* program stream pack header */
    int32_t nStuffingBytes;

    bMpeg1 = (p[4] & 0x40) == 0;

    if (bMpeg1) {
      p += 12;
    } else { /* mpeg2 */
      nStuffingBytes = p[0xD] & 0x07;
      p += 14 + nStuffingBytes;
    }
  }

  if (p[3] == 0xbb) { /* program stream system header */
    nHeaderLen = (p[4] << 8) | p[5];
    p += 6 + nHeaderLen;
  }

  /* we should now have a PES packet here */
  if (p[0] || p[1] || (p[2] != 1)) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_Demux_error,p[0],p[1],p[2]);
    return 0;
  }

  nPacketLen = p[4] << 8 | p[5];
  nStreamID  = p[3];

  nHeaderLen = 6;
  p += nHeaderLen;

  if (nStreamID == 0xbf) { /* Private stream 2 */
#if 0
    int32_t i;
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: nav packet=%u\n",p-p_start-6);
    for(i=0;i<80;i++)
      mp_msg(MSGT_FIXME,MSGL_FIXME, "%02x ",p[i-6]);
    mp_msg(MSGT_FIXME,MSGL_FIXME, "\n");
#endif

    if(p[0] == 0x00) {
      navRead_PCI(nav_pci, p+1);
    }

    p += nPacketLen;

    /* We should now have a DSI packet. */
    if(p[6] == 0x01) {
      nPacketLen = p[4] << 8 | p[5];
      p += 6;
      navRead_DSI(nav_dsi, p+1);
    } 
    return 1;
  }
  return 0;
}

/* DSI is used for most angle stuff. 
 * PCI is used for only non-seemless angle stuff
 */ 
static int32_t dvdnav_get_vobu(dvdnav_t *this, dsi_t *nav_dsi, pci_t *nav_pci, dvdnav_vobu_t *vobu) {
  uint32_t next;
  int32_t angle, num_angle;

  vobu->vobu_start = nav_dsi->dsi_gi.nv_pck_lbn; /* Absolute offset from start of disk */
  vobu->vobu_length = nav_dsi->dsi_gi.vobu_ea; /* Relative offset from vobu_start */
     
  /*
   * If we're not at the end of this cell, we can determine the next
   * VOBU to display using the VOBU_SRI information section of the
   * DSI.  Using this value correctly follows the current angle,
   * avoiding the doubled scenes in The Matrix, and makes our life
   * really happy.
   *
   * vobu_next is an offset value, 0x3fffffff = SRI_END_OF_CELL
   * DVDs are about 6 Gigs, which is only up to 0x300000 blocks
   * Should really assert if bit 31 != 1
   */
  
#if 0
  /* Old code -- may still be useful one day */
  if(nav_dsi->vobu_sri.next_vobu != SRI_END_OF_CELL ) {
    vobu->vobu_next = ( nav_dsi->vobu_sri.next_vobu & 0x3fffffff );
  } else {
    vobu->vobu_next = vobu->vobu_length;
  }
#else
  /* Relative offset from vobu_start */
  vobu->vobu_next = ( nav_dsi->vobu_sri.next_vobu & 0x3fffffff );
#endif
  
  vm_get_angle_info(this->vm, &angle, &num_angle);

  /* FIMXE: The angle reset doesn't work for some reason for the moment */
#if 0
  if((num_angle < angle) && (angle != 1)) {
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: angle ends!\n");
    
    /* This is to switch back to angle one when we
     * finish with angles. */
    dvdnav_angle_change(this, 1);
  } 
#endif

  if(num_angle != 0) {
    
    if((next = nav_pci->nsml_agli.nsml_agl_dsta[angle-1]) != 0) {
      if((next & 0x3fffffff) != 0) {
	if(next & 0x80000000)
	  vobu->vobu_next = - (int32_t)(next & 0x3fffffff);
	else
	  vobu->vobu_next = + (int32_t)(next & 0x3fffffff);
      }
    } else if((next = nav_dsi->sml_agli.data[angle-1].address) != 0) {
      vobu->vobu_length = nav_dsi->sml_pbi.ilvu_ea;
      
      if((next & 0x80000000) && (next != 0x7fffffff))
	vobu->vobu_next =  - (int32_t)(next & 0x3fffffff);
      else
	vobu->vobu_next =  + (int32_t)(next & 0x3fffffff);
    }
  }

  return 1;
}

/*
 * These are the main get_next_block function which actually get the media stream video and audio etc.
 *
 * There are two versions: The second one is using the zero-copy read ahead cache and therefore
 * hands out pointers targetting directly into the cache.
 * The first one uses a memcopy to fill this cache block into the application provided memory.
 * The benefit of this first one is that no special memory management is needed. The application is
 * the only one responsible of allocating and freeing the memory associated with the pointer.
 * The drawback is the additional memcopy.
 */

dvdnav_status_t dvdnav_get_next_block(dvdnav_t *this, uint8_t *buf,
				      int32_t *event, int32_t *len) {
  unsigned char *block;
  dvdnav_status_t status;
  
  block = buf;
  status = dvdnav_get_next_cache_block(this, &block, event, len);
  if (status == DVDNAV_STATUS_OK && block != buf) {
    /* we received a block from the cache, copy it, so we can give it back */
    memcpy(buf, block, DVD_VIDEO_LB_LEN);
    dvdnav_free_cache_block(this, block);
  }
  return status;
}
 
dvdnav_status_t dvdnav_get_next_cache_block(dvdnav_t *this, uint8_t **buf,
					    int32_t *event, int32_t *len) {
  dvd_state_t *state;
  int32_t result;

  if(!this || !event || !len || !buf || !*buf) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  pthread_mutex_lock(&this->vm_lock);
  
  if(!this->started) {
    /* Start the VM */
    vm_start(this->vm);
    this->started = 1;
  }

  state = &(this->vm->state);
  (*event) = DVDNAV_NOP;
  (*len) = 0;
 
  /* Check the STOP flag */
  if(this->vm->stopped) {
    vm_stop(this->vm);
    (*event) = DVDNAV_STOP;
    this->started = 0;
    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_OK;
  }

  vm_position_get(this->vm, &this->position_next);
  
#ifdef LOG_DEBUG
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: POS-NEXT ");
  vm_position_print(this->vm, &this->position_next);
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: POS-CUR  ");
  vm_position_print(this->vm, &this->position_current);
#endif

  /* did we hop? */
  if(this->position_current.hop_channel != this->position_next.hop_channel) {
    (*event) = DVDNAV_HOP_CHANNEL;
#ifdef LOG_DEBUG
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: HOP_CHANNEL\n");
#endif
    if (this->position_next.hop_channel - this->position_current.hop_channel >= HOP_SEEK) {
      int32_t num_angles = 0, current;
      
      /* we seeked -> check for multiple angles */
      vm_get_angle_info(this->vm, &current, &num_angles);
      if (num_angles > 1) {
        int32_t result, block;
	/* we have to skip the first VOBU when seeking in a multiangle feature,
	 * because it might belong to the wrong angle */
	block = this->position_next.cell_start + this->position_next.block;
	result = dvdnav_read_cache_block(this->cache, block, 1, buf);
	if(result <= 0) {
	  mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ErrorReadingNAVPacket);
	  pthread_mutex_unlock(&this->vm_lock); 
	  return DVDNAV_STATUS_ERR;
	}
	/* Decode nav into pci and dsi. Then get next VOBU info. */
	if(!dvdnav_decode_packet(this, *buf, &this->dsi, &this->pci)) {
	  mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ExpectedNAVPacketButNoneFound);
	  pthread_mutex_unlock(&this->vm_lock); 
	  return DVDNAV_STATUS_ERR;
	}
	dvdnav_get_vobu(this, &this->dsi, &this->pci, &this->vobu);
	/* skip to next, if there is a next */
	if (this->vobu.vobu_next != SRI_END_OF_CELL) {
	  this->vobu.vobu_start += this->vobu.vobu_next;
	  this->vobu.vobu_next   = 0;
	}
	/* update VM state */
	this->vm->state.blockN = this->vobu.vobu_start - this->position_next.cell_start;
      }
    }
    this->position_current.hop_channel = this->position_next.hop_channel;
    /* update VOBU info */
    this->vobu.vobu_start  = this->position_next.cell_start + this->position_next.block;
    this->vobu.vobu_next   = 0;
    /* Make blockN == vobu_length to do expected_nav */
    this->vobu.vobu_length = 0;
    this->vobu.blockN      = 0;
    this->sync_wait        = 0;
    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_OK;
  }

  /* Check the HIGHLIGHT flag */
  if(this->position_current.button != this->position_next.button) {
    dvdnav_highlight_event_t *hevent = (dvdnav_highlight_event_t *)*buf;

    (*event) = DVDNAV_HIGHLIGHT;
#ifdef LOG_DEBUG
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: HIGHLIGHT\n");
#endif
    (*len) = sizeof(dvdnav_highlight_event_t);
    hevent->display = 1;
    hevent->buttonN = this->position_next.button;
    this->position_current.button = this->position_next.button;
    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_OK;
  }
  
  /* Check the WAIT flag */
  if(this->sync_wait) {
    (*event) = DVDNAV_WAIT;
#ifdef LOG_DEBUG
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: WAIT\n");
#endif
    (*len) = 0;
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_OK;
  }

  /* Check to see if we need to change the currently opened VOB */
  if((this->position_current.vts != this->position_next.vts) || 
     (this->position_current.domain != this->position_next.domain)) {
    dvd_read_domain_t domain;
    int32_t vtsN;
    dvdnav_vts_change_event_t *vts_event = (dvdnav_vts_change_event_t *)*buf;
    
    if(this->file) {
      DVDCloseFile(this->file);
      this->file = NULL;
    }

    vts_event->old_vtsN = this->position_current.vts;
    vts_event->old_domain = this->position_current.domain;
     
    /* Use the DOMAIN to find whether to open menu or title VOBs */
    switch(this->position_next.domain) {
    case FP_DOMAIN:
    case VMGM_DOMAIN:
      domain = DVD_READ_MENU_VOBS;
      vtsN = 0;
      break;
    case VTSM_DOMAIN:
      domain = DVD_READ_MENU_VOBS;
      vtsN = this->position_next.vts; 
      break;
    case VTS_DOMAIN:
      domain = DVD_READ_TITLE_VOBS;
      vtsN = this->position_next.vts; 
      break;
    default:
      mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_UnknownDomainWhenChangingVTS);
      pthread_mutex_unlock(&this->vm_lock); 
      return DVDNAV_STATUS_ERR;
    }
    
    this->position_current.vts = this->position_next.vts; 
    this->position_current.domain = this->position_next.domain;
    dvdnav_read_cache_clear(this->cache);
    this->file = DVDOpenFile(vm_get_dvd_reader(this->vm), vtsN, domain);
    vts_event->new_vtsN = this->position_next.vts; 
    vts_event->new_domain = this->position_next.domain; 

    /* If couldn't open the file for some reason, moan */
    if(this->file == NULL) {
      mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ErrorOpeningVtsDomain, vtsN, domain);
      pthread_mutex_unlock(&this->vm_lock); 
      return DVDNAV_STATUS_ERR;
    }

    /* File opened successfully so return a VTS change event */
    (*event) = DVDNAV_VTS_CHANGE;
#ifdef LOG_DEBUG
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: VTS_CHANGE\n");
#endif
    (*len) = sizeof(dvdnav_vts_change_event_t);

    this->spu_clut_changed = 1;
    this->position_current.cell = -1; /* Force an update */
    this->position_current.spu_channel = -1; /* Force an update */
    this->position_current.audio_channel = -1; /* Force an update */;
     
    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_OK;
  }

  /* Check if the cell changed */  
  if( (this->position_current.cell != this->position_next.cell) ||
      (this->position_current.cell_restart != this->position_next.cell_restart) ||
      (this->position_current.cell_start != this->position_next.cell_start) ) {
    dvdnav_cell_change_event_t *cell_event = (dvdnav_cell_change_event_t *)*buf;
    int32_t first_cell_nr, last_cell_nr, i;
    dvd_state_t *state = &this->vm->state;
    
    (*event) = DVDNAV_CELL_CHANGE;
#ifdef LOG_DEBUG
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: CELL_CHANGE\n");
#endif
    (*len) = sizeof(dvdnav_cell_change_event_t);
    
    cell_event->cellN = state->cellN;
    cell_event->pgN   = state->pgN;
    cell_event->cell_length =
      dvdnav_convert_time(&state->pgc->cell_playback[state->cellN-1].playback_time);

    cell_event->pg_length = 0;
    /* Find start cell of program. */
    first_cell_nr = state->pgc->program_map[state->pgN-1];
    /* Find end cell of program */
    if(state->pgN < state->pgc->nr_of_programs)
      last_cell_nr = state->pgc->program_map[state->pgN] - 1;
    else
      last_cell_nr = state->pgc->nr_of_cells;
    for (i = first_cell_nr; i <= last_cell_nr; i++)
      cell_event->pg_length +=
        dvdnav_convert_time(&state->pgc->cell_playback[i - 1].playback_time);
    cell_event->pgc_length = dvdnav_convert_time(&state->pgc->playback_time);

    cell_event->cell_start = 0;
    for (i = 1; i < state->cellN; i++)
      cell_event->cell_start +=
        dvdnav_convert_time(&state->pgc->cell_playback[i - 1].playback_time);

    cell_event->pg_start = 0;
    for (i = 1; i < state->pgc->program_map[state->pgN-1]; i++)
      cell_event->pg_start +=
        dvdnav_convert_time(&state->pgc->cell_playback[i - 1].playback_time);

    this->position_current.cell         = this->position_next.cell;
    this->position_current.cell_restart = this->position_next.cell_restart;
    this->position_current.cell_start   = this->position_next.cell_start;
    this->position_current.block        = this->position_next.block;
    
    /* vobu info is used for mid cell resumes */
    this->vobu.vobu_start               = this->position_next.cell_start + this->position_next.block;
    this->vobu.vobu_next                = 0;
    /* Make blockN == vobu_length to do expected_nav */
    this->vobu.vobu_length = 0;
    this->vobu.blockN      = 0;
    
    /* update the spu palette at least on PGC changes */
    this->spu_clut_changed = 1;
    this->position_current.spu_channel = -1; /* Force an update */
    this->position_current.audio_channel = -1; /* Force an update */

    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_OK;
  }
 
  /* has the CLUT changed? */
  if(this->spu_clut_changed) {
    (*event) = DVDNAV_SPU_CLUT_CHANGE;
#ifdef LOG_DEBUG
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: SPU_CLUT_CHANGE\n");
#endif
    (*len) = 16 * sizeof(uint32_t);
    memcpy(*buf, &(state->pgc->palette), 16 * sizeof(uint32_t));
    this->spu_clut_changed = 0;
    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_OK;
  }

  /* has the SPU channel changed? */  
  if(this->position_current.spu_channel != this->position_next.spu_channel) {
    dvdnav_spu_stream_change_event_t *stream_change = (dvdnav_spu_stream_change_event_t *)*buf;

    (*event) = DVDNAV_SPU_STREAM_CHANGE;
#ifdef LOG_DEBUG
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: SPU_STREAM_CHANGE\n");
#endif
    (*len) = sizeof(dvdnav_spu_stream_change_event_t);
    stream_change->physical_wide      = vm_get_subp_active_stream(this->vm, 0);
    stream_change->physical_letterbox = vm_get_subp_active_stream(this->vm, 1);
    stream_change->physical_pan_scan  = vm_get_subp_active_stream(this->vm, 2);
    this->position_current.spu_channel = this->position_next.spu_channel;
#ifdef LOG_DEBUG
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: SPU_STREAM_CHANGE stream_id_wide=%d\n",stream_change->physical_wide);
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: SPU_STREAM_CHANGE stream_id_letterbox=%d\n",stream_change->physical_letterbox);
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: SPU_STREAM_CHANGE stream_id_pan_scan=%d\n",stream_change->physical_pan_scan);
#endif
    if (stream_change->physical_wide != -1 &&
        stream_change->physical_letterbox != -1 &&
        stream_change->physical_pan_scan != -1) {
#ifdef LOG_DEBUG
      mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: SPU_STREAM_CHANGE returning DVDNAV_STATUS_OK\n");
#endif
      pthread_mutex_unlock(&this->vm_lock); 
      return DVDNAV_STATUS_OK;
    }
  }

  /* has the audio channel changed? */  
  if(this->position_current.audio_channel != this->position_next.audio_channel) {
    dvdnav_audio_stream_change_event_t *stream_change = (dvdnav_audio_stream_change_event_t *)*buf;
    
    (*event) = DVDNAV_AUDIO_STREAM_CHANGE;
#ifdef LOG_DEBUG
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: AUDIO_STREAM_CHANGE\n");
#endif
    (*len) = sizeof(dvdnav_audio_stream_change_event_t);
    stream_change->physical = vm_get_audio_active_stream( this->vm );
    this->position_current.audio_channel = this->position_next.audio_channel;
#ifdef LOG_DEBUG
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: AUDIO_STREAM_CHANGE stream_id=%d returning DVDNAV_STATUS_OK\n",stream_change->physical);
#endif
    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_OK;
  }
     
  /* Check the STILLFRAME flag */
  if(this->position_current.still != 0) {
    dvdnav_still_event_t *still_event = (dvdnav_still_event_t *)*buf;

    (*event) = DVDNAV_STILL_FRAME;
#ifdef LOG_DEBUG
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: STILL_FRAME\n");
#endif
    (*len) = sizeof(dvdnav_still_event_t);
    still_event->length = this->position_current.still;
    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_OK;
  }

  /* Have we reached the end of a VOBU? */
  if (this->vobu.blockN >= this->vobu.vobu_length) {

    /* Have we reached the end of a cell? */
    if(this->vobu.vobu_next == SRI_END_OF_CELL) {
      /* End of Cell from NAV DSI info */
#ifdef LOG_DEBUG
      mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: Still set to %x\n", this->position_next.still);
#endif
      this->position_current.still = this->position_next.still;

      /* we are about to leave a cell, so a lot of state changes could occur;
       * under certain conditions, the application should get in sync with us before this,
       * otherwise it might show stills or menus too shortly */
      if ((this->position_current.still || this->pci.hli.hl_gi.hli_ss) && !this->sync_wait_skip) {
        this->sync_wait = 1;
      } else {
	if( this->position_current.still == 0 || this->skip_still ) {
	  /* no active cell still -> get us to the next cell */
	  vm_get_next_cell(this->vm);
	  this->position_current.still = 0; /* still gets activated at end of cell */
	  this->skip_still = 0;
	  this->sync_wait_skip = 0;
	}
      }
      /* handle related state changes in next iteration */
      (*event) = DVDNAV_NOP;
      (*len) = 0;
      pthread_mutex_unlock(&this->vm_lock); 
      return DVDNAV_STATUS_OK;
    }

    /* Perform remapping jump if necessary (this is always a 
     * VOBU boundary). */
    if (this->vm->map) {
      this->vobu.vobu_next = remap_block( this->vm->map,
        this->vm->state.domain, this->vm->state.TTN_REG,
        this->vm->state.pgN,
        this->vobu.vobu_start, this->vobu.vobu_next);
    }

    /* at the start of the next VOBU -> expecting NAV packet */
    result = dvdnav_read_cache_block(this->cache, this->vobu.vobu_start + this->vobu.vobu_next, 1, buf);

    if(result <= 0) {
      mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ErrorReadingNAVPacket);
      pthread_mutex_unlock(&this->vm_lock); 
      return DVDNAV_STATUS_ERR;
    }
    /* Decode nav into pci and dsi. Then get next VOBU info. */
    if(!dvdnav_decode_packet(this, *buf, &this->dsi, &this->pci)) {
      mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ExpectedNAVPacketButNoneFound);
      pthread_mutex_unlock(&this->vm_lock); 
      return DVDNAV_STATUS_ERR;
    }
    /* We need to update the vm state->blockN with which VOBU we are in.
     * This is so RSM resumes to the VOBU level and not just the CELL level.
     */
    this->vm->state.blockN = this->vobu.vobu_start - this->position_current.cell_start;

    dvdnav_get_vobu(this, &this->dsi, &this->pci, &this->vobu); 
    this->vobu.blockN = 0;
    /* Give the cache a hint about the size of next VOBU.
     * This improves pre-caching, because the VOBU will almost certainly be read entirely.
     */
    dvdnav_pre_cache_blocks(this->cache, this->vobu.vobu_start+1, this->vobu.vobu_length+1);
    
    /* release NAV menu filter, when we reach the same NAV packet again */
    if (this->last_cmd_nav_lbn == this->pci.pci_gi.nv_pck_lbn)
      this->last_cmd_nav_lbn = SRI_END_OF_CELL;
    
    /* Successfully got a NAV packet */
    (*event) = DVDNAV_NAV_PACKET;
#ifdef LOG_DEBUG
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: NAV_PACKET\n");
#endif
    (*len) = 2048; 
    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_OK;
  }
  
  /* If we've got here, it must just be a normal block. */
  if(!this->file) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_AttemptingToReadWithoutOpeningFile);
    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_ERR;
  }

  this->vobu.blockN++;
  result = dvdnav_read_cache_block(this->cache, this->vobu.vobu_start + this->vobu.blockN, 1, buf);
  if(result <= 0) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ErrorReadingFromDVD);
    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_ERR;
  }
  (*event) = DVDNAV_BLOCK_OK;
  (*len) = 2048;

  pthread_mutex_unlock(&this->vm_lock); 
  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_get_title_string(dvdnav_t *this, const char **title_str) {
  
  if(!this || !title_str) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  (*title_str) = this->vm->dvd_name;
  return DVDNAV_STATUS_OK;
}

uint8_t dvdnav_get_video_aspect(dvdnav_t *this) {
  uint8_t         retval;
  
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return -1;
  }
  if(!this->started) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_VirtualDVDMachineNotStarted);
    return -1;
  }

  pthread_mutex_lock(&this->vm_lock);
  retval = (uint8_t)vm_get_video_aspect(this->vm);
  pthread_mutex_unlock(&this->vm_lock);
  
  return retval;
}

uint8_t dvdnav_get_video_scale_permission(dvdnav_t *this) {
  uint8_t         retval;
  
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return -1;
  }
  if(!this->started) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_VirtualDVDMachineNotStarted);
    return -1;
  }
  
  pthread_mutex_lock(&this->vm_lock);
  retval = (uint8_t)vm_get_video_scale_permission(this->vm);
  pthread_mutex_unlock(&this->vm_lock);
  
  return retval;
}

uint16_t dvdnav_audio_stream_to_lang(dvdnav_t *this, uint8_t stream) {
  audio_attr_t  attr;
  
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return -1;
  }
  if(!this->started) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_VirtualDVDMachineNotStarted);
    return -1;
  }
  
  pthread_mutex_lock(&this->vm_lock); 
  attr = vm_get_audio_attr(this->vm, stream);
  pthread_mutex_unlock(&this->vm_lock); 
  
  if(attr.lang_type != 1)
    return 0xffff;
  
  return attr.lang_code;
}

uint16_t dvdnav_spu_stream_to_lang(dvdnav_t *this, uint8_t stream) {
  subp_attr_t  attr;
  
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return -1;
  }
  if(!this->started) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_VirtualDVDMachineNotStarted);
    return -1;
  }
  
  pthread_mutex_lock(&this->vm_lock); 
  attr = vm_get_subp_attr(this->vm, stream);
  pthread_mutex_unlock(&this->vm_lock); 
  
  if(attr.type != 1)
    return 0xffff;
  
  return attr.lang_code;
}

int8_t dvdnav_get_audio_logical_stream(dvdnav_t *this, uint8_t audio_num) {
  int8_t       retval;
  
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return -1;
  }
  if(!this->started) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_VirtualDVDMachineNotStarted);
    return -1;
  }
  
  pthread_mutex_lock(&this->vm_lock);
  if (!this->vm->state.pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock); 
    return -1;
  }
  retval = vm_get_audio_stream(this->vm, audio_num);
  pthread_mutex_unlock(&this->vm_lock); 

  return retval;
}

#ifdef USE_MPDVDNAV
dvdnav_status_t dvdnav_get_audio_attr(dvdnav_t *this, uint8_t audio_num, audio_attr_t *audio_attr) {
  if(!this || !audio_attr) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
  pthread_mutex_lock(&this->vm_lock);
  if (!this->vm->state.pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock); 
    return -1;
  }
  *audio_attr=vm_get_audio_attr(this->vm, audio_num);
  pthread_mutex_unlock(&this->vm_lock);
  return DVDNAV_STATUS_OK;
}
#endif

int8_t dvdnav_get_spu_logical_stream(dvdnav_t *this, uint8_t subp_num) {
  int8_t       retval;

  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return -1;
  }
  if(!this->started) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_VirtualDVDMachineNotStarted);
    return -1;
  }

  pthread_mutex_lock(&this->vm_lock);
  if (!this->vm->state.pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock); 
    return -1;
  }
  retval = vm_get_subp_stream(this->vm, subp_num, 0);
  pthread_mutex_unlock(&this->vm_lock);

  return retval;
}

#ifdef USE_MPDVDNAV
dvdnav_status_t dvdnav_get_spu_attr(dvdnav_t *this, uint8_t audio_num, subp_attr_t *subp_attr) {
  if(!this || !subp_attr) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
  pthread_mutex_lock(&this->vm_lock);
  if (!this->vm->state.pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock); 
    return -1;
  }
  *subp_attr=vm_get_subp_attr(this->vm, audio_num);
  pthread_mutex_unlock(&this->vm_lock);
  return DVDNAV_STATUS_OK;
}
#endif

int8_t dvdnav_get_active_audio_stream(dvdnav_t *this) {
  int8_t        retval;

  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return -1;
  }
  if(!this->started) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_VirtualDVDMachineNotStarted);
    return -1;
  }
  
  pthread_mutex_lock(&this->vm_lock); 
  if (!this->vm->state.pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock); 
    return -1;
  }
  retval = vm_get_audio_active_stream(this->vm);
  pthread_mutex_unlock(&this->vm_lock); 
  
  return retval;
}

int8_t dvdnav_get_active_spu_stream(dvdnav_t *this) {
  int8_t        retval;

  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return -1;
  }
  if(!this->started) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_VirtualDVDMachineNotStarted);
    return -1;
  }
  
  pthread_mutex_lock(&this->vm_lock); 
  if (!this->vm->state.pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock); 
    return -1;
  }
  retval = vm_get_subp_active_stream(this->vm, 0);
  pthread_mutex_unlock(&this->vm_lock); 
  
  return retval;
}

static int8_t dvdnav_is_domain(dvdnav_t *this, domain_t domain) {
  int8_t        retval;
  
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return -1;
  }
  if(!this->started) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_VirtualDVDMachineNotStarted);
    return -1;
  }
  
  pthread_mutex_lock(&this->vm_lock);
  retval = (this->vm->state.domain == domain);
  pthread_mutex_unlock(&this->vm_lock);
  
  return retval;
}

/* First Play domain. (Menu) */
int8_t dvdnav_is_domain_fp(dvdnav_t *this) {
  return dvdnav_is_domain(this, FP_DOMAIN);
}
/* Video management Menu domain. (Menu) */
int8_t dvdnav_is_domain_vmgm(dvdnav_t *this) {
  return dvdnav_is_domain(this, VMGM_DOMAIN);
}
/* Video Title Menu domain (Menu) */
int8_t dvdnav_is_domain_vtsm(dvdnav_t *this) {
  return dvdnav_is_domain(this, VTSM_DOMAIN);
}
/* Video Title domain (playing movie). */
int8_t dvdnav_is_domain_vts(dvdnav_t *this) { 
  return dvdnav_is_domain(this, VTS_DOMAIN);
}

/* Generally delegate angle information handling to VM */
dvdnav_status_t dvdnav_angle_change(dvdnav_t *this, int32_t angle) {
  int32_t num, current;
  
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  pthread_mutex_lock(&this->vm_lock);
  vm_get_angle_info(this->vm, &current, &num);
  /* Set angle SPRM if valid */
  if((angle > 0) && (angle <= num)) {
    this->vm->state.AGL_REG = angle;
  } else {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedAnInvalidAngleNumber);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }
  pthread_mutex_unlock(&this->vm_lock);

  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_get_angle_info(dvdnav_t *this, int32_t *current_angle,
				      int32_t *number_of_angles) {
  if(!this || !current_angle || !number_of_angles) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  pthread_mutex_lock(&this->vm_lock);
  vm_get_angle_info(this->vm, current_angle, number_of_angles);
  pthread_mutex_unlock(&this->vm_lock);

  return DVDNAV_STATUS_OK;
}

pci_t* dvdnav_get_current_nav_pci(dvdnav_t *this) {
  if(!this) return 0;
  return &this->pci;
}

dsi_t* dvdnav_get_current_nav_dsi(dvdnav_t *this) {
  if(!this) return 0;
  return &this->dsi;
}

uint32_t dvdnav_get_next_still_flag(dvdnav_t *this) {
  if(!this) return -1;
  return this->position_next.still;
}

// ######################################## dvdnav.c end ############################################

// ##################################### highlight.c start ##########################################
static void print_time(dvd_time_t *dtime) {
  const char *rate;
  assert((dtime->hour>>4) < 0xa && (dtime->hour&0xf) < 0xa);
  assert((dtime->minute>>4) < 0x7 && (dtime->minute&0xf) < 0xa);
  assert((dtime->second>>4) < 0x7 && (dtime->second&0xf) < 0xa);
  assert((dtime->frame_u&0xf) < 0xa);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"%02x:%02x:%02x.%02x",
         dtime->hour,
         dtime->minute,
         dtime->second,
         dtime->frame_u & 0x3f);
  switch((dtime->frame_u & 0xc0) >> 6) {
  case 1:
    rate = "25.00";
    break;
  case 3:
    rate = "29.97";
    break;
  default:
    rate = "(please send a bug report)";
    break;
  }
  mp_msg(MSGT_CPLAYER,MSGL_INFO," @ %s fps", rate);
}

static void nav_print_PCI_GI(pci_gi_t *pci_gi) {
  int32_t i;

  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: pci_gi:\n");
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: nv_pck_lbn    0x%08x\n", pci_gi->nv_pck_lbn);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: vobu_cat      0x%04x\n", pci_gi->vobu_cat);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: vobu_uop_ctl  0x%08x\n", *(uint32_t*)&pci_gi->vobu_uop_ctl);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: vobu_s_ptm    0x%08x\n", pci_gi->vobu_s_ptm);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: vobu_e_ptm    0x%08x\n", pci_gi->vobu_e_ptm);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: vobu_se_e_ptm 0x%08x\n", pci_gi->vobu_se_e_ptm);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: e_eltm        ");
  print_time(&pci_gi->e_eltm);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"\n");

  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: vobu_isrc     \"");
  for(i = 0; i < 32; i++) {
    char c = pci_gi->vobu_isrc[i];
    if((c >= ' ') && (c <= '~'))
      mp_msg(MSGT_CPLAYER,MSGL_INFO,"%c", c);
    else
      mp_msg(MSGT_CPLAYER,MSGL_INFO,".");
  }
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"\"\n");
}

static void nav_print_NSML_AGLI(nsml_agli_t *nsml_agli) {
  int32_t i, j = 0;

  for(i = 0; i < 9; i++)
    j |= nsml_agli->nsml_agl_dsta[i];
  if(j == 0)
    return;
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: nsml_agli:\n");
  for(i = 0; i < 9; i++)
    if(nsml_agli->nsml_agl_dsta[i])
      mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: nsml_agl_c%d_dsta  0x%08x\n", i + 1,
             nsml_agli->nsml_agl_dsta[i]);
}

static void nav_print_HL_GI(hl_gi_t *hl_gi, int32_t *btngr_ns, int32_t *btn_ns) {

  if((hl_gi->hli_ss & 0x03) == 0)
    return;

  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: hl_gi:\n");
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: hli_ss        0x%01x\n", hl_gi->hli_ss & 0x03);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: hli_s_ptm     0x%08x\n", hl_gi->hli_s_ptm);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: hli_e_ptm     0x%08x\n", hl_gi->hli_e_ptm);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: btn_se_e_ptm  0x%08x\n", hl_gi->btn_se_e_ptm);

  *btngr_ns = hl_gi->btngr_ns;
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: btngr_ns      %d\n",  hl_gi->btngr_ns);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: btngr%d_dsp_ty    0x%02x\n", 1, hl_gi->btngr1_dsp_ty);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: btngr%d_dsp_ty    0x%02x\n", 2, hl_gi->btngr2_dsp_ty);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: btngr%d_dsp_ty    0x%02x\n", 3, hl_gi->btngr3_dsp_ty);

  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: btn_ofn       %d\n", hl_gi->btn_ofn);
  *btn_ns = hl_gi->btn_ns;
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: btn_ns        %d\n", hl_gi->btn_ns);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: nsl_btn_ns    %d\n", hl_gi->nsl_btn_ns);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: fosl_btnn     %d\n", hl_gi->fosl_btnn);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: foac_btnn     %d\n", hl_gi->foac_btnn);
}

static void nav_print_BTN_COLIT(btn_colit_t *btn_colit) {
  int32_t i, j;
  j = 0;
  for(i = 0; i < 6; i++)
    j |= btn_colit->btn_coli[i/2][i&1];
  if(j == 0)
    return;
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: btn_colit:\n");
  for(i = 0; i < 3; i++)
    for(j = 0; j < 2; j++)
      mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: btn_cqoli %d  %s_coli:  %08x\n",
             i, (j == 0) ? "sl" : "ac",
             btn_colit->btn_coli[i][j]);
}

static void nav_print_BTNIT(btni_t *btni_table, int32_t btngr_ns, int32_t btn_ns) {
  int32_t i, j, k;
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: btnit:\n");
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: btngr_ns: %i\n", btngr_ns);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: btn_ns: %i\n", btn_ns);
  if(btngr_ns == 0)
    return;

  for(i = 0; i < btngr_ns; i++) {
    for(j = 0; j < (36 / btngr_ns); j++) {
      if(j < btn_ns) {
        btni_t *btni = &btni_table[(36 / btngr_ns) * i + j];
        mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: group %d btni %d:  ", i+1, j+1);
        mp_msg(MSGT_CPLAYER,MSGL_INFO,"btn_coln %d, auto_action_mode %d\n",
               btni->btn_coln, btni->auto_action_mode);
        mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: coords   (%d, %d) .. (%d, %d)\n",
               btni->x_start, btni->y_start, btni->x_end, btni->y_end);

        mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: up %d, ", btni->up);
        mp_msg(MSGT_CPLAYER,MSGL_INFO,"down %d, ", btni->down);
        mp_msg(MSGT_CPLAYER,MSGL_INFO,"left %d, ", btni->left);
        mp_msg(MSGT_CPLAYER,MSGL_INFO,"right %d\n", btni->right);
        for(k = 0; k < 8; k++) {
          mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: %02x ", btni->cmd.bytes[k]);
        }
        mp_msg(MSGT_CPLAYER,MSGL_INFO, "| ");
#ifdef USE_MPDVDNAV_TRACE
  if (dvdnav_trace) vm_print_mnemonic(&btni->cmd);
#endif
        mp_msg(MSGT_CPLAYER,MSGL_INFO,"\n");
      }
    }
  }
}

static void nav_print_HLI(hli_t *hli) {
  int32_t btngr_ns = 0, btn_ns = 0;
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: hli:\n");
  nav_print_HL_GI(&hli->hl_gi, & btngr_ns, & btn_ns);
  nav_print_BTN_COLIT(&hli->btn_colit);
  nav_print_BTNIT(hli->btnit, btngr_ns, btn_ns);
}

void nav_print_PCI(pci_t *pci) {
  mp_msg(MSGT_CPLAYER,MSGL_INFO,"libdvdnav: pci packet:\n");
  nav_print_PCI_GI(&pci->pci_gi);
  nav_print_NSML_AGLI(&pci->nsml_agli);
  nav_print_HLI(&pci->hli);
}


/* Highlighting API calls */

dvdnav_status_t dvdnav_get_current_highlight(dvdnav_t *this, int32_t *button) {
  if(!this || !button) { mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer); return DVDNAV_STATUS_ERR; }
  /* Simply return the appropriate value based on the SPRM */
  if(((*button) = this->position_current.button) == -1)
    (*button) = this->vm->state.HL_BTNN_REG >> 10;
  return DVDNAV_STATUS_OK;
}

static btni_t *get_current_button(dvdnav_t *this, pci_t *pci) {
  int32_t button = 0;
  if(!this || !pci) { mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer); return NULL; }
  if(!pci->hli.hl_gi.hli_ss) { mp_msg( MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NotInAMenu); return NULL; }
  if(this->last_cmd_nav_lbn == pci->pci_gi.nv_pck_lbn) { mp_msg( MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ThisNAVHasAlreadyBeenLeft); return NULL; }
  button = this->vm->state.HL_BTNN_REG >> 10;
#ifdef BUTTON_TESTING
  nav_print_PCI(pci);
#endif
  return &(pci->hli.btnit[button-1]);
}

static dvdnav_status_t button_auto_action(dvdnav_t *this, pci_t *pci) {
#ifdef USE_MPDVDNAV
  if (this->vm->auto_action_mode) return DVDNAV_STATUS_OK;
#endif
  if (get_current_button(this, pci)->auto_action_mode) 
    return dvdnav_button_activate(this, pci);
  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_upper_button_select(dvdnav_t *this, pci_t *pci) {
  btni_t *button_ptr;
  if(!(button_ptr = get_current_button(this, pci)))
    return DVDNAV_STATUS_ERR;
  dvdnav_button_select(this, pci, button_ptr->up);
  return button_auto_action(this, pci);
}

dvdnav_status_t dvdnav_lower_button_select(dvdnav_t *this, pci_t *pci) {
  btni_t *button_ptr;
  if(!(button_ptr = get_current_button(this, pci)))
    return DVDNAV_STATUS_ERR;
  dvdnav_button_select(this, pci, button_ptr->down);
  return button_auto_action(this, pci);
}

dvdnav_status_t dvdnav_right_button_select(dvdnav_t *this, pci_t *pci) {
  btni_t *button_ptr;
  if(!(button_ptr = get_current_button(this, pci)))
    return DVDNAV_STATUS_ERR;
  dvdnav_button_select(this, pci, button_ptr->right);
  return button_auto_action(this, pci);
}

dvdnav_status_t dvdnav_left_button_select(dvdnav_t *this, pci_t *pci) {
  btni_t *button_ptr;
  if(!(button_ptr = get_current_button(this, pci)))
    return DVDNAV_STATUS_ERR;
  dvdnav_button_select(this, pci, button_ptr->left);
  return button_auto_action(this, pci);
}

dvdnav_status_t dvdnav_get_highlight_area(pci_t *nav_pci , int32_t button, int32_t mode, 
					  dvdnav_highlight_area_t *highlight) {
  btni_t *button_ptr;
#ifdef BUTTON_TESTING
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Button get_highlight_area %i\n", button);
#endif
  if(!nav_pci->hli.hl_gi.hli_ss)
    return DVDNAV_STATUS_ERR;
  if((button <= 0) || (button > nav_pci->hli.hl_gi.btn_ns))
    return DVDNAV_STATUS_ERR;
  button_ptr = &nav_pci->hli.btnit[button-1];
  highlight->sx = button_ptr->x_start;
  highlight->sy = button_ptr->y_start;
  highlight->ex = button_ptr->x_end;
  highlight->ey = button_ptr->y_end;
  if(button_ptr->btn_coln != 0) {
    highlight->palette = nav_pci->hli.btn_colit.btn_coli[button_ptr->btn_coln-1][mode];
  } else {
    highlight->palette = 0;
  }
  highlight->pts = nav_pci->hli.hl_gi.hli_s_ptm;
  highlight->buttonN = button;
#ifdef BUTTON_TESTING
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: highlight: Highlight area is (%u,%u)-(%u,%u), display = %i, button = %u\n",
               button_ptr->x_start, button_ptr->y_start,
               button_ptr->x_end, button_ptr->y_end,
               1,
               button);
#endif
  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_button_activate(dvdnav_t *this, pci_t *pci) {
  int32_t button;
  btni_t *button_ptr = NULL;
  if(!this || !pci) { mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer); return DVDNAV_STATUS_ERR; }
  if(!pci->hli.hl_gi.hli_ss) { mp_msg( MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NotInAMenu); return DVDNAV_STATUS_ERR; }
  if(this->last_cmd_nav_lbn == pci->pci_gi.nv_pck_lbn) {mp_msg( MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ThisNAVHasAlreadyBeenLeft); return DVDNAV_STATUS_ERR; }
  pthread_mutex_lock(&this->vm_lock); 
  button = this->vm->state.HL_BTNN_REG >> 10;
  if((button <= 0) || (button > pci->hli.hl_gi.btn_ns)) {
    /* Special code to handle still menus with no buttons.
     * The navigation is expected to report to the application that a STILL is
     * underway. In turn, the application is supposed to report to the user
     * that the playback is paused. The user is then expected to undo the pause,
     * ie: hit play. At that point, the navigation should release the still and
     * go to the next Cell.
     * Explanation by Mathieu Lacage <mathieu_lacage@realmagic.fr>
     * Code added by jcdutton.
     */
    if (this->position_current.still != 0) {
      /* In still, but no buttons. */
      vm_get_next_cell(this->vm);
      this->position_current.still = 0;
      this->sync_wait = 0;
      this->last_cmd_nav_lbn = pci->pci_gi.nv_pck_lbn;
      pthread_mutex_unlock(&this->vm_lock);
      return DVDNAV_STATUS_OK;
    }
    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_ERR;
  }
  button_ptr = get_current_button(this, pci);
  /* Finally, make the VM execute the appropriate code and probably
   * scedule a jump */
#ifdef BUTTON_TESTING
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Evaluating Button Activation commands.\n");
#endif
  if(vm_exec_cmd(this->vm, &(button_ptr->cmd)) == 1) {
    /* Command caused a jump */
    this->vm->hop_channel++;
    this->position_current.still = 0;
    this->last_cmd_nav_lbn = pci->pci_gi.nv_pck_lbn;
  }
  pthread_mutex_unlock(&this->vm_lock); 
  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_button_activate_cmd(dvdnav_t *this, int32_t button, vm_cmd_t *cmd)
{
  if(!this || !cmd) { mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer); return DVDNAV_STATUS_ERR; }
  pthread_mutex_lock(&this->vm_lock);
  /* make the VM execute the appropriate code and probably
   * schedule a jump */
#ifdef BUTTON_TESTING
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: dvdnav_button_activate_cmd: Evaluating Button Activation commands.\n");
#endif
  if(button > 0) {
    this->vm->state.HL_BTNN_REG = (button << 10);
    if(vm_exec_cmd(this->vm, cmd) == 1) {
      /* Command caused a jump */
      this->vm->hop_channel++;
    }
  }
  /* Always remove still, because some still menus have no buttons. */
  this->position_current.still = 0;
  this->sync_wait = 0;
  pthread_mutex_unlock(&this->vm_lock);
  return DVDNAV_STATUS_OK;
}  

dvdnav_status_t dvdnav_button_select(dvdnav_t *this, pci_t *pci, int32_t button) {
  
  if(!this || !pci) { mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer); return DVDNAV_STATUS_ERR; }
  if(!pci->hli.hl_gi.hli_ss) { mp_msg( MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NotInAMenu); return DVDNAV_STATUS_ERR; }
  if(this->last_cmd_nav_lbn == pci->pci_gi.nv_pck_lbn) { mp_msg( MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ThisNAVHasAlreadyBeenLeft); return DVDNAV_STATUS_ERR; }
#ifdef BUTTON_TESTING
  mp_msg(MSGT_CPLAYER,MSGL_INFO, "libdvdnav: Button select %i\n", button); 
#endif
  if((button <= 0) || (button > pci->hli.hl_gi.btn_ns)) { mp_msg( MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ButtonDoesNotExist); 
  return DVDNAV_STATUS_ERR; }
  this->vm->state.HL_BTNN_REG = (button << 10);
  this->position_current.button = -1; /* Force Highligh change */
  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_button_select_and_activate(dvdnav_t *this, pci_t *pci, 
						  int32_t button) {
  /* A trivial function */
  if(dvdnav_button_select(this, pci, button) != DVDNAV_STATUS_ERR)
    return dvdnav_button_activate(this, pci);
  return DVDNAV_STATUS_ERR;
}

dvdnav_status_t dvdnav_mouse_select(dvdnav_t *this, pci_t *pci, int32_t x, int32_t y) {
  int32_t button, cur_button;
  int32_t best,dist,d;
  int32_t mx,my,dx,dy;

  if(!this || !pci) { mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer); return DVDNAV_STATUS_ERR; }
  if(!pci->hli.hl_gi.hli_ss) { mp_msg( MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NotInAMenu); return DVDNAV_STATUS_ERR; }
  if(this->last_cmd_nav_lbn == pci->pci_gi.nv_pck_lbn) { mp_msg( MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ThisNAVHasAlreadyBeenLeft); return DVDNAV_STATUS_ERR; }
  cur_button = this->vm->state.HL_BTNN_REG >> 10;
  best = 0;
  dist = 0x08000000; /* >> than  (720*720)+(567*567); */
  /* Loop through all buttons */
  for(button = 1; button <= pci->hli.hl_gi.btn_ns; button++) {
    btni_t *button_ptr = &(pci->hli.btnit[button-1]);

    if((x >= button_ptr->x_start) && (x <= button_ptr->x_end) &&
       (y >= button_ptr->y_start) && (y <= button_ptr->y_end)) {
      mx = (button_ptr->x_start + button_ptr->x_end)/2;
      my = (button_ptr->y_start + button_ptr->y_end)/2;
      dx = mx - x;
      dy = my - y;
      d = (dx*dx) + (dy*dy);
      /* If the mouse is within the button and the mouse is closer
       * to the center of this button then it is the best choice. */
      if(d < dist) {
        dist = d;
        best = button;
      }
    }
  }
  /* As an efficiency measure, only re-select the button
   * if it is different to the previously selected one. */
  if (best != 0 && best != cur_button)
    dvdnav_button_select(this, pci, best);
  /* return DVDNAV_STATUS_OK only if we actually found a matching button */
  return best ? DVDNAV_STATUS_OK : DVDNAV_STATUS_ERR;
}

dvdnav_status_t dvdnav_mouse_activate(dvdnav_t *this, pci_t *pci, int32_t x, int32_t y) {
  /* A trivial function */
  if(dvdnav_mouse_select(this, pci, x,y) != DVDNAV_STATUS_ERR)
    return dvdnav_button_activate(this, pci);
  return DVDNAV_STATUS_ERR;
}
// ###################################### highlight.c end ###########################################


// ###################################### settings.c start ##########################################
/* Characteristics/setting API calls */

dvdnav_status_t dvdnav_get_region_mask(dvdnav_t *this, int32_t *region) {
  if(!this || !region) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  (*region) = this->vm->state.registers.SPRM[20];
  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_set_region_mask(dvdnav_t *this, int32_t mask) {
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  pthread_mutex_lock(&this->vm_lock);
  this->vm->state.registers.SPRM[20] = (mask & 0xff);
  pthread_mutex_unlock(&this->vm_lock);
  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_set_readahead_flag(dvdnav_t *this, int32_t use_readahead) {
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  this->use_read_ahead = use_readahead;
  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_get_readahead_flag(dvdnav_t *this, int32_t *flag) {
  if(!this || !flag) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  (*flag) = this->use_read_ahead;
  return DVDNAV_STATUS_OK;
}

static dvdnav_status_t set_language_register(dvdnav_t *this, char *code, int reg) {
  if(!this || !code) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
    
  if(!code[0] || !code[1]) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedIllegalLanguageCode);
    return DVDNAV_STATUS_ERR;
  }
  
  pthread_mutex_lock(&this->vm_lock);
  this->vm->state.registers.SPRM[reg] = (code[0] << 8) | code[1];
  pthread_mutex_unlock(&this->vm_lock);
  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_menu_language_select(dvdnav_t *this, char *code) {
  return set_language_register(this, code, 0);
}

dvdnav_status_t dvdnav_audio_language_select(dvdnav_t *this, char *code) {
  return set_language_register(this, code, 16);
}

dvdnav_status_t dvdnav_spu_language_select(dvdnav_t *this, char *code) {
  return set_language_register(this, code, 18);
}

dvdnav_status_t dvdnav_set_PGC_positioning_flag(dvdnav_t *this, int32_t pgc) {
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  this->pgc_based = pgc;
  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_get_PGC_positioning_flag(dvdnav_t *this, int32_t *flag) {
  if(!this || !flag) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  (*flag) = this->pgc_based;
  return DVDNAV_STATUS_OK;
}

#ifdef USE_MPDVDNAV
dvdnav_status_t dvdnav_button_select_auto_action(dvdnav_t *this, int mode)
{
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  pthread_mutex_lock(&this->vm_lock);
  this->vm->auto_action_mode = mode;
  pthread_mutex_unlock(&this->vm_lock);
  return DVDNAV_STATUS_OK;
}
#endif
// ####################################### settings.c end ###########################################

// ####################################### search.c start ###########################################
dvdnav_status_t dvdnav_time_search(dvdnav_t *this, uint64_t time) {
  /* FIXME: Time search the current PGC based on the xxx table */
  return DVDNAV_STATUS_OK;
}

/* Scan the ADMAP for a particular block number. */
/* Return placed in vobu. */
/* Returns error status */
/* FIXME: Maybe need to handle seeking outside current cell. */
static dvdnav_status_t dvdnav_scan_admap(dvdnav_t *this, int32_t domain, uint32_t seekto_block, uint32_t *vobu) {
  vobu_admap_t *admap = NULL;

#ifdef LOG_DEBUG
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: Seeking to target %u ...\n", seekto_block);
#endif
  *vobu = -1;

  /* Search through the VOBU_ADMAP for the nearest VOBU
   * to the target block */
  switch(domain) {
  case FP_DOMAIN:
  case VMGM_DOMAIN:
    admap = this->vm->vmgi->menu_vobu_admap;
    break;
  case VTSM_DOMAIN:
    admap = this->vm->vtsi->menu_vobu_admap;
    break;
  case VTS_DOMAIN:
    admap = this->vm->vtsi->vts_vobu_admap;
    break;
  default:
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_UnknownDomainForSeeking);
  }
  if(admap) {
    uint32_t address = 0;
    uint32_t vobu_start, next_vobu;
    int32_t found = 0;

    /* Search through ADMAP for best sector */
    vobu_start = SRI_END_OF_CELL;
    /* FIXME: Implement a faster search algorithm */
    while((!found) && ((address<<2) < admap->last_byte)) {
      next_vobu = admap->vobu_start_sectors[address];

      /* mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: Found block %u\n", next_vobu); */

      if(vobu_start <= seekto_block &&
          next_vobu > seekto_block) {
        found = 1;
      } else {
        vobu_start = next_vobu;
      }
      address ++;
    }
    if(found) {
      *vobu = vobu_start;
      return DVDNAV_STATUS_OK;
    } else {
      mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_CouldNotLocateBlock);
      return DVDNAV_STATUS_ERR;
    }
  }
  mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_AdmapNotLocated);
  return DVDNAV_STATUS_ERR;
}

dvdnav_status_t dvdnav_sector_search(dvdnav_t *this,
				     uint64_t offset, int32_t origin) {
  uint32_t target = 0;
  uint32_t length = 0;
  uint32_t first_cell_nr, last_cell_nr, cell_nr;
  int32_t found;
  cell_playback_t *cell;
  dvd_state_t *state;
  dvdnav_status_t result;

  if(this->position_current.still != 0) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_CannotSeekInAStillFrame);
    return DVDNAV_STATUS_ERR;
  }
  
  result = dvdnav_get_position(this, &target, &length);
  if(!result) {
    return DVDNAV_STATUS_ERR;
  }
 
  pthread_mutex_lock(&this->vm_lock);
  state = &(this->vm->state);
  if(!state->pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }
#ifdef LOG_DEBUG
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: seeking to offset=%lu pos=%u length=%u\n", offset, target, length); 
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: Before cellN=%u blockN=%u\n", state->cellN, state->blockN);
#endif

  switch(origin) {
   case SEEK_SET:
    if(offset > length) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_RequestToSeekBehindEnd);
      pthread_mutex_unlock(&this->vm_lock);
      return DVDNAV_STATUS_ERR;
    }
    target = offset;
    break;
   case SEEK_CUR:
    if(target + offset > length) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_RequestToSeekBehindEnd);
      pthread_mutex_unlock(&this->vm_lock);
      return DVDNAV_STATUS_ERR;
    }
    target += offset;
    break;
   case SEEK_END:
    if(length - offset < 0) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_RequestToSeekBeforeStart);
      pthread_mutex_unlock(&this->vm_lock);
      return DVDNAV_STATUS_ERR;
    }
    target = length - offset;
    break;
   default:
    /* Error occured */
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_IllegalSeekMode);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }
  
  if (this->pgc_based) {
    first_cell_nr = 1;
    last_cell_nr = state->pgc->nr_of_cells;
  } else {
    /* Find start cell of program. */
    first_cell_nr = state->pgc->program_map[state->pgN-1];
    /* Find end cell of program */
    if(state->pgN < state->pgc->nr_of_programs)
      last_cell_nr = state->pgc->program_map[state->pgN] - 1;
    else
      last_cell_nr = state->pgc->nr_of_cells;
  }

  found = 0;
  for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr) && !found; cell_nr ++) {
    cell =  &(state->pgc->cell_playback[cell_nr-1]);
    length = cell->last_sector - cell->first_sector + 1;
    if (target >= length) {
      target -= length;
    } else {
      /* convert the target sector from Cell-relative to absolute physical sector */
      target += cell->first_sector;
      found = 1;
      break;
    }
  }

  if(found) {
    int32_t vobu;
#ifdef LOG_DEBUG
    mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: Seeking to cell %i from choice of %i to %i\n",
	    cell_nr, first_cell_nr, last_cell_nr);
#endif
    if (dvdnav_scan_admap(this, state->domain, target, &vobu) == DVDNAV_STATUS_OK) {
      int32_t start = state->pgc->cell_playback[cell_nr-1].first_sector;
      
      if (vm_jump_cell_block(this->vm, cell_nr, vobu - start)) {
#ifdef LOG_DEBUG
        mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: After cellN=%u blockN=%u target=%x vobu=%x start=%x\n" ,
          state->cellN, state->blockN, target, vobu, start);
#endif
        this->vm->hop_channel += HOP_SEEK;
        pthread_mutex_unlock(&this->vm_lock);
        return DVDNAV_STATUS_OK;
      }
    }
  }
  
  mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_ErrorWhenSeeking);
  mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_FIXMEImplementSeekingToLocation, target); 
  mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_ErrorWhenSeeking);
  pthread_mutex_unlock(&this->vm_lock);
  return DVDNAV_STATUS_ERR;
}

dvdnav_status_t dvdnav_part_search(dvdnav_t *this, int32_t part) {
  int32_t title, old_part;
  
  if (dvdnav_current_title_info(this, &title, &old_part) == DVDNAV_STATUS_OK)
    return dvdnav_part_play(this, title, part);
  return DVDNAV_STATUS_ERR;
}

dvdnav_status_t dvdnav_prev_pg_search(dvdnav_t *this) {

  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
  
  pthread_mutex_lock(&this->vm_lock);
  if(!this->vm->state.pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }

#ifdef LOG_DEBUG
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: previous chapter\n");
#endif
  if (!vm_jump_prev_pg(this->vm)) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_PreviousChapterFailed);
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_SkipToPreviousChapterFailed);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }
  this->position_current.still = 0;
  this->vm->hop_channel++;
#ifdef LOG_DEBUG
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: previous chapter done\n");
#endif
  pthread_mutex_unlock(&this->vm_lock);

  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_top_pg_search(dvdnav_t *this) {

  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
    
  pthread_mutex_lock(&this->vm_lock);
  if(!this->vm->state.pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }

#ifdef LOG_DEBUG
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: top chapter\n");
#endif
  if (!vm_jump_top_pg(this->vm)) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_TopChapterFailed);
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_SkipToTopChapterFailed);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }
  this->position_current.still = 0;
  this->vm->hop_channel++;
#ifdef LOG_DEBUG
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: top chapter done\n");
#endif
  pthread_mutex_unlock(&this->vm_lock);

  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_next_pg_search(dvdnav_t *this) {
  vm_t *try_vm;

  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  pthread_mutex_lock(&this->vm_lock);
  if(!this->vm->state.pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }

#ifdef LOG_DEBUG
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: next chapter\n");
#endif
  /* make a copy of current VM and try to navigate the copy to the next PG */
  try_vm = vm_new_copy(this->vm);
  if (!vm_jump_next_pg(try_vm) || try_vm->stopped) {
    vm_free_copy(try_vm);
    /* next_pg failed, try to jump at least to the next cell */
    try_vm = vm_new_copy(this->vm);
    vm_get_next_cell(try_vm);
    if (try_vm->stopped) {
      vm_free_copy(try_vm);
      mp_msg(MSGT_CPLAYER,MSGL_INFO,MSGTR_LIBMPDVDNAV_NextChapterFailed);
      mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_SkipToNextChapterFailed);
      pthread_mutex_unlock(&this->vm_lock);
      return DVDNAV_STATUS_ERR;
    }
  }
  /* merge changes on success */
  vm_merge(this->vm, try_vm);
  vm_free_copy(try_vm);
  this->position_current.still = 0;
  this->vm->hop_channel++;
#ifdef LOG_DEBUG
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: next chapter done\n");
#endif
  pthread_mutex_unlock(&this->vm_lock);

  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_menu_call(dvdnav_t *this, DVDMenuID_t menu) {
  vm_t *try_vm;
  
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  pthread_mutex_lock(&this->vm_lock);
  if(!this->vm->state.pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }
  
  /* make a copy of current VM and try to navigate the copy to the menu */
  try_vm = vm_new_copy(this->vm);
  if ( (menu == DVD_MENU_Escape) && (this->vm->state.domain != VTS_DOMAIN)) {
    /* Try resume */
    if (vm_jump_resume(try_vm) && !try_vm->stopped) {
        /* merge changes on success */
        vm_merge(this->vm, try_vm);
        vm_free_copy(try_vm);
        this->position_current.still = 0;
        this->vm->hop_channel++;
        pthread_mutex_unlock(&this->vm_lock); 
        return DVDNAV_STATUS_OK;
    }
  }
  if (menu == DVD_MENU_Escape) menu = DVD_MENU_Root;
 
  if (vm_jump_menu(try_vm, menu) && !try_vm->stopped) {
    /* merge changes on success */
    vm_merge(this->vm, try_vm);
    vm_free_copy(try_vm);
    this->position_current.still = 0;
    this->vm->hop_channel++;
    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_OK;
  } else {
    vm_free_copy(try_vm);
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoSuchMenuOrMenuNotReachable);
    pthread_mutex_unlock(&this->vm_lock); 
    return DVDNAV_STATUS_ERR;
  }
}

dvdnav_status_t dvdnav_get_position(dvdnav_t *this, uint32_t *pos,
				    uint32_t *len) {
  uint32_t cur_sector;
  int32_t cell_nr, first_cell_nr, last_cell_nr;
  cell_playback_t *cell;
  dvd_state_t *state;

  if(!this || !pos || !len) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
  if(!this->started) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_VirtualDVDMachineNotStarted);
    return DVDNAV_STATUS_ERR;
  }

  pthread_mutex_lock(&this->vm_lock);
  state = &(this->vm->state);
  if(!state->pgc || this->vm->stopped) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }
  if (this->position_current.hop_channel  != this->vm->hop_channel ||
      this->position_current.domain       != state->domain         ||
      this->position_current.vts          != state->vtsN           ||
      this->position_current.cell_restart != state->cell_restart) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NewPositionNotYetDetermined);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }

  /* Get current sector */
  cur_sector = this->vobu.vobu_start + this->vobu.blockN;

  if (this->pgc_based) {
    first_cell_nr = 1;
    last_cell_nr = state->pgc->nr_of_cells;
  } else {
    /* Find start cell of program. */
    first_cell_nr = state->pgc->program_map[state->pgN-1];
    /* Find end cell of program */
    if(state->pgN < state->pgc->nr_of_programs)
      last_cell_nr = state->pgc->program_map[state->pgN] - 1;
    else
      last_cell_nr = state->pgc->nr_of_cells;
  }

  *pos = -1;
  *len = 0;
  for (cell_nr = first_cell_nr; cell_nr <= last_cell_nr; cell_nr++) {
    cell = &(state->pgc->cell_playback[cell_nr-1]);
    if (cell_nr == state->cellN) {
      /* the current sector is in this cell,
       * pos is length of PG up to here + sector's offset in this cell */
      *pos = *len + cur_sector - cell->first_sector;
    }
    *len += cell->last_sector - cell->first_sector + 1;
  }
  
  assert((signed)*pos != -1);

  pthread_mutex_unlock(&this->vm_lock);

  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_get_position_in_title(dvdnav_t *this,
					     uint32_t *pos,
					     uint32_t *len) {
  uint32_t cur_sector;
  uint32_t first_cell_nr;
  uint32_t last_cell_nr;
  cell_playback_t *first_cell;
  cell_playback_t *last_cell;
  dvd_state_t *state;

  if(!this || !pos || !len) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  state = &(this->vm->state);
  if(!state->pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    return DVDNAV_STATUS_ERR;
  }

  /* Get current sector */
  cur_sector = this->vobu.vobu_start + this->vobu.blockN;

  /* Now find first and last cells in title. */
  first_cell_nr = state->pgc->program_map[0];
  first_cell = &(state->pgc->cell_playback[first_cell_nr-1]);
  last_cell_nr = state->pgc->nr_of_cells;
  last_cell = &(state->pgc->cell_playback[last_cell_nr-1]);
  
  *pos = cur_sector - first_cell->first_sector;
  *len = last_cell->last_sector - first_cell->first_sector;
  
  return DVDNAV_STATUS_OK;
}

#ifdef USE_MPDVDNAV
dvdnav_status_t dvdnav_get_pgc(dvdnav_t *this, int *pg_num)
{
  if(!this || !pg_num) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
  
  pthread_mutex_lock(&this->vm_lock);
  if(!this->vm->state.pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }
#ifdef LOG_DEBUG
  mp_msg(MSGT_FIXME,MSGL_FIXME, "libdvdnav: get pgc\n");
#endif
  *pg_num = vm_get_PGCN(this->vm);
  pthread_mutex_unlock(&this->vm_lock);
  return DVDNAV_STATUS_OK;
}
#endif

// ######################################## search.c end ############################################

// ##################################### navigation.c start #########################################
/* Navigation API calls */

dvdnav_status_t dvdnav_still_skip(dvdnav_t *this) {
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  this->position_current.still = 0;
  this->skip_still = 1;
  this->sync_wait = 0;
  this->sync_wait_skip = 1;

  return DVDNAV_STATUS_OK;
}

#ifdef USE_MPDVDNAV
dvdnav_status_t dvdnav_still_back(dvdnav_t *this) {
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  this->position_current.still = 0;
  this->skip_still = 0;
  this->sync_wait = 0;
  this->sync_wait_skip = 1;

//  process_command(this->vm, play_Cell(this->vm));
  play_Cell(this->vm);

  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_wait_back(dvdnav_t *this) {
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  this->position_current.still = 0;
  this->skip_still = 0;
  this->sync_wait = 0;
  this->sync_wait_skip = 0;
//  play_PGC(this->vm);
  process_command(this->vm, play_PGC(this->vm));

  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_wait_still_clear(dvdnav_t *this) {
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  this->position_current.still = 0;
  this->skip_still = 0;
  this->sync_wait = 0;
  this->sync_wait_skip = 0;

  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_is_still_cell(dvdnav_t *this, int *flag)
{
  if(!this || !flag) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
  *flag=this->position_current.still;
  return DVDNAV_STATUS_OK;
}

#endif

dvdnav_status_t dvdnav_wait_skip(dvdnav_t *this) {
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  this->sync_wait = 0;
  this->sync_wait_skip = 1;

  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_get_number_of_titles(dvdnav_t *this, int32_t *titles) {
  if(!this || !titles) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
  if(!this->started) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_VirtualDVDMachineNotStarted);
    return DVDNAV_STATUS_ERR;
  }

  (*titles) = vm_get_vmgi(this->vm)->tt_srpt->nr_of_srpts;

  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_get_number_of_parts(dvdnav_t *this, int32_t title, int32_t *parts) {
  if(!this || !parts) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
  if(!this->started) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_VirtualDVDMachineNotStarted);
    return DVDNAV_STATUS_ERR;
  }
  if ((title < 1) || (title > vm_get_vmgi(this->vm)->tt_srpt->nr_of_srpts) ) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedATitleNumberOutOfRange);
    return DVDNAV_STATUS_ERR;
  }

  (*parts) = vm_get_vmgi(this->vm)->tt_srpt->title[title-1].nr_of_ptts;

  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_current_title_info(dvdnav_t *this, int32_t *title, int32_t *part) {
  int32_t retval;
  
  if(!this || !title || !part) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
  
  pthread_mutex_lock(&this->vm_lock);
  if (!this->vm->vtsi || !this->vm->vmgi) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_BadVMState);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }
  if (!this->vm->state.pgc) {
  mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }
  if ( (this->vm->state.domain == VTSM_DOMAIN)
      || (this->vm->state.domain == VMGM_DOMAIN) ) {
    /* Get current Menu ID: into *part. */
    vm_get_current_menu(this->vm, part);
    if (*part > -1) {
      *title = 0;
      pthread_mutex_unlock(&this->vm_lock);
      return DVDNAV_STATUS_OK;
    }
  }
  if (this->vm->state.domain == VTS_DOMAIN) {
    retval = vm_get_current_title_part(this->vm, title, part);
    pthread_mutex_unlock(&this->vm_lock);
    return retval ? DVDNAV_STATUS_OK : DVDNAV_STATUS_ERR;
  }
  mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NotInATitleOrMenu);
  pthread_mutex_unlock(&this->vm_lock);
  return DVDNAV_STATUS_ERR;
}

dvdnav_status_t dvdnav_title_play(dvdnav_t *this, int32_t title) {
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
  return dvdnav_part_play(this, title, 1);
}

dvdnav_status_t dvdnav_part_play(dvdnav_t *this, int32_t title, int32_t part) {
  int32_t retval;

  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
  
  pthread_mutex_lock(&this->vm_lock);
  if (!this->vm->vmgi) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_BadVMState);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }
  if (!this->vm->state.pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NoCurrentPGC);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }
  if((title < 1) || (title > this->vm->vmgi->tt_srpt->nr_of_srpts)) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_TitleOutOfRange);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }
  if((part < 1) || (part > this->vm->vmgi->tt_srpt->title[title-1].nr_of_ptts)) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PartOutOfRange);
    pthread_mutex_unlock(&this->vm_lock);
    return DVDNAV_STATUS_ERR;
  }

  retval = vm_jump_title_part(this->vm, title, part);
  if (retval)
    this->vm->hop_channel++;
  pthread_mutex_unlock(&this->vm_lock);

  return retval ? DVDNAV_STATUS_OK : DVDNAV_STATUS_ERR;
}

dvdnav_status_t dvdnav_part_play_auto_stop(dvdnav_t *this, int32_t title,
					   int32_t part, int32_t parts_to_play) {
  /* FIXME: Implement auto-stop */
 if (dvdnav_part_play(this, title, part) == DVDNAV_STATUS_OK)
   mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NotImplementedYet);
 return DVDNAV_STATUS_ERR;
}

dvdnav_status_t dvdnav_time_play(dvdnav_t *this, int32_t title,
				 uint64_t time) {
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
  
  /* FIXME: Implement */
   mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_NotImplementedYet);
  return DVDNAV_STATUS_ERR;
}

dvdnav_status_t dvdnav_stop(dvdnav_t *this) {
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }
  
  pthread_mutex_lock(&this->vm_lock);
  this->vm->stopped = 1;
  pthread_mutex_unlock(&this->vm_lock);
  return DVDNAV_STATUS_OK;
}

dvdnav_status_t dvdnav_go_up(dvdnav_t *this) {
  if(!this) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_LIBMPDVDNAV_PassedANullPointer);
    return DVDNAV_STATUS_ERR;
  }

  /* A nice easy function... delegate to the VM */
  pthread_mutex_lock(&this->vm_lock);
  vm_jump_up(this->vm);
  pthread_mutex_unlock(&this->vm_lock);

  return DVDNAV_STATUS_OK;
}

// ###################################### navigation.c end ##########################################

// ##################################### read_cahce.c start #########################################


/*
#define READ_CACHE_TRACE 0
*/

read_cache_t *dvdnav_read_cache_new(dvdnav_t* dvd_self) {
  read_cache_t *self;
  int i;

  self = (read_cache_t *)malloc(sizeof(read_cache_t));

  if(self) {
    self->current = 0;
    self->freeing = 0;
    self->dvd_self = dvd_self;
    self->last_sector = 0;
    self->read_ahead_size = READ_AHEAD_SIZE_MIN;
    self->read_ahead_incr = 0;
    pthread_mutex_init(&self->lock, NULL);
    dvdnav_read_cache_clear(self);
    for (i = 0; i < READ_CACHE_CHUNKS; i++) {
      self->chunk[i].cache_buffer = NULL;
      self->chunk[i].usage_count = 0;
    }
  }

  return self;
}

void dvdnav_read_cache_free(read_cache_t* self) {
  dvdnav_t *tmp;
  int i;

  pthread_mutex_lock(&self->lock);
  self->freeing = 1;
  for (i = 0; i < READ_CACHE_CHUNKS; i++)
    if (self->chunk[i].cache_buffer && self->chunk[i].usage_count == 0) {
      free(self->chunk[i].cache_buffer_base);
      self->chunk[i].cache_buffer = NULL;
    }
  pthread_mutex_unlock(&self->lock);

  for (i = 0; i < READ_CACHE_CHUNKS; i++)
    if (self->chunk[i].cache_buffer) return;

  /* all buffers returned, free everything */
  tmp = self->dvd_self;
  pthread_mutex_destroy(&self->lock);
  free(self);
  free(tmp);
}

/* This function MUST be called whenever self->file changes. */
void dvdnav_read_cache_clear(read_cache_t *self) {
  int i;

  if(!self)
   return;

  pthread_mutex_lock(&self->lock);
  for (i = 0; i < READ_CACHE_CHUNKS; i++)
    self->chunk[i].cache_valid = 0;
  pthread_mutex_unlock(&self->lock);
}

/* This function is called just after reading the NAV packet. */
void dvdnav_pre_cache_blocks(read_cache_t *self, int sector, size_t block_count) {
  int i, use;

  if(!self)
    return;

  if(!self->dvd_self->use_read_ahead)
    return;

  pthread_mutex_lock(&self->lock);

  /* find a free cache chunk that best fits the required size */
  use = -1;
  for (i = 0; i < READ_CACHE_CHUNKS; i++)
    if (self->chunk[i].usage_count == 0 && self->chunk[i].cache_buffer &&
        self->chunk[i].cache_malloc_size >= block_count &&
        (use == -1 || self->chunk[use].cache_malloc_size > self->chunk[i].cache_malloc_size))
      use = i;

  if (use == -1) {
    /* we haven't found a cache chunk, so we try to reallocate an existing one */
    for (i = 0; i < READ_CACHE_CHUNKS; i++)
      if (self->chunk[i].usage_count == 0 && self->chunk[i].cache_buffer &&
          (use == -1 || self->chunk[use].cache_malloc_size < self->chunk[i].cache_malloc_size))
        use = i;
    if (use >= 0) {
      self->chunk[use].cache_buffer_base = realloc(self->chunk[use].cache_buffer_base,
        block_count * DVD_VIDEO_LB_LEN + ALIGNMENT);
      self->chunk[use].cache_buffer =
        (uint8_t *)(((uintptr_t)self->chunk[use].cache_buffer_base & ~((uintptr_t)(ALIGNMENT - 1))) + ALIGNMENT);
      mp_msg(MSGT_CPLAYER,MSGL_DBG2,MSGTR_LIBMPDVDNAV_PreCacheDVDReadReallocHappened);
      self->chunk[use].cache_malloc_size = block_count;
    } else {
      /* we still haven't found a cache chunk, let's allocate a new one */
      for (i = 0; i < READ_CACHE_CHUNKS; i++)
        if (!self->chunk[i].cache_buffer) {
	  use = i;
	  break;
	}
      if (use >= 0) {
        /* We start with a sensible figure for the first malloc of 500 blocks.
         * Some DVDs I have seen venture to 450 blocks.
         * This is so that fewer realloc's happen if at all.
         */
	self->chunk[i].cache_buffer_base =
	  malloc((block_count > 500 ? block_count : 500) * DVD_VIDEO_LB_LEN + ALIGNMENT);
	self->chunk[i].cache_buffer =
	  (uint8_t *)(((uintptr_t)self->chunk[i].cache_buffer_base & ~((uintptr_t)(ALIGNMENT - 1))) + ALIGNMENT);
	self->chunk[i].cache_malloc_size = block_count > 500 ? block_count : 500;
      mp_msg(MSGT_CPLAYER,MSGL_DBG2,MSGTR_LIBMPDVDNAV_PreCacheDVDReadMalloc,
	  (block_count > 500 ? block_count : 500 ));
      }
    }
  }

  if (use >= 0) {
    self->chunk[use].cache_start_sector = sector;
    self->chunk[use].cache_block_count = block_count;
    self->chunk[use].cache_read_count = 0;
    self->chunk[use].cache_valid = 1;
    self->current = use;
  } else {
    mp_msg(MSGT_CPLAYER,MSGL_DBG2,MSGTR_LIBMPDVDNAV_PreCachingWasImpossible);
  }
  pthread_mutex_unlock(&self->lock);
}

int dvdnav_read_cache_block(read_cache_t *self, int sector, size_t block_count, uint8_t **buf) {
  int i, use;
  int start;
  int size;
  int incr;
  uint8_t *read_ahead_buf;
  int32_t res;

  if(!self)
    return 0;

  use = -1;

  if(self->dvd_self->use_read_ahead) {
    /* first check, if sector is in current chunk */
    read_cache_chunk_t cur = self->chunk[self->current];
    if (cur.cache_valid && sector >= cur.cache_start_sector &&
        sector <= (cur.cache_start_sector + cur.cache_read_count) &&
        sector + block_count <= cur.cache_start_sector + cur.cache_block_count)
      use = self->current;
    else
      for (i = 0; i < READ_CACHE_CHUNKS; i++)
        if (self->chunk[i].cache_valid &&
            sector >= self->chunk[i].cache_start_sector &&
            sector <= (self->chunk[i].cache_start_sector + self->chunk[i].cache_read_count) &&
            sector + block_count <= self->chunk[i].cache_start_sector + self->chunk[i].cache_block_count)
            use = i;
  }

  if (use >= 0) {
    read_cache_chunk_t *chunk;
    
    /* Increment read-ahead size if sector follows the last sector */
    if (sector == (self->last_sector + 1)) {
      if (self->read_ahead_incr < READ_AHEAD_SIZE_MAX)
        self->read_ahead_incr++;
    } else {
      self->read_ahead_size = READ_AHEAD_SIZE_MIN;
      self->read_ahead_incr = 0;
    }
    self->last_sector = sector;

    /* The following resources need to be protected by a mutex :
     *   self->chunk[*].cache_buffer
     *   self->chunk[*].cache_malloc_size
     *   self->chunk[*].usage_count
     */
    pthread_mutex_lock(&self->lock);
    chunk = &self->chunk[use];
    read_ahead_buf = chunk->cache_buffer + chunk->cache_read_count * DVD_VIDEO_LB_LEN;
    *buf = chunk->cache_buffer + (sector - chunk->cache_start_sector) * DVD_VIDEO_LB_LEN;
    chunk->usage_count++;
    pthread_mutex_unlock(&self->lock);

    mp_msg(MSGT_CPLAYER,MSGL_DBG2,MSGTR_LIBMPDVDNAV_ReadCacheSectorInfo, sector, chunk->cache_start_sector, chunk->cache_start_sector + chunk->cache_block_count);

    /* read_ahead_size */
    incr = self->read_ahead_incr >> 1;
    if ((self->read_ahead_size + incr) > READ_AHEAD_SIZE_MAX) {
      self->read_ahead_size = READ_AHEAD_SIZE_MAX;
    } else {
      self->read_ahead_size += incr;
    }

    /* real read size */
    start = chunk->cache_start_sector + chunk->cache_read_count;
    if (chunk->cache_read_count + self->read_ahead_size > chunk->cache_block_count) {
      size = chunk->cache_block_count - chunk->cache_read_count;
    } else {
      size = self->read_ahead_size;
      /* ensure that the sector we want will be read */
      if (sector >= chunk->cache_start_sector + chunk->cache_read_count + size)
        size = sector - chunk->cache_start_sector - chunk->cache_read_count;
    }
    mp_msg(MSGT_CPLAYER,MSGL_DBG2,MSGTR_LIBMPDVDNAV_ReadCacheReadAheadSize, self->read_ahead_size, size);

    if (size)
      chunk->cache_read_count += DVDReadBlocks(self->dvd_self->file,
                                               start,
                                               size,
                                               read_ahead_buf);

    res = DVD_VIDEO_LB_LEN * block_count;

  } else {

    if (self->dvd_self->use_read_ahead)
    mp_msg(MSGT_CPLAYER,MSGL_DBG2,MSGTR_LIBMPDVDNAV_CacheMissOnSector, sector);

    res = DVDReadBlocks(self->dvd_self->file,
                        sector,
                        block_count,
                        *buf) * DVD_VIDEO_LB_LEN;
  }

  return res;

}

dvdnav_status_t dvdnav_free_cache_block(dvdnav_t *self, unsigned char *buf) {
  read_cache_t *cache;
  int i;

  if (!self)
    return DVDNAV_STATUS_ERR;

  cache = self->cache;
  if (!cache)
    return DVDNAV_STATUS_ERR;

  pthread_mutex_lock(&cache->lock);
  for (i = 0; i < READ_CACHE_CHUNKS; i++) {
    if (cache->chunk[i].cache_buffer && buf >= cache->chunk[i].cache_buffer &&
        buf < cache->chunk[i].cache_buffer + cache->chunk[i].cache_malloc_size * DVD_VIDEO_LB_LEN) {
      cache->chunk[i].usage_count--;
    }
  }
  pthread_mutex_unlock(&cache->lock);

  if (cache->freeing)
    /* when we want to dispose the cache, try freeing it now */
    dvdnav_read_cache_free(cache);

  return DVDNAV_STATUS_OK;
}
// ###################################### read_cahce.c end ##########################################



int dvdnav_seek(dvdnav_priv_t * dvdnav_priv, off_t newpos,stream_t *s) {

  if (dvdnav_sector_search(dvdnav_priv->dvdnav, newpos, SEEK_SET)!=DVDNAV_STATUS_OK)
    {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_MPDVDNAV_ErrorSeek,dvdnav_err_to_string(dvdnav_priv->dvdnav),newpos );
    return 0;
    }
return 1;
}

static int seek(stream_t *s, off_t newpos) {
  off_t seekpos;

  mp_msg(MSGT_CPLAYER,MSGL_DBG2,"DVDNAV seek pos: %llx\n",newpos);
  dvdnav_priv_t* dvdnav_priv=s->priv;
  int domain_vts=dvdnav_priv->vts_domain;
  if(newpos!=s->pos) {
    if (!domain_vts)
      mp_msg(MSGT_CPLAYER,MSGL_V,"Cannot seek in DVDNAV streams yet!\n"); else {
    seekpos=newpos/2048;
    if (dvdnav_seek((dvdnav_priv_t*)s->priv,seekpos,s)) s->pos=newpos;}
    }
  return 1;
}

unsigned int * dvdnav_stream_get_palette(dvdnav_priv_t * dvdnav_priv)
{
  if (!dvdnav_priv) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,"%s: NULL dvdnav_priv\n",__FUNCTION__);
    return NULL;
  }
  if (!dvdnav_priv->dvdnav) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,"%s: NULL dvdnav_priv->dvdnav\n",__FUNCTION__);
    return NULL;
  }
  if (!dvdnav_priv->dvdnav->vm) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,"%s: NULL dvdnav_priv->dvdnav->vm\n",__FUNCTION__);
    return NULL;
  }
  if (!dvdnav_priv->dvdnav->vm->state.pgc) {
    mp_msg(MSGT_CPLAYER,MSGL_INFO,"%s: NULL dvdnav_priv->dvdnav->vm->state.pgc\n",__FUNCTION__);
    return NULL;
  }
return dvdnav_priv->dvdnav->vm->state.pgc->palette;
}

int dvdnav_menu_action(dvdnav_priv_t * dvdnav_priv, int action)
{
  pci_t* pnavpci;
  dsi_t* pnavdsi;
  uint32_t buttonN;
  pnavpci = dvdnav_get_current_nav_pci( dvdnav_priv->dvdnav );
  pnavdsi = dvdnav_get_current_nav_dsi( dvdnav_priv->dvdnav );
  dvdnav_status_t nav_status;
  if (!dvdnav_priv) return 0;
      switch (action) {
        case MP_CMD_DVDNAV_UP:
          nav_status = dvdnav_upper_button_select(dvdnav_priv->dvdnav, pnavpci);
          break;
        case MP_CMD_DVDNAV_DOWN:
          nav_status = dvdnav_lower_button_select(dvdnav_priv->dvdnav, pnavpci);
          break;
        case MP_CMD_DVDNAV_LEFT:
    	  nav_status = dvdnav_left_button_select(dvdnav_priv->dvdnav, pnavpci);
          break;
        case MP_CMD_DVDNAV_RIGHT:
    	  nav_status = dvdnav_right_button_select(dvdnav_priv->dvdnav, pnavpci);
          break;
        case MP_CMD_DVDNAV_MENU:
    	  dvdnav_wait_still_clear(dvdnav_priv->dvdnav);
	  dvdnav_priv->cell_timer=0;
	  dvdnav_priv->stillflg=0;
	  dvdnav_button_select_auto_action( dvdnav_priv->dvdnav, 0);
          nav_status = dvdnav_menu_call(dvdnav_priv->dvdnav,DVD_MENU_Root);
          break;
        case MP_CMD_DVDNAV_SELECT:
    	  dvdnav_wait_still_clear(dvdnav_priv->dvdnav);
	  dvdnav_priv->cell_timer=0;
	  dvdnav_priv->stillflg=0;
	  dvdnav_button_select_auto_action( dvdnav_priv->dvdnav, 0);
          nav_status = dvdnav_button_activate(dvdnav_priv->dvdnav, pnavpci);
          break;
        default:
	  return 0;
	}
if (nav_status == DVDNAV_STATUS_ERR) {mp_msg(MSGT_CPLAYER, MSGL_WARN, MSGTR_MPDVDNAV_ErrorNav,dvdnav_err_to_string(dvdnav_priv->dvdnav)); return 0;}
return 1;
}

void dvdnav_stream_fullstart(dvdnav_priv_t * dvdnav_priv) {
  if (dvdnav_priv && !dvdnav_priv->started) {
//    dvdnav_stream_reset(dvdnav_priv);
    dvdnav_priv->started=1;
  }
  dvdnav_event_clear(dvdnav_priv);
}

int dvdnav_reallyeof(dvdnav_priv_t * dvdnav_priv) 
{
if (!dvdnav_priv) return 1;
return dvdnav_priv->stopflg;
}

void dvdnav_set_language(dvdnav_priv_t *dvdnav_priv, char *alang, char *slang, char *mlang)	/* set nav languages */
{
if (slang) { if (dvdnav_spu_language_select(dvdnav_priv->dvdnav, slang ) != DVDNAV_STATUS_OK)	/* FIXME: don't work! */
  mp_msg(MSGT_FIXME, MSGL_FIXME, "Error on setting spu languages: %s\n",dvdnav_err_to_string(dvdnav_priv->dvdnav)); }
if (alang) { if (dvdnav_audio_language_select(dvdnav_priv->dvdnav, alang ) != DVDNAV_STATUS_OK)	/* FIXME: don't work! */
  mp_msg(MSGT_FIXME, MSGL_FIXME, "Error on setting audio languages: %s\n",dvdnav_err_to_string(dvdnav_priv->dvdnav)); }
if (mlang) { if (dvdnav_menu_language_select(dvdnav_priv->dvdnav, dvdmenu_lang ) != DVDNAV_STATUS_OK)
  mp_msg(MSGT_FIXME, MSGL_FIXME, "Error on setting menu languages: %s\n",dvdnav_err_to_string(dvdnav_priv->dvdnav)); }
return;
}

int dvdnav_lang_from_sid(dvdnav_priv_t *dvdnav_priv, int id) {
  if (!dvdnav_priv) return 0;
  if (id >= dvdnav_priv->nr_of_subtitles) return 0;
  return dvdnav_priv->subtitles[id].language;
}

int dvdnav_number_of_subs(dvdnav_priv_t *dvdnav_priv) {
  if (!dvdnav_priv) return -1;
  return dvdnav_priv->nr_of_subtitles;
}

void dvdnav_event_lock(dvdnav_priv_t * dvdnav_priv, int lock_mode) {
  dvdnav_priv->event_lock=lock_mode;	/* set event lock */
}
int dvdnav_isevent_lock(dvdnav_priv_t * dvdnav_priv) {
  return dvdnav_priv->event_lock;	/* get event lock */
}


static void stream_dvdnav_close(stream_t *s) {
free_dvdnav_stream((dvdnav_priv_t*)s->priv);
}

void dvdnav_event_clear(dvdnav_priv_t * dvdnav_priv)
{
memset(&(dvdnav_priv->event),0,sizeof(stream_dvdnav_event_t));
return;
}

void dvdnav_get_highlight(dvdnav_priv_t *dvdnav_priv,dvdnav_highlight_event_t *highlight_event)
{
pci_t* pnavpci;
int button_number = -1;
int b_mode = 0;
if (!dvdnav_priv) return;
if (!dvdnav_priv->dvdnav) return;
if (!highlight_event) return;
if (NULL==(pnavpci = dvdnav_get_current_nav_pci( dvdnav_priv->dvdnav ))) return;
dvdnav_get_current_highlight(dvdnav_priv->dvdnav, &(highlight_event->buttonN));
highlight_event->display=1; /* show */
if (highlight_event->display==2) b_mode=1;	/* activate */
if (highlight_event->buttonN > 0 && pnavpci->hli.hl_gi.btn_ns > 0 && highlight_event->display) {
    for (button_number = 0; button_number < pnavpci->hli.hl_gi.btn_ns; button_number++) {
        btni_t *btni = &(pnavpci->hli.btnit[button_number]);
        if(highlight_event->buttonN==button_number+1) {
	    highlight_event->sx=min(btni->x_start,btni->x_end);
	    highlight_event->ex=max(btni->x_start,btni->x_end);
	    highlight_event->sy=min(btni->y_start,btni->y_end);
	    highlight_event->ey=max(btni->y_start,btni->y_end);
    	    if (btni->btn_coln==0) highlight_event->palette=0;
		else highlight_event->palette=pnavpci->hli.btn_colit.btn_coli[btni->btn_coln-1][b_mode];
	    }
	}
    } else {	/* hide button or none button */
    highlight_event->sx=0;
    highlight_event->ex=0;
    highlight_event->sy=0;
    highlight_event->ey=0;
    highlight_event->palette=0; 
    highlight_event->buttonN=0; }
return;
}

void dvdnav_get_button_palette(dvdnav_priv_t *dvdnav_priv,uint32_t *palette)	/* get button palette */
{
pci_t* pnavpci;
int button_number;
int buttonN;
int b_mode = 0;
if (!dvdnav_priv) return;
if (!dvdnav_priv->dvdnav) return;
if (!palette) return;
if (NULL==(pnavpci = dvdnav_get_current_nav_pci( dvdnav_priv->dvdnav ))) return;
dvdnav_get_current_highlight(dvdnav_priv->dvdnav, &buttonN);	/* get current button ID */
if (pnavpci->hli.hl_gi.btn_ns) {	/* is buttons */
    for (button_number = 0; button_number < pnavpci->hli.hl_gi.btn_ns; button_number++) {
        btni_t *btni = &(pnavpci->hli.btnit[button_number]);
        if(buttonN==button_number+1) {					/* current button? */
    	    if (btni->btn_coln==0) *palette=0;
		else *palette=pnavpci->hli.btn_colit.btn_coli[btni->btn_coln-1][b_mode];
	    }
	}
    }
if (*palette==0)	/* if none current button? */
    {
    btni_t *btni = &(pnavpci->hli.btnit[0]);
    if (btni->btn_coln==0) *palette=0;
	else *palette=pnavpci->hli.btn_colit.btn_coli[btni->btn_coln-1][b_mode];
    }
return;
}

#ifdef HAVE_NEW_GUI
void dvdnav_mouse(dvdnav_priv_t * dvdnav_priv)
{
  pci_t* pnavpci;
  if (dvdnav_mouse_set!=1) return;
  dvdnav_mouse_set=2;		// Lock mouse event
  if (NULL==(pnavpci = dvdnav_get_current_nav_pci( dvdnav_priv->dvdnav ))) return;
  if (dvdnav_window_orig_width>0 && dvdnav_window_orig_height>0 && dvdnav_window_width>0 && dvdnav_window_height>0)
    {
    // Calculate original mouse position (if scaled window)
    dvdnav_mouse_orig_x=dvdnav_mouse_x*dvdnav_window_orig_width/dvdnav_window_width;
    dvdnav_mouse_orig_y=dvdnav_mouse_y*dvdnav_window_orig_height/dvdnav_window_height;
    }
    else
    {
    dvdnav_mouse_orig_x=dvdnav_mouse_x;
    dvdnav_mouse_orig_y=dvdnav_mouse_y;
    }
  if (pnavpci->hli.hl_gi.btn_ns) {	/* is buttons */
    dvdnav_mouse_select(dvdnav_priv->dvdnav, pnavpci, dvdnav_mouse_orig_x, dvdnav_mouse_orig_y); /* mouse position select */
    if (dvdnav_mouse_button) dvdnav_menu_action(dvdnav_priv, MP_CMD_DVDNAV_SELECT); }	/* mouse button event */
  dvdnav_mouse_set=0;
  dvdnav_mouse_button=0;
  return;
}
#endif

#define FIRST_AC3_AID 128
#define FIRST_DTS_AID 136
#define FIRST_MPG_AID 0
#define FIRST_PCM_AID 160

int dvdnav_stream_read(dvdnav_priv_t * dvdnav_priv, unsigned char *buf, int *len,stream_dvdnav_event_t *event) {
  int eventid = DVDNAV_NOP;

  if (!len) return -1;
  *len=-1;
  if (!dvdnav_priv) return -1;
  if (!buf) return -1;
  if (dvdnav_priv->stopflg) return -1;

  if (dvd_nav_still) {
    mp_msg(MSGT_CPLAYER,MSGL_DBG2,MSGTR_MPDVDNAV_ReadInStillActive,__FUNCTION__);
    *len=0;
    return -1;
  }

  if (dvdnav_get_next_block(dvdnav_priv->dvdnav,buf,&eventid,len)!=DVDNAV_STATUS_OK) {
    mp_msg(MSGT_CPLAYER,MSGL_ERR,MSGTR_MPDVDNAV_ErrorGetNextBlock,dvdnav_err_to_string(dvdnav_priv->dvdnav) );
    *len=-1;
  }
  else if (eventid!=DVDNAV_BLOCK_OK) {
    switch (eventid) {
#if 0
	case DVDNAV_NOP : {
	    event->eventflag.nop=1; 
	    event->eventflag.isevent=1;
	    break; }
#endif
	case DVDNAV_STILL_FRAME: {
    	    event->eventflag.still_frame=1;
	    event->eventflag.isevent=1;
	    dvdnav_priv->stillflg=1;
    	    dvdnav_still_event_t *still_event = (dvdnav_still_event_t*)(buf);
    	    event->still_length=still_event->length;
#ifdef USE_MPDVDNAV_TRACE
	    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "dvdnav: still frame (%x)\n",event->still_length);
#endif
	    if (event->still_length==0xff)
	      {
	      dvdnav_still_back(dvdnav_priv->dvdnav);
	      } else {
	      unsigned int end_time=still_event->length*1000000;
	      if (!dvdnav_priv->cell_timer) dvdnav_priv->cell_timer = GetTimer();
	      unsigned int now = GetTimer();
#ifdef USE_MPDVDNAV_TRACE
	    if (dvdnav_trace) {
	      float pt=now-dvdnav_priv->cell_timer;
	      pt=pt/1000000;
	      float et=end_time;
	      et=et/1000000;
	      mp_msg(MSGT_CPLAYER,MSGL_INFO, "dvdnav: remains time %8.3f sec\n",et-pt); }
#endif

	      if (now-dvdnav_priv->cell_timer<end_time) {	// cell play time end?
	        dvdnav_button_select_auto_action( dvdnav_priv->dvdnav, 1);
    	        dvdnav_still_back(dvdnav_priv->dvdnav); // don't let dvdnav stall on this image
	        } else {
//	        dvdnav_priv->back_from_timer=1;
//	        dvdnav_wait_still_clear(dvdnav_priv->dvdnav);
    	        dvdnav_still_skip(dvdnav_priv->dvdnav);
	        dvdnav_priv->cell_timer=0;}
	      }
    	    break; }
	case DVDNAV_SPU_STREAM_CHANGE : {
	    event->eventflag.spu_stream_change=1;
	    event->eventflag.isevent=1;
	    dvdnav_spu_stream_change_event_t *spu_stream_change_event = (dvdnav_spu_stream_change_event_t*)(buf);
	    event->spu_physical_wide=spu_stream_change_event->physical_wide;
	    event->spu_physical_letterbox=spu_stream_change_event->physical_letterbox;
	    event->spu_physical_pan_scan=spu_stream_change_event->physical_pan_scan;
#ifdef USE_MPDVDNAV_TRACE
	    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "dvdnav: spu stream change\n");
#endif
	    break; }
        case DVDNAV_AUDIO_STREAM_CHANGE : {
	    event->eventflag.audio_stream_change=1; 
	    event->eventflag.isevent=1;
	    dvdnav_audio_stream_change_event_t *audio_stream_change_event = (dvdnav_audio_stream_change_event_t*)(buf);
	    event->audio_physical=audio_stream_change_event->physical;
	    event->audio_logical=audio_stream_change_event->logical;
#ifdef USE_MPDVDNAV_TRACE
	    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "dvdnav: audio stream change\n");
#endif
	    break; }
	case DVDNAV_VTS_CHANGE : {
#ifdef USE_MPDVDNAV_TRACE
	    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "dvdnav: vts change\n");
#endif
	    event->eventflag.vts_change=1; 
	    event->eventflag.isevent=1;
	    dvdnav_vts_change_event_t *vts_change_event = (dvdnav_vts_change_event_t*)(buf);
	    event->vts_old_vtsN=vts_change_event->old_vtsN;
	    event->vts_old_domain=vts_change_event->old_domain;
	    event->vts_new_vtsN=vts_change_event->new_vtsN;
	    event->vts_new_domain=vts_change_event->new_domain;
	    break; }
        case DVDNAV_CELL_CHANGE : {
	    event->eventflag.cell_change=1; 
	    event->eventflag.isevent=1;
	    dvdnav_cell_change_event_t *cell_change_event = (dvdnav_cell_change_event_t*)(buf);
	    event->cell_cellN=cell_change_event->cellN;
	    event->cell_pgN=cell_change_event->pgN;
	    event->cell_cell_length=cell_change_event->cell_length;
	    event->cell_pg_length=cell_change_event->pg_length;
	    event->cell_pgc_length=cell_change_event->pgc_length;
	    event->cell_cell_start=cell_change_event->cell_start;
	    event->cell_pg_start=cell_change_event->pg_start;
	    int pgcN = 0;
	    dvdnav_get_pgc(dvdnav_priv->dvdnav, &pgcN);
// get dvdnav audio settings
	    uint8_t alang=dvdnav_get_active_audio_stream(dvdnav_priv->dvdnav);
	    dvdnav_priv->alang=128+dvdnav_get_active_audio_stream(dvdnav_priv->dvdnav);
// get dvdnav spu settings
	    dvdnav_priv->slang=dvdnav_get_active_spu_stream(dvdnav_priv->dvdnav);		
// get dvdnav aspect settings
	    dvdnav_priv->aspect=dvdnav_get_video_aspect(dvdnav_priv->dvdnav);
// setting dvdnav info
	    dvdnav_priv->vts_domain=dvdnav_is_domain_vts(dvdnav_priv->dvdnav);
	    dvdnav_get_position(dvdnav_priv->dvdnav, &(dvdnav_priv->tpos), &(dvdnav_priv->tlen));
	    dvdnav_get_number_of_titles(dvdnav_priv->dvdnav, &(dvdnav_priv->titles_nr));
	    dvdnav_priv->part=0;
	    dvdnav_priv->title=0;
	    if (!(dvdnav_priv->vts_domain)) {
	      dvdnav_priv->chapters_nr=0; 
	      if (!dvdnav_priv->cell_timer && !dvdnav_priv->event_lock &&
	        (dvdnav_priv->cell_cellN!=event->cell_cellN || dvdnav_priv->cell_pgN!=event->cell_pgN || dvdnav_priv->cell_pgcN!=pgcN)	// really cell change?
	        ) dvdnav_priv->cell_timer=GetTimer();
	      if (dvdnav_priv->event_lock) dvdnav_priv->cell_timer=0;
	      } else {
	      if (!dvdnav_priv->stillflg) dvdnav_priv->cell_timer=0;
	      dvdnav_current_title_info(dvdnav_priv->dvdnav, &dvdnav_priv->title, &dvdnav_priv->part);
	      dvdnav_get_number_of_parts(dvdnav_priv->dvdnav, dvdnav_priv->title, &dvdnav_priv->chapters_nr);

	      uint32_t audio_id;
	      audio_attr_t audio_attr;
	      dvdnav_priv->nr_of_channels=0;
	      int language;
	      int i;
	      for( i=0; i<8; i++)
	        {
		if (-1!=(audio_id=audio_id=dvdnav_get_audio_logical_stream(dvdnav_priv->dvdnav, i))) {
	          dvdnav_get_audio_attr(dvdnav_priv->dvdnav, i, &audio_attr);
		  language=0;
		  if(audio_attr.lang_type==1)
		    {
		    language=audio_attr.lang_code;
		    }
		  dvdnav_priv->audio_streams[dvdnav_priv->nr_of_channels].language=language;
		  dvdnav_priv->audio_streams[dvdnav_priv->nr_of_channels].id=audio_id;
		  switch (audio_attr.audio_format) {
		    case 0: // ac3
		      dvdnav_priv->audio_streams[dvdnav_priv->nr_of_channels].id+=FIRST_AC3_AID;
		    break;
		    case 6: // dts
		      dvdnav_priv->audio_streams[dvdnav_priv->nr_of_channels].id+=FIRST_DTS_AID;
		    break;
		    case 2: // mpeg layer 1/2/3
		    case 3: // mpeg2 ext
		      dvdnav_priv->audio_streams[dvdnav_priv->nr_of_channels].id+=FIRST_MPG_AID;
		    break;
		    case 4: // lpcm
		      dvdnav_priv->audio_streams[dvdnav_priv->nr_of_channels].id+=FIRST_PCM_AID;
		    break;
		    }
		  dvdnav_priv->audio_streams[dvdnav_priv->nr_of_channels].type=audio_attr.audio_format;
		  dvdnav_priv->audio_streams[dvdnav_priv->nr_of_channels].channels=audio_attr.channels;
		  if (alang==i) dvdnav_priv->alang=dvdnav_priv->audio_streams[dvdnav_priv->nr_of_channels].id;
		  dvdnav_priv->nr_of_channels++;
	          }
		}
	      dvdnav_priv->nr_of_subtitles=0;
	      uint32_t sub_id;
	      subp_attr_t subp_attr;
	      for( i=0; i<32; i++)
	        {
		if (-1!=(sub_id=dvdnav_get_spu_logical_stream(dvdnav_priv->dvdnav, i))) {
	          dvdnav_get_spu_attr(dvdnav_priv->dvdnav, i, &subp_attr);
		  language=0;
		  if(subp_attr.type==1)
		    {
		    language=subp_attr.lang_code;
		    }
		  dvdnav_priv->subtitles[dvdnav_priv->nr_of_subtitles].language=language;
		  dvdnav_priv->subtitles[dvdnav_priv->nr_of_subtitles].id=sub_id;
		  dvdnav_priv->nr_of_subtitles++;
	          }
		}
	      }
	    dvdnav_priv->cell_cellN=cell_change_event->cellN;
	    dvdnav_priv->cell_pgN=cell_change_event->pgN;
	    dvdnav_priv->cell_pgcN=pgcN;
#ifdef USE_MPDVDNAV_TRACE
	    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "dvdnav: cell change\n");
#endif
	    break; }
	case DVDNAV_NAV_PACKET : {
	    event->eventflag.nav_packet=1; 
	    event->eventflag.isevent=1;
	    pci_t* pnavpci = dvdnav_get_current_nav_pci( dvdnav_priv->dvdnav );
	    int buttonN;
	    if (pnavpci && pnavpci->hli.hl_gi.btn_ns) {
	      dvdnav_get_current_highlight(dvdnav_priv->dvdnav, &buttonN);	/* get current button ID */
	      if (buttonN==0 || buttonN > pnavpci->hli.hl_gi.btn_ns) dvdnav_button_select( dvdnav_priv->dvdnav , pnavpci, 1);
	      }
#ifdef USE_MPDVDNAV_TRACE
	    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "dvdnav: nav packet\n");
#endif
	    break; }
	case DVDNAV_STOP: {
    	    event->eventflag.stop=1;
	    event->eventflag.isevent=1;
	    dvdnav_priv->stopflg=1;
#ifdef USE_MPDVDNAV_TRACE
	    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "dvdnav: stop\n");
#endif
	    break; }
        case DVDNAV_HIGHLIGHT : {
	    event->eventflag.highlight=1; 
	    event->eventflag.isevent=1;
	    dvdnav_highlight_event_t *highlight_event = (dvdnav_highlight_event_t*)(buf);
	    event->hl_display=highlight_event->display;
	    /* libdvdnav v0.1.10: these fields are currently not set!! */
	    dvdnav_get_highlight(dvdnav_priv,highlight_event);
	    event->hl_palette=highlight_event->palette;
	    event->hl_sx=highlight_event->sx;
	    event->hl_sy=highlight_event->sy;
	    event->hl_ex=highlight_event->ex;
	    event->hl_ey=highlight_event->ey;
	    event->hl_pts=highlight_event->pts;
	    event->hl_buttonN=highlight_event->buttonN;
#ifdef USE_MPDVDNAV_TRACE
	    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "dvdnav: highlight (%i:%i-%i:%i #%i)\n",event->hl_sx,event->hl_sy,event->hl_ex,event->hl_ey,event->hl_buttonN);
#endif
	    break; }
	case DVDNAV_SPU_CLUT_CHANGE: {
	    event->eventflag.spu_clut_change=1;
	    event->eventflag.isevent=1;
	    memcpy(event->spu_clut,buf,16*sizeof(uint32_t));
	    dvdnav_get_button_palette(dvdnav_priv,&(event->spu_palette));
#ifdef USE_MPDVDNAV_TRACE
	    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "dvdnav: spu clut change\n");
#endif
    	    break; }
	case DVDNAV_HOP_CHANNEL : {
	    event->eventflag.hop_channel=1; 
	    event->eventflag.isevent=1;
#ifdef USE_MPDVDNAV_TRACE
	    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "dvdnav: hop channel\n");
#endif
	    break; }
	case DVDNAV_WAIT: {
#ifdef USE_MPDVDNAV_TRACE
	    if (dvdnav_trace) mp_msg(MSGT_CPLAYER,MSGL_INFO, "dvdnav: wait\n");
#endif
	    event->eventflag.wait=1;
	    event->eventflag.isevent=1;
	    int flag=0;
	    dvdnav_is_still_cell( dvdnav_priv->dvdnav , &flag);
	    if (!dvdnav_priv->vts_domain && dvdnav_priv->cell_timer && !dvdnav_priv->event_lock && !flag)
	      {
	      pci_t* pnavpci = dvdnav_get_current_nav_pci( dvdnav_priv->dvdnav );
	      unsigned int end_time=(pnavpci->pci_gi.e_eltm.hour*3600+pnavpci->pci_gi.e_eltm.minute*60+pnavpci->pci_gi.e_eltm.second)*1000000;
	      unsigned int now = GetTimer();
	      float t;
	      t=now-dvdnav_priv->cell_timer;
	      t=t/1000000;
#ifdef USE_MPDVDNAV_TRACE
	      if (dvdnav_trace) {
	        float rt=end_time-(now-dvdnav_priv->cell_timer);
	        rt=rt/1000000;
	        mp_msg(MSGT_CPLAYER,MSGL_INFO, "dvdnav: remains time %8.3f sec\n",rt); }
#endif
	      if (t<2.00 || 1) {
	        if (now-dvdnav_priv->cell_timer<end_time) {	// cell play time end?
		  dvdnav_wait_back(dvdnav_priv->dvdnav);
		  } else {
		  dvdnav_priv->cell_timer=0;
		  dvdnav_priv->back_from_timer=1;
		  dvdnav_wait_still_clear(dvdnav_priv->dvdnav);
		  } 
		} else {
		dvdnav_wait_skip(dvdnav_priv->dvdnav); 
		dvdnav_priv->cell_timer=0;
		}
	      } else {
	      dvdnav_wait_skip(dvdnav_priv->dvdnav);
	      if (!flag) dvdnav_priv->cell_timer=0;
	      }
	    break; }
	}
    *len=0;
  }
return eventid;
}


static int fill_buffer(stream_t *s, char *but, int len)
{
  int event;
  dvdnav_priv_t* dvdnav_priv=s->priv;
  len=0;
  while (len==0) /* grab all event until DVDNAV_BLOCK_OK (len=2048), DVDNAV_STOP or DVDNAV_STILL_FRAME */
    {
    if (-1==(event=dvdnav_stream_read(dvdnav_priv,s->buffer,&len,&(dvdnav_priv->event)))) {mp_msg(MSGT_CPLAYER,MSGL_ERR,"DVDNAV stream read error!\n"); return 0;}
    switch (event) {
	case DVDNAV_STOP: return len; break; /* return eof */
//	case DVDNAV_STILL_FRAME: return len; break; /* return eof */
	}
    /* the query of the time lenght of title stream mode: event_lock true */
    if (dvdnav_priv->event_lock && (dvdnav_priv->event.eventflag.cell_change) && (dvdnav_priv->title!=dvdnav_priv->lasttitle || 
	(dvdnav_priv->title==0 && dvdnav_priv->vts_domain!=dvdnav_priv->old_vts_domain))) { /* if end of title switch other then eof */
      /* end of play title */
      dvdnav_event_clear(dvdnav_priv); /* stream event clear */
      /* reset title play title & part */
      dvdnav_part_play(dvdnav_priv->dvdnav, dvdnav_priv->lasttitle, dvdnav_priv->lastpart);
      /* return eof */
//      printf("eof!!\n");
      return len;
      }
    }
#if 1
  if (dvdnav_priv->event_lock) // lock events ??
    dvdnav_event_clear(dvdnav_priv); /* stream event clear */
#endif
  mp_msg(MSGT_STREAM,MSGL_DBG2,"DVDNAV fill_buffer len: %d\n",len);
  return len;
}

dvdnav_priv_t * new_dvdnav_stream(char * filename, uint32_t titlenum) {
  char * title_str;
  dvdnav_priv_t *dvdnav_priv;

  if (!filename) return NULL;
  if (!(dvdnav_priv=(dvdnav_priv_t*)calloc(1,sizeof(*dvdnav_priv))))  return NULL;
  if (!(dvdnav_priv->filename=strdup(filename))) {
    free(dvdnav_priv);
    return NULL; }
  if(dvdnav_open(&(dvdnav_priv->dvdnav),dvdnav_priv->filename)!=DVDNAV_STATUS_OK) {
    free(dvdnav_priv->filename);
    free(dvdnav_priv);
    return NULL; }
  if (!dvdnav_priv->dvdnav) {
    free(dvdnav_priv);
    return NULL; }
//  dvdnav_stream_ignore_timers(dvdnav_priv,dvd_nav_skip_opening);
  /* turn on dvdnav caching */
  dvdnav_set_readahead_flag(dvdnav_priv->dvdnav,0);
  /* report the title?! */
#if 0
  if (dvdnav_get_title_string(dvdnav_priv->dvdnav,&title_str)==DVDNAV_STATUS_OK) {
    mp_msg(MSGT_OPEN,MSGL_INFO,"Title: '%s'\n",title_str);
  }
#endif
  if (dvdmenu_lang)	// menu
    {
    if (dvdnav_menu_language_select(dvdnav_priv->dvdnav, dvdmenu_lang ) != DVDNAV_STATUS_OK)
      mp_msg(MSGT_FIXME, MSGL_FIXME, "Error on setting menu languages: %s\n",dvdnav_err_to_string(dvdnav_priv->dvdnav));
    }
    /* use title time length (default: part time length) */
  if (dvdnav_set_PGC_positioning_flag(dvdnav_priv->dvdnav, 1) != DVDNAV_STATUS_OK)
      mp_msg(MSGT_FIXME, MSGL_FIXME, "Error on setting pgc based: %s\n",dvdnav_err_to_string(dvdnav_priv->dvdnav));
  dvdnav_event_clear(dvdnav_priv);
  if (titlenum>0) dvdnav_go_title=titlenum;
  return dvdnav_priv;
}

void free_dvdnav_stream(dvdnav_priv_t * dvdnav_priv) {
  if (!dvdnav_priv) return;
  dvdnav_close(dvdnav_priv->dvdnav);
  dvdnav_priv->dvdnav=NULL;
  free(dvdnav_priv);
}


static int open_s(stream_t *stream,int mode, void* opts, int* file_format) {
  struct stream_priv_s* p = (struct stream_priv_s*)opts;
  char *filename, *name;
  int event,len,tmplen=0;
  dvdnav_priv_t *dvdnav_priv;

  mp_msg(MSGT_OPEN,MSGL_INFO,"URL: %s\n", filename);
  filename = strdup(stream->url);
  name = (filename[9] == '\0') ? NULL : filename + 9;

  uint32_t titlenum=0;
  if (name) titlenum=atoi(name);	/* eg. mplayer dvdnav://1 */
  if (dvd_device)
    {
    if(!(dvdnav_priv=new_dvdnav_stream(dvd_device,titlenum))) {
      mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_CantOpenDVD,dvd_device);
      return STREAM_UNSUPORTED;}
    }
    else
    {
    if(!(dvdnav_priv=new_dvdnav_stream(DEFAULT_DVD_DEVICE,titlenum))) {
      mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_CantOpenDVD,dvd_device);
      return STREAM_UNSUPORTED;}
    }
  stream->sector_size = 2048;
  stream->flags = STREAM_READ | STREAM_SEEK;
  stream->fill_buffer = fill_buffer;
  stream->seek = seek;
  stream->close = stream_dvdnav_close;
  stream->type = STREAMTYPE_DVDNAV;
  stream->priv=(void*)dvdnav_priv;

  return STREAM_OK;
}

stream_info_t stream_info_dvdnav = {
  "DVDNAV stream",
  "dvdnav",
  "Otvos Attila",
  "",
  open_s,
  { "dvdnav", NULL },
  &stream_opts,
  1 // Urls are an option string
};

