Files
Main_MiSTer/cfg.cpp
Martin Donlon edcb3fcefc video: Vertical Scaling Changes (#608)
Integer Resolution Scaling
Generate a video mode whose vertical resolution is an integer multiple
of the cores vertical resolution. The video mode set via the
`video_mode` parameter is considered the maximum resolution. There are
two modes of operation: vscale_mode=4 - Both vertical and horizontal
resolution are modified. The horizontal resolution is scaled in
propotion to the vertical, so the aspect ratio is maintained. This will
be most suited to VGA CRTs that expect a specific aspect ratio signal.
vscale_mode=5 - Only the vertical resolution is modified. This will be
suitable for most modern LCD/OLED displays. The additional horizontal
resolution provides more space for wide aspect ratio cores.

Video Mode Calculation
CVT timing calculations are used to generate the modified video modes
for Interger Resolution Scaling, this mode generation is also exposed as
an additional way to specify video modes in the MiSTer.ini. Modes can be
specified with just the width, height and refresh rate:
`video_mode=1920,1200,60`. Whether to use the original CVT or the
reduced blanking CVT-RB timings can be specified with an optional flag,
e.g. `video_mode=1024,768,60,cvt`. Additionally the sync polarities can
also be expressed using flags now, e.g.
`video_mode=1280,110,40,220,720,5,5,20,74250,+hsync,-vsync`

Code Changes
More functionality was broken out of `video_mode_adjust`.
`video_mode_select` determines what the correct base video mode is based
on the timing and vsync_adjust configuration. The mode selected there is
then passed to `video_resolution_adjust` which modifies it based on the
integer resolution scaling selected. Other than that the functionality
of `video_mode_adjust` remains the same.

The `video_calculate_cvt` function was added to calculated video mode
timings.

`parse_custom_video_mode` was changed to support the additional flags.
It now uses `str_tokenize` to split up the input string so it is more
easy to manage the different variations of parameter counts allowed for
the modes.

`vmode_custom_param_t` was added to alias the items in the
vmode_custom_t instead of using raw array indexing everywhere.

`str_util.h/.cpp` added to provide some safer and easy to use string
functions. `strcpyz`, `strncpyz`, `sprintfz` all ensure that the
resulting string is null terminated and doesn't overrun memory.
2022-05-21 01:48:19 +08:00

393 lines
14 KiB
C++

// cfg.c
// 2015, rok.krajnc@gmail.com
// 2017+, Sorgelig
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <ctype.h>
#include "cfg.h"
#include "debug.h"
#include "file_io.h"
#include "user_io.h"
#include "video.h"
cfg_t cfg;
typedef enum
{
UINT8 = 0, INT8, UINT16, INT16, UINT32, INT32, FLOAT, STRING, UINT32ARR
} ini_vartypes_t;
typedef struct
{
const char* name;
void* var;
ini_vartypes_t type;
int min;
int max;
} ini_var_t;
static const ini_var_t ini_vars[] =
{
{ "YPBPR", (void*)(&(cfg.ypbpr)), UINT8, 0, 1 },
{ "COMPOSITE_SYNC", (void*)(&(cfg.csync)), UINT8, 0, 1 },
{ "FORCED_SCANDOUBLER", (void*)(&(cfg.forced_scandoubler)), UINT8, 0, 1 },
{ "VGA_SCALER", (void*)(&(cfg.vga_scaler)), UINT8, 0, 1 },
{ "VGA_SOG", (void*)(&(cfg.vga_sog)), UINT8, 0, 1 },
{ "KEYRAH_MODE", (void*)(&(cfg.keyrah_mode)), UINT32, 0, (int)0xFFFFFFFF },
{ "RESET_COMBO", (void*)(&(cfg.reset_combo)), UINT8, 0, 3 },
{ "KEY_MENU_AS_RGUI", (void*)(&(cfg.key_menu_as_rgui)), UINT8, 0, 1 },
{ "VIDEO_MODE", (void*)(cfg.video_conf), STRING, 0, sizeof(cfg.video_conf) - 1 },
{ "VIDEO_MODE_PAL", (void*)(cfg.video_conf_pal), STRING, 0, sizeof(cfg.video_conf_pal) - 1 },
{ "VIDEO_MODE_NTSC", (void*)(cfg.video_conf_ntsc), STRING, 0, sizeof(cfg.video_conf_ntsc) - 1 },
{ "VIDEO_INFO", (void*)(&(cfg.video_info)), UINT8, 0, 10 },
{ "VSYNC_ADJUST", (void*)(&(cfg.vsync_adjust)), UINT8, 0, 2 },
{ "HDMI_AUDIO_96K", (void*)(&(cfg.hdmi_audio_96k)), UINT8, 0, 1 },
{ "DVI_MODE", (void*)(&(cfg.dvi_mode)), UINT8, 0, 1 },
{ "HDMI_LIMITED", (void*)(&(cfg.hdmi_limited)), UINT8, 0, 2 },
{ "KBD_NOMOUSE", (void*)(&(cfg.kbd_nomouse)), UINT8, 0, 1 },
{ "MOUSE_THROTTLE", (void*)(&(cfg.mouse_throttle)), UINT8, 1, 100 },
{ "BOOTSCREEN", (void*)(&(cfg.bootscreen)), UINT8, 0, 1 },
{ "VSCALE_MODE", (void*)(&(cfg.vscale_mode)), UINT8, 0, 5 },
{ "VSCALE_BORDER", (void*)(&(cfg.vscale_border)), UINT16, 0, 399 },
{ "RBF_HIDE_DATECODE", (void*)(&(cfg.rbf_hide_datecode)), UINT8, 0, 1 },
{ "MENU_PAL", (void*)(&(cfg.menu_pal)), UINT8, 0, 1 },
{ "BOOTCORE", (void*)(&(cfg.bootcore)), STRING, 0, sizeof(cfg.bootcore) - 1 },
{ "BOOTCORE_TIMEOUT", (void*)(&(cfg.bootcore_timeout)), INT16, 2, 30 },
{ "FONT", (void*)(&(cfg.font)), STRING, 0, sizeof(cfg.font) - 1 },
{ "FB_SIZE", (void*)(&(cfg.fb_size)), UINT8, 0, 4 },
{ "FB_TERMINAL", (void*)(&(cfg.fb_terminal)), UINT8, 0, 1 },
{ "OSD_TIMEOUT", (void*)(&(cfg.osd_timeout)), INT16, 0, 3600 },
{ "DIRECT_VIDEO", (void*)(&(cfg.direct_video)), UINT8, 0, 1 },
{ "OSD_ROTATE", (void*)(&(cfg.osd_rotate)), UINT8, 0, 2 },
{ "GAMEPAD_DEFAULTS", (void*)(&(cfg.gamepad_defaults)), UINT8, 0, 1 },
{ "RECENTS", (void*)(&(cfg.recents)), UINT8, 0, 1 },
{ "CONTROLLER_INFO", (void*)(&(cfg.controller_info)), UINT8, 0, 10 },
{ "REFRESH_MIN", (void*)(&(cfg.refresh_min)), UINT8, 0, 150 },
{ "REFRESH_MAX", (void*)(&(cfg.refresh_max)), UINT8, 0, 150 },
{ "JAMMA_VID", (void*)(&(cfg.jamma_vid)), UINT16, 0, 0xFFFF },
{ "JAMMA_PID", (void*)(&(cfg.jamma_pid)), UINT16, 0, 0xFFFF },
{ "SNIPER_MODE", (void*)(&(cfg.sniper_mode)), UINT8, 0, 1 },
{ "BROWSE_EXPAND", (void*)(&(cfg.browse_expand)), UINT8, 0, 1 },
{ "LOGO", (void*)(&(cfg.logo)), UINT8, 0, 1 },
{ "SHARED_FOLDER", (void*)(&(cfg.shared_folder)), STRING, 0, sizeof(cfg.shared_folder) - 1 },
{ "NO_MERGE_VID", (void*)(&(cfg.no_merge_vid)), UINT16, 0, 0xFFFF },
{ "NO_MERGE_PID", (void*)(&(cfg.no_merge_pid)), UINT16, 0, 0xFFFF },
{ "NO_MERGE_VIDPID", (void*)(cfg.no_merge_vidpid), UINT32ARR, 0, (int)0xFFFFFFFF },
{ "CUSTOM_ASPECT_RATIO_1", (void*)(&(cfg.custom_aspect_ratio[0])), STRING, 0, sizeof(cfg.custom_aspect_ratio[0]) - 1 },
{ "CUSTOM_ASPECT_RATIO_2", (void*)(&(cfg.custom_aspect_ratio[1])), STRING, 0, sizeof(cfg.custom_aspect_ratio[1]) - 1 },
{ "SPINNER_VID", (void*)(&(cfg.spinner_vid)), UINT16, 0, 0xFFFF },
{ "SPINNER_PID", (void*)(&(cfg.spinner_pid)), UINT16, 0, 0xFFFF },
{ "SPINNER_AXIS", (void*)(&(cfg.spinner_axis)), UINT8, 0, 1 },
{ "SPINNER_THROTTLE", (void*)(&(cfg.spinner_throttle)), INT32, -10000, 10000 },
{ "AFILTER_DEFAULT", (void*)(&(cfg.afilter_default)), STRING, 0, sizeof(cfg.afilter_default) - 1 },
{ "VFILTER_DEFAULT", (void*)(&(cfg.vfilter_default)), STRING, 0, sizeof(cfg.vfilter_default) - 1 },
{ "VFILTER_VERTICAL_DEFAULT", (void*)(&(cfg.vfilter_vertical_default)), STRING, 0, sizeof(cfg.vfilter_vertical_default) - 1 },
{ "VFILTER_SCANLINES_DEFAULT", (void*)(&(cfg.vfilter_scanlines_default)), STRING, 0, sizeof(cfg.vfilter_scanlines_default) - 1 },
{ "SHMASK_DEFAULT", (void*)(&(cfg.shmask_default)), STRING, 0, sizeof(cfg.shmask_default) - 1 },
{ "SHMASK_MODE_DEFAULT", (void*)(&(cfg.shmask_mode_default)), UINT8, 0, 255 },
{ "LOG_FILE_ENTRY", (void*)(&(cfg.log_file_entry)), UINT8, 0, 1 },
{ "BT_AUTO_DISCONNECT", (void*)(&(cfg.bt_auto_disconnect)), UINT32, 0, 180 },
{ "BT_RESET_BEFORE_PAIR", (void*)(&(cfg.bt_reset_before_pair)), UINT8, 0, 1 },
{ "WAITMOUNT", (void*)(&(cfg.waitmount)), STRING, 0, sizeof(cfg.waitmount) - 1 },
{ "RUMBLE", (void *)(&(cfg.rumble)), UINT8, 0, 1},
{ "WHEEL_FORCE", (void*)(&(cfg.wheel_force)), UINT8, 0, 100 },
{ "WHEEL_RANGE", (void*)(&(cfg.wheel_range)), UINT16, 0, 1000 },
{ "HDMI_GAME_MODE", (void *)(&(cfg.hdmi_game_mode)), UINT8, 0, 1},
};
static const int nvars = (int)(sizeof(ini_vars) / sizeof(ini_var_t));
#define INI_LINE_SIZE 1024
#define INI_SECTION_START '['
#define INI_SECTION_END ']'
#define INCL_SECTION '+'
#define CHAR_IS_NUM(c) (((c) >= '0') && ((c) <= '9'))
#define CHAR_IS_ALPHA_LOWER(c) (((c) >= 'a') && ((c) <= 'z'))
#define CHAR_IS_ALPHA_UPPER(c) (((c) >= 'A') && ((c) <= 'Z'))
#define CHAR_IS_ALPHANUM(c) (CHAR_IS_ALPHA_LOWER(c) || CHAR_IS_ALPHA_UPPER(c) || CHAR_IS_NUM(c))
#define CHAR_IS_SPECIAL(c) (((c) == '[') || ((c) == ']') || ((c) == '(') || ((c) == ')') || \
((c) == '-') || ((c) == '+') || ((c) == '/') || ((c) == '=') || \
((c) == '#') || ((c) == '$') || ((c) == '@') || ((c) == '_') || \
((c) == ',') || ((c) == '.') || ((c) == '!') || ((c) == '*') || \
((c) == ':') || ((c) == '~'))
#define CHAR_IS_VALID(c) (CHAR_IS_ALPHANUM(c) || CHAR_IS_SPECIAL(c))
#define CHAR_IS_SPACE(c) (((c) == ' ') || ((c) == '\t'))
#define CHAR_IS_LINEEND(c) (((c) == '\n'))
#define CHAR_IS_COMMENT(c) (((c) == ';'))
#define CHAR_IS_QUOTE(c) (((c) == '"'))
fileTYPE ini_file;
static bool has_video_sections = false;
static bool using_video_section = false;
int ini_pt = 0;
static char ini_getch()
{
static uint8_t buf[512];
if (!(ini_pt & 0x1ff)) FileReadSec(&ini_file, buf);
if (ini_pt >= ini_file.size) return 0;
return buf[(ini_pt++) & 0x1ff];
}
static int ini_getline(char* line)
{
char c, ignore = 0, skip = 1;
int i = 0;
while ((c = ini_getch()))
{
if (!CHAR_IS_SPACE(c)) skip = 0;
if (i >= (INI_LINE_SIZE - 1) || CHAR_IS_COMMENT(c)) ignore = 1;
if (CHAR_IS_LINEEND(c)) break;
if ((CHAR_IS_SPACE(c) || CHAR_IS_VALID(c)) && !ignore && !skip) line[i++] = c;
}
line[i] = 0;
while (i > 0 && CHAR_IS_SPACE(line[i - 1])) line[--i] = 0;
return c == 0;
}
static int ini_get_section(char* buf, const char *vmode)
{
int i = 0;
int incl = (buf[0] == INCL_SECTION);
// get section start marker
if (buf[0] != INI_SECTION_START && buf[0] != INCL_SECTION)
{
return 0;
}
else buf++;
int wc_pos = -1;
int eq_pos = -1;
// get section stop marker
while (buf[i])
{
if (buf[i] == INI_SECTION_END)
{
buf[i] = 0;
break;
}
if (buf[i] == '*') wc_pos = i;
if (buf[i] == '=') eq_pos = i;
i++;
if (i >= INI_LINE_SIZE) return 0;
}
if (!strcasecmp(buf, "MiSTer") ||
(is_arcade() && !strcasecmp(buf, "arcade")) ||
((wc_pos >= 0) ? !strncasecmp(buf, user_io_get_core_name(1), wc_pos) : !strcasecmp(buf, user_io_get_core_name(1))) ||
((wc_pos >= 0) ? !strncasecmp(buf, user_io_get_core_name(0), wc_pos) : !strcasecmp(buf, user_io_get_core_name(0))))
{
if (incl)
{
ini_parser_debugf("included '%s'", buf);
}
else
{
ini_parser_debugf("Got SECTION '%s'", buf);
}
return 1;
}
else if ((eq_pos >= 0) && !strncasecmp(buf, "video", eq_pos))
{
has_video_sections = true;
if(!strcasecmp(&buf[eq_pos+1], vmode))
{
using_video_section = true;
ini_parser_debugf("Got SECTION '%s'", buf);
return 1;
}
else
{
return 0;
}
}
return 0;
}
static void ini_parse_var(char* buf)
{
// find var
int i = 0;
while (1)
{
if (buf[i] == '=' || CHAR_IS_SPACE(buf[i]))
{
buf[i] = 0;
break;
}
else if (!buf[i]) return;
i++;
}
// parse var
int var_id = -1;
for (int j = 0; j < (int)(sizeof(ini_vars) / sizeof(ini_var_t)); j++)
{
if (!strcasecmp(buf, ini_vars[j].name)) var_id = j;
}
// get data
if (var_id != -1)
{
i++;
while (buf[i] == '=' || CHAR_IS_SPACE(buf[i])) i++;
ini_parser_debugf("Got VAR '%s' with VALUE %s", buf, buf+i);
switch (ini_vars[var_id].type)
{
case UINT8:
*(uint8_t*)(ini_vars[var_id].var) = strtoul(&(buf[i]), NULL, 0);
if (*(uint8_t*)(ini_vars[var_id].var) > ini_vars[var_id].max) *(uint8_t*)(ini_vars[var_id].var) = ini_vars[var_id].max;
if (*(uint8_t*)(ini_vars[var_id].var) < ini_vars[var_id].min) *(uint8_t*)(ini_vars[var_id].var) = ini_vars[var_id].min;
break;
case INT8:
*(int8_t*)(ini_vars[var_id].var) = strtol(&(buf[i]), NULL, 0);
if (*(int8_t*)(ini_vars[var_id].var) > ini_vars[var_id].max) *(int8_t*)(ini_vars[var_id].var) = ini_vars[var_id].max;
if (*(int8_t*)(ini_vars[var_id].var) < ini_vars[var_id].min) *(int8_t*)(ini_vars[var_id].var) = ini_vars[var_id].min;
break;
case UINT16:
*(uint16_t*)(ini_vars[var_id].var) = strtoul(&(buf[i]), NULL, 0);
if (*(uint16_t*)(ini_vars[var_id].var) > ini_vars[var_id].max) *(uint16_t*)(ini_vars[var_id].var) = ini_vars[var_id].max;
if (*(uint16_t*)(ini_vars[var_id].var) < ini_vars[var_id].min) *(uint16_t*)(ini_vars[var_id].var) = ini_vars[var_id].min;
break;
case UINT32ARR:
{
uint32_t *arr = (uint32_t*)ini_vars[var_id].var;
uint32_t pos = ++arr[0];
arr[pos] = strtoul(&(buf[i]), NULL, 0);
if (arr[pos] > (uint32_t)ini_vars[var_id].max) arr[pos] = (uint32_t)ini_vars[var_id].max;
if (arr[pos] < (uint32_t)ini_vars[var_id].min) arr[pos] = (uint32_t)ini_vars[var_id].min;
}
break;
case INT16:
*(int16_t*)(ini_vars[var_id].var) = strtol(&(buf[i]), NULL, 0);
if (*(int16_t*)(ini_vars[var_id].var) > ini_vars[var_id].max) *(int16_t*)(ini_vars[var_id].var) = ini_vars[var_id].max;
if (*(int16_t*)(ini_vars[var_id].var) < ini_vars[var_id].min) *(int16_t*)(ini_vars[var_id].var) = ini_vars[var_id].min;
break;
case UINT32:
*(uint32_t*)(ini_vars[var_id].var) = strtoul(&(buf[i]), NULL, 0);
if (*(uint32_t*)(ini_vars[var_id].var) > (uint32_t)ini_vars[var_id].max) *(uint32_t*)(ini_vars[var_id].var) = ini_vars[var_id].max;
if (*(uint32_t*)(ini_vars[var_id].var) < (uint32_t)ini_vars[var_id].min) *(uint32_t*)(ini_vars[var_id].var) = ini_vars[var_id].min;
break;
case INT32:
*(int32_t*)(ini_vars[var_id].var) = strtol(&(buf[i]), NULL, 0);
if (*(int32_t*)(ini_vars[var_id].var) > ini_vars[var_id].max) *(int32_t*)(ini_vars[var_id].var) = ini_vars[var_id].max;
if (*(int32_t*)(ini_vars[var_id].var) < ini_vars[var_id].min) *(int32_t*)(ini_vars[var_id].var) = ini_vars[var_id].min;
break;
case FLOAT:
*(float*)(ini_vars[var_id].var) = strtof(&(buf[i]), NULL);
if (*(float*)(ini_vars[var_id].var) > ini_vars[var_id].max) *(float*)(ini_vars[var_id].var) = ini_vars[var_id].max;
if (*(float*)(ini_vars[var_id].var) < ini_vars[var_id].min) *(float*)(ini_vars[var_id].var) = ini_vars[var_id].min;
break;
case STRING:
memset(ini_vars[var_id].var, 0, ini_vars[var_id].max);
strncpy((char*)(ini_vars[var_id].var), &(buf[i]), ini_vars[var_id].max);
break;
}
}
}
static void ini_parse(int alt, const char *vmode)
{
static char line[INI_LINE_SIZE];
int section = 0;
int eof;
ini_parser_debugf("Start INI parser for core \"%s\"(%s), video mode \"%s\".", user_io_get_core_name(0), user_io_get_core_name(1), vmode);
memset(line, 0, sizeof(line));
memset(&ini_file, 0, sizeof(ini_file));
const char *name = cfg_get_name(alt);
if (!FileOpen(&ini_file, name)) return;
ini_parser_debugf("Opened file %s with size %llu bytes.", name, ini_file.size);
ini_pt = 0;
// parse ini
while (1)
{
// get line
eof = ini_getline(line);
ini_parser_debugf("line(%d): \"%s\".", section, line);
if (line[0] == INI_SECTION_START)
{
// if first char in line is INI_SECTION_START, get section
section = ini_get_section(line, vmode);
}
else if (line[0] == INCL_SECTION && !section)
{
section = ini_get_section(line, vmode);
}
else if(section)
{
// otherwise this is a variable, get it
ini_parse_var(line);
}
// if end of file, stop
if (eof) break;
}
FileClose(&ini_file);
}
const char* cfg_get_name(uint8_t alt)
{
static char name[64];
strcpy(name, "MiSTer.ini");
if (alt == 1)
{
strcpy(name, "MiSTer_alt_1.ini");
if (FileExists(name)) return name;
return "MiSTer_alt.ini";
}
if (alt && alt < 4) sprintf(name, "MiSTer_alt_%d.ini", alt);
return name;
}
void cfg_parse()
{
memset(&cfg, 0, sizeof(cfg));
cfg.bootscreen = 1;
cfg.fb_terminal = 1;
cfg.controller_info = 6;
cfg.browse_expand = 1;
cfg.logo = 1;
cfg.rumble = 1;
cfg.wheel_force = 50;
cfg.dvi_mode = 2;
has_video_sections = false;
using_video_section = false;
ini_parse(altcfg(), video_get_core_mode_name(1));
if (has_video_sections && !using_video_section)
{
// second pass to look for section without vrefresh
ini_parse(altcfg(), video_get_core_mode_name(0));
}
}
bool cfg_has_video_sections()
{
return has_video_sections;
}