reeeeally rough first pass at hdmi support

This commit is contained in:
Shaun Inman 2023-03-01 22:53:15 -05:00
parent a416457520
commit e2f7c63892
8 changed files with 167 additions and 55 deletions

View file

@ -247,8 +247,8 @@ SDL_Surface* GFX_init(int mode) {
ioctl(gfx.fb0_fd, FBIOGET_FSCREENINFO, &gfx.finfo); ioctl(gfx.fb0_fd, FBIOGET_FSCREENINFO, &gfx.finfo);
ioctl(gfx.fb0_fd, FBIOGET_VSCREENINFO, &gfx.vinfo); ioctl(gfx.fb0_fd, FBIOGET_VSCREENINFO, &gfx.vinfo);
gfx.vinfo.bits_per_pixel = FIXED_DEPTH; gfx.vinfo.bits_per_pixel = FIXED_DEPTH;
gfx.vinfo.xres = FIXED_WIDTH; gfx.vinfo.xres = SCREEN_WIDTH;
gfx.vinfo.yres = FIXED_HEIGHT; gfx.vinfo.yres = SCREEN_HEIGHT;
gfx.vinfo.xres_virtual = VIRTUAL_WIDTH; gfx.vinfo.xres_virtual = VIRTUAL_WIDTH;
gfx.vinfo.yres_virtual = VIRTUAL_HEIGHT; gfx.vinfo.yres_virtual = VIRTUAL_HEIGHT;
gfx.vinfo.xoffset = 0; gfx.vinfo.xoffset = 0;
@ -275,7 +275,7 @@ SDL_Surface* GFX_init(int mode) {
gfx.fb0_buffer = mmap(0, gfx.finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, gfx.fb0_fd, 0); gfx.fb0_buffer = mmap(0, gfx.finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, gfx.fb0_fd, 0);
memset(gfx.fb0_buffer, 0, VIRTUAL_SIZE); // clear both buffers memset(gfx.fb0_buffer, 0, VIRTUAL_SIZE); // clear both buffers
gfx.screen = SDL_CreateRGBSurfaceFrom(gfx.fb0_buffer + (gfx.page * PAGE_SIZE), FIXED_WIDTH,FIXED_HEIGHT, FIXED_DEPTH,FIXED_PITCH, 0,0,0,0); gfx.screen = SDL_CreateRGBSurfaceFrom(gfx.fb0_buffer + (gfx.page * PAGE_SIZE), SCREEN_WIDTH,SCREEN_HEIGHT, FIXED_DEPTH,SCREEN_PITCH, 0,0,0,0);
////////////////////////////// //////////////////////////////
@ -329,10 +329,10 @@ void GFX_quit(void) {
// restore for other binaries // restore for other binaries
gfx.vinfo.bits_per_pixel = FIXED_DEPTH; gfx.vinfo.bits_per_pixel = FIXED_DEPTH;
gfx.vinfo.xres = FIXED_WIDTH; gfx.vinfo.xres = SCREEN_WIDTH;
gfx.vinfo.yres = FIXED_HEIGHT; gfx.vinfo.yres = SCREEN_HEIGHT;
gfx.vinfo.xres_virtual = FIXED_WIDTH; gfx.vinfo.xres_virtual = SCREEN_WIDTH;
gfx.vinfo.yres_virtual = FIXED_HEIGHT; gfx.vinfo.yres_virtual = SCREEN_HEIGHT;
gfx.vinfo.xoffset = 0; gfx.vinfo.xoffset = 0;
gfx.vinfo.yoffset = 0; gfx.vinfo.yoffset = 0;
if (ioctl(gfx.fb0_fd, FBIOPUT_VSCREENINFO, &gfx.vinfo)) LOG_info("FBIOPUT_VSCREENINFO failed %s\n", strerror(errno)); if (ioctl(gfx.fb0_fd, FBIOPUT_VSCREENINFO, &gfx.vinfo)) LOG_info("FBIOPUT_VSCREENINFO failed %s\n", strerror(errno));
@ -373,6 +373,7 @@ SDL_Surface* GFX_resize(int w, int h, int pitch) {
if (gfx.screen) SDL_FreeSurface(gfx.screen); if (gfx.screen) SDL_FreeSurface(gfx.screen);
gfx.screen = SDL_CreateRGBSurfaceFrom(gfx.fb0_buffer + (gfx.page * PAGE_SIZE), w,h, FIXED_DEPTH,pitch, 0,0,0,0); gfx.screen = SDL_CreateRGBSurfaceFrom(gfx.fb0_buffer + (gfx.page * PAGE_SIZE), w,h, FIXED_DEPTH,pitch, 0,0,0,0);
LOG_info("fb offset: %i end: %i all: %i\n", gfx.screen->pixels - gfx.fb0_buffer, gfx.screen->pixels - gfx.fb0_buffer + PAGE_SIZE, gfx.finfo.smem_len);
memset(gfx.screen->pixels, 0, PAGE_SIZE); memset(gfx.screen->pixels, 0, PAGE_SIZE);
gfx.vinfo.xres = w; gfx.vinfo.xres = w;
@ -1205,12 +1206,7 @@ static void POW_initOverlay(void) {
GFX_blitAsset(ASSET_BLACK_PILL, NULL, pow.overlay, NULL); GFX_blitAsset(ASSET_BLACK_PILL, NULL, pow.overlay, NULL);
SDL_SetAlpha(gfx.assets, SDL_SRCALPHA,0); SDL_SetAlpha(gfx.assets, SDL_SRCALPHA,0);
GFX_blitBattery(pow.overlay, NULL); GFX_blitBattery(pow.overlay, NULL);
// SDL_Rect rect = asset_rects[ASSET_BATTERY];
// int ox = (SCALE1(PILL_SIZE) - (rect.w + SCREEN_SCALE)) / 2;
// int oy = (SCALE1(PILL_SIZE) - rect.h) / 2;
// GFX_blitAsset(ASSET_BATTERY_LOW, NULL, pow.overlay, &(SDL_Rect){ox,oy});
// setup overlay // setup overlay
memset(&pow.oargs, 0, sizeof(struct owlfb_overlay_args)); memset(&pow.oargs, 0, sizeof(struct owlfb_overlay_args));
pow.oargs.fb_id = OVERLAY_FB; pow.oargs.fb_id = OVERLAY_FB;
@ -1220,7 +1216,7 @@ static void POW_initOverlay(void) {
int x,y,w,h; int x,y,w,h;
w = h = pow.overlay->w; w = h = pow.overlay->w;
x = FIXED_WIDTH - SCALE1(PADDING) - w; x = SCREEN_WIDTH - SCALE1(PADDING) - w;
y = SCALE1(PADDING); y = SCALE1(PADDING);
pow.oinfo.mem_off = offset; pow.oinfo.mem_off = offset;
@ -1370,7 +1366,7 @@ void POW_update(int* _dirty, int* _show_setting, POW_callback_t before_sleep, PO
} }
#define MENU_DELAY 250 // also in PAD_tappedMenu() #define MENU_DELAY 250 // also in PAD_tappedMenu()
if (PAD_justRepeated(BTN_PLUS) || PAD_justRepeated(BTN_MINUS) || (PAD_isPressed(BTN_MENU) && now-menu_start>=MENU_DELAY)) { if (PAD_justRepeated(BTN_PLUS) || PAD_justRepeated(BTN_MINUS) || (PAD_isPressed(BTN_MENU) && now-menu_start>=MENU_DELAY && !GetHDMI())) {
setting_start = now; setting_start = now;
if (PAD_isPressed(BTN_MENU)) { if (PAD_isPressed(BTN_MENU)) {
show_setting = 1; show_setting = 1;

View file

@ -27,6 +27,11 @@ void LOG_note(int level, const char* fmt, ...);
#define FIXED_PITCH FIXED_WIDTH * FIXED_BPP #define FIXED_PITCH FIXED_WIDTH * FIXED_BPP
#define FIXED_SIZE FIXED_HEIGHT * FIXED_PITCH #define FIXED_SIZE FIXED_HEIGHT * FIXED_PITCH
#define HDMI_WIDTH 1280
#define HDMI_HEIGHT 720
#define HDMI_PITCH HDMI_WIDTH * FIXED_BPP
#define HDMI_SIZE HDMI_HEIGHT * HDMI_PITCH
#define PAGE_COUNT 2 #define PAGE_COUNT 2
#define PAGE_SCALE 2 #define PAGE_SCALE 2
#define PAGE_WIDTH FIXED_WIDTH * PAGE_SCALE #define PAGE_WIDTH FIXED_WIDTH * PAGE_SCALE

View file

@ -26,36 +26,56 @@
#define INPUT_COUNT 2 #define INPUT_COUNT 2
static int inputs[INPUT_COUNT]; static int inputs[INPUT_COUNT];
static struct input_event ev; static struct input_event ev;
static int jack_fd; static int jack_fd;
static pthread_t jack_pt; static pthread_t ports_pt;
#define JACK_STATE_PATH "/sys/class/switch/h2w/state" #define JACK_STATE_PATH "/sys/class/switch/h2w/state"
#define HDMI_STATE_PATH "/sys/class/switch/hdmi/state" // TODO: #define HDMI_STATE_PATH "/sys/class/switch/hdmi/state"
static void* watchJack(void *arg) { int getInt(char* path) {
uint32_t has_headphones; int i = 0;
uint32_t had_headphones; FILE *file = fopen(path, "r");
if (file!=NULL) {
fscanf(file, "%i", &i);
fclose(file);
}
return i;
}
// TODO: test HDMI connect/disconnect
// TODO: still resetting between launches
static void* watchPorts(void *arg) {
int has_headphones,had_headphones;
int has_hdmi,had_hdmi;
FILE *file = fopen(JACK_STATE_PATH, "r"); has_headphones = had_headphones = getInt(JACK_STATE_PATH);
fscanf(file, "%i", &has_headphones); has_hdmi = had_hdmi = getInt(HDMI_STATE_PATH);
had_headphones = has_headphones;
SetJack(has_headphones); SetJack(has_headphones);
SetHDMI(has_hdmi);
while(1) { while(1) {
sleep(1); sleep(1);
rewind(file);
fscanf(file, "%i", &has_headphones); has_headphones = getInt(JACK_STATE_PATH);
if (had_headphones!=has_headphones) { if (had_headphones!=has_headphones) {
had_headphones = has_headphones; had_headphones = has_headphones;
SetJack(has_headphones); SetJack(has_headphones);
} }
has_hdmi = getInt(HDMI_STATE_PATH);
if (had_hdmi!=has_hdmi) {
had_hdmi = has_hdmi;
SetHDMI(has_hdmi);
}
} }
return 0; return 0;
} }
int main (int argc, char *argv[]) { int main (int argc, char *argv[]) {
InitSettings(); InitSettings();
pthread_create(&jack_pt, NULL, &watchJack, NULL); pthread_create(&ports_pt, NULL, &watchPorts, NULL);
char path[32]; char path[32];
for (int i=0; i<INPUT_COUNT; i++) { for (int i=0; i<INPUT_COUNT; i++) {

View file

@ -3,6 +3,7 @@
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <linux/fb.h>
#include <errno.h> #include <errno.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <dlfcn.h> #include <dlfcn.h>
@ -12,30 +13,38 @@
/////////////////////////////////////// ///////////////////////////////////////
#define SETTINGS_VERSION 2
typedef struct Settings { typedef struct Settings {
int version; // future proofing int version; // future proofing
int brightness; int brightness;
int headphones; int headphones;
int speaker; int speaker;
int unused[3]; // for future use int unused[2]; // for future use
int jack; // NOTE: doesn't really need to be persisted but still needs to be shared // NOTE: doesn't really need to be persisted but still needs to be shared
int jack;
int hdmi;
} Settings; } Settings;
static Settings DefaultSettings = { static Settings DefaultSettings = {
.version = 1, .version = SETTINGS_VERSION,
.brightness = 2, .brightness = 2,
.headphones = 4, .headphones = 4,
.speaker = 8, .speaker = 8,
.jack = 0, .jack = 0,
.hdmi = 0,
}; };
static Settings* settings; static Settings* settings;
#define SHM_KEY "/SharedSettings" #define SHM_KEY "/SharedSettings"
// static char SettingsPath[256];
static char* SettingsPath = "/mnt/sdcard/.userdata/rg35xx/msettings.bin"; static char* SettingsPath = "/mnt/sdcard/.userdata/rg35xx/msettings.bin";
static int shm_fd = -1; static int shm_fd = -1;
static int is_host = 0; static int is_host = 0;
static int shm_size = sizeof(Settings); static int shm_size = sizeof(Settings);
#define BACKLIGHT_PATH "/sys/class/backlight/backlight.2/bl_power"
#define BRIGHTNESS_PATH "/sys/class/backlight/backlight.2/brightness"
#define VOLUME_PATH "/sys/class/volume/value"
#define MIRROR_PATH "/sys/class/graphics/fb0/mirror_to_hdmi"
void InitSettings(void) { void InitSettings(void) {
// sprintf(SettingsPath, "%s/msettings.bin", getenv("USERDATA_PATH")); // sprintf(SettingsPath, "%s/msettings.bin", getenv("USERDATA_PATH"));
@ -46,7 +55,7 @@ void InitSettings(void) {
settings = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); settings = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
} }
else { // host else { // host
puts("Settings host"); puts("Settings host"); // keymon
is_host = 1; is_host = 1;
// we created it so set initial size and populate // we created it so set initial size and populate
ftruncate(shm_fd, shm_size); ftruncate(shm_fd, shm_size);
@ -55,18 +64,22 @@ void InitSettings(void) {
int fd = open(SettingsPath, O_RDONLY); int fd = open(SettingsPath, O_RDONLY);
if (fd>=0) { if (fd>=0) {
read(fd, settings, shm_size); read(fd, settings, shm_size);
// TODO: use settings->version for future proofing // TODO: use settings->version for future proofing?
close(fd); close(fd);
} }
else { else {
// load defaults // load defaults
memcpy(settings, &DefaultSettings, shm_size); memcpy(settings, &DefaultSettings, shm_size);
} }
// these shouldn't be persisted
// settings->jack = 0;
// settings->hdmi = 0;
} }
printf("brightness: %i\nspeaker: %i \n", settings->brightness, settings->speaker); printf("brightness: %i\nspeaker: %i \n", settings->brightness, settings->speaker);
SetVolume(GetVolume()); SetVolume(GetVolume());
SetBrightness(GetBrightness()); SetBrightness(GetBrightness()); // also sets HDMI
} }
void QuitSettings(void) { void QuitSettings(void) {
munmap(settings, shm_size); munmap(settings, shm_size);
@ -117,26 +130,60 @@ void SetVolume(int value) {
} }
void SetRawBrightness(int val) { // 0 - 1024 void SetRawBrightness(int val) { // 0 - 1024
int fd = open("/sys/class/backlight/backlight.2/brightness", O_WRONLY); // printf("SetRawBrightness(%i)\n", val); fflush(stdout);
int fd = open(BRIGHTNESS_PATH, O_WRONLY);
if (fd>=0) { if (fd>=0) {
dprintf(fd,"%d",val); dprintf(fd,"%d",val);
close(fd); close(fd);
} }
// this prevents exiting/launching from turning the screen back on
SetHDMI(GetHDMI()); // TODO: isn't working
} }
void SetRawVolume(int val) { // 0 - 40 void SetRawVolume(int val) { // 0 - 40
int fd = open("/sys/class/volume/value", O_WRONLY); int fd = open(VOLUME_PATH, O_WRONLY);
if (fd>=0) { if (fd>=0) {
dprintf(fd,"%d",val); dprintf(fd,"%d",val);
close(fd); close(fd);
} }
} }
// monitored and set by thread in keymon
int GetJack(void) { int GetJack(void) {
// return /sys/class/switch/h2w/state==1`
// access("/dev/dsp1", F_OK)==0
return settings->jack; return settings->jack;
} }
void SetJack(int value) { // monitored and set by thread in keymon void SetJack(int value) {
// printf("SetJack(%i)\n", value); fflush(stdout);
settings->jack = value; settings->jack = value;
SetVolume(GetVolume()); SetVolume(GetVolume());
}
// TODO: tmp, testing hdmi without an hdmi output
// int fd = open(BACKLIGHT_PATH, O_WRONLY);
// if (fd>=0) {
// dprintf(fd,"%d",value ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
// close(fd);
// }
}
int GetHDMI(void) {
// TODO: tmp, testing hdmi without an hdmi output
// return settings->jack;
return settings->hdmi;
}
void SetHDMI(int value) {
// printf("SetHDMI(%i)\n", value); fflush(stdout);
settings->hdmi = value;
int fd = open(MIRROR_PATH, O_WRONLY);
if (fd>=0) {
dprintf(fd,"%d",value);
close(fd);
}
fd = open(BACKLIGHT_PATH, O_WRONLY);
if (fd>=0) {
dprintf(fd,"%d",value ? FB_BLANK_POWERDOWN : FB_BLANK_UNBLANK);
close(fd);
}
}

View file

@ -16,4 +16,7 @@ void SetVolume(int value); // 0-20
int GetJack(void); int GetJack(void);
void SetJack(int value); // 0-1 void SetJack(int value); // 0-1
int GetHDMI(void);
void SetHDMI(int value); // 0-1
#endif // __msettings_h__ #endif // __msettings_h__

View file

@ -1205,12 +1205,16 @@ void Input_init(const struct retro_input_descriptor *vars) {
} }
} }
puts("---------------------------------");
for (int i=0;default_button_mapping[i].name; i++) { for (int i=0;default_button_mapping[i].name; i++) {
ButtonMapping* mapping = &default_button_mapping[i]; ButtonMapping* mapping = &default_button_mapping[i];
LOG_info("DEFAULT %s (%s): <%s>\n", core_button_names[mapping->retro], mapping->name, (mapping->local==BTN_ID_NONE ? "NONE" : device_button_names[mapping->local])); LOG_info("DEFAULT %s (%s): <%s>\n", core_button_names[mapping->retro], mapping->name, (mapping->local==BTN_ID_NONE ? "NONE" : device_button_names[mapping->local]));
mapping->name = (char*)core_button_names[mapping->retro]; mapping->name = (char*)core_button_names[mapping->retro];
} }
puts("---------------------------------");
for (int i=0; config.controls[i].name; i++) { for (int i=0; config.controls[i].name; i++) {
ButtonMapping* mapping = &config.controls[i]; ButtonMapping* mapping = &config.controls[i];
mapping->default_ = mapping->local; mapping->default_ = mapping->local;
@ -2306,10 +2310,13 @@ static void selectScaler_AR(int width, int height, int pitch) {
int src_w = width; int src_w = width;
int src_h = height; int src_h = height;
int scale_x = CEIL_DIV(FIXED_WIDTH, src_w); int has_hdmi = GetHDMI();
int scale_y = CEIL_DIV(FIXED_HEIGHT,src_h); int scale_x = CEIL_DIV(has_hdmi ? HDMI_WIDTH : SCREEN_WIDTH, src_w);
int scale_y = CEIL_DIV(has_hdmi ? HDMI_HEIGHT: SCREEN_HEIGHT,src_h);
int scale = MAX(scale_x, scale_y); int scale = MAX(scale_x, scale_y);
// TODO: this "logic" is a disaster
// if (scale>6) scale = 6; // if (scale>6) scale = 6;
// else // else
if (scale>2) scale = 4; // TODO: pillar/letterboxing at 3x produces vertical banding (some kind of alignment issue?) if (scale>2) scale = 4; // TODO: pillar/letterboxing at 3x produces vertical banding (some kind of alignment issue?)
@ -2328,20 +2335,47 @@ static void selectScaler_AR(int width, int height, int pitch) {
char scaler_name[8]; char scaler_name[8];
#define FOUR_THREE 4.0f / 3 #define FOUR_THREE 4.0f / 3
#define SIXTEEN_NINE 16.0 / 9
double target_ratio = has_hdmi ? SIXTEEN_NINE : FOUR_THREE;
if (screen_scaling==1) { if (screen_scaling==1) {
sprintf(scaler_name, "AR_%iX", scale); sprintf(scaler_name, "AR_%iX%s", scale, has_hdmi?"W":"R");
if (core.aspect_ratio==FOUR_THREE) { if (core.aspect_ratio==target_ratio) {
// LOG_info("already 4:3\n"); // LOG("already correct ratio\n");
}
else if (core.aspect_ratio<FOUR_THREE) {
// LOG_info("pillarbox\n");
target_w = CEIL_DIV(src_h, 3) * 4 * scale;
target_h = src_h * scale;
} }
else { else {
// LOG_info("letterbox\n"); // TODO: is this ignoring the core's desired aspect ratio?
target_w = src_w * scale;
target_h = CEIL_DIV(src_w, 4) * 3 * scale; int ratio_left = target_ratio==SIXTEEN_NINE ? 16 : 4;
int ratio_right = target_ratio==SIXTEEN_NINE ? 9 : 3;
// TODO: why do these use CEIL_DIV?
if (core.aspect_ratio<target_ratio) {
// LOG_info("pillarbox\n");
target_w = CEIL_DIV(src_h, ratio_right) * ratio_left * scale;
target_h = src_h * scale;
}
else if (core.aspect_ratio>target_ratio) {
// LOG_info("letterbox\n");
target_w = src_w * scale;
target_h = CEIL_DIV(src_w, ratio_left) * ratio_right * scale;
}
// TODO:
if (target_w > PAGE_WIDTH) {
target_h = CEIL_DIV(PAGE_WIDTH, ratio_left) * ratio_right;
target_w = PAGE_WIDTH;
if (dst_h > target_h) {
scale -= 1;
dst_w = src_w * scale;
dst_h = src_h * scale;
}
sprintf(scaler_name, "AR_%iXD", scale);
LOG_warn("target: %ix%i (%i) page: %ix%i (%i) \n", target_w,target_h, target_w*target_h*FIXED_BPP, PAGE_WIDTH,PAGE_HEIGHT,PAGE_SIZE);
}
} }
} }
else { else {
@ -2350,17 +2384,23 @@ static void selectScaler_AR(int width, int height, int pitch) {
if (target_w%2) target_w += 1; if (target_w%2) target_w += 1;
if (target_h%2) target_h += 1; if (target_h%2) target_h += 1;
int dx = (target_w - dst_w) / 2; int dx = (target_w - dst_w) / 2;
int dy = (target_h - dst_h) / 2; int dy = (target_h - dst_h) / 2;
// TODO: this masks a larger problem above
if (dx<0) dx = 0;
if (dy<0) dy = 0;
int target_pitch = target_w * FIXED_BPP; int target_pitch = target_w * FIXED_BPP;
renderer.dst_w = target_w; renderer.dst_w = target_w;
renderer.dst_h = target_h; renderer.dst_h = target_h;
renderer.dst_p = target_pitch; renderer.dst_p = target_pitch;
renderer.dst_offset = (dy * target_pitch) + (dx * FIXED_BPP); renderer.dst_offset = (dy * target_pitch) + (dx * FIXED_BPP);
if (has_hdmi) LOG_warn("dst offset: %i,%i (%i)\n", dx,dy, renderer.dst_offset);
switch (scale) { switch (scale) {
case 6: renderer.scaler = scale6x_n16; break; case 6: renderer.scaler = scale6x_n16; break;
case 5: renderer.scaler = scale5x_n16; break; case 5: renderer.scaler = scale5x_n16; break;

View file

@ -5,7 +5,7 @@ static CoreOverrides fceumm_overrides = {
.option_overrides = (OptionOverride[]){ .option_overrides = (OptionOverride[]){
{"fceumm_sndquality", "High"}, {"fceumm_sndquality", "High"},
{"fceumm_sndvolume", "10"}, {"fceumm_sndvolume", "10"},
{"fceumm_turbo_enable", "Player 1"}, // stupidly defaults to None {"fceumm_turbo_enable", "Player 1"}, // stupidly defaults to None despite binding turbo buttons by default
{"fceumm_show_adv_system_options","disabled",1}, // doesn't do anything {"fceumm_show_adv_system_options","disabled",1}, // doesn't do anything
{"fceumm_show_adv_sound_options","disabled", 1}, // doesn't do anything {"fceumm_show_adv_sound_options","disabled", 1}, // doesn't do anything
{NULL,NULL}, {NULL,NULL},

View file

@ -3,6 +3,7 @@
Please see the README.txt in the zip file for installation and update instructions. Please see the README.txt in the zip file for installation and update instructions.
*base* *base*
- initial hdmi support
- fixed input mapping for cores with different input modes - fixed input mapping for cores with different input modes
- fixed late loading of configs (was preventing boot logos for certain systems) - fixed late loading of configs (was preventing boot logos for certain systems)
- fixed flicker at low end of screen brightness ramp - fixed flicker at low end of screen brightness ramp