/////////////////////////////////////////////////////////////////////////////// // Copyright (c) Electronic Arts Inc. All rights reserved. /////////////////////////////////////////////////////////////////////////////// #ifdef _MSC_VER #pragma warning(disable: 4530 4548 4509) #pragma warning(disable: 6320) // Exception-filter expression is the constant EXCEPTION_EXECUTE_HANDLER. #pragma warning(disable: 4472 4355) // additional warnings generated by XDK with VS2015 #endif #include #include #include #include #ifdef EA_PLATFORM_ANDROID #include #endif #include #include #include #include #include #include #include EA_DISABLE_ALL_VC_WARNINGS() #include #include #include #include #include #if defined(EA_PLATFORM_MICROSOFT) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #elif defined(__APPLE__) // OS X, iPhone, iPad, etc. #include #include #include #include #import #import #elif defined(EA_PLATFORM_BSD) #include #include #elif defined(EA_HAVE_SYS_PTRACE_H) #include #include #elif defined(EA_PLATFORM_ANDROID) #include #include #endif EA_RESTORE_ALL_VC_WARNINGS() /////////////////////////////////////////////////////////////////////////////// // EA_COMPILER_VA_COPY_REQUIRED // // This is already present in EABase version >= 2.00.40a report may not cause a flush // See EABase for documentation. // #ifndef EA_COMPILER_VA_COPY_REQUIRED #if (EABASE_VERSION_N < 20040) // If not already handled by EABase... #if ((defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__)) && (!defined(__i386__) || defined(__x86_64__)) && !defined(__ppc__) && !defined(__PPC__) && !defined(__PPC64__) #define EA_COMPILER_VA_COPY_REQUIRED 1 #endif #endif #endif namespace EA { namespace EAMain { namespace Internal { EAMAIN_API EAMainFunction gEAMainFunction; /// ReportDefault /// /// This is the default report function. /// It does not append any newlines to the output nor does it require /// the user to do so. It simply passes on the input to stdout. /// If the user wants the output to have a newline, the user must supply it. /// This allows the user to report multiple text items to the same line if desired. /// It does not send the input to stderr, as the output of a unit test /// is deemed to be test results (success or failure) and not errors. /// #ifdef EA_PLATFORM_ANDROID static const size_t ANDROID_REPORT_BUFFER_SIZE = 1023; static char gAndroidReportBuffer[ANDROID_REPORT_BUFFER_SIZE + 1]; static char *gAndroidReportBufferWritePtr = gAndroidReportBuffer; static EA::Thread::Futex gBufferFutex; static EA::Thread::ThreadTime gLastThreadTime = EA::Thread::kTimeoutImmediate; static EA::Thread::ThreadTime gMinTimeBetweenPrints = EA::Thread::ThreadTime(1); // This function assumes that the buffer futex above is held // prior to entry. Please ensure that the buffer futex is either // held by the calling code. static void FlushAndroidReportBuffer() { if (gAndroidReportBufferWritePtr != gAndroidReportBuffer) { *gAndroidReportBufferWritePtr = 0; __android_log_write(ANDROID_LOG_INFO, "EAMain", gAndroidReportBuffer); // We found that if the OS is spammed too quickly with log info, it stops taking output from the app // because it thinks it's a DDOS attack. So we sleep to give the OS time in the case the last // log output was too recent. EA::Thread::ThreadTime currentTime = EA::Thread::GetThreadTime(); if (gLastThreadTime != EA::Thread::kTimeoutImmediate && ((currentTime - gLastThreadTime) < gMinTimeBetweenPrints)) { EA::Thread::ThreadSleep(gMinTimeBetweenPrints); } gLastThreadTime = currentTime; gAndroidReportBufferWritePtr = gAndroidReportBuffer; } } static void AppendToReportBuffer(char8_t c) { char *ptr = gAndroidReportBufferWritePtr; char *end = &gAndroidReportBuffer[ANDROID_REPORT_BUFFER_SIZE]; if (ptr >= end) { FlushAndroidReportBuffer(); } *gAndroidReportBufferWritePtr++ = c; } static void AndroidReport(const char8_t *pMessage) { using namespace EA::StdC; using namespace EA::Thread; AutoFutex autoFutex(gBufferFutex); size_t messageLength = Strlen(pMessage); for (size_t i = 0; i < messageLength; ++i) { char8_t c = pMessage[i]; switch (c) { case '\n': FlushAndroidReportBuffer(); break; default: AppendToReportBuffer(c); break; } } } #endif static void ReportDefault(const char8_t* pMessage) { if (!pMessage) { return; } // It's possible that the underlying print back end can't handle large // output sizes. For example, the OutputDebugStringA call below drops // chars beyond about 4096. size_t length = EA::StdC::Strlen(pMessage); // It might be faster to make a custom Strlen which quits after N chars. const size_t kMaxLength = 1024; if(length > kMaxLength) { for(size_t i = 0, copiedLength = 0; i < length; i += copiedLength) { char8_t buffer[kMaxLength + 1]; size_t c; copiedLength = ((length - i) >= kMaxLength) ? kMaxLength : (length - i); for(c = 0; c < copiedLength; c++) buffer[c] = pMessage[i + c]; buffer[c] = 0; ReportDefault(buffer); } } else { #if defined(EA_PLATFORM_MICROSOFT) && !defined(CS_UNDEFINED_STRING) // No need to do this for Microsoft console platforms, as the fputs below covers that. OutputDebugStringA(pMessage); #endif #if defined(EA_PLATFORM_ANDROID) // Android doesn't implement stdio (e.g. fputs), though sometimes we use compiler // linking statements to redirect stdio functions to our own implementations which // allow it to work. // // __android_log_write can write only 512 bytes at a time. Normally we don't write // so much text in unit test output, but if this becomes a problem then we can loop // and write blocks of the output. The primary downside to such an approach is that // __android_log_write appends a \n to your output for each call. See the EAStdC // EASprintfCore.cpp code for example loop code. AndroidReport(pMessage); #else fputs(pMessage, stdout); fflush(stdout); #endif } } EAMAIN_API const char *ExtractPrintServerAddress(int argc, char **argv) { CommandLine commandLine(argc, argv); const char *printServerAddress = NULL; if (commandLine.FindSwitch("PrintServerIPAddress", false, &printServerAddress, 0, '=') >= 0) { if (EA::StdC::Strlen(printServerAddress) > 0) { return printServerAddress; } } return NULL; } } ReportFunction gpReportFunction = EA::EAMain::Internal::ReportDefault; EAMAIN_API void SetReportFunction(ReportFunction pReportFunction) { gpReportFunction = pReportFunction; } EAMAIN_API ReportFunction GetReportFunction() { return gpReportFunction; } EAMAIN_API ReportFunction GetDefaultReportFunction() { using namespace EA::EAMain::Internal; return ReportDefault; } /////////////////////////////////////////////////////////////////////////////// // GetVerbosity / SetVerbosity /////////////////////////////////////////////////////////////////////////////// unsigned gVerbosity = 0; // 0 means to display just failures. EAMAIN_API unsigned GetVerbosity() { return gVerbosity; } EAMAIN_API void SetVerbosity(unsigned verbosity) { gVerbosity = verbosity; } /////////////////////////////////////////////////////////////////////////////// // ReportVaList // static void ReportVaList(unsigned minVerbosity, ReportFunction pReportFunction, const char8_t* pFormat, va_list arguments) { if(pFormat && (GetVerbosity() >= minVerbosity)) { #if defined(EA_PLATFORM_DESKTOP) const int kBufferSize = 2048; #else const int kBufferSize = 512; #endif char buffer[kBufferSize]; #if defined(EA_COMPILER_VA_COPY_REQUIRED) va_list argumentsSaved; va_copy(argumentsSaved, arguments); #endif const int nReturnValue = EA::StdC::Vsnprintf(buffer, kBufferSize, pFormat, arguments); if(!pReportFunction) pReportFunction = gpReportFunction; if(pReportFunction) { if((nReturnValue >= 0) && (nReturnValue < (int)kBufferSize)) pReportFunction(buffer); else if(nReturnValue < 0) // If we simply didn't have enough buffer space. { pReportFunction("Invalid format specified.\n Format: "); pReportFunction(pFormat); } else // Else we simply didn't have enough buffer space. { char* pBuffer = static_cast(calloc(nReturnValue + 1, 1)); if(pBuffer) { #if defined(EA_COMPILER_VA_COPY_REQUIRED) va_end(arguments); va_copy(arguments, argumentsSaved); #endif EA::StdC::Vsnprintf(pBuffer, nReturnValue + 1, pFormat, arguments); pReportFunction(pBuffer); free(pBuffer); } else pReportFunction("Unable to allocate buffer space for large printf.\n"); } } #if defined(EA_COMPILER_VA_COPY_REQUIRED) // The caller will call va_end(arguments) va_end(argumentsSaved); #endif } } /////////////////////////////////////////////////////////////////////////////// // Report // EAMAIN_API void Report(const char8_t* pFormat, ...) { va_list arguments; va_start(arguments, pFormat); ReportVaList(0, gpReportFunction, pFormat, arguments); va_end(arguments); } /////////////////////////////////////////////////////////////////////////////// // ReportVerbosity // EAMAIN_API void ReportVerbosity(unsigned minVerbosity, const char8_t* pFormat, ...) { va_list arguments; va_start(arguments, pFormat); ReportVaList(minVerbosity, gpReportFunction, pFormat, arguments); va_end(arguments); } /////////////////////////////////////////////////////////////////////////////// // VReport // EAMAIN_API void VReport(const char8_t* pFormat, va_list arguments) { ReportVaList(0, gpReportFunction, pFormat, arguments); } /////////////////////////////////////////////////////////////////////////////// // VReportVerbosity // EAMAIN_API void VReportVerbosity(unsigned minVerbosity, const char8_t* pFormat, va_list arguments) { ReportVaList(minVerbosity, gpReportFunction, pFormat, arguments); } /////////////////////////////////////////////////////////////////////////////// // PlatformStartup // EAMAIN_API void PlatformStartup() { // Routed to EAMainStartup to centralize // the platform specific startup code. PlatformStartup(NULL); } EAMAIN_API void PlatformStartup(int argc, char **argv) { const char *printServerNetworkAddress = Internal::ExtractPrintServerAddress(argc, argv); PlatformStartup(printServerNetworkAddress); } EAMAIN_API void PlatformStartup(const char *printServerNetworkAddress) { // Routed to EAMainStartup to centralize // the platform specific startup code. EA::EAMain::Internal::EAMainStartup(printServerNetworkAddress); } /////////////////////////////////////////////////////////////////////////////// // PlatformShutdown // EAMAIN_API void PlatformShutdown(int errorCount) { #ifdef EA_PLATFORM_ANDROID // The Android reporting functions will flush the output buffers // when a newline is encountered. Calling AndroidReport with a // single newline will cause any accumulated output to flush to // the log. // // An alternative would be to call the FlushAndroidReportBuffer // function but doing so would necessitate having separate locks // for both FlushAndroidReportBuffer and AndroidReport, as both // of these could be caused at the same time. To avoid this // complication, FlushAndroidReportBuffer will only be called by // AndroidReport or its children. // -mburke Internal::AndroidReport("\n"); #endif // Routed to EAMainShutdown to centralize // the platform specific shutdown code. EA::EAMain::Internal::EAMainShutdown(errorCount); } /////////////////////////////////////////////////////////////////////////////// // CommandLine /////////////////////////////////////////////////////////////////////////////// CommandLine::CommandLine(int argc, char** argv) : mArgc(argc) , mArgv(NULL) , mCommandLine(NULL) { mArgv = static_cast(calloc(argc + 1, sizeof(char *))); EA_ASSERT(mArgv != NULL); for (int i = 0; i < argc; ++i) { mArgv[i] = argv[i]; } mArgv[argc] = NULL; // Microsoft fails to support argc/argv on Xenon. Sometimes it works; sometimes it doesn't. } CommandLine::CommandLine(const char *args) : mArgc(0), mArgv(NULL), mCommandLine(NULL) { ParseCommandLine(args, FLAG_NONE); } CommandLine::CommandLine(const char *args, unsigned int flags) : mArgc(0), mArgv(NULL), mCommandLine(NULL) { ParseCommandLine(args, flags); } CommandLine::~CommandLine() { if (mArgv) { free(mArgv); mArgv = NULL; } if (mCommandLine) { free(mCommandLine); mCommandLine = NULL; } } /// Stristr /// We implement this here because it isn't consistently present with all compiler-supplied C libraries. static char* Stristr(const char* s1, const char* s2) { const char* cp = s1; if(!*s2) return (char*)s1; while(*cp) { const char* s = cp; const char* t = s2; while(*s && *t && (tolower(*s) == tolower(*t))) ++s, ++t; if(*t == 0) return (char*)cp; ++cp; } return 0; } //Returns position switch is found at. Returns -1 if not found int CommandLine::FindSwitch(const char* pSwitch, bool bCaseSensitive, const char** pResult, int nStartingIndex, char delimeter) const { const char8_t kSwitchIDs[] = { '-', '/' }; const int kSwitchIDCount = sizeof(kSwitchIDs)/sizeof(kSwitchIDs[0]); static const char sEmptyString[] = { 0 }; if(nStartingIndex < 0) { nStartingIndex = 0; } if (pResult) { *pResult = sEmptyString; } // Here we move the input pSwitch past any one leading switch indicator such as '-'. for(int i = 0; i < kSwitchIDCount; ++i) { if(*pSwitch == kSwitchIDs[i]) { ++pSwitch; break; } } const size_t nSwitchLength = strlen(pSwitch); if (!nSwitchLength || (nStartingIndex >= mArgc)) return -1; for(int i = nStartingIndex; i < mArgc; ++i) { const char *sCurrent = mArgv[i]; if(strlen(sCurrent) >= 2) // Enough, for example, for "-x". { int j; // Make sure the string starts with a switch ID (e.g. '-'). for(j = 0; j < kSwitchIDCount; ++j) { if(sCurrent[0] == kSwitchIDs[j]) break; } if(j < kSwitchIDCount) // If a leading '-' was found... { const char* pCurrent = bCaseSensitive ? strstr(sCurrent + 1, pSwitch) : Stristr(sCurrent + 1, pSwitch); const char* pCStr = sCurrent; if(pCurrent == (pCStr + 1)) // If the user's input switch matched at least the start of the current argument switch... { pCurrent += nSwitchLength; // Move pCurrent past the input switch. // At this point, we require that *pCurrent is either 0 or delimeter. if((*pCurrent == 0) || (*pCurrent == delimeter)) { // We have a match. Now possibly return a result string. if(*pCurrent == delimeter) { if(*++pCurrent) { if(pResult) { *pResult = pCurrent; } } } return i; } } } } } return -1; } bool CommandLine::HasHelpSwitch() const { if((FindSwitch("-help", false, NULL, 0) >= 0) || (FindSwitch("-h", false, NULL, 0) >= 0) || (FindSwitch("-?", false, NULL, 0) >= 0)) { return true; } return false; } void CommandLine::ParseCommandLine(const char *inputCommandLine, unsigned int flags) { size_t commandLineLength = strlen(inputCommandLine); size_t allocSize = commandLineLength + 1; size_t startOffset = 0; if (flags & FLAG_NO_PROGRAM_NAME) { allocSize += 1; startOffset = 1; } char *commandLine = static_cast(calloc(allocSize, 1)); EA_ASSERT(commandLine != NULL); memcpy(commandLine + startOffset, inputCommandLine, commandLineLength); int argc = 0; char **argv = static_cast(calloc(MAX_COMMANDLINE_ARGS, sizeof(char *))); EA_ASSERT(argv != NULL); char *start = commandLine + startOffset; char *ptr = start; char *end = start + commandLineLength; bool isQuoted = false; const char quote = '"'; if (flags & FLAG_NO_PROGRAM_NAME) { argv[argc++] = commandLine; } while (ptr < end) { // The two cases this parser handles for quotes are: // "this is a quoted parameter"; and // -D:"this is a quoted parameter" // The parser does not handle edge cases like // "this is a quoted parameter"and"this is the same" char *quoteStart = NULL; for (;;) { while ((ptr < end) && !isspace((unsigned char)*ptr)) { if (*ptr == quote && !isQuoted) { isQuoted = true; quoteStart = ptr; } ++ptr; } if (isQuoted) { if (*(ptr - 1) == quote) { // If we find a quote, shift the whole string back // by one character, ie: // -D:"this is a quoted parameter" // becomes // -D:this is a quoted parameter" // The trailing quote is removed below when we place // a null terminator at the end of our argument. memmove(quoteStart, quoteStart + 1, (end - quoteStart)); --end; ptr -= 2; isQuoted = false; break; } ++ptr; } else { break; } } if (ptr != start) { *ptr = 0; argv[argc++] = start; ++ptr; } while ((ptr < end) && isspace((unsigned char)*ptr)) { ++ptr; } start = ptr; } mArgc = argc; mArgv = argv; mCommandLine = commandLine; } } } // WinRT-based Windows: #if (defined(EA_PLATFORM_MICROSOFT) && !defined(CS_UNDEFINED_STRING) && !EA_WINAPI_FAMILY_PARTITION(EA_WINAPI_PARTITION_DESKTOP)) EA_DISABLE_VC_WARNING(4350 4571 4625 4626 4265) #include EA_RESTORE_ALL_VC_WARNINGS() extern "C" int EAMain(int argc, char** argv); namespace EA { namespace EAMain { class WinRTRunner : public IWinRTRunner { private: // The copy/assignment operator is explicitly inaccessible due to the base class 'IWinRTRunner' containing a member // that is non-copyable (eg. std::future). WinRTRunner(const WinRTRunner &); WinRTRunner& operator=(const WinRTRunner &); public: WinRTRunner() {} virtual void Run(int argc, char** argv) override { mResult = std::async(std::launch::async, [=]() { const char *printServerAddress = Internal::ExtractPrintServerAddress(argc, argv); EA::EAMain::Internal::EAMainStartup(printServerAddress); int result = EA::EAMain::Internal::gEAMainFunction(argc, argv); EA::EAMain::Internal::EAMainShutdown(result); return result; }); } virtual bool IsFinished() override { return mResult.wait_for(std::chrono::milliseconds(33)) == std::future_status::ready; } virtual void ReportResult() override { char output[100]; EA::StdC::Snprintf(output, EAArrayCount(output), "EXIT(%d)\n", mResult.get()); // Using OutputDebugStringA directly here as opposed to Report as someone may overload // the default reporter. And this is what counts for EARunner to know what to do. OutputDebugStringA(output); } std::future mResult; }; EAMAIN_API IWinRTRunner* CreateWinRTRunner() { return new WinRTRunner(); } } // namespace EAMain } // namespace EA #endif // WinRT-based Windows