OSDN Git Service

b421e445294e3195a096f28081eafda61272b48b
[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 #include <QLibrary>
25 #include <QEventLoop>
26 #include <QTimer>
27 #include <QMutexLocker>
28 #include <QApplication>
29 #include <QProcess>
30 #include <QDir>
31
32 //Internal
33 #include "global.h"
34 #include "model_sysinfo.h"
35
36 //MUtils
37 #include <MUtils/Global.h>
38 #include <MUtils/OSSupport.h>
39
40 //Static
41 QMutex AvisynthCheckThread::m_avsLock;
42 QScopedPointer<QFile> AvisynthCheckThread::m_avsDllPath[2];
43
44 //Helper
45 #define BOOLIFY(X) ((X) ? '1' : '0')
46
47 //Utility function
48 QString AVS_CHECK_BINARY(const SysinfoModel *sysinfo, const bool& x64)
49 {
50         return QString("%1/toolset/%2/avs_check_%2.exe").arg(sysinfo->getAppPath(), (x64 ? "x64": "x86"));
51 }
52
53 class Wow64RedirectionDisabler
54 {
55 public:
56         Wow64RedirectionDisabler(void)
57         {
58                 m_oldValue = NULL;
59                 m_disabled = MUtils::OS::wow64fsredir_disable(m_oldValue);
60         }
61         ~Wow64RedirectionDisabler(void)
62         {
63                 if(m_disabled)
64                 {
65                         if(!MUtils::OS::wow64fsredir_revert(m_oldValue))
66                         {
67                                 qWarning("Failed to renable WOW64 filesystem redirection!");
68                         }
69                 }
70         }
71 private:
72         bool  m_disabled;
73         void* m_oldValue;
74 };
75
76 //-------------------------------------
77 // External API
78 //-------------------------------------
79
80 bool AvisynthCheckThread::detect(SysinfoModel *sysinfo)
81 {
82         sysinfo->clearAvisynth();
83         double version = 0.0;
84         QMutexLocker lock(&m_avsLock);
85
86         QEventLoop loop;
87         AvisynthCheckThread thread(sysinfo);
88
89         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
90
91         connect(&thread, SIGNAL(finished()), &loop, SLOT(quit()));
92         connect(&thread, SIGNAL(terminated()), &loop, SLOT(quit()));
93         
94         thread.start();
95         QTimer::singleShot(15000, &loop, SLOT(quit()));
96         
97         qDebug("Avisynth thread has been created, please wait...");
98         loop.exec(QEventLoop::ExcludeUserInputEvents);
99         qDebug("Avisynth thread finished.");
100
101         QApplication::restoreOverrideCursor();
102
103         if(!thread.wait(1000))
104         {
105                 qWarning("Avisynth thread encountered timeout -> probably deadlock!");
106                 thread.terminate();
107                 thread.wait();
108                 return false;
109         }
110
111         if(thread.getException())
112         {
113                 qWarning("Avisynth thread encountered an exception !!!");
114                 return false;
115         }
116         
117         if(thread.getSuccess())
118         {
119                 sysinfo->setAvisynth(SysinfoModel::Avisynth_X86, thread.getSuccess() & AVISYNTH_X86);
120                 sysinfo->setAvisynth(SysinfoModel::Avisynth_X64, thread.getSuccess() & AVISYNTH_X64);
121                 qDebug("Avisynth support is officially enabled now! [x86=%c, x64=%c]", BOOLIFY(sysinfo->getAvisynth(SysinfoModel::Avisynth_X86)), BOOLIFY(sysinfo->getAvisynth(SysinfoModel::Avisynth_X64)));
122         }
123         else
124         {
125                 qWarning("Avisynth could not be found -> Avisynth support disabled!");
126         }
127
128         return true;
129 }
130
131 //-------------------------------------
132 // Thread class
133 //-------------------------------------
134
135 AvisynthCheckThread::AvisynthCheckThread(const SysinfoModel *const sysinfo)
136 :
137         m_sysinfo(sysinfo)
138 {
139         m_success = false;
140         m_exception = false;
141 }
142
143 AvisynthCheckThread::~AvisynthCheckThread(void)
144 {
145 }
146
147 void AvisynthCheckThread::run(void)
148 {
149         m_exception = false;
150         m_success &= 0;
151
152         detectAvisynthVersion1(m_success, m_sysinfo, &m_exception);
153 }
154
155 void AvisynthCheckThread::detectAvisynthVersion1(int &success, const SysinfoModel *const sysinfo, volatile bool *exception)
156 {
157         __try
158         {
159                 detectAvisynthVersion2(success, sysinfo, exception);
160         }
161         __except(1)
162         {
163                 *exception = true;
164                 qWarning("Unhandled exception error in Avisynth thread !!!");
165         }
166 }
167
168 void AvisynthCheckThread::detectAvisynthVersion2(int &success, const SysinfoModel *const sysinfo, volatile bool *exception)
169 {
170         try
171         {
172                 return detectAvisynthVersion3(success, sysinfo);
173         }
174         catch(...)
175         {
176                 *exception = true;
177                 qWarning("Avisynth initializdation raised an C++ exception!");
178         }
179 }
180
181 void AvisynthCheckThread::detectAvisynthVersion3(int &success, const SysinfoModel *const sysinfo)
182 {
183         success &= 0;
184
185         QFile *avsPath32;
186         if(checkAvisynth(sysinfo, avsPath32, false))
187         {
188                 m_avsDllPath[0].reset(avsPath32);
189                 success |= AVISYNTH_X86;
190                 qDebug("Avisynth 32-Bit edition found!");
191         }
192         else
193         {
194                 qDebug("Avisynth 32-Bit edition *not* found!");
195         }
196
197         if(sysinfo->getCPUFeatures(SysinfoModel::CPUFeatures_X64))
198         {
199                 QFile *avsPath64;
200                 if(checkAvisynth(sysinfo, avsPath64, true))
201                 {
202                         m_avsDllPath[1].reset(avsPath64);
203                         success |= AVISYNTH_X64;
204                         qDebug("Avisynth 64-Bit edition found!");
205                 }
206                 else
207                 {
208                         qDebug("Avisynth 64-Bit edition *not* found!");
209                 }
210         }
211         else
212         {
213                 qWarning("Skipping 64-Bit Avisynth check on non-x64 system!");
214         }
215 }
216
217 bool AvisynthCheckThread::checkAvisynth(const SysinfoModel *const sysinfo, QFile *&path, const bool &x64)
218 {
219         qDebug("Avisynth %s-Bit support is being tested.", x64 ? "64" : "32");
220
221         QProcess process;
222         QStringList output;
223
224         //Setup process object
225         process.setWorkingDirectory(QDir::tempPath());
226         process.setProcessChannelMode(QProcess::MergedChannels);
227         process.setReadChannel(QProcess::StandardOutput);
228
229         //Try to start VSPIPE.EXE
230         process.start(AVS_CHECK_BINARY(sysinfo, x64), QStringList());
231         if(!process.waitForStarted())
232         {
233                 qWarning("Failed to launch AVS_CHECK.EXE -> %s", process.errorString().toUtf8().constData());
234                 return false;
235         }
236
237         //Wait for process to finish
238         while(process.state() != QProcess::NotRunning)
239         {
240                 if(process.waitForReadyRead(12000))
241                 {
242                         while(process.canReadLine())
243                         {
244                                 output << QString::fromUtf8(process.readLine()).simplified();
245                         }
246                         continue;
247                 }
248                 if(process.state() != QProcess::NotRunning)
249                 {
250                         qWarning("AVS_CHECK.EXE process encountered a deadlock -> aborting now!");
251                         break;
252                 }
253         }
254
255         //Make sure VSPIPE.EXE has terminated!
256         process.waitForFinished(2500);
257         if(process.state() != QProcess::NotRunning)
258         {
259                 qWarning("AVS_CHECK.EXE process still running, going to kill it!");
260                 process.kill();
261                 process.waitForFinished(-1);
262         }
263
264         //Read pending lines
265         while(process.canReadLine())
266         {
267                 output << QString::fromUtf8(process.readLine()).simplified();
268         }
269
270         //Check exit code
271         if(process.exitCode() != 0)
272         {
273                 qWarning("AVS_CHECK.EXE failed with code 0x%08X -> disable Avisynth support!", process.exitCode());
274                 return false;
275         }
276
277         //Init regular expressions
278         QRegExp avsLogo("Avisynth\\s+Checker\\s+(x86|x64)");
279         QRegExp avsPath("Avisynth_DLLPath=(.+)");
280         QRegExp avsVers("Avisynth_Version=(\\d+)\\.(\\d+)");
281         
282         //Check for version info
283         bool avisynthLogo = false;
284         quint32 avisynthVersion[2] = { 0, 0 };
285         QString avisynthPath;
286         for(QStringList::ConstIterator iter = output.constBegin(); iter != output.constEnd(); iter++)
287         {
288                 if(avisynthLogo)
289                 {
290                         if(avsPath.indexIn(*iter) >= 0)
291                         {
292                                 avisynthPath =  avsPath.cap(1).trimmed();
293                         }
294                         else if(avsVers.indexIn(*iter) >= 0)
295                         {
296                                 quint32 temp[2];
297                                 if(MUtils::regexp_parse_uint32(avsVers, temp, 2))
298                                 {
299                                         avisynthVersion[0] = temp[0];
300                                         avisynthVersion[1] = temp[1];
301                                 }
302                         }
303                 }
304                 else
305                 {
306                         if(avsLogo.lastIndexIn(*iter) >= 0)
307                         {
308                                 avisynthLogo = true;
309                         }
310                 }
311         }
312         
313         //Minimum required version found?
314         if((avisynthVersion[0] >= 2) && (avisynthVersion[1] >= 50) && (!avisynthPath.isEmpty()))
315         {
316                 Wow64RedirectionDisabler disableWow64Redir;
317                 path = new QFile(avisynthPath);
318                 if(!path->open(QIODevice::ReadOnly))
319                 {
320                         MUTILS_DELETE(path);
321                 }
322
323                 qDebug("Avisynth was detected successfully (current version: %u.%02u).", avisynthVersion[0], avisynthVersion[1]);
324                 qDebug("Avisynth DLL path: %s", MUTILS_UTF8(avisynthPath));
325                 return true;
326         }
327         
328         //Failed to determine version
329         qWarning("Failed to determine Avisynth version!");
330         return false;
331 }