Compare commits
34 Commits
alpha_2023
...
master
Author | SHA1 | Date |
---|---|---|
![]() |
6b882e9395 | |
![]() |
4e4c2aa2b5 | |
![]() |
dd90d498c4 | |
![]() |
877f7c61d0 | |
![]() |
7a7190baea | |
![]() |
edd43a6fcc | |
![]() |
50e2f30e0d | |
![]() |
79b2457e8b | |
![]() |
5356e5c89a | |
![]() |
67ce019b36 | |
![]() |
41db8760a6 | |
![]() |
3a70cdc27f | |
![]() |
b1c66e24a2 | |
![]() |
f7efd62ea9 | |
![]() |
073daac2bb | |
![]() |
15436e02a7 | |
![]() |
3e91716c80 | |
![]() |
f9786b14a1 | |
![]() |
bd5b207106 | |
![]() |
7c8c3d6fe8 | |
![]() |
6cf6059023 | |
![]() |
1ad621d4f4 | |
![]() |
9f331c24f7 | |
![]() |
3ffb706f3b | |
![]() |
c85a4b1b57 | |
![]() |
6db20e190c | |
![]() |
d7f701f02a | |
![]() |
2bca9b04b6 | |
![]() |
678cbd9ae3 | |
![]() |
479069d039 | |
![]() |
99cf0fdacc | |
![]() |
9d13ad3722 | |
![]() |
07bdaf521d | |
![]() |
e14704181c |
|
@ -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
105
README.md
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -22,4 +22,13 @@
|
|||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
Result browseFiles(const char *const basePath, char selected[512]);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
|
@ -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
|
|
@ -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);
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 = ®_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 = ®_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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -27,4 +27,4 @@ int main(void)
|
|||
while(1) __wfi();
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue