2012-11-01 15:19:01 +00:00
|
|
|
// Headless version of PPSSPP, for testing using http://code.google.com/p/pspautotests/ .
|
|
|
|
// See headless.txt.
|
|
|
|
// To build on non-windows systems, just run CMake in the SDL directory, it will build both a normal ppsspp and the headless version.
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
2013-02-18 22:25:06 +00:00
|
|
|
#include "Core/Config.h"
|
|
|
|
#include "Core/Core.h"
|
|
|
|
#include "Core/CoreTiming.h"
|
|
|
|
#include "Core/System.h"
|
2013-04-11 05:17:43 +00:00
|
|
|
#include "Core/HLE/sceUtility.h"
|
2013-02-18 22:25:06 +00:00
|
|
|
#include "Core/MIPS/MIPS.h"
|
|
|
|
#include "Core/Host.h"
|
2012-11-01 15:19:01 +00:00
|
|
|
#include "Log.h"
|
|
|
|
#include "LogManager.h"
|
2013-03-30 05:39:37 +00:00
|
|
|
#include "native/input/input_state.h"
|
2012-11-01 15:19:01 +00:00
|
|
|
|
2013-02-08 08:22:37 +00:00
|
|
|
#include "Compare.h"
|
2012-12-30 03:36:03 +00:00
|
|
|
#include "StubHost.h"
|
|
|
|
#ifdef _WIN32
|
2013-02-19 16:00:05 +00:00
|
|
|
#include "Windows/OpenGLBase.h"
|
2012-12-30 03:36:03 +00:00
|
|
|
#include "WindowsHeadlessHost.h"
|
|
|
|
#endif
|
2012-11-01 15:19:01 +00:00
|
|
|
|
2012-11-09 12:40:09 +00:00
|
|
|
class PrintfLogger : public LogListener
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
void Log(LogTypes::LOG_LEVELS level, const char *msg)
|
|
|
|
{
|
|
|
|
switch (level)
|
|
|
|
{
|
2013-03-11 05:31:47 +00:00
|
|
|
case LogTypes::LVERBOSE:
|
|
|
|
fprintf(stderr, "V %s", msg);
|
|
|
|
break;
|
2012-11-09 12:40:09 +00:00
|
|
|
case LogTypes::LDEBUG:
|
2012-12-30 04:49:45 +00:00
|
|
|
fprintf(stderr, "D %s", msg);
|
2012-11-09 12:40:09 +00:00
|
|
|
break;
|
|
|
|
case LogTypes::LINFO:
|
2012-12-30 04:49:45 +00:00
|
|
|
fprintf(stderr, "I %s", msg);
|
2012-11-09 12:40:09 +00:00
|
|
|
break;
|
|
|
|
case LogTypes::LERROR:
|
2012-12-30 04:49:45 +00:00
|
|
|
fprintf(stderr, "E %s", msg);
|
2012-11-09 12:40:09 +00:00
|
|
|
break;
|
|
|
|
case LogTypes::LWARNING:
|
2012-12-30 04:49:45 +00:00
|
|
|
fprintf(stderr, "W %s", msg);
|
2012-11-09 12:40:09 +00:00
|
|
|
break;
|
|
|
|
case LogTypes::LNOTICE:
|
|
|
|
default:
|
2012-12-30 04:49:45 +00:00
|
|
|
fprintf(stderr, "N %s", msg);
|
2012-11-09 12:40:09 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-03-29 17:50:08 +00:00
|
|
|
struct InputState;
|
2013-02-18 22:25:06 +00:00
|
|
|
// Temporary hack around annoying linking error.
|
|
|
|
void GL_SwapBuffers() { }
|
2013-03-30 05:39:37 +00:00
|
|
|
void NativeUpdate(InputState &input_state) { }
|
|
|
|
void NativeRender() { }
|
|
|
|
|
|
|
|
#ifndef _WIN32
|
|
|
|
InputState input_state;
|
|
|
|
#endif
|
2013-02-18 22:25:06 +00:00
|
|
|
|
2012-11-25 05:45:32 +00:00
|
|
|
void printUsage(const char *progname, const char *reason)
|
2012-11-01 15:19:01 +00:00
|
|
|
{
|
2012-11-25 05:45:32 +00:00
|
|
|
if (reason != NULL)
|
|
|
|
fprintf(stderr, "Error: %s\n\n", reason);
|
2012-11-01 15:19:01 +00:00
|
|
|
fprintf(stderr, "PPSSPP Headless\n");
|
2012-12-06 07:47:09 +00:00
|
|
|
fprintf(stderr, "This is primarily meant as a non-interactive test tool.\n\n");
|
|
|
|
fprintf(stderr, "Usage: %s file.elf [options]\n\n", progname);
|
2012-11-25 05:45:32 +00:00
|
|
|
fprintf(stderr, "Options:\n");
|
|
|
|
fprintf(stderr, " -m, --mount umd.cso mount iso on umd:\n");
|
|
|
|
fprintf(stderr, " -l, --log full log output, not just emulated printfs\n");
|
2012-12-30 05:01:13 +00:00
|
|
|
|
|
|
|
HEADLESSHOST_CLASS h1;
|
|
|
|
HeadlessHost h2;
|
|
|
|
if (typeid(h1) != typeid(h2))
|
2013-01-14 00:35:34 +00:00
|
|
|
{
|
2012-12-30 05:01:13 +00:00
|
|
|
fprintf(stderr, " --graphics use the full gpu backend (slower)\n");
|
2013-01-14 00:35:34 +00:00
|
|
|
fprintf(stderr, " --screenshot=FILE compare against a screenshot\n");
|
|
|
|
}
|
2012-12-30 05:01:13 +00:00
|
|
|
|
2013-02-09 09:14:39 +00:00
|
|
|
fprintf(stderr, " -i use the interpreter\n");
|
|
|
|
fprintf(stderr, " -j use jit (default)\n");
|
2012-11-25 05:45:32 +00:00
|
|
|
fprintf(stderr, " -c, --compare compare with output in file.expected\n");
|
|
|
|
fprintf(stderr, "\nSee headless.txt for details.\n");
|
2012-11-01 15:19:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, const char* argv[])
|
|
|
|
{
|
|
|
|
bool fullLog = false;
|
2013-02-24 03:31:34 +00:00
|
|
|
bool useJit = true;
|
2012-11-01 15:19:01 +00:00
|
|
|
bool autoCompare = false;
|
2012-12-30 05:01:13 +00:00
|
|
|
bool useGraphics = false;
|
2012-11-01 15:19:01 +00:00
|
|
|
|
2012-11-25 05:45:32 +00:00
|
|
|
const char *bootFilename = 0;
|
2012-11-01 15:19:01 +00:00
|
|
|
const char *mountIso = 0;
|
2013-01-14 00:35:34 +00:00
|
|
|
const char *screenshotFilename = 0;
|
2012-11-01 15:19:01 +00:00
|
|
|
bool readMount = false;
|
|
|
|
|
2012-11-25 05:45:32 +00:00
|
|
|
for (int i = 1; i < argc; i++)
|
2012-11-01 15:19:01 +00:00
|
|
|
{
|
|
|
|
if (readMount)
|
|
|
|
{
|
|
|
|
mountIso = argv[i];
|
|
|
|
readMount = false;
|
|
|
|
continue;
|
|
|
|
}
|
2012-11-25 05:45:32 +00:00
|
|
|
if (!strcmp(argv[i], "-m") || !strcmp(argv[i], "--mount"))
|
2012-11-01 15:19:01 +00:00
|
|
|
readMount = true;
|
2012-11-25 05:45:32 +00:00
|
|
|
else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--log"))
|
2012-11-01 15:19:01 +00:00
|
|
|
fullLog = true;
|
2013-02-09 09:14:39 +00:00
|
|
|
else if (!strcmp(argv[i], "-i"))
|
|
|
|
useJit = false;
|
2012-11-01 15:19:01 +00:00
|
|
|
else if (!strcmp(argv[i], "-j"))
|
|
|
|
useJit = true;
|
2012-11-25 05:45:32 +00:00
|
|
|
else if (!strcmp(argv[i], "-c") || !strcmp(argv[i], "--compare"))
|
2012-11-01 15:19:01 +00:00
|
|
|
autoCompare = true;
|
2012-12-30 05:01:13 +00:00
|
|
|
else if (!strcmp(argv[i], "--graphics"))
|
|
|
|
useGraphics = true;
|
2013-01-14 00:35:34 +00:00
|
|
|
else if (!strncmp(argv[i], "--screenshot=", strlen("--screenshot=")) && strlen(argv[i]) > strlen("--screenshot="))
|
|
|
|
screenshotFilename = argv[i] + strlen("--screenshot=");
|
2012-11-25 05:45:32 +00:00
|
|
|
else if (bootFilename == 0)
|
|
|
|
bootFilename = argv[i];
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h"))
|
|
|
|
printUsage(argv[0], NULL);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
std::string reason = "Unexpected argument " + std::string(argv[i]);
|
|
|
|
printUsage(argv[0], reason.c_str());
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
2012-11-01 15:19:01 +00:00
|
|
|
}
|
|
|
|
|
2012-11-25 05:45:32 +00:00
|
|
|
if (readMount)
|
|
|
|
{
|
|
|
|
printUsage(argv[0], "Missing argument after -m");
|
|
|
|
return 1;
|
|
|
|
}
|
2012-11-01 15:19:01 +00:00
|
|
|
if (!bootFilename)
|
|
|
|
{
|
2012-11-25 05:45:32 +00:00
|
|
|
printUsage(argv[0], argc <= 1 ? NULL : "No executable specified");
|
2012-11-01 15:19:01 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-12-30 05:01:13 +00:00
|
|
|
HeadlessHost *headlessHost = useGraphics ? new HEADLESSHOST_CLASS() : new HeadlessHost();
|
2012-12-30 03:36:03 +00:00
|
|
|
host = headlessHost;
|
2013-03-10 22:08:57 +00:00
|
|
|
|
|
|
|
std::string error_string;
|
2013-04-11 05:07:57 +00:00
|
|
|
bool glWorking = host->InitGL(&error_string);
|
2012-11-01 15:19:01 +00:00
|
|
|
|
|
|
|
LogManager::Init();
|
|
|
|
LogManager *logman = LogManager::GetInstance();
|
|
|
|
|
2012-11-09 12:40:09 +00:00
|
|
|
PrintfLogger *printfLogger = new PrintfLogger();
|
|
|
|
|
2012-11-01 15:19:01 +00:00
|
|
|
for (int i = 0; i < LogTypes::NUMBER_OF_LOGS; i++)
|
|
|
|
{
|
|
|
|
LogTypes::LOG_TYPE type = (LogTypes::LOG_TYPE)i;
|
|
|
|
logman->SetEnable(type, fullLog);
|
|
|
|
logman->SetLogLevel(type, LogTypes::LDEBUG);
|
2012-11-09 12:40:09 +00:00
|
|
|
logman->AddListener(type, printfLogger);
|
2012-11-01 15:19:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
CoreParameter coreParameter;
|
2013-02-16 09:48:28 +00:00
|
|
|
coreParameter.cpuCore = useJit ? CPU_JIT : CPU_INTERPRETER;
|
2013-04-11 05:07:57 +00:00
|
|
|
coreParameter.gpuCore = glWorking ? GPU_GLES : GPU_NULL;
|
2012-11-01 15:19:01 +00:00
|
|
|
coreParameter.enableSound = false;
|
2013-04-11 05:17:43 +00:00
|
|
|
coreParameter.fileToStart = bootFilename;
|
|
|
|
coreParameter.mountIso = mountIso ? mountIso : "";
|
|
|
|
coreParameter.startPaused = false;
|
|
|
|
coreParameter.enableDebugging = false;
|
2012-11-09 10:03:01 +00:00
|
|
|
coreParameter.printfEmuLog = true;
|
2013-04-11 05:17:43 +00:00
|
|
|
coreParameter.headLess = true;
|
|
|
|
coreParameter.renderWidth = 480;
|
|
|
|
coreParameter.renderHeight = 272;
|
|
|
|
coreParameter.outputWidth = 480;
|
|
|
|
coreParameter.outputHeight = 272;
|
|
|
|
coreParameter.pixelWidth = 480;
|
|
|
|
coreParameter.pixelHeight = 272;
|
2013-05-20 15:28:07 +00:00
|
|
|
coreParameter.unthrottle = true;
|
2012-11-01 15:19:01 +00:00
|
|
|
|
2012-11-17 17:08:10 +00:00
|
|
|
g_Config.bEnableSound = false;
|
2012-11-05 12:36:12 +00:00
|
|
|
g_Config.bFirstRun = false;
|
|
|
|
g_Config.bIgnoreBadMemAccess = true;
|
2013-03-09 20:53:53 +00:00
|
|
|
// Never report from tests.
|
|
|
|
g_Config.sReportHost = "";
|
2013-04-11 05:17:43 +00:00
|
|
|
g_Config.bAutoSaveSymbolMap = false;
|
2013-07-21 15:17:42 +00:00
|
|
|
g_Config.iRenderingMode = true;
|
2013-04-11 05:17:43 +00:00
|
|
|
g_Config.bHardwareTransform = true;
|
|
|
|
#ifdef USING_GLES2
|
|
|
|
g_Config.iAnisotropyLevel = 0;
|
|
|
|
#else
|
|
|
|
g_Config.iAnisotropyLevel = 8;
|
|
|
|
#endif
|
|
|
|
g_Config.bVertexCache = true;
|
|
|
|
g_Config.bTrueColor = true;
|
|
|
|
g_Config.ilanguage = PSP_SYSTEMPARAM_LANGUAGE_ENGLISH;
|
2013-06-19 05:08:29 +00:00
|
|
|
g_Config.iTimeFormat = PSP_SYSTEMPARAM_TIME_FORMAT_24HR;
|
2013-04-11 05:17:43 +00:00
|
|
|
g_Config.bEncryptSave = true;
|
2013-04-20 16:30:46 +00:00
|
|
|
g_Config.sNickName = "shadow";
|
|
|
|
g_Config.iTimeZone = 60;
|
|
|
|
g_Config.iDateFormat = PSP_SYSTEMPARAM_DATE_FORMAT_DDMMYYYY;
|
2013-06-19 05:08:29 +00:00
|
|
|
g_Config.iButtonPreference = PSP_SYSTEMPARAM_BUTTON_CROSS;
|
2013-04-20 16:30:46 +00:00
|
|
|
g_Config.iLockParentalLevel = 9;
|
2012-11-05 12:36:12 +00:00
|
|
|
|
2013-01-21 00:50:42 +00:00
|
|
|
#if defined(ANDROID)
|
|
|
|
#elif defined(BLACKBERRY) || defined(__SYMBIAN32__)
|
2013-01-22 06:46:53 +00:00
|
|
|
#elif !defined(_WIN32)
|
2013-01-21 00:50:42 +00:00
|
|
|
g_Config.memCardDirectory = std::string(getenv("HOME"))+"/.ppsspp/";
|
|
|
|
g_Config.flashDirectory = g_Config.memCardDirectory+"/flash/";
|
|
|
|
#endif
|
|
|
|
|
2012-11-01 15:19:01 +00:00
|
|
|
if (!PSP_Init(coreParameter, &error_string)) {
|
2012-11-09 10:03:01 +00:00
|
|
|
fprintf(stderr, "Failed to start %s. Error: %s\n", coreParameter.fileToStart.c_str(), error_string.c_str());
|
|
|
|
printf("TESTERROR\n");
|
2012-11-01 15:19:01 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-12-30 04:49:45 +00:00
|
|
|
host->BootDone();
|
2012-11-01 15:19:01 +00:00
|
|
|
|
2013-01-14 00:35:34 +00:00
|
|
|
if (screenshotFilename != 0)
|
|
|
|
headlessHost->SetComparisonScreenshot(screenshotFilename);
|
|
|
|
|
2012-12-30 04:49:45 +00:00
|
|
|
coreState = CORE_RUNNING;
|
2012-11-01 15:19:01 +00:00
|
|
|
while (coreState == CORE_RUNNING)
|
|
|
|
{
|
|
|
|
// Run for a frame at a time, just because.
|
|
|
|
u64 nowTicks = CoreTiming::GetTicks();
|
|
|
|
u64 frameTicks = usToCycles(1000000/60);
|
|
|
|
mipsr4k.RunLoopUntil(nowTicks + frameTicks);
|
2012-11-25 02:15:33 +00:00
|
|
|
|
|
|
|
// If we were rendering, this might be a nice time to do something about it.
|
2013-02-18 22:25:06 +00:00
|
|
|
if (coreState == CORE_NEXTFRAME) {
|
2012-11-25 02:15:33 +00:00
|
|
|
coreState = CORE_RUNNING;
|
2013-02-21 09:05:37 +00:00
|
|
|
headlessHost->SwapBuffers();
|
2013-02-18 22:25:06 +00:00
|
|
|
}
|
2012-11-01 15:19:01 +00:00
|
|
|
}
|
|
|
|
|
2012-12-30 03:36:03 +00:00
|
|
|
host->ShutdownGL();
|
2012-11-01 15:19:01 +00:00
|
|
|
PSP_Shutdown();
|
|
|
|
|
2012-12-30 03:36:03 +00:00
|
|
|
delete host;
|
|
|
|
host = NULL;
|
|
|
|
headlessHost = NULL;
|
|
|
|
|
2012-11-01 15:19:01 +00:00
|
|
|
if (autoCompare)
|
2013-02-08 08:22:37 +00:00
|
|
|
CompareOutput(bootFilename);
|
2012-11-01 15:19:01 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|