Skip to content

Commit a6ce261

Browse files
committed
Add game speed adjustment
Does not apply to B4DS mode
1 parent 6b6de70 commit a6ce261

File tree

16 files changed

+269
-19
lines changed

16 files changed

+269
-19
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,12 @@ An alternative forwarder generator for 3DS users. YANBF forwarders are 3DS-mode
6767
- [shutterbug2000](https://github.com/shutterbug2000): SDK5 support, help with DSi mode support, and some other implemented stuff
6868
- [ahezard](https://github.com/ahezard): Starting the project, former lead developer
6969
- [Pk11](https://github.com/Epicpkmn11): In-game menu, screenshot taking, manual loading, and translation management
70-
- [Gericom](https://github.com/Gericom): Improving B4DS compatibility, parts of libtwl code used, Pokémon Wii connection patch, and SD -> flashcard R/W patch for DSiWare
70+
- [Gericom](https://github.com/Gericom):
71+
- Improving B4DS compatibility
72+
- Parts of libtwl code used
73+
- Pokémon Wii connection patch from Pico Loader
74+
- SD -> flashcard R/W patch for DSiWare
75+
- Frame rate adjustment code from [FastVideoDSPlayer](https://github.com/Gericom/FastVideoDSPlayer) used for game speed adjustment
7176

7277
## Other
7378
- [devkitPro](https://devkitpro.org): devkitARM and libnds

retail/arm9/source/conf_sd.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -624,8 +624,9 @@ void getIgmStrings(configuration* conf, bool b4ds) {
624624
setIgmString(lang.fetch("OPTIONS", "MAIN_SCREEN", "Main Screen").c_str(), igmText->optionsLabels[0]);
625625
setIgmString(lang.fetch("OPTIONS", "BRIGHTNESS", "Brightness").c_str(), igmText->optionsLabels[1]);
626626
setIgmString(lang.fetch("OPTIONS", "VOLUME", "Volume").c_str(), igmText->optionsLabels[2]);
627-
setIgmString(lang.fetch("OPTIONS", "CLOCK_SPEED", "Clock Speed").c_str(), igmText->optionsLabels[3]);
628-
setIgmString(lang.fetch("OPTIONS", "VRAM_MODE", "VRAM Mode").c_str(), igmText->optionsLabels[4]);
627+
setIgmString(lang.fetch("OPTIONS", "GAME_SPEED", "Game Speed").c_str(), igmText->optionsLabels[3]);
628+
setIgmString(lang.fetch("OPTIONS", "CLOCK_SPEED", "Clock Speed").c_str(), igmText->optionsLabels[4]);
629+
setIgmString(lang.fetch("OPTIONS", "VRAM_MODE", "VRAM Mode").c_str(), igmText->optionsLabels[5]);
629630
setIgmString(lang.fetch("OPTIONS", "AUTO", "Auto").c_str(), igmText->optionsValues[0]);
630631
setIgmString(lang.fetch("OPTIONS", "BOTTOM", "Bottom").c_str(), igmText->optionsValues[1]);
631632
setIgmString(lang.fetch("OPTIONS", "TOP", "Top").c_str(), igmText->optionsValues[2]);

retail/bootloaderi/source/arm7/hook_arm7.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ int hookNdsRetailArm7(
530530

531531
extern u32 cheatSizeTotal;
532532
extern char cheatEngineBuffer[0x400];
533-
u16 cheatSizeLimit = (/* ce7NotFound ? 0x1C00 : */ 0x8000);
533+
u16 cheatSizeLimit = (/* ce7NotFound ? 0x1C00 : */ 0x4000);
534534
// if (!ce7NotFound) {
535535
if (cheatEngineOffset == CHEAT_ENGINE_DSIWARE_LOCATION) {
536536
cheatSizeLimit -= 0x1800;

retail/bootloaderi/source/arm7/main.arm7.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2245,7 +2245,7 @@ int arm7_main(void) {
22452245

22462246
const u16 ce9size = (ROMsupportsDsiMode(&dsiHeaderTemp.ndshdr) && dsiModeConfirmed) ? 0x3800 : 0x2FA0;
22472247
ce7Location = *(u32*)CARDENGINEI_ARM7_BUFFERED_LOCATION;
2248-
u32 ce7Size = 0xB800;
2248+
u32 ce7Size = 0xF800;
22492249

22502250
const bool useSdk5ce7 = (isSdk5(moduleParams) && ROMsupportsDsiMode(&dsiHeaderTemp.ndshdr) && dsiModeConfirmed);
22512251
if (useSdk5ce7) {

retail/cardenginei/arm7/cardengine.ld.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ OUTPUT_ARCH(arm)
66

77
MEMORY {
88

9-
vram : ORIGIN = CARDENGINEI_ARM7_LOCATION, LENGTH = 45K /* WRAM A */
9+
vram : ORIGIN = CARDENGINEI_ARM7_LOCATION, LENGTH = 61K /* WRAM A */
1010
}
1111

1212
__vram_start = ORIGIN(vram);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#pragma once
2+
3+
#define FPSA_SYS_CLOCK 33513982
4+
5+
#define FPSA_CYCLES_PER_PIXEL 6
6+
7+
#define FPSA_LCD_WIDTH 256
8+
#define FPSA_LCD_HBLANK 99
9+
#define FPSA_LCD_COLUMNS (FPSA_LCD_WIDTH + FPSA_LCD_HBLANK)
10+
11+
#define FPSA_LCD_HEIGHT 192
12+
#define FPSA_LCD_VBLANK 71
13+
#define FPSA_LCD_LINES (FPSA_LCD_HEIGHT + FPSA_LCD_VBLANK)
14+
15+
#define FPSA_CYCLES_PER_LINE (FPSA_LCD_COLUMNS * FPSA_CYCLES_PER_PIXEL)
16+
#define FPSA_CYCLES_PER_FRAME (FPSA_LCD_COLUMNS * FPSA_LCD_LINES * FPSA_CYCLES_PER_PIXEL)
17+
18+
#define FPSA_ADJUST_MIN_VCOUNT 202
19+
#define FPSA_ADJUST_MAX_VCOUNT 212
20+
21+
typedef struct
22+
{
23+
u32 isStarted;
24+
u32 isFpsLower; //TRUE if vblank needs to be extended, FALSE if it needs to be shortened
25+
u32 backJump;
26+
u32 initial;
27+
u64 targetCycles; //target cycles in 0.40.24 format
28+
s64 cycleDelta;
29+
} fpsa_t;
30+
31+
void fpsa_init(fpsa_t* fpsa);
32+
void fpsa_start(fpsa_t* fpsa);
33+
void fpsa_stop(fpsa_t* fpsa);
34+
35+
//Sets the target frame rate as clock cycles per frame in 0.40.24 format
36+
void fpsa_setTargetFrameCycles(fpsa_t* fpsa, u64 cycles);
37+
38+
//Sets the target frame rate as a frames per second fraction
39+
void fpsa_setTargetFpsFraction(fpsa_t* fpsa, u32 num, u32 den);
40+
41+
//Sets the target frame rate in integer frames per second
42+
static inline void fpsa_setTargetFps(fpsa_t* fpsa, u32 fps)
43+
{
44+
fpsa_setTargetFpsFraction(fpsa, fps, 1);
45+
}

retail/cardenginei/arm7/source/cardengine.c

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include "unpatched_funcs.h"
4141
#include "debug_file.h"
4242
#include "cardengine.h"
43+
#include "fpsAdjust.h"
4344
#include "nds_header.h"
4445
#include "igm_text.h"
4546

@@ -182,6 +183,10 @@ static int noI2CVolLevel = 127; // Volume workaround for bricked I2C chips
182183
static int volumeAdjustDelay = 0;
183184
static bool volumeAdjustActivated = false;
184185

186+
#ifndef TWLSDK
187+
fpsa_t sActiveFpsa;
188+
#endif
189+
185190
//static bool ndmaUsed = false;
186191

187192
// static int cardEgnineCommandMutex = 0;
@@ -1172,6 +1177,142 @@ void unloadInGameMenu(void) {
11721177
sharedAddr[5] = 0;
11731178
}
11741179

1180+
#ifndef TWLSDK
1181+
static void vcountIrqLower()
1182+
{
1183+
while (1)
1184+
{
1185+
if (sActiveFpsa.initial)
1186+
{
1187+
sActiveFpsa.initial = FALSE;
1188+
break;
1189+
}
1190+
1191+
if (!sActiveFpsa.backJump)
1192+
sActiveFpsa.cycleDelta += sActiveFpsa.targetCycles - ((u64)FPSA_CYCLES_PER_FRAME << 24);
1193+
u32 linesToAdd = 0;
1194+
while (sActiveFpsa.cycleDelta >= (s64)((u64)FPSA_CYCLES_PER_LINE << 23))
1195+
{
1196+
sActiveFpsa.cycleDelta -= (u64)FPSA_CYCLES_PER_LINE << 24;
1197+
if (++linesToAdd == 5)
1198+
break;
1199+
}
1200+
if (linesToAdd == 0)
1201+
{
1202+
sActiveFpsa.backJump = FALSE;
1203+
break;
1204+
}
1205+
if (linesToAdd > 1)
1206+
{
1207+
sActiveFpsa.backJump = TRUE;
1208+
}
1209+
else
1210+
{
1211+
// don't set the backJump flag because the irq is not retriggered if the new vcount
1212+
// is the same as the previous line
1213+
sActiveFpsa.backJump = FALSE;
1214+
}
1215+
// ensure we won't accidentally run out of line time
1216+
while (REG_DISPSTAT & DISP_IN_HBLANK)
1217+
;
1218+
int curVCount = REG_VCOUNT;
1219+
REG_VCOUNT = curVCount - (linesToAdd - 1);
1220+
if (linesToAdd == 1)
1221+
break;
1222+
1223+
while (REG_VCOUNT >= curVCount)//FPSA_ADJUST_MAX_VCOUNT - 5)
1224+
;
1225+
while (REG_VCOUNT < curVCount)//FPSA_ADJUST_MAX_VCOUNT - 5)
1226+
;
1227+
}
1228+
REG_IF = IRQ_VCOUNT;
1229+
}
1230+
1231+
static void vcountIrqHigher()
1232+
{
1233+
if (sActiveFpsa.initial)
1234+
{
1235+
sActiveFpsa.initial = FALSE;
1236+
return;
1237+
}
1238+
sActiveFpsa.cycleDelta += ((u64)FPSA_CYCLES_PER_FRAME << 24) - sActiveFpsa.targetCycles;
1239+
u32 linesToSkip = 0;
1240+
while (sActiveFpsa.cycleDelta >= (s64)((u64)FPSA_CYCLES_PER_LINE << 23))
1241+
{
1242+
sActiveFpsa.cycleDelta -= (u64)FPSA_CYCLES_PER_LINE << 24;
1243+
if (++linesToSkip == 55)
1244+
break;
1245+
}
1246+
if (linesToSkip == 0)
1247+
return;
1248+
// ensure we won't accidentally run out of line time
1249+
while (REG_DISPSTAT & DISP_IN_HBLANK)
1250+
;
1251+
REG_VCOUNT = REG_VCOUNT + (linesToSkip + 1);
1252+
}
1253+
1254+
void fpsa_init(fpsa_t* fpsa)
1255+
{
1256+
toncset(fpsa, 0, sizeof(fpsa_t));
1257+
fpsa->isStarted = FALSE;
1258+
fpsa_setTargetFrameCycles(fpsa, (u64)FPSA_CYCLES_PER_FRAME << 24); // default to no adjustment
1259+
}
1260+
1261+
void fpsa_start(fpsa_t* fpsa)
1262+
{
1263+
// int irq = enterCriticalSection();
1264+
do
1265+
{
1266+
if (fpsa->isStarted)
1267+
break;
1268+
if (fpsa->targetCycles == ((u64)FPSA_CYCLES_PER_FRAME << 24))
1269+
break;
1270+
fpsa->backJump = FALSE;
1271+
fpsa->cycleDelta = 0;
1272+
fpsa->initial = TRUE;
1273+
fpsa->isFpsLower = fpsa->targetCycles >= ((u64)FPSA_CYCLES_PER_FRAME << 24);
1274+
// prevent the irq from immediately happening
1275+
while (REG_VCOUNT != FPSA_ADJUST_MAX_VCOUNT + 2)
1276+
;
1277+
fpsa->isStarted = TRUE;
1278+
if (fpsa->isFpsLower)
1279+
{
1280+
SetYtrigger(FPSA_ADJUST_MAX_VCOUNT - 5);
1281+
}
1282+
else
1283+
{
1284+
SetYtrigger(FPSA_ADJUST_MIN_VCOUNT);
1285+
}
1286+
} while (0);
1287+
// leaveCriticalSection(irq);
1288+
}
1289+
1290+
void fpsa_stop(fpsa_t* fpsa)
1291+
{
1292+
if (!fpsa->isStarted)
1293+
return;
1294+
fpsa->isStarted = FALSE;
1295+
}
1296+
1297+
void fpsa_setTargetFrameCycles(fpsa_t* fpsa, u64 cycles)
1298+
{
1299+
fpsa->targetCycles = cycles;
1300+
}
1301+
1302+
void fpsa_setTargetFpsFraction(fpsa_t* fpsa, u32 num, u32 den)
1303+
{
1304+
u64 cycles = (((double)FPSA_SYS_CLOCK * den * (1 << 24)) / num) + 0.5;
1305+
fpsa_setTargetFrameCycles(fpsa, cycles);//((((u64)FPSA_SYS_CLOCK * (u64)den) << 24) + ((num + 1) >> 1)) / num);
1306+
}
1307+
1308+
void fpsa_run(void) {
1309+
if (!sActiveFpsa.isStarted) {
1310+
return;
1311+
}
1312+
sActiveFpsa.isFpsLower ? vcountIrqLower() : vcountIrqHigher();
1313+
}
1314+
#endif
1315+
11751316
#ifdef DEBUG
11761317
static void log_arm9(void) {
11771318
//driveInitialize();
@@ -2007,6 +2148,10 @@ void myIrqHandlerVBlank(void) {
20072148
IPC_SendSync(screenIpc);
20082149
}
20092150

2151+
#ifndef TWLSDK
2152+
fpsa_run();
2153+
#endif
2154+
20102155
if (sharedAddr[0] == 0x524F5245) { // 'EROR'
20112156
REG_MASTER_VOLUME = 0;
20122157
while (REG_VCOUNT != 191) swiDelay(100);

retail/cardenginei/arm7/source/inGameMenu.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "igm_text.h"
1111
#include "locations.h"
1212
#include "cardengine.h"
13+
#include "fpsAdjust.h"
1314
#include "nds_header.h"
1415
#include "tonccpy.h"
1516

@@ -145,6 +146,22 @@ void inGameMenu(void) {
145146
saveMainScreenSetting();
146147
afterSwapTimer = 0;
147148
break;
149+
case 0x41535046: // FPSA
150+
#ifndef TWLSDK
151+
extern fpsa_t sActiveFpsa;
152+
153+
const u32 num = sharedAddr[0];
154+
if (num == 60000) {
155+
fpsa_stop(&sActiveFpsa);
156+
} else {
157+
fpsa_init(&sActiveFpsa);
158+
fpsa_setTargetFpsFraction(&sActiveFpsa, num, num >= 1000 ? 1001 : 1);
159+
fpsa_start(&sActiveFpsa);
160+
}
161+
#else
162+
sharedAddr[0] = 0xFFFFFFFF;
163+
#endif
164+
break;
148165
case 0x444D4152: // RAMD
149166
dumpRam();
150167
exitMenu = true;

retail/cardenginei/arm7_alt/cardengine.ld.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ OUTPUT_ARCH(arm)
66

77
MEMORY {
88

9-
vram : ORIGIN = CARDENGINEI_ARM7_LOCATION_ALT, LENGTH = 45K /* WRAM A */
9+
vram : ORIGIN = CARDENGINEI_ARM7_LOCATION_ALT, LENGTH = 61K /* WRAM A */
1010
}
1111

1212
__vram_start = ORIGIN(vram);

retail/cardenginei/arm7_dsiware/source/inGameMenu.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ void inGameMenu(void) {
144144
saveMainScreenSetting();
145145
afterSwapTimer = 0;
146146
break;
147+
case 0x41535046: // FPSA
148+
sharedAddr[0] = 0xFFFFFFFF;
149+
break;
147150
case 0x444D4152: // RAMD
148151
dumpRam();
149152
exitMenu = true;

0 commit comments

Comments
 (0)