singe/singe/main.c

666 lines
20 KiB
C

/*
*
* Singe 2
* Copyright (C) 2006-2020 Scott Duensing <scott@kangaroopunch.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
// -c -x 720 -y 480 -d data/maddog_dvd -v maddog_dvd/frame_maddog_dvd.txt maddog_dvd/maddog_dvd.singe
// -c -x 640 -y 480 -v ActionMax/frame_SonicFury.txt ActionMax/SonicFury.singe
// -x 640 -y 480 -d data/ActionMax ActionMax/BlueThunder.singe
// -d data/ActionMax -v ActionMax/BlueThunder.mp4 test.singe
#include <stdio.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_mixer.h>
#include <SDL2/SDL_ttf.h>
#include <carg_parser.h>
#include <manymouse.h>
#include "stddclmr.h"
#include "common.h"
#include "util.h"
#include "frameFile.h"
#include "videoPlayer.h"
#include "singe.h"
#include "extensions.h"
#include "embedded.h"
typedef struct RatioS {
int32_t aspectNum;
int32_t aspectDom;
} RatioT;
typedef struct ResolutionS {
int32_t width;
int32_t height;
} ResolutionT;
typedef struct ModeS {
RatioT ratio;
ResolutionT resolution;
} ModeT;
bool extractFile(char *filename, unsigned char *data, int32_t length);
void showUsage(char *name, char *message);
bool extractFile(char *filename, unsigned char *data, int32_t length) {
FILE *out;
if (!utilFileExists(filename)) {
out = fopen(filename, "wb");
if (!out) utilDie("Unable to create %s", filename);
fwrite(data, length, 1, out);
fclose(out);
utilSay("Created File: %s", filename);
return true;
}
return false;
}
__attribute__((noreturn))
void showUsage(char *name, char *message) {
char *temp = NULL;
bool created = false;
int32_t result = 0;
utilRedirectConsole();
// 00000000011111111112222222222333333333344444444445555555555666666666677777777778
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
utilSay(" ___ ___ _ _ ___ ___");
utilSay("/ __|_ _| \\| |/ __| __| Somewhat Interactive Nostalgic Game Engine %s", VERSION_STRING);
utilSay("\\__ \\| || .` | (_ | _| Copyright (c) 2006-%s Scott C. Duensing", COPYRIGHT_END_YEAR);
utilSay("|___/___|_|\\_|\\___|___| https://kangaroopunch.com");
utilSay("");
utilSay("Usage: %s [OPTIONS] scriptName{.singe}", utilGetLastPathComponent(name));
utilSay("");
utilSay(" -a, --aspect=N:D force aspect ratio");
utilSay(" -c, --showcalculated show calculated framefile values for debugging");
utilSay(" -d, --datadir=PATHNAME alternate location for written files");
utilSay(" -e, --volume_nonvldp=PERCENT specify sound effects volume in percent");
utilSay(" -f, --fullscreen run in full screen mode");
utilSay(" -g, --sindengun=PARAMS enable Sinden Light Gun support");
utilSay(" -h, --help this display");
utilSay(" -k, --nologos kill the splash screens");
utilSay(" -l, --volume_vldp=PERCENT specify laserdisc volume in percent");
utilSay(" -m, --nomouse disable mouse");
utilSay(" -o, --scalefactor=PERCENT reduce screen size for overscan compensation");
utilSay(" -s, --nosound, --mutesound mutes all sound");
utilSay(" -t, --trace trace script execution to screen and file");
utilSay(" -u, --stretch use ugly stretched video");
utilSay(" -v, --framefile=FILENAME use an alternate video file");
utilSay(" -w, --fullscreen_window run in windowed full screen mode");
utilSay(" -x, --xresolution=VALUE specify horizontal resolution");
utilSay(" -y, --yresolution=VALUE specify vertical resolution");
utilSay(" -z, --noconsole zero console output");
utilSay("");
// Extract any missing support files. We do this here so they're not generated if launched from a front end.
if (!utilPathExists("Singe")) utilMkDirP("Singe", 0777);
// Singe/Framework.singe
temp = utilCreateString("Singe%cFramework.singe", utilGetPathSeparator());
created |= extractFile(temp, Framework_singe, Framework_singe_len);
free(temp);
// Singe/controls.cfg.example
temp = utilCreateString("Singe%ccontrols.cfg.example", utilGetPathSeparator());
created |= extractFile(temp, controls_cfg, controls_cfg_len);
free(temp);
temp = NULL;
if (created) utilSay("");
if (message) {
utilSay("Error: %s", message);
utilSay("");
result = 1;
}
#ifdef _WIN32
getchar();
#endif
exit(result);
}
int main(int argc, char *argv[]) {
int32_t x = 0;
int32_t err = 0;
int32_t flags = 0;
int32_t bestResIndex = -1;
int32_t bestRatioIndex = -1;
int32_t aspectNum = -1;
int32_t aspectDom = -1;
int32_t argCount = 0;
int32_t argIndex = 0;
int32_t code = 0;
char *exeName = NULL;
char *temp = NULL;
char *aspectString = NULL;
char *sindenString = NULL;
float thisRatio = 0;
float bestRatio = 9999;
bool tracing = false;
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
SDL_Surface *icon = NULL;
SDL_DisplayMode mode;
const char *arg = NULL;
const char **cargv = (const char **)argv;
struct Arg_parser parser;
static struct ap_Option options[] = {
{ 'a', "aspect", ap_yes },
{ 'c', "showcalculated", ap_no },
{ 'd', "datadir", ap_yes },
{ 'e', "volume_nonlvdp", ap_yes },
{ 'f', "fullscreen", ap_no },
{ 'g', "sindengun", ap_yes },
{ 'h', "help", ap_no },
{ 'k', "nologos", ap_no },
{ 'l', "volume_vldp", ap_yes },
{ 'm', "nomouse", ap_no },
{ 'o', "scalefactor", ap_yes },
{ 's', "nosound", ap_no },
{ 't', "trace", ap_no },
{ 'u', "stretch", ap_no },
{ 'v', "framefile", ap_yes },
{ 'w', "fullscreenwindow", ap_no },
{ 'x', "xresolution", ap_yes },
{ 'y', "yresolution", ap_yes },
{ 'z', "noconsole", ap_no },
{ 0, 0, ap_no }
};
static ModeT modes[] = {
{ { 4, 3 }, { 640, 480 } },
{ { 4, 3 }, { 800, 600 } },
{ { 4, 3 }, { 960, 720 } },
{ { 4, 3 }, { 1024, 768 } },
{ { 4, 3 }, { 1280, 960 } },
{ { 4, 3 }, { 1400, 1050 } },
{ { 4, 3 }, { 1440, 1080 } },
{ { 4, 3 }, { 1600, 1200 } },
{ { 4, 3 }, { 1856, 1392 } },
{ { 4, 3 }, { 1920, 1440 } },
{ { 4, 3 }, { 2048, 1536 } },
{ { 16, 9 }, { 1024, 576 } },
{ { 16, 9 }, { 1152, 648 } },
{ { 16, 9 }, { 1280, 720 } },
{ { 16, 9 }, { 1366, 768 } },
{ { 16, 9 }, { 1600, 900 } },
{ { 16, 9 }, { 1920, 1080 } },
{ { 16, 9 }, { 2560, 1440 } },
{ { 16, 9 }, { 3840, 2160 } },
{ { 16, 9 }, { 7680, 4320 } },
{ { 16, 10 }, { 1280, 800 } },
{ { 16, 10 }, { 1440, 900 } },
{ { 16, 10 }, { 1680, 1050 } },
{ { 16, 10 }, { 1920, 1200 } },
{ { 16, 10 }, { 2560, 1600 } },
{ { 0, 0 }, { 0, 0 } }
};
exeName = (char *)argv[0];
if (!ap_init(&parser, argc, cargv, options, 0)) {
utilDie("Out of memory parsing arguments.");
}
if (ap_error(&parser)) {
utilDie("%s", ap_error(&parser));
}
// Parse command line
argCount = ap_arguments(&parser);
for (argIndex=0; argIndex < ap_arguments(&parser); ++argIndex) {
code = ap_code(&parser, argIndex);
if (!code) break;
arg = ap_argument(&parser, argIndex);
// Handle options
switch (code) {
// Aspect
case 'a':
if (aspectString) free(aspectString);
aspectString = strdup(arg);
argCount++;
break;
// Show Calculated Frame File Values
case 'c':
_confShowCalculated = true;
break;
// Data Dir
case 'd':
if (_confDataDir) free(_confDataDir);
_confDataDir = strdup(arg);
argCount++;
break;
// Effects Volume
case 'e':
_confVolumeNonVldp = atoi(arg);
argCount++;
break;
// Full Screen
case 'f':
_confFullScreen = true;
break;
// Sinden Light Gun
case 'g':
if (sindenString) free(sindenString);
sindenString = strdup(arg);
argCount++;
break;
// Help
case 'h':
showUsage(exeName, NULL);
break;
// No Logos
case 'k':
_confNoLogos = true;
break;
// Video Volume
case 'l':
_confVolumeVldp = atoi(arg);
argCount++;
break;
// No Mouse
case 'm':
_confNoMouse = true;
break;
// Overscan Zoom
case 'o':
_confScaleFactor = atoi(arg);
argCount++;
break;
// No Sound
case 's':
_confNoSound = true;
break;
// Trace
case 't':
tracing = true;
break;
// Ugly Stretched Video
case 'u':
_confStretchVideo = true;
break;
// Video File
case 'v':
if (_confVideoFile) free(_confVideoFile);
_confVideoFile = strdup(arg);
argCount++;
break;
// Full Screen Windowed
case 'w':
_confFullScreenWindow = true;
break;
// X Resolution
case 'x':
_confXResolution = atoi(arg);
argCount++;
break;
// Y Resolution
case 'y':
_confYResolution = atoi(arg);
argCount++;
break;
// No console output or splash screens
case 'z':
_confNoConsole = true;
break;
default:
abort(); // Something bad happened
break;
}
}
// For that dumb OS
if (!_confNoConsole) utilRedirectConsole();
// Did we get a filename or path to open?
if ((argc - argCount) != 1) showUsage(exeName, "No script file specified.");
_confScriptFile = strdup(argv[argCount]);
utilFixPathSeparators(&_confScriptFile, false);
// Exists?
if (!utilFileExists(_confScriptFile)) {
// Missing. Is a path?
if (utilPathExists(_confScriptFile)) {
// See if the script exists in the path.
temp = utilCreateString("%s%c%s.singe", _confScriptFile, utilGetPathSeparator(), utilGetLastPathComponent(_confScriptFile));
if (utilFileExists(temp)) {
// Found script named for path inside path.
free(_confScriptFile);
_confScriptFile = temp;
temp = NULL;
} else {
// Not in the path either.
free(_confScriptFile);
_confScriptFile = NULL;
}
} else {
// Not a path either.
free(_confScriptFile);
_confScriptFile = NULL;
}
}
if (!_confScriptFile) showUsage(exeName, "Unable to locate script.");
// Do we need to generate a video name?
if (_confVideoFile) {
utilFixPathSeparators(&_confVideoFile, false);
if (!utilFileExists(_confVideoFile)) {
free(_confVideoFile);
_confVideoFile = NULL;
}
} else {
x = (int32_t)(strlen(_confScriptFile) - strlen(utilGetFileExtension(_confScriptFile))) - 1;
if (x < 0) {
x = 0;
}
temp = strdup(_confScriptFile);
temp[x] = 0;
// Check all known extensions - lower case only, Windows users!
x = 0;
while (ffmpegExtensions[x]) {
_confVideoFile = utilCreateString("%s.%s", temp, ffmpegExtensions[x]);
if (utilFileExists(_confVideoFile)) {
break;
}
free(_confVideoFile);
_confVideoFile = NULL;
x++;
}
free(temp);
// If we still don't have one, try a framefile
if (!_confVideoFile) {
_confVideoFile = utilCreateString("%s.txt", temp);
if (!utilFileExists(_confVideoFile)) {
free(_confVideoFile);
_confVideoFile = NULL;
}
}
}
if (!_confVideoFile) showUsage(exeName, "Unable to locate video.");
// Is it a framefile?
if (strncmp(utilGetFileExtension(_confVideoFile), "txt", 3) == 0) {
_confIsFrameFile = true;
}
// Do we need to generate a data directory name?
if (_confDataDir) {
utilFixPathSeparators(&_confDataDir, false);
// Try to create data directory to ensure it exists.
utilMkDirP(_confDataDir, 0777);
// Does it exist?
if (!utilPathExists(_confDataDir)) {
free(_confDataDir);
_confDataDir = NULL;
}
} else {
// Put it in the game folder.
x = (int32_t)(strlen(_confScriptFile) - strlen(utilGetLastPathComponent(_confScriptFile))) - 1;
if (x < 0) {
x = 0;
}
temp = strdup(_confScriptFile);
temp[x] = 0;
_confDataDir = strdup(temp);
free(temp);
temp = NULL;
}
if (!_confDataDir) showUsage(exeName, "Unable to locate data directory.");
utilFixPathSeparators(&_confDataDir, true);
// Do they want tracing?
if (tracing) {
temp = utilCreateString("%strace.txt", _confDataDir);
utilTraceStart(temp);
free(temp);
temp = NULL;
}
// Do the full screen options make sense?
if (_confFullScreen && _confFullScreenWindow) showUsage(exeName, "Full Screen or Full Screen Windowed. Pick one.");
// Sane volume values?
if ((_confVolumeVldp < 0) || (_confVolumeVldp > 100)) showUsage(exeName, "Laserdisc volume must be between 0 and 100 percent.");
if ((_confVolumeNonVldp < 0) || (_confVolumeNonVldp > 100)) showUsage(exeName, "Effects volume must be between 0 and 100 percent.");
// Sane scale factor?
if ((_confScaleFactor < 50) || (_confScaleFactor > 100)) showUsage(exeName, "Display scale must be between 50 and 100 percent.");
// Sinden light gun?
if (sindenString) {
if (_confScaleFactor != 100) showUsage(exeName, "Cannot use --sindengun and --scalefactor together.");
// Was it wrapped in quotes?
if ((sindenString[0] == '"') || (sindenString[0] == '\'')) {
sindenString[0] = ' ';
}
// Ok, this thing can have a mess of different arguments:
// WW - Just the width of the white border
// WW WB - Width of white border and then black border
// RW GW BW WW - Custom color "white" border and width
// RW GW BW WW WB - Custom color "white" border and width then width of black border
// RW GW BW WW RB GB BB WB - Custom color "white" border and width then custom color "black" border and width
temp = strtok(sindenString, " ");
while (temp != NULL) {
_confSindenArgv[_confSindenArgc++] = atoi(temp);
temp = strtok(NULL, " ");
if ((temp != NULL) && (_confSindenArgc > SINDEN_OPTION_COUNT)) showUsage(exeName, "Too many arguments to --sindengun.");
}
// Did we get a sane number of arguments?
if ((_confSindenArgc != SINDEN_WHITE) && (_confSindenArgc != SINDEN_WHITE_BLACK) && (_confSindenArgc != SINDEN_CUSTOM_WHITE) && (_confSindenArgc != SINDEN_CUSTOM_WHITE_BLACK) && (_confSindenArgc != SINDEN_CUSTOM_WHITE_CUSTOM_BLACK)) showUsage(exeName, "Bad argument count to --sindengun.");
free(sindenString);
}
// Init SDL
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) utilDie("%s", SDL_GetError());
if (SDL_GetCurrentDisplayMode(0, &mode) < 0) utilDie("%s", SDL_GetError());
// Determine resolution if not specified
if ((_confXResolution <= 0) || (_confYResolution <= 0)) {
// Did they specify an aspect ratio?
if (aspectString) {
aspectNum = atoi(aspectString);
temp = strstr(aspectString, ":");
if (temp) {
temp++;
aspectDom = atoi(temp);
temp = NULL;
}
if ((aspectNum > 0) && (aspectDom > 0)) {
// Do we understand what they asked for?
x = 0;
while (modes[x].ratio.aspectNum != 0) {
if ((modes[x].ratio.aspectNum == aspectNum) && (modes[x].ratio.aspectDom == aspectDom)) {
bestRatioIndex = x;
break;
}
x++;
}
}
free(aspectString);
} else {
// Find our current aspect ratio
x = 0;
while (modes[x].ratio.aspectNum != 0) {
thisRatio = fabsf(((float)modes[x].ratio.aspectNum / (float)modes[x].ratio.aspectDom) - ((float)mode.w / (float)mode.h));
if (thisRatio < bestRatio) {
bestRatio = thisRatio;
bestRatioIndex = x;
}
x++;
}
}
if (bestRatioIndex < 0) showUsage(exeName, "Unknown aspect ratio.");
x = 0;
// Were both resolutions not specified?
if ((_confXResolution <= 0) && (_confYResolution <= 0)) {
// Are we full screen?
if (_confFullScreen || _confFullScreenWindow) {
// Use desktop resolution
_confXResolution = mode.w;
_confYResolution = mode.h;
} else {
// Find largest window that will fit on the screen but not fill it
while (modes[x].ratio.aspectNum != 0) {
if ((modes[x].ratio.aspectNum == modes[bestRatioIndex].ratio.aspectNum) && (modes[x].ratio.aspectDom == modes[bestRatioIndex].ratio.aspectDom)) {
if ((modes[x].resolution.width < mode.w) && (modes[x].resolution.height < mode.h)) {
bestResIndex = x;
}
}
x++;
}
_confXResolution = modes[bestResIndex].resolution.width;
_confYResolution = modes[bestResIndex].resolution.height;
}
} else {
// Find unprovided width/height using provided value
while (modes[x].ratio.aspectNum != 0) {
// Is this the aspect ratio we're using?
if ((modes[bestRatioIndex].ratio.aspectNum == modes[x].ratio.aspectNum) && (modes[bestRatioIndex].ratio.aspectDom == modes[x].ratio.aspectDom)) {
// Do we have the width or height?
if (_confXResolution > 0) {
// We have the width. Is this the matching entry?
if (modes[x].resolution.width == _confXResolution) {
bestResIndex = x;
_confYResolution = modes[x].resolution.height;
break;
}
} else {
// We have the height. Is this the matching entry?
if (modes[x].resolution.height == _confYResolution) {
bestResIndex = x;
_confXResolution = modes[x].resolution.width;
break;
}
}
}
x++;
}
}
}
// Did we end up with a valid resolution?
if (_confXResolution <= 0) showUsage(exeName, "Unable to determine X resolution. (Is the Y value sane?)");
if (_confYResolution <= 0) showUsage(exeName, "Unable to determine Y resolution. (Is the X value sane?)");
if ((_confXResolution > mode.w) || (_confYResolution > mode.h)) showUsage(exeName, "Specified resolution is larger than the display.");
// Init SDL_mixer ***FIX***
flags = /* MIX_INIT_FLAC | */ MIX_INIT_MOD | /* MIX_INIT_MP3 | */ MIX_INIT_OGG | MIX_INIT_MID | MIX_INIT_OPUS;
err = Mix_Init(flags);
if (err != flags) utilDie("%s", Mix_GetError());
// Init SDL_image ***FIX***
flags = IMG_INIT_JPG | IMG_INIT_PNG | /* IMG_INIT_TIF | */ IMG_INIT_WEBP;
err = IMG_Init(flags);
if (err != flags) utilDie("%s", IMG_GetError());
// Init SDL_ttf
if (TTF_Init() < 0) utilDie("%s", TTF_GetError());
// Create Resizable Window
window = SDL_CreateWindow("SINGE", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, _confXResolution, _confYResolution, SDL_WINDOW_RESIZABLE);
if (window == NULL) utilDie("%s", SDL_GetError());
// Window Icon
icon = IMG_LoadPNG_RW(SDL_RWFromMem(icon_png, icon_png_len));
if (icon == NULL) utilDie("%s", SDL_GetError());
SDL_SetWindowIcon(window, icon);
SDL_FreeSurface(icon);
icon = NULL;
// Do we want full screen of some kind?
if (_confFullScreen || _confFullScreenWindow) {
flags = _confFullScreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP;
SDL_SetWindowFullscreen(window, (Uint32)flags);
}
// Create an accelerated renderer.
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == NULL) utilDie("%s", SDL_GetError());
// Clear screen with black
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
// Create audio mixer device
err = Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 44100 /* freq */ * 16 /* bits */ * 2 /* channels */ * 2 /* seconds */);
if (err != 0) utilDie("%s", Mix_GetError());
Mix_AllocateChannels(16);
// Start our video playback system
if (frameFileInit()) utilDie("Unable to initialize framefile handler.");
if (videoInit()) utilDie("Unable to initialize video player.");
// Finish our setup
SDL_DisableScreenSaver();
// Run Singe!
singe(window, renderer);
// Shutdown
videoQuit();
frameFileQuit();
Mix_CloseAudio();
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_EnableScreenSaver();
TTF_Quit();
IMG_Quit();
Mix_Quit();
SDL_Quit();
if (_confDataDir) free(_confDataDir);
if (_confVideoFile) free(_confVideoFile);
if (_confScriptFile) free(_confScriptFile);
utilTraceEnd();
ap_free(&parser);
#ifdef _WIN32
if (!_confNoConsole) getchar();
#endif
return 0;
}