mirror of
https://github.com/glest/glest-source.git
synced 2025-02-24 19:52:25 +01:00
1290 lines
36 KiB
C
1290 lines
36 KiB
C
/**
|
|
* MojoSetup; a portable, flexible installation application.
|
|
*
|
|
* Please see the file LICENSE.txt in the source's root directory.
|
|
*
|
|
* This file written by Ryan C. Gordon.
|
|
*
|
|
Copyright (c) 2006-2010 Ryan C. Gordon and others.
|
|
|
|
This software is provided 'as-is', without any express or implied warranty.
|
|
In no event will the authors be held liable for any damages arising from
|
|
the use of this software.
|
|
|
|
Permission is granted to anyone to use this software for any purpose,
|
|
including commercial applications, and to alter it and redistribute it
|
|
freely, subject to the following restrictions:
|
|
|
|
1. The origin of this software must not be misrepresented; you must not
|
|
claim that you wrote the original software. If you use this software in a
|
|
product, an acknowledgment in the product documentation would be
|
|
appreciated but is not required.
|
|
|
|
2. Altered source versions must be plainly marked as such, and must not be
|
|
misrepresented as being the original software.
|
|
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
|
|
Ryan C. Gordon <icculus@icculus.org>
|
|
*
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include "universal.h"
|
|
#include "platform.h"
|
|
#include "gui.h"
|
|
#include "lua_glue.h"
|
|
#include "fileio.h"
|
|
|
|
#define TEST_LAUNCH_BROWSER_CODE 0
|
|
int MojoSetup_testLaunchBrowserCode(int argc, char **argv);
|
|
|
|
#define TEST_ARCHIVE_CODE 0
|
|
int MojoSetup_testArchiveCode(int argc, char **argv);
|
|
|
|
#define TEST_NETWORK_CODE 0
|
|
int MojoSetup_testNetworkCode(int argc, char **argv);
|
|
|
|
|
|
uint8 scratchbuf_128k[128 * 1024];
|
|
MojoSetupEntryPoints GEntryPoints =
|
|
{
|
|
xmalloc,
|
|
xrealloc,
|
|
xstrdup,
|
|
xstrncpy,
|
|
translate,
|
|
logWarning,
|
|
logError,
|
|
logInfo,
|
|
logDebug,
|
|
format,
|
|
numstr,
|
|
MojoPlatform_ticks,
|
|
utf8codepoint,
|
|
utf8len,
|
|
splitText,
|
|
isValidProductKey,
|
|
};
|
|
|
|
int GArgc = 0;
|
|
const char **GArgv = NULL;
|
|
|
|
static char *crashedmsg = NULL;
|
|
static char *termedmsg = NULL;
|
|
|
|
void MojoSetup_crashed(void)
|
|
{
|
|
if (crashedmsg == NULL)
|
|
panic("BUG: crash at startup");
|
|
else
|
|
fatal(crashedmsg);
|
|
} // MojoSetup_crash
|
|
|
|
|
|
void MojoSetup_terminated(void)
|
|
{
|
|
if (termedmsg == NULL) // no translation yet.
|
|
panic("The installer has been stopped by the system.");
|
|
else
|
|
fatal(termedmsg);
|
|
} // MojoSetup_crash
|
|
|
|
|
|
#if !SUPPORT_MULTIARCH
|
|
#define trySwitchBinaries()
|
|
#else
|
|
static void trySwitchBinary(MojoArchive *ar)
|
|
{
|
|
MojoInput *io = ar->openCurrentEntry(ar);
|
|
if (io != NULL)
|
|
{
|
|
const uint32 imglen = (uint32) io->length(io);
|
|
uint8 *img = (uint8 *) xmalloc(imglen);
|
|
const uint32 br = io->read(io, img, imglen);
|
|
io->close(io);
|
|
if (br == imglen)
|
|
{
|
|
logInfo("Switching binary with '%0'...", ar->prevEnum.filename);
|
|
MojoPlatform_switchBin(img, imglen); // no return on success.
|
|
logError("...Switch binary failed.");
|
|
} // if
|
|
free(img);
|
|
} // if
|
|
} // trySwitchBinary
|
|
|
|
|
|
static void trySwitchBinaries(void)
|
|
{
|
|
if (cmdlinestr("nobinswitch", "MOJOSETUP_NOBINSWITCH", NULL) != NULL)
|
|
return; // we are already switched or the user is preventing it.
|
|
|
|
setenv("MOJOSETUP_NOBINSWITCH", "1", 1);
|
|
setenv("MOJOSETUP_BASE", GBaseArchivePath, 1);
|
|
|
|
if (GBaseArchive->enumerate(GBaseArchive))
|
|
{
|
|
const MojoArchiveEntry *entinfo;
|
|
while ((entinfo = GBaseArchive->enumNext(GBaseArchive)) != NULL)
|
|
{
|
|
if (entinfo->type != MOJOARCHIVE_ENTRY_FILE)
|
|
continue;
|
|
|
|
if (strncmp(entinfo->filename, "arch/", 5) != 0)
|
|
continue;
|
|
|
|
trySwitchBinary(GBaseArchive);
|
|
} // while
|
|
} // if
|
|
|
|
} // trySwitchBinaries
|
|
#endif
|
|
|
|
|
|
static boolean trySpawnTerminalGui(void)
|
|
{
|
|
if (cmdlinestr("notermspawn", "MOJOSETUP_NOTERMSPAWN", NULL) != NULL)
|
|
return false; // we already spawned or the user is preventing it.
|
|
|
|
if (MojoPlatform_istty()) // maybe we can spawn a terminal for stdio?
|
|
return false; // We're a terminal already, no need to spawn one.
|
|
|
|
logInfo("No usable GUI found. Trying to spawn a terminal...");
|
|
if (!MojoPlatform_spawnTerminal())
|
|
{
|
|
logError("...Terminal spawning failed.");
|
|
return false;
|
|
} // if
|
|
|
|
assert(MojoPlatform_istty());
|
|
return (MojoGui_initGuiPlugin() != NULL);
|
|
} // trySpawnTerminalGui
|
|
|
|
|
|
static boolean initEverything(void)
|
|
{
|
|
MojoLog_initLogging();
|
|
|
|
logInfo("MojoSetup starting up...");
|
|
|
|
// We have to panic on errors until the GUI is ready. Try to make things
|
|
// "succeed" unless they are catastrophic, and report problems later.
|
|
|
|
// Start with the base archive work, since it might have GUI plugins.
|
|
// None of these panic() calls are localized, since localization isn't
|
|
// functional until MojoLua_initLua() succeeds.
|
|
if (!MojoArchive_initBaseArchive())
|
|
panic("Initial setup failed. Cannot continue.");
|
|
|
|
trySwitchBinaries(); // may not return.
|
|
|
|
if (!MojoGui_initGuiPlugin())
|
|
{
|
|
// This could terminate the process (and relaunch).
|
|
if (!trySpawnTerminalGui())
|
|
panic("Failed to start GUI. Is your download incomplete or corrupt?");
|
|
} // if
|
|
|
|
else if (!MojoLua_initLua())
|
|
{
|
|
// (...but if you're the developer: are your files in the wrong place?)
|
|
panic("Failed to start. Is your download incomplete or corrupt?");
|
|
} // else if
|
|
|
|
crashedmsg = xstrdup(_("The installer has crashed due to a bug."));
|
|
termedmsg = xstrdup(_("The installer has been stopped by the system."));
|
|
|
|
return true;
|
|
} // initEverything
|
|
|
|
|
|
static void deinitEverything(void)
|
|
{
|
|
char *tmp = NULL;
|
|
|
|
logInfo("MojoSetup shutting down...");
|
|
MojoLua_deinitLua();
|
|
MojoGui_deinitGuiPlugin();
|
|
MojoArchive_deinitBaseArchive();
|
|
MojoLog_deinitLogging();
|
|
|
|
tmp = crashedmsg;
|
|
crashedmsg = NULL;
|
|
free(tmp);
|
|
tmp = termedmsg;
|
|
termedmsg = NULL;
|
|
free(tmp);
|
|
} // deinitEverything
|
|
|
|
|
|
void MojoChecksum_init(MojoChecksumContext *ctx)
|
|
{
|
|
memset(ctx, '\0', sizeof (MojoChecksumContext));
|
|
#if SUPPORT_CRC32
|
|
MojoCrc32_init(&ctx->crc32);
|
|
#endif
|
|
#if SUPPORT_MD5
|
|
MojoMd5_init(&ctx->md5);
|
|
#endif
|
|
#if SUPPORT_SHA1
|
|
MojoSha1_init(&ctx->sha1);
|
|
#endif
|
|
} // MojoChecksum_init
|
|
|
|
|
|
void MojoChecksum_append(MojoChecksumContext *ctx, const uint8 *d, uint32 len)
|
|
{
|
|
#if SUPPORT_CRC32
|
|
MojoCrc32_append(&ctx->crc32, d, len);
|
|
#endif
|
|
#if SUPPORT_MD5
|
|
MojoMd5_append(&ctx->md5, d, len);
|
|
#endif
|
|
#if SUPPORT_SHA1
|
|
MojoSha1_append(&ctx->sha1, d, len);
|
|
#endif
|
|
} // MojoChecksum_append
|
|
|
|
|
|
void MojoChecksum_finish(MojoChecksumContext *ctx, MojoChecksums *sums)
|
|
{
|
|
memset(sums, '\0', sizeof (MojoChecksums));
|
|
#if SUPPORT_CRC32
|
|
MojoCrc32_finish(&ctx->crc32, &sums->crc32);
|
|
#endif
|
|
#if SUPPORT_MD5
|
|
MojoMd5_finish(&ctx->md5, sums->md5);
|
|
#endif
|
|
#if SUPPORT_SHA1
|
|
MojoSha1_finish(&ctx->sha1, sums->sha1);
|
|
#endif
|
|
} // MojoChecksum_finish
|
|
|
|
|
|
boolean cmdline(const char *arg)
|
|
{
|
|
int argc = GArgc;
|
|
const char **argv = GArgv;
|
|
int i;
|
|
|
|
if ((arg == NULL) || (argv == NULL))
|
|
return false;
|
|
|
|
while (*arg == '-') // Skip all '-' chars, so "--nosound" == "-nosound"
|
|
arg++;
|
|
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
const char *thisarg = argv[i];
|
|
if (*thisarg != '-')
|
|
continue; // no dash in the string, skip it.
|
|
while (*(++thisarg) == '-') { /* keep looping. */ }
|
|
if (strcmp(arg, thisarg) == 0)
|
|
return true;
|
|
} // for
|
|
|
|
return false;
|
|
} // cmdline
|
|
|
|
|
|
const char *cmdlinestr(const char *arg, const char *envr, const char *deflt)
|
|
{
|
|
uint32 len = 0;
|
|
int argc = GArgc;
|
|
const char **argv = GArgv;
|
|
int i;
|
|
|
|
if (envr != NULL)
|
|
{
|
|
const char *val = getenv(envr);
|
|
if (val != NULL)
|
|
return val;
|
|
} // if
|
|
|
|
if (arg == NULL)
|
|
return deflt;
|
|
|
|
while (*arg == '-') // Skip all '-' chars, so "--nosound" == "-nosound"
|
|
arg++;
|
|
|
|
len = strlen(arg);
|
|
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
const char *thisarg = argv[i];
|
|
if (*thisarg != '-')
|
|
continue; // no dash in the string, skip it.
|
|
|
|
while (*(++thisarg) == '-') { /* keep looping. */ }
|
|
if (strncmp(arg, thisarg, len) != 0)
|
|
continue; // not us.
|
|
|
|
thisarg += len; // skip ahead in string to end of match.
|
|
|
|
if (*thisarg == '=') // --a=b format.
|
|
return (thisarg + 1);
|
|
else if (*thisarg == '\0') // --a b format.
|
|
return ((argv[i+1] == NULL) ? deflt : argv[i+1]);
|
|
} // for
|
|
|
|
return deflt;
|
|
} // cmdlinestr
|
|
|
|
|
|
boolean wildcardMatch(const char *str, const char *pattern)
|
|
{
|
|
char sch = *(str++);
|
|
while (true)
|
|
{
|
|
const char pch = *(pattern++);
|
|
if (pch == '?')
|
|
{
|
|
if ((sch == '\0') || (sch == '/'))
|
|
return false;
|
|
sch = *(str++);
|
|
} // else if
|
|
|
|
else if (pch == '*')
|
|
{
|
|
char nextpch = *pattern;
|
|
if ((nextpch != '?') && (nextpch != '*'))
|
|
{
|
|
while ((sch != '\0') && (sch != nextpch))
|
|
sch = *(str++);
|
|
} // if
|
|
} // else if
|
|
|
|
else
|
|
{
|
|
if (pch != sch)
|
|
return false;
|
|
else if (pch == '\0')
|
|
break;
|
|
sch = *(str++);
|
|
} // else
|
|
} // while
|
|
|
|
return true;
|
|
} // wildcardMatch
|
|
|
|
|
|
const char *numstr(int val)
|
|
{
|
|
static int pos = 0;
|
|
char *ptr = ((char *) scratchbuf_128k) + (pos * 128);
|
|
snprintf(ptr, 128, "%d", val);
|
|
pos = (pos + 1) % 1000;
|
|
return ptr;
|
|
} // numstr
|
|
|
|
|
|
static char *format_internal(const char *fmt, va_list ap)
|
|
{
|
|
// This is kinda nasty. String manipulation in C always is.
|
|
char *retval = NULL;
|
|
const char *strs[10]; // 0 through 9.
|
|
const char *ptr = NULL;
|
|
char *wptr = NULL;
|
|
size_t len = 0;
|
|
int maxfmtid = -2;
|
|
int i;
|
|
|
|
// figure out what this format string contains...
|
|
for (ptr = fmt; *ptr; ptr++)
|
|
{
|
|
if (*ptr == '%')
|
|
{
|
|
const char ch = *(++ptr);
|
|
if (ch == '%') // a literal '%'
|
|
maxfmtid = (maxfmtid == -2) ? -1 : maxfmtid;
|
|
else if ((ch >= '0') && (ch <= '9'))
|
|
maxfmtid = ((maxfmtid > (ch - '0')) ? maxfmtid : (ch - '0'));
|
|
else
|
|
fatal(_("BUG: Invalid format() string"));
|
|
} // if
|
|
} // while
|
|
|
|
if (maxfmtid == -2) // no formatters present at all.
|
|
return xstrdup(fmt); // just copy it, we're done.
|
|
|
|
for (i = 0; i <= maxfmtid; i++) // referenced varargs --> linear array.
|
|
{
|
|
strs[i] = va_arg(ap, const char *);
|
|
if (strs[i] == NULL)
|
|
strs[i] = "(null)"; // just to match sprintf() behaviour...
|
|
} // for
|
|
|
|
// allocate the string we'll need in one shot, so we don't have to resize.
|
|
for (ptr = fmt; *ptr; ptr++)
|
|
{
|
|
if (*ptr != '%')
|
|
len++;
|
|
else
|
|
{
|
|
const char ch = *(++ptr);
|
|
if (ch == '%') // a literal '%'
|
|
len++; // just want '%' char.
|
|
else //if ((ch >= '0') && (ch <= '9'))
|
|
len += strlen(strs[ch - '0']);
|
|
} // else
|
|
} // while
|
|
|
|
// Now write the formatted string...
|
|
wptr = retval = (char *) xmalloc(len+1);
|
|
for (ptr = fmt; *ptr; ptr++)
|
|
{
|
|
const char strch = *ptr;
|
|
if (strch != '%')
|
|
*(wptr++) = strch;
|
|
else
|
|
{
|
|
const char ch = *(++ptr);
|
|
if (ch == '%') // a literal '%'
|
|
*(wptr++) = '%';
|
|
else //if ((ch >= '0') && (ch <= '9'))
|
|
{
|
|
const char *str = strs[ch - '0'];
|
|
strcpy(wptr, str);
|
|
wptr += strlen(str);
|
|
} // else
|
|
} // else
|
|
} // while
|
|
*wptr = '\0';
|
|
|
|
return retval;
|
|
} // format_internal
|
|
|
|
|
|
char *format(const char *fmt, ...)
|
|
{
|
|
char *retval = NULL;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
retval = format_internal(fmt, ap);
|
|
va_end(ap);
|
|
return retval;
|
|
} // format
|
|
|
|
|
|
#if ((defined _NDEBUG) || (defined NDEBUG))
|
|
#define DEFLOGLEV "info"
|
|
#else
|
|
#define DEFLOGLEV "everything"
|
|
#endif
|
|
MojoSetupLogLevel MojoLog_logLevel = MOJOSETUP_LOG_EVERYTHING;
|
|
static void *logFile = NULL;
|
|
|
|
void MojoLog_initLogging(void)
|
|
{
|
|
const char *level = cmdlinestr("loglevel","MOJOSETUP_LOGLEVEL", DEFLOGLEV);
|
|
const char *fname = cmdlinestr("log", "MOJOSETUP_LOG", NULL);
|
|
|
|
if (strcmp(level, "nothing") == 0)
|
|
MojoLog_logLevel = MOJOSETUP_LOG_NOTHING;
|
|
else if (strcmp(level, "errors") == 0)
|
|
MojoLog_logLevel = MOJOSETUP_LOG_ERRORS;
|
|
else if (strcmp(level, "warnings") == 0)
|
|
MojoLog_logLevel = MOJOSETUP_LOG_WARNINGS;
|
|
else if (strcmp(level, "info") == 0)
|
|
MojoLog_logLevel = MOJOSETUP_LOG_INFO;
|
|
else if (strcmp(level, "debug") == 0)
|
|
MojoLog_logLevel = MOJOSETUP_LOG_DEBUG;
|
|
else // Unknown string gets everything...that'll teach you.
|
|
MojoLog_logLevel = MOJOSETUP_LOG_EVERYTHING;
|
|
|
|
if ((fname != NULL) && (strcmp(fname, "-") == 0))
|
|
logFile = MojoPlatform_stdout();
|
|
else if (fname != NULL)
|
|
{
|
|
const uint32 flags = MOJOFILE_WRITE|MOJOFILE_CREATE|MOJOFILE_TRUNCATE;
|
|
const uint16 mode = MojoPlatform_defaultFilePerms();
|
|
logFile = MojoPlatform_open(fname, flags, mode);
|
|
} // if
|
|
} // MojoLog_initLogging
|
|
|
|
|
|
void MojoLog_deinitLogging(void)
|
|
{
|
|
if (logFile != NULL)
|
|
{
|
|
MojoPlatform_close(logFile);
|
|
logFile = NULL;
|
|
} // if
|
|
} // MojoLog_deinitLogging
|
|
|
|
|
|
static inline void addLog(MojoSetupLogLevel level, char levelchar,
|
|
const char *fmt, va_list ap)
|
|
{
|
|
if (level <= MojoLog_logLevel)
|
|
{
|
|
char *str = format_internal(fmt, ap);
|
|
//int len = vsnprintf(buf + 2, sizeof (buf) - 2, fmt, ap) + 2;
|
|
//buf[0] = levelchar;
|
|
//buf[1] = ' ';
|
|
int len = strlen(str);
|
|
while ( (--len >= 0) && ((str[len] == '\n') || (str[len] == '\r')) ) {}
|
|
str[len+1] = '\0'; // delete trailing newline crap.
|
|
MojoPlatform_log(str);
|
|
if (logFile != NULL)
|
|
{
|
|
const char *endl = MOJOPLATFORM_ENDLINE;
|
|
MojoPlatform_write(logFile, str, strlen(str));
|
|
MojoPlatform_write(logFile, endl, strlen(endl));
|
|
MojoPlatform_flush(logFile);
|
|
} // if
|
|
free(str);
|
|
} // if
|
|
} // addLog
|
|
|
|
|
|
void logWarning(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
addLog(MOJOSETUP_LOG_WARNINGS, '-', fmt, ap);
|
|
va_end(ap);
|
|
} // logWarning
|
|
|
|
|
|
void logError(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
addLog(MOJOSETUP_LOG_ERRORS, '!', fmt, ap);
|
|
va_end(ap);
|
|
} // logError
|
|
|
|
|
|
void logInfo(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
addLog(MOJOSETUP_LOG_INFO, '*', fmt, ap);
|
|
va_end(ap);
|
|
} // logInfo
|
|
|
|
|
|
void logDebug(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
addLog(MOJOSETUP_LOG_DEBUG, '#', fmt, ap);
|
|
va_end(ap);
|
|
} // logDebug
|
|
|
|
|
|
uint32 profile(const char *what, uint32 start_time)
|
|
{
|
|
uint32 retval = MojoPlatform_ticks() - start_time;
|
|
if (what != NULL)
|
|
logDebug("%0 took %1 ms.", what, numstr((int) retval));
|
|
return retval;
|
|
} // profile_start
|
|
|
|
|
|
int fatal(const char *fmt, ...)
|
|
{
|
|
static boolean in_fatal = false;
|
|
|
|
if (in_fatal)
|
|
return panic("BUG: fatal() called more than once!");
|
|
|
|
in_fatal = true;
|
|
|
|
// may not want to show a message, since we displayed one elsewhere, etc.
|
|
if (fmt != NULL)
|
|
{
|
|
char *buf = NULL;
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
buf = format_internal(fmt, ap);
|
|
va_end(ap);
|
|
|
|
logError("FATAL: %0", buf);
|
|
|
|
if (GGui != NULL)
|
|
GGui->msgbox(_("Fatal error"), buf);
|
|
|
|
free(buf);
|
|
} // if
|
|
|
|
// Shouldn't call fatal() before app is initialized!
|
|
if ( (GGui == NULL) || (!MojoLua_initialized()) )
|
|
panic("fatal() called before app is initialized! Panicking...");
|
|
|
|
MojoLua_callProcedure("revertinstall");
|
|
|
|
deinitEverything();
|
|
exit(23);
|
|
return 0;
|
|
} // fatal
|
|
|
|
|
|
int panic(const char *err)
|
|
{
|
|
static int panic_runs = 0;
|
|
|
|
panic_runs++;
|
|
if (panic_runs == 1)
|
|
{
|
|
logError("PANIC: %0", err);
|
|
panic(err);
|
|
} // if
|
|
|
|
else if (panic_runs == 2)
|
|
{
|
|
boolean domsgbox = ((GGui != NULL) && (GGui->msgbox != NULL));
|
|
if (domsgbox)
|
|
GGui->msgbox(_("PANIC"), err);
|
|
|
|
if ((GGui != NULL) && (GGui->deinit != NULL))
|
|
GGui->deinit();
|
|
|
|
if (!domsgbox)
|
|
panic(err); /* no GUI plugin...double-panic. */
|
|
} // if
|
|
|
|
else if (panic_runs == 3) // no GUI or panic panicked...write to stderr...
|
|
fprintf(stderr, "\n\n\n%s\n %s\n\n\n", _("PANIC"), err);
|
|
|
|
else // panic is panicking in a loop, terminate without any cleanup...
|
|
MojoPlatform_die();
|
|
|
|
exit(22);
|
|
return 0; // shouldn't hit this.
|
|
} // panic
|
|
|
|
|
|
char *xstrncpy(char *dst, const char *src, size_t len)
|
|
{
|
|
snprintf(dst, len, "%s", src);
|
|
return dst;
|
|
} // xstrncpy
|
|
|
|
|
|
uint32 utf8codepoint(const char **_str)
|
|
{
|
|
const char *str = *_str;
|
|
uint32 retval = 0;
|
|
uint32 octet = (uint32) ((uint8) *str);
|
|
uint32 octet2, octet3, octet4;
|
|
|
|
if (octet == 0) // null terminator, end of string.
|
|
return 0;
|
|
|
|
else if (octet < 128) // one octet char: 0 to 127
|
|
{
|
|
(*_str)++; // skip to next possible start of codepoint.
|
|
return octet;
|
|
} // else if
|
|
|
|
else if ((octet > 127) && (octet < 192)) // bad (starts with 10xxxxxx).
|
|
{
|
|
// Apparently each of these is supposed to be flagged as a bogus
|
|
// char, instead of just resyncing to the next valid codepoint.
|
|
(*_str)++; // skip to next possible start of codepoint.
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
} // else if
|
|
|
|
else if (octet < 224) // two octets
|
|
{
|
|
(*_str)++; // advance at least one byte in case of an error
|
|
octet -= (128+64);
|
|
octet2 = (uint32) ((uint8) *(++str));
|
|
if ((octet2 & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
*_str += 1; // skip to next possible start of codepoint.
|
|
retval = ((octet << 6) | (octet2 - 128));
|
|
if ((retval >= 0x80) && (retval <= 0x7FF))
|
|
return retval;
|
|
} // else if
|
|
|
|
else if (octet < 240) // three octets
|
|
{
|
|
(*_str)++; // advance at least one byte in case of an error
|
|
octet -= (128+64+32);
|
|
octet2 = (uint32) ((uint8) *(++str));
|
|
if ((octet2 & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
octet3 = (uint32) ((uint8) *(++str));
|
|
if ((octet3 & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
*_str += 2; // skip to next possible start of codepoint.
|
|
retval = ( ((octet << 12)) | ((octet2-128) << 6) | ((octet3-128)) );
|
|
|
|
// There are seven "UTF-16 surrogates" that are illegal in UTF-8.
|
|
switch (retval)
|
|
{
|
|
case 0xD800:
|
|
case 0xDB7F:
|
|
case 0xDB80:
|
|
case 0xDBFF:
|
|
case 0xDC00:
|
|
case 0xDF80:
|
|
case 0xDFFF:
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
} // switch
|
|
|
|
// 0xFFFE and 0xFFFF are illegal, too, so we check them at the edge.
|
|
if ((retval >= 0x800) && (retval <= 0xFFFD))
|
|
return retval;
|
|
} // else if
|
|
|
|
else if (octet < 248) // four octets
|
|
{
|
|
(*_str)++; // advance at least one byte in case of an error
|
|
octet -= (128+64+32+16);
|
|
octet2 = (uint32) ((uint8) *(++str));
|
|
if ((octet2 & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
octet3 = (uint32) ((uint8) *(++str));
|
|
if ((octet3 & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
octet4 = (uint32) ((uint8) *(++str));
|
|
if ((octet4 & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
*_str += 3; // skip to next possible start of codepoint.
|
|
retval = ( ((octet << 18)) | ((octet2 - 128) << 12) |
|
|
((octet3 - 128) << 6) | ((octet4 - 128)) );
|
|
if ((retval >= 0x10000) && (retval <= 0x10FFFF))
|
|
return retval;
|
|
} // else if
|
|
|
|
// Five and six octet sequences became illegal in rfc3629.
|
|
// We throw the codepoint away, but parse them to make sure we move
|
|
// ahead the right number of bytes and don't overflow the buffer.
|
|
|
|
else if (octet < 252) // five octets
|
|
{
|
|
(*_str)++; // advance at least one byte in case of an error
|
|
octet = (uint32) ((uint8) *(++str));
|
|
if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
octet = (uint32) ((uint8) *(++str));
|
|
if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
octet = (uint32) ((uint8) *(++str));
|
|
if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
octet = (uint32) ((uint8) *(++str));
|
|
if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
*_str += 4; // skip to next possible start of codepoint.
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
} // else if
|
|
|
|
else // six octets
|
|
{
|
|
(*_str)++; // advance at least one byte in case of an error
|
|
octet = (uint32) ((uint8) *(++str));
|
|
if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
octet = (uint32) ((uint8) *(++str));
|
|
if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
octet = (uint32) ((uint8) *(++str));
|
|
if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
octet = (uint32) ((uint8) *(++str));
|
|
if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
octet = (uint32) ((uint8) *(++str));
|
|
if ((octet & (128+64)) != 128) // Format isn't 10xxxxxx?
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
|
|
*_str += 5; // skip to next possible start of codepoint.
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
} // else if
|
|
|
|
return UNICODE_BOGUS_CHAR_VALUE;
|
|
} // utf8codepoint
|
|
|
|
|
|
int utf8len(const char *str)
|
|
{
|
|
int retval = 0;
|
|
while (utf8codepoint(&str))
|
|
retval++;
|
|
return retval;
|
|
} // utf8len
|
|
|
|
|
|
static char *strfrombuf(const char *text, int len)
|
|
{
|
|
char *retval = xmalloc(len + 1);
|
|
memcpy(retval, text, len);
|
|
retval[len] = '\0';
|
|
return retval;
|
|
} // strfrombuf
|
|
|
|
|
|
char **splitText(const char *text, int scrw, int *_count, int *_w)
|
|
{
|
|
int i = 0;
|
|
int j = 0;
|
|
char **retval = NULL;
|
|
int count = 0;
|
|
int w = 0;
|
|
|
|
*_count = *_w = 0;
|
|
while (*text)
|
|
{
|
|
const char *utf8text = text;
|
|
uint32 ch = 0;
|
|
int pos = 0;
|
|
int furthest = 0;
|
|
|
|
for (i = 0; ((ch = utf8codepoint(&utf8text))) && (i < scrw); i++)
|
|
{
|
|
if ((ch == '\r') || (ch == '\n'))
|
|
{
|
|
const char nextbyte = *utf8text;
|
|
count++;
|
|
retval = (char **) xrealloc(retval, count * sizeof (char *));
|
|
retval[count-1] = strfrombuf(text, utf8text - text);
|
|
if ((ch == '\r') && (nextbyte == '\n')) // DOS endlines!
|
|
utf8text++; // skip it.
|
|
text = (char *) utf8text; // update to start of new line.
|
|
|
|
if (i > w)
|
|
w = i;
|
|
i = -1; // will be zero on next iteration...
|
|
} // if
|
|
else if ((ch == ' ') || (ch == '\t'))
|
|
{
|
|
if (i != 0) // trim spaces from start of line...
|
|
furthest = i;
|
|
else
|
|
{
|
|
text++;
|
|
i = -1; // it'll be zero on next iteration.
|
|
} // else
|
|
} // else if
|
|
} // for
|
|
|
|
// line overflow or end of stream...
|
|
pos = (ch) ? furthest : i;
|
|
if ((ch) && (furthest == 0)) // uhoh, no split at all...hack it.
|
|
{
|
|
pos = utf8len(text);
|
|
if (pos > scrw) // too big, have to chop a string in the middle.
|
|
pos = scrw;
|
|
} // if
|
|
|
|
if (pos > 0)
|
|
{
|
|
utf8text = text; // adjust pointer by redecoding from start...
|
|
for (j = 0; j < pos; j++)
|
|
utf8codepoint(&utf8text);
|
|
|
|
count++;
|
|
retval = (char **) xrealloc(retval, count * sizeof (char*));
|
|
retval[count-1] = strfrombuf(text, utf8text - text);
|
|
text = (char *) utf8text;
|
|
if (pos > w)
|
|
w = pos;
|
|
} // if
|
|
} // while
|
|
|
|
*_count = count;
|
|
*_w = w;
|
|
return retval;
|
|
} // splitText
|
|
|
|
|
|
static void outOfMemory(void)
|
|
{
|
|
// Try to translate "out of memory", but not if it causes recursion.
|
|
static boolean already_panicked = false;
|
|
const char *errstr = "out of memory";
|
|
if (!already_panicked)
|
|
{
|
|
already_panicked = true;
|
|
errstr = translate(errstr);
|
|
} // if
|
|
panic(errstr);
|
|
} // outOfMemory
|
|
|
|
|
|
#undef calloc
|
|
void *xmalloc(size_t bytes)
|
|
{
|
|
void *retval = calloc(1, bytes);
|
|
if (retval == NULL)
|
|
outOfMemory();
|
|
return retval;
|
|
} // xmalloc
|
|
#define calloc(x,y) DO_NOT_CALL_CALLOC__USE_XMALLOC_INSTEAD
|
|
|
|
#undef realloc
|
|
void *xrealloc(void *ptr, size_t bytes)
|
|
{
|
|
void *retval = realloc(ptr, bytes);
|
|
if (retval == NULL)
|
|
outOfMemory();
|
|
return retval;
|
|
} // xrealloc
|
|
#define realloc(x,y) DO_NOT_CALL_REALLOC__USE_XREALLOC_INSTEAD
|
|
|
|
char *xstrdup(const char *str)
|
|
{
|
|
char *retval = (char *) xmalloc(strlen(str) + 1);
|
|
strcpy(retval, str);
|
|
return retval;
|
|
} // xstrdup
|
|
|
|
|
|
// We have to supply this function under certain build types.
|
|
#if MOJOSETUP_INTERNAL_BZLIB && BZ_NO_STDIO
|
|
void bz_internal_error(int errcode)
|
|
{
|
|
fatal(_("bzlib triggered an internal error: %0"), numstr(errcode));
|
|
} // bz_internal_error
|
|
#endif
|
|
|
|
|
|
#if SUPPORT_STBIMAGE
|
|
unsigned char *stbi_load_from_memory(unsigned char *buffer, int len, int *x,
|
|
int *y, int *comp, int req_comp);
|
|
#endif
|
|
|
|
uint8 *decodeImage(const uint8 *data, uint32 size, uint32 *w, uint32 *h)
|
|
{
|
|
uint8 *retval = MojoPlatform_decodeImage(data, size, w, h);
|
|
|
|
#if SUPPORT_STBIMAGE
|
|
if (retval == NULL) // try our built-in routines.
|
|
{
|
|
const int siz = (int) size;
|
|
unsigned char *buf = (unsigned char *) data;
|
|
int x = 0, y = 0, comp = 0;
|
|
retval = (uint8 *) stbi_load_from_memory(buf, siz, &x, &y, &comp, 4);
|
|
*w = (uint32) x;
|
|
*h = (uint32) y;
|
|
} // if
|
|
#endif
|
|
|
|
if (retval == NULL)
|
|
*w = *h = 0;
|
|
|
|
return retval;
|
|
} // decodeImage
|
|
|
|
|
|
boolean isValidProductKey(const char *fmt, const char *key)
|
|
{
|
|
if (fmt == NULL)
|
|
return true;
|
|
else if (key == NULL)
|
|
return false;
|
|
|
|
while (*fmt)
|
|
{
|
|
const char fmtch = *(fmt++);
|
|
const char keych = *(key++);
|
|
switch (fmtch)
|
|
{
|
|
case '-':
|
|
case ' ':
|
|
if ((keych == ' ') || (keych == '-'))
|
|
break;
|
|
key--; // user didn't type this, roll back.
|
|
break;
|
|
|
|
case '#':
|
|
if ((keych >= '0') && (keych <= '9'))
|
|
break;
|
|
return false;
|
|
|
|
case 'X':
|
|
if ( ((keych >= 'A') && (keych <= 'Z')) ||
|
|
((keych >= 'a') && (keych <= 'z')) )
|
|
break;
|
|
return false;
|
|
|
|
case '?':
|
|
if ( ((keych >= 'A') && (keych <= 'Z')) ||
|
|
((keych >= 'a') && (keych <= 'z')) ||
|
|
((keych >= '0') && (keych <= '9')) )
|
|
break;
|
|
return false;
|
|
|
|
case '*':
|
|
break;
|
|
|
|
default:
|
|
// this should have been caught by schema sanitize.
|
|
assert(false && "Invalid product key format.");
|
|
return false;
|
|
} // switch
|
|
} // while
|
|
|
|
return (*key == '\0');
|
|
} // isValidProductKey
|
|
|
|
|
|
// This is called from main()/WinMain()/whatever.
|
|
int MojoSetup_main(int argc, char **argv)
|
|
{
|
|
GArgc = argc;
|
|
GArgv = (const char **) argv;
|
|
|
|
if (cmdline("buildver"))
|
|
{
|
|
printf("%s\n", GBuildVer);
|
|
return 0;
|
|
} // if
|
|
|
|
#if TEST_LAUNCH_BROWSER_CODE
|
|
return MojoSetup_testLaunchBrowserCode(argc, argv);
|
|
#endif
|
|
|
|
#if TEST_ARCHIVE_CODE
|
|
return MojoSetup_testArchiveCode(argc, argv);
|
|
#endif
|
|
|
|
#if TEST_NETWORK_CODE
|
|
return MojoSetup_testNetworkCode(argc, argv);
|
|
#endif
|
|
|
|
if (!initEverything())
|
|
return 1;
|
|
|
|
// Jump into Lua for the heavy lifting.
|
|
MojoLua_runFile("mojosetup_mainline");
|
|
deinitEverything();
|
|
|
|
return 0;
|
|
} // MojoSetup_main
|
|
|
|
|
|
|
|
#if TEST_LAUNCH_BROWSER_CODE
|
|
int MojoSetup_testLaunchBrowserCode(int argc, char **argv)
|
|
{
|
|
int i;
|
|
|
|
if (!MojoArchive_initBaseArchive()) // Maybe need for xdg-open script.
|
|
panic("Initial setup failed. Cannot continue.");
|
|
|
|
printf("Testing browser launching code...\n\n");
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
const boolean rc = MojoPlatform_launchBrowser(argv[i]);
|
|
printf("Launch '%s': %s\n", argv[i], rc ? "success" : "failure");
|
|
} // for
|
|
|
|
MojoArchive_deinitBaseArchive();
|
|
return 0;
|
|
} // MojoSetup_testLaunchBrowserCode
|
|
#endif
|
|
|
|
|
|
#if TEST_ARCHIVE_CODE
|
|
int MojoSetup_testArchiveCode(int argc, char **argv)
|
|
{
|
|
int i;
|
|
printf("Testing archiver code...\n\n");
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
MojoArchive *archive = MojoArchive_newFromDirectory(argv[i]);
|
|
if (archive != NULL)
|
|
printf("directory '%s'...\n", argv[i]);
|
|
else
|
|
{
|
|
MojoInput *io = MojoInput_newFromFile(argv[i]);
|
|
if (io != NULL)
|
|
{
|
|
archive = MojoArchive_newFromInput(io, argv[i]);
|
|
if (archive != NULL)
|
|
printf("archive '%s'...\n", argv[i]);
|
|
} // if
|
|
} // else
|
|
|
|
if (archive == NULL)
|
|
fprintf(stderr, "Couldn't handle '%s'\n", argv[i]);
|
|
else
|
|
{
|
|
if (!archive->enumerate(archive))
|
|
fprintf(stderr, "enumerate() failed.\n");
|
|
else
|
|
{
|
|
const MojoArchiveEntry *ent;
|
|
while ((ent = archive->enumNext(archive)) != NULL)
|
|
{
|
|
printf("%s ", ent->filename);
|
|
if (ent->type == MOJOARCHIVE_ENTRY_FILE)
|
|
{
|
|
printf("(file, %d bytes, %o)\n",
|
|
(int) ent->filesize, ent->perms);
|
|
|
|
MojoInput *input = archive->openCurrentEntry(archive);
|
|
|
|
if(&archive->prevEnum != ent)
|
|
{
|
|
fprintf(stderr, "Address of MojoArchiveEntry pointer differs\n");
|
|
exit(EXIT_FAILURE);
|
|
} // if
|
|
|
|
if(!input)
|
|
{
|
|
fprintf(stderr, "Could not open current entry\n");
|
|
exit(EXIT_FAILURE);
|
|
} // if
|
|
|
|
if(!input->ready(input))
|
|
{
|
|
input->close(input);
|
|
continue;
|
|
} // if
|
|
|
|
uint64 pos = input->tell(input);
|
|
if(0 != pos)
|
|
{
|
|
fprintf(stderr, "position has to be 0 on start, is: %d\n", (int) pos);
|
|
exit(EXIT_FAILURE);
|
|
} // if
|
|
|
|
int64 filesize = input->length(input);
|
|
if(filesize != ent->filesize)
|
|
{
|
|
fprintf(stderr, "file size mismatch %d != %d\n",
|
|
(int) filesize, (int) ent->filesize);
|
|
exit(EXIT_FAILURE);
|
|
} // if
|
|
|
|
boolean ret = input->seek(input, filesize - 1);
|
|
if(!ret)
|
|
{
|
|
fprintf(stderr, "seek() has to return 'true'.\n");
|
|
exit(EXIT_FAILURE);
|
|
} // if
|
|
|
|
ret = input->seek(input, filesize);
|
|
if(ret)
|
|
{
|
|
fprintf(stderr, "seek() has to return 'false'.\n");
|
|
exit(EXIT_FAILURE);
|
|
} // if
|
|
|
|
pos = input->tell(input);
|
|
if(filesize -1 != pos)
|
|
{
|
|
fprintf(stderr, "position should be %d after seek(), is: %d\n",
|
|
(int) filesize - 1, (int) pos);
|
|
exit(EXIT_FAILURE);
|
|
} // if
|
|
|
|
input->close(input);
|
|
} // if
|
|
else if (ent->type == MOJOARCHIVE_ENTRY_DIR)
|
|
printf("(dir, %o)\n", ent->perms);
|
|
else if (ent->type == MOJOARCHIVE_ENTRY_SYMLINK)
|
|
printf("(symlink -> '%s')\n", ent->linkdest);
|
|
else
|
|
{
|
|
printf("(UNKNOWN?!, %d bytes, -> '%s', %o)\n",
|
|
(int) ent->filesize, ent->linkdest,
|
|
ent->perms);
|
|
} // else
|
|
} // while
|
|
} // else
|
|
archive->close(archive);
|
|
printf("\n\n");
|
|
} // else
|
|
} // for
|
|
|
|
return 0;
|
|
} // MojoSetup_testArchiveCode
|
|
#endif
|
|
|
|
|
|
#if TEST_NETWORK_CODE
|
|
int MojoSetup_testNetworkCode(int argc, char **argv)
|
|
{
|
|
int i;
|
|
fprintf(stderr, "Testing networking code...\n\n");
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
static char buf[64 * 1024];
|
|
uint32 start = 0;
|
|
const char *url = argv[i];
|
|
int64 length = -1;
|
|
int64 total_br = 0;
|
|
int64 br = 0;
|
|
printf("\n\nFetching '%s' ...\n", url);
|
|
MojoInput *io = MojoInput_newFromURL(url);
|
|
if (io == NULL)
|
|
{
|
|
fprintf(stderr, "failed!\n");
|
|
continue;
|
|
} // if
|
|
|
|
start = MojoPlatform_ticks();
|
|
while (!io->ready(io))
|
|
MojoPlatform_sleep(10);
|
|
fprintf(stderr, "took about %d ticks to get started\n",
|
|
(int) (MojoPlatform_ticks() - start));
|
|
|
|
length = io->length(io);
|
|
fprintf(stderr, "Ready to read (%lld) bytes.\n",
|
|
(long long) length);
|
|
|
|
do
|
|
{
|
|
start = MojoPlatform_ticks();
|
|
if (!io->ready(io))
|
|
{
|
|
fprintf(stderr, "Not ready!\n");
|
|
while (!io->ready(io))
|
|
MojoPlatform_sleep(10);
|
|
fprintf(stderr, "took about %d ticks to get ready\n",
|
|
(int) (MojoPlatform_ticks() - start));
|
|
} // if
|
|
|
|
start = MojoPlatform_ticks();
|
|
br = io->read(io, buf, sizeof (buf));
|
|
fprintf(stderr, "read blocked for about %d ticks\n",
|
|
(int) (MojoPlatform_ticks() - start));
|
|
if (br > 0)
|
|
{
|
|
total_br += br;
|
|
fprintf(stderr, "read %lld bytes\n", (long long) br);
|
|
fwrite(buf, br, 1, stdout);
|
|
} // if
|
|
} while (br > 0);
|
|
|
|
if (br < 0)
|
|
fprintf(stderr, "ERROR IN TRANSMISSION.\n\n");
|
|
else
|
|
{
|
|
fprintf(stderr, "TRANSMISSION COMPLETE!\n\n");
|
|
fprintf(stderr, "(Read %lld bytes, expected %lld.)\n",
|
|
(long long) total_br, length);
|
|
} // else
|
|
io->close(io);
|
|
} // for
|
|
|
|
return 0;
|
|
} // MojoSetup_testNetworkCode
|
|
#endif
|
|
|
|
// end of mojosetup.c ...
|
|
|