Skip to content

Commit 0ad0fb8

Browse files
committed
Implement basic state saving and loading
1 parent b54398a commit 0ad0fb8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2071
-694
lines changed

src/android/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ add_library(noods-core SHARED
2727
../ipc.cpp
2828
../memory.cpp
2929
../rtc.cpp
30+
../save_states.cpp
3031
../settings.cpp
3132
../spi.cpp
3233
../spu.cpp

src/android/cpp/interface.cpp

+22-4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ int keyBinds[12] = {};
3939
std::string ndsPath = "", gbaPath = "";
4040
int ndsRomFd = -1, gbaRomFd = -1;
4141
int ndsSaveFd = -1, gbaSaveFd = -1;
42+
int ndsStateFd = -1, gbaStateFd = -1;
4243
Core *core = nullptr;
4344
ScreenLayout layout;
4445
uint32_t framebuffer[256 * 192 * 8];
@@ -143,7 +144,7 @@ extern "C" JNIEXPORT jint JNICALL Java_com_hydra_noods_FileBrowser_startCore(JNI
143144
try
144145
{
145146
if (core) delete core;
146-
core = new Core(ndsPath, gbaPath, "", "", 0, ndsRomFd, gbaRomFd, ndsSaveFd, gbaSaveFd);
147+
core = new Core(ndsPath, gbaPath, 0, ndsRomFd, gbaRomFd, ndsSaveFd, gbaSaveFd, ndsStateFd, gbaStateFd);
147148
return 0;
148149
}
149150
catch (CoreError e)
@@ -180,18 +181,20 @@ extern "C" JNIEXPORT void JNICALL Java_com_hydra_noods_FileBrowser_setGbaPath(JN
180181
env->ReleaseStringUTFChars(value, str);
181182
}
182183

183-
extern "C" JNIEXPORT void JNICALL Java_com_hydra_noods_FileBrowser_setNdsFds(JNIEnv* env, jobject obj, jint romFd, jint saveFd)
184+
extern "C" JNIEXPORT void JNICALL Java_com_hydra_noods_FileBrowser_setNdsFds(JNIEnv* env, jobject obj, jint romFd, jint saveFd, jint stateFd)
184185
{
185186
// Set the NDS ROM file descriptors
186187
ndsRomFd = romFd;
187188
ndsSaveFd = saveFd;
189+
ndsStateFd = stateFd;
188190
}
189191

190-
extern "C" JNIEXPORT void JNICALL Java_com_hydra_noods_FileBrowser_setGbaFds(JNIEnv* env, jobject obj, jint romFd, jint saveFd)
192+
extern "C" JNIEXPORT void JNICALL Java_com_hydra_noods_FileBrowser_setGbaFds(JNIEnv* env, jobject obj, jint romFd, jint saveFd, jint stateFd)
191193
{
192194
// Set the GBA ROM file descriptors
193195
gbaRomFd = romFd;
194196
gbaSaveFd = saveFd;
197+
gbaStateFd = stateFd;
195198
}
196199

197200
extern "C" JNIEXPORT void JNICALL Java_com_hydra_noods_NooActivity_startAudioPlayer(JNIEnv* env, jobject obj)
@@ -529,7 +532,22 @@ extern "C" JNIEXPORT void JNICALL Java_com_hydra_noods_NooActivity_writeSave(JNI
529532
extern "C" JNIEXPORT void JNICALL Java_com_hydra_noods_NooActivity_restartCore(JNIEnv *env, jobject obj)
530533
{
531534
if (core) delete core;
532-
core = new Core(ndsPath, gbaPath, "", "", 0, ndsRomFd, gbaRomFd, ndsSaveFd, gbaSaveFd);
535+
core = new Core(ndsPath, gbaPath, 0, ndsRomFd, gbaRomFd, ndsSaveFd, gbaSaveFd, ndsStateFd, gbaStateFd);
536+
}
537+
538+
extern "C" JNIEXPORT jint JNICALL Java_com_hydra_noods_NooActivity_checkState(JNIEnv *env, jobject obj)
539+
{
540+
return core->saveStates.checkState();
541+
}
542+
543+
extern "C" JNIEXPORT jboolean JNICALL Java_com_hydra_noods_NooActivity_saveState(JNIEnv *env, jobject obj)
544+
{
545+
return core->saveStates.saveState();
546+
}
547+
548+
extern "C" JNIEXPORT jboolean JNICALL Java_com_hydra_noods_NooActivity_loadState(JNIEnv *env, jobject obj)
549+
{
550+
return core->saveStates.loadState();
533551
}
534552

535553
extern "C" JNIEXPORT void JNICALL Java_com_hydra_noods_NooActivity_pressScreen(JNIEnv *env, jobject obj, jint x, jint y)

src/android/java/com/hydra/noods/FileBrowser.java

+29-8
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ private void update()
422422
if (ext.equals(".nds"))
423423
{
424424
if (scoped)
425-
setNdsFds(getRomFd(file.getUri()), getSaveFd(file));
425+
setNdsFds(getRomFd(file.getUri()), getSaveFd(file), getStateFd(file));
426426
else
427427
setNdsPath(file.getUri().getPath());
428428

@@ -447,7 +447,7 @@ public void onClick(DialogInterface dialog, int id)
447447
public void onClick(DialogInterface dialog, int id)
448448
{
449449
setGbaPath("");
450-
setGbaFds(-1, -1);
450+
setGbaFds(-1, -1, -1);
451451
tryStartCore();
452452
}
453453
});
@@ -458,7 +458,7 @@ public void onClick(DialogInterface dialog, int id)
458458
public void onCancel(DialogInterface dialog)
459459
{
460460
setGbaPath("");
461-
setGbaFds(-1, -1);
461+
setGbaFds(-1, -1, -1);
462462
tryStartCore();
463463
}
464464
});
@@ -470,7 +470,7 @@ public void onCancel(DialogInterface dialog)
470470
else
471471
{
472472
if (scoped)
473-
setGbaFds(getRomFd(file.getUri()), getSaveFd(file));
473+
setGbaFds(getRomFd(file.getUri()), getSaveFd(file), getStateFd(file));
474474
else
475475
setGbaPath(file.getUri().getPath());
476476

@@ -495,7 +495,7 @@ public void onClick(DialogInterface dialog, int id)
495495
public void onClick(DialogInterface dialog, int id)
496496
{
497497
setNdsPath("");
498-
setNdsFds(-1, -1);
498+
setNdsFds(-1, -1, -1);
499499
tryStartCore();
500500
}
501501
});
@@ -506,7 +506,7 @@ public void onClick(DialogInterface dialog, int id)
506506
public void onCancel(DialogInterface dialog)
507507
{
508508
setNdsPath("");
509-
setNdsFds(-1, -1);
509+
setNdsFds(-1, -1, -1);
510510
tryStartCore();
511511
}
512512
});
@@ -642,13 +642,34 @@ private int getSaveFd(DocumentFile rom)
642642
}
643643
}
644644

645+
private int getStateFd(DocumentFile rom)
646+
{
647+
// Make a state file URI based on the ROM file URI
648+
String str = rom.getUri().toString();
649+
Uri uri = Uri.parse(str.substring(0, str.length() - 4) + ".noo");
650+
651+
try
652+
{
653+
// Get a descriptor for the file in scoped mode
654+
return getContentResolver().openFileDescriptor(uri, "rw").detachFd();
655+
}
656+
catch (Exception e)
657+
{
658+
// Create a new state file if one doesn't exist
659+
str = rom.getName().toString();
660+
DocumentFile save = DocumentFile.fromTreeUri(this, pathUris.get(pathUris.size() - 2));
661+
save.createFile("application/sav", str.substring(0, str.length() - 4) + ".noo");
662+
return getStateFd(rom);
663+
}
664+
}
665+
645666
public static native boolean loadSettings(String rootPath);
646667
public static native void getNdsIcon(int fd, Bitmap bitmap);
647668
public static native int startCore();
648669
public static native boolean isNdsLoaded();
649670
public static native boolean isGbaLoaded();
650671
public static native void setNdsPath(String value);
651672
public static native void setGbaPath(String value);
652-
public static native void setNdsFds(int romFd, int saveFd);
653-
public static native void setGbaFds(int romFd, int saveFd);
673+
public static native void setNdsFds(int romFd, int saveFd, int stateFd);
674+
public static native void setGbaFds(int romFd, int saveFd, int stateFd);
654675
}

src/android/java/com/hydra/noods/NooActivity.java

+91-22
Original file line numberDiff line numberDiff line change
@@ -155,27 +155,102 @@ public boolean onOptionsItemSelected(MenuItem item)
155155
}
156156
return true;
157157

158-
case R.id.save_action:
158+
case R.id.restart_action:
159+
// Restart the core
160+
pauseCore();
161+
restartCore();
162+
resumeCore();
163+
return true;
164+
165+
case R.id.save_state_action:
166+
// Create a confirmation dialog
167+
AlertDialog.Builder builder = new AlertDialog.Builder(NooActivity.this);
168+
builder.setTitle("Save State");
169+
builder.setNegativeButton("Cancel", null);
170+
builder.setPositiveButton("OK", new DialogInterface.OnClickListener()
171+
{
172+
@Override
173+
public void onClick(DialogInterface dialog, int id)
174+
{
175+
// Save the state if confirmed
176+
pauseCore();
177+
saveState();
178+
resumeCore();
179+
}
180+
});
181+
182+
// Use the message with extra information if a state file doesn't exist yet
183+
if (checkState() == 1) // File fail
184+
builder.setMessage("Saving and loading states is dangerous and can lead to data " +
185+
"loss. States are also not guaranteed to be compatible across emulator " +
186+
"versions. Please rely on in-game saving to keep your progress, and back up " +
187+
".sav files before using this feature. Do you want to save the current state?");
188+
else
189+
builder.setMessage("Do you want to overwrite the saved " +
190+
"state with the current state? This can't be undone!");
191+
builder.create().show();
192+
return true;
193+
194+
case R.id.load_state_action:
195+
// Create a confirmation dialog, or an error if something went wrong
196+
AlertDialog.Builder builder2 = new AlertDialog.Builder(NooActivity.this);
197+
builder2.setTitle("Load State");
198+
switch (checkState())
199+
{
200+
case 0: // Success
201+
builder2.setMessage("Do you want to load the saved state " +
202+
"and lose the current state? This can't be undone!");
203+
builder2.setNegativeButton("Cancel", null);
204+
builder2.setPositiveButton("OK", new DialogInterface.OnClickListener()
205+
{
206+
@Override
207+
public void onClick(DialogInterface dialog, int id)
208+
{
209+
// Load the state if confirmed
210+
pauseCore();
211+
loadState();
212+
resumeCore();
213+
}
214+
});
215+
break;
216+
217+
case 1: // File fail
218+
builder2.setMessage("The state file doesn't exist or couldn't be opened.");
219+
builder2.setNegativeButton("OK", null);
220+
break;
221+
222+
case 2: // Format fail
223+
builder2.setMessage("The state file doesn't have a valid format.");
224+
builder2.setNegativeButton("OK", null);
225+
break;
226+
227+
case 3: // Version fail
228+
builder2.setMessage("The state file isn't compatible with this version of NooDS.");
229+
builder2.setPositiveButton("OK", null);
230+
break;
231+
}
232+
builder2.create().show();
233+
return true;
234+
235+
case R.id.save_type_action:
159236
final boolean gba = isGbaMode();
160237
final String[] names = getResources().getStringArray(gba ? R.array.save_entries_gba : R.array.save_entries_nds);
161238
final int[] values = getResources().getIntArray(gba ? R.array.save_values_gba : R.array.save_values_nds);
162239

163240
// Create the save type dialog
164-
AlertDialog.Builder builder = new AlertDialog.Builder(this);
165-
builder.setTitle("Change Save Type");
166-
167-
builder.setItems(names, new DialogInterface.OnClickListener()
241+
AlertDialog.Builder builder3 = new AlertDialog.Builder(this);
242+
builder3.setTitle("Change Save Type");
243+
builder3.setItems(names, new DialogInterface.OnClickListener()
168244
{
169245
@Override
170246
public void onClick(DialogInterface dialog, final int which)
171247
{
172248
// Confirm the change because accidentally resizing a working save file could be bad!
173-
AlertDialog.Builder builder2 = new AlertDialog.Builder(NooActivity.this);
174-
builder2.setTitle("Changing Save Type");
175-
builder2.setMessage("Are you sure? This may result in data loss!");
176-
builder2.setNegativeButton("Cancel", null);
177-
178-
builder2.setPositiveButton("OK", new DialogInterface.OnClickListener()
249+
AlertDialog.Builder builder4 = new AlertDialog.Builder(NooActivity.this);
250+
builder4.setTitle("Changing Save Type");
251+
builder4.setMessage("Are you sure? This may result in data loss!");
252+
builder4.setNegativeButton("Cancel", null);
253+
builder4.setPositiveButton("OK", new DialogInterface.OnClickListener()
179254
{
180255
@Override
181256
public void onClick(DialogInterface dialog, int id)
@@ -190,19 +265,10 @@ public void onClick(DialogInterface dialog, int id)
190265
resumeCore();
191266
}
192267
});
193-
194-
builder2.create().show();
268+
builder4.create().show();
195269
}
196270
});
197-
198-
builder.create().show();
199-
return true;
200-
201-
case R.id.restart_action:
202-
// Restart the core
203-
pauseCore();
204-
restartCore();
205-
resumeCore();
271+
builder3.create().show();
206272
return true;
207273

208274
case R.id.bindings_action:
@@ -472,6 +538,9 @@ else if (showingFps)
472538
public static native void runFrame();
473539
public static native void writeSave();
474540
public static native void restartCore();
541+
public static native int checkState();
542+
public static native boolean saveState();
543+
public static native boolean loadState();
475544
public static native void pressScreen(int x, int y);
476545
public static native void releaseScreen();
477546
public static native void resizeGbaSave(int size);

src/android/res/menu/noo_menu.xml

+7-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88
<item android:id="@+id/restart_action"
99
android:title="Restart" />
1010

11-
<item android:id="@+id/save_action"
11+
<item android:id="@+id/save_state_action"
12+
android:title="Save State" />
13+
14+
<item android:id="@+id/load_state_action"
15+
android:title="Load State" />
16+
17+
<item android:id="@+id/save_type_action"
1218
android:title="Change Save Type" />
1319

1420
<item android:id="@+id/bindings_action"

src/bios.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,18 @@ int (Bios::*Bios::swiTableGba[])(uint32_t**) =
6464
&Bios::swiUnknown // 0x20
6565
};
6666

67+
void Bios::saveState(FILE *file)
68+
{
69+
// Write state data to the file
70+
fwrite(&waitFlags, sizeof(waitFlags), 1, file);
71+
}
72+
73+
void Bios::loadState(FILE *file)
74+
{
75+
// Read state data from the file
76+
fread(&waitFlags, sizeof(waitFlags), 1, file);
77+
}
78+
6779
int Bios::execute(uint8_t vector, uint32_t **registers)
6880
{
6981
// Execute the HLE version of the given exception vector

src/bios.h

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#define BIOS_H
2222

2323
#include <cstdint>
24+
#include <cstdio>
2425

2526
class Core;
2627

@@ -33,6 +34,8 @@ class Bios
3334

3435
Bios(Core *core, bool arm7, int (Bios::**swiTable)(uint32_t**)):
3536
core(core), arm7(arm7), swiTable(swiTable) {}
37+
void saveState(FILE *file);
38+
void loadState(FILE *file);
3639

3740
int execute(uint8_t vector, uint32_t **registers);
3841
void checkWaitFlags();

0 commit comments

Comments
 (0)