mirror of https://github.com/odrling/Aegisub
1260 lines
26 KiB
C++
1260 lines
26 KiB
C++
/*
|
|
* Copyright (C) 2003-2006 Gabest
|
|
* http://www.gabest.org
|
|
*
|
|
* This Program 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, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This Program 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 GNU Make; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*
|
|
*/
|
|
|
|
#include "StdAfx.h"
|
|
#include "vobsubfileripper.h"
|
|
#include "../include/decss/VobDec.h"
|
|
#include "../subtitles/CCDecoder.h"
|
|
|
|
//
|
|
// CVobSubFileRipper
|
|
//
|
|
|
|
CVobSubFileRipper::CVobSubFileRipper()
|
|
: CVobSubFile(NULL)
|
|
, m_fThreadActive(false)
|
|
, m_fBreakThread(false)
|
|
, m_fIndexing(false)
|
|
{
|
|
m_rd.Reset();
|
|
CAMThread::Create();
|
|
}
|
|
|
|
CVobSubFileRipper::~CVobSubFileRipper()
|
|
{
|
|
CAMThread::CallWorker(CMD_EXIT);
|
|
CAMThread::Close();
|
|
}
|
|
|
|
STDMETHODIMP CVobSubFileRipper::NonDelegatingQueryInterface(REFIID riid, void** ppv)
|
|
{
|
|
return
|
|
QI(IVSFRipper)
|
|
__super::NonDelegatingQueryInterface(riid, ppv);
|
|
}
|
|
|
|
void CVobSubFileRipper::Log(log_t type, LPCTSTR lpszFormat, ...)
|
|
{
|
|
CAutoLock cAutoLock(&m_csCallback);
|
|
if(!m_pCallback) return;
|
|
|
|
TCHAR buff[1024];
|
|
|
|
va_list args;
|
|
va_start(args, lpszFormat);
|
|
_vstprintf(buff, lpszFormat, args);
|
|
va_end(args);
|
|
|
|
CString msg;
|
|
switch(type)
|
|
{
|
|
default:
|
|
case LOG_INFO: msg = _T(""); break;
|
|
case LOG_WARNING: msg = _T("WARNING: "); break;
|
|
case LOG_ERROR: msg = _T("ERROR: "); break;
|
|
}
|
|
|
|
msg += buff;
|
|
|
|
m_pCallback->OnMessage(msg);
|
|
}
|
|
|
|
void CVobSubFileRipper::Progress(double progress)
|
|
{
|
|
CAutoLock cAutoLock(&m_csCallback);
|
|
if(!m_pCallback) return;
|
|
|
|
m_pCallback->OnProgress(progress);
|
|
}
|
|
|
|
void CVobSubFileRipper::Finished(bool fSucceeded)
|
|
{
|
|
CAutoLock cAutoLock(&m_csCallback);
|
|
if(!m_pCallback) return;
|
|
|
|
m_pCallback->OnFinished(fSucceeded);
|
|
}
|
|
|
|
#define ReadBEb(var) \
|
|
f.Read(&((BYTE*)&var)[0], 1); \
|
|
|
|
#define ReadBEw(var) \
|
|
f.Read(&((BYTE*)&var)[1], 1); \
|
|
f.Read(&((BYTE*)&var)[0], 1); \
|
|
|
|
#define ReadBEdw(var) \
|
|
f.Read(&((BYTE*)&var)[3], 1); \
|
|
f.Read(&((BYTE*)&var)[2], 1); \
|
|
f.Read(&((BYTE*)&var)[1], 1); \
|
|
f.Read(&((BYTE*)&var)[0], 1); \
|
|
|
|
bool CVobSubFileRipper::LoadIfo(CString fn)
|
|
{
|
|
CString str;
|
|
|
|
CFileStatus status;
|
|
if(!CFileGetStatus(fn, status) || !status.m_size)
|
|
{
|
|
Log(LOG_ERROR, _T("Invalid ifo"));
|
|
return(false);
|
|
}
|
|
|
|
CFile f;
|
|
if(!f.Open(fn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyWrite))
|
|
{
|
|
Log(LOG_ERROR, _T("Cannot open ifo"));
|
|
return(false);
|
|
}
|
|
|
|
Log(LOG_INFO, _T("Opening ifo OK"));
|
|
|
|
char hdr[13];
|
|
f.Read(hdr, 12);
|
|
hdr[12] = 0;
|
|
if(strcmp(hdr, "DVDVIDEO-VTS"))
|
|
{
|
|
Log(LOG_ERROR, _T("Not a Video Title Set IFO file!"));
|
|
return(false);
|
|
}
|
|
|
|
// lang ids
|
|
|
|
f.Seek(0x254, CFile::begin);
|
|
|
|
WORD ids[32];
|
|
memset(ids, 0, sizeof(ids));
|
|
|
|
int len = 0;
|
|
ReadBEw(len);
|
|
|
|
for(int i = 0; i < len; i++)
|
|
{
|
|
f.Seek(2, CFile::current); // 01 00 ?
|
|
ReadBEw(ids[i]);
|
|
if(ids[i] == 0) ids[i] = '--';
|
|
f.Seek(2, CFile::current); // 00 00 ?
|
|
}
|
|
|
|
/* Video info */
|
|
|
|
f.Seek(0x200, CFile::begin);
|
|
f.Read(&m_rd.vidinfo, 2);
|
|
|
|
SIZE res[4][2] =
|
|
{
|
|
{{720,480},{720,576}},
|
|
{{704,480},{704,576}},
|
|
{{352,480},{352,576}},
|
|
{{352,240},{352,288}}
|
|
};
|
|
|
|
m_rd.vidsize = res[m_rd.vidinfo.source_res][m_rd.vidinfo.system&1];
|
|
|
|
double rate = (m_rd.vidinfo.system == 0) ? 30.0/29.97 : 1.0;
|
|
|
|
/* PGCs */
|
|
|
|
{
|
|
DWORD offset;
|
|
|
|
DWORD pgcpos;
|
|
f.Seek(0xc0+0x0c, CFile::begin);
|
|
ReadBEdw(pgcpos);
|
|
pgcpos *= 0x800;
|
|
|
|
WORD nPGC;
|
|
f.Seek(pgcpos, CFile::begin);
|
|
ReadBEw(nPGC);
|
|
|
|
m_rd.pgcs.RemoveAll();
|
|
m_rd.pgcs.SetCount(nPGC);
|
|
|
|
for(int i = 0; i < nPGC; i++)
|
|
{
|
|
PGC& pgc = m_rd.pgcs[i];
|
|
|
|
f.Seek(pgcpos + 8 + i*8 + 4, CFile::begin);
|
|
ReadBEdw(offset);
|
|
offset += pgcpos;
|
|
|
|
BYTE nProgs, nCells;
|
|
f.Seek(offset + 2, CFile::begin);
|
|
ReadBEb(nProgs);
|
|
ReadBEb(nCells);
|
|
|
|
//
|
|
|
|
memcpy(pgc.ids, ids, sizeof(ids));
|
|
|
|
struct splanginfo {BYTE res1, id1, id2, res2;};
|
|
splanginfo splinfo[32];
|
|
|
|
f.Seek(offset + 0x1c, CFile::begin);
|
|
f.Read(splinfo, 32*4);
|
|
|
|
for(int j = 0; j < 32; j++)
|
|
{
|
|
if(splinfo[j].id1 || splinfo[i].id2)
|
|
{
|
|
WORD tmpids[32];
|
|
memset(tmpids, 0, sizeof(tmpids));
|
|
|
|
for(j = 0; j < 32; j++)
|
|
{
|
|
if(!(splinfo[j].res1 & 0x80)) break;
|
|
|
|
pgc.ids[splinfo[j].id1] = ids[j];
|
|
pgc.ids[splinfo[j].id2] = ids[j];
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
|
|
f.Seek(offset + 0xa4, CFile::begin);
|
|
|
|
for(int j = 0; j < 16; j++)
|
|
{
|
|
BYTE y, u, v, tmp;
|
|
|
|
f.Read(&tmp, 1);
|
|
f.Read(&y, 1);
|
|
f.Read(&u, 1);
|
|
f.Read(&v, 1);
|
|
|
|
y = (y-16)*255/219;
|
|
|
|
pgc.pal[j].rgbRed = (BYTE)min(max(1.0*y + 1.4022*(u-128), 0), 255);
|
|
pgc.pal[j].rgbGreen = (BYTE)min(max(1.0*y - 0.3456*(u-128) - 0.7145*(v-128), 0), 255);
|
|
pgc.pal[j].rgbBlue = (BYTE)min(max(1.0*y + 1.7710*(v-128), 0) , 255);
|
|
}
|
|
|
|
//
|
|
|
|
WORD progoff, celladdroff, vobcelloff;
|
|
f.Seek(offset + 0xe6, CFile::begin);
|
|
ReadBEw(progoff);
|
|
f.Seek(offset + 0xe8, CFile::begin);
|
|
ReadBEw(celladdroff);
|
|
f.Seek(offset + 0xea, CFile::begin);
|
|
ReadBEw(vobcelloff);
|
|
|
|
//
|
|
|
|
CAtlArray<BYTE> progs;
|
|
progs.SetCount(nProgs);
|
|
f.Seek(offset + progoff, CFile::begin);
|
|
f.Read(progs.GetData(), nProgs);
|
|
|
|
//
|
|
|
|
pgc.angles[0].SetCount(nCells);
|
|
pgc.iSelAngle = 0;
|
|
|
|
//
|
|
|
|
f.Seek(offset + vobcelloff, CFile::begin);
|
|
for(int j = 0; j < nCells; j++)
|
|
{
|
|
ReadBEw(pgc.angles[0][j].vob);
|
|
ReadBEw(pgc.angles[0][j].cell);
|
|
}
|
|
|
|
//
|
|
|
|
DWORD tOffset = 0, tTotal = 0;
|
|
|
|
int iAngle = 0;
|
|
|
|
pgc.nAngles = 0;
|
|
|
|
f.Seek(offset + celladdroff, CFile::begin);
|
|
for(int j = 0; j < nCells; j++)
|
|
{
|
|
BYTE b;
|
|
ReadBEb(b);
|
|
switch(b>>6)
|
|
{
|
|
case 0: iAngle = 0; break; // normal
|
|
case 1: iAngle = 1; break; // first angle block
|
|
case 2: iAngle++; break; // middle angle block
|
|
case 3: iAngle++; break; // last angle block (no more should follow)
|
|
}
|
|
pgc.angles[0][j].iAngle = iAngle;
|
|
pgc.nAngles = max(pgc.nAngles, iAngle);
|
|
|
|
f.Seek(3, CFile::current);
|
|
ReadBEdw(pgc.angles[0][j].tTime);
|
|
ReadBEdw(pgc.angles[0][j].start);
|
|
f.Seek(8, CFile::current);
|
|
ReadBEdw(pgc.angles[0][j].end);
|
|
|
|
float fps;
|
|
switch((pgc.angles[0][j].tTime>>6)&0x3)
|
|
{
|
|
default:
|
|
case 3: fps = 30; break;
|
|
case 1: fps = 25; break;
|
|
}
|
|
|
|
int t = pgc.angles[0][j].tTime;
|
|
int hh = ((t>>28)&0xf)*10+((t>>24)&0xf);
|
|
int mm = ((t>>20)&0xf)*10+((t>>16)&0xf);
|
|
int ss = ((t>>12)&0xf)*10+((t>>8)&0xf);
|
|
int ms = (int)(1000.0 * (((t>>4)&0x3)*10+((t>>0)&0xf)) / fps);
|
|
pgc.angles[0][j].tTime = (DWORD)((((hh*60+mm)*60+ss)*1000+ms)*rate);
|
|
|
|
// time discontinuity
|
|
if(b&0x02) tOffset = tTotal;
|
|
pgc.angles[0][j].fDiscontinuity = !!(b&0x02);
|
|
|
|
pgc.angles[0][j].tTotal = tTotal;
|
|
pgc.angles[0][j].tOffset = tOffset;
|
|
|
|
tTotal += pgc.angles[0][j].tTime;
|
|
}
|
|
|
|
for(iAngle = 1; iAngle <= 9; iAngle++)
|
|
{
|
|
tOffset = tTotal = 0;
|
|
|
|
for(int j = 0, k = 0; j < nCells; j++)
|
|
{
|
|
if(pgc.angles[0][j].iAngle != 0
|
|
&& pgc.angles[0][j].iAngle != iAngle)
|
|
continue;
|
|
|
|
pgc.angles[iAngle].Add(pgc.angles[0][j]);
|
|
|
|
if(pgc.angles[iAngle][k].fDiscontinuity) tOffset = tTotal;
|
|
|
|
pgc.angles[iAngle][k].tTotal = tTotal;
|
|
pgc.angles[iAngle][k].tOffset = tOffset;
|
|
|
|
tTotal += pgc.angles[iAngle][k].tTime;
|
|
|
|
k++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Log(LOG_INFO, _T("Parsing ifo OK"));
|
|
|
|
return(true);
|
|
}
|
|
|
|
bool CVobSubFileRipper::LoadVob(CString fn)
|
|
{
|
|
Log(LOG_INFO, _T("Searching vobs..."));
|
|
/*
|
|
CAtlList<CString> m_vobs;
|
|
|
|
fn = fn.Left(fn.ReverseFind('.')+1);
|
|
fn.TrimRight(_T(".0123456789"));
|
|
for(int i = 0; i < 100; i++)
|
|
{
|
|
CString vob;
|
|
vob.Format(_T("%s%d.vob"), fn, i);
|
|
|
|
CFileStatus status;
|
|
if(!(CFileGetStatus(vob, status) && status.m_size))
|
|
{
|
|
if(i > 0) break;
|
|
else continue;
|
|
}
|
|
|
|
if(status.m_size&0x7ff)
|
|
{
|
|
Log(LOG_ERROR, _T("Length of %s is not n*2048!"), vob);
|
|
m_vobs.RemoveAll();
|
|
break;
|
|
}
|
|
|
|
CString str = _T("Found ") + vob;
|
|
|
|
if(i == 0)
|
|
{
|
|
str += _T(" (skipping, if not a menu vob rename it)");
|
|
}
|
|
else
|
|
{
|
|
m_vobs.AddTail(vob);
|
|
}
|
|
|
|
Log(LOG_INFO, str);
|
|
}
|
|
|
|
if(m_vobs.GetCount() <= 0)
|
|
{
|
|
Log(LOG_ERROR, _T("Nothing found! (%s*.vob)"), fn);
|
|
return(false);
|
|
}
|
|
*/
|
|
CAtlList<CString> vobs;
|
|
if(!m_vob.Open(fn, vobs/*m_vobs*/))
|
|
{
|
|
Log(LOG_ERROR, _T("Cannot open vob sequence"));
|
|
return(false);
|
|
}
|
|
|
|
if(vobs.GetCount() <= 0)
|
|
{
|
|
Log(LOG_ERROR, _T("Nothing found! (%s*.vob)"), fn);
|
|
return(false);
|
|
}
|
|
|
|
POSITION pos = vobs.GetHeadPosition();
|
|
while(pos) Log(LOG_INFO, _T("Found ") + vobs.GetNext(pos));
|
|
|
|
if(m_vob.IsDVD())
|
|
{
|
|
Log(LOG_INFO, _T("DVD detected..."));
|
|
|
|
BYTE key[5];
|
|
|
|
if(m_vob.HasDiscKey(key))
|
|
Log(LOG_INFO, _T("Disc key: %02x%02x%02x%02x%02x"), key[0], key[1], key[2], key[3], key[4]);
|
|
else
|
|
Log(LOG_WARNING, _T("Couldn't get the disc key"));
|
|
|
|
if(m_vob.HasTitleKey(key))
|
|
Log(LOG_INFO, _T("Title key: %02x%02x%02x%02x%02x"), key[0], key[1], key[2], key[3], key[4]);
|
|
else
|
|
Log(LOG_WARNING, _T("Couldn't get the title key"));
|
|
|
|
BYTE buff[2048];
|
|
|
|
m_vob.Seek(0);
|
|
if(!m_vob.Read(buff))
|
|
{
|
|
Log(LOG_ERROR, _T("Can't read vob, please unlock it with a software player!"));
|
|
return(false);
|
|
}
|
|
m_vob.Seek(0);
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
DWORD CVobSubFileRipper::ThreadProc()
|
|
{
|
|
SetThreadPriority(m_hThread, THREAD_PRIORITY_BELOW_NORMAL);
|
|
|
|
while(1)
|
|
{
|
|
DWORD cmd = GetRequest();
|
|
|
|
m_fThreadActive = true;
|
|
|
|
switch(cmd)
|
|
{
|
|
case CMD_EXIT:
|
|
Reply(S_OK);
|
|
return 0;
|
|
|
|
case CMD_INDEX:
|
|
Reply(S_OK);
|
|
{
|
|
m_fIndexing = true;
|
|
bool fSucceeded = Create();
|
|
m_fIndexing = false;
|
|
Finished(fSucceeded);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
Reply(E_FAIL);
|
|
return -1;
|
|
}
|
|
|
|
m_fBreakThread = false;
|
|
m_fThreadActive = false;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int SubPosSortProc(const void* e1, const void* e2)
|
|
{
|
|
return((int)(((CVobSubFile::SubPos*)e1)->start - ((CVobSubFile::SubPos*)e2)->start));
|
|
}
|
|
|
|
bool CVobSubFileRipper::Create()
|
|
{
|
|
CAutoLock cAutoLock(&m_csAccessLock);
|
|
|
|
if(m_rd.iSelPGC < 0 || m_rd.iSelPGC >= m_rd.pgcs.GetCount())
|
|
{
|
|
Log(LOG_ERROR, _T("Invalid program chain number (%d)!"), m_rd.iSelPGC);
|
|
return(false);
|
|
}
|
|
|
|
PGC& pgc = m_rd.pgcs[m_rd.iSelPGC];
|
|
|
|
if(pgc.iSelAngle < 0 || pgc.iSelAngle > 9 || pgc.angles[pgc.iSelAngle].GetCount() == 0)
|
|
{
|
|
Log(LOG_ERROR, _T("Invalid angle number (%d)!"), pgc.iSelAngle);
|
|
return(false);
|
|
}
|
|
|
|
CAtlArray<vc_t>& angle = pgc.angles[pgc.iSelAngle];
|
|
|
|
if(m_rd.selids.GetCount() == 0 && !m_rd.fClosedCaption)
|
|
{
|
|
Log(LOG_ERROR, _T("No valid stream set to be extacted!"));
|
|
return(false);
|
|
}
|
|
|
|
if(m_rd.selvcs.GetCount() == 0)
|
|
{
|
|
Log(LOG_ERROR, _T("No valid vob/cell id set to be extacted!"));
|
|
return(false);
|
|
}
|
|
|
|
Log(LOG_INFO, _T("Indexing..."));
|
|
|
|
// initalize CVobSubFile
|
|
CVobSubFile::Close();
|
|
InitSettings();
|
|
m_title = m_outfn;
|
|
m_size = m_rd.vidsize;
|
|
TrimExtension(m_title);
|
|
memcpy(m_orgpal, pgc.pal, sizeof(m_orgpal));
|
|
m_sub.SetLength(0);
|
|
|
|
CCDecoder ccdec(m_title + _T(".cc.srt"), m_title + _T(".cc.raw"));
|
|
|
|
CVobDec vd;
|
|
|
|
__int64 SCR, PTS, tOffset = 0, tPrevOffset = 0, tTotal = 0, tStart = 0;
|
|
int vob = 0, cell = 0;
|
|
bool fDiscontinuity = false, fDiscontinuityFixApplied = false, fNavpackFound = false;
|
|
|
|
int PTSframeoffset = 0, minPTSframeoffset = 0;
|
|
|
|
if(m_rd.fResetTime)
|
|
{
|
|
for(int i = 0; i < angle.GetCount() && ((angle[i].vob<<16)|angle[i].cell) != m_rd.selvcs[0]; i++)
|
|
tStart += angle[i].tTime;
|
|
|
|
Log(LOG_INFO, _T("Counting timestamps from %I64dms (v%02dc%02d)"),
|
|
tStart, m_rd.selvcs[0]>>16, m_rd.selvcs[0]&0xffff);
|
|
}
|
|
|
|
CAtlMap<DWORD, int> selvcmap;
|
|
selvcmap.RemoveAll();
|
|
for(int i = 0; i < m_rd.selvcs.GetCount(); i++)
|
|
selvcmap[m_rd.selvcs[i]] = 90000;
|
|
|
|
CAtlArray<vcchunk> chunks, foundchunks, loadedchunks;
|
|
|
|
if(m_vob.IsDVD())
|
|
{
|
|
Log(LOG_INFO, _T("Indexing mode: DVD"));
|
|
|
|
for(int i = 0; i < angle.GetCount(); i++)
|
|
{
|
|
DWORD vc = (angle[i].vob<<16)|angle[i].cell;
|
|
if(!selvcmap.Lookup(vc))
|
|
continue;
|
|
|
|
vcchunk c = {2048i64*angle[i].start, 2048i64*angle[i].end+2048, vc};
|
|
chunks.Add(c);
|
|
|
|
Log(LOG_INFO, _T("Adding: 0x%x - 0x%x (lba) for vob %d cell %d"),
|
|
angle[i].start, angle[i].end, angle[i].vob, angle[i].cell);
|
|
}
|
|
}
|
|
else if(LoadChunks(loadedchunks))
|
|
{
|
|
Log(LOG_INFO, _T("Indexing mode: File"));
|
|
|
|
for(int i = 0; i < loadedchunks.GetCount(); i++)
|
|
{
|
|
DWORD vcid = loadedchunks[i].vc;
|
|
if(!selvcmap.Lookup(vcid))
|
|
continue;
|
|
|
|
chunks.Add(loadedchunks[i]);
|
|
}
|
|
|
|
Log(LOG_INFO, _T(".chunk file loaded"));
|
|
}
|
|
else
|
|
{
|
|
Log(LOG_INFO, _T("Indexing mode: File"));
|
|
|
|
chunks.RemoveAll();
|
|
vcchunk c = {0, 2048i64*m_vob.GetLength(), 0};
|
|
chunks.Add(c);
|
|
}
|
|
|
|
__int64 sizedone = 0, sizetotal = 0;
|
|
for(int i = 0; i < chunks.GetCount(); i++)
|
|
sizetotal += chunks[i].end - chunks[i].start;
|
|
|
|
for(int i = 0; !m_fBreakThread && i < chunks.GetCount(); i++)
|
|
{
|
|
__int64 curpos = chunks[i].start, endpos = chunks[i].end;
|
|
|
|
vcchunk curchunk = {curpos, curpos, chunks[i].vc};
|
|
|
|
for(m_vob.Seek((int)(curpos/2048)); !m_fBreakThread && curpos < endpos; curpos += 2048, sizedone += 2048)
|
|
{
|
|
if(!(curpos&0x7ffff))
|
|
Progress(1.0 * sizedone / sizetotal);
|
|
|
|
static BYTE buff[2048];
|
|
|
|
if(!m_vob.Read(buff))
|
|
{
|
|
Log(LOG_ERROR, _T("Cannot read, either locked dvd or truncated/missing files!"));
|
|
return(false);
|
|
}
|
|
|
|
curchunk.end = curpos;
|
|
|
|
if(buff[0x14] & 0x30)
|
|
{
|
|
if(!vd.m_fFoundKey)
|
|
{
|
|
Log(LOG_INFO, _T("Encrypted sector found, searching key..."));
|
|
|
|
__int64 savepos = curpos;
|
|
|
|
m_vob.Seek(0);
|
|
for(__int64 pos = 0; !m_fBreakThread && pos < endpos; pos += 2048)
|
|
{
|
|
if(!m_vob.Read(buff))
|
|
{
|
|
Log(LOG_ERROR, _T("Cannot read, either locked dvd or truncated/missing files!"));
|
|
return(false);
|
|
}
|
|
|
|
if(vd.FindKey(buff))
|
|
break;
|
|
}
|
|
|
|
if(m_fBreakThread)
|
|
break;
|
|
|
|
if(!vd.m_fFoundKey)
|
|
{
|
|
Log(LOG_ERROR, _T("Key not found, can't decrypt!"));
|
|
return(false);
|
|
}
|
|
|
|
Log(LOG_INFO, _T("Key found, continuing extraction..."));
|
|
|
|
m_vob.Seek((int)((curpos = savepos)/2048));
|
|
m_vob.Read(buff);
|
|
}
|
|
|
|
vd.Decrypt(buff);
|
|
}
|
|
|
|
if(*((DWORD*)&buff[0]) != 0xba010000)
|
|
{
|
|
Log(LOG_WARNING, _T("Bad sector header at block %08d!"), (int)(curpos/2048));
|
|
|
|
if(AfxMessageBox(_T("Bad packet header found, do you want to continue?"), MB_YESNO) == IDNO)
|
|
{
|
|
Log(LOG_ERROR, _T("Terminated!"));
|
|
return(false);
|
|
}
|
|
}
|
|
|
|
SCR = (__int64(buff[0x04] & 0x38) >> 3) << 30
|
|
| __int64(buff[0x04] & 0x03) << 28
|
|
| __int64(buff[0x05]) << 20
|
|
| (__int64(buff[0x06] & 0xf8) >> 3) << 15
|
|
| __int64(buff[0x06] & 0x03) << 13
|
|
| __int64(buff[0x07]) << 5
|
|
| (__int64(buff[0x08] & 0xf8) >> 3) << 0;
|
|
|
|
bool hasPTS = false;
|
|
|
|
if((*(DWORD*)&buff[0x0e] == 0xe0010000 || *(DWORD*)&buff[0x0e] == 0xbd010000)
|
|
&& buff[0x15] & 0x80)
|
|
{
|
|
PTS = (__int64)(buff[0x17] & 0x0e) << 29 // 32-30 (+marker)
|
|
| ((__int64)(buff[0x18]) << 22) // 29-22
|
|
| ((__int64)(buff[0x19] & 0xfe) << 14) // 21-15 (+marker)
|
|
| ((__int64)(buff[0x1a]) << 7) // 14-07
|
|
| ((__int64)(buff[0x1b]) >> 1); // 06-00 (+marker)
|
|
|
|
hasPTS = true;
|
|
}
|
|
|
|
if(*((DWORD*)&buff[0x0e]) == 0xbb010000)
|
|
{
|
|
fNavpackFound = true;
|
|
|
|
if(vob == buff[0x420] && cell == buff[0x422])
|
|
continue;
|
|
|
|
vob = buff[0x420];
|
|
cell = buff[0x422];
|
|
|
|
tOffset = tTotal = 0;
|
|
|
|
for(int i = 0; i < angle.GetCount(); i++)
|
|
{
|
|
if(angle[i].vob == vob && angle[i].cell == cell)
|
|
{
|
|
tPrevOffset = tOffset;
|
|
tOffset = (__int64)angle[i].tOffset;
|
|
tTotal = (__int64)angle[i].tTotal;
|
|
fDiscontinuity = angle[i].fDiscontinuity;
|
|
fDiscontinuityFixApplied = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(curchunk.vc != ((vob<<16)|cell))
|
|
{
|
|
if(curchunk.vc != 0) foundchunks.Add(curchunk);
|
|
curchunk.start = curchunk.end = curpos;
|
|
curchunk.vc = (vob<<16)|cell;
|
|
}
|
|
|
|
CString str, str2;
|
|
str.Format(_T("v%02d c%02d lba%08d"), vob, cell, (int)(curpos/2048));
|
|
UINT vcid = (vob<<16)|cell;
|
|
if(!selvcmap.Lookup(vcid, minPTSframeoffset)) str2 = _T(", skipping");
|
|
else str2.Format(_T(", total=%I64dms, off=%I64dms, corr=%I64dms, discont.:%d"),
|
|
tTotal, tOffset, -tStart, (int)fDiscontinuity);
|
|
Log(LOG_INFO, str + str2);
|
|
}
|
|
|
|
DWORD vcid = (vob<<16)|cell;
|
|
if(!selvcmap.Lookup(vcid, minPTSframeoffset))
|
|
continue;
|
|
|
|
if(hasPTS && fDiscontinuity && !fDiscontinuityFixApplied)
|
|
{
|
|
__int64 tDiff = tOffset - tPrevOffset;
|
|
if(tDiff > 0 && tDiff < (PTS/90+1000))
|
|
{
|
|
CString str;
|
|
str.Format(_T("False discontinuity detected, correcting time by %I64dms"), -tDiff);
|
|
Log(LOG_INFO, str);
|
|
|
|
tStart += tDiff;
|
|
}
|
|
fDiscontinuityFixApplied = true;
|
|
}
|
|
|
|
if(*(DWORD*)&buff[0x0e] == 0xe0010000)
|
|
{
|
|
if(fDiscontinuity)
|
|
{
|
|
if(PTS < minPTSframeoffset)
|
|
{
|
|
selvcmap[vcid] = PTSframeoffset = PTS;
|
|
}
|
|
|
|
fDiscontinuity = false;
|
|
}
|
|
|
|
if(m_rd.fClosedCaption)
|
|
ccdec.ExtractCC(buff, 2048, tOffset + ((PTS - PTSframeoffset) / 90) - tStart);
|
|
}
|
|
else if(*(DWORD*)&buff[0x0e] == 0xbd010000)
|
|
{
|
|
BYTE id = buff[0x17 + buff[0x16]], iLang = id&0x1f;
|
|
|
|
if((id & 0xe0) == 0x20 && m_rd.selids.Lookup(iLang))
|
|
{
|
|
if(hasPTS)
|
|
{
|
|
SubPos sb;
|
|
sb.filepos = m_sub.GetPosition();
|
|
sb.start = tOffset + ((PTS - PTSframeoffset) / 90) - tStart;
|
|
sb.vobid = (char)vob;
|
|
sb.cellid = (char)cell;
|
|
sb.celltimestamp = tTotal;
|
|
sb.fValid = true;
|
|
m_langs[iLang].subpos.Add(sb);
|
|
}
|
|
|
|
m_sub.Write(buff, 2048);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(curchunk.vc != ((vob<<16)|cell))
|
|
{
|
|
if(curchunk.vc != 0) foundchunks.Add(curchunk);
|
|
curchunk.start = curchunk.end = curpos;
|
|
curchunk.vc = (vob<<16)|cell;
|
|
}
|
|
}
|
|
|
|
if(sizedone < sizetotal)
|
|
{
|
|
Log(LOG_ERROR, _T("Indexing terminated before reaching the end!"));
|
|
Progress(0);
|
|
return(false);
|
|
}
|
|
|
|
if(!fNavpackFound)
|
|
{
|
|
Log(LOG_ERROR, _T("Could not find any system header start code! (0x000001bb)"));
|
|
if(!m_vob.IsDVD()) Log(LOG_ERROR, _T("Make sure the ripper doesn't strip these packets."));
|
|
Progress(0);
|
|
return(false);
|
|
}
|
|
|
|
Log(LOG_INFO, _T("Indexing finished"));
|
|
Progress(1);
|
|
|
|
for(int i = 0; i < 32; i++)
|
|
{
|
|
if(m_iLang == -1 && m_langs[i].subpos.GetCount() > 0) m_iLang = i;
|
|
m_langs[i].id = pgc.ids[i];
|
|
m_langs[i].name = m_langs[i].alt = FindLangFromId(m_langs[i].id);
|
|
|
|
CAtlArray<SubPos>& sp = m_langs[i].subpos;
|
|
qsort(sp.GetData(), sp.GetCount(), sizeof(SubPos), SubPosSortProc);
|
|
|
|
if(m_rd.fForcedOnly)
|
|
{
|
|
Log(LOG_INFO, _T("Searching for forced subs..."));
|
|
Progress(0);
|
|
|
|
for(int j = 0, len = sp.GetCount(); j < len; j++)
|
|
{
|
|
Progress(1.0 * j / len);
|
|
|
|
sp[j].fValid = false;
|
|
int packetsize = 0, datasize = 0;
|
|
if(BYTE* buff = GetPacket(j, packetsize, datasize, i))
|
|
{
|
|
m_img.GetPacketInfo(buff, packetsize, datasize);
|
|
sp[j].fValid = m_img.fForced;
|
|
delete [] buff;
|
|
}
|
|
}
|
|
|
|
Progress(1);
|
|
}
|
|
}
|
|
|
|
Log(LOG_INFO, _T("Saving files..."));
|
|
|
|
if(m_iLang != -1)
|
|
{
|
|
if(!Save(m_title))
|
|
{
|
|
Log(LOG_ERROR, _T("Could not save output files!"));
|
|
return(false);
|
|
}
|
|
}
|
|
|
|
Log(LOG_INFO, _T("Subtitles saved"));
|
|
|
|
if(!m_vob.IsDVD() && loadedchunks.GetCount() == 0)
|
|
{
|
|
if(SaveChunks(foundchunks))
|
|
{
|
|
Log(LOG_INFO, _T(".chunk file saved"));
|
|
}
|
|
}
|
|
|
|
Log(LOG_INFO, _T("Done!"));
|
|
|
|
return(true);
|
|
}
|
|
|
|
static const DWORD s_version = 1;
|
|
|
|
bool CVobSubFileRipper::LoadChunks(CAtlArray<vcchunk>& chunks)
|
|
{
|
|
CFile f;
|
|
|
|
CString fn = m_infn;
|
|
TrimExtension(fn);
|
|
fn += _T(".chunks");
|
|
|
|
DWORD chksum = 0, chunklen, version;
|
|
__int64 voblen;
|
|
|
|
if(!f.Open(fn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyWrite))
|
|
return(false);
|
|
f.Read(&version, sizeof(version));
|
|
if(version == 1)
|
|
{
|
|
f.Read(&chksum, sizeof(chksum));
|
|
f.Read(&voblen, sizeof(voblen));
|
|
f.Read(&chunklen, sizeof(chunklen));
|
|
chunks.SetCount(chunklen);
|
|
f.Read(chunks.GetData(), sizeof(vcchunk)*chunks.GetCount());
|
|
}
|
|
f.Close();
|
|
|
|
if(voblen != m_vob.GetLength())
|
|
{
|
|
chunks.RemoveAll();
|
|
return(false);
|
|
}
|
|
|
|
if(!f.Open(m_infn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyWrite))
|
|
return(false);
|
|
DWORD dw, chksum2 = 0;
|
|
while(f.Read(&dw, sizeof(dw)) == sizeof(dw)) chksum2 += dw;
|
|
f.Close();
|
|
|
|
if(chksum != chksum2)
|
|
{
|
|
chunks.RemoveAll();
|
|
return(false);
|
|
}
|
|
|
|
return(true);
|
|
}
|
|
|
|
bool CVobSubFileRipper::SaveChunks(CAtlArray<vcchunk>& chunks)
|
|
{
|
|
CFile f;
|
|
|
|
CString fn = m_infn;
|
|
TrimExtension(fn);
|
|
fn += _T(".chunks");
|
|
|
|
DWORD chksum = 0, chunklen = chunks.GetCount();
|
|
__int64 voblen = m_vob.GetLength();
|
|
|
|
if(!f.Open(m_infn, CFile::modeRead|CFile::typeBinary|CFile::shareDenyWrite))
|
|
return(false);
|
|
DWORD dw;
|
|
while(f.Read(&dw, sizeof(dw)) == sizeof(dw)) chksum += dw;
|
|
f.Close();
|
|
|
|
if(!f.Open(fn, CFile::modeCreate|CFile::modeWrite|CFile::typeBinary|CFile::shareDenyWrite))
|
|
return(false);
|
|
f.Write(&s_version, sizeof(s_version));
|
|
f.Write(&chksum, sizeof(chksum));
|
|
f.Write(&voblen, sizeof(voblen));
|
|
f.Write(&chunklen, sizeof(chunklen));
|
|
f.Write(chunks.GetData(), sizeof(vcchunk)*chunklen);
|
|
f.Close();
|
|
|
|
return(true);
|
|
}
|
|
|
|
// IVSFRipper
|
|
|
|
STDMETHODIMP CVobSubFileRipper::SetCallBack(IVSFRipperCallback* pCallback)
|
|
{
|
|
CAutoLock cAutoLock(&m_csCallback);
|
|
m_pCallback = pCallback;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CVobSubFileRipper::LoadParamFile(CString fn)
|
|
{
|
|
CAutoLock cAutoLock(&m_csAccessLock);
|
|
|
|
m_rd.Reset();
|
|
|
|
CStdioFile f;
|
|
if(!f.Open(fn, CFile::modeRead|CFile::typeText))
|
|
return E_FAIL;
|
|
|
|
TCHAR langid[256];
|
|
|
|
enum {P_INPUT, P_OUTPUT, P_PGC, P_ANGLE, P_LANGS, P_OPTIONS};
|
|
int phase = P_INPUT;
|
|
|
|
CString line;
|
|
while(f.ReadString(line))
|
|
{
|
|
if(line.Trim().IsEmpty() || line[0] == '#') continue;
|
|
|
|
if(phase == P_INPUT)
|
|
{
|
|
if(S_OK != SetInput(line)) break;
|
|
phase = P_OUTPUT;
|
|
}
|
|
else if(phase == P_OUTPUT)
|
|
{
|
|
if(S_OK != SetOutput(line)) break;
|
|
phase = P_PGC;
|
|
}
|
|
else if(phase == P_PGC)
|
|
{
|
|
m_rd.iSelPGC = _tcstol(line, NULL, 10)-1;
|
|
if(m_rd.iSelPGC < 0 || m_rd.iSelPGC >= m_rd.pgcs.GetCount()) break;
|
|
phase = P_ANGLE;
|
|
}
|
|
else if(phase == 3)
|
|
{
|
|
PGC& pgc = m_rd.pgcs[m_rd.iSelPGC];
|
|
|
|
pgc.iSelAngle = _tcstol(line, NULL, 10);
|
|
if(pgc.iSelAngle < 0 || pgc.iSelAngle > max(1, pgc.nAngles) || pgc.iSelAngle > 9) break;
|
|
|
|
CAtlArray<vc_t>& angle = pgc.angles[pgc.iSelAngle];
|
|
|
|
if(line.Find('v') >= 0)
|
|
{
|
|
int vob = 0, cell = 0;
|
|
|
|
line += ' ';
|
|
|
|
TCHAR* s = (LPTSTR)(LPCTSTR)line;
|
|
TCHAR* e = s + line.GetLength();
|
|
while(s < e)
|
|
{
|
|
if(*s == 'v' || s == e-1)
|
|
{
|
|
s++;
|
|
if(vob != 0 && cell == 0)
|
|
{
|
|
for(int i = 0; i < angle.GetCount(); i++)
|
|
{
|
|
if(angle[i].vob == vob)
|
|
m_rd.selvcs.Add((angle[i].vob<<16)|angle[i].cell);
|
|
}
|
|
}
|
|
|
|
vob = _tcstol(s, &s, 10);
|
|
cell = 0;
|
|
}
|
|
else if(*s == 'c' && vob > 0)
|
|
{
|
|
s++;
|
|
cell = _tcstol(s, &s, 10);
|
|
|
|
for(int i = 0; i < angle.GetCount(); i++)
|
|
{
|
|
if(angle[i].vob == vob && angle[i].cell == cell)
|
|
{
|
|
m_rd.selvcs.Add((vob<<16)|cell);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
s++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(int i = 0; i < angle.GetCount(); i++)
|
|
m_rd.selvcs.Add((angle[i].vob<<16)|angle[i].cell);
|
|
}
|
|
|
|
phase = P_LANGS;
|
|
}
|
|
else if(phase == 4)
|
|
{
|
|
if(!line.CompareNoCase(_T("ALL")))
|
|
{
|
|
for(int i = 0; i < 32; i++) m_rd.selids[i] = true;
|
|
m_rd.fClosedCaption = true;
|
|
phase = P_OPTIONS;
|
|
}
|
|
else
|
|
{
|
|
line += ' ';
|
|
|
|
while(line.GetLength() > 0)
|
|
{
|
|
int n = line.Find(_T(" "));
|
|
|
|
CString lang = line.Left(n);
|
|
|
|
line = line.Mid(n);
|
|
line.TrimLeft();
|
|
|
|
n = 0;
|
|
|
|
int langnum;
|
|
|
|
if(_istdigit(lang[0]))
|
|
{
|
|
n = _stscanf(lang, _T("%d"), &langnum);
|
|
if(n != 1) break;
|
|
|
|
m_rd.selids[langnum] = true;
|
|
}
|
|
else if(_istalpha(lang[0]))
|
|
{
|
|
n = _stscanf(lang, _T("%s"), langid);
|
|
if(n != 1) break;
|
|
|
|
int id = (langid[0] << 8) + langid[1];
|
|
|
|
if(id == 'cc')
|
|
{
|
|
m_rd.fClosedCaption = true;
|
|
}
|
|
else
|
|
{
|
|
m_rd.selids[id] = true;
|
|
}
|
|
}
|
|
else break;
|
|
|
|
if(n != 1) break;
|
|
}
|
|
|
|
if((m_rd.selids.GetCount() > 0 || m_rd.fClosedCaption) && line.IsEmpty())
|
|
phase = P_OPTIONS;
|
|
}
|
|
}
|
|
else if(phase == 5 && !line.CompareNoCase(_T("CLOSE")))
|
|
m_rd.fClose = true;
|
|
else if(phase == 5 && !line.CompareNoCase(_T("BEEP")))
|
|
m_rd.fBeep = true;
|
|
else if(phase == 5 && !line.CompareNoCase(_T("RESETTIME")))
|
|
m_rd.fResetTime = true;
|
|
else if(phase == 5 && !line.CompareNoCase(_T("FORCEDONLY")))
|
|
m_rd.fForcedOnly = true;
|
|
else if(phase == 5 && !line.CompareNoCase(_T("CLOSEIGNOREERRORS")))
|
|
m_rd.fCloseIgnoreError = true;
|
|
|
|
}
|
|
|
|
m_rd.fAuto = true;
|
|
|
|
return phase == P_OPTIONS ? S_OK : E_FAIL;
|
|
}
|
|
|
|
STDMETHODIMP CVobSubFileRipper::SetInput(CString infn)
|
|
{
|
|
CAutoLock cAutoLock(&m_csAccessLock);
|
|
|
|
m_rd.Reset();
|
|
|
|
if(!LoadIfo(infn) || !LoadVob(infn))
|
|
return E_INVALIDARG;
|
|
|
|
m_infn = infn;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CVobSubFileRipper::SetOutput(CString outfn)
|
|
{
|
|
CAutoLock cAutoLock(&m_csAccessLock);
|
|
m_outfn = outfn;
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CVobSubFileRipper::GetRipperData(VSFRipperData& rd)
|
|
{
|
|
CAutoLock cAutoLock(&m_csAccessLock);
|
|
rd.Copy(m_rd);
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CVobSubFileRipper::UpdateRipperData(VSFRipperData& rd)
|
|
{
|
|
CAutoLock cAutoLock(&m_csAccessLock);
|
|
m_rd.Copy(rd);
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CVobSubFileRipper::Index()
|
|
{
|
|
if(m_fIndexing) return E_FAIL;
|
|
CAMThread::CallWorker(CMD_INDEX);
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CVobSubFileRipper::IsIndexing()
|
|
{
|
|
return m_fIndexing ? S_OK : S_FALSE;
|
|
}
|
|
|
|
STDMETHODIMP CVobSubFileRipper::Abort(bool fSavePartial)
|
|
{
|
|
m_fBreakThread = true;
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
|
|
void VSFRipperData::Reset()
|
|
{
|
|
vidsize.SetSize(0,0);
|
|
memset(&vidinfo, 0, sizeof(vidinfo));
|
|
pgcs.RemoveAll();
|
|
iSelPGC = -1;
|
|
fResetTime = fClosedCaption = true;
|
|
fForcedOnly = false;
|
|
fClose = fBeep = fAuto = false;
|
|
fCloseIgnoreError = false;
|
|
|
|
selvcs.RemoveAll();
|
|
selids.RemoveAll();
|
|
}
|
|
|
|
void VSFRipperData::Copy(VSFRipperData& rd)
|
|
{
|
|
Reset();
|
|
|
|
vidsize = rd.vidsize;
|
|
vidinfo = rd.vidinfo;
|
|
if(int len = rd.pgcs.GetCount())
|
|
{
|
|
pgcs.SetCount(len);
|
|
for(int i = 0; i < len; i++)
|
|
{
|
|
PGC& src = rd.pgcs[i];
|
|
PGC& dst = pgcs[i];
|
|
dst.nAngles = src.nAngles;
|
|
for(int i = 0; i < countof(dst.angles); i++)
|
|
dst.angles[i].Copy(src.angles[i]);
|
|
dst.iSelAngle = src.iSelAngle;
|
|
memcpy(dst.pal, src.pal, sizeof(src.pal));
|
|
memcpy(dst.ids, src.ids, sizeof(src.ids));
|
|
}
|
|
}
|
|
iSelPGC = rd.iSelPGC;
|
|
fResetTime = rd.fResetTime;
|
|
fClosedCaption = rd.fClosedCaption;
|
|
fForcedOnly = rd.fForcedOnly;
|
|
fClose = rd.fClose;
|
|
fBeep = rd.fBeep;
|
|
fAuto = rd.fAuto;
|
|
fCloseIgnoreError = rd.fCloseIgnoreError;
|
|
selvcs.Copy(rd.selvcs);
|
|
POSITION pos = rd.selids.GetStartPosition();
|
|
while(pos)
|
|
{
|
|
BYTE key;
|
|
bool val;
|
|
rd.selids.GetNextAssoc(pos, key, val);
|
|
selids[key] = val;
|
|
}
|
|
}
|
|
|