Compare commits

..

No commits in common. "master" and "beta_2024-07-30" have entirely different histories.

8 changed files with 146 additions and 326 deletions

View File

@ -2,6 +2,7 @@ name: C/C++ CI
on:
push:
pull_request:
workflow_dispatch:
jobs:

View File

@ -8,14 +8,10 @@ open_agb_firm is also a complete and better alternative to GBA VC injects (AGB_F
* User configuration, such as gamma settings
* Button remapping
* Border support for 1:1 scaling mode
* Gamma correction to fix the washed out look of games
* Color correction to mimic the look of the GBA/DS phat LCD
* And more to come!
Unlike AGB_FIRM open_agb_firm is not affected by the famous bug where the video output wraps around leaving garbled lines at the bottom of the screen. SD cluster size doesn't matter.
## Disclaimer
open_agb_firm is currently in beta. While open_agb_firm is relatively stable and safe to use, some quirks that have not been fixed. See [Known Issues](#known-issues) for more information.
open_agb_firm is currently in alpha. While open_agb_firm is relatively stable and safe to use, some quirks that have not been fixed. See [Known Issues](#known-issues) for more information.
Additionally, we are not responsible for any damage that may occur to your system as a direct or indirect result of you using open_agb_firm.
@ -49,7 +45,7 @@ Settings are stored in `/3ds/open_agb_firm/config.ini`.
### General
General settings.
`u8 backlight` - Backlight brightness in luminance (cd/m²).
`u8 backlight` - Backlight brightness in luminance (cd/m²)
* Default: `64`
* Possible values:
* Old 3DS: `20`-`117`
@ -57,56 +53,44 @@ General settings.
* Values ≤`64` are recommended.
* Hardware calibration from your CTRNAND is required to get the correct brightness for both LCDs.
`u8 backlightSteps` - How much to adjust backlight brightness by.
`u8 backlightSteps` - How much to adjust backlight brightness by
* Default: `5`
`bool directBoot` - Skip GBA BIOS intro at game startup.
`bool directBoot` - Skip GBA BIOS intro at game startup
* Default: `false`
`bool useGbaDb` - Use `gba_db.bin` to get save types.
* Default: `true`
`bool useSavesFolder` - Use `/3ds/open_agb_firm/saves` for save files instead of the ROM directory.
`bool useGbaDb` - Use `gba_db.bin` to get save types
* Default: `true`
### Video
Video-related settings.
`string scaler` - Video scaler.
* Default: `matrix`
* Options: `none`, `bilinear`, `matrix`
`u8 scaler` - Video scaler. 0 = none, 1 = bilinear, 2 = hardware.
* Default: `2`
`string colorProfile` - Color correction profile.
* Default: `none`
* Options:
* `none` Disable all color correction options.
* `gba` Game Boy Advance.
* `gb_micro` Game Boy micro.
* `gba_sp101` Game Boy Advance SP (AGS-101).
* `nds` Nintendo DS (DS phat).
* `ds_lite` Nintendo DS lite.
* `nso` Nintendo Switch Online.
* `vba` Visual Boy Advance/No$GBA full.
* `identity` No color space conversion.
* If you just want less saturated colors or to change other basic settings like contrast or brightness then set this to `identity`.
* Due to most 2/3DS LCDs not being calibrated correctly from factory the look may not match exactly what you see on real hardware.
* Due to a lot of extra RAM access and extra CPU processing per frame, battery runtime is affected with color profiles other than `none`.
`float gbaGamma` - GBA input gamma
* Default: `2.2`
`float contrast` - Screen gain. No effect when `colorProfile=none`.
`float lcdGamma` - Output LCD gamma
* Default : `1.54`
`float contrast` - Screen gain
* Default: `1.0`
`float brightness` - Screen lift. No effect when `colorProfile=none`.
`float brightness` - Screen lift
* Default: `0.0`
`float saturation` - Screen saturation. No effect when `colorProfile=none`.
* Default: `1.0`
`string colorProfile` - Color correction profile. `none`, `gba`, `nds` or `nds_white`.
* Default: `none`
* For the gba profile it's recommended to adjust lcdGamma to match a GBA. For New 3DS XL with IPS LCD roughly 1.8 is good.
* Due to most 2/3DS LCDs not being calibrated correctly from factory the look may not match exactly what you see on a real GBA.
* Due to a lot of extra RAM access and up to 6.3 ms (worst case for scaler=2) of extra CPU processing time per frame, battery run time is affected with color profiles other than none.
### Audio
Audio settings.
`string audioOut` - Audio output.
* Default: `auto`
* Options: `auto`, `speakers`, `headphones`
`u8 audioOut` - Audio output. 0 = auto, 1 = speakers, 2 = headphones.
* Default: `0`
`s8 volume` - Audio volume. Values above 48 mean control via volume slider. Range -128 (muted) to -20 (100%). Avoid the range -19 to 48.
* Default: `127`
@ -148,7 +132,7 @@ Note that button mappings can cause input lag of up to 1 frame depending on when
`L` - Button map for the L button.
* Default: `none`
Example which maps the D-Pad and Circle-Pad to the GBA D-Pad:
Example:
```
[input]
RIGHT=RIGHT,CP_RIGHT
@ -160,38 +144,29 @@ DOWN=DOWN,CP_DOWN
### Game
Game-specific settings. Only intended to be used in the per-game settings (romName.ini in `/3ds/open_agb_firm/saves`).
`u8 saveSlot` - Savegame slot (0-9).
`u8 saveSlot` - Savegame slot (0-9)
* Default: `0`
`string saveType` - Override to use a specific save type.
* Default: `auto`
* Options starting with `rom_256m` are intended for 32 MiB games. Options ending with `rtc` enable the hardware real-time clock:
* `eeprom_8k`
* `rom_256m_eeprom_8k`
* `eeprom_64k`
* `rom_256m_eeprom_64k`
* `flash_512k_atmel_rtc`
* `flash_512k_atmel`
* `flash_512k_sst_rtc`
* `flash_512k_sst`
* `flash_512k_panasonic_rtc`
* `flash_512k_panasonic`
* `flash_1m_macronix_rtc`
* `flash_1m_macronix`
* `flash_1m_sanyo_rtc`
* `flash_1m_sanyo`
* `sram_256k`
* `none`
* `auto`
`u8 saveType` - Override to use a specific save type, see values for `defaultSave` (0-15, 255)
* Default: `255` (disabled)
### Advanced
Options for advanced users. No pun intended.
`bool saveOverride` - Open save type override menu after selecting a game.
`bool saveOverride` - Open save type override menu after selecting a game
* Default: `false`
`string defaultSave` - Save type default when save type is not in `gba_db.bin` and cannot be autodetected. Same options as for `saveType` above except `auto` is not supported.
* Default: `sram_256k`
`u16 defaultSave` - Change save type default when save type is not in `gba_db.bin` and cannot be autodetected
* Default: `14` (SRAM 256k)
* Possible values:
* `0`, `1`: EEPROM 8k
* `2`, `3`: EEPROM 64k
* `4`, `6`, `8`: Flash 512k RTC
* `5`, `7`, `9`: Flash 512k
* `10`, `12`: Flash 1m RTC
* `11`, `13`: Flash 1m
* `14`: SRAM 256k
* `15`: None
## Patches
open_agb_firm supports automatically applying IPS and UPS patches. To use a patch, rename the patch file to match the ROM file name (without the extension).

View File

@ -39,14 +39,14 @@ typedef struct
u8 backlightSteps;
bool directBoot;
bool useGbaDb;
bool useSavesFolder;
// [video]
u8 scaler; // 0 = 1:1/none, 1 = bilinear (GPU) x1.5, 2 = matrix (hardware) x1.5.
u8 colorProfile; // 0 = none, 1 = GBA, 2 = GB micro, 3 = GBA SP (AGS-101), 4 = DS phat, 5 = DS lite, 6 = Nintendo Switch Online, 7 = Visual Boy Advance/No$GBA, 8 = identity.
float contrast; // Range 0.0-1.0.
float brightness; // Range 0.0-1.0.
float saturation; // Range 0.0-1.0.
u8 scaler; // 0 = 1:1, 1 = bilinear (GPU) x1.5, 2 = matrix (hardware) x1.5.
float gbaGamma;
float lcdGamma;
float contrast;
float brightness;
u8 colorProfile; // 0 = none, 1 = GBA, 2 = DS phat, 3 = DS phat white.
// [audio]
u8 audioOut; // 0 = auto, 1 = speakers, 2 = headphones.
@ -62,10 +62,11 @@ typedef struct
// [advanced]
bool saveOverride;
u16 defaultSave; // TODO: Should be u8. Investigate if u8 has any downsides.
u16 defaultSave;
} OafConfig;
//static_assert(sizeof(OafConfig) == 76, "nope");
extern OafConfig g_oafConfig; // Global config in config.c.
extern OafConfig g_oafConfig;

@ -1 +1 @@
Subproject commit f6717f66858634b677ed695ee346a89db7684b43
Subproject commit 6259b6b8ffe4bf82481dc93aeadbcc96738c2b9f

View File

@ -30,23 +30,20 @@
"backlight=64\n" \
"backlightSteps=5\n" \
"directBoot=false\n" \
"useGbaDb=true\n" \
"useSavesFolder=true\n\n" \
\
"useGbaDb=true\n\n" \
"[video]\n" \
"scaler=matrix\n" \
"colorProfile=none\n" \
"scaler=2\n" \
"gbaGamma=2.2\n" \
"lcdGamma=1.54\n" \
"contrast=1.0\n" \
"brightness=0.0\n" \
"saturation=1.0\n\n" \
\
"colorProfile=none\n\n" \
"[audio]\n" \
"audioOut=auto\n" \
"audioOut=0\n" \
"volume=127\n\n" \
\
"[advanced]\n" \
"saveOverride=false\n" \
"defaultSave=sram_256k"
"defaultSave=14"
@ -58,14 +55,14 @@ OafConfig g_oafConfig =
5, // backlightSteps
false, // directBoot
true, // useGbaDb
true, // useSavesFolder
// [video]
2, // scaler
0, // colorProfile
2.2f, // gbaGamma
1.54f, // lcdGamma
1.f, // contrast
0.f, // brightness
1.f, // saturation
0, // colorProfile
// [audio]
0, // Automatic audio output.
@ -87,7 +84,7 @@ OafConfig g_oafConfig =
// [game]
0, // saveSlot
255, // saveType
0xFF, // saveType
// [advanced]
false, // saveOverride
@ -132,7 +129,7 @@ static u32 parseButtons(const char *str)
return map & ~(1u<<12);
}
static int cfgIniCallback(void *user, const char *section, const char *name, const char *value)
static int cfgIniCallback(void* user, const char* section, const char* name, const char* value)
{
OafConfig *const config = (OafConfig*)user;
@ -146,61 +143,37 @@ static int cfgIniCallback(void *user, const char *section, const char *name, con
config->directBoot = (strcmp(value, "false") == 0 ? false : true);
else if(strcmp(name, "useGbaDb") == 0)
config->useGbaDb = (strcmp(value, "true") == 0 ? true : false);
else if(strcmp(name, "useSavesFolder") == 0)
config->useSavesFolder = (strcmp(value, "true") == 0 ? true : false);
}
else if(strcmp(section, "video") == 0)
{
if(strcmp(name, "scaler") == 0)
{
if(strcmp(value, "none") == 0)
config->scaler = 0;
else if(strcmp(value, "bilinear") == 0)
config->scaler = 1;
else if(strcmp(value, "matrix") == 0)
config->scaler = 2;
}
config->scaler = (u8)strtoul(value, NULL, 10);
else if(strcmp(name, "gbaGamma") == 0)
config->gbaGamma = str2float(value);
else if(strcmp(name, "lcdGamma") == 0)
config->lcdGamma = str2float(value);
else if(strcmp(name, "contrast") == 0)
config->contrast = str2float(value);
else if(strcmp(name, "brightness") == 0)
config->brightness = str2float(value);
else if(strcmp(name, "colorProfile") == 0)
{
if(strcmp(value, "none") == 0)
config->colorProfile = 0;
else if(strcmp(value, "gba") == 0)
config->colorProfile = 1;
else if(strcmp(value, "gb_micro") == 0)
config->colorProfile = 2;
else if(strcmp(value, "gba_sp101") == 0)
config->colorProfile = 3;
else if(strcmp(value, "nds") == 0)
config->colorProfile = 4;
else if(strcmp(value, "ds_lite") == 0)
config->colorProfile = 5;
else if(strcmp(value, "nso") == 0)
config->colorProfile = 6;
else if(strcmp(value, "vba") == 0)
config->colorProfile = 7;
else if(strcmp(value, "identity") == 0)
config->colorProfile = 8;
config->colorProfile = 2;
else if(strcmp(value, "nds_white") == 0)
config->colorProfile = 3;
//else if(strcmp(value, "custom") == 0) // TODO: Implement user provided profile.
// config->colorProfile = 9;
// config->colorProfile = 4;
}
else if(strcmp(name, "contrast") == 0)
config->contrast = str2float(value);
else if(strcmp(name, "brightness") == 0)
config->brightness = str2float(value);
else if(strcmp(name, "saturation") == 0)
config->saturation = str2float(value);
}
else if(strcmp(section, "audio") == 0)
{
if(strcmp(name, "audioOut") == 0)
{
if(strcmp(value, "auto") == 0)
config->audioOut = 0;
else if(strcmp(value, "speakers") == 0)
config->audioOut = 1;
else if(strcmp(value, "headphones") == 0)
config->audioOut = 2;
}
config->audioOut = (u8)strtoul(value, NULL, 10);
else if(strcmp(name, "volume") == 0)
config->volume = (s8)strtol(value, NULL, 10);
}
@ -220,82 +193,14 @@ static int cfgIniCallback(void *user, const char *section, const char *name, con
if(strcmp(name, "saveSlot") == 0)
config->saveSlot = (u8)strtoul(value, NULL, 10);
if(strcmp(name, "saveType") == 0)
{
if(strcmp(value, "eeprom_8k") == 0)
config->saveType = 0;
if(strcmp(value, "rom_256m_eeprom_8k") == 0)
config->saveType = 1;
if(strcmp(value, "eeprom_64k") == 0)
config->saveType = 2;
if(strcmp(value, "rom_256m_eeprom_64k") == 0)
config->saveType = 3;
if(strcmp(value, "flash_512k_atmel_rtc") == 0)
config->saveType = 4;
if(strcmp(value, "flash_512k_atmel") == 0)
config->saveType = 5;
if(strcmp(value, "flash_512k_sst_rtc") == 0)
config->saveType = 6;
if(strcmp(value, "flash_512k_sst") == 0)
config->saveType = 7;
if(strcmp(value, "flash_512k_panasonic_rtc") == 0)
config->saveType = 8;
if(strcmp(value, "flash_512k_panasonic") == 0)
config->saveType = 9;
if(strcmp(value, "flash_1m_macronix_rtc") == 0)
config->saveType = 10;
if(strcmp(value, "flash_1m_macronix") == 0)
config->saveType = 11;
if(strcmp(value, "flash_1m_sanyo_rtc") == 0)
config->saveType = 12;
if(strcmp(value, "flash_1m_sanyo") == 0)
config->saveType = 13;
if(strcmp(value, "sram_256k") == 0)
config->saveType = 14;
if(strcmp(value, "none") == 0)
config->saveType = 15;
if(strcmp(value, "auto") == 0)
config->saveType = 255;
}
config->saveType = (u8)strtoul(value, NULL, 10);
}
else if(strcmp(section, "advanced") == 0)
{
if(strcmp(name, "saveOverride") == 0)
config->saveOverride = (strcmp(value, "false") == 0 ? false : true);
if(strcmp(name, "defaultSave") == 0)
{
if(strcmp(value, "eeprom_8k") == 0)
config->defaultSave = 0;
if(strcmp(value, "rom_256m_eeprom_8k") == 0)
config->defaultSave = 1;
if(strcmp(value, "eeprom_64k") == 0)
config->defaultSave = 2;
if(strcmp(value, "rom_256m_eeprom_64k") == 0)
config->defaultSave = 3;
if(strcmp(value, "flash_512k_atmel_rtc") == 0)
config->defaultSave = 4;
if(strcmp(value, "flash_512k_atmel") == 0)
config->defaultSave = 5;
if(strcmp(value, "flash_512k_sst_rtc") == 0)
config->defaultSave = 6;
if(strcmp(value, "flash_512k_sst") == 0)
config->defaultSave = 7;
if(strcmp(value, "flash_512k_panasonic_rtc") == 0)
config->defaultSave = 8;
if(strcmp(value, "flash_512k_panasonic") == 0)
config->defaultSave = 9;
if(strcmp(value, "flash_1m_macronix_rtc") == 0)
config->defaultSave = 10;
if(strcmp(value, "flash_1m_macronix") == 0)
config->defaultSave = 11;
if(strcmp(value, "flash_1m_sanyo_rtc") == 0)
config->defaultSave = 12;
if(strcmp(value, "flash_1m_sanyo") == 0)
config->defaultSave = 13;
if(strcmp(value, "sram_256k") == 0)
config->defaultSave = 14;
if(strcmp(value, "none") == 0)
config->defaultSave = 15;
}
config->defaultSave = (u16)strtoul(value, NULL, 10);
}
else return 0; // Error.

View File

@ -92,8 +92,7 @@ static Result scanDir(const char *const path, DirList *const dList, const char *
const u32 nameLen = strlen(fis[i].fname);
if(entType == ENT_TYPE_FILE)
{
if(nameLen <= filterLen || strcmp(filter, fis[i].fname + nameLen - filterLen) != 0
|| fis[i].fname[0] == '.')
if(nameLen <= filterLen || strcmp(filter, fis[i].fname + nameLen - filterLen) != 0)
continue;
}

View File

@ -126,73 +126,31 @@ typedef struct
float displayGamma;
} ColorProfile;
// libretro shader values. Credits: hunterk and Pokefan531.
// Last updated 2014-12-03.
static const ColorProfile g_colorProfiles[8] =
static const ColorProfile g_colorProfiles[3] =
{
{ // libretro GBA color (sRGB).
2.2f + (0.3f * 1.6f), // Darken screen. Default 0. Modified to 0.3.
0.91f,
0.905f, 0.195f, -0.1f,
0.1f, 0.65f, 0.25f,
0.1575f, 0.1425f, 0.7f,
1.f / 2.2f
{ // libretro GBA color (sRGB). Credits: hunterk and Pokefan531.
2.f + 0.5f,
0.93f,
0.8f, 0.275f, -0.075f,
0.135f, 0.64f, 0.225f,
0.195f, 0.155f, 0.65f,
1.f / 2.f
},
{ // libretro GB micro color (sRGB).
2.2f,
0.9f,
0.8025f, 0.31f, -0.1125f,
0.1f, 0.6875f, 0.2125f,
0.1225f, 0.1125f, 0.765f,
1.f / 2.2f
},
{ // libretro GBA SP (AGS-101) color (sRGB).
2.2f,
0.935f,
0.96f, 0.11f, -0.07f,
0.0325f, 0.89f, 0.0775f,
0.001f, -0.03f, 1.029f,
1.f / 2.2f
},
{ // libretro NDS color (sRGB).
2.2f,
0.905f,
0.835f, 0.27f, -0.105f,
0.1f, 0.6375f, 0.2625f,
0.105f, 0.175f, 0.72f,
1.f / 2.2f
},
{ // libretro NDS lite color (sRGB).
2.2f,
0.935f,
0.93f, 0.14f, -0.07f,
0.025f, 0.9f, 0.075f,
0.008f, -0.03f, 1.022f,
1.f / 2.2f
},
{ // libretro Nintendo Switch Online color (sRGB).
2.2f + 0.8f, // Darken screen. Default 0.8.
{ // libretro DS phat (sRGB). Credits: hunterk and Pokefan531.
2.f,
1.f,
0.865f, 0.1225f, 0.0125f,
0.0575f, 0.925f, 0.0125f,
0.0575f, 0.1225f, 0.82f,
1.f / 2.2f
0.705f, 0.235f, -0.075f,
0.09f, 0.585f, 0.24f,
0.1075f, 0.1725f, 0.72f,
1.f / 2.f
},
{ // libretro Visual Boy Advance/No$GBA full color.
1.45f + 1.f, // Darken screen. Default 1.
1.f,
0.73f, 0.27f, 0.f,
0.0825f, 0.6775f, 0.24f,
0.0825f, 0.24f, 0.6775f,
1.f / 1.45f
},
{ // Identity.
1.f,
1.f,
1.f, 0.f, 0.f,
0.f, 1.f, 0.f,
0.f, 0.f, 1.f,
1.f / 1.f
{ // libretro DS phat white (sRGB). Credits: hunterk and Pokefan531.
2.f,
0.915f,
0.815f, 0.275f, -0.09f,
0.1f, 0.64f, 0.26f,
0.1075f, 0.1725f, 0.72f,
1.f / 2.f
}
};
@ -201,21 +159,9 @@ ALWAYS_INLINE float clamp_float(const float x, const float min, const float max)
return (x < min ? min : (x > max ? max : x));
}
void makeColorLut(const ColorProfile *const p)
static void makeColorLut(const ColorProfile *const p)
{
const float targetGamma = p->targetGamma;
const float contrast = g_oafConfig.contrast;
const float brightness = g_oafConfig.brightness / contrast;
const float targetContrast = powf(contrast, targetGamma);
// Calculate saturation weights.
// Note: We are using the Rec. 709 luminance vector here.
const float sat = g_oafConfig.saturation;
const float rwgt = (1.f - sat) * 0.2126f;
const float gwgt = (1.f - sat) * 0.7152f;
const float bwgt = (1.f - sat) * 0.0722f;
u32 *const colorLut = (u32*)COLOR_LUT_ADDR;
u32 *colorLut = (u32*)COLOR_LUT_ADDR;
for(u32 i = 0; i < 32768; i++)
{
// Convert to 8-bit and normalize.
@ -224,9 +170,10 @@ void makeColorLut(const ColorProfile *const p)
float r = (float)rgbFive2Eight(i>>10) / 255;
// Convert to linear gamma.
b = powf(b + brightness, targetGamma);
g = powf(g + brightness, targetGamma);
r = powf(r + brightness, targetGamma);
const float targetGamma = p->targetGamma;
b = powf(b, targetGamma);
g = powf(g, targetGamma);
r = powf(r, targetGamma);
// Apply luminance.
const float lum = p->lum;
@ -246,47 +193,39 @@ void makeColorLut(const ColorProfile *const p)
* [rb][gb][ b] [b]
*/
// Assuming no alpha channel in original calculation.
float tmpB = p->rb * r + p->gb * g + p->b * b;
float tmpG = p->rg * r + p->g * g + p->bg * b;
float tmpR = p->r * r + p->gr * g + p->br * b;
float newB = p->rb * r + p->gb * g + p->b * b;
float newG = p->rg * r + p->g * g + p->bg * b;
float newR = p->r * r + p->gr * g + p->br * b;
// Apply saturation.
// Note: Some duplicated muls here. gcc optimizes them out.
b = rwgt * tmpR + gwgt * tmpG + (bwgt + sat) * tmpB;
g = rwgt * tmpR + (gwgt + sat) * tmpG + bwgt * tmpB;
r = (rwgt + sat) * tmpR + gwgt * tmpG + bwgt * tmpB;
b = (b < 0.f ? 0.f : b);
g = (g < 0.f ? 0.f : g);
r = (r < 0.f ? 0.f : r);
newB = (newB < 0.f ? 0.f : newB);
newG = (newG < 0.f ? 0.f : newG);
newR = (newR < 0.f ? 0.f : newR);
// Convert to display gamma.
const float displayGamma = p->displayGamma;
b = powf(targetContrast * b, displayGamma);
g = powf(targetContrast * g, displayGamma);
r = powf(targetContrast * r, displayGamma);
newB = powf(newB, displayGamma);
newG = powf(newG, displayGamma);
newR = powf(newR, displayGamma);
// Denormalize, clamp, convert to ABGR8 and write lut.
u32 entry = 255; // Alpha.
entry |= clamp_s32(lroundf(b * 255), 0, 255)<<8;
entry |= clamp_s32(lroundf(g * 255), 0, 255)<<16;
entry |= clamp_s32(lroundf(r * 255), 0, 255)<<24;
colorLut[i] = entry;
u32 tmp = 0xFF; // Alpha.
tmp |= clamp_s32(lroundf(newB * 255), 0, 255)<<8;
tmp |= clamp_s32(lroundf(newG * 255), 0, 255)<<16;
tmp |= clamp_s32(lroundf(newR * 255), 0, 255)<<24;
*colorLut++ = tmp;
}
flushDCacheRange(colorLut, 4 * 32768);
flushDCacheRange((void*)COLOR_LUT_ADDR, 1024u * 128);
}
static Result dumpFrameTex(void)
{
// Capture a single frame in native resolution.
// Note: This adds 1 frame of delay after pressing the screenshot buttons.
if(LGYCAP_captureFrameUnscaled(LGYCAP_DEV_TOP) != KRES_OK)
return RES_INVALID_ARG;
// Stop LgyCap before dumping the frame to prevent glitches.
LGYCAP_stop(LGYCAP_DEV_TOP);
// A1BGR5 format (alpha ignored).
constexpr u32 alignment = 0x80; // Make PPF happy.
alignas(4) static const BmpV1WithMasks bmpHeaders =
alignas(4) static BmpV1WithMasks bmpHeaders =
{
{
.magic = 0x4D42,
@ -313,12 +252,25 @@ static Result dumpFrameTex(void)
.bMask = 0x003E
};
u32 outDim = PPF_DIM(240, 160);
u32 fileSize = alignment + 240 * 160 * 2;
if(g_oafConfig.scaler > 1)
{
outDim = PPF_DIM(360, 240);
fileSize = alignment + 360 * 240 * 2;
bmpHeaders.header.fileSize = fileSize;
bmpHeaders.dib.width = 360;
bmpHeaders.dib.height = -240;
bmpHeaders.dib.imageSize = 360 * 240 * 2;
}
// Transfer frame data out of the 512x512 texture.
// We will use the currently hidden frame buffer as temporary buffer.
// Note: This is a race with the currently displaying frame buffer
// because we just swapped buffers in the gfx handler function.
u32 *const tmpBuf = GFX_getBuffer(GFX_LCD_TOP, GFX_SIDE_LEFT);
GX_displayTransfer((u32*)GPU_TEXTURE_ADDR, PPF_DIM(512, 160), tmpBuf + (alignment / 4), PPF_DIM(240, 160),
GX_displayTransfer((u32*)GPU_TEXTURE_ADDR, PPF_DIM(512, 240), tmpBuf + (alignment / 4), outDim,
PPF_O_FMT(GX_A1BGR5) | PPF_I_FMT(GX_A1BGR5) | PPF_CROP_EN);
memcpy(tmpBuf, &bmpHeaders, sizeof(bmpHeaders));
GFX_waitForPPF();
@ -330,13 +282,8 @@ static Result dumpFrameTex(void)
// Construct file path from date & time. Then write the file.
char fn[36];
ee_sprintf(fn, OAF_SCREENSHOT_DIR "/%04X_%02X_%02X_%02X_%02X_%02X.bmp",
td.year + 0x2000, td.mon, td.day, td.hour, td.min, td.sec);
const Result res = fsQuickWrite(fn, tmpBuf, bmpHeaders.header.fileSize);
// Clear overwritten texture area in case we overwrote padding (different resolution).
// This is important because padding pixels must be fully transparent to get sharp edges when the GPU renders.
GX_memoryFill((u32*)GPU_TEXTURE_ADDR, PSC_FILL_32_BITS, 512 * 160 * 2, 0, NULL, 0, 0, 0);
GFX_waitForPSC0();
td.y + 0x2000, td.mon, td.d, td.h, td.min, td.s);
const Result res = fsQuickWrite(fn, tmpBuf, fileSize);
// Restart LgyCap.
LGYCAP_start(LGYCAP_DEV_TOP);

View File

@ -211,23 +211,15 @@ static Result showFileBrowser(char romAndSavePath[512])
static void rom2GameCfgPath(char romPath[512])
{
if (g_oafConfig.useSavesFolder)
{
// Extract the file name and change the extension.
// For cfg2SavePath() we need to reserve 2 extra bytes/chars.
char tmpIniFileName[256];
safeStrcpy(tmpIniFileName, strrchr(romPath, '/') + 1, 256 - 2);
strcpy(tmpIniFileName + strlen(tmpIniFileName) - 4, ".ini");
// Extract the file name and change the extension.
// For cfg2SavePath() we need to reserve 2 extra bytes/chars.
char tmpIniFileName[256];
safeStrcpy(tmpIniFileName, strrchr(romPath, '/') + 1, 256 - 2);
strcpy(tmpIniFileName + strlen(tmpIniFileName) - 4, ".ini");
// Construct the new path.
strcpy(romPath, OAF_SAVE_DIR "/");
strcat(romPath, tmpIniFileName);
}
else
{
// Change the extension to .ini.
strcpy(romPath + strlen(romPath) - 4, ".ini");
}
// Construct the new path.
strcpy(romPath, OAF_SAVE_DIR "/");
strcat(romPath, tmpIniFileName);
}
static void gameCfg2SavePath(char cfgPath[512], const u8 saveSlot)