Compare commits

...

34 Commits

Author SHA1 Message Date
profi200 6b882e9395
Don't run actions on pull requests. 2024-12-06 22:37:25 +01:00
profi200 4e4c2aa2b5
Updated all libretro based color profiles.
Added GB micro, GBA SP (AGS-101), DS lite and VBA/No$GBA profiles.
2024-12-04 00:44:14 +01:00
profi200 dd90d498c4
!Breaking change! Changed the config file format to use strings in many places instead of values.
Added a new saturation setting for color profiles other than none.
Removed lcdGamma and displayGamma temporarily.
Updated README.md with all the changes.
2024-12-03 20:34:53 +01:00
Elouan Martinet 877f7c61d0 Add an option to use current rom directory for config and saves 2024-10-28 09:30:36 +01:00
profi200 7a7190baea
[README] Mention that oaf is not affected by the screen wrap bug because people are talking shit. 2024-09-22 15:14:03 +02:00
profi200 edd43a6fcc
Updated libn3ds.
Fixed hang on power off when all backlights are off (libn3ds).
Screenshots are now always in native resolution.
2024-08-30 18:45:24 +02:00
profi200 50e2f30e0d
[README] Mention new features and oaf is no longer in alpha. 2024-08-02 19:35:52 +02:00
profi200 79b2457e8b
[File browser] Filter out all entries starting with a dot. F.....g Mac OS. 2024-07-31 16:19:46 +02:00
profi200 5356e5c89a
Implemented full gamma correction for top LCDs which improves colors noticeably without crushing details in shadows.
The disadvantage is that the gbaGamma, lcdGamma, brightness and contrast settings don't work anymore for now. They will be reimplemented with color profile settings later.
2024-07-30 16:37:05 +02:00
profi200 67ce019b36
Forgot to check the ROM size before adding the padding. 2024-07-26 21:22:40 +02:00
profi200 41db8760a6
Made ROM padding code ~42% faster. 2024-07-26 20:37:49 +02:00
profi200 3a70cdc27f Improved README for screenshots and color correction. 2024-07-25 17:19:41 +02:00
profi200 b1c66e24a2 Added experimental support for true color correction to mimic the look of the crappy GBA LCD. Thanks to hunterk and Pokefan531 for their work on the libretro shaders this is based on. 2024-07-25 17:19:41 +02:00
profi200 f7efd62ea9 Fixed save type selector cursor at the wrong position and added cache flushing. 2024-07-25 17:19:41 +02:00
profi200 073daac2bb Separated video functions into own .c file. 2024-07-25 17:19:41 +02:00
profi200 15436e02a7 Updated submodules.
Fixed compatibility with latest libn3ds.
2024-07-25 17:19:41 +02:00
profi200 3e91716c80 c2x --> c23. Supported since gcc 14.1.0. 2024-07-25 17:19:41 +02:00
profi200 f9786b14a1 Updated libn3ds for a critical GPIO fix. Also updated the other submodules. 2024-07-25 17:19:41 +02:00
profi200 bd5b207106 Updated bitmap.h with comments. 2024-07-25 17:19:41 +02:00
profi200 7c8c3d6fe8 Load GBA scaler matrix from SD if found. 2024-07-25 17:19:41 +02:00
profi200 6cf6059023 Updated libn3ds submodule for the capture card workaround. 2024-07-25 17:19:41 +02:00
profi200 1ad621d4f4 Fixed a file handle leak just introduced by myself. Thanks @PSI-Rockin for finding it. 2024-07-25 17:19:41 +02:00
profi200 9f331c24f7 Use a more safe variant of binary search for the GBA db. 2024-07-25 17:19:41 +02:00
profi200 3ffb706f3b Fixed strict-aliasing warning in save_type.c. 2024-07-25 17:19:41 +02:00
profi200 c85a4b1b57 Fixed strict-aliasing warnings in gpu_cmd_lists.c. 2024-07-25 17:19:41 +02:00
profi200 6db20e190c Added min macro to patch.c because it was removed from libn3ds. 2024-07-25 17:19:41 +02:00
profi200 d7f701f02a Fixed compatibility with latest libn3ds. 2024-07-25 17:19:41 +02:00
profi200 2bca9b04b6 Updated submodules. 2024-07-25 17:19:41 +02:00
profi200 678cbd9ae3
Automatic builds from all branches. 2024-05-19 22:36:52 +02:00
profi200 479069d039
Updated README.md adding the missing [input] to the example. 2024-04-08 15:36:49 +02:00
profi200 99cf0fdacc
Updated to checkout/cache/upload-artifact v4. 2024-02-24 20:58:04 +01:00
Nemris 9d13ad3722 Refactor IPS patching. 2024-02-24 20:45:18 +01:00
Nemris 07bdaf521d Normalize indentation. 2024-02-24 20:45:18 +01:00
profi200 e14704181c
Round the gamma table entries instead of discarding the fraction part. 2024-01-29 17:43:58 +01:00
30 changed files with 1637 additions and 495 deletions

View File

@ -2,11 +2,6 @@ name: C/C++ CI
on:
push:
branches:
- master
pull_request:
branches:
- master
workflow_dispatch:
jobs:
@ -15,7 +10,7 @@ jobs:
container: devkitpro/devkitarm
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: recursive
@ -25,7 +20,7 @@ jobs:
- name: Cache ctr_firm_builder
id: cache-firm-builder
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ctr_firm_builder
key: ${{ runner.os }}-${{ env.FIRM_BUILDER_COMMIT }}
@ -50,7 +45,7 @@ jobs:
echo ${{ github.sha }} >> ./nightly/nightly_commit.txt
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: open_agb_firm_nightly
path: ./nightly

105
README.md
View File

@ -8,10 +8,14 @@ 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 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.
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.
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.
@ -25,7 +29,8 @@ Additionally, we are not responsible for any damage that may occur to your syste
## Controls
A/B/L/R/START/SELECT - GBA buttons, respectively
SELECT+Y - Dump screen output to `/3ds/open_agb_firm/texture_dump.bmp`
SELECT+Y - Dump hardware frame output to `/3ds/open_agb_firm/screenshots/YYYY_MM_DD_HH_MM_SS.bmp`
* The file name is the current date and time from your real-time clock.
* If the screen output freezes, press HOME to fix it. This is a hard to track down bug that will be fixed.
X+UP/DOWN - Adjust screen brightness up or down by `backlightSteps` units.
@ -44,45 +49,64 @@ Settings are stored in `/3ds/open_agb_firm/config.ini`.
### General
General settings.
`u8 backlight` - Backlight brightness
`u8 backlight` - Backlight brightness in luminance (cd/m²).
* Default: `64`
* Possible values:
* Old 3DS: `20`-`117`
* New 3DS: `16`-`142`
* 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
`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.
* Default: `true`
### Video
Video-related settings.
`u8 scaler` - Video scaler. 0 = none, 1 = bilinear, 2 = hardware.
* Default: `2`
`string scaler` - Video scaler.
* Default: `matrix`
* Options: `none`, `bilinear`, `matrix`
`float gbaGamma` - GBA input gamma
* Default: `2.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 lcdGamma` - Output LCD gamma
* Default : `1.54`
`float contrast` - Screen gain
`float contrast` - Screen gain. No effect when `colorProfile=none`.
* Default: `1.0`
`float brightness` - Screen lift
`float brightness` - Screen lift. No effect when `colorProfile=none`.
* Default: `0.0`
`float saturation` - Screen saturation. No effect when `colorProfile=none`.
* Default: `1.0`
### Audio
Audio settings.
`u8 audioOut` - Audio output. 0 = auto, 1 = speakers, 2 = headphones.
* Default: `0`
`string audioOut` - Audio output.
* Default: `auto`
* Options: `auto`, `speakers`, `headphones`
`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`
@ -124,8 +148,9 @@ 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:
Example which maps the D-Pad and Circle-Pad to the GBA D-Pad:
```
[input]
RIGHT=RIGHT,CP_RIGHT
LEFT=LEFT,CP_LEFT
UP=UP,CP_UP
@ -135,29 +160,38 @@ 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`
`u8 saveType` - Override to use a specific save type, see values for `defaultSave` (0-15, 255)
* Default: `255` (disabled)
`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`
### 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`
`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
`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`
## 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).
@ -253,8 +287,9 @@ You may use this under the terms of the GNU General Public License GPL v3 or the
* **MAME**
* **No-Intro**
* **Wolfvak, Sono and all the other people in #GodMode9 on freenode/Discord**
* **endrift, Extrems and all the other people in #mgba on freenode**
* **endrift, Extrems and all the other people in #mgba on Libera.Chat**
* **Oleh Prypin (oprypin) for nightly.link**
* **[hunterk and Pokefan531 for their amazing libretro shaders](https://forums.libretro.com/t/real-gba-and-ds-phat-colors/1540/220)**
* ...everyone who contributed to **3dbrew.org**
Copyright (C) 2021 derrek, profi200, d0k3
Copyright (C) 2024 derrek, profi200, d0k3

View File

@ -22,7 +22,7 @@ BUILD := build
SOURCES += ../source ../source/arm11 ../libraries/inih
DATA :=
INCLUDES += ../include ../libraries
DEFINES := -DARM11 -D__3DS__ -DLIBN3DS_LEGACY=1 -DVERS_STRING=\"$(VERS_STRING)\" \
DEFINES := -D__ARM11__ -D__3DS__ -DLIBN3DS_LEGACY=1 -DVERS_STRING=\"$(VERS_STRING)\" \
-DVERS_MAJOR=$(VERS_MAJOR) -DVERS_MINOR=$(VERS_MINOR)
ASSETS :=
@ -35,11 +35,11 @@ endif
#---------------------------------------------------------------------------------
ARCH := -march=armv6k+vfpv2 -mtune=mpcore -mfloat-abi=hard -mtp=soft -marm -mthumb-interwork -masm-syntax-unified
CFLAGS := $(ARCH) -std=c17 -O2 -gdwarf-4 -flto -mword-relocations \
CFLAGS := $(ARCH) -std=c23 -O2 -gdwarf-4 -flto -mword-relocations \
-ffunction-sections -fno-math-errno -Wall -Wextra
CFLAGS += $(INCLUDE) $(DEFINES)
CXXFLAGS := $(ARCH) -std=c++17 -O2 -gdwarf-4 -flto -fno-rtti -fno-exceptions \
CXXFLAGS := $(ARCH) -std=c++23 -O2 -gdwarf-4 -flto -fno-rtti -fno-exceptions \
-mword-relocations -ffunction-sections -fno-math-errno -Wall -Wextra
CXXFLAGS += $(INCLUDE) $(DEFINES)

View File

@ -21,8 +21,8 @@ include $(TOPDIR)/../libraries/libn3ds/libn3ds9.mk
BUILD := build
SOURCES += ../source/arm9
DATA :=
INCLUDES += ../include ../thirdparty
DEFINES := -DARM9 -D__3DS__ -DLIBN3DS_LEGACY=1 -DVERS_STRING=\"$(VERS_STRING)\" \
INCLUDES += ../include
DEFINES := -D__ARM9__ -D__3DS__ -DLIBN3DS_LEGACY=1 -DVERS_STRING=\"$(VERS_STRING)\" \
-DVERS_MAJOR=$(VERS_MAJOR) -DVERS_MINOR=$(VERS_MINOR)
ifneq ($(strip $(NO_DEBUG)),)
@ -34,11 +34,11 @@ endif
#---------------------------------------------------------------------------------
ARCH := -march=armv5te -mtune=arm946e-s -mfloat-abi=soft -mtp=soft -marm -mthumb-interwork -masm-syntax-unified
CFLAGS := $(ARCH) -std=c17 -O2 -gdwarf-4 -flto -mword-relocations \
CFLAGS := $(ARCH) -std=c23 -O2 -gdwarf-4 -flto -mword-relocations \
-ffunction-sections -Wall -Wextra
CFLAGS += $(INCLUDE) $(DEFINES)
CXXFLAGS := $(ARCH) -std=c++17 -O2 -gdwarf-4 -flto -fno-rtti -fno-exceptions \
CXXFLAGS := $(ARCH) -std=c++23 -O2 -gdwarf-4 -flto -fno-rtti -fno-exceptions \
-mword-relocations -ffunction-sections -Wall -Wextra
CXXFLAGS += $(INCLUDE) $(DEFINES)

89
include/arm11/bitmap.h Normal file
View File

@ -0,0 +1,89 @@
#pragma once
/*
* This file is part of open_agb_firm
* Copyright (C) 2024 profi200
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "types.h"
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct
{
u16 magic; // "BM"
u32 fileSize;
u16 reserved;
u16 reserved2;
u32 pixelOffset; // From file start.
} PACKED BmpHeader;
static_assert(sizeof(BmpHeader) == 14);
typedef enum
{
BI_RGB = 0u,
BI_RLE8 = 1u, // 8 bit per pixel only.
BI_RLE4 = 2u, // 4 bit per pixel only.
BI_BITFIELDS = 3u, // BitmapV2Infoheader RGB masks, BitmapV3Infoheader+ RGBA masks.
BI_JPEG = 4u, // BitmapV4Infoheader+.
BI_PNG = 5u, // BitmapV4Infoheader+.
BI_CMYK = 11u, // Only Windows Metafile (WMF) CMYK.
BI_CMYKRLE8 = 12u, // Only Windows Metafile (WMF) CMYK.
BI_CMYKRLE4 = 13u // Only Windows Metafile (WMF) CMYK.
} BitmapCompr;
typedef struct
{
u32 headerSize; // Size of this header. 40 bytes.
s32 width;
s32 height; // If >=0, pixel lines are in order bottom to top. Otherwise top to bottom.
u16 colorPlanes; // Must be 1.
u16 bitsPerPixel; // 1, 4, 8, 16, 24, 32.
u32 compression; // See BitmapCompr enum.
u32 imageSize; // Can be 0 if compression is BI_RGB.
s32 xPixelsPerMeter;
s32 yPixelsPerMeter;
u32 colorsUsed;
u32 colorsImportant;
} PACKED Bitmapinfoheader;
static_assert(sizeof(Bitmapinfoheader) == 0x28);
typedef struct
{
BmpHeader header;
Bitmapinfoheader dib;
} PACKED BmpV1;
static_assert(sizeof(BmpV1) == 0x36);
// Note: Technically this is BMP V2 but we use the shorter Bitmapinfoheader (V1).
// The color masks are only needed for compression BI_BITFIELDS.
typedef struct
{
BmpHeader header;
Bitmapinfoheader dib;
u32 rMask;
u32 gMask;
u32 bMask;
} PACKED BmpV1WithMasks;
static_assert(sizeof(BmpV1WithMasks) == 0x42);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@ -2,7 +2,7 @@
/*
* This file is part of open_agb_firm
* Copyright (C) 2023 profi200
* Copyright (C) 2024 profi200
*
* 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
@ -22,6 +22,16 @@
#include "oaf_error_codes.h"
#ifdef __cplusplus
extern "C"
{
#endif
#define OAF_WORK_DIR "sdmc:/3ds/open_agb_firm"
#define OAF_SAVE_DIR "saves" // Relative to work dir.
#define OAF_SCREENSHOT_DIR "screenshots" // Relative to work dir.
typedef struct
{
// [general]
@ -29,13 +39,14 @@ typedef struct
u8 backlightSteps;
bool directBoot;
bool useGbaDb;
bool useSavesFolder;
// [video]
u8 scaler; // 0 = 1:1, 1 = bilinear (GPU) x1.5, 2 = matrix (hardware) x1.5.
float gbaGamma;
float lcdGamma;
float contrast;
float brightness;
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.
// [audio]
u8 audioOut; // 0 = auto, 1 = speakers, 2 = headphones.
@ -51,10 +62,15 @@ typedef struct
// [advanced]
bool saveOverride;
u16 defaultSave;
u16 defaultSave; // TODO: Should be u8. Investigate if u8 has any downsides.
} OafConfig;
//static_assert(sizeof(OafConfig) == 76, "nope");
extern OafConfig g_oafConfig; // Global config in config.c.
Result parseOafConfig(const char *const path, OafConfig *const cfg, const bool newCfgOnError);
Result parseOafConfig(const char *const path, OafConfig *cfg, const bool newCfgOnError);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@ -0,0 +1,33 @@
#pragma once
/*
* This file is part of open_agb_firm
* Copyright (C) 2024 profi200
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#ifdef __cplusplus
extern "C"
{
#endif
void convert160pFrameFast(void);
void convert240pFrameFast(void);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@ -0,0 +1,34 @@
#pragma once
/*
* This file is part of open_agb_firm
* Copyright (C) 2024 profi200
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "types.h"
#ifdef __cplusplus
extern "C"
{
#endif
void makeOpenBusPaddingFast(u32 *romEnd);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@ -22,4 +22,13 @@
#ifdef __cplusplus
extern "C"
{
#endif
Result browseFiles(const char *const basePath, char selected[512]);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@ -21,7 +21,14 @@
#include "types.h"
#ifdef __cplusplus
extern "C"
{
#endif
#define GPU_RENDER_BUF_ADDR (0x18180000)
#define GPU_TEXTURE_ADDR (0x18200000)
#define GPU_TEXTURE2_ADDR (0x18300000)
#define GBA_INIT_LIST_SIZE (1136)
#define GBA_LIST2_SIZE (448)
@ -31,4 +38,8 @@ extern u8 gbaGpuList2[GBA_LIST2_SIZE];
void patchGbaGpuCmdList(u8 scaleType);
void patchGbaGpuCmdList(const u8 scaleType, const bool useSecondTexture);
#ifdef __cplusplus
} // extern "C"
#endif

25
include/arm11/oaf_video.h Normal file
View File

@ -0,0 +1,25 @@
/*
* This file is part of open_agb_firm
* Copyright (C) 2024 profi200
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "types.h"
#include "kernel.h"
KHandle OAF_videoInit(void);
void OAF_videoExit(void);

View File

@ -22,8 +22,17 @@
#ifdef __cplusplus
extern "C"
{
#endif
Result oafParseConfigEarly(void);
void changeBacklight(s16 amount);
Result oafInitAndRun(void);
void oafUpdate(void);
void oafFinish(void);
void oafFinish(void);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@ -18,4 +18,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
Result patchRom(const char *const gamePath, u32 *romSize);
#ifdef __cplusplus
extern "C"
{
#endif
Result patchRom(const char *const gamePath, u32 *romSize);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@ -23,6 +23,11 @@
#include "arm11/config.h"
#ifdef __cplusplus
extern "C"
{
#endif
typedef struct
{
u8 sha1[20];
@ -34,4 +39,8 @@ static_assert(sizeof(GbaDbEntry) == 28, "Error: GBA DB entry struct is not packe
u16 detectSaveType(const u32 romSize, const u16 defaultSave);
u16 getSaveType(const OafConfig *const cfg, const u32 romSize, const char *const savePath);
u16 getSaveType(const OafConfig *const cfg, const u32 romSize, const char *const savePath);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@ -21,6 +21,11 @@
#include "error_codes.h"
#ifdef __cplusplus
extern "C"
{
#endif
#define MAKE_CUSTOM_ERR(e) (CUSTOM_ERR_OFFSET + (e))
// Keep errors in the range of 0-CUSTOM_ERR_OFFSET - 1.
@ -38,7 +43,11 @@ enum
const char* oafResult2String(Result res);
#ifdef ARM11
#ifdef __ARM11__
void printError(Result res);
void printErrorWaitInput(Result res, u32 waitKeys);
#endif // ifdef ARM11
#endif // ifdef __ARM11__
#ifdef __cplusplus
} // extern "C"
#endif

@ -1 +1 @@
Subproject commit 4e618f77d4bae216865c5abd972d99b1ba5031e2
Subproject commit ed4525140dacc54e5924f60b25a00c69371866a0

@ -1 +1 @@
Subproject commit 8f88216789f47916755bc591546e842af83eb18c
Subproject commit f6717f66858634b677ed695ee346a89db7684b43

View File

@ -1,6 +1,6 @@
/*
* This file is part of open_agb_firm
* Copyright (C) 2023 profi200
* Copyright (C) 2024 profi200
*
* 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
@ -26,24 +26,73 @@
#define INI_BUF_SIZE (1024u)
// Note: Keep this synchronized with g_oafConfig in open_agb_firm.c.
#define DEFAULT_CONFIG "[general]\n" \
"backlight=64\n" \
"backlightSteps=5\n" \
"directBoot=false\n" \
"useGbaDb=true\n\n" \
"useGbaDb=true\n" \
"useSavesFolder=true\n\n" \
\
"[video]\n" \
"scaler=2\n" \
"gbaGamma=2.2\n" \
"lcdGamma=1.54\n" \
"scaler=matrix\n" \
"colorProfile=none\n" \
"contrast=1.0\n" \
"brightness=0.0\n\n" \
"brightness=0.0\n" \
"saturation=1.0\n\n" \
\
"[audio]\n" \
"audioOut=0\n" \
"audioOut=auto\n" \
"volume=127\n\n" \
\
"[advanced]\n" \
"saveOverride=false\n" \
"defaultSave=14"
"defaultSave=sram_256k"
// Default config.
OafConfig g_oafConfig =
{
// [general]
64, // backlight
5, // backlightSteps
false, // directBoot
true, // useGbaDb
true, // useSavesFolder
// [video]
2, // scaler
0, // colorProfile
1.f, // contrast
0.f, // brightness
1.f, // saturation
// [audio]
0, // Automatic audio output.
127, // Control via volume slider.
// [input]
{ // buttonMaps
0, // A
0, // B
0, // Select
0, // Start
0, // Right
0, // Left
0, // Up
0, // Down
0, // R
0 // L
},
// [game]
0, // saveSlot
255, // saveType
// [advanced]
false, // saveOverride
14 // defaultSave
};
@ -83,7 +132,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;
@ -97,24 +146,61 @@ 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)
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);
{
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;
}
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;
//else if(strcmp(value, "custom") == 0) // TODO: Implement user provided profile.
// config->colorProfile = 9;
}
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)
config->audioOut = (u8)strtoul(value, NULL, 10);
{
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;
}
else if(strcmp(name, "volume") == 0)
config->volume = (s8)strtol(value, NULL, 10);
}
@ -134,14 +220,82 @@ 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)
config->saveType = (u8)strtoul(value, NULL, 10);
{
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;
}
}
else if(strcmp(section, "advanced") == 0)
{
if(strcmp(name, "saveOverride") == 0)
config->saveOverride = (strcmp(value, "false") == 0 ? false : true);
if(strcmp(name, "defaultSave") == 0)
config->defaultSave = (u16)strtoul(value, NULL, 10);
{
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;
}
}
else return 0; // Error.
@ -149,11 +303,12 @@ static int cfgIniCallback(void* user, const char* section, const char* name, con
}
// TODO: Instead of writing a hardcoded string turn default config into a string.
Result parseOafConfig(const char *const path, OafConfig *const cfg, const bool newCfgOnError)
Result parseOafConfig(const char *const path, OafConfig *cfg, const bool newCfgOnError)
{
char *iniBuf = (char*)calloc(INI_BUF_SIZE, 1);
if(iniBuf == NULL) return RES_OUT_OF_MEM;
cfg = (cfg != NULL ? cfg : &g_oafConfig);
Result res = fsQuickRead(path, iniBuf, INI_BUF_SIZE - 1);
if(res == RES_OK) ini_parse_string(iniBuf, cfgIniCallback, cfg);
else if(newCfgOnError)
@ -165,4 +320,4 @@ Result parseOafConfig(const char *const path, OafConfig *const cfg, const bool n
free(iniBuf);
return res;
}
}

View File

@ -0,0 +1,312 @@
@ This file is part of open_agb_firm
@ Copyright (C) 2024 profi200
@
@ 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 3 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, see <http://www.gnu.org/licenses/>.
#include "asm_macros.h"
#include "mem_map.h"
.syntax unified
.cpu mpcore
.fpu vfpv2
@ Whole frame converter.
/*BEGIN_ASM_FUNC convertFrameFast
@ Load frame, output and lookup table pointers.
@ Our frame is in a 512x512 texture. Same for the output.
@ The table is a 15 to 32-bit 3D lookup table with color correction pre-applied.
ldr r0, =0x18200000 @ r0 = 0x18200000;
ldr r1, =0x18300000 @ r1 = 0x18300000;
ldr r2, =0x1FF00000 @ r2 = 0x1FF00000;
@ Prefetch first cache line, save registers, load color mask and load 8 line counter.
pld [r0] @ Prefetch from r0.
stmfd sp!, {r4-r11, lr} @ Save registers.
ldrh r12, =0x7FFF @ r12 = 0x7FFF;
mov r11, #30 @ r11 = 30;
@ Convert 8 lines each round until we have a whole frame.
convertFrameFast_8l_lp:
@ Load size of 8 lines in bytes.
mov r3, #0x1680 @ r3 = 0x1680;
@ Convert 8 pixels each round until we have 8 lines.
convertFrameFast_8p_lp:
@ Load 8 pixels from frame.
ldmia r0!, {r8-r10, lr} @ r8_to_r10_lr = *((_16BytesBlock*)r0); r0 += 16;
@ Decrement size and extract first 2 pixels.
subs r3, r3, #16 @ r3 -= 16; // Updates flags.
and r4, r12, r8, lsr #1 @ r4 = 0x7FFF & (r8>>1); // r12 is 0x7FFF.
lsr r5, r8, #17 @ r5 = r8>>17;
@ Look up pixel 1 and extract pixel 3.
ldr r4, [r2, r4, lsl #2] @ r4 = r2[r4]; // u32.
and r6, r12, r9, lsr #1 @ r6 = 0x7FFF & (r9>>1); // r12 is 0x7FFF.
@ Look up pixel 2 and extract pixel 4.
ldr r5, [r2, r5, lsl #2] @ r5 = r2[r5]; // u32.
lsr r7, r9, #17 @ r7 = r9>>17;
@ Look up pixel 3 and extract pixel 5.
ldr r6, [r2, r6, lsl #2] @ r6 = r2[r6]; // u32.
and r8, r12, r10, lsr #1 @ r8 = 0x7FFF & (r10>>1); // r12 is 0x7FFF.
@ Look up pixel 4 and extract pixel 6.
ldr r7, [r2, r7, lsl #2] @ r7 = r2[r7]; // u32.
lsr r9, r10, #17 @ r9 = r10>>17;
@ Look up pixel 5 and extract pixel 7.
ldr r8, [r2, r8, lsl #2] @ r8 = r2[r8]; // u32.
and r10, r12, lr, lsr #1 @ r10 = 0x7FFF & (lr>>1); // r12 is 0x7FFF.
@ Look up pixel 6 and extract pixel 8.
ldr r9, [r2, r9, lsl #2] @ r9 = r2[r9]; // u32.
lsr lr, lr, #17 @ lr = lr>>17;
@ Look up pixel 7 and 8.
ldr r10, [r2, r10, lsl #2] @ r10 = r2[r10]; // u32.
ldr lr, [r2, lr, lsl #2] @ lr = r2[lr]; // u32.
@ Prefetch next cache line, write 8 pixels and jump back if we are not done yet.
pld [r0, #32] @ Prefetch from r0 + 32. // Offset 32 is a tiny bit better. Most of the time the result is the same as 64.
stmia r1!, {r4-r10, lr} @ *((_32BytesBlock*)r1) = r4_to_r10_lr; r1 += 32;
bne convertFrameFast_8p_lp @ if(r3 != 0) goto convertFrameFast_8p_lp;
@ Decrement 8 line counter, skip texture padding and jump back if we are not done yet.
subs r11, r11, #1 @ r11--; // Updates flags.
add r0, r0, #0x980 @ r0 += 0x980;
add r1, r1, #0x1300 @ r1 += 0x1300;
bne convertFrameFast_8l_lp @ if(r11 != 0) goto convertFrameFast_8l_lp;
ldmfd sp!, {r4-r11, pc} @ Restore registers and return.
END_ASM_FUNC*/
@ Converts a 160p frame while it's being DMAd to memory.
BEGIN_ASM_FUNC convert160pFrameFast
@ Enable top LCD LgyCap IRQs.
mov r0, #77 @ r0 = 77; // id IRQ_LGYCAP_TOP.
mov r1, #0 @ r1 = 0; // prio 0 (highest).
mov r2, #0 @ r2 = 0; // target 0 (this CPU).
mov r3, #0 @ r3 = 0; // isr NULL.
blx IRQ_registerIsr @ IRQ_registerIsr(IRQ_LGYCAP_TOP, 0, 0, (IrqIsr)NULL);
@ We will be using IRQs without our IRQ handler to minimize latency.
cpsid i @ __disableIrq();
@ Load lookup table address and color mask.
ldr r2, =0x1FF00000 @ r2 = 0x1FF00000;
ldrh r12, =0x7FFF @ r12 = 0x7FFF;
convert160pFrameFast_frame_lp:
@ Load input and output addresses.
ldr r0, =0x18200000 @ r0 = 0x18200000; // u32.
@ldr r1, =0x18300000 @ r1 = 0x18300000; // u32.
add r1, r0, #0x100000 @ r1 = r0 + 0x100000; // Note: ldr would be faster here (result latency). Saves 4 bytes.
@ Convert 8 lines each round until we have a whole frame.
convert160pFrameFast_8l_lp:
ldr r4, =0x10111008 @ r4 = &REG_LGYCAP1_STAT; // u32.
ldr r5, =MPCORE_PRIV_BASE @ r5 = MPCORE_PRIV_BASE; // u32.
convert160pFrameFast_wait_irq:
@ Wait for LgyCap IRQs.
wfi @ __waitForInterrupt();
@ Acknowledge IRQ and extract line number.
ldr r11, [r4] @ r11 = REG_LGYCAP_STAT; // u32.
ldr r7, [r5, #0x10C] @ r7 = REG_GICC_INTACK; // u32.
str r11, [r4] @ REG_LGYCAP_STAT = r11; // u32.
lsrs r11, r11, #16 @ r11 >>= 16; // Updates flags.
str r7, [r5, #0x110] @ REG_GICC_EOI = r7; // u32.
@ Ignore DREQ IRQ for line 0.
beq convert160pFrameFast_wait_irq @ if((r11>>16) == 0) goto convert160pFrameFast_wait_irq;
convert160pFrameFast_skip_irq_wait:
@ Load size of 8 lines in bytes.
mov r3, #0xF00 @ r3 = 0xF00;
@ Convert 8 pixels each round until we have 8 lines.
convert160pFrameFast_8p_lp:
@ Load 8 pixels from frame.
ldmia r0!, {r8-r10, lr} @ r8_to_r10_lr = *((_16BytesBlock*)r0); r0 += 16;
@ Decrement size and extract first 2 pixels.
subs r3, r3, #16 @ r3 -= 16; // Updates flags.
and r4, r12, r8, lsr #1 @ r4 = 0x7FFF & (r8>>1); // r12 is 0x7FFF.
lsr r5, r8, #17 @ r5 = r8>>17;
@ Look up pixel 1 and extract pixel 3.
ldr r4, [r2, r4, lsl #2] @ r4 = r2[r4]; // u32.
and r6, r12, r9, lsr #1 @ r6 = 0x7FFF & (r9>>1); // r12 is 0x7FFF.
@ Look up pixel 2 and extract pixel 4.
ldr r5, [r2, r5, lsl #2] @ r5 = r2[r5]; // u32.
lsr r7, r9, #17 @ r7 = r9>>17;
@ Look up pixel 3 and extract pixel 5.
ldr r6, [r2, r6, lsl #2] @ r6 = r2[r6]; // u32.
and r8, r12, r10, lsr #1 @ r8 = 0x7FFF & (r10>>1); // r12 is 0x7FFF.
@ Look up pixel 4 and extract pixel 6.
ldr r7, [r2, r7, lsl #2] @ r7 = r2[r7]; // u32.
lsr r9, r10, #17 @ r9 = r10>>17;
@ Look up pixel 5 and extract pixel 7.
ldr r8, [r2, r8, lsl #2] @ r8 = r2[r8]; // u32.
and r10, r12, lr, lsr #1 @ r10 = 0x7FFF & (lr>>1); // r12 is 0x7FFF.
@ Look up pixel 6 and extract pixel 8.
ldr r9, [r2, r9, lsl #2] @ r9 = r2[r9]; // u32.
lsr lr, lr, #17 @ lr = lr>>17;
@ Look up pixel 7 and 8.
ldr r10, [r2, r10, lsl #2] @ r10 = r2[r10]; // u32.
ldr lr, [r2, lr, lsl #2] @ lr = r2[lr]; // u32.
@ Prefetch next cache line, write 8 pixels and jump back if we are not done yet.
pld [r0, #32] @ Prefetch from r0 + 32. // Offset 32 is a tiny bit better. Most of the time the result is the same as 64.
stmia r1!, {r4-r10, lr} @ *((_32BytesBlock*)r1) = r4_to_r10_lr; r1 += 32;
bne convert160pFrameFast_8p_lp @ if(r3 != 0) goto convert160pFrameFast_8p_lp;
@ Test if 8 line counter is 152, skip texture padding and jump back if we are not done yet.
cmp r11, #152 @ r11 - 152; // Updates flags.
add r0, r0, #0x1100 @ r0 += 0x1100;
add r1, r1, #0x2200 @ r1 += 0x2200;
moveq r11, #160 @ if(r11 == 152) r11 = 160;
beq convert160pFrameFast_skip_irq_wait @ if(r11 == 152) goto convert160pFrameFast_skip_irq_wait;
bls convert160pFrameFast_8l_lp @ if(r11 <= 152) goto convert160pFrameFast_8l_lp;
@ Flush the D-Cache, wait for flush completion, notify core 0 and jump back.
@ Note: r3 has been decremented down to 0 previously and so it's safe to use.
mcr p15, 0, r3, c7, c14, 0 @ Clean and Invalidate Entire Data Cache.
ldr r4, =MPCORE_PRIV_BASE @ r4 = MPCORE_PRIV_BASE; // u32.
mov r5, #0x10000 @ r5 = 0x10000;
orr r5, r5, #0xF @ r5 |= 0xF;
add r4, r4, #0x1F00 @ r4 += 0x1F00; // REG_GICD_SOFTINT.
mcr p15, 0, r3, c7, c10, 4 @ Data Synchronization Barrier.
str r5, [r4] @ *r4 = r5; // u32.
b convert160pFrameFast_frame_lp @ goto convert160pFrameFast_frame_lp;
END_ASM_FUNC
@ Converts the frame while it's being DMAd to memory.
BEGIN_ASM_FUNC convert240pFrameFast
@ Enable top LCD LgyCap IRQs.
mov r0, #77 @ r0 = 77; // id IRQ_LGYCAP_TOP.
mov r1, #0 @ r1 = 0; // prio 0 (highest).
mov r2, #0 @ r2 = 0; // target 0 (this CPU).
mov r3, #0 @ r3 = 0; // isr NULL.
blx IRQ_registerIsr @ IRQ_registerIsr(IRQ_LGYCAP_TOP, 0, 0, (IrqIsr)NULL);
@ We will be using IRQs without our IRQ handler to minimize latency.
cpsid i @ __disableIrq();
@ Load lookup table address and color mask.
ldr r2, =0x1FF00000 @ r2 = 0x1FF00000;
ldrh r12, =0x7FFF @ r12 = 0x7FFF;
convert240pFrameFast_frame_lp:
@ Load input and output addresses.
ldr r0, =0x18200000 @ r0 = 0x18200000; // u32.
@ldr r1, =0x18300000 @ r1 = 0x18300000; // u32.
add r1, r0, #0x100000 @ r1 = r0 + 0x100000; // Note: ldr would be faster here (result latency). Saves 4 bytes.
@ Convert 8 lines each round until we have a whole frame.
convert240pFrameFast_8l_lp:
ldr r4, =0x10111008 @ r4 = &REG_LGYCAP1_STAT; // u32.
ldr r5, =MPCORE_PRIV_BASE @ r5 = MPCORE_PRIV_BASE; // u32.
convert240pFrameFast_wait_irq:
@ Wait for LgyCap IRQs.
wfi @ __waitForInterrupt();
@ Acknowledge IRQ and extract line number.
ldr r11, [r4] @ r11 = REG_LGYCAP_STAT; // u32.
ldr r7, [r5, #0x10C] @ r7 = REG_GICC_INTACK; // u32.
str r11, [r4] @ REG_LGYCAP_STAT = r11; // u32.
lsrs r11, r11, #16 @ r11 >>= 16; // Updates flags.
str r7, [r5, #0x110] @ REG_GICC_EOI = r7; // u32.
@ Ignore DREQ IRQ for line 0.
beq convert240pFrameFast_wait_irq @ if((r11>>16) == 0) goto convert240pFrameFast_wait_irq;
convert240pFrameFast_skip_irq_wait:
@ Load size of 8 lines in bytes.
mov r3, #0x1680 @ r3 = 0x1680;
@ Convert 8 pixels each round until we have 8 lines.
convert240pFrameFast_8p_lp:
@ Load 8 pixels from frame.
ldmia r0!, {r8-r10, lr} @ r8_to_r10_lr = *((_16BytesBlock*)r0); r0 += 16;
@ Decrement size and extract first 2 pixels.
subs r3, r3, #16 @ r3 -= 16; // Updates flags.
and r4, r12, r8, lsr #1 @ r4 = 0x7FFF & (r8>>1); // r12 is 0x7FFF.
lsr r5, r8, #17 @ r5 = r8>>17;
@ Look up pixel 1 and extract pixel 3.
ldr r4, [r2, r4, lsl #2] @ r4 = r2[r4]; // u32.
and r6, r12, r9, lsr #1 @ r6 = 0x7FFF & (r9>>1); // r12 is 0x7FFF.
@ Look up pixel 2 and extract pixel 4.
ldr r5, [r2, r5, lsl #2] @ r5 = r2[r5]; // u32.
lsr r7, r9, #17 @ r7 = r9>>17;
@ Look up pixel 3 and extract pixel 5.
ldr r6, [r2, r6, lsl #2] @ r6 = r2[r6]; // u32.
and r8, r12, r10, lsr #1 @ r8 = 0x7FFF & (r10>>1); // r12 is 0x7FFF.
@ Look up pixel 4 and extract pixel 6.
ldr r7, [r2, r7, lsl #2] @ r7 = r2[r7]; // u32.
lsr r9, r10, #17 @ r9 = r10>>17;
@ Look up pixel 5 and extract pixel 7.
ldr r8, [r2, r8, lsl #2] @ r8 = r2[r8]; // u32.
and r10, r12, lr, lsr #1 @ r10 = 0x7FFF & (lr>>1); // r12 is 0x7FFF.
@ Look up pixel 6 and extract pixel 8.
ldr r9, [r2, r9, lsl #2] @ r9 = r2[r9]; // u32.
lsr lr, lr, #17 @ lr = lr>>17;
@ Look up pixel 7 and 8.
ldr r10, [r2, r10, lsl #2] @ r10 = r2[r10]; // u32.
ldr lr, [r2, lr, lsl #2] @ lr = r2[lr]; // u32.
@ Prefetch next cache line, write 8 pixels and jump back if we are not done yet.
pld [r0, #32] @ Prefetch from r0 + 32. // Offset 32 is a tiny bit better. Most of the time the result is the same as 64.
stmia r1!, {r4-r10, lr} @ *((_32BytesBlock*)r1) = r4_to_r10_lr; r1 += 32;
bne convert240pFrameFast_8p_lp @ if(r3 != 0) goto convert240pFrameFast_8p_lp;
@ Test if 8 line counter is 232, skip texture padding and jump back if we are not done yet.
cmp r11, #232 @ r11 - 232; // Updates flags.
add r0, r0, #0x980 @ r0 += 0x980;
add r1, r1, #0x1300 @ r1 += 0x1300;
moveq r11, #240 @ if(r11 == 232) r11 = 240;
beq convert240pFrameFast_skip_irq_wait @ if(r11 == 232) goto convert240pFrameFast_skip_irq_wait;
bls convert240pFrameFast_8l_lp @ if(r11 <= 232) goto convert240pFrameFast_8l_lp;
@ Flush the D-Cache, wait for flush completion, notify core 0 and jump back.
@ Note: r3 has been decremented down to 0 previously and so it's safe to use.
mcr p15, 0, r3, c7, c14, 0 @ Clean and Invalidate Entire Data Cache.
ldr r4, =MPCORE_PRIV_BASE @ r4 = MPCORE_PRIV_BASE; // u32.
mov r5, #0x10000 @ r5 = 0x10000;
orr r5, r5, #0xF @ r5 |= 0xF;
add r4, r4, #0x1F00 @ r4 += 0x1F00; // REG_GICD_SOFTINT.
mcr p15, 0, r3, c7, c10, 4 @ Data Synchronization Barrier.
str r5, [r4] @ *r4 = r5; // u32.
b convert240pFrameFast_frame_lp @ goto convert240pFrameFast_frame_lp;
END_ASM_FUNC

View File

@ -0,0 +1,60 @@
@ This file is part of open_agb_firm
@ Copyright (C) 2024 profi200
@
@ 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 3 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, see <http://www.gnu.org/licenses/>.
#include "asm_macros.h"
.syntax unified
.cpu mpcore
.fpu vfpv2
@ void makeOpenBusPaddingFast(u32 *romEnd);
BEGIN_ASM_FUNC makeOpenBusPaddingFast
@ Save registers and calculate size from start and highest ROM address.
stmfd sp!, {r4, lr} @ Save registers.
rsb r1, r0, #0x22000000 @ r1 = 0x22000000 - r0;
@ Generate pattern halves from address.
lsr r2, r0, #1 @ r2 = r0>>1;
add r3, r2, #1 @ r3 = r2 + 1;
@ Generate constant for incrementing the pattern halves.
mov r12, #2 @ r12 = 2;
add r12, r12, #0x20000 @ r12 += 0x20000;
@ Join pattern halves and precalculate the next 3 patterns.
pkhbt r2, r2, r3, lsl #16 @ r2 = (r2 & 0xFFFF) | r3<<16;
uadd16 r3, r2, r12 @ r3 = ((r2 + 0x20000) & 0xFFFF0000) | ((r2 + 2) & 0xFFFF); // r12 is 0x20002.
uadd16 r4, r3, r12 @ r4 = ((r3 + 0x20000) & 0xFFFF0000) | ((r3 + 2) & 0xFFFF); // r12 is 0x20002.
uadd16 lr, r4, r12 @ lr = ((r4 + 0x20000) & 0xFFFF0000) | ((r4 + 2) & 0xFFFF); // r12 is 0x20002.
@ Adjust constant for unrolled loop. 0x20002 --> 0x80008.
lsl r12, r12, #2 @ r12 <<= 2;
makeOpenBusPaddingFast_blk_lp:
@ Store 16 pattern bytes at a time and decrement size.
stmia r0!, {r2-r4, lr} @ *((_16BytesBlock*)r0) = r2_to_r4_lr; r0 += 16;
subs r1, r1, #16 @ r1 -= 16; // Updates flags.
@ Increment patterns and jump back if we are not done yet.
uadd16 r2, r2, r12 @ r2 = ((r2 + 0x80000) & 0xFFFF0000) | ((r2 + 8) & 0xFFFF); // r12 is 0x80008.
uadd16 r3, r3, r12 @ r3 = ((r3 + 0x80000) & 0xFFFF0000) | ((r3 + 8) & 0xFFFF); // r12 is 0x80008.
uadd16 r4, r4, r12 @ r3 = ((r4 + 0x80000) & 0xFFFF0000) | ((r4 + 8) & 0xFFFF); // r12 is 0x80008.
uadd16 lr, lr, r12 @ lr = ((lr + 0x80000) & 0xFFFF0000) | ((lr + 8) & 0xFFFF); // r12 is 0x80008.
bne makeOpenBusPaddingFast_blk_lp @ if(r1 != 0) goto makeOpenBusPaddingFast_blk_lp;
ldmfd sp!, {r4, pc} @ Restore registers and return.
END_ASM_FUNC

View File

@ -92,7 +92,8 @@ 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)
if(nameLen <= filterLen || strcmp(filter, fis[i].fname + nameLen - filterLen) != 0
|| fis[i].fname[0] == '.')
continue;
}
@ -129,9 +130,9 @@ static void showDirList(const DirList *const dList, u32 start)
for(u32 i = start; i < listLength; i++)
{
const char *const printStr =
(*dList->ptrs[i] == ENT_TYPE_FILE ? "\x1b[%lu;H\x1b[37m %.51s" : "\x1b[%lu;H\x1b[33m %.51s");
(*dList->ptrs[i] == ENT_TYPE_FILE ? "\x1b[%lu;H\x1b[37;1m %.52s" : "\x1b[%lu;H\x1b[33;1m %.52s");
ee_printf(printStr, i - start, &dList->ptrs[i][1]);
ee_printf(printStr, i - start + 1, &dList->ptrs[i][1]);
}
}
@ -156,8 +157,9 @@ Result browseFiles(const char *const basePath, char selected[512])
s32 oldCursorPos = 0;
while(1)
{
ee_printf("\x1b[%lu;H ", oldCursorPos - windowPos); // Clear old cursor.
ee_printf("\x1b[%lu;H\x1b[37m>", cursorPos - windowPos); // Draw cursor.
ee_printf("\x1b[%lu;H ", oldCursorPos - windowPos + 1); // Clear old cursor.
ee_printf("\x1b[%lu;H\x1b[37m>", cursorPos - windowPos + 1); // Draw cursor.
GFX_flushBuffers();
u32 kDown;
do
@ -240,4 +242,4 @@ end:
ee_printf("\x1b[2J");
return res;
}
}

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "types.h"
#include "arm11/gpu_cmd_lists.h"
#include "drivers/cache.h"
@ -72,7 +73,7 @@ alignas(16) u8 gbaGpuInitList[GBA_INIT_LIST_SIZE] =
0x18, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
0x81, 0x00, 0x4F, 0x80, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, // Last 4 bytes: GPUREG_TEXUNIT0_PARAM.
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x01, 0x00, 0x00, 0x00, // Last 4 bytes: GPUREG_TEXUNIT0_TYPE.
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x02, 0x00, 0x00, 0x00, // Last 4 bytes: GPUREG_TEXUNIT0_TYPE.
0x8E, 0x00, 0x0F, 0x00, 0x01, 0x10, 0x01, 0x00, 0x80, 0x00, 0x0B, 0x00,
0x00, 0x00, 0x01, 0x00, 0x80, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00,
0x8B, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x07, 0x00,
@ -166,51 +167,64 @@ alignas(16) u8 gbaGpuList2[GBA_LIST2_SIZE] =
void patchGbaGpuCmdList(u8 scaleType)
void patchGbaGpuCmdList(const u8 scaleType, const bool useSecondTexture)
{
if(useSecondTexture)
{
u32 tmp = GPU_TEXTURE2_ADDR>>3;
memcpy(&gbaGpuInitList[580], &tmp, 4);
tmp = 0;
memcpy(&gbaGpuInitList[584], &tmp, 4);
}
if(scaleType == 0)
{
*((u16*)&gbaGpuInitList[952]) = 0x4440;
u32 tmp = 0x4440;
memcpy(&gbaGpuInitList[952], &tmp, 2);
gbaGpuInitList[958] = 0x45;
gbaGpuInitList[968] = 0x60;
*((u16*)&gbaGpuInitList[984]) = 0x4440;
memcpy(&gbaGpuInitList[984], &tmp, 2);
gbaGpuInitList[989] = 0x40;
gbaGpuInitList[1000] = 0x60;
*((u32*)&gbaGpuInitList[1004]) = 0x3DE000;
tmp = 0x3DE000;
memcpy(&gbaGpuInitList[1004], &tmp, 4);
gbaGpuInitList[1016] = 0x90;
gbaGpuInitList[1022] = 0x45;
gbaGpuInitList[1048] = 0x90;
gbaGpuInitList[1053] = 0x40;
*((u32*)&gbaGpuInitList[1068]) = 0x3DE000;
memcpy(&gbaGpuInitList[1068], &tmp, 4);
*((u16*)&gbaGpuList2[264]) = 0x4440;
tmp = 0x4440;
memcpy(&gbaGpuList2[264], &tmp, 2);
gbaGpuList2[270] = 0x45;
gbaGpuList2[280] = 0x60;
*((u16*)&gbaGpuList2[296]) = 0x4440;
memcpy(&gbaGpuList2[296], &tmp, 2);
gbaGpuList2[301] = 0x40;
gbaGpuList2[312] = 0x60;
*((u32*)&gbaGpuList2[316]) = 0x3DE000;
tmp = 0x3DE000;
memcpy(&gbaGpuList2[316], &tmp, 4);
gbaGpuList2[328] = 0x90;
gbaGpuList2[334] = 0x45;
gbaGpuList2[360] = 0x90;
gbaGpuList2[365] = 0x40;
*((u32*)&gbaGpuList2[380]) = 0x3DE000;
memcpy(&gbaGpuList2[380], &tmp, 4);
}
else if(scaleType == 1)
{
gbaGpuInitList[572] = 2;
gbaGpuInitList[968] = 0x60;
gbaGpuInitList[1000] = 0x60;
*((u32*)&gbaGpuInitList[1004]) = 0x3DE000;
*((u32*)&gbaGpuInitList[1068]) = 0x3DE000;
u32 tmp = 0x3DE000;
memcpy(&gbaGpuInitList[1004], &tmp, 4);
memcpy(&gbaGpuInitList[1068], &tmp, 4);
gbaGpuList2[280] = 0x60;
gbaGpuList2[312] = 0x60;
*((u32*)&gbaGpuList2[316]) = 0x3DE000;
*((u32*)&gbaGpuList2[380]) = 0x3DE000;
memcpy(&gbaGpuList2[316], &tmp, 4);
memcpy(&gbaGpuList2[380], &tmp, 4);
}
else return; // Nothing to do.
// else nothing to do.
flushDCacheRange(gbaGpuInitList, sizeof(gbaGpuInitList));
flushDCacheRange(gbaGpuList2, sizeof(gbaGpuList2));
}
}

View File

@ -30,11 +30,10 @@
int main(void)
{
Result res = fMount(FS_DRIVE_SDMC);
if(res == RES_OK) res = oafParseConfigEarly();
GFX_init(GFX_BGR8, GFX_RGB565);
Result res = oafParseConfigEarly();
GFX_init(GFX_BGR8, GFX_BGR565, GFX_TOP_2D);
changeBacklight(0); // Apply backlight config.
consoleInit(SCREEN_BOT, NULL);
consoleInit(GFX_LCD_BOT, NULL);
//CODEC_init();
if(res == RES_OK && (res = oafInitAndRun()) == RES_OK)
@ -53,9 +52,9 @@ int main(void)
CODEC_deinit();
GFX_deinit();
fUnmount(FS_DRIVE_SDMC);
fUnmount(FS_DRIVE_SDMC); // TODO: Move elsewhere. __systemDeinit() already calls it.
power_off();
return 0;
}
}

519
source/arm11/oaf_video.c Normal file
View File

@ -0,0 +1,519 @@
/*
* This file is part of open_agb_firm
* Copyright (C) 2024 profi200
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <string.h>
#include "types.h"
#include "arm11/config.h"
#include "arm11/drivers/gx.h"
#include "drivers/cache.h"
#include "util.h"
#include "oaf_error_codes.h"
#include "arm11/drivers/lgycap.h"
#include "arm11/bitmap.h"
#include "drivers/gfx.h"
#include "arm11/drivers/mcu.h"
#include "arm11/fmt.h"
#include "fsutil.h"
#include "kernel.h"
#include "kevent.h"
#include "arm11/drivers/hid.h"
#include "arm11/drivers/interrupt.h"
#include "arm11/gpu_cmd_lists.h"
#include "system.h"
#include "arm11/fast_frame_convert.h"
#define COLOR_LUT_ADDR (0x1FF00000u)
static KHandle g_convFinishedEvent = 0;
static const u32 g_topLcdCurveCorrect[73] =
{
// Curve correction from 3DS top LCD gamma to 2.2 gamma for all channels.
// Unfortunately this doesn't fix color temperature but that varies wildly
// for all 2/3DS consoles on the market anyway.
0x01000000, 0x03020102, 0x00060406, 0x01060507,
0x03080608, 0x020B090C, 0x010E0B0F, 0x010F0D10,
0x01110E12, 0x01121014, 0x00141116, 0x00151216,
0x01151317, 0x01171419, 0x0118161B, 0x011A171C,
0x021B191E, 0x001E1B21, 0x011E1C22, 0x01201E23,
0x03211F25, 0x01242329, 0x0226242B, 0x0028272E,
0x0229282E, 0x002B2B31, 0x042C2B32, 0x04303037,
0x0034353C, 0x0535353D, 0x073A3B43, 0x0A41434B,
0x034B4E56, 0x084F525B, 0x0D575B64, 0x03656973,
0x12686D77, 0x017B818A, 0x167C838C, 0x03939AA4,
0x0E979FA8, 0x01A6AFB7, 0x06A9B1B9, 0x01B0B9C0,
0x00B2BBC3, 0x04B4BCC4, 0x00B9C2C9, 0x04BBC3CA,
0x00C1C8CF, 0x00C2CAD0, 0x02C3CBD2, 0x01C7CED5,
0x00C9D1D7, 0x03CBD2D8, 0x00D0D7DC, 0x01D1D8DE,
0x01D4DAE0, 0x00D6DDE2, 0x02D8DEE3, 0x00DCE1E6,
0x01DDE3E7, 0x02E0E5EA, 0x01E4E9ED, 0x02E7EBEF,
0x01EBEFF2, 0x01EEF1F4, 0x00F1F3F6, 0x01F2F5F7,
0x01F5F7F9, 0x01F8F9FB, 0x00FAFCFD, 0x01FCFDFD,
0x00FFFFFF
};
// TODO: Reimplement contrast and brightness in color lut below.
static void adjustGammaTableForGba(void)
{
// Credits for this algo go to Extrems.
/*const float targetGamma = g_oafConfig.gbaGamma;
const float lcdGamma = 1.f / g_oafConfig.lcdGamma;
const float contrast = g_oafConfig.contrast;
const float brightness = g_oafConfig.brightness / contrast;
const float contrastInTargetGamma = powf(contrast, targetGamma);
vu32 *const color_lut_data = &getGxRegs()->pdc0.color_lut_data;
for(u32 i = 0; i < 256; i++)
{
// Adjust i with brightness and convert to target gamma.
const float adjusted = powf((float)i / 255 + brightness, targetGamma);
// Apply contrast, convert to LCD gamma, round to nearest and clamp.
const u32 res = clamp_s32(lroundf(powf(contrastInTargetGamma * adjusted, lcdGamma) * 255), 0, 255);
// Same adjustment for red/green/blue.
*color_lut_data = res<<16 | res<<8 | res;
}*/
// Very simple gamma table expansion code.
// Code + hardcoded tables are way smaller than hardcoding the uncompressed tables.
const u32 *encTable = g_topLcdCurveCorrect;
vu32 *const color_lut_data = &getGxRegs()->pdc0.color_lut_data;
u32 decoded = 0;
do
{
// Get table entry and extract the number of linearly increasing entries.
u32 entry = *encTable++;
u32 steps = (entry>>24) + 1;
// Keep track of how many table entries we generated.
decoded += steps;
do
{
// Set gamma table entry and increment.
// Note: Bits 24-31 don't matter so we don't need to mask.
*color_lut_data = entry;
entry += 0x010101;
} while(--steps != 0);
} while(decoded < 256);
}
typedef struct
{
float targetGamma;
float lum;
float r, gr, br;
float rg, g, bg;
float rb, gb, b;
float displayGamma;
} ColorProfile;
// libretro shader values. Credits: hunterk and Pokefan531.
// Last updated 2014-12-03.
static const ColorProfile g_colorProfiles[8] =
{
{ // 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 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.
1.f,
0.865f, 0.1225f, 0.0125f,
0.0575f, 0.925f, 0.0125f,
0.0575f, 0.1225f, 0.82f,
1.f / 2.2f
},
{ // 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
}
};
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)
{
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;
for(u32 i = 0; i < 32768; i++)
{
// Convert to 8-bit and normalize.
float b = (float)rgbFive2Eight(i & 31u) / 255;
float g = (float)rgbFive2Eight((i>>5) & 31u) / 255;
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);
// Apply luminance.
const float lum = p->lum;
b = clamp_float(b * lum, 0.f, 1.f);
g = clamp_float(g * lum, 0.f, 1.f);
r = clamp_float(r * lum, 0.f, 1.f);
/*
* Input
* [r]
* [g]
* [b]
*
* Correction Output
* [ r][gr][br] [r]
* [rg][ g][bg] [g]
* [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;
// 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);
// Convert to display gamma.
const float displayGamma = p->displayGamma;
b = powf(targetContrast * b, displayGamma);
g = powf(targetContrast * g, displayGamma);
r = powf(targetContrast * r, 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;
}
flushDCacheRange(colorLut, 4 * 32768);
}
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;
// A1BGR5 format (alpha ignored).
constexpr u32 alignment = 0x80; // Make PPF happy.
alignas(4) static const BmpV1WithMasks bmpHeaders =
{
{
.magic = 0x4D42,
.fileSize = alignment + 240 * 160 * 2,
.reserved = 0,
.reserved2 = 0,
.pixelOffset = alignment
},
{
.headerSize = sizeof(Bitmapinfoheader),
.width = 240,
.height = -160,
.colorPlanes = 1,
.bitsPerPixel = 16,
.compression = BI_BITFIELDS,
.imageSize = 240 * 160 * 2,
.xPixelsPerMeter = 0,
.yPixelsPerMeter = 0,
.colorsUsed = 0,
.colorsImportant = 0
},
.rMask = 0xF800,
.gMask = 0x07C0,
.bMask = 0x003E
};
// 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),
PPF_O_FMT(GX_A1BGR5) | PPF_I_FMT(GX_A1BGR5) | PPF_CROP_EN);
memcpy(tmpBuf, &bmpHeaders, sizeof(bmpHeaders));
GFX_waitForPPF();
// Get current date & time.
RtcTimeDate td;
MCU_getRtcTimeDate(&td);
// 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();
// Restart LgyCap.
LGYCAP_start(LGYCAP_DEV_TOP);
return res;
}
static void convFinishedHandler(UNUSED const u32 intSource)
{
signalEvent(g_convFinishedEvent, false);
}
static void gbaGfxHandler(void *args)
{
const KHandle event = (KHandle)args;
while(1)
{
if(waitForEvent(event) != KRES_OK) break;
clearEvent(event);
// All measurements are the worst timings in ~30 seconds of runtime.
// Measured with timer prescaler 1.
// BGR8:
// 240x160 no scaling: ~184 µs
// 240x160 bilinear x1.5: ~408 µs
// 360x240 no scaling: ~437 µs
//
// A1BGR5:
// 240x160 no scaling: ~188 µs (25300 ticks)
// 240x160 bilinear x1.5: ~407 µs (54619 ticks)
// 360x240 no scaling: ~400 µs (53725 ticks)
static bool inited = false;
u32 listSize;
const u32 *list;
if(inited == false)
{
inited = true;
listSize = sizeof(gbaGpuInitList);
list = (u32*)gbaGpuInitList;
}
else
{
listSize = sizeof(gbaGpuList2);
list = (u32*)gbaGpuList2;
}
GX_processCommandList(listSize, list);
GFX_waitForP3D();
GX_displayTransfer((u32*)GPU_RENDER_BUF_ADDR, PPF_DIM(240, 400), GFX_getBuffer(GFX_LCD_TOP, GFX_SIDE_LEFT),
PPF_DIM(240, 400), PPF_O_FMT(GX_BGR8) | PPF_I_FMT(GX_BGR8));
GFX_waitForPPF();
GFX_swapBuffers();
// Trigger only if both are held and at least one is detected as newly pressed down.
if(hidKeysHeld() == (KEY_Y | KEY_SELECT) && hidKeysDown() != 0)
dumpFrameTex();
}
taskExit();
}
static KHandle setupFrameCapture(const u8 scaler, const bool colorCorrectionEnabled)
{
const bool is240x160 = scaler < 2;
static s16 matrix[12 * 8] =
{
// Vertical.
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0x24B0, 0x4000, 0, 0x24B0, 0x4000, 0, 0,
0x4000, 0x2000, 0, 0x4000, 0x2000, 0, 0, 0,
0, -0x4B0, 0, 0, -0x4B0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
// Horizontal.
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0x24B0, 0, 0, 0x24B0, 0, 0,
0x4000, 0x4000, 0x2000, 0x4000, 0x4000, 0x2000, 0, 0,
0, 0, -0x4B0, 0, 0, -0x4B0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
const Result res = fsQuickRead("gba_scaler_matrix.bin", matrix, sizeof(matrix));
if(res != RES_OK && res != RES_FR_NO_FILE)
{
ee_printf("Failed to load hardware scaling matrix: %s\n", result2String(res));
}
LgyCapCfg gbaCfg;
gbaCfg.cnt = LGYCAP_SWIZZLE | LGYCAP_ROT_NONE | LGYCAP_FMT_A1BGR5 | (is240x160 ? 0 : LGYCAP_HSCALE_EN | LGYCAP_VSCALE_EN);
gbaCfg.w = (is240x160 ? 240 : 360);
gbaCfg.h = (is240x160 ? 160 : 240);
gbaCfg.irq = (colorCorrectionEnabled ? LGYCAP_IRQ_DMA_REQ : 0); // We need the DMA request IRQ for core 1.
gbaCfg.vLen = 6;
gbaCfg.vPatt = 0b00011011;
memcpy(gbaCfg.vMatrix, matrix, 6 * 8 * 2);
gbaCfg.hLen = 6;
gbaCfg.hPatt = 0b00011011;
memcpy(gbaCfg.hMatrix, &matrix[6 * 8], 6 * 8 * 2);
return LGYCAP_init(LGYCAP_DEV_TOP, &gbaCfg);
}
KHandle OAF_videoInit(void)
{
#ifdef NDEBUG
// Force black and turn the backlight off on the bottom screen.
// Don't turn the backlight off on 2DS (1 panel).
GFX_setForceBlack(false, true);
if(MCU_getSystemModel() != SYS_MODEL_2DS)
GFX_powerOffBacklight(GFX_BL_BOT);
#endif
// Initialize frame capture.
const u8 scaler = g_oafConfig.scaler;
const u8 colorProfile = g_oafConfig.colorProfile;
KHandle frameReadyEvent;
KHandle convFinishedEvent;
if(colorProfile > 0)
{
// Start capture hardware and create event handles.
frameReadyEvent = setupFrameCapture(scaler, true);
convFinishedEvent = createEvent(false);
g_convFinishedEvent = convFinishedEvent;
// Patch GPU cmd list with texture location 2.
patchGbaGpuCmdList(scaler, true);
// Compute the (linear) 3D lookup table.
makeColorLut(&g_colorProfiles[colorProfile - 1]);
// Register IPI handler and start core 1 for color conversion.
IRQ_registerIsr(IRQ_IPI15, 13, 0, convFinishedHandler);
__systemBootCore1((scaler < 2 ? convert160pFrameFast : convert240pFrameFast));
}
else
{
// Start capture hardware.
frameReadyEvent = setupFrameCapture(scaler, false);
// Patch GPU cmd list with texture location 1.
patchGbaGpuCmdList(scaler, false);
}
// Start frame handler.
createTask(0x800, 3, gbaGfxHandler, (void*)(colorProfile > 0 ? convFinishedEvent : frameReadyEvent));
// Adjust hardware gamma table.
adjustGammaTableForGba();
// Load border if any exists.
if(scaler == 0) // No borders for scaled modes.
{
// Abuse currently invisible frame buffer as temporary buffer.
void *const borderBuf = GFX_getBuffer(GFX_LCD_TOP, GFX_SIDE_LEFT);
if(fsQuickRead("border.bgr", borderBuf, 400 * 240 * 3) == RES_OK)
{
// Copy border in swizzled form to GPU render buffer.
GX_displayTransfer(borderBuf, PPF_DIM(240, 400), (u32*)GPU_RENDER_BUF_ADDR,
PPF_DIM(240, 400), PPF_O_FMT(GX_BGR8) | PPF_I_FMT(GX_BGR8) | PPF_OUT_TILED);
GFX_waitForPPF();
}
}
return frameReadyEvent;
}
void OAF_videoExit(void)
{
// frameReadyEvent deleted by this function.
// gbaGfxHandler() will automatically terminate.
LGYCAP_deinit(LGYCAP_DEV_TOP);
if(g_convFinishedEvent != 0)
{
deleteEvent(g_convFinishedEvent);
g_convFinishedEvent = 0;
}
}

View File

@ -1,6 +1,6 @@
/*
* This file is part of open_agb_firm
* Copyright (C) 2021 derrek, profi200
* Copyright (C) 2024 derrek, profi200
*
* 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
@ -16,81 +16,30 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "types.h"
#include "arm11/config.h"
#include "util.h"
#include "arm11/drivers/lgy11.h"
#include "drivers/lgy_common.h"
#include "arm_intrinsic.h"
#include "arm11/fast_rom_padding.h"
#include "oaf_error_codes.h"
#include "fs.h"
#include "fsutil.h"
#include "arm11/fmt.h"
#include "arm11/drivers/lcd.h"
#include "arm11/drivers/lgyfb.h"
#include "drivers/gfx.h"
#include "arm11/drivers/mcu.h"
#include "kernel.h"
#include "kevent.h"
#include "arm11/gpu_cmd_lists.h"
#include "drivers/gfx.h"
#include "arm11/drivers/hid.h"
#include "fsutil.h"
#include "arm11/filebrowser.h"
#include "arm11/drivers/codec.h"
#include "arm11/config.h"
#include "arm11/save_type.h"
#include "arm11/patch.h"
#include "arm11/drivers/codec.h"
#include "drivers/lgy_common.h"
#include "arm11/oaf_video.h"
#include "arm11/drivers/lgy11.h"
#include "kernel.h"
#include "kevent.h"
#define OAF_WORK_DIR "sdmc:/3ds/open_agb_firm"
#define OAF_SAVE_DIR "saves" // Relative to work dir.
// Default config.
// Note: Keep this synchronized with DEFAULT_CONFIG in config.c.
static OafConfig g_oafConfig =
{
// [general]
64, // backlight
5, // backlightSteps
false, // directBoot
true, // useGbaDb
// [video]
2, // scaler
2.2f, // gbaGamma
1.54f, // lcdGamma
1.f, // contrast
0.f, // brightness
// [audio]
0, // Automatic audio output.
127, // Control via volume slider.
// [input]
{ // buttonMaps
0, // A
0, // B
0, // Select
0, // Start
0, // Right
0, // Left
0, // Up
0, // Down
0, // R
0 // L
},
// [game]
0, // saveSlot
0xFF, // saveType
// [advanced]
false, // saveOverride
14 // defaultSave
};
static KHandle g_frameReadyEvent = 0;
@ -100,9 +49,9 @@ static u32 fixRomPadding(const u32 romFileSize)
// Pad unused ROM area with 0xFFs (trimmed ROMs).
// Smallest retail ROM chip is 8 Mbit (1 MiB).
u32 romSize = nextPow2(romFileSize);
if(romSize < 0x100000) romSize = 0x100000;
romSize = (romSize < 0x100000 ? 0x100000 : romSize);
const uintptr_t romLoc = LGY_ROM_LOC;
memset((void*)(romLoc + romFileSize), 0xFFFFFFFF, romSize - romFileSize);
memset((void*)(romLoc + romFileSize), 0xFF, romSize - romFileSize);
u32 mirroredSize = romSize;
if(romSize == 0x100000) // 1 MiB.
@ -120,13 +69,8 @@ static u32 fixRomPadding(const u32 romFileSize)
}
// Fake "open bus" padding.
u32 padding = (romLoc + mirroredSize) / 2;
padding = __pkhbt(padding, padding + 1, 16); // Copy lower half + 1 to upper half.
for(uintptr_t i = romLoc + mirroredSize; i < romLoc + LGY_MAX_ROM_SIZE; i += 4)
{
*(u32*)i = padding;
padding = __uadd16(padding, 0x20002); // Unsigned parallel halfword-wise addition.
}
if(romSize < LGY_MAX_ROM_SIZE)
makeOpenBusPaddingFast((u32*)(romLoc + mirroredSize));
// We don't return the mirrored size because the db hashes are over unmirrored dumps.
return romSize;
@ -156,103 +100,6 @@ static Result loadGbaRom(const char *const path, u32 *const romSizeOut)
return res;
}
static void adjustGammaTableForGba(void)
{
// Credits for this algo go to Extrems.
const float targetGamma = g_oafConfig.gbaGamma;
const float lcdGamma = 1.f / g_oafConfig.lcdGamma;
const float contrast = g_oafConfig.contrast;
const float brightness = g_oafConfig.brightness / contrast;
const float contrastInTargetGamma = powf(contrast, targetGamma);
for(u32 i = 0; i < 256; i++)
{
// Adjust i with brightness and convert to target gamma.
const float adjusted = powf((float)i / 255 + brightness, targetGamma);
// Apply contrast, convert to LCD gamma and clamp.
const u32 res = clamp_s32(powf(contrastInTargetGamma * adjusted, lcdGamma) * 255, 0, 255);
// Same adjustment for red/green/blue.
REG_LCD_PDC0_GTBL_FIFO = res<<16 | res<<8 | res;
}
}
static Result dumpFrameTex(void)
{
// Stop LgyFb before dumping the frame to prevent glitches.
LGYFB_stop();
// 512x-512 (hight negative to flip vertically).
// Pixels at offset 0x40.
alignas(4) static const u8 bmpHeader[54] =
{
0x42, 0x4D, 0x40, 0x00, 0x0C, 0x00, 0x00, 0x00,
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x28, 0x00,
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0xFE,
0xFF, 0xFF, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x13, 0x0B,
0x00, 0x00, 0x13, 0x0B, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
GX_displayTransfer((u32*)0x18200000, 240u<<16 | 512, (u32*)0x18400040, 240u<<16 | 512, 1u<<12 | 1u<<8);
GFX_waitForPPF();
memcpy((void*)0x18400000, bmpHeader, sizeof(bmpHeader));
RtcTimeDate td;
char fn[32];
MCU_getRtcTimeDate(&td);
ee_sprintf(fn, "texture_dump_%04X%02X%02X%02X%02X%02X.bmp", td.y + 0x2000, td.mon, td.d, td.h, td.min, td.s);
const Result res = fsQuickWrite(fn, (void*)0x18400000, 0x40 + 512 * 512 * 3);
// Restart LgyFb.
LGYFB_start();
return res;
}
static void gbaGfxHandler(void *args)
{
const KHandle event = (KHandle)args;
while(1)
{
if(waitForEvent(event) != KRES_OK) break;
clearEvent(event);
// Rotate the frame using the GPU.
// 240x160 no scaling: 184 µs
// 240x160 bilinear x1.5: 408 µs
// 360x240 no scaling: 437 µs
static bool inited = false;
u32 listSize;
const u32 *list;
if(inited == false)
{
inited = true;
listSize = sizeof(gbaGpuInitList);
list = (u32*)gbaGpuInitList;
}
else
{
listSize = sizeof(gbaGpuList2);
list = (u32*)gbaGpuList2;
}
GX_processCommandList(listSize, list);
GFX_waitForP3D();
GX_displayTransfer((u32*)GPU_RENDER_BUF_ADDR, 400u<<16 | 240, GFX_getFramebuffer(SCREEN_TOP), 400u<<16 | 240, 1u<<12 | 1u<<8);
GFX_waitForPPF();
GFX_swapFramebufs();
// Trigger only if both are held and at least one is detected as newly pressed down.
if(hidKeysHeld() == (KEY_Y | KEY_SELECT) && hidKeysDown() != 0)
dumpFrameTex();
}
taskExit();
}
void changeBacklight(s16 amount)
{
u8 min, max;
@ -272,7 +119,7 @@ void changeBacklight(s16 amount)
newVal = (newVal < min ? min : newVal);
g_oafConfig.backlight = (u8)newVal;
GFX_setBrightness((u8)newVal, (u8)newVal);
GFX_setLcdLuminance(newVal);
}
static void updateBacklight(void)
@ -292,23 +139,23 @@ static void updateBacklight(void)
changeBacklight(-steps);
// Disable backlight switching in debug builds on 2DS.
const GfxBlight lcd = (MCU_getSystemModel() != 3 ? GFX_BLIGHT_TOP : GFX_BLIGHT_BOT);
const GfxBl lcd = (MCU_getSystemModel() != SYS_MODEL_2DS ? GFX_BL_TOP : GFX_BL_BOT);
#ifndef NDEBUG
if(lcd != GFX_BLIGHT_BOT)
if(lcd != GFX_BL_BOT)
#endif
{
// Turn off backlight.
if(backlightOn && kHeld == (KEY_X | KEY_DLEFT))
{
backlightOn = false;
GFX_powerOffBacklights(lcd);
GFX_powerOffBacklight(lcd);
}
// Turn on backlight.
if(!backlightOn && kHeld == (KEY_X | KEY_DRIGHT))
{
backlightOn = true;
GFX_powerOnBacklights(lcd);
GFX_powerOnBacklight(lcd);
}
}
}
@ -364,15 +211,23 @@ static Result showFileBrowser(char romAndSavePath[512])
static void rom2GameCfgPath(char romPath[512])
{
// 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");
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");
// Construct the new path.
strcpy(romPath, OAF_SAVE_DIR "/");
strcat(romPath, tmpIniFileName);
// Construct the new path.
strcpy(romPath, OAF_SAVE_DIR "/");
strcat(romPath, tmpIniFileName);
}
else
{
// Change the extension to .ini.
strcpy(romPath + strlen(romPath) - 4, ".ini");
}
}
static void gameCfg2SavePath(char cfgPath[512], const u8 saveSlot)
@ -407,6 +262,10 @@ Result oafParseConfigEarly(void)
res = fMkdir(OAF_SAVE_DIR);
if(res != RES_OK && res != RES_FR_EXIST) break;
// Create screenshots folder.
res = fMkdir(OAF_SCREENSHOT_DIR);
if(res != RES_OK && res != RES_FR_EXIST) break;
// Parse the config.
res = parseOafConfig("config.ini", &g_oafConfig, true);
} while(0);
@ -414,55 +273,6 @@ Result oafParseConfigEarly(void)
return res;
}
KHandle setupFrameCapture(const u8 scaler)
{
const bool is240x160 = scaler < 2;
static const s16 matrix[12 * 8] =
{
// Vertical.
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0x24B0, 0x4000, 0, 0x24B0, 0x4000, 0, 0,
0x4000, 0x2000, 0, 0x4000, 0x2000, 0, 0, 0,
0, -0x4B0, 0, 0, -0x4B0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
// Horizontal.
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0x24B0, 0, 0, 0x24B0, 0, 0,
0x4000, 0x4000, 0x2000, 0x4000, 0x4000, 0x2000, 0, 0,
0, 0, -0x4B0, 0, 0, -0x4B0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
ScalerCfg gbaCfg;
gbaCfg.w = (is240x160 ? 240 : 360);
gbaCfg.h = (is240x160 ? 160 : 240);
gbaCfg.vLen = 6;
gbaCfg.vPatt = 0b00011011;
memcpy(gbaCfg.vMatrix, matrix, 6 * 8 * 2);
gbaCfg.hLen = 6;
gbaCfg.hPatt = (is240x160 ? 0b00111111 : 0b00011011);
if(is240x160)
{
memset(gbaCfg.hMatrix, 0, 6 * 8 * 2);
s16 *const identityRow = &gbaCfg.hMatrix[3 * 8];
for(unsigned i = 0; i < 6; i++)
{
// Set identity entries.
identityRow[i] = 0x4000;
}
}
else
{
memcpy(gbaCfg.hMatrix, &matrix[6 * 8], 6 * 8 * 2);
}
return LGYFB_init(&gbaCfg);
}
Result oafInitAndRun(void)
{
Result res;
@ -518,41 +328,17 @@ Result oafInitAndRun(void)
res = LGY_prepareGbaMode(g_oafConfig.directBoot, saveType, filePath);
if(res == RES_OK)
{
#ifdef NDEBUG
// Force black and turn the backlight off on the bottom screen.
// Don't turn the backlight off on 2DS (1 panel).
GFX_setForceBlack(false, true);
if(MCU_getSystemModel() != 3) GFX_powerOffBacklights(GFX_BLIGHT_BOT);
#endif
// Initialize video output (frame capture, post processing ect.).
g_frameReadyEvent = OAF_videoInit();
// Initialize frame capture and frame handler.
const KHandle frameReadyEvent = setupFrameCapture(g_oafConfig.scaler);
patchGbaGpuCmdList(g_oafConfig.scaler);
createTask(0x800, 3, gbaGfxHandler, (void*)frameReadyEvent);
g_frameReadyEvent = frameReadyEvent;
// Adjust gamma table and setup button overrides.
adjustGammaTableForGba();
// Setup button overrides.
const u32 *const maps = g_oafConfig.buttonMaps;
u16 overrides = 0;
for(unsigned i = 0; i < 10; i++)
if(maps[i] != 0) overrides |= 1u<<i;
LGY11_selectInput(overrides);
// Load border if any exists.
if(g_oafConfig.scaler == 0) // No borders for scaled modes.
{
// Abuse currently invisible frame buffer as temporary buffer.
void *const borderBuf = GFX_getFramebuffer(SCREEN_TOP);
if(fsQuickRead("border.bgr", borderBuf, 400 * 240 * 3) == RES_OK)
{
// Copy border in swizzled form to GPU render buffer.
GX_displayTransfer(borderBuf, 400u<<16 | 240, (u32*)GPU_RENDER_BUF_ADDR, 400u<<16 | 240, 1u<<12 | 1u<<8 | 1u<<1);
GFX_waitForPPF();
}
}
// Sync LgyFb start with LCD VBlank.
// Sync LgyCap start with LCD VBlank.
GFX_waitForVBlank0();
LGY11_switchMode();
}
@ -580,13 +366,13 @@ void oafUpdate(void)
CODEC_runHeadphoneDetection();
updateBacklight();
waitForEvent(g_frameReadyEvent);
clearEvent(g_frameReadyEvent);
}
void oafFinish(void)
{
// frameReadyEvent deleted by this function.
// gbaGfxHandler() will automatically terminate.
LGYFB_deinit();
OAF_videoExit();
g_frameReadyEvent = 0;
LGY11_deinit();
}
}

View File

@ -30,91 +30,93 @@
#include "drivers/sha.h"
#define min(a, b) ((size_t) (a) <= (size_t) (b) ? (size_t) (a) : (size_t) (b))
typedef struct
{
u8 *buffer;
u16 cacheSize;
u16 cacheOffset;
u16 maxCacheSize;
u8 *buffer;
u16 cacheSize;
u16 cacheOffset;
u16 maxCacheSize;
} Cache;
static u8 readCache(const FHandle patchHandle, Cache *cache, Result *res) {
u8 result = (cache->buffer)[(cache->cacheOffset)++];
if((cache->cacheOffset) >= (cache->cacheSize)) {
(cache->cacheSize) = min((cache->maxCacheSize), ((fSize(patchHandle)-12) - fTell(patchHandle)));
*res = fRead(patchHandle, (cache->buffer), (cache->cacheSize), NULL);
(cache->cacheOffset) = 0;
}
u8 result = (cache->buffer)[(cache->cacheOffset)++];
if((cache->cacheOffset) >= (cache->cacheSize)) {
(cache->cacheSize) = min((cache->maxCacheSize), ((fSize(patchHandle)-12) - fTell(patchHandle)));
*res = fRead(patchHandle, (cache->buffer), (cache->cacheSize), NULL);
(cache->cacheOffset) = 0;
}
return result;
return result;
}
static Result patchIPS(const FHandle patchHandle) {
Result res = RES_OK;
ee_puts("IPS patch found! Patching...");
const u16 bufferSize = 512;
char *buffer = (char*)calloc(bufferSize, 1);
if(buffer == NULL) {
return RES_OUT_OF_MEM;
if(buffer == NULL) return RES_OUT_OF_MEM;
// Verify patch is IPS (magic number "PATCH").
Result res = fRead(patchHandle, buffer, 5, NULL);
if(res != RES_OK || memcmp("PATCH", buffer, 5) != 0)
{
free(buffer);
return RES_INVALID_PATCH;
}
//verify patch is IPS patch (magic number "PATCH")
bool isValidPatch = false;
res = fRead(patchHandle, buffer, 5, NULL);
if(res == RES_OK) {
if(memcmp("PATCH", buffer, 5) == 0) {
isValidPatch = true;
} else {
res = RES_INVALID_PATCH;
}
}
u32 offset = 0;
u16 length = 0;
while(res == RES_OK)
{
// Read offset.
res = fRead(patchHandle, buffer, 3, NULL);
if(res != RES_OK || memcmp("EOF", buffer, 3) == 0) break;
offset = (buffer[0] << 16) + (buffer[1] << 8) + buffer[2];
if(isValidPatch) {
u32 offset = 0;
u16 length = 0;
// Read length.
res = fRead(patchHandle, buffer, 2, NULL);
if(res != RES_OK) break;
length = (buffer[0] << 8) + buffer[1];
while(res == RES_OK) {
//read offset
// RLE hunk.
if(length == 0)
{
res = fRead(patchHandle, buffer, 3, NULL);
if (res != RES_OK || memcmp("EOF", buffer, 3)==0) break;
offset = (buffer[0]<<16) + (buffer[1]<<8) + (buffer[2]);
//read length
res = fRead(patchHandle, buffer, 2, NULL);
if(res != RES_OK) break;
length = (buffer[0]<<8) + (buffer[1]);
//RLE hunk
if(length == 0) {
res = fRead(patchHandle, buffer, 3, NULL);
if(res != RES_OK) break;
length = (buffer[0] << 8) + buffer[1];
memset((void*)(LGY_ROM_LOC + offset), buffer[2], length * sizeof(char));
u16 tempLen = (buffer[0]<<8) + (buffer[1]);
memset((void*)(LGY_ROM_LOC + offset), buffer[2], tempLen*sizeof(char));
continue;
}
// Regular hunks.
u16 fullCount = length / bufferSize;
for(u16 i = 0; i < fullCount; ++i)
{
res = fRead(patchHandle, buffer, bufferSize, NULL);
if(res != RES_OK) break;
for(u16 j = 0; j < bufferSize; ++j)
{
*(char*)(LGY_ROM_LOC + offset + (bufferSize * i) + j) = buffer[j];
}
//regular hunks
else {
u16 fullCount = length/bufferSize;
for(u16 i=0; i<fullCount; ++i) {
res = fRead(patchHandle, buffer, bufferSize, NULL);
if(res != RES_OK) break;
for(u16 j=0; j<bufferSize; ++j) {
*(char*)(LGY_ROM_LOC+offset+(bufferSize*i)+j) = buffer[j];
}
}
}
u16 remaining = length%bufferSize;
if(remaining != 0) {
res = fRead(patchHandle, buffer, remaining, NULL);
if(res != RES_OK) break;
for(u16 j=0; j<remaining; ++j) {
*(char*)(LGY_ROM_LOC+offset+(fullCount*bufferSize)+j) = buffer[j];
}
}
}
u16 remaining = length % bufferSize;
if(remaining == 0) continue;
res = fRead(patchHandle, buffer, remaining, NULL);
if(res != RES_OK) break;
for(u16 j = 0; j < remaining; ++j)
{
*(char*)(LGY_ROM_LOC + offset + (bufferSize * fullCount) + j) = buffer[j];
}
}
@ -130,7 +132,7 @@ static uintmax_t read_vuint(const FHandle patchFile, Result *res, Cache *cache)
uint8_t octet = 0;
for (;;) {
//*res = fRead(patchFile, &octet, 1, NULL);
octet = readCache(patchFile, cache, res);
octet = readCache(patchFile, cache, res);
if(*res != RES_OK) break;
if(octet & 0x80) {
result += (octet & 0x7f) << shift;
@ -170,7 +172,7 @@ static Result patchUPS(const FHandle patchHandle, u32 *romSize) {
magic[i] = readCache(patchHandle, &cache, &res);
if(res != RES_OK) break;
}
if(res == RES_OK) {
if(memcmp(&magic, "UPS1", 4) == 0) {
isValidPatch = true;
@ -180,16 +182,16 @@ static Result patchUPS(const FHandle patchHandle, u32 *romSize) {
}
if(isValidPatch) {
//get rom size
//get rom size
u32 baseRomSize = (u32)read_vuint(patchHandle, &res, &cache);
if(res != RES_OK) { free(cache.buffer); return res; }
//get patched rom size
u32 patchedRomSize = (u32)read_vuint(patchHandle, &res, &cache);
if(res != RES_OK) { free(cache.buffer); return res; }
debug_printf("Base size: 0x%lx\nPatched size: 0x%lx\n", baseRomSize, patchedRomSize);
debug_printf("Base size: 0x%lx\nPatched size: 0x%lx\n", baseRomSize, patchedRomSize);
if(patchedRomSize > baseRomSize) {
if(patchedRomSize > baseRomSize) {
//scale up rom
*romSize = nextPow2(patchedRomSize);
//check if upscaled rom is too big
@ -203,19 +205,19 @@ static Result patchUPS(const FHandle patchHandle, u32 *romSize) {
memset((char*)(LGY_ROM_LOC + baseRomSize), 0x00u, patchedRomSize - baseRomSize); //fill new patch area with 0's
}
uintmax_t patchFileSize = fSize(patchHandle);
uintmax_t patchFileSize = fSize(patchHandle);
uintmax_t offset = 0;
uintmax_t offset = 0;
u8 readByte = 0;
u8 *romBytes = ((u8*)LGY_ROM_LOC);
while(fTell(patchHandle) < (patchFileSize-12) && res==RES_OK) {
offset += read_vuint(patchHandle, &res, &cache);
if(res != RES_OK) break;
while(fTell(patchHandle) < (patchFileSize-12) && res==RES_OK) {
offset += read_vuint(patchHandle, &res, &cache);
if(res != RES_OK) break;
while(offset<*romSize) {
readByte = readCache(patchHandle, &cache, &res);
if(res != RES_OK) break;
if(res != RES_OK) break;
if(readByte == 0x00) {
offset++;
@ -225,8 +227,8 @@ static Result patchUPS(const FHandle patchHandle, u32 *romSize) {
offset++;
}
}
}
}
free(cache.buffer);
@ -251,7 +253,7 @@ Result patchRom(const char *const gamePath, u32 *romSize) {
if(patchPathBase != NULL && patchPath != NULL) {
strcpy(patchPathBase, gamePath);
memset(patchPathBase+extensionOffset, '\0', 3); //replace 'gba' with '\0' characters
FHandle f;
//check if patch file is present. If so, call appropriate patching function
if((res = fOpen(&f, strcat(patchPathBase, "ips"), FA_OPEN_EXISTING | FA_READ)) == RES_OK)
@ -272,7 +274,7 @@ Result patchRom(const char *const gamePath, u32 *romSize) {
}
//reset patchPathBase
memset(patchPathBase+extensionOffset, '\0', 3);
if ((res = fOpen(&f, strcat(patchPathBase, "ups"), FA_OPEN_EXISTING | FA_READ)) == RES_OK)
{
res = patchUPS(f, romSize);

View File

@ -135,46 +135,54 @@ u16 detectSaveType(const u32 romSize, const u16 defaultSave)
return saveType;
}
// Search for entry with first u64 of the SHA1 = x using binary search.
static Result searchGbaDb(const u64 x, GbaDbEntry *const db, s32 *const entryPos)
// Search for the entry with first u64 of the SHA1 = x using binary search.
// Note: Loading the whole db to memory first is still slower.
static Result searchGbaDb(const u64 x, GbaDbEntry *const db)
{
debug_printf("Database search: '%016" PRIX64 "'\n", __builtin_bswap64(x));
Result res;
FHandle f;
if((res = fOpen(&f, "gba_db.bin", FA_OPEN_EXISTING | FA_READ)) == RES_OK)
Result res = fOpen(&f, "gba_db.bin", FA_OPEN_EXISTING | FA_READ);
if(res != RES_OK) return res;
u32 l = 0;
u32 r = fSize(f) / sizeof(GbaDbEntry);
while(l < r)
{
s32 l = 0;
s32 r = fSize(f) / sizeof(GbaDbEntry) - 1; // TODO: Check for 0!
while(1)
const u32 m = l + (r - l) / 2;
//debug_printf("l: %" PRIu32 " m: %" PRIu32 " r: %" PRIu32 "\n", l, m, r);
res = fLseek(f, sizeof(GbaDbEntry) * m);
if(res != RES_OK)
{
const s32 mid = l + (r - l) / 2;
debug_printf("l: %ld r: %ld mid: %ld\n", l, r, mid);
if((res = fLseek(f, sizeof(GbaDbEntry) * mid)) != RES_OK) break;
if((res = fRead(f, db, sizeof(GbaDbEntry), NULL)) != RES_OK) break;
const u64 tmp = *(u64*)db->sha1; // Unaligned access.
if(tmp == x)
{
*entryPos = mid; // TODO: Remove.
break;
}
if(r <= l)
{
debug_printf("Not found!\n");
res = RES_NOT_FOUND;
break;
}
if(tmp > x) r = mid - 1;
else l = mid + 1;
fClose(f);
return res;
}
res = fRead(f, db, sizeof(GbaDbEntry), NULL);
if(res != RES_OK)
{
fClose(f);
return res;
}
fClose(f);
u64 tmp;
memcpy(&tmp, db->sha1, 8);
if(x > tmp)
{
l = m + 1;
}
else if(x < tmp)
{
r = m;
}
else
{
fClose(f);
return RES_OK;
}
}
return res;
fClose(f);
return RES_NOT_FOUND;
}
u16 getSaveType(const OafConfig *const cfg, const u32 romSize, const char *const savePath)
@ -189,9 +197,8 @@ u16 getSaveType(const OafConfig *const cfg, const u32 romSize, const char *const
Result res;
GbaDbEntry dbEntry;
s32 dbPos = -1;
u16 saveType = SAVE_TYPE_NONE;
res = searchGbaDb(*sha1, &dbEntry, &dbPos);
res = searchGbaDb(*sha1, &dbEntry);
if(res == RES_OK) saveType = dbEntry.attr & 0xFu;
else if(!saveOverride && res == RES_NOT_FOUND) return autoSaveType;
else if(res != RES_NOT_FOUND)
@ -237,9 +244,10 @@ u16 getSaveType(const OafConfig *const cfg, const u32 romSize, const char *const
cursor = saveTypeCursorLut[saveType];
while(1)
{
ee_printf("\x1b[%u;H ", oldCursor + 6);
ee_printf("\x1b[%u;H>", cursor + 6);
ee_printf("\x1b[%u;H ", oldCursor + 7);
ee_printf("\x1b[%u;H>", cursor + 7);
oldCursor = cursor;
GFX_flushBuffers();
u32 kDown;
do

View File

@ -27,4 +27,4 @@ int main(void)
while(1) __wfi();
return 0;
}
}

View File

@ -18,10 +18,10 @@
#include "oaf_error_codes.h"
#include "drivers/gfx.h"
#ifdef ARM11
#include "arm11/fmt.h"
#include "arm11/drivers/hid.h"
#endif
#ifdef __ARM11__
#include "arm11/fmt.h"
#include "arm11/drivers/hid.h"
#endif // #ifdef __ARM11__
@ -36,7 +36,7 @@ const char* oafResult2String(Result res)
return (res < CUSTOM_ERR_OFFSET ? result2String(res) : oafResultStrings[res - CUSTOM_ERR_OFFSET]);
}
#ifdef ARM11
#ifdef __ARM11__
void printError(Result res)
{
ee_printf("Error: %s.\n", oafResult2String(res));
@ -63,4 +63,4 @@ void printErrorWaitInput(Result res, u32 waitKeys)
if(hidGetExtraKeys(0) & (KEY_POWER_HELD | KEY_POWER)) break;
}
}
#endif // ifdef ARM11
#endif // ifdef __ARM11__

@ -1 +1 @@
Subproject commit c18b949b71f45e78b1f9a28c5d458bce0da505d6
Subproject commit ed3c5f14b136c936d615ee3b38aaa7e7e642f12c