From c49fe5afe4e27582a70de23da13786c11e001793 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Fri, 1 May 2020 20:18:56 -0500 Subject: [PATCH] Crazy memory bugs fixed. Menu improved. --- .gitattributes | 1 + .gitignore | 1 + singe/Manual.docx | Bin 0 -> 57435 bytes singe/Manual.odt | 3 - singe/Menu.singe | 153 +++++++----- singe/buildPrerequisites.sh | 10 +- singe/frameFile.c | 2 + singe/main.c | 73 ++++-- singe/main.h | 6 +- singe/makePDFs.docbuilder | 3 + singe/singe.c | 473 +++++++++++++++++++++++------------- singe/singe.h | 1 + singe/singe.pro | 3 + singe/util.c | 12 +- 14 files changed, 480 insertions(+), 261 deletions(-) create mode 100644 singe/Manual.docx delete mode 100644 singe/Manual.odt create mode 100644 singe/makePDFs.docbuilder diff --git a/.gitattributes b/.gitattributes index f4d504ccb..e61059165 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,3 +3,4 @@ *.mp4 filter=lfs diff=lfs merge=lfs -text *.ttf filter=lfs diff=lfs merge=lfs -text *.odt filter=lfs diff=lfs merge=lfs -text +*.mpg filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index dd4a3b07e..b3b4be178 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +~* *~ *.log *.user diff --git a/singe/Manual.docx b/singe/Manual.docx new file mode 100644 index 0000000000000000000000000000000000000000..6d7e8c3e5fe45a5cce302d584477cbc04ea56bcb GIT binary patch literal 57435 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 literal 0 HcmV?d00001 diff --git a/singe/Manual.odt b/singe/Manual.odt deleted file mode 100644 index 2ece389e5..000000000 --- a/singe/Manual.odt +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1e736b38a360a6f5fe56e9dd5dbdc17922337a0043b2710329037e105440bf7f -size 13740 diff --git a/singe/Menu.singe b/singe/Menu.singe index da0333eda..0c38a37f2 100644 --- a/singe/Menu.singe +++ b/singe/Menu.singe @@ -71,38 +71,43 @@ end function onInputPressed(what) - if what == SWITCH_UP then - if TEXT_LINE_TOP > 1 then - TEXT_LINE_TOP = TEXT_LINE_TOP - 1 - end - end + -- Are we displaying the grid background? + if (discGetFrame() >= DISC_GRID_START) then - if what == SWITCH_DOWN then - if TEXT_LINE_TOP < TEXT_LINE_COUNT - TEXT_LINE_LIMIT + 1 then - TEXT_LINE_TOP = TEXT_LINE_TOP + 1 + if what == SWITCH_UP then + if TEXT_LINE_TOP > 1 then + TEXT_LINE_TOP = TEXT_LINE_TOP - 1 + end end - end - if what == SWITCH_LEFT then - GAME_SELECTED = GAME_SELECTED - 1 - if GAME_SELECTED < 1 then - GAME_SELECTED = GAME_COUNT + if what == SWITCH_DOWN then + if TEXT_LINE_TOP < TEXT_LINE_COUNT - TEXT_LINE_LIMIT + 1 then + TEXT_LINE_TOP = TEXT_LINE_TOP + 1 + end end - loadGameAssets(false) - end - if what == SWITCH_RIGHT then - GAME_SELECTED = GAME_SELECTED + 1 - if GAME_SELECTED > GAME_COUNT then - GAME_SELECTED = 1 + if what == SWITCH_LEFT then + GAME_SELECTED = GAME_SELECTED - 1 + if GAME_SELECTED < 1 then + GAME_SELECTED = GAME_COUNT + end + loadGameAssets(false) + end + + if what == SWITCH_RIGHT then + GAME_SELECTED = GAME_SELECTED + 1 + if GAME_SELECTED > GAME_COUNT then + GAME_SELECTED = 1 + end + loadGameAssets(false) + end + + if what == SWITCH_START1 or what == SWITCH_START2 or what == SWITCH_BUTTON1 or what == SWITCH_BUTTON2 or what == SWITCH_BUTTON3 or what == SWITCH_BUTTON4 then + -- Start next game + SHUTDOWN_FROM_PUSH = true + scriptPush(GAME_LIST[GAME_SELECTED]) end - loadGameAssets(false) - end - if what == SWITCH_START1 or what == SWITCH_START2 or what == SWITCH_BUTTON1 or what == SWITCH_BUTTON2 or what == SWITCH_BUTTON3 or what == SWITCH_BUTTON4 then - saveConfig() - -- Start next game - scriptPush(GAME_LIST[GAME_SELECTED]) end end @@ -117,46 +122,55 @@ function onOverlayUpdate() overlayClear() - -- Cabinet image - x = CABINET_X + (CABINET_W - spriteGetWidth(SPRITE_CABINET)) * 0.5 - y = CABINET_Y + (CABINET_H - spriteGetHeight(SPRITE_CABINET)) * 0.5 - spriteDraw(x, y, SPRITE_CABINET) + -- Are we displaying the grid background? + if (discGetFrame() >= DISC_GRID_START) then - -- Marquee Image - x = MARQUEE_X + (MARQUEE_W - spriteGetWidth(SPRITE_MARQUEE)) * 0.5 - y = MARQUEE_Y + (MARQUEE_H - spriteGetHeight(SPRITE_MARQUEE)) * 0.5 - spriteDraw(x, y, SPRITE_MARQUEE) + -- Cabinet image + x = CABINET_X + (CABINET_W - spriteGetWidth(SPRITE_CABINET)) * 0.5 + y = CABINET_Y + (CABINET_H - spriteGetHeight(SPRITE_CABINET)) * 0.5 + spriteDraw(x, y, SPRITE_CABINET) - -- Attract Mode Video - videoDraw(VIDEO_ATTRACT, VIDEO_X, VIDEO_Y, VIDEO_X + VIDEO_W, VIDEO_Y + VIDEO_H) - if videoGetFrame(VIDEO_ATTRACT) > GAME_LIST[GAME_SELECTED].ATTRACT_END then - videoSeek(VIDEO_ATTRACT, GAME_LIST[GAME_SELECTED].ATTRACT_START) + -- Marquee Image + x = MARQUEE_X + (MARQUEE_W - spriteGetWidth(SPRITE_MARQUEE)) * 0.5 + y = MARQUEE_Y + (MARQUEE_H - spriteGetHeight(SPRITE_MARQUEE)) * 0.5 + spriteDraw(x, y, SPRITE_MARQUEE) + + -- Attract Mode Video + videoDraw(VIDEO_ATTRACT, VIDEO_X, VIDEO_Y, VIDEO_X + VIDEO_W, VIDEO_Y + VIDEO_H) + if videoGetFrame(VIDEO_ATTRACT) > GAME_LIST[GAME_SELECTED].ATTRACT_END then + videoSeek(VIDEO_ATTRACT, GAME_LIST[GAME_SELECTED].ATTRACT_START) + end + + -- Game Description + colorForeground(255, 255, 255, 255) + y = TEXT_Y + c = 0 + t = 1 + for _, handle in ipairs(TEXT_SPRITE_LIST) do + -- Find height of font and number of lines that fit + if TEXT_LINE_HEIGHT == 0 then + if (handle >= 0) then + TEXT_LINE_HEIGHT = spriteGetHeight(handle) + TEXT_LINE_LIMIT = math.floor(TEXT_H / TEXT_LINE_HEIGHT) + end + end + -- Only display what is visible in the window + if (t >= TEXT_LINE_TOP) then + if (c < TEXT_LINE_LIMIT) then + if (handle >= 0) then + spriteDraw(TEXT_X, y, handle) + end + y = y + TEXT_LINE_HEIGHT + 1 + c = c + 1 + end + end + t = t + 1 + end end - -- Game Description - colorForeground(255, 255, 255, 255) - y = TEXT_Y - c = 0 - t = 1 - for _, handle in ipairs(TEXT_SPRITE_LIST) do - -- Find height of font and number of lines that fit - if TEXT_LINE_HEIGHT == 0 then - if (handle >= 0) then - TEXT_LINE_HEIGHT = spriteGetHeight(handle) - TEXT_LINE_LIMIT = math.floor(TEXT_H / TEXT_LINE_HEIGHT) - end - end - -- Only display what is visible in the window - if (t >= TEXT_LINE_TOP) then - if (c < TEXT_LINE_LIMIT) then - if (handle >= 0) then - spriteDraw(TEXT_X, y, handle) - end - y = y + TEXT_LINE_HEIGHT + 1 - c = c + 1 - end - end - t = t + 1 + -- Loop video? + if (discGetFrame() >= DISC_LAST_FRAME) then + discSkipToFrame(DISC_GRID_START) end return(OVERLAY_UPDATED) @@ -165,15 +179,16 @@ end function onShutdown() - saveConfig() + saveConfig(not SHUTDOWN_FROM_PUSH) end -function saveConfig() +function saveConfig(showIntro) if GAME_COUNT > 0 then -- Save what game we're currently viewing local cfg = io.open(CONFIG_FILE, "w") cfg:write("GAME_SELECTED = " .. GAME_SELECTED .. "\n") + cfg:write("SHOW_INTRO = " .. tostring(showIntro) .. "\n") cfg:close() end end @@ -267,13 +282,15 @@ if GAME_COUNT == 0 then debugPrint("No games found! Exiting.") singeQuit() else - discPlay() overlaySetResolution(vldpGetWidth(), vldpGetHeight()) freeSans18 = fontLoad("Singe/FreeSansBold.ttf", 18) fontQuality(FONT_QUALITY_BLENDED) fontSelect(freeSans18) + DISC_GRID_START = 180 + DISC_LAST_FRAME = 359 + MARGIN_X = 25 MARGIN_Y = 25 @@ -319,7 +336,10 @@ else TEXT_LINE_HEIGHT = 0 TEXT_SPRITE_LIST = {} + SHUTDOWN_FROM_PUSH = false + -- Load configuration + SHOW_INTRO = true CONFIG_FILE = singeGetDataPath() .. "menu.dat" local confattr = lfs.attributes(CONFIG_FILE) if confattr then @@ -331,6 +351,11 @@ else GAME_SELECTED = 1 end + discPlay() + if (not SHOW_INTRO) then + discSkipToFrame(DISC_GRID_START) + end + -- Prime the pump loadGameAssets(true) end diff --git a/singe/buildPrerequisites.sh b/singe/buildPrerequisites.sh index 59da2ea33..b2c171d3b 100755 --- a/singe/buildPrerequisites.sh +++ b/singe/buildPrerequisites.sh @@ -622,17 +622,19 @@ createEmbeddedBinary FreeSansBold.ttf FreeSansBold_ttf.h FREESANSBOLD_TTF_H # === Singe Menu Background Video === if [[ ! -f menuBackground_mkv.h ]]; then - ffmpeg -i 180503_01_PurpleGrid.mp4 -filter:v 'crop=ih/3*4:ih' -vf scale=720:480 -c:v libx264 -c:a copy menuBackground.mkv + ffmpeg -i "Singe Engine Intro.mpg" -filter:v 'crop=ih/3*4:ih' -vf scale=720:480 -c:v libx264 -c:a aac temp1.mkv + ffmpeg -i 180503_01_PurpleGrid.mp4 -filter:v 'crop=ih/3*4:ih' -vf scale=720:480 -c:v libx264 -c:a aac temp2.mkv + ffmpeg -f concat -safe 0 -i <(echo -e "file $PWD/temp1.mkv\nfile $PWD/temp2.mkv\n") -c copy menuBackground.mkv createEmbeddedBinary menuBackground.mkv menuBackground_mkv.h MENUBACKGROUND_MKV_H - rm menuBackground.mkv + rm temp1.mkv temp2.mkv # menuBackground.mkv fi popd # === Singe Manual === -libreoffice --headless "-env:UserInstallation=file:///tmp/LibreOffice_Conversion_${USER}" --convert-to pdf:writer_pdf_Export Manual.odt +#libreoffice --headless "-env:UserInstallation=file:///tmp/LibreOffice_Conversion_${USER}" --convert-to pdf:writer_pdf_Export Manual.odt createEmbeddedBinary Manual.pdf Manual_pdf.h MANUAL_H -rm Manual.pdf +#rm Manual.pdf # Clean Uo case "${G_PLATFORM}" in diff --git a/singe/frameFile.c b/singe/frameFile.c index 21bbde95b..e3d1943c2 100644 --- a/singe/frameFile.c +++ b/singe/frameFile.c @@ -192,6 +192,8 @@ int32_t frameFileLoad(char *filename, char *indexPath, bool stretchVideo, SDL_Re free(frameLine); } + free(data); + // Allocate new framefile frameFile = malloc(sizeof(FrameFileT)); frameFile->id = _nextId; diff --git a/singe/main.c b/singe/main.c index 2da962103..a3bb5b2d1 100644 --- a/singe/main.c +++ b/singe/main.c @@ -62,7 +62,7 @@ typedef struct ModeS { } ModeT; typedef struct QueueS { - ConfigT conf; + ConfigT *conf; struct QueueS *next; } QueueT; @@ -100,12 +100,48 @@ static ModeT _modes[] = { ConfigT *createConf(char *exeName, int argc, char *argv[]); -void destroyConf(ConfigT **confPointer); bool extractFile(char *filename, unsigned char *data, int32_t length); void launcher(char *exeName, ConfigT *conf); void showUsage(char *name, char *message); +ConfigT *cloneConf(ConfigT *conf) { + int32_t x; + ConfigT *c; + + c = (ConfigT *)calloc(1, sizeof(ConfigT)); + + // Deep copy conf manually to avoid optimizer issues + if (conf->scriptFile) c->scriptFile = strdup(conf->scriptFile); + if (conf->videoFile) c->videoFile = strdup(conf->videoFile); + if (conf->dataDirBase) c->dataDirBase = strdup(conf->dataDirBase); + if (conf->dataDir) c->dataDir = strdup(conf->dataDir); + c->resolutionWasCalculated = conf->resolutionWasCalculated; + c->isFrameFile = conf->isFrameFile; + c->stretchVideo = conf->stretchVideo; + c->noMouse = conf->noMouse; + c->noSound = conf->noSound; + c->fullScreen = conf->fullScreen; + c->fullScreenWindow = conf->fullScreenWindow; + c->showCalculated = conf->showCalculated; + c->noConsole = conf->noConsole; + c->noLogos = conf->noLogos; + c->tracing = conf->tracing; + c->bestRatioIndex = conf->bestRatioIndex; + c->volumeVldp = conf->volumeVldp; + c->volumeNonVldp = conf->volumeNonVldp; + c->scaleFactor = conf->scaleFactor; + c->xResolution = conf->xResolution; + c->yResolution = conf->yResolution; + c->sindenArgc = conf->sindenArgc; + for (x=0; xsindenArgv[x] = conf->sindenArgv[x]; + } + + return c; +} + + ConfigT *createConf(char *exeName, int argc, char *argv[]) { int32_t x = 0; int32_t argCount = 0; @@ -153,10 +189,11 @@ ConfigT *createConf(char *exeName, int argc, char *argv[]) { // Default configuration values conf = (ConfigT *)calloc(1, sizeof(ConfigT)); if (!conf) utilDie("Out of memory creating config."); - conf->bestRatioIndex = -1; - conf->volumeVldp = 100; - conf->volumeNonVldp = 100; - conf->scaleFactor = 100; + conf->bestRatioIndex = -1; + conf->volumeVldp = 100; + conf->volumeNonVldp = 100; + conf->scaleFactor = 100; + conf->resolutionWasCalculated = true; // Parse command line argCount = ap_arguments(&parser); @@ -173,6 +210,7 @@ ConfigT *createConf(char *exeName, int argc, char *argv[]) { case 'a': if (aspectString) free(aspectString); aspectString = strdup(arg); + conf->resolutionWasCalculated = false; argCount++; break; @@ -263,12 +301,14 @@ ConfigT *createConf(char *exeName, int argc, char *argv[]) { // X Resolution case 'x': conf->xResolution = atoi(arg); + conf->resolutionWasCalculated = false; argCount++; break; // Y Resolution case 'y': conf->yResolution = atoi(arg); + conf->resolutionWasCalculated = false; argCount++; break; @@ -424,11 +464,13 @@ ConfigT *createConf(char *exeName, int argc, char *argv[]) { void destroyConf(ConfigT **confPointer) { ConfigT *conf = *confPointer; - if (conf->dataDir) free(conf->dataDir); - if (conf->videoFile) free(conf->videoFile); - if (conf->scriptFile) free(conf->scriptFile); + if (conf->dataDir) free(conf->dataDir); + if (conf->dataDirBase) free(conf->dataDirBase); + if (conf->videoFile) free(conf->videoFile); + if (conf->scriptFile) free(conf->scriptFile); free(conf); + conf = NULL; } @@ -553,8 +595,8 @@ void launcher(char *exeName, ConfigT *conf) { // Init SDL_ttf if (TTF_Init() < 0) utilDie("%s", TTF_GetError()); - // Create Resizable Window - window = SDL_CreateWindow("SINGE", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, conf->xResolution, conf->yResolution, SDL_WINDOW_RESIZABLE); + // Create Window + window = SDL_CreateWindow("SINGE", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, conf->xResolution, conf->yResolution, 0 /* SDL_WINDOW_RESIZABLE */); if (window == NULL) utilDie("%s", SDL_GetError()); // Window Icon @@ -640,7 +682,7 @@ void queueScript(ConfigT *conf) { QueueT *q; q = (QueueT *)calloc(1, sizeof(QueueT)); - memcpy(&q->conf, conf, sizeof(ConfigT)); + q->conf = cloneConf(conf); LL_APPEND(_scriptQueue, q); } @@ -752,14 +794,15 @@ int main(int argc, char *argv[]) { // Queue initial script conf = createConf(exeName, argc, argv); queueScript(conf); + destroyConf(&conf); // Run script queue while (_scriptQueue) { q = _scriptQueue; - conf = (ConfigT *)&q->conf; + launcher(exeName, q->conf); + destroyConf(&q->conf); LL_DELETE(_scriptQueue, q); - launcher(exeName, conf); - destroyConf(&conf); + free(q); } #ifdef _WIN32 diff --git a/singe/main.h b/singe/main.h index 43dcd9624..50d8c8387 100644 --- a/singe/main.h +++ b/singe/main.h @@ -29,8 +29,10 @@ #include "singe.h" -bool parseSindenString(char **sindenStringPointer, ConfigT *conf); -void queueScript(ConfigT *conf); +ConfigT *cloneConf(ConfigT *conf); +void destroyConf(ConfigT **confPointer); +bool parseSindenString(char **sindenStringPointer, ConfigT *conf); +void queueScript(ConfigT *conf); #endif // MAIN_H diff --git a/singe/makePDFs.docbuilder b/singe/makePDFs.docbuilder new file mode 100644 index 000000000..092530621 --- /dev/null +++ b/singe/makePDFs.docbuilder @@ -0,0 +1,3 @@ +builder.OpenFile("Manual.docx"); +builder.SaveFile("pdf", "/tmp/Manual.pdf"); +builder.CloseFile(); diff --git a/singe/singe.c b/singe/singe.c index b9bed14e2..6ed0695bb 100644 --- a/singe/singe.c +++ b/singe/singe.c @@ -221,7 +221,7 @@ typedef struct GlobalS { FontT *fontList; FontT *fontCurrent; MappingT controlMappings[INPUT_COUNT]; - ConfigT conf; // Local copy of command line options + ConfigT *conf; // Local copy of command line options } GlobalT; @@ -272,8 +272,10 @@ int32_t apiMouseDisable(lua_State *L); int32_t apiMouseEnable(lua_State *L); int32_t apiMouseGetPosition(lua_State *L); int32_t apiMouseHowMany(lua_State *L); +int32_t apiMouseSetCaptured(lua_State *L); int32_t apiMouseSetMode(lua_State *L); +int32_t apiOverlayBox(lua_State *L); int32_t apiOverlayCircle(lua_State *L); int32_t apiOverlayClear(lua_State *L); int32_t apiOverlayEllipse(lua_State *L); @@ -338,12 +340,13 @@ void doIndexDisplay(int32_t percent); void doLogos(void); void callLua(const char *func, const char *sig, ...); void channelFinished(int channel); +void line(int32_t x1, int32_t y1, int32_t x2, int32_t y2, SDL_Color *c); void luaDie(lua_State *L, char *method, char *fmt, ...); int32_t luaError(lua_State *L); void luaPreload(lua_State *L, const char *name, lua_CFunction func); void luaTrace(lua_State *L, char *method, char *fmt, ...); void processKey(bool down, int keysym, int32_t scancode); -void putPixel(int32_t x, int32_t y); +void putPixel(int32_t x, int32_t y, SDL_Color *c); void startControllers(void); void startLuaContext(lua_State *L); void stopControllers(void); @@ -367,7 +370,7 @@ int32_t apiColorBackground(lua_State *L) { d = lua_tonumber(L, 2); _global.colorBackground.g = (byte)d; d = lua_tonumber(L, 3); _global.colorBackground.b = (byte)d; if (n == 3) { - _global.colorBackground.a = (byte)255; + _global.colorBackground.a = (byte)0; // Default to transparent. } else { if (lua_isnumber(L, 4)) { d = lua_tonumber(L, 4); _global.colorBackground.a = (byte)d; @@ -404,7 +407,7 @@ int32_t apiColorForeground(lua_State *L) { d = lua_tonumber(L, 2); _global.colorForeground.g = (byte)d; d = lua_tonumber(L, 3); _global.colorForeground.b = (byte)d; if (n == 3) { - _global.colorForeground.a = (byte)255; + _global.colorForeground.a = (byte)255; // Default to opaque. } else { if (lua_isnumber(L, 4)) { d = lua_tonumber(L, 4); _global.colorForeground.a = (byte)d; @@ -517,8 +520,8 @@ int32_t apiDiscAudio(lua_State *L) { d = lua_tonumber(L, 1); channel = (int32_t)d; d = lua_toboolean(L, 2); onOff = (bool)d; videoGetVolume(_global.videoHandle, &left, &right); - if (channel == 1) left = (onOff ? _global.conf.volumeVldp : 0); - if (channel == 2) right = (onOff ? _global.conf.volumeVldp : 0); + if (channel == 1) left = (onOff ? _global.conf->volumeVldp : 0); + if (channel == 2) right = (onOff ? _global.conf->volumeVldp : 0); videoSetVolume(_global.videoHandle, left, right); result = true; } @@ -548,7 +551,7 @@ int32_t apiDiscGetFrame(lua_State *L) { int64_t frame = 0; if (!_global.discStopped) { - if (_global.conf.isFrameFile) { + if (_global.conf->isFrameFile) { frame = frameFileGetFrame(_global.frameFileHandle, _global.videoHandle); } else { if (_global.videoHandle >= 0) frame = videoGetFrame(_global.videoHandle); @@ -601,7 +604,7 @@ int32_t apiDiscSearch(lua_State *L) { if (n == 1) { if (lua_isnumber(L, 1)) { d = lua_tonumber(L, 1); frame = (int64_t)d; - if (_global.conf.isFrameFile) { + if (_global.conf->isFrameFile) { frameFileSeek(_global.frameFileHandle, frame, &_global.videoHandle, &aFrame); } else { if (_global.videoHandle >= 0) videoSeek(_global.videoHandle, frame); @@ -652,7 +655,7 @@ int32_t apiDiscSkipBackward(lua_State *L) { if (lua_isnumber(L, 1)) { d = lua_tonumber(L, 1); if (_global.videoHandle >= 0) frame = videoGetFrame(_global.videoHandle) - (int64_t)d; - if (_global.conf.isFrameFile) { + if (_global.conf->isFrameFile) { frameFileSeek(_global.frameFileHandle, frame, &_global.videoHandle, &aFrame); } else { if (_global.videoHandle >= 0) videoSeek(_global.videoHandle, frame); @@ -696,7 +699,7 @@ int32_t apiDiscSkipForward(lua_State *L) { if (n == 1) { if (lua_isnumber(L, 1)) { d = lua_tonumber(L, 1); if (_global.videoHandle >= 0) frame = videoGetFrame(_global.videoHandle) + (int64_t)d; - if (_global.conf.isFrameFile) { + if (_global.conf->isFrameFile) { frameFileSeek(_global.frameFileHandle, frame, &_global.videoHandle, &aFrame); } else { if (_global.videoHandle >= 0) videoSeek(_global.videoHandle, frame); @@ -731,7 +734,7 @@ int32_t apiDiscSkipToFrame(lua_State *L) { if (n == 1) { if (lua_isnumber(L, 1)) { d = lua_tonumber(L, 1); frame = (int64_t)d; - if (_global.conf.isFrameFile) { + if (_global.conf->isFrameFile) { frameFileSeek(_global.frameFileHandle, frame, &_global.videoHandle, &aFrame); } else { if (_global.videoHandle >= 0) videoSeek(_global.videoHandle, frame); @@ -761,7 +764,7 @@ int32_t apiDiscStepBackward(lua_State *L) { // No matter disc state, go back a frame. If playing, pause. if (_global.videoHandle >= 0) { - if (_global.conf.isFrameFile) { + if (_global.conf->isFrameFile) { frame = frameFileGetFrame(_global.frameFileHandle, _global.videoHandle) - 1; frameFileSeek(_global.frameFileHandle, frame, &_global.videoHandle, &aFrame); } else { @@ -785,7 +788,7 @@ int32_t apiDiscStepForward(lua_State *L) { // No matter disc state, go forward a frame. If playing, pause. if (_global.videoHandle >= 0) { - if (_global.conf.isFrameFile) { + if (_global.conf->isFrameFile) { frame = frameFileGetFrame(_global.frameFileHandle, _global.videoHandle) + 1; frameFileSeek(_global.frameFileHandle, frame, &_global.videoHandle, &aFrame); } else { @@ -1044,6 +1047,59 @@ int32_t apiFontToSprite(lua_State *L) { } +int32_t apiOverlayBox(lua_State *L) { + int32_t n = lua_gettop(L); + int32_t x1 = 0; + int32_t y1 = 0; + int32_t x2 = 0; + int32_t y2 = 0; + //SDL_Rect r; + double d = 0; + bool result = false; + + if (n == 4) { + if (lua_isnumber(L, 1)) { + if (lua_isnumber(L, 2)) { + if (lua_isnumber(L, 3)) { + if (lua_isnumber(L, 4)) { + d = lua_tonumber(L, 1); x1 = (int32_t)d; + d = lua_tonumber(L, 2); y1 = (int32_t)d; + d = lua_tonumber(L, 3); x2 = (int32_t)d; + d = lua_tonumber(L, 4); y2 = (int32_t)d; + + /* + r.x = x1; + r.y = y1; + r.w = abs(x2 - x1) + 1; + r.h = abs(y2 - y1) + 1; + */ + + SDL_LockSurface(_global.overlay); + //***TODO*** No filling until I can find an efficient way to blend individual pixels. + //SDL_FillRect(_global.overlay, &r, SDL_MapRGBA(_global.overlay->format, _global.colorBackground.r, _global.colorBackground.g, _global.colorBackground.b, _global.colorBackground.a)); + line(x1, y1, x2, y1, &_global.colorForeground); + line(x2, y1, x2, y2, &_global.colorForeground); + line(x2, y2, x1, y2, &_global.colorForeground); + line(x1, y2, x1, y1, &_global.colorForeground); + SDL_UnlockSurface(_global.overlay); + result = true; + } + } + } + } + } + + if (result) { + luaTrace(L, "overlayBox", "%d %d %d %d", x1, y1, x2, y2); + } else { + luaDie(L, "overlayBox", "Failed!"); + } + + return 0; + +} + + int32_t apiOverlayCircle(lua_State *L) { int32_t n = lua_gettop(L); @@ -1075,14 +1131,14 @@ int32_t apiOverlayCircle(lua_State *L) { SDL_LockSurface(_global.overlay); while (x >= y) { - putPixel(x0 + x, y0 + y); - putPixel(x0 + y, y0 + x); - putPixel(x0 - y, y0 + x); - putPixel(x0 - x, y0 + y); - putPixel(x0 - x, y0 - y); - putPixel(x0 - y, y0 - x); - putPixel(x0 + y, y0 - x); - putPixel(x0 + x, y0 - y); + putPixel(x0 + x, y0 + y, &_global.colorForeground); + putPixel(x0 + y, y0 + x, &_global.colorForeground); + putPixel(x0 - y, y0 + x, &_global.colorForeground); + putPixel(x0 - x, y0 + y, &_global.colorForeground); + putPixel(x0 - x, y0 - y, &_global.colorForeground); + putPixel(x0 - y, y0 - x, &_global.colorForeground); + putPixel(x0 + y, y0 - x, &_global.colorForeground); + putPixel(x0 + x, y0 - y, &_global.colorForeground); if (err <= 0) { y++; @@ -1178,10 +1234,10 @@ int32_t apiOverlayEllipse(lua_State *L) { b1 = 8 * b * b; do { - putPixel(x1, y0); // I. Quadrant - putPixel(x0, y0); // II. Quadrant - putPixel(x0, y1); // III. Quadrant - putPixel(x1, y1); // IV. Quadrant + putPixel(x1, y0, &_global.colorForeground); // I. Quadrant + putPixel(x0, y0, &_global.colorForeground); // II. Quadrant + putPixel(x0, y1, &_global.colorForeground); // III. Quadrant + putPixel(x1, y1, &_global.colorForeground); // IV. Quadrant e2 = 2 * err; if (e2 <= dy) { // y step y0++; @@ -1196,10 +1252,10 @@ int32_t apiOverlayEllipse(lua_State *L) { } while (x0 <= x1); while (y0-y1 < b) { // too early stop of flat ellipses a = 1 - putPixel(x0-1, y0); // -> finish tip of ellipse - putPixel(x1+1, y0++); - putPixel(x0-1, y1); - putPixel(x1+1, y1--); + putPixel(x0-1, y0, &_global.colorForeground); // -> finish tip of ellipse + putPixel(x1+1, y0++, &_global.colorForeground); + putPixel(x0-1, y1, &_global.colorForeground); + putPixel(x1+1, y1--, &_global.colorForeground); } SDL_UnlockSurface(_global.overlay); @@ -1241,13 +1297,6 @@ int32_t apiOverlayLine(lua_State *L) { int32_t y1 = 0; int32_t x2 = 0; int32_t y2 = 0; - int32_t x = 0; - int32_t y = 0; - int32_t dx = 0; - int32_t dy = 0; - int32_t incX = 0; - int32_t incY = 0; - int32_t balance = 0; double d = 0; bool result = false; @@ -1262,56 +1311,7 @@ int32_t apiOverlayLine(lua_State *L) { d = lua_tonumber(L, 4); y2 = (int32_t)d; SDL_LockSurface(_global.overlay); - - if (x2 >= x1) { - dx = x2 - x1; - incX = 1; - } else { - dx = x1 - x2; - incX = -1; - } - - if (y2 >= y1) { - dy = y2 - y1; - incY = 1; - } else { - dy = y1 - y2; - incY = -1; - } - - x = x1; - y = y1; - - if (dx >= dy) { - dy <<= 1; - balance = dy - dx; - dx <<= 1; - while (x != x2) { - putPixel(x, y); - if (balance >= 0) { - y += incY; - balance -= dx; - } - balance += dy; - x += incX; - } - putPixel(x, y); - } else { - dx <<= 1; - balance = dx - dy; - dy <<= 1; - while (y != y2) { - putPixel(x, y); - if (balance >= 0) { - x += incX; - balance -= dy; - } - balance += dx; - y += incY; - } - putPixel(x, y); - } - + line(x1, y1, x2, y2, &_global.colorForeground); SDL_UnlockSurface(_global.overlay); result = true; } @@ -1345,7 +1345,7 @@ int32_t apiOverlayPlot(lua_State *L) { d = lua_tonumber(L, 2); y1 = (int32_t)d; SDL_LockSurface(_global.overlay); - putPixel(x1, y1); + putPixel(x1, y1, &_global.colorForeground); SDL_UnlockSurface(_global.overlay); result = true; } @@ -1764,24 +1764,42 @@ int32_t apiSoundUnload(lua_State *L) { int32_t apiSpriteDraw(lua_State *L) { - int32_t n = lua_gettop(L); - int32_t id = -1; - double d = 0; - SpriteT *sprite = NULL; - SDL_Rect dest; + int32_t n = lua_gettop(L); + int32_t id = -1; + double d = 0; + SpriteT *sprite = NULL; + SDL_Rect dest; - if (n == 3) { + if ((n == 3) || (n == 5)) { if (lua_isnumber(L, 1)) { if (lua_isnumber(L, 2)) { if (lua_isnumber(L, 3)) { d = lua_tonumber(L, 1); dest.x = (int32_t)d; d = lua_tonumber(L, 2); dest.y = (int32_t)d; - d = lua_tonumber(L, 3); id = (int32_t)d; + if (n == 5) { + // Target is scaled + if (lua_isnumber(L, 4)) { + if (lua_isnumber(L, 5)) { + d = lua_tonumber(L, 3); dest.w = (int32_t)d - dest.x + 1; + d = lua_tonumber(L, 4); dest.h = (int32_t)d - dest.y + 1; + d = lua_tonumber(L, 5); id = (int32_t)d; + } + } + } else { + // Target is same size as sprite + d = lua_tonumber(L, 3); id = (int32_t)d; + } HASH_FIND_INT(_global.spriteList, &id, sprite); if (!sprite) luaDie(L, "spriteDraw", "No sprite at index %d in apiSpriteGetWidth.", id); - dest.w = sprite->surface->w; - dest.h = sprite->surface->h; - SDL_BlitSurface(sprite->surface, NULL, _global.overlay, &dest); + if (n == 5) { + // Target is scaled + SDL_BlitScaled(sprite->surface, NULL, _global.overlay, &dest); + } else { + // Target is same size as sprite + dest.w = sprite->surface->w; + dest.h = sprite->surface->h; + SDL_BlitSurface(sprite->surface, NULL, _global.overlay, &dest); + } } } } @@ -1934,8 +1952,8 @@ int32_t apiVideoDraw(lua_State *L) { d = lua_tonumber(L, 1); id = (int32_t)d; d = lua_tonumber(L, 2); dest.x = (int32_t)d; d = lua_tonumber(L, 3); dest.y = (int32_t)d; - d = lua_tonumber(L, 4); dest.w = (int32_t)d - dest.x; - d = lua_tonumber(L, 5); dest.h = (int32_t)d - dest.y; + d = lua_tonumber(L, 4); dest.w = (int32_t)d - dest.x + 1; + d = lua_tonumber(L, 5); dest.h = (int32_t)d - dest.y + 1; // Get our video structure HASH_FIND_INT(_global.videoList, &id, video); if (!video) luaDie(L, "videoDraw", "No video at index %d in apiVideoDraw.", id); @@ -2166,7 +2184,7 @@ int32_t apiVideoLoad(lua_State *L) { name = lua_tostring(L, 1); // Create data directory based on video path. temp = utilGetUpToLastPathComponent((char *)name); - data = utilCreateString("%s%s", _global.conf.dataDirBase, temp); + data = utilCreateString("%s%s", _global.conf->dataDirBase, temp); free(temp); temp = NULL; utilFixPathSeparators(&data, false); @@ -2470,8 +2488,8 @@ int32_t apiKeyboardSetMode(lua_State *L) { int32_t apiMouseEnable(lua_State *L) { // Enables mouse monitoring (void)L; - luaTrace(L, "mouseEnable", "%d", _global.conf.noMouse); - _global.mouseEnabled = (bool)!_global.conf.noMouse; + luaTrace(L, "mouseEnable", "%d", _global.conf->noMouse); + _global.mouseEnabled = (bool)!_global.conf->noMouse; return 0; } @@ -2485,6 +2503,36 @@ int32_t apiMouseDisable(lua_State *L) { } +int32_t apiMouseSetCaptured(lua_State *L) { + int32_t n = lua_gettop(L); + bool result = false; + + if (n == 1) { + if (lua_isboolean(L, 1)) { + _global.mouseGrabbed = (bool)lua_toboolean(L, 1); + if (_global.mouseGrabbed) { + // Grab mouse + SDL_SetWindowGrab(_global.window, SDL_TRUE); + SDL_ShowCursor(SDL_DISABLE); + } else { + // Ungrab mouse + SDL_SetWindowGrab(_global.window, SDL_FALSE); + SDL_ShowCursor(SDL_ENABLE); + } + result = true; + } + } + + if (result) { + luaTrace(L, "mouseSetCaptured", "%d", _global.mouseGrabbed); + } else { + luaDie(L, "mouseSetCaptured", "Failed!"); + } + + return 0; +} + + int32_t apiMouseSetMode(lua_State *L) { // Sets the scanning mode for mouse input. // Can be one of two values: @@ -2599,7 +2647,7 @@ int32_t apiScriptExecute(lua_State *L) { // Push next script. conf = buildConfFromTable(L); queueScript(conf); - free(conf); + destroyConf(&conf); // Stop this script running. _global.running = false; result = true; @@ -2626,9 +2674,9 @@ int32_t apiScriptPush(lua_State *L) { // Push next script. conf = buildConfFromTable(L); queueScript(conf); - free(conf); + destroyConf(&conf); // Push this script. - queueScript(&_global.conf); + queueScript(_global.conf); // Stop this script running. _global.running = false; result = true; @@ -2718,8 +2766,8 @@ int32_t apiSingeVersion(lua_State *L) { int32_t apiSingeGetDataPath(lua_State *L) { - luaTrace(L, "singeGetDataPath", "%s", _global.conf.dataDir); - lua_pushstring(L, _global.conf.dataDir); + luaTrace(L, "singeGetDataPath", "%s", _global.conf->dataDir); + lua_pushstring(L, _global.conf->dataDir); return 1; } @@ -2758,8 +2806,8 @@ int32_t apiSingeSetGameName(lua_State *L) { int32_t apiSingeGetScriptPath(lua_State *L) { - luaTrace(L, "singeGetScriptPath", "%s", _global.conf.scriptFile); - lua_pushstring(L, _global.conf.scriptFile); + luaTrace(L, "singeGetScriptPath", "%s", _global.conf->scriptFile); + lua_pushstring(L, _global.conf->scriptFile); return 1; } @@ -2773,12 +2821,7 @@ ConfigT *buildConfFromTable(lua_State *L) { ConfigT *c = NULL; // Start with current config. - c = (ConfigT *)calloc(1, sizeof(ConfigT)); - memcpy(c, &_global.conf, sizeof(ConfigT)); - if (_global.conf.scriptFile) c->scriptFile = strdup(_global.conf.scriptFile); - if (_global.conf.videoFile) c->videoFile = strdup(_global.conf.videoFile); - if (_global.conf.dataDirBase) c->dataDirBase = strdup(_global.conf.dataDirBase); - if (_global.conf.dataDir) c->dataDir = strdup(_global.conf.dataDir); + c = cloneConf(_global.conf); // Update with data in the table on the top of the Lua stack. lua_pushnil(L); @@ -3140,6 +3183,66 @@ void doLogos(void) { } +void line(int32_t x1, int32_t y1, int32_t x2, int32_t y2, SDL_Color *c) { + int32_t x = 0; + int32_t y = 0; + int32_t dx = 0; + int32_t dy = 0; + int32_t incX = 0; + int32_t incY = 0; + int32_t balance = 0; + + if (x2 >= x1) { + dx = x2 - x1; + incX = 1; + } else { + dx = x1 - x2; + incX = -1; + } + + if (y2 >= y1) { + dy = y2 - y1; + incY = 1; + } else { + dy = y1 - y2; + incY = -1; + } + + x = x1; + y = y1; + + if (dx >= dy) { + dy <<= 1; + balance = dy - dx; + dx <<= 1; + while (x != x2) { + putPixel(x, y, c); + if (balance >= 0) { + y += incY; + balance -= dx; + } + balance += dy; + x += incX; + } + putPixel(x, y, c); + } else { + dx <<= 1; + balance = dx - dy; + dy <<= 1; + while (y != y2) { + putPixel(x, y, c); + if (balance >= 0) { + x += incX; + balance -= dy; + } + balance += dx; + y += incY; + } + putPixel(x, y, c); + } +} + + void luaDie(lua_State *L, char *method, char *fmt, ...) { va_list args; lua_Debug ar; @@ -3297,12 +3400,12 @@ void processKey(bool down, int32_t keysym, int32_t scancode) { } -void putPixel(int32_t x, int32_t y) { +void putPixel(int32_t x, int32_t y, SDL_Color *c) { SDL_Surface *surface = _global.overlay; int32_t bpp = surface->format->BytesPerPixel; Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; - Uint32 pixel = SDL_MapRGBA(surface->format, _global.colorForeground.r, _global.colorForeground.g, _global.colorForeground.b, _global.colorForeground.a); + Uint32 pixel = SDL_MapRGBA(surface->format, c->r, c->g, c->b, c->a); if ((x < 0) || (x >= _global.overlay->w) || (y < 0) || (y >= _global.overlay->h)) return; @@ -3343,6 +3446,7 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { int64_t thisFrame = -1; int64_t lastFrame = -1; uint32_t frameClock = 0; + bool changed = false; char *temp = NULL; char *temp2 = NULL; SDL_Rect windowTarget; @@ -3382,12 +3486,8 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { _global.discStopped = true; _global.mouseEnabled = true; - // Deep copy conf - memcpy(&_global.conf, conf, sizeof(ConfigT)); - _global.conf.dataDir = strdup(conf->dataDir); - _global.conf.dataDirBase = strdup(conf->dataDirBase); - _global.conf.scriptFile = strdup(conf->scriptFile); - _global.conf.videoFile = strdup(conf->videoFile); + // Local copy of config + _global.conf = cloneConf(conf); // Input mappings _global.controlMappings[INPUT_UP].name = "INPUT_UP"; @@ -3431,20 +3531,21 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { if (luaL_loadbuffer(_global.luaContext, (char *)controls_cfg, controls_cfg_len, "Input Mappings") || lua_pcall(_global.luaContext, 0, 0, 0)) utilDie("%s", lua_tostring(_global.luaContext, -1)); if (utilFileExists("controls.cfg") && luaL_dofile(_global.luaContext, "controls.cfg")) utilDie("%s", lua_tostring(_global.luaContext, -1)); // Load mappings in main data dir - temp = utilCreateString("%s..%ccontrols.cfg", _global.conf.dataDir, utilGetPathSeparator()); + temp = utilCreateString("%s..%ccontrols.cfg", _global.conf->dataDir, utilGetPathSeparator()); if (utilFileExists(temp) && luaL_dofile(_global.luaContext, temp)) utilDie("%s", lua_tostring(_global.luaContext, -1)); free(temp); // Load mappings in game data dir - temp = utilCreateString("%scontrols.cfg", _global.conf.dataDir); + temp = utilCreateString("%scontrols.cfg", _global.conf->dataDir); if (utilFileExists(temp) && luaL_dofile(_global.luaContext, temp)) utilDie("%s", lua_tostring(_global.luaContext, -1)); free(temp); // Load mappings in game script dir - temp = strdup(_global.conf.scriptFile); + temp = strdup(_global.conf->scriptFile); temp2 = utilStrndup(temp, strlen(temp) - strlen(utilGetLastPathComponent(temp))); free(temp); temp = utilCreateString("%scontrols.cfg", temp2); if (utilFileExists(temp) && luaL_dofile(_global.luaContext, temp)) utilDie("%s", lua_tostring(_global.luaContext, -1)); free(temp); + free(temp2); // Parse results lua_getglobal(_global.luaContext, "DEAD_ZONE"); if (lua_isnumber(_global.luaContext, -1)) { @@ -3496,7 +3597,7 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { lua_close(_global.luaContext); // Show splash screens - if (!_global.conf.noLogos) { + if (!_global.conf->noLogos) { doLogos(); } @@ -3548,8 +3649,10 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { lua_register(_global.luaContext, "mouseDisable", apiMouseDisable); lua_register(_global.luaContext, "mouseGetPosition", apiMouseGetPosition); lua_register(_global.luaContext, "mouseHowMany", apiMouseHowMany); + lua_register(_global.luaContext, "mouseSetCaptured", apiMouseSetCaptured); lua_register(_global.luaContext, "mouseSetMode", apiMouseSetMode); + lua_register(_global.luaContext, "overlayBox", apiOverlayBox); lua_register(_global.luaContext, "overlayCircle", apiOverlayCircle); lua_register(_global.luaContext, "overlayClear", apiOverlayClear); lua_register(_global.luaContext, "overlayEllipse", apiOverlayEllipse); @@ -3615,39 +3718,61 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { // Open main video file doIndexDisplay(-1); videoSetIndexCallback(doIndexDisplay); - if (_global.conf.isFrameFile) { - _global.frameFileHandle = frameFileLoad(_global.conf.videoFile, _global.conf.dataDir, (bool)_global.conf.stretchVideo, _global.renderer, _global.conf.showCalculated); - if (_global.frameFileHandle < 0) utilDie("Unable to load framefile: %s", _global.conf.videoFile); + if (_global.conf->isFrameFile) { + _global.frameFileHandle = frameFileLoad(_global.conf->videoFile, _global.conf->dataDir, (bool)_global.conf->stretchVideo, _global.renderer, _global.conf->showCalculated); + if (_global.frameFileHandle < 0) utilDie("Unable to load framefile: %s", _global.conf->videoFile); frameFileSeek(_global.frameFileHandle, 0, &_global.videoHandle, &thisFrame); // Fills in _global.videoHandle } else { - _global.videoHandle = videoLoad(_global.conf.videoFile, _global.conf.dataDir, (bool)_global.conf.stretchVideo, _global.renderer); + _global.videoHandle = videoLoad(_global.conf->videoFile, _global.conf->dataDir, (bool)_global.conf->stretchVideo, _global.renderer); } - if (_global.videoHandle < 0) utilDie("Unable to load video file: %s", _global.conf.videoFile); - videoSetVolume(_global.videoHandle, _global.conf.volumeVldp, _global.conf.volumeVldp); + if (_global.videoHandle < 0) utilDie("Unable to load video file: %s", _global.conf->videoFile); + videoSetVolume(_global.videoHandle, _global.conf->volumeVldp, _global.conf->volumeVldp); videoSetIndexCallback(NULL); doIndexDisplay(-2); // Should we resize the window? - //***TODO*** + if (conf->resolutionWasCalculated) { + // Is the video wider than the display window? + if ((videoGetWidth(_global.videoHandle) / videoGetHeight(_global.videoHandle)) > (conf->xResolution / conf->yResolution)) { + // Find new window height + conf->yResolution = ((float)conf->xResolution / (float)videoGetWidth(_global.videoHandle) * (float)videoGetHeight(_global.videoHandle)); + changed = true; + } else { + // Find new window width + conf->xResolution = ((float)conf->yResolution / (float)videoGetHeight(_global.videoHandle) * (float)videoGetWidth(_global.videoHandle)); + changed = true; + } + if (changed) { + SDL_SetWindowSize(_global.window, conf->xResolution, conf->yResolution); + SDL_DestroyRenderer(_global.renderer); + // Recreate an accelerated renderer. + _global.renderer = SDL_CreateRenderer(_global.window, -1, SDL_RENDERER_ACCELERATED); + if (_global.renderer == NULL) utilDie("%s", SDL_GetError()); + // Clear screen with black + SDL_SetRenderDrawColor(_global.renderer, 0, 0, 0, 255); + SDL_RenderClear(_global.renderer); + changed = false; + } + } // Default render location is the entire window - windowTarget.x = 0; - windowTarget.y = 0; - windowTarget.w = videoGetWidth(_global.videoHandle); - windowTarget.h = videoGetHeight(_global.videoHandle); + windowTarget.x = 0; + windowTarget.y = 0; + windowTarget.w = videoGetWidth(_global.videoHandle); + windowTarget.h = videoGetHeight(_global.videoHandle); sindenWhite.x = -1; sindenBlack.x = -1; // Overscan compensation - if (_global.conf.scaleFactor < 100) { - windowTarget.w = videoGetWidth(_global.videoHandle) * _global.conf.scaleFactor / 100; - windowTarget.h = videoGetHeight(_global.videoHandle) * _global.conf.scaleFactor / 100; + if (_global.conf->scaleFactor < 100) { + windowTarget.w = videoGetWidth(_global.videoHandle) * _global.conf->scaleFactor / 100; + windowTarget.h = videoGetHeight(_global.videoHandle) * _global.conf->scaleFactor / 100; windowTarget.x = (videoGetWidth(_global.videoHandle) - windowTarget.w) / 2; windowTarget.y = (videoGetHeight(_global.videoHandle) - windowTarget.h) / 2; } // Sinden Light Gun Border Setup - if (_global.conf.sindenArgc > 0) { + if (_global.conf->sindenArgc > 0) { //***TODO*** ADD MOUSE SCALING TO COMPENSATE FOR BORDER sindenWhiteColor.r = 255; sindenWhiteColor.g = 255; @@ -3658,41 +3783,41 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { sindenBlackColor.b = 0; sindenBlackColor.a = 255; // Ok, this thing can have a mess of different arguments: - switch(_global.conf.sindenArgc) { + switch(_global.conf->sindenArgc) { // WW - Just the width of the white border case SINDEN_WHITE: - sindenWhite.x = _global.conf.sindenArgv[0]; + sindenWhite.x = _global.conf->sindenArgv[0]; break; // WW WB - Width of white border and then black border case SINDEN_WHITE_BLACK: - sindenWhite.x = _global.conf.sindenArgv[0]; - sindenBlack.x = _global.conf.sindenArgv[1]; + sindenWhite.x = _global.conf->sindenArgv[0]; + sindenBlack.x = _global.conf->sindenArgv[1]; break; // RW GW BW WW - Custom color "white" border and width case SINDEN_CUSTOM_WHITE: - sindenWhiteColor.r = _global.conf.sindenArgv[0]; - sindenWhiteColor.g = _global.conf.sindenArgv[1]; - sindenWhiteColor.b = _global.conf.sindenArgv[2]; - sindenWhite.x = _global.conf.sindenArgv[3]; + sindenWhiteColor.r = _global.conf->sindenArgv[0]; + sindenWhiteColor.g = _global.conf->sindenArgv[1]; + sindenWhiteColor.b = _global.conf->sindenArgv[2]; + sindenWhite.x = _global.conf->sindenArgv[3]; break; // RW GW BW WW WB - Custom color "white" border and width then width of black border case SINDEN_CUSTOM_WHITE_BLACK: - sindenWhiteColor.r = _global.conf.sindenArgv[0]; - sindenWhiteColor.g = _global.conf.sindenArgv[1]; - sindenWhiteColor.b = _global.conf.sindenArgv[2]; - sindenWhite.x = _global.conf.sindenArgv[3]; - sindenBlack.x = _global.conf.sindenArgv[4]; + sindenWhiteColor.r = _global.conf->sindenArgv[0]; + sindenWhiteColor.g = _global.conf->sindenArgv[1]; + sindenWhiteColor.b = _global.conf->sindenArgv[2]; + sindenWhite.x = _global.conf->sindenArgv[3]; + sindenBlack.x = _global.conf->sindenArgv[4]; break; // RW GW BW WW RB GB BB WB - Custom color "white" border and width then custom color "black" border and width case SINDEN_CUSTOM_WHITE_CUSTOM_BLACK: - sindenWhiteColor.r = _global.conf.sindenArgv[0]; - sindenWhiteColor.g = _global.conf.sindenArgv[1]; - sindenWhiteColor.b = _global.conf.sindenArgv[2]; - sindenWhite.x = _global.conf.sindenArgv[3]; - sindenBlackColor.r = _global.conf.sindenArgv[4]; - sindenBlackColor.g = _global.conf.sindenArgv[5]; - sindenBlackColor.b = _global.conf.sindenArgv[6]; - sindenBlack.x = _global.conf.sindenArgv[7]; + sindenWhiteColor.r = _global.conf->sindenArgv[0]; + sindenWhiteColor.g = _global.conf->sindenArgv[1]; + sindenWhiteColor.b = _global.conf->sindenArgv[2]; + sindenWhite.x = _global.conf->sindenArgv[3]; + sindenBlackColor.r = _global.conf->sindenArgv[4]; + sindenBlackColor.g = _global.conf->sindenArgv[5]; + sindenBlackColor.b = _global.conf->sindenArgv[6]; + sindenBlack.x = _global.conf->sindenArgv[7]; break; } if (sindenWhite.x >= 0) { @@ -3730,7 +3855,7 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { _global.mouseCount = MAX_MICE; } memset(_global.mice, 0, sizeof(_global.mice)); - _global.mouseEnabled = (bool)!_global.conf.noMouse; + _global.mouseEnabled = (bool)!_global.conf->noMouse; for (x=0; x<_global.mouseCount; x++) { strncpy(_global.mice[x].name, ManyMouse_DeviceName((unsigned)x), sizeof(_global.mice[x].name)); _global.mice[x].name[sizeof(_global.mice[x].name) - 1] = 0; @@ -3752,7 +3877,7 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { // Controllers are started by the event loop // Set volume - _global.effectsVolume = (int32_t)((float)AUDIO_MAX_VOLUME * (float)_global.conf.volumeNonVldp * (float)0.01); + _global.effectsVolume = (int32_t)((float)AUDIO_MAX_VOLUME * (float)_global.conf->volumeNonVldp * (float)0.01); Mix_Volume(-1, _global.effectsVolume * 2); // Let us know when sounds end @@ -3770,7 +3895,7 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { _global.discStopped = false; // Start script - if (luaL_dofile(_global.luaContext, _global.conf.scriptFile) != 0) utilDie("Error compiling script: %s", lua_tostring(_global.luaContext, -1)); + if (luaL_dofile(_global.luaContext, _global.conf->scriptFile) != 0) utilDie("Error compiling script: %s", lua_tostring(_global.luaContext, -1)); // Game Loop while (_global.running) { @@ -3984,7 +4109,7 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { // Update video thisFrame = videoUpdate(_global.videoHandle, &_global.videoTexture); - if (_global.conf.isFrameFile) { + if (_global.conf->isFrameFile) { frameFileUpdate(_global.frameFileHandle, &_global.videoHandle); } // Did we get a new video frame? @@ -4032,7 +4157,7 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { // Overlay overlayTexture = SDL_CreateTextureFromSurface(_global.renderer, _global.overlay); if (!overlayTexture) utilDie("%s", SDL_GetError()); - if (!_global.conf.stretchVideo) { + if (!_global.conf->stretchVideo) { SDL_RenderSetLogicalSize(renderer, videoGetWidth(_global.videoHandle), videoGetHeight(_global.videoHandle)); } SDL_RenderCopy(_global.renderer, overlayTexture, NULL, &windowTarget); @@ -4103,7 +4228,7 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { } // Unload background video - if (_global.conf.isFrameFile) { + if (_global.conf->isFrameFile) { frameFileUnload(_global.frameFileHandle); } else { videoUnload(_global.videoHandle); @@ -4115,6 +4240,14 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, ConfigT *conf) { // Stop mice SDL_ShowCursor(SDL_ENABLE); ManyMouse_Quit(); + + // Release global conf memory + destroyConf(&_global.conf); + + // Release control mappings + for (x=0; xdataDir, x); if (!utilFileExists(filename)) break; x++; } diff --git a/singe/singe.h b/singe/singe.h index fe02b9b06..26a6dc3e5 100644 --- a/singe/singe.h +++ b/singe/singe.h @@ -50,6 +50,7 @@ typedef struct ConfigS { char *scriptFile; char *dataDir; char *dataDirBase; + bool resolutionWasCalculated; bool isFrameFile; bool stretchVideo; bool noMouse; diff --git a/singe/singe.pro b/singe/singe.pro index 2b9a821f1..c86ce6c39 100644 --- a/singe/singe.pro +++ b/singe/singe.pro @@ -322,3 +322,6 @@ platformLinux { write_file("source.inc.sh", FILEINFO) } + +DISTFILES += \ + makePDFs.docbuilder diff --git a/singe/util.c b/singe/util.c index b2dd535f8..a671439d5 100644 --- a/singe/util.c +++ b/singe/util.c @@ -156,7 +156,7 @@ void utilFixPathSeparators(char **path, bool slash) { // No - append one. temp = strdup(work); free(work); - work = malloc(sizeof(char) * (strlen(temp) + 1)); + work = malloc(sizeof(char) * (strlen(temp) + 2)); strcpy(work, temp); work[strlen(temp)] = utilGetPathSeparator(); work[strlen(temp) + 1] = 0; @@ -219,12 +219,18 @@ char utilGetPathSeparator(void) { char *utilGetUpToLastPathComponent(char *pathname) { - static char *copy; + static char *copy = NULL; + bool dumb = false; // Using (copy == NULL) below didn't work after optimizations, so enter the dummy. int32_t x; x = (int32_t)(strlen(pathname) - strlen(utilGetLastPathComponent(pathname))) - 1; if (x < 0) x = 0; - if (copy) free(copy); + if (dumb) { + free(copy); + copy = NULL; + } else { + dumb = true; + } copy = strdup(pathname); copy[x] = 0; utilFixPathSeparators(©, true);