Initial commit
This commit is contained in:
263
src/acsmdownloader.cpp
Normal file
263
src/acsmdownloader.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
Copyright 2021 Grégory Soutadé
|
||||
|
||||
This is a 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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It 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 it. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <log.h>
|
||||
|
||||
#include <QFile>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <dp.h>
|
||||
#include <rmsdk_wrapper.h>
|
||||
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
|
||||
|
||||
int verbose = INFO;
|
||||
|
||||
static const char* deviceFile = "device.xml";
|
||||
static const char* activationFile = "activation.xml";
|
||||
static const char* devicekeyFile = "devicesalt";
|
||||
static const char* acsmFile = 0;
|
||||
static const char* outputFile = 0;
|
||||
static const char* outputDir = 0;
|
||||
static const char* defaultDirs[] = {
|
||||
".adept/",
|
||||
"./adobe-digital-editions/",
|
||||
"./.adobe-digital-editions/"
|
||||
};
|
||||
|
||||
class ACSMDownloader
|
||||
{
|
||||
public:
|
||||
ACSMDownloader(QCoreApplication* app):
|
||||
app(app)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static void* run(void* param)
|
||||
{
|
||||
ACSMDownloader* _this;
|
||||
|
||||
LOG_FUNC();
|
||||
|
||||
int ret = dp::platformInit(0xFFFFFFFF);
|
||||
|
||||
if (ret) {
|
||||
LOG(ERROR, "Error platform init " << ret);
|
||||
_this->app->exit(ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dp::cryptRegisterOpenSSL();
|
||||
dp::documentRegisterEPUB();
|
||||
dp::documentRegisterPDF();
|
||||
|
||||
LOG(DEBUG, "Create Adobe objects");
|
||||
|
||||
try
|
||||
{
|
||||
MockDRMProcessorClient processorClient(outputDir, outputFile);
|
||||
MockDevice device(&processorClient, deviceFile, activationFile, devicekeyFile);
|
||||
MockProvider provider(&device);
|
||||
device.setProvider(&provider);
|
||||
dpdev::DeviceProvider::addProvider(&provider);
|
||||
|
||||
MockNetProvider netProvider;
|
||||
dpnet::NetProvider::setProvider(&netProvider);
|
||||
|
||||
adept::DRMProviderImpl* _prov = rmsdk::getProvider();
|
||||
|
||||
LOG(DEBUG, "Create DRM Processor");
|
||||
adept::DRMProcessorImpl* drmprocessor = _prov->createDRMProcessor(&processorClient, &device);
|
||||
|
||||
processorClient.setProcessor(drmprocessor);
|
||||
|
||||
unsigned char* buffer;
|
||||
int buffer_size;
|
||||
MockDevice::readFile(acsmFile, &buffer, &buffer_size, true);
|
||||
|
||||
LOG(DEBUG, "Init workflow ");
|
||||
|
||||
drmprocessor->initWorkflows(WORKFLOW_AUTH_SIGN_IN|WORKFLOW_FULFILLMENT|WORKFLOW_DOWNLOAD|WORKFLOW_NOTIFICATION, dp::Data(buffer, buffer_size));
|
||||
|
||||
LOG(DEBUG, "Start work");
|
||||
drmprocessor->startWorkflows(WORKFLOW_AUTH_SIGN_IN|WORKFLOW_FULFILLMENT|WORKFLOW_DOWNLOAD|WORKFLOW_NOTIFICATION);
|
||||
delete[] buffer;
|
||||
|
||||
LOG(DEBUG, "Bye bye");
|
||||
|
||||
ret = (int)processorClient.getErrors();
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
LOG(ERROR, e.what());
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
_this->app->exit(ret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
QCoreApplication* app;
|
||||
};
|
||||
|
||||
static const char* findFile(const char* filename, bool inDefaultDirs=true)
|
||||
{
|
||||
QFile file(filename);
|
||||
|
||||
if (file.exists())
|
||||
return strdup(filename);
|
||||
|
||||
if (!inDefaultDirs) return 0;
|
||||
|
||||
for (int i=0; i<(int)ARRAY_SIZE(defaultDirs); i++)
|
||||
{
|
||||
uft::String path = uft::String(defaultDirs[i]) + filename;
|
||||
file.setFileName(path.c_str());
|
||||
if (file.exists())
|
||||
return strdup(path.c_str());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
std::cout << "Download EPUB file from ACSM request file" << std::endl;
|
||||
|
||||
std::cout << "Usage: ./acsmdownloader [(-d|--device-file) device.xml] [(-a|--activation-file) activation.xml] [(-s|--device-key-file) devicesalt] [(-O|--output-dir) dir] [(-o|--output-file) output.epub] [(-v|--verbose)] [(-h|--help)] (-f|--acsm-file) file.acsm" << std::endl << std::endl;
|
||||
|
||||
std::cout << " " << "-d|--device-file" << "\t" << "device.xml file from eReader" << std::endl;
|
||||
std::cout << " " << "-a|--activation-file" << "\t" << "activation.xml file from eReader" << std::endl;
|
||||
std::cout << " " << "-k|--device-key-file" << "\t" << "private device key file (eg devicesalt/devkey.bin) from eReader" << std::endl;
|
||||
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./)" << std::endl;
|
||||
std::cout << " " << "-o|--output-file" << "\t" << "Optional output epub filename (default <title.epub>)" << std::endl;
|
||||
std::cout << " " << "-f|--acsm-file" << "\t" << "ACSM request file for epub download" << std::endl;
|
||||
std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
|
||||
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Device file, activation file and device key file are optionals. If not set, they are looked into :" << std::endl;
|
||||
std::cout << " * Current directory" << std::endl;
|
||||
std::cout << " * .adept" << std::endl;
|
||||
std::cout << " * adobe-digital-editions directory" << std::endl;
|
||||
std::cout << " * .adobe-digital-editions directory" << std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int c, ret = -1;
|
||||
|
||||
const char** files[] = {&devicekeyFile, &deviceFile, &activationFile};
|
||||
|
||||
while (1) {
|
||||
int option_index = 0;
|
||||
static struct option long_options[] = {
|
||||
{"device-file", required_argument, 0, 'd' },
|
||||
{"activation-file", required_argument, 0, 'a' },
|
||||
{"device-key-file", required_argument, 0, 'k' },
|
||||
{"output-dir", required_argument, 0, 'O' },
|
||||
{"output-file", required_argument, 0, 'o' },
|
||||
{"acsm-file", required_argument, 0, 'f' },
|
||||
{"verbose", no_argument, 0, 'v' },
|
||||
{"help", no_argument, 0, 'h' },
|
||||
{0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "d:a:k:O:o:f:vh",
|
||||
long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
switch (c) {
|
||||
case 'd':
|
||||
deviceFile = optarg;
|
||||
break;
|
||||
case 'a':
|
||||
activationFile = optarg;
|
||||
break;
|
||||
case 'k':
|
||||
devicekeyFile = optarg;
|
||||
break;
|
||||
case 'f':
|
||||
acsmFile = optarg;
|
||||
break;
|
||||
case 'O':
|
||||
outputDir = optarg;
|
||||
break;
|
||||
case 'o':
|
||||
outputFile = optarg;
|
||||
break;
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
case 'h':
|
||||
usage();
|
||||
return 0;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!acsmFile || (outputDir && !outputDir[0]) ||
|
||||
(outputFile && !outputFile[0]))
|
||||
{
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i=0; i<(int)ARRAY_SIZE(files); i++)
|
||||
{
|
||||
*files[i] = findFile(*files[i]);
|
||||
if (!*files[i])
|
||||
{
|
||||
LOG(ERROR, "Error : " << *files[i] << " doesn't exists");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
QFile file(acsmFile);
|
||||
if (!file.exists())
|
||||
{
|
||||
LOG(ERROR, "Error : " << acsmFile << " doesn't exists");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOG(INFO, "RMSDK Version " << dp::getVersionInfo("hobbes").utf8());
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
ACSMDownloader downloader(&app);
|
||||
pthread_t thread;
|
||||
pthread_create(&thread, NULL, ACSMDownloader::run, (void*)&downloader);
|
||||
|
||||
ret = app.exec();
|
||||
|
||||
for (i=0; i<(int)ARRAY_SIZE(files); i++)
|
||||
free((void*)*files[i]);
|
||||
|
||||
return ret;
|
||||
}
|
382
src/activate.cpp
Normal file
382
src/activate.cpp
Normal file
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
Copyright 2021 Grégory Soutadé
|
||||
|
||||
This is a 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 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It 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 it. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <sys/random.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <gnu/lib-names.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <stdlib.h>
|
||||
#include <termios.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
#include <log.h>
|
||||
|
||||
#include <QFile>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <dp.h>
|
||||
#include <rmsdk_wrapper.h>
|
||||
|
||||
#define RANDOM_NAME_SIZE 64
|
||||
#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
|
||||
|
||||
int verbose = INFO;
|
||||
|
||||
static const char* username = 0;
|
||||
static const char* password = 0;
|
||||
static const char* outputDir = 0;
|
||||
static const char* hobbesVersion = 0;
|
||||
static bool randomSerial = false;
|
||||
static char randomName[RANDOM_NAME_SIZE];
|
||||
|
||||
/* Overload libc getpwuid function (used to generate device serial) */
|
||||
struct passwd *getpwuid(uid_t uid)
|
||||
{
|
||||
struct passwd *res;
|
||||
void *handle;
|
||||
struct passwd* (*_getpwuid)(uid_t uid);
|
||||
|
||||
handle = dlopen(LIBC_SO, RTLD_LAZY);
|
||||
|
||||
if (!handle) {
|
||||
LOG(ERROR, "Unable to open libc (" << dlerror() << ")");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Original getpwuid()
|
||||
_getpwuid = (struct passwd *(*)(uid_t uid)) dlsym(handle, "getpwuid");
|
||||
|
||||
if (!_getpwuid)
|
||||
{
|
||||
LOG(ERROR, "getpwuid() not found in libc (" << dlerror() << ")");
|
||||
return 0;
|
||||
}
|
||||
|
||||
res = _getpwuid(uid);
|
||||
|
||||
LOG(DEBUG, "Original name " << res->pw_name);
|
||||
|
||||
if (res && randomSerial)
|
||||
{
|
||||
// Already generated
|
||||
if (randomName[0] == 0)
|
||||
{
|
||||
unsigned int seed;
|
||||
|
||||
LOG(WARN, "Generate random name");
|
||||
|
||||
getrandom((void*)&seed, sizeof(seed), GRND_RANDOM);
|
||||
|
||||
rand_r(&seed); rand_r(&seed); rand_r(&seed);
|
||||
|
||||
for(int i=0; i<RANDOM_NAME_SIZE-1; i++)
|
||||
randomName[i] = (rand_r(&seed)%('z'-'A'))+'A';
|
||||
randomName[RANDOM_NAME_SIZE-1] = 0;
|
||||
|
||||
LOG(DEBUG, "Random name " << randomName);
|
||||
}
|
||||
|
||||
res->pw_name = randomName;
|
||||
}
|
||||
|
||||
dlclose(handle);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// From http://www.cplusplus.com/articles/E6vU7k9E/
|
||||
static int getch() {
|
||||
int ch;
|
||||
struct termios t_old, t_new;
|
||||
|
||||
tcgetattr(STDIN_FILENO, &t_old);
|
||||
t_new = t_old;
|
||||
t_new.c_lflag &= ~(ICANON | ECHO);
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &t_new);
|
||||
|
||||
ch = getchar();
|
||||
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &t_old);
|
||||
return ch;
|
||||
}
|
||||
|
||||
static std::string getpass(const char *prompt, bool show_asterisk=false)
|
||||
{
|
||||
const char BACKSPACE=127;
|
||||
const char RETURN=10;
|
||||
|
||||
std::string password;
|
||||
unsigned char ch=0;
|
||||
|
||||
std::cout <<prompt;
|
||||
|
||||
while((ch=getch())!= RETURN)
|
||||
{
|
||||
if(ch==BACKSPACE)
|
||||
{
|
||||
if(password.length()!=0)
|
||||
{
|
||||
if(show_asterisk)
|
||||
std::cout <<"\b \b";
|
||||
password.resize(password.length()-1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
password+=ch;
|
||||
if(show_asterisk)
|
||||
std::cout <<'*';
|
||||
}
|
||||
}
|
||||
std::cout <<std::endl;
|
||||
return password;
|
||||
}
|
||||
|
||||
|
||||
class ACSMDownloader
|
||||
{
|
||||
public:
|
||||
ACSMDownloader(QCoreApplication* app):
|
||||
app(app)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static void* run(void* param)
|
||||
{
|
||||
ACSMDownloader* _this;
|
||||
|
||||
LOG_FUNC();
|
||||
|
||||
int ret = dp::platformInit(0xFFFFFFFF);
|
||||
|
||||
if (ret) {
|
||||
LOG(ERROR, "Error platform init " << ret);
|
||||
_this->app->exit(ret);
|
||||
return (void*)ret;
|
||||
}
|
||||
|
||||
dp::cryptRegisterOpenSSL();
|
||||
dp::documentRegisterEPUB();
|
||||
dp::documentRegisterPDF();
|
||||
|
||||
LOG(DEBUG, "Create Adobe objects");
|
||||
|
||||
char tmpActivate [] = "/tmp/activateXXXXXX";
|
||||
char* tmpDir = mkdtemp(tmpActivate);
|
||||
setenv((char*)"HOME", tmpDir, 1);
|
||||
|
||||
LOG(DEBUG, tmpDir << " created");
|
||||
|
||||
/* Will create a new dir at /tmp/activateXXXXXX/.adept */
|
||||
dpdev::UNIXDevice uDevice;
|
||||
uDevice.getFingerprint();
|
||||
|
||||
|
||||
uft::String root(tmpDir);
|
||||
uft::String tmpDevice = root + "/.adept/device.xml";
|
||||
uft::String tmpActivation = root + "/.adept/activation.xml";
|
||||
uft::String tmpDevicekey = root + "/.adept/devicesalt";
|
||||
|
||||
MockDRMProcessorClient processorClient(outputDir, 0, tmpDir);
|
||||
MockDevice device(&processorClient, tmpDevice.utf8(), tmpActivation.utf8(), tmpDevicekey.utf8());
|
||||
MockProvider provider(&device);
|
||||
device.setProvider(&provider);
|
||||
dpdev::DeviceProvider::addProvider(&provider);
|
||||
|
||||
MockNetProvider netProvider;
|
||||
dpnet::NetProvider::setProvider(&netProvider);
|
||||
|
||||
adept::DRMProviderImpl* _prov = rmsdk::getProvider();
|
||||
|
||||
LOG(DEBUG, "Create DRM Processor");
|
||||
adept::DRMProcessorImpl* drmprocessor = _prov->createDRMProcessor(&processorClient, &device);
|
||||
|
||||
processorClient.setProcessor(drmprocessor);
|
||||
|
||||
LOG(DEBUG, "Init workflow ");
|
||||
|
||||
unsigned int workflows = WORKFLOW_AUTH_SIGN_IN|WORKFLOW_ACTIVATION;
|
||||
drmprocessor->initSignInWorkflow(workflows,
|
||||
dp::String("AdobeID"),
|
||||
username,
|
||||
password);
|
||||
|
||||
LOG(DEBUG, "Start work");
|
||||
drmprocessor->startWorkflows(workflows);
|
||||
|
||||
LOG(DEBUG, "Bye bye");
|
||||
|
||||
_this->app->exit(processorClient.getErrors());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
QCoreApplication* app;
|
||||
};
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
std::cout << "Create new device files used by ADEPT DRM" << std::endl;
|
||||
|
||||
std::cout << "Usage: ./activate (-u|--username) username [(-p|--password) password] [(-O|--output-dir) dir] [(-r|--random-serial)] [(-v|--verbose)] [(-h|--help)]" << std::endl << std::endl;
|
||||
|
||||
std::cout << " " << "-u|--username" << "\t\t" << "AdobeID username (ie adobe.com email account)" << std::endl;
|
||||
std::cout << " " << "-p|--password" << "\t\t" << "AdobeID password (asked if not set via command line) " << std::endl;
|
||||
std::cout << " " << "-O|--output-dir" << "\t" << "Optional output directory were to put result (default ./.adept). This directory must not already exists" << std::endl;
|
||||
std::cout << " " << "-H|--hobbes-version" << "\t"<< "Force RMSDK version to a specific value (default: version of current librmsdk)" << std::endl;
|
||||
std::cout << " " << "-r|--random-serial" << "\t"<< "Generate a random device serial (if not set, it will be dependent of your current configuration)" << std::endl;
|
||||
std::cout << " " << "-v|--verbose" << "\t\t" << "Increase verbosity, can be set multiple times" << std::endl;
|
||||
std::cout << " " << "-h|--help" << "\t\t" << "This help" << std::endl;
|
||||
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
static const char* abspath(const char* filename)
|
||||
{
|
||||
const char* root = getcwd(0, PATH_MAX);
|
||||
uft::StringBuffer fullPath(root);
|
||||
fullPath.append("/");
|
||||
fullPath.append(filename);
|
||||
const char* res = strdup(fullPath.utf8());
|
||||
|
||||
free((void*)root);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int c, ret = -1;
|
||||
const char* _outputDir = outputDir;
|
||||
|
||||
while (1) {
|
||||
int option_index = 0;
|
||||
static struct option long_options[] = {
|
||||
{"username", required_argument, 0, 'u' },
|
||||
{"password", required_argument, 0, 'p' },
|
||||
{"output-dir", required_argument, 0, 'O' },
|
||||
{"hibbes-version",required_argument, 0, 'H' },
|
||||
{"random-serial", no_argument, 0, 'r' },
|
||||
{"verbose", no_argument, 0, 'v' },
|
||||
{"help", no_argument, 0, 'h' },
|
||||
{0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
c = getopt_long(argc, argv, "u:p:O:H:rvh",
|
||||
long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
||||
switch (c) {
|
||||
case 'u':
|
||||
username = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
password = optarg;
|
||||
break;
|
||||
case 'O':
|
||||
_outputDir = optarg;
|
||||
break;
|
||||
case 'H':
|
||||
hobbesVersion = optarg;
|
||||
break;
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
case 'h':
|
||||
usage();
|
||||
return 0;
|
||||
break;
|
||||
case 'r':
|
||||
randomSerial = true;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!username)
|
||||
{
|
||||
usage();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!_outputDir || _outputDir[0] == 0)
|
||||
{
|
||||
outputDir = abspath(".adept");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Relative path
|
||||
if (_outputDir[0] == '.' || _outputDir[0] != '/')
|
||||
{
|
||||
QFile file(_outputDir);
|
||||
// realpath doesn't works if file/dir doesn't exists
|
||||
if (file.exists())
|
||||
outputDir = realpath(_outputDir, 0);
|
||||
else
|
||||
outputDir = abspath(_outputDir);
|
||||
}
|
||||
else
|
||||
outputDir = strdup(_outputDir);
|
||||
}
|
||||
|
||||
QDir dir;
|
||||
dir.setPath(outputDir);
|
||||
if (dir.exists())
|
||||
{
|
||||
LOG(ERROR, "Error, output directory ./.adept already exists");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!password)
|
||||
{
|
||||
char prompt[128];
|
||||
std::snprintf(prompt, sizeof(prompt), "Enter password for <%s> : ", username);
|
||||
std::string pass = getpass((const char*)prompt, false);
|
||||
password = pass.c_str();
|
||||
}
|
||||
|
||||
if (hobbesVersion)
|
||||
{
|
||||
dp::setVersionInfo("hobbes", hobbesVersion);
|
||||
LOG(INFO, "RMSDK Version forced to " << dp::getVersionInfo("hobbes").utf8());
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(INFO, "RMSDK Version " << dp::getVersionInfo("hobbes").utf8());
|
||||
}
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
ACSMDownloader downloader(&app);
|
||||
pthread_t thread;
|
||||
pthread_create(&thread, NULL, ACSMDownloader::run, (void*)&downloader);
|
||||
|
||||
ret = app.exec();
|
||||
|
||||
free((void*)outputDir);
|
||||
return ret;
|
||||
}
|
Reference in New Issue
Block a user