OSDN Git Service

Shut up some warnings when detecting VapourSynth registry path.
[x264-launcher/x264-launcher.git] / src / thread_avisynth.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2016 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 //
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include "thread_avisynth.h"
23
24 //Qt
25 #include <QLibrary>
26 #include <QEventLoop>
27 #include <QTimer>
28 #include <QMutexLocker>
29 #include <QApplication>
30 #include <QProcess>
31 #include <QDir>
32
33 //Internal
34 #include "global.h"
35 #include "model_sysinfo.h"
36
37 //MUtils
38 #include <MUtils/Global.h>
39 #include <MUtils/OSSupport.h>
40
41 //Const
42 static const bool ENABLE_PORTABLE_AVS = true;
43
44 //Static
45 QMutex AvisynthCheckThread::m_avsLock;
46 QScopedPointer<QFile> AvisynthCheckThread::m_avsDllPath[2];
47
48 //Helper
49 #define VALID_DIR(STR) ((!(STR).isEmpty()) && QDir((STR)).exists())
50 #define BOOLIFY(X) ((X) ? '1' : '0')
51
52 //Utility function
53 QString AVS_CHECK_BINARY(const SysinfoModel *sysinfo, const bool& x64)
54 {
55         return QString("%1/toolset/%2/avs_check_%2.exe").arg(sysinfo->getAppPath(), (x64 ? "x64": "x86"));
56 }
57
58 class Wow64RedirectionDisabler
59 {
60 public:
61         Wow64RedirectionDisabler(void)
62         {
63                 m_oldValue = NULL;
64                 m_disabled = MUtils::OS::wow64fsredir_disable(m_oldValue);
65         }
66         ~Wow64RedirectionDisabler(void)
67         {
68                 if(m_disabled)
69                 {
70                         if(!MUtils::OS::wow64fsredir_revert(m_oldValue))
71                         {
72                                 qWarning("Failed to renable WOW64 filesystem redirection!");
73                         }
74                 }
75         }
76 private:
77         bool  m_disabled;
78         void* m_oldValue;
79 };
80
81 //-------------------------------------
82 // External API
83 //-------------------------------------
84
85 bool AvisynthCheckThread::detect(SysinfoModel *sysinfo)
86 {
87         sysinfo->clearAvisynth();
88         double version = 0.0;
89         QMutexLocker lock(&m_avsLock);
90
91         QEventLoop loop;
92         AvisynthCheckThread thread(sysinfo);
93
94         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
95
96         connect(&thread, SIGNAL(finished()), &loop, SLOT(quit()));
97         connect(&thread, SIGNAL(terminated()), &loop, SLOT(quit()));
98         
99         thread.start();
100         QTimer::singleShot(15000, &loop, SLOT(quit()));
101         
102         qDebug("Avisynth thread has been created, please wait...");
103         loop.exec(QEventLoop::ExcludeUserInputEvents);
104         qDebug("Avisynth thread finished.");
105
106         QApplication::restoreOverrideCursor();
107
108         if(!thread.wait(1000))
109         {
110                 qWarning("Avisynth thread encountered timeout -> probably deadlock!");
111                 thread.terminate();
112                 thread.wait();
113                 return false;
114         }
115
116         if(thread.getException())
117         {
118                 qWarning("Avisynth thread encountered an exception !!!");
119                 return false;
120         }
121         
122         if(thread.getSuccess())
123         {
124                 sysinfo->setAvisynth(SysinfoModel::Avisynth_X86, thread.getSuccess() & AVISYNTH_X86);
125                 sysinfo->setAvisynth(SysinfoModel::Avisynth_X64, thread.getSuccess() & AVISYNTH_X64);
126                 sysinfo->setAVSPath(thread.getPath());
127                 qDebug("Avisynth support is officially enabled now! [x86=%c, x64=%c]", BOOLIFY(sysinfo->getAvisynth(SysinfoModel::Avisynth_X86)), BOOLIFY(sysinfo->getAvisynth(SysinfoModel::Avisynth_X64)));
128         }
129         else
130         {
131                 qWarning("Avisynth could not be found -> Avisynth support disabled!");
132         }
133
134         return true;
135 }
136
137 //-------------------------------------
138 // Thread class
139 //-------------------------------------
140
141 AvisynthCheckThread::AvisynthCheckThread(const SysinfoModel *const sysinfo)
142 :
143         m_sysinfo(sysinfo)
144 {
145         m_success = false;
146         m_exception = false;
147 }
148
149 AvisynthCheckThread::~AvisynthCheckThread(void)
150 {
151 }
152
153 void AvisynthCheckThread::run(void)
154 {
155         m_exception = false;
156         m_success &= 0;
157         m_basePath.clear();
158
159         detectAvisynthVersion1(m_success, m_basePath, m_sysinfo, &m_exception);
160 }
161
162 void AvisynthCheckThread::detectAvisynthVersion1(int &success, QString &basePath, const SysinfoModel *const sysinfo, volatile bool *exception)
163 {
164         __try
165         {
166                 detectAvisynthVersion2(success, basePath, sysinfo, exception);
167         }
168         __except(1)
169         {
170                 *exception = true;
171                 qWarning("Unhandled exception error in Avisynth thread !!!");
172         }
173 }
174
175 void AvisynthCheckThread::detectAvisynthVersion2(int &success, QString &basePath, const SysinfoModel *const sysinfo, volatile bool *exception)
176 {
177         try
178         {
179                 return detectAvisynthVersion3(success, basePath, sysinfo);
180         }
181         catch(...)
182         {
183                 *exception = true;
184                 qWarning("Avisynth initializdation raised an C++ exception!");
185         }
186 }
187
188 void AvisynthCheckThread::detectAvisynthVersion3(int &success, QString &basePath, const SysinfoModel *const sysinfo)
189 {
190         success &= 0;
191
192         QFile *avsPath32;
193         if(checkAvisynth(basePath, sysinfo, avsPath32, false))
194         {
195                 m_avsDllPath[0].reset(avsPath32);
196                 success |= AVISYNTH_X86;
197                 qDebug("Avisynth 32-Bit edition found!");
198         }
199         else
200         {
201                 qDebug("Avisynth 32-Bit edition *not* found!");
202         }
203
204         if(sysinfo->getCPUFeatures(SysinfoModel::CPUFeatures_X64))
205         {
206                 QFile *avsPath64;
207                 if(checkAvisynth(basePath, sysinfo, avsPath64, true))
208                 {
209                         m_avsDllPath[1].reset(avsPath64);
210                         success |= AVISYNTH_X64;
211                         qDebug("Avisynth 64-Bit edition found!");
212                 }
213                 else
214                 {
215                         qDebug("Avisynth 64-Bit edition *not* found!");
216                 }
217         }
218         else
219         {
220                 qWarning("Skipping 64-Bit Avisynth check on non-x64 system!");
221         }
222 }
223
224 bool AvisynthCheckThread::checkAvisynth(QString &basePath, const SysinfoModel *const sysinfo, QFile *&path, const bool &x64)
225 {
226         qDebug("Avisynth %s-Bit support is being tested.", x64 ? "64" : "32");
227
228         QProcess process;
229         QStringList output;
230
231         //Look for "portable" Avisynth version
232         static const char *const ARCH_DIR[] = { "x64", "x86" };
233         const QLatin1String archSuffix = QLatin1String(ARCH_DIR[x64 ? 1 : 0]);
234         if (ENABLE_PORTABLE_AVS)
235         {
236                 const QString avsPortableDir = QString("%1/extra/Avisynth").arg(QCoreApplication::applicationDirPath());
237                 if (VALID_DIR(avsPortableDir))
238                 {
239                         QFileInfo avsDllFile(QString("%1/%2/avisynth.dll").arg(avsPortableDir, archSuffix)), devilDllFile(QString("%1/%2/devil.dll").arg(avsPortableDir, archSuffix));
240                         if (avsDllFile.exists() && devilDllFile.exists() && avsDllFile.isFile() && devilDllFile.isFile())
241                         {
242                                 qWarning("Adding portable Avisynth to PATH environment variable: %s", MUTILS_UTF8(avsPortableDir));
243                                 basePath = avsPortableDir;
244                         }
245                 }
246         }
247
248         //Setup process object
249         MUtils::init_process(process, QDir::tempPath(), true, basePath.isEmpty() ? NULL : &(QStringList() << QString("%1/%2").arg(basePath, archSuffix)));
250
251         //Try to start VSPIPE.EXE
252         process.start(AVS_CHECK_BINARY(sysinfo, x64), QStringList());
253         if(!process.waitForStarted())
254         {
255                 qWarning("Failed to launch AVS_CHECK.EXE -> %s", process.errorString().toUtf8().constData());
256                 return false;
257         }
258
259         //Wait for process to finish
260         while(process.state() != QProcess::NotRunning)
261         {
262                 if(process.waitForReadyRead(12000))
263                 {
264                         while(process.canReadLine())
265                         {
266                                 output << QString::fromUtf8(process.readLine()).simplified();
267                         }
268                         continue;
269                 }
270                 if(process.state() != QProcess::NotRunning)
271                 {
272                         qWarning("AVS_CHECK.EXE process encountered a deadlock -> aborting now!");
273                         break;
274                 }
275         }
276
277         //Make sure VSPIPE.EXE has terminated!
278         process.waitForFinished(2500);
279         if(process.state() != QProcess::NotRunning)
280         {
281                 qWarning("AVS_CHECK.EXE process still running, going to kill it!");
282                 process.kill();
283                 process.waitForFinished(-1);
284         }
285
286         //Read pending lines
287         while(process.canReadLine())
288         {
289                 output << QString::fromUtf8(process.readLine()).simplified();
290         }
291
292         //Check exit code
293         if(process.exitCode() != 0)
294         {
295                 qWarning("AVS_CHECK.EXE failed with code 0x%08X -> disable Avisynth support!", process.exitCode());
296                 return false;
297         }
298
299         //Init regular expressions
300         QRegExp avsLogo("Avisynth\\s+Checker\\s+(x86|x64)");
301         QRegExp avsPath("Avisynth_DLLPath=(.+)");
302         QRegExp avsVers("Avisynth_Version=(\\d+)\\.(\\d+)");
303         
304         //Check for version info
305         bool avisynthLogo = false;
306         quint32 avisynthVersion[2] = { 0, 0 };
307         QString avisynthPath;
308         for(QStringList::ConstIterator iter = output.constBegin(); iter != output.constEnd(); iter++)
309         {
310                 if(avisynthLogo)
311                 {
312                         if(avsPath.indexIn(*iter) >= 0)
313                         {
314                                 avisynthPath =  avsPath.cap(1).trimmed();
315                         }
316                         else if(avsVers.indexIn(*iter) >= 0)
317                         {
318                                 quint32 temp[2];
319                                 if(MUtils::regexp_parse_uint32(avsVers, temp, 2))
320                                 {
321                                         avisynthVersion[0] = temp[0];
322                                         avisynthVersion[1] = temp[1];
323                                 }
324                         }
325                 }
326                 else
327                 {
328                         if(avsLogo.lastIndexIn(*iter) >= 0)
329                         {
330                                 avisynthLogo = true;
331                         }
332                 }
333         }
334         
335         //Minimum required version found?
336         if((avisynthVersion[0] >= 2) && (avisynthVersion[1] >= 50) && (!avisynthPath.isEmpty()))
337         {
338                 Wow64RedirectionDisabler disableWow64Redir;
339                 path = new QFile(avisynthPath);
340                 if(!path->open(QIODevice::ReadOnly))
341                 {
342                         MUTILS_DELETE(path);
343                 }
344                 qDebug("Avisynth was detected successfully (current version: %u.%02u).", avisynthVersion[0], avisynthVersion[1]);
345                 qDebug("Avisynth DLL path: %s", MUTILS_UTF8(avisynthPath));
346                 return true;
347         }
348         
349         //Failed to determine version
350         qWarning("Failed to determine Avisynth version!");
351         return false;
352 }