Files
vba10/EmulatorFileHandler.cpp

3053 lines
82 KiB
C++

#include "EmulatorFileHandler.h"
#include "EmulatorSettings.h"
#include "Emulator.h"
#include <string>
#include <sstream>
#include <System.h>
#include <NLS.h>
#include <Port.h>
#include <GBA.h>
#include <GB.h>
#include <Globals.h>
#include <RTC.h>
#include <robuffer.h>
#include "EmulatorSettings.h"
#include <fstream>
#include "Util.h"
#include <Gb_Apu.h>
#include <Sound.h>
#include <gbSound.h>
#include <gbMemory.h>
#include <gbCheats.h>
#include <Cheats.h>
#include "stringhelper.h"
using namespace Platform;
using namespace std;
extern bool cheatsEnabled;
extern int gbaSaveType;
extern int romSize;
extern int emulating;
// Extern functions used for read and save state
extern bool soundInit();
extern void reset_apu();
extern void write_SGCNT0_H( int data );
extern void apply_muting();
extern void CPUUpdateWindow0();
extern void CPUUpdateWindow1();
extern void sramWrite(u32, u8);
extern void CPUReadHelper(void);
extern int gbBattery;
extern int gbRomType;
extern u8 *gbRam;
extern int gbRamSizeMask;
extern u8 *gbMemoryMap[16];
extern mapperMBC3 gbDataMBC3;
extern mapperTAMA5 gbDataTAMA5;
extern u8 *gbMemory;
extern u8 *gbTAMA5ram;
extern int gbTAMA5ramSize;
extern int gbRamSize;
namespace VBA10
{
public value struct ROMConfig
{
int saveType;
int flashSize;
int rtcEnabled;
int mirroringEnabled;
};
Map<String ^, ROMConfig> ^romConfigs = nullptr;
Map<String^, HIDControllerInput^>^ hidConfigs = ref new Map<String^, HIDControllerInput^>();
int ROMSize = 0;
bool ROMLoaded = false;
StorageFile ^ROMFile = nullptr;
StorageFolder ^ROMFolder = nullptr;
int SavestateSlot = 0;
bool iniParsed = false;
bool gbaROMLoaded = true;
Windows::Foundation::Collections::IVector<CheatData ^> ^ROMCheats = nullptr;
bool ShouldApplyNewCheats = false;
template <typename charT>
int firstIndexOf(std::basic_string<charT> &s, charT c)
{
for(int i = 0; i < s.size(); ++i)
{
if(s[i] == c) return i;
}
return -1;
}
template <typename charT>
bool stringWhitespace(const basic_string<charT> &s)
{
for (int i = 0; i < s.size(); i++)
{
if(!isspace(s.at(i)))
{
return false;
}
}
return true;
}
Platform::Array<unsigned char> ^GetSnapshotBuffer(unsigned char *backbuffer, size_t pitch, int imageWidth, int imageHeight)
{
Platform::Array<unsigned char> ^buffer = ref new Platform::Array<unsigned char>(imageWidth * imageHeight * 4);
/*Microsoft::WRL::ComPtr<IBufferByteAccess> byteAccess;
reinterpret_cast<IUnknown*>(buffer)->QueryInterface(IID_PPV_ARGS(&byteAccess));
byte *buf;
byteAccess->Buffer(&buf);
uint16 *targetBuffer = (uint16 *) buf;*/
int dstPitch = imageWidth * 4;
for (int i = 0; i < imageHeight; i++)
{
for (int j = 0; j < imageWidth * 4; j += 4)
{
// red
buffer[dstPitch * i + j] = *(backbuffer + pitch * i + j + 2);
// green
buffer[dstPitch * i + j + 1] = *(backbuffer + pitch * i + j + 1);
// blue
buffer[dstPitch * i + j + 2] = *(backbuffer + pitch * i + j + 0);
// alpha
buffer[dstPitch * i + j + 3] = 0xff;
//*(targetBuffer + imageWidth * i + j) = *(backbuffer + (pitch / 2) * i + j) ;
}
}
return buffer;
}
task<void> ParseVBAIniAsync()
{
if(iniParsed)
{
return create_task([](){}); //this will launch on another thread!!!
}
auto reader = make_shared<DataReader ^>();
StorageFolder ^installDir = Windows::ApplicationModel::Package::Current->InstalledLocation;
return create_task(installDir->GetFolderAsync("Assets/")).then([](task<StorageFolder ^> t)
{
StorageFolder ^assetsFolder = t.get();
return assetsFolder->GetFileAsync("vba-over.ini");
}).then([=](task<StorageFile ^> fileTask)
{
StorageFile ^file = fileTask.get();
return file->OpenReadAsync();
}).then([reader](IRandomAccessStream ^stream)
{
*reader = ref new DataReader(stream);
return create_task((*reader)->LoadAsync((unsigned int) stream->Size));
}).then([reader](unsigned int bytesRead)
{
String ^text = nullptr;
Map<Platform::String ^, ROMConfig> ^map = ref new Map<Platform::String ^, ROMConfig>();
text = (*reader)->ReadString(bytesRead);
if(text == nullptr)
return;
string str(text->Begin(), text->End());
vector<string> lines = split(str, '\n');
for(vector<string>::const_iterator i = lines.begin(); i != lines.end(); ++i)
{
string line = *i;
int startBraces = firstIndexOf(line, '[');
if(startBraces == -1)
{
continue;
}
int endBraces = firstIndexOf(line, ']');
if(endBraces == -1)
{
continue;
}
ROMConfig config;
config.flashSize = -1;
config.mirroringEnabled = -1;
config.rtcEnabled = -1;
config.saveType = -1;
string romCode = line.substr(startBraces + 1, endBraces - startBraces - 1);
for (++i; i != lines.end() && !stringWhitespace(line = *i); ++i)
{
int equalsIndex = firstIndexOf(line, '=');
if(equalsIndex == -1)
{
continue;
}
if(equalsIndex + 1 >= line.size())
{
continue;
}
string configName = line.substr(0, equalsIndex);
string configValue = line.substr(equalsIndex + 1);
stringstream ss;
ss << configValue;
int value;
ss >> value;
const char *configNameStr = configName.c_str();
if(strcmp(configNameStr, "rtcEnabled") == 0)
{
config.rtcEnabled = value;
}else if(strcmp(configNameStr, "flashSize") == 0)
{
config.flashSize = value;
}else if(strcmp(configNameStr, "saveType") == 0)
{
config.saveType = value;
}else if(strcmp(configNameStr, "mirroringEnabled") == 0)
{
config.mirroringEnabled = value;
}
}
wstring wRomCode(romCode.begin(), romCode.end());
map->Insert(ref new String(wRomCode.c_str()), config);
if(i == lines.end())
{
break;
}
}
romConfigs = map;
}).then([](task<void> t)
{
try
{
iniParsed = true;
t.get();
}catch(Platform::Exception ^e)
{
romConfigs = nullptr;
#if _DEBUG
String ^message = e->Message;
wstring wstr(message->Begin(), message->End());
OutputDebugStringW((L"ParseVBAIniAsync: " + wstr).c_str());
#endif
}
});
}
bool IsROMLoaded(void)
{
return (ROMFile && ROMFolder && ROMLoaded);
}
bool IsGBAROMLoaded(void)
{
return (ROMFile && ROMFolder && ROMLoaded && gbaROMLoaded);
}
task<void> LoadROMAsync(StorageFile ^file, StorageFolder ^folder)
{
bool gba = false;
String^ filename = file->Name;
if(filename->Length() >= 3)
{
const wchar_t *end = filename->End();
const wchar_t *extStart = end - 3;
if(extStart >= filename->Begin())
{
gba = ((extStart[0] == 'g' || extStart[0] == 'G') &&
(extStart[1] == 'b' || extStart[1] == 'B') &&
(extStart[2] == 'a' || extStart[2] == 'A'));
}
}
if(gba)
{
return LoadGBAROMAsync(file, folder);
}else
{
return LoadGBROMAsync(file, folder);
}
}
task<void> LoadGBROMAsync(StorageFile ^file, StorageFolder ^folder)
{
EmulatorGame *emulator = EmulatorGame::GetInstance();
gbaLoopForceQuit = true;
gbexecute = false; //added by Duc Le
return create_task([emulator]()
{
return emulator->StopROMAsync();
}).then([file]()
{
return GetBytesFromFileAsync(file);
}).then([file, folder](ROMData data)
{
int size = data.Length;
if(rom != NULL) {
CPUCleanUp();
}
systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;
extern u8 *gbRom;
extern int gbRomSize;
extern bool gbBatteryError;
extern int gbHardware;
extern int gbBorderOn;
extern int gbCgbMode;
for(int i = 0; i < 24;)
{
systemGbPalette[i++] = (0x1f) | (0x1f << 5) | (0x1f << 10);
systemGbPalette[i++] = (0x15) | (0x15 << 5) | (0x15 << 10);
systemGbPalette[i++] = (0x0c) | (0x0c << 5) | (0x0c << 10);
systemGbPalette[i++] = 0;
}
gbRom = (u8 *) malloc(size);
if(gbRom == NULL) {
systemMessage(MSG_OUT_OF_MEMORY, N_("Failed to allocate memory for %s"),
"ROM");
CPUCleanUp();
}
memcpy_s(gbRom, size, data.ROM, size);
if(data.ROM)
{
delete [] data.ROM;
}
gbRomSize = size;
gbBatteryError = false;
if(bios != NULL) {
free(bios);
bios = NULL;
}
bios = (u8 *)calloc(1,0x100);
gbUpdateSizes();
gbGetHardwareType();
gbReset();
EmulatorGame::emulator = GBSystem;
gbBorderOn = false;
soundInit();
gbSoundReset();
ROMSize = size;
ROMFile = file;
ROMFolder = folder;
gbaROMLoaded = false;
return;
}).then([]()
{
return LoadSRAMAsync();
}).then([emulator]()
{
ROMLoaded = true;
emulator->Unpause();
emulator->Start();
emulating = true;
}).then([]()
{
return LoadCheats();
}).then([] (task<Windows::Foundation::Collections::IVector<CheatData ^> ^> tcheats)
{
try
{
ROMCheats = tcheats.get();
ApplyCheats(ROMCheats);
}
catch (COMException ^ex)
{
#if _DEBUG
Platform::String ^str = ex->Message;
wstring wstr(str->Begin(), str->End());
OutputDebugStringW(wstr.c_str());
//EngineLog(LOG_LEVEL::Error, wstr);
#endif
}
});
}
task<void> LoadGBAROMAsync(StorageFile ^file, StorageFolder ^folder)//(unsigned char *rom, size_t length)
{
EmulatorGame *emulator = EmulatorGame::GetInstance();
gbaLoopForceQuit = false;
return ParseVBAIniAsync().then([emulator]()
{
return emulator->StopROMAsync();
}, task_continuation_context::use_current()).then([file]()
{
return GetBytesFromFileAsync(file);
})
.then([file, folder](ROMData data)
{
int size = 0x2000000;
if(rom != NULL) {
CPUCleanUp();
}
systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;
rom = (u8 *)malloc(0x2000000);
if(rom == NULL) {
systemMessage(MSG_OUT_OF_MEMORY, N_("Failed to allocate memory for %s"),
"ROM");
}
workRAM = (u8 *)calloc(1, 0x40000);
if(workRAM == NULL) {
systemMessage(MSG_OUT_OF_MEMORY, N_("Failed to allocate memory for %s"),
"WRAM");
}
u8 *whereToLoad = rom;
if(cpuIsMultiBoot)
whereToLoad = workRAM;
int read = size = data.Length < size ? data.Length : size;
memcpy_s(whereToLoad, read, data.ROM, read);
u16 *temp = (u16 *)(rom+((size+1)&~1));
int i;
for(i = (size+1)&~1; i < 0x2000000; i+=2) {
WRITE16LE(temp, (i >> 1) & 0xFFFF);
temp++;
}
bios = (u8 *)calloc(1,0x4000);
if(bios == NULL) {
systemMessage(MSG_OUT_OF_MEMORY, N_("Failed to allocate memory for %s"),
"BIOS");
CPUCleanUp();
}
internalRAM = (u8 *)calloc(1,0x8000);
if(internalRAM == NULL) {
systemMessage(MSG_OUT_OF_MEMORY, N_("Failed to allocate memory for %s"),
"IRAM");
CPUCleanUp();
}
paletteRAM = (u8 *)calloc(1,0x400);
if(paletteRAM == NULL) {
systemMessage(MSG_OUT_OF_MEMORY, N_("Failed to allocate memory for %s"),
"PRAM");
CPUCleanUp();
}
vram = (u8 *)calloc(1, 0x20000);
if(vram == NULL) {
systemMessage(MSG_OUT_OF_MEMORY, N_("Failed to allocate memory for %s"),
"VRAM");
CPUCleanUp();
}
oam = (u8 *)calloc(1, 0x400);
if(oam == NULL) {
systemMessage(MSG_OUT_OF_MEMORY, N_("Failed to allocate memory for %s"),
"OAM");
CPUCleanUp();
}
/*pix = (u8 *)calloc(1, 4 * 241 * 162);
if(pix == NULL) {
systemMessage(MSG_OUT_OF_MEMORY, N_("Failed to allocate memory for %s"),
"PIX");
CPUCleanUp();
}
extern size_t gbaPitch;
gbaPitch = 964;*/
ioMem = (u8 *)calloc(1, 0x400);
if(ioMem == NULL) {
systemMessage(MSG_OUT_OF_MEMORY, N_("Failed to allocate memory for %s"),
"IO");
CPUCleanUp();
}
memset(flashSaveMemory, 0xff, sizeof(flashSaveMemory));
memset(eepromData, 255, sizeof(eepromData));
extern void CPUUpdateRenderBuffers(bool);
CPUUpdateRenderBuffers(true);
ROMSize = read;
romSize = read;
if(data.ROM)
{
delete [] data.ROM;
}
//try to auto detect save type
utilGBAFindSave(romSize);
// read from vba-over.ini
Map<String ^, ROMConfig> ^configs = romConfigs;
if(configs != nullptr)
{
char buffer[5];
strncpy_s(buffer, (const char *) &rom[0xac], 4);
buffer[4] = 0;
string codeA = string(buffer);
String ^code = ref new String(wstring(codeA.begin(), codeA.end()).c_str());
#if _DEBUG
stringstream ss;
ss << "Game code: ";
ss << codeA << "\n";
OutputDebugStringA(ss.str().c_str());
#endif
if(configs->HasKey(code))
{
ROMConfig config = configs->Lookup(code);
if(config.flashSize != -1)
{
flashSetSize(config.flashSize);
}
if(config.mirroringEnabled != -1)
{
doMirroring(config.mirroringEnabled != 0);
}
if(config.rtcEnabled != -1)
{
rtcEnable(config.rtcEnabled != 0);
}
if(config.saveType != -1)
{
cpuSaveType = config.saveType;
}
}
}
skipBios = true;
EmulatorGame::emulator = GBASystem;
soundInit();
CPUInit(nullptr, false);
CPUReset();
ROMFile = file;
ROMFolder = folder;
gbaROMLoaded = true;
return;
}).then([]()
{
return LoadSRAMAsync();
}).then([emulator]()
{
ROMLoaded = true;
emulator->Unpause();
emulator->Start();
emulating = true;
}).then([]()
{
return LoadCheats();
}).then([](task<Windows::Foundation::Collections::IVector<CheatData ^>^ > tcheats)
{
try
{
ROMCheats = tcheats.get();
ApplyCheats(ROMCheats);
}
catch(COMException ^ex)
{
#if _DEBUG
Platform::String ^str = ex->Message;
wstring wstr(str->Begin(), str->End());
OutputDebugStringW(wstr.c_str());
//EngineLog(LOG_LEVEL::Error, wstr);
#endif
}
});
}
task<void> SaveSRAMAsync ()
{
if(gbaROMLoaded)
{
return SaveGBASRAMAsync();
}else
{
return SaveGBSRAMAsync();
}
}
task<void> SaveGBSRAMAsync()
{
if(!ROMFile || !ROMFolder)
return task<void>([](){});
Platform::String ^name = ROMFile->Name;
const wchar_t *end = name->End();
while(*end != '.') end--;
size_t diff = name->End() - end;
Platform::String ^nameWithoutExt = ref new Platform::String(name->Begin(), name->Length() - diff);
Platform::String ^sramName = nameWithoutExt->Concat(nameWithoutExt, ".sav");
return create_task([sramName]()
{
// try to open the file
return ROMFolder->CreateFileAsync(sramName, CreationCollisionOption::OpenIfExists);
}).then([](StorageFile ^file)
{
if(gbBattery)
{
switch(gbRomType)
{
case 0xff:
case 0x03:
// MBC1
if(gbRam)
{
SaveBytesToFileAsync(file, gbRam, gbRamSizeMask + 1).wait();
}
break;
case 0x06:
// MBC2
if(gbRam)
{
SaveBytesToFileAsync(file, gbMemoryMap[0x0a], 512).wait();
}
break;
case 0x0d:
// MMM01
if(gbRam)
{
SaveBytesToFileAsync(file, gbRam, gbRamSizeMask + 1).wait();
}
break;
case 0x0f:
case 0x10:
// MBC3
if(gbRam)
{
int tmpSize = gbRamSizeMask + 1 + 10 * sizeof(int) + sizeof(time_t);
u8 *tmp = new u8[tmpSize];
memcpy_s(tmp, gbRamSizeMask + 1, gbRam, gbRamSizeMask + 1);
memcpy_s(tmp + gbRamSizeMask + 1, 10 * sizeof(int) + sizeof(time_t), &gbDataMBC3.mapperSeconds, 10 * sizeof(int) + sizeof(time_t));
SaveBytesToFileAsync(file, tmp, tmpSize).wait();
delete [] tmp;
}else
{
SaveBytesToFileAsync(file, (u8 *) &gbDataMBC3.mapperSeconds, 10 * sizeof(int) + sizeof(time_t)).wait();
}
break;
case 0x13:
case 0xfc:
// MBC3 - 2
if(gbRam)
{
SaveBytesToFileAsync(file, gbRam, gbRamSizeMask + 1).wait();
}
break;
case 0x1b:
case 0x1e:
// MBC5
if(gbRam)
{
SaveBytesToFileAsync(file, gbRam, gbRamSizeMask + 1).wait();
}
break;
case 0x22:
// MBC7
if(gbRam)
{
SaveBytesToFileAsync(file, &gbMemory[0xa000], 256).wait();
}
break;
case 0xfd:
if(gbRam)
{
int tmpSize = gbRamSizeMask + 1 + gbTAMA5ramSize + 14 * sizeof(int) + sizeof(time_t);
u8 *tmp = new u8[tmpSize];
memcpy_s(tmp, gbRamSizeMask + 1, gbRam, gbRamSizeMask + 1);
memcpy_s(tmp + gbRamSizeMask + 1, gbTAMA5ramSize, gbTAMA5ram, gbTAMA5ramSize);
memcpy_s(tmp + gbRamSizeMask + 1 + gbTAMA5ramSize, 14 * sizeof(int) + sizeof(time_t), &gbDataTAMA5.mapperSeconds, 14 * sizeof(int) + sizeof(time_t));
SaveBytesToFileAsync(file, tmp, tmpSize).wait();
delete [] tmp;
}else
{
int tmpSize = gbTAMA5ramSize + 14 * sizeof(int) + sizeof(time_t);
u8 *tmp = new u8[tmpSize];
memcpy_s(tmp, gbTAMA5ramSize, gbTAMA5ram, gbTAMA5ramSize);
memcpy_s(tmp + gbTAMA5ramSize, 14 * sizeof(int) + sizeof(time_t), &gbDataTAMA5.mapperSeconds, 14 * sizeof(int) + sizeof(time_t));
SaveBytesToFileAsync(file, tmp, tmpSize).wait();
delete [] tmp;
}
break;
}
}
}).then([sramName](task<void> t)
{
try
{
t.get();
}catch(Platform::COMException ^ex)
{
#if _DEBUG
Platform::String ^error = ex->Message;
wstring wname(sramName->Begin(), sramName->End());
wstring werror(error->Begin(), error->End());
//EngineLog(LOG_LEVEL::Error, wname + L": " + werror);
OutputDebugStringW((wname + L": " + werror).c_str());
#endif
}
});
}
task<void> SaveGBASRAMAsync()
{
if(!ROMFile || !ROMFolder)
return task<void>([](){});
Platform::String ^name = ROMFile->Name;
Platform::String ^nameWithoutExt = ref new Platform::String(name->Begin(), name->Length() - 4);
Platform::String ^sramName = nameWithoutExt->Concat(nameWithoutExt, ".sav");
return create_task([sramName]()
{
// try to open the file
return ROMFolder->CreateFileAsync(sramName, CreationCollisionOption::OpenIfExists);
}).then([](StorageFile ^file)
{
if(gbaSaveType == 0) {
if(eepromInUse)
gbaSaveType = 3;
else switch(saveType) {
case 1:
gbaSaveType = 1;
break;
case 2:
gbaSaveType = 2;
break;
}
}
if((gbaSaveType) && (gbaSaveType!=5))
{
// only save if Flash/Sram in use or EEprom in use
if(gbaSaveType != 3)
{
if(gbaSaveType == 2)
{
SaveBytesToFileAsync(file, flashSaveMemory, flashSize).wait();
}
else
{
SaveBytesToFileAsync(file, flashSaveMemory, 0x10000).wait();
}
}
else
{
SaveBytesToFileAsync(file, eepromData, eepromSize).wait();
}
}
}).then([sramName](task<void> t)
{
try
{
t.get();
}catch(Platform::COMException ^ex)
{
#if _DEBUG
Platform::String ^error = ex->Message;
wstring wname(sramName->Begin(), sramName->End());
wstring werror(error->Begin(), error->End());
//EngineLog(LOG_LEVEL::Error, wname + L": " + werror);
OutputDebugStringW((wname + L": " + werror).c_str());
#endif
}
});
}
task<void> SaveSRAMCopyAsync(void)
{
if(gbaROMLoaded)
{
return SaveGBASRAMCopyAsync();
}else
{
return SaveGBSRAMCopyAsync();
}
}
task<void> SaveGBSRAMCopyAsync(void)
{
if(!ROMFile || !ROMFolder)
return task<void>([](){});
Platform::String ^name = ROMFile->Name;
const wchar_t *end = name->End();
while(*end != '.') end--;
size_t diff = name->End() - end;
Platform::String ^nameWithoutExt = ref new Platform::String(name->Begin(), name->Length() - diff);
Platform::String ^sramName = nameWithoutExt->Concat(nameWithoutExt, ".sav");
EmulatorGame *emulator = EmulatorGame::GetInstance();
emulator->Pause();
u8 *gbRamCopy = NULL;
int gbRamSizeCopy = gbRamSize;
int gbRamSizeMaskCopy = gbRamSizeMask;
u8 *gbMemoryMapACopy = NULL;
mapperMBC3 gbDataMBC3Copy = gbDataMBC3;
u8 *gbMemoryCopy = NULL;
u8 *gbTAMA5ramCopy = NULL;
mapperTAMA5 gbDataTAMA5Copy = gbDataTAMA5;
int gbTAMA5ramSizeCopy = gbTAMA5ramSize;
int gbBatteryCopy = gbBattery;
int gbRomTypeCopy = gbRomType;
if(gbRam)
{
gbRamCopy = new u8[gbRamSize];
memcpy_s(gbRamCopy, gbRamSize, gbRam, gbRamSize);
}
if(gbRomType == 0x06)
{
gbMemoryMapACopy = new u8[512];
memcpy_s(gbMemoryMapACopy, 512, gbMemoryMap[0x0a], 512);
}
if(gbRomType == 0x22)
{
gbMemoryCopy = new u8[256];
memcpy_s(gbMemoryCopy, 256, &gbMemory[0xa000], 256);
}
if(gbRomType == 0xfd)
{
gbTAMA5ramCopy = new u8[gbTAMA5ramSize];
memcpy_s(gbTAMA5ramCopy, gbTAMA5ramSize, gbTAMA5ram, gbTAMA5ramSize);
}
emulator->Unpause();
auto gbRamPtr = make_shared<u8 *>(gbRamCopy);
auto gbMemoryMapACopyPtr = make_shared<u8 *>(gbMemoryMapACopy);
auto gbMemoryCopyPtr = make_shared<u8 *>(gbMemoryCopy);
auto gbTAMA5ramCopyPtr = make_shared<u8 *>(gbTAMA5ramCopy);
return create_task([sramName]()
{
// try to open the file
return ROMFolder->CreateFileAsync(sramName, CreationCollisionOption::OpenIfExists);
}).then([=](StorageFile ^file)
{
if(gbBatteryCopy)
{
switch(gbRomTypeCopy)
{
case 0xff:
case 0x03:
// MBC1
if(*gbRamPtr)
{
SaveBytesToFileAsync(file, *gbRamPtr, gbRamSizeMaskCopy + 1).wait();
}
break;
case 0x06:
// MBC2
if(*gbRamPtr)
{
SaveBytesToFileAsync(file, *gbMemoryMapACopyPtr, 512).wait();
}
break;
case 0x0d:
// MMM01
if(*gbRamPtr)
{
SaveBytesToFileAsync(file, *gbRamPtr, gbRamSizeMaskCopy + 1).wait();
}
break;
case 0x0f:
case 0x10:
// MBC3
if(gbRamCopy)
{
int tmpSize = gbRamSizeMaskCopy + 1 + 10 * sizeof(int) + sizeof(time_t);
u8 *tmp = new u8[tmpSize];
memcpy_s(tmp, gbRamSizeMaskCopy + 1, *gbRamPtr, gbRamSizeMaskCopy + 1);
memcpy_s(tmp + gbRamSizeMaskCopy + 1, 10 * sizeof(int) + sizeof(time_t), &gbDataMBC3Copy.mapperSeconds, 10 * sizeof(int) + sizeof(time_t));
SaveBytesToFileAsync(file, tmp, tmpSize).wait();
delete [] tmp;
}else
{
SaveBytesToFileAsync(file, (u8 *) &gbDataMBC3Copy.mapperSeconds, 10 * sizeof(int) + sizeof(time_t)).wait();
}
break;
case 0x13:
case 0xfc:
// MBC3 - 2
if(*gbRamPtr)
{
SaveBytesToFileAsync(file, *gbRamPtr, gbRamSizeMaskCopy + 1).wait();
}
break;
case 0x1b:
case 0x1e:
// MBC5
if(*gbRamPtr)
{
SaveBytesToFileAsync(file, *gbRamPtr, gbRamSizeMaskCopy + 1).wait();
}
break;
case 0x22:
// MBC7
if(*gbRamPtr)
{
SaveBytesToFileAsync(file, *gbMemoryCopyPtr, 256).wait();
}
break;
case 0xfd:
if(*gbRamPtr)
{
int tmpSize = gbRamSizeMaskCopy + 1 + gbTAMA5ramSizeCopy + 14 * sizeof(int) + sizeof(time_t);
u8 *tmp = new u8[tmpSize];
memcpy_s(tmp, gbRamSizeMaskCopy + 1, *gbRamPtr, gbRamSizeMaskCopy + 1);
memcpy_s(tmp + gbRamSizeMaskCopy + 1, gbTAMA5ramSizeCopy, *gbTAMA5ramCopyPtr, gbTAMA5ramSizeCopy);
memcpy_s(tmp + gbRamSizeMaskCopy + 1 + gbTAMA5ramSizeCopy, 14 * sizeof(int) + sizeof(time_t), &gbDataTAMA5Copy.mapperSeconds, 14 * sizeof(int) + sizeof(time_t));
SaveBytesToFileAsync(file, tmp, tmpSize).wait();
delete [] tmp;
}else
{
int tmpSize = gbTAMA5ramSizeCopy + 14 * sizeof(int) + sizeof(time_t);
u8 *tmp = new u8[tmpSize];
memcpy_s(tmp, gbTAMA5ramSizeCopy, *gbTAMA5ramCopyPtr, gbTAMA5ramSizeCopy);
memcpy_s(tmp + gbTAMA5ramSizeCopy, 14 * sizeof(int) + sizeof(time_t), &gbDataTAMA5Copy.mapperSeconds, 14 * sizeof(int) + sizeof(time_t));
SaveBytesToFileAsync(file, tmp, tmpSize).wait();
delete [] tmp;
}
break;
}
}
}).then([=](task<void> t)
{
try
{
if(*gbRamPtr)
{
delete [] *gbRamPtr;
*gbRamPtr = nullptr;
}
if(*gbMemoryMapACopyPtr)
{
delete [] *gbMemoryMapACopyPtr;
*gbMemoryMapACopyPtr = nullptr;
}
if(*gbMemoryCopyPtr)
{
delete [] *gbMemoryCopyPtr;
*gbMemoryCopyPtr = nullptr;
}
if(*gbTAMA5ramCopyPtr)
{
delete [] *gbTAMA5ramCopyPtr;
*gbTAMA5ramCopyPtr = nullptr;
}
t.get();
}catch(Platform::COMException ^ex)
{
#if _DEBUG
Platform::String ^error = ex->Message;
wstring wname(sramName->Begin(), sramName->End());
wstring werror(error->Begin(), error->End());
//EngineLog(LOG_LEVEL::Error, wname + L": " + werror);
OutputDebugStringW((wname + L": " + werror).c_str());
#endif
}
});
}
task<void> SaveGBASRAMCopyAsync(void)
{
if(!ROMFile || !ROMFolder)
return task<void>([](){});
Platform::String ^name = ROMFile->Name;
Platform::String ^nameWithoutExt = ref new Platform::String(name->Begin(), name->Length() - 4);
Platform::String ^sramName = nameWithoutExt->Concat(nameWithoutExt, ".sav");
EmulatorGame *emulator = EmulatorGame::GetInstance();
emulator->Pause();
u8 *flashSaveCopy = new u8[131072];
u8 *eepromCopy = new u8[8192];
memcpy_s(flashSaveCopy, 131072, flashSaveMemory, 131072);
memcpy_s(eepromCopy, 8192, eepromData, 8192);
emulator->Unpause();
auto flashCopyPtr = make_shared<u8 *>(flashSaveCopy);
auto eepromCopyPtr = make_shared<u8 *>(eepromCopy);
return create_task([sramName]()
{
// try to open the file
return ROMFolder->CreateFileAsync(sramName, CreationCollisionOption::OpenIfExists);
}).then([flashCopyPtr, eepromCopyPtr](StorageFile ^file)
{
if(gbaSaveType == 0) {
if(eepromInUse)
gbaSaveType = 3;
else switch(saveType) {
case 1:
gbaSaveType = 1;
break;
case 2:
gbaSaveType = 2;
break;
}
}
if((gbaSaveType) && (gbaSaveType!=5))
{
// only save if Flash/Sram in use or EEprom in use
if(gbaSaveType != 3)
{
if(gbaSaveType == 2)
{
SaveBytesToFileAsync(file, *flashCopyPtr, flashSize).wait();
}
else
{
SaveBytesToFileAsync(file, *flashCopyPtr, 0x10000).wait();
}
}
else
{
SaveBytesToFileAsync(file, *eepromCopyPtr, eepromSize).wait();
}
}
delete [] *flashCopyPtr;
*flashCopyPtr = nullptr;
delete [] *eepromCopyPtr;
*eepromCopyPtr = nullptr;
}).then([sramName, flashCopyPtr, eepromCopyPtr](task<void> t)
{
try
{
if(*flashCopyPtr)
{
delete [] *flashCopyPtr;
*flashCopyPtr = nullptr;
}
if(*eepromCopyPtr)
{
delete [] *eepromCopyPtr;
*eepromCopyPtr = nullptr;
}
t.get();
}catch(Platform::COMException ^ex)
{
#if _DEBUG
Platform::String ^error = ex->Message;
wstring wname(sramName->Begin(), sramName->End());
wstring werror(error->Begin(), error->End());
//EngineLog(LOG_LEVEL::Error, wname + L": " + werror);
OutputDebugStringW((wname + L": " + werror).c_str());
#endif
}
});
}
task<void> LoadSRAMAsync ()
{
if(gbaROMLoaded)
{
return LoadGBASRAMAsync();
}else
{
return LoadGBSRAMAsync();
}
}
task<void> LoadGBSRAMAsync ()
{
if(!ROMFile || !ROMFolder)
return task<void>([](){});
Platform::String ^name = ROMFile->Name;
const wchar_t *end = name->End();
while(*end != '.') end--;
size_t diff = name->End() - end;
Platform::String ^nameWithoutExt = ref new Platform::String(name->Begin(), name->Length() - diff);
Platform::String ^sramName = nameWithoutExt->Concat(nameWithoutExt, ".sav");
return create_task([sramName]()
{
return ROMFolder->GetFileAsync(sramName);
}).then([](StorageFile ^file)
{
return GetBytesFromFileAsync(file);
}).then([](ROMData data)
{
if(gbBattery)
{
switch(gbRomType)
{
case 0x03:
// MBC1
if(gbRam)
{
memcpy_s(gbRam, gbRamSizeMask + 1, data.ROM, gbRamSizeMask + 1);
}
break;
case 0x06:
// MBC2
if(gbRam)
{
memcpy_s(gbMemoryMap[0x0a], 512, data.ROM, 512);
}
break;
case 0x0d:
// MMM01
if(gbRam)
{
memcpy_s(gbRam, gbRamSizeMask + 1, data.ROM, gbRamSizeMask + 1);
}
break;
case 0x0f:
case 0x10:
// MBC3
try{
if(gbRam)
{
memcpy_s(gbRam, gbRamSizeMask + 1, data.ROM, gbRamSizeMask + 1);
memcpy_s(&gbDataMBC3.mapperSeconds, sizeof(int) * 10 + sizeof(time_t), data.ROM + gbRamSizeMask + 1, sizeof(int) * 10 + sizeof(time_t));
}else
{
memcpy_s(&gbDataMBC3.mapperSeconds, sizeof(int) * 10 + sizeof(time_t), data.ROM, sizeof(int) * 10 + sizeof(time_t));
}
}catch(...)
{
time(&gbDataMBC3.mapperLastTime);
struct tm *lt;
lt = localtime(&gbDataMBC3.mapperLastTime);
gbDataMBC3.mapperSeconds = lt->tm_sec;
gbDataMBC3.mapperMinutes = lt->tm_min;
gbDataMBC3.mapperHours = lt->tm_hour;
gbDataMBC3.mapperDays = lt->tm_yday & 255;
gbDataMBC3.mapperControl = (gbDataMBC3.mapperControl & 0xfe) |
(lt->tm_yday > 255 ? 1: 0);
}
break;
case 0x13:
case 0xfc:
// MBC3 - 2
if(gbRam)
{
memcpy_s(gbRam, gbRamSizeMask + 1, data.ROM, gbRamSizeMask + 1);
memcpy_s(&gbDataMBC3.mapperSeconds, sizeof(int) * 10 + sizeof(time_t), data.ROM + gbRamSizeMask + 1, sizeof(int) * 10 + sizeof(time_t));
}else
{
memcpy_s(&gbDataMBC3.mapperSeconds, sizeof(int) * 10 + sizeof(time_t), data.ROM, sizeof(int) * 10 + sizeof(time_t));
}
break;
case 0x1b:
case 0x1e:
// MBC5
if(gbRam)
{
memcpy_s(gbRam, gbRamSizeMask + 1, data.ROM, gbRamSizeMask + 1);
}
break;
case 0x22:
// MBC7
if(gbRam)
{
memcpy_s(&gbMemory[0xa000], 256, data.ROM, 256);
}
break;
case 0xfd:
try
{
if(gbRam)
{
memcpy_s(gbRam, gbRamSizeMask + 1, data.ROM, gbRamSizeMask + 1);
memcpy_s(gbTAMA5ram, gbTAMA5ramSize, data.ROM + gbRamSizeMask + 1, gbTAMA5ramSize);
memcpy_s(&gbDataTAMA5.mapperSeconds, sizeof(int)*14 + sizeof(time_t), data.ROM + gbRamSizeMask + 1 + gbTAMA5ramSize, sizeof(int)*14 + sizeof(time_t));
}else
{
memcpy_s(gbTAMA5ram, gbTAMA5ramSize, data.ROM, gbTAMA5ramSize);
memcpy_s(&gbDataTAMA5.mapperSeconds, sizeof(int)*14 + sizeof(time_t), data.ROM + gbTAMA5ramSize, sizeof(int)*14 + sizeof(time_t));
}
}catch(...)
{
u8 gbDaysinMonth [12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
time(&gbDataTAMA5.mapperLastTime);
struct tm *lt;
lt = localtime(&gbDataTAMA5.mapperLastTime);
gbDataTAMA5.mapperSeconds = lt->tm_sec;
gbDataTAMA5.mapperMinutes = lt->tm_min;
gbDataTAMA5.mapperHours = lt->tm_hour;
gbDataTAMA5.mapperDays = 1;
gbDataTAMA5.mapperMonths = 1;
gbDataTAMA5.mapperYears = 1970;
int days = lt->tm_yday+365*3;
while (days)
{
gbDataTAMA5.mapperDays++;
days--;
if (gbDataTAMA5.mapperDays>gbDaysinMonth[gbDataTAMA5.mapperMonths-1])
{
gbDataTAMA5.mapperDays = 1;
gbDataTAMA5.mapperMonths++;
if (gbDataTAMA5.mapperMonths>12)
{
gbDataTAMA5.mapperMonths = 1;
gbDataTAMA5.mapperYears++;
if ((gbDataTAMA5.mapperYears & 3) == 0)
gbDaysinMonth[1] = 29;
else
gbDaysinMonth[1] = 28;
}
}
}
gbDataTAMA5.mapperControl = (gbDataTAMA5.mapperControl & 0xfe) |
(lt->tm_yday > 255 ? 1: 0);
}
break;
}
}
delete [] data.ROM;
}).then([sramName](task<void> t)
{
try
{
t.get();
}
catch(Platform::COMException ^ex)
{
#if _DEBUG
Platform::String ^error = ex->Message;
wstring wname(sramName->Begin(), sramName->End());
wstring werror(error->Begin(), error->End());
//EngineLog(LOG_LEVEL::Info, wname + L": " + werror);
OutputDebugStringW((wname + L": " + werror).c_str());
#endif
}catch(Platform::AccessDeniedException ^ex)
{
#if _DEBUG
Platform::String ^error = ex->Message;
wstring wname(sramName->Begin(), sramName->End());
wstring werror(error->Begin(), error->End());
//EngineLog(LOG_LEVEL::Info, wname + L": " + werror);
OutputDebugStringW((wname + L": " + werror).c_str());
#endif
}catch(Platform::Exception ^ex)
{
#if _DEBUG
Platform::String ^error = ex->Message;
wstring wname(sramName->Begin(), sramName->End());
wstring werror(error->Begin(), error->End());
//EngineLog(LOG_LEVEL::Info, wname + L": " + werror);
OutputDebugStringW((wname + L": " + werror).c_str());
#endif
}
});
}
task<void> LoadGBASRAMAsync ()
{
if(!ROMFile || !ROMFolder)
return task<void>([](){});
Platform::String ^name = ROMFile->Name;
Platform::String ^nameWithoutExt = ref new Platform::String(name->Begin(), name->Length() - 4);
Platform::String ^sramName = nameWithoutExt->Concat(nameWithoutExt, ".sav");
return create_task([sramName]()
{
return ROMFolder->GetFileAsync(sramName);
}).then([](StorageFile ^file)
{
return GetBytesFromFileAsync(file);
}).then([](ROMData data)
{
systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;
if(data.Length == 512 || data.Length == 0x2000)
{
memcpy_s(eepromData, data.Length, data.ROM, data.Length);
}
else
{
if(data.Length == 0x20000)
{
memcpy_s(flashSaveMemory, 0x20000, data.ROM, 0x20000);
flashSetSize(0x20000);
}
else if(data.Length >= 0x10000)
{
memcpy_s(flashSaveMemory, 0x10000, data.ROM, 0x10000);
flashSetSize(0x10000);
}
}
delete [] data.ROM;
}).then([sramName](task<void> t)
{
try
{
t.get();
}
catch(Platform::COMException ^ex)
{
#if _DEBUG
Platform::String ^error = ex->Message;
wstring wname(sramName->Begin(), sramName->End());
wstring werror(error->Begin(), error->End());
//EngineLog(LOG_LEVEL::Info, wname + L": " + werror);
OutputDebugStringW((wname + L": " + werror).c_str());
#endif
}catch(Platform::AccessDeniedException ^ex)
{
#if _DEBUG
Platform::String ^error = ex->Message;
wstring wname(sramName->Begin(), sramName->End());
wstring werror(error->Begin(), error->End());
//EngineLog(LOG_LEVEL::Info, wname + L": " + werror);
OutputDebugStringW((wname + L": " + werror).c_str());
#endif
}catch(Platform::Exception ^ex)
{
#if _DEBUG
Platform::String ^error = ex->Message;
wstring wname(sramName->Begin(), sramName->End());
wstring werror(error->Begin(), error->End());
//EngineLog(LOG_LEVEL::Info, wname + L": " + werror);
OutputDebugStringW((wname + L": " + werror).c_str());
#endif
}
});
}
void ResetSync()
{
if (!ROMFile || !ROMFolder)
return;
EmulatorGame *emulator = EmulatorGame::GetInstance();
emulator->Pause();
gbexecute = false; //TODO: need to uncomment when we update the VBA-M engine
EmulatorGame::emulator.emuReset();
emulator->Unpause();
}
//task<void> ResetAsync(void)
//{
// if(!ROMFile || !ROMFolder)
// return task<void>([](){});
// return LoadROMAsync(ROMFile, ROMFolder);
//}
task<void> SaveStateAsync(void)
{
if(gbaROMLoaded)
{
return SaveGBAStateAsync();
}else
{
return SaveGBStateAsync();
}
}
task<void> SaveGBStateAsync(void)
{
EmulatorGame *emulator = EmulatorGame::GetInstance();
return create_task([emulator]()
{
if(!ROMFile || !ROMFolder)
{
throw ref new Exception(E_FAIL, L"No ROM loaded.");
}
emulator->Pause();
// Generate random file name to store in temp folder
Platform::String ^folderpath = Windows::Storage::ApplicationData::Current->TemporaryFolder->Path;
string folderPathStr(folderpath->Begin(), folderpath->End());
stringstream tmpFileNameStream;
tmpFileNameStream << folderPathStr << "\\";
tmpFileNameStream << rand() << rand() << ".sgm";
string fileNameA = tmpFileNameStream.str();
FILE *file;
auto error = fopen_s(&file, fileNameA.c_str(), "wb");
if(!file)
{
stringstream ss;
ss << "Unable to open tmp file '";
ss << fileNameA;
ss << "' to store savestate (";
ss << error;
ss << ").";
//throw GameException(ss.str().c_str());
string str(ss.str());
throw ref new Exception(E_FAIL, ref new Platform::String(wstring(str.begin(), str.end()).c_str()));
}
fclose(file);
ofstream stream (fileNameA.c_str(), ios::binary);
if(!stream.is_open())
{
#if _DEBUG
stringstream ss;
ss << "Unable to open tmp file '";
ss << fileNameA;
ss << "' to store savestate.";
string str(ss.str());
throw ref new Exception(E_FAIL, ref new Platform::String(wstring(str.begin(), str.end()).c_str()));
#endif
}
extern u8* gbRom;
extern bool useBios;
extern bool inBios;
extern variable_desc gbSaveGameStruct[78];
extern u16 IFF;
extern int gbSgbMode;
extern variable_desc gbSgbSaveStructV3[11];
extern u8 *gbSgbBorder;
extern u8 *gbSgbBorderChar;
extern u8 gbSgbPacket[112];
extern u16 gbSgbSCPPalette[2048];
extern u8 gbSgbATF[360];
extern u8 gbSgbATFList[16200];
extern mapperMBC1 gbDataMBC1;
extern mapperMBC2 gbDataMBC2;
extern mapperMBC3 gbDataMBC3;
extern mapperMBC5 gbDataMBC5;
extern mapperHuC1 gbDataHuC1;
extern mapperHuC3 gbDataHuC3;
extern mapperTAMA5 gbDataTAMA5;
extern u8 *gbTAMA5ram;
extern int gbTAMA5ramSize;
extern mapperMMM01 gbDataMMM01;
extern u16 gbPalette[128];
extern u8 *gbMemory;
extern int gbRamSize;
extern u8 *gbRam;
extern int gbCgbMode;
extern u8 *gbVram;
extern u8 *gbWram;
extern variable_desc gb_state[20];
extern int gbCheatNumber;
extern gbCheat gbCheatList[100];
extern int gbLcdModeDelayed;
extern int gbLcdTicksDelayed;
extern int gbLcdLYIncrementTicksDelayed;
extern u8 gbSpritesTicks[300];
extern bool gbTimerModeChange;
extern bool gbTimerOnChange;
extern int gbHardware;
extern bool gbBlackScreen;
extern u8 oldRegister_WY;
extern int gbWindowLine;
extern int inUseRegister_WY;
extern bool gbScreenOn;
int marker = 0x12345678;
int version = 12;
stream.write(reinterpret_cast<const char *>(&version), sizeof(int));
stream.write(reinterpret_cast<const char *>(&gbRom[0x134]), 15);
int ub = useBios;
int ib = inBios;
stream.write(reinterpret_cast<const char *>(&ub), sizeof(int));
stream.write(reinterpret_cast<const char *>(&ib), sizeof(int));
int i = 0;
for (; i < ARRAYSIZE(gbSaveGameStruct); i++)
{
if(gbSaveGameStruct[i].size > 0)
{
stream.write(reinterpret_cast<const char *>(gbSaveGameStruct[i].address), gbSaveGameStruct[i].size);
}
}
stream.write(reinterpret_cast<const char *>(&IFF), sizeof(u16));
if(gbSgbMode)
{
i = 0;
for (; i < ARRAYSIZE(gbSgbSaveStructV3); i++)
{
if(gbSgbSaveStructV3[i].size > 0)
{
stream.write(reinterpret_cast<const char *>(gbSgbSaveStructV3[i].address), gbSgbSaveStructV3[i].size);
}
}
stream.write(reinterpret_cast<const char *>(gbSgbBorder), 2048);
stream.write(reinterpret_cast<const char *>(gbSgbBorderChar), 32*256);
stream.write(reinterpret_cast<const char *>(gbSgbPacket), 16 * 7);
stream.write(reinterpret_cast<const char *>(gbSgbSCPPalette), 4 * 512 * sizeof(u16));
stream.write(reinterpret_cast<const char *>(gbSgbATF), 20 * 18);
stream.write(reinterpret_cast<const char *>(gbSgbATFList), 45 * 20 * 18);
}
stream.write(reinterpret_cast<const char *>(&gbDataMBC1), sizeof(gbDataMBC1));
stream.write(reinterpret_cast<const char *>(&gbDataMBC2), sizeof(gbDataMBC2));
stream.write(reinterpret_cast<const char *>(&gbDataMBC3), sizeof(gbDataMBC3));
stream.write(reinterpret_cast<const char *>(&gbDataMBC5), sizeof(gbDataMBC5));
stream.write(reinterpret_cast<const char *>(&gbDataHuC1), sizeof(gbDataHuC1));
stream.write(reinterpret_cast<const char *>(&gbDataHuC3), sizeof(gbDataHuC3));
stream.write(reinterpret_cast<const char *>(&gbDataTAMA5), sizeof(gbDataTAMA5));
if(gbTAMA5ram != NULL)
{
stream.write(reinterpret_cast<const char *>(gbTAMA5ram), gbTAMA5ramSize);
}
stream.write(reinterpret_cast<const char *>(&gbDataMMM01), sizeof(gbDataMMM01));
stream.write(reinterpret_cast<const char *>(gbPalette), 128 * sizeof(u16));
stream.write(reinterpret_cast<const char *>(&gbMemory[0x8000]), 0x8000);
if(gbRamSize && gbRam)
{
stream.write(reinterpret_cast<const char *>(&gbRamSize), sizeof(int));
stream.write(reinterpret_cast<const char *>(gbRam), gbRamSize);
}
if(gbCgbMode)
{
stream.write(reinterpret_cast<const char *>(gbVram), 0x4000);
stream.write(reinterpret_cast<const char *>(gbWram), 0x8000);
}
// Sound
gbSoundSaveGame2();
i = 0;
for (; i < ARRAYSIZE(gb_state); i++)
{
if(gb_state[i].size > 0)
{
stream.write(reinterpret_cast<const char *>(gb_state[i].address), gb_state[i].size);
}
}
// Cheats
stream.write(reinterpret_cast<const char *>(&gbCheatNumber), sizeof(int));
if(gbCheatNumber > 0)
{
stream.write(reinterpret_cast<const char *>(&gbCheatList[0]), sizeof(gbCheat)*gbCheatNumber);
}
int spriteTicks = gbSpritesTicks[299];
int timerModeChange = gbTimerModeChange;
int timerOnChange = gbTimerOnChange;
int blackScreen = gbBlackScreen;
int oldRegister = oldRegister_WY;
int screenOn = gbScreenOn;
stream.write(reinterpret_cast<const char *>(&gbLcdModeDelayed), sizeof(int));
stream.write(reinterpret_cast<const char *>(&gbLcdTicksDelayed), sizeof(int));
stream.write(reinterpret_cast<const char *>(&gbLcdLYIncrementTicksDelayed), sizeof(int));
stream.write(reinterpret_cast<const char *>(&spriteTicks), sizeof(int));
stream.write(reinterpret_cast<const char *>(&timerModeChange), sizeof(int));
stream.write(reinterpret_cast<const char *>(&timerOnChange), sizeof(int));
stream.write(reinterpret_cast<const char *>(&gbHardware), sizeof(int));
stream.write(reinterpret_cast<const char *>(&blackScreen), sizeof(int));
stream.write(reinterpret_cast<const char *>(&oldRegister), sizeof(int));
stream.write(reinterpret_cast<const char *>(&gbWindowLine), sizeof(int));
stream.write(reinterpret_cast<const char *>(&inUseRegister_WY), sizeof(int));
stream.write(reinterpret_cast<const char *>(&screenOn), sizeof(int));
stream.write(reinterpret_cast<const char *>(&marker), sizeof(int));
stream.flush();
stream.close();
wstring wname(fileNameA.begin(), fileNameA.end());
Platform::String ^str = ref new Platform::String(wname.c_str());
return StorageFile::GetFileFromPathAsync(str);
}).then([](StorageFile ^file)
{
// Generate target file name and extension
StorageFile ^romFile = ROMFile;
Platform::String ^tmp = ROMFile->Name;
const wchar_t *end = tmp->End();
while(*end != '.') end--;
size_t diff = tmp->End() - end;
Platform::String ^name = ref new Platform::String(romFile->Name->Begin(), romFile->Name->Length() - diff) + SavestateSlot;
Platform::String ^extension = file->FileType;
Platform::String ^fullName = name + extension;
return file->MoveAsync(ROMFolder, fullName, NameCollisionOption::ReplaceExisting);
}).then([emulator](task<void> t)
{
try
{
emulator->Unpause();
t.get();
}catch(Exception ^ex)
{
#if _DEBUG
Platform::String ^message = ex->Message;
wstring str(message->Begin(), message->End());
OutputDebugStringW((L"Save state: " + str).c_str());
#endif
}
});
}
task<void> SaveGBAStateAsync(void)
{
EmulatorGame *emulator = EmulatorGame::GetInstance();
return create_task([emulator]()
{
if(!ROMFile || !ROMFolder)
{
throw ref new Exception(E_FAIL, L"No ROM loaded.");
}
emulator->Pause();
// Generate random file name to store in temp folder
Platform::String ^folderpath = Windows::Storage::ApplicationData::Current->TemporaryFolder->Path;
string folderPathStr(folderpath->Begin(), folderpath->End());
stringstream tmpFileNameStream;
tmpFileNameStream << folderPathStr << "\\";
tmpFileNameStream << rand() << rand() << ".sgm";
string fileNameA = tmpFileNameStream.str();
FILE *file;
auto error = fopen_s(&file, fileNameA.c_str(), "wb");
if(!file)
{
stringstream ss;
ss << "Unable to open tmp file '";
ss << fileNameA;
ss << "' to store savestate (";
ss << error;
ss << ").";
//throw GameException(ss.str().c_str());
string str(ss.str());
throw ref new Exception(E_FAIL, ref new Platform::String(wstring(str.begin(), str.end()).c_str()));
}
fclose(file);
ofstream stream (fileNameA.c_str(), ios::binary);
if(!stream.is_open())
{
#if _DEBUG
stringstream ss;
ss << "Unable to open tmp file '";
ss << fileNameA;
ss << "' to store savestate.";
string str(ss.str());
throw ref new Exception(E_FAIL, ref new Platform::String(wstring(str.begin(), str.end()).c_str()));
#endif
}
extern Gb_Apu *gb_apu;
extern gb_apu_state_ss state;
extern variable_desc saveGameStruct[116];
extern variable_desc eepromSaveData[8];
extern variable_desc flashSaveData3[6];
extern variable_desc gba_state[32];
extern RTCCLOCKDATA rtcClockData;
extern bool stopState;
extern int IRQTicks;
extern int dummy_state [16];
int version = SAVE_GAME_VERSION;
stream.write(reinterpret_cast<const char *>(&version), sizeof(int));
stream.write(reinterpret_cast<const char *>(&rom[0xa0]), 16);
stream.write(reinterpret_cast<const char *>(&useBios), sizeof(bool));
stream.write(reinterpret_cast<const char *>(&reg[0]), sizeof(reg));
int i = 0;
for (; i < ARRAYSIZE(saveGameStruct); i++)
{
if(saveGameStruct[i].size > 0)
{
stream.write(reinterpret_cast<const char *>(saveGameStruct[i].address), saveGameStruct[i].size);
}
}
stream.write(reinterpret_cast<const char *>(&stopState), sizeof(bool));
stream.write(reinterpret_cast<const char *>(&IRQTicks), sizeof(int));
stream.write(reinterpret_cast<const char *>(internalRAM), 0x8000);
stream.write(reinterpret_cast<const char *>(paletteRAM), 0x400);
stream.write(reinterpret_cast<const char *>(workRAM), 0x40000);
stream.write(reinterpret_cast<const char *>(vram), 0x20000);
stream.write(reinterpret_cast<const char *>(oam), 0x400);
//stream.write(reinterpret_cast<const char *>(pix), 0x400);
stream.write(reinterpret_cast<const char *>(ioMem), 0x400);
// EEPROM
for (i = 0; i < ARRAYSIZE(eepromSaveData); i++)
{
if(eepromSaveData[i].size > 0)
{
stream.write(reinterpret_cast<const char *>(eepromSaveData[i].address), eepromSaveData[i].size);
}
}
stream.write(reinterpret_cast<const char *>(&eepromSize), sizeof(int));
stream.write(reinterpret_cast<const char *>(eepromData), 0x2000);
// Flash
for (i = 0; i < ARRAYSIZE(flashSaveData3); i++)
{
if(flashSaveData3[i].size > 0)
{
stream.write(reinterpret_cast<const char *>(flashSaveData3[i].address), flashSaveData3[i].size);
}
}
// Sound
gb_apu->save_state(&state.apu);
memset(dummy_state, 0, sizeof dummy_state);
for (i = 0; i < ARRAYSIZE(gba_state); i++)
{
if(gba_state[i].size > 0)
{
stream.write(reinterpret_cast<const char *>(gba_state[i].address), gba_state[i].size);
}
}
stream.write(reinterpret_cast<const char *>(&rtcClockData), sizeof(rtcClockData));
stream.flush();
stream.close();
wstring wname(fileNameA.begin(), fileNameA.end());
Platform::String ^str = ref new Platform::String(wname.c_str());
return StorageFile::GetFileFromPathAsync(str);
}).then([](StorageFile ^file)
{
// Generate target file name and extension
StorageFile ^romFile = ROMFile;
Platform::String ^name = ref new Platform::String(romFile->Name->Begin(), romFile->Name->Length() - 4) + SavestateSlot;
Platform::String ^extension = file->FileType;
Platform::String ^fullName = name + extension;
return file->MoveAsync(ROMFolder, fullName, NameCollisionOption::ReplaceExisting);
}).then([emulator](task<void> t)
{
try
{
emulator->Unpause();
t.get();
}catch(Exception ^ex)
{
#if _DEBUG
Platform::String ^message = ex->Message;
wstring str(message->Begin(), message->End());
OutputDebugStringW((L"Save state: " + str).c_str());
#endif
}
});
}
task<void> LoadStateAsync(int slot)
{
if(gbaROMLoaded)
{
return LoadGBAStateAsync(slot);
}else
{
return LoadGBStateAsync(slot);
}
}
task<void> LoadGBStateAsync(int slot)
{
int whichslot;
if (slot <0)
whichslot = SavestateSlot;
else
whichslot = slot;
EmulatorGame *emulator = EmulatorGame::GetInstance();
return create_task([emulator, whichslot]()
{
if(!ROMFile || !ROMFolder)
{
throw ref new Platform::Exception(E_FAIL, "No ROM loaded.");
}
emulator->Pause();
wstringstream extension;
extension << whichslot << L".sgm";
Platform::String ^name = ROMFile->Name;
const wchar_t *end = name->End();
while(*end != '.') end--;
size_t diff = name->End() - end;
Platform::String ^nameWithoutExt = ref new Platform::String(name->Begin(), name->Length() - diff);
Platform::String ^stateName = nameWithoutExt + ref new Platform::String(extension.str().c_str());
return ROMFolder->GetFileAsync(stateName);
}).then([](StorageFile ^file)
{
return file->CopyAsync(Windows::Storage::ApplicationData::Current->TemporaryFolder, file->Name, NameCollisionOption::ReplaceExisting);
}).then([](StorageFile ^file)
{
Platform::String ^path = file->Path;
wstring str = path->Data();
ifstream stream(str, ios::binary);
if(!stream.is_open())
{
#if _DEBUG
wstringstream ss;
ss << L"Unable to open file '";
ss << str;
ss << L"' to load savestate.";
wstring str(ss.str());
throw ref new Exception(E_FAIL, ref new Platform::String(str.c_str()));
#endif
}
extern u8* gbRom;
extern bool useBios;
extern bool inBios;
extern variable_desc gbSaveGameStruct[78];
extern u16 IFF;
extern int gbSgbMode;
extern variable_desc gbSgbSaveStructV3[11];
extern u8 *gbSgbBorder;
extern u8 *gbSgbBorderChar;
extern u8 gbSgbPacket[112];
extern u16 gbSgbSCPPalette[2048];
extern u8 gbSgbATF[360];
extern u8 gbSgbATFList[16200];
extern mapperMBC1 gbDataMBC1;
extern mapperMBC2 gbDataMBC2;
extern mapperMBC3 gbDataMBC3;
extern mapperMBC5 gbDataMBC5;
extern mapperHuC1 gbDataHuC1;
extern mapperHuC3 gbDataHuC3;
extern mapperTAMA5 gbDataTAMA5;
extern u8 *gbTAMA5ram;
extern int gbTAMA5ramSize;
extern mapperMMM01 gbDataMMM01;
extern u16 gbPalette[128];
extern u8 *gbMemory;
extern int gbRamSize;
extern u8 *gbRam;
extern int gbCgbMode;
extern u8 *gbVram;
extern u8 *gbWram;
extern variable_desc gb_state[20];
extern int gbCheatNumber;
extern gbCheat gbCheatList[100];
extern int gbLcdModeDelayed;
extern int gbLcdTicksDelayed;
extern int gbLcdLYIncrementTicksDelayed;
extern u8 gbSpritesTicks[300];
extern bool gbTimerModeChange;
extern bool gbTimerOnChange;
extern int gbHardware;
extern bool gbBlackScreen;
extern u8 oldRegister_WY;
extern int gbWindowLine;
extern int inUseRegister_WY;
extern bool gbScreenOn;
extern int gbSgbMask;
extern u8 gbSCYLine[300], register_SCY, gbSCXLine[300], register_SCX;
extern u8 gbBgpLine[300];
extern u8 gbBgp[4];
extern u8 gbObp0Line[300], gbObp0[4], gbObp1Line[300], gbObp1[4];
extern u8 register_SVBK, register_VBK;
extern int gbBorderOn;
extern int gbSpeed, gbLine99Ticks;
int marker = 0x12345678;
int version;
stream.read(reinterpret_cast<char *>(&version), sizeof(int));
u8 romname[20];
stream.read(reinterpret_cast<char *>(romname), 15);
int ub, ib;
stream.read(reinterpret_cast<char *>(&ub), sizeof(int));
stream.read(reinterpret_cast<char *>(&ib), sizeof(int));
gbReset();
inBios = ib ? true : false;
int i = 0;
for (; i < ARRAYSIZE(gbSaveGameStruct); i++)
{
if(gbSaveGameStruct[i].size > 0)
{
stream.read(reinterpret_cast<char *>(gbSaveGameStruct[i].address), gbSaveGameStruct[i].size);
}
}
// Correct crash when loading color gameboy save in regular gameboy type.
if (!gbCgbMode)
{
if(gbVram != NULL) {
free(gbVram);
gbVram = NULL;
}
if(gbWram != NULL) {
free(gbWram);
gbWram = NULL;
}
}
else
{
if(gbVram == NULL)
gbVram = (u8 *)malloc(0x4000);
if(gbWram == NULL)
gbWram = (u8 *)malloc(0x8000);
memset(gbVram,0,0x4000);
memset(gbPalette,0, 2*128);
}
stream.read(reinterpret_cast<char *>(&IFF), sizeof(u16));
if(gbSgbMode)
{
i = 0;
for (; i < ARRAYSIZE(gbSgbSaveStructV3); i++)
{
if(gbSgbSaveStructV3[i].size > 0)
{
stream.read(reinterpret_cast<char *>(gbSgbSaveStructV3[i].address), gbSgbSaveStructV3[i].size);
}
}
stream.read(reinterpret_cast<char *>(gbSgbBorder), 2048);
stream.read(reinterpret_cast<char *>(gbSgbBorderChar), 32*256);
stream.read(reinterpret_cast<char *>(gbSgbPacket), 16*7);
stream.read(reinterpret_cast<char *>(gbSgbSCPPalette), 4 * 512 * sizeof(u16));
stream.read(reinterpret_cast<char *>(gbSgbATF), 20 * 18);
stream.read(reinterpret_cast<char *>(gbSgbATFList), 45 * 20 * 18);
}else
{
gbSgbMask = 0;
}
stream.read(reinterpret_cast<char *>(&gbDataMBC1), sizeof(gbDataMBC1));
stream.read(reinterpret_cast<char *>(&gbDataMBC2), sizeof(gbDataMBC2));
stream.read(reinterpret_cast<char *>(&gbDataMBC3), sizeof(gbDataMBC3));
stream.read(reinterpret_cast<char *>(&gbDataMBC5), sizeof(gbDataMBC5));
stream.read(reinterpret_cast<char *>(&gbDataHuC1), sizeof(gbDataHuC1));
stream.read(reinterpret_cast<char *>(&gbDataHuC3), sizeof(gbDataHuC3));
stream.read(reinterpret_cast<char *>(&gbDataTAMA5), sizeof(gbDataTAMA5));
if(gbTAMA5ram != NULL)
{
if(skipSaveGameBattery)
{
stream.seekg(gbTAMA5ramSize, ios_base::cur);
}else
{
stream.read(reinterpret_cast<char *>(gbTAMA5ram), gbTAMA5ramSize);
}
}
stream.read(reinterpret_cast<char *>(&gbDataMMM01), sizeof(gbDataMMM01));
stream.read(reinterpret_cast<char *>(gbPalette), 128 * sizeof(u16));
stream.read(reinterpret_cast<char *>(&gbMemory[0x8000]), 0x8000);
if(gbRamSize && gbRam)
{
int ramSize;
stream.read(reinterpret_cast<char *>(&ramSize), sizeof(int));
if(skipSaveGameBattery)
{
stream.seekg((gbRamSize>ramSize) ? ramSize : gbRamSize, ios_base::cur);
}else
{
stream.read(reinterpret_cast<char *>(gbRam), (gbRamSize>ramSize) ? ramSize : gbRamSize);
}
if(ramSize > gbRamSize)
{
stream.seekg(ramSize-gbRamSize, ios_base::cur);
}
}
memset(gbSCYLine, register_SCY, sizeof(gbSCYLine));
memset(gbSCXLine, register_SCX, sizeof(gbSCXLine));
memset(gbBgpLine, (gbBgp[0] | (gbBgp[1]<<2) | (gbBgp[2]<<4) |
(gbBgp[3]<<6)), sizeof(gbBgpLine));
memset(gbObp0Line, (gbObp0[0] | (gbObp0[1]<<2) | (gbObp0[2]<<4) |
(gbObp0[3]<<6)), sizeof(gbObp0Line));
memset(gbObp1Line, (gbObp1[0] | (gbObp1[1]<<2) | (gbObp1[2]<<4) |
(gbObp1[3]<<6)), sizeof(gbObp1Line));
memset(gbSpritesTicks, 0x0, sizeof(gbSpritesTicks));
if (inBios)
{
gbMemoryMap[0x00] = &gbMemory[0x0000];
memcpy ((u8 *)(gbMemory), (u8 *)(gbRom), 0x1000);
memcpy ((u8 *)(gbMemory), (u8 *)(bios), 0x100);
}
else
{
gbMemoryMap[0x00] = &gbRom[0x0000];
}
gbMemoryMap[0x01] = &gbRom[0x1000];
gbMemoryMap[0x02] = &gbRom[0x2000];
gbMemoryMap[0x03] = &gbRom[0x3000];
gbMemoryMap[0x04] = &gbRom[0x4000];
gbMemoryMap[0x05] = &gbRom[0x5000];
gbMemoryMap[0x06] = &gbRom[0x6000];
gbMemoryMap[0x07] = &gbRom[0x7000];
gbMemoryMap[0x08] = &gbMemory[0x8000];
gbMemoryMap[0x09] = &gbMemory[0x9000];
gbMemoryMap[0x0a] = &gbMemory[0xa000];
gbMemoryMap[0x0b] = &gbMemory[0xb000];
gbMemoryMap[0x0c] = &gbMemory[0xc000];
gbMemoryMap[0x0d] = &gbMemory[0xd000];
gbMemoryMap[0x0e] = &gbMemory[0xe000];
gbMemoryMap[0x0f] = &gbMemory[0xf000];
switch(gbRomType)
{
case 0x00:
case 0x01:
case 0x02:
case 0x03:
// MBC 1
memoryUpdateMapMBC1();
break;
case 0x05:
case 0x06:
// MBC2
memoryUpdateMapMBC2();
break;
case 0x0b:
case 0x0c:
case 0x0d:
// MMM01
memoryUpdateMapMMM01();
break;
case 0x0f:
case 0x10:
case 0x11:
case 0x12:
case 0x13:
// MBC 3
memoryUpdateMapMBC3();
break;
case 0x19:
case 0x1a:
case 0x1b:
// MBC5
memoryUpdateMapMBC5();
break;
case 0x1c:
case 0x1d:
case 0x1e:
// MBC 5 Rumble
memoryUpdateMapMBC5();
break;
case 0x22:
// MBC 7
memoryUpdateMapMBC7();
break;
case 0x56:
// GS3
memoryUpdateMapGS3();
break;
case 0xfd:
// TAMA5
memoryUpdateMapTAMA5();
break;
case 0xfe:
// HuC3
memoryUpdateMapHuC3();
break;
case 0xff:
// HuC1
memoryUpdateMapHuC1();
break;
}
if(gbCgbMode)
{
stream.read(reinterpret_cast<char *>(gbVram), 0x4000);
stream.read(reinterpret_cast<char *>(gbWram), 0x8000);
int value = register_SVBK;
if(value == 0)
value = 1;
gbMemoryMap[0x08] = &gbVram[register_VBK * 0x2000];
gbMemoryMap[0x09] = &gbVram[register_VBK * 0x2000 + 0x1000];
gbMemoryMap[0x0d] = &gbWram[value * 0x1000];
}
gbSoundReadGame2();
i = 0;
for (; i < ARRAYSIZE(gb_state); i++)
{
if(gb_state[i].size > 0)
{
stream.read(reinterpret_cast<char *>(gb_state[i].address), gb_state[i].size);
}
}
gbSoundReadGame3();
if (gbCgbMode && gbSgbMode) {
gbSgbMode = 0;
}
if(gbBorderOn && !gbSgbMask) {
gbSgbRenderBorder();
}
// systemDrawScreen(); // Deadlock!
int numberCheats;
stream.read(reinterpret_cast<char *>(&numberCheats), sizeof(int));
if(skipSaveGameCheats)
{
stream.seekg(numberCheats * sizeof(gbCheat), ios_base::cur);
}else
{
gbCheatNumber = numberCheats;
if(gbCheatNumber > 0)
{
stream.read(reinterpret_cast<char *>(&gbCheatList[0]), sizeof(gbCheat) * gbCheatNumber);
}
}
gbCheatUpdateMap();
int spriteTicks;
int timerModeChange;
int timerOnChange;
int blackScreen;
int oldRegister;
int screenOn;
stream.read(reinterpret_cast<char *>(&gbLcdModeDelayed), sizeof(int));
stream.read(reinterpret_cast<char *>(&gbLcdTicksDelayed), sizeof(int));
stream.read(reinterpret_cast<char *>(&gbLcdLYIncrementTicksDelayed), sizeof(int));
stream.read(reinterpret_cast<char *>(&spriteTicks), sizeof(int));
stream.read(reinterpret_cast<char *>(&timerModeChange), sizeof(int));
stream.read(reinterpret_cast<char *>(&timerOnChange), sizeof(int));
stream.read(reinterpret_cast<char *>(&gbHardware), sizeof(int));
stream.read(reinterpret_cast<char *>(&blackScreen), sizeof(int));
stream.read(reinterpret_cast<char *>(&oldRegister), sizeof(int));
stream.read(reinterpret_cast<char *>(&gbWindowLine), sizeof(int));
stream.read(reinterpret_cast<char *>(&inUseRegister_WY), sizeof(int));
stream.read(reinterpret_cast<char *>(&screenOn), sizeof(int));
gbSpritesTicks[299] = spriteTicks;
gbTimerModeChange = timerModeChange ? true : false;
gbTimerOnChange = timerOnChange ? true : false;
gbBlackScreen = blackScreen ? true : false;
oldRegister_WY = oldRegister;
gbScreenOn = screenOn;
if (gbSpeed)
gbLine99Ticks *= 2;
systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;
return file->DeleteAsync();
}).then([]()
{
return ApplyCheats(ROMCheats);
}).then([](){}).then([emulator](task<void> t)
{
try
{
emulator->Unpause();
t.get();
}catch(Platform::Exception ^ex)
{
#if _DEBUG
wstring err = ex->Message->Data();
OutputDebugStringW(err.c_str());
//EngineLog(LOG_LEVEL::Error, L"Load savestate: " + err);
#endif
}
});
}
task<void> LoadGBAStateAsync(int slot)
{
int whichslot;
if (slot <0)
whichslot = SavestateSlot;
else
whichslot = slot;
EmulatorGame *emulator = EmulatorGame::GetInstance();
return create_task([emulator, whichslot]()
{
if(!ROMFile || !ROMFolder)
{
throw ref new Platform::Exception(E_FAIL, "No ROM loaded.");
}
emulator->Pause();
wstringstream extension;
extension << whichslot << L".sgm";
Platform::String ^name = ROMFile->Name;
Platform::String ^nameWithoutExt = ref new Platform::String(name->Begin(), name->Length() - 4);
Platform::String ^stateName = nameWithoutExt + ref new Platform::String(extension.str().c_str());
return ROMFolder->GetFileAsync(stateName);
}).then([](StorageFile ^file)
{
return file->CopyAsync(Windows::Storage::ApplicationData::Current->TemporaryFolder, file->Name, NameCollisionOption::ReplaceExisting);
}).then([](StorageFile ^file)
{
Platform::String ^path = file->Path;
wstring str = path->Data();
ifstream stream(str, ios::binary);
if(!stream.is_open())
{
#if _DEBUG
wstringstream ss;
ss << L"Unable to open file '";
ss << str;
ss << L"' to load savestate.";
wstring str(ss.str());
throw ref new Exception(E_FAIL, ref new Platform::String(str.c_str()));
#endif
}
extern Gb_Apu *gb_apu;
extern gb_apu_state_ss state;
extern variable_desc saveGameStruct[116];
extern variable_desc eepromSaveData[8];
extern variable_desc flashSaveData3[6];
extern variable_desc gba_state[32];
extern RTCCLOCKDATA rtcClockData;
extern bool stopState;
extern int IRQTicks;
extern int dummy_state [16];
extern bool intState;
int version;
u8 romname[17];
romname[16] = 0;
bool ub;
stream.read(reinterpret_cast<char *>(&version), sizeof(int));
stream.read(reinterpret_cast<char *>(romname), 16);
stream.read(reinterpret_cast<char *>(&ub), sizeof(bool));
stream.read(reinterpret_cast<char *>(&reg[0]), sizeof(reg));
int i = 0;
for (; i < ARRAYSIZE(saveGameStruct); i++)
{
if(saveGameStruct[i].size > 0)
{
stream.read(reinterpret_cast<char *>(saveGameStruct[i].address), saveGameStruct[i].size);
}
}
stream.read(reinterpret_cast<char *>(&stopState), sizeof(bool));
stream.read(reinterpret_cast<char *>(&IRQTicks), sizeof(int));
if(IRQTicks > 0)
{
intState = true;
}else
{
intState = false;
IRQTicks = 0;
}
stream.read(reinterpret_cast<char *>(internalRAM), 0x8000);
stream.read(reinterpret_cast<char *>(paletteRAM), 0x400);
stream.read(reinterpret_cast<char *>(workRAM), 0x40000);
stream.read(reinterpret_cast<char *>(vram), 0x20000);
stream.read(reinterpret_cast<char *>(oam), 0x400);
stream.read(reinterpret_cast<char *>(ioMem), 0x400);
if(skipSaveGameBattery)
{
// Skip EEPROM
for (i = 0; i < ARRAYSIZE(eepromSaveData); i++)
{
stream.seekg(eepromSaveData[i].size, ios_base::cur);
}
stream.seekg(sizeof(int), ios_base::cur);
stream.seekg(0x2000, ios_base::cur);
// Skip Flash
for (i = 0; i < ARRAYSIZE(flashSaveData3); i++)
{
stream.seekg(flashSaveData3[i].size, ios_base::cur);
}
}else
{
// Read EEPROM
for (i = 0; i < ARRAYSIZE(eepromSaveData); i++)
{
if(eepromSaveData[i].size > 0)
{
stream.read(reinterpret_cast<char *>(eepromSaveData[i].address), eepromSaveData[i].size);
}
}
stream.read(reinterpret_cast<char *>(&eepromSize), sizeof(int));
stream.read(reinterpret_cast<char *>(eepromData), 0x2000);
// Read Flash
for (i = 0; i < ARRAYSIZE(flashSaveData3); i++)
{
if(flashSaveData3[i].size > 0)
{
stream.read(reinterpret_cast<char *>(flashSaveData3[i].address), flashSaveData3[i].size);
}
}
}
// Sound
reset_apu();
gb_apu->save_state(&state.apu);
for (i = 0; i < ARRAYSIZE(gba_state); i++)
{
if(gba_state[i].size > 0)
{
stream.read(reinterpret_cast<char *>(gba_state[i].address), gba_state[i].size);
}
}
gb_apu->load_state(state.apu);
write_SGCNT0_H(READ16LE(&ioMem[SGCNT0_H]) & 0x770F);
apply_muting();
stream.read(reinterpret_cast<char *>(&rtcClockData), sizeof(rtcClockData));
stream.close();
layerEnable = layerSettings & DISPCNT;
CPUUpdateRender();
CPUUpdateRenderBuffers(true);
CPUUpdateWindow0();
CPUUpdateWindow1();
gbaSaveType = 0;
switch(saveType) {
case 0:
cpuSaveGameFunc = flashSaveDecide;
break;
case 1:
cpuSaveGameFunc = sramWrite;
gbaSaveType = 1;
break;
case 2:
cpuSaveGameFunc = flashWrite;
gbaSaveType = 2;
break;
case 3:
break;
case 5:
gbaSaveType = 5;
break;
default:
systemMessage(MSG_UNSUPPORTED_SAVE_TYPE,
N_("Unsupported save type %d"), saveType);
break;
}
if(eepromInUse)
gbaSaveType = 3;
CPUReadHelper();
return file->DeleteAsync();
}).then([]()
{
return ApplyCheats(ROMCheats);
}).then([](){}).then([emulator](task<void> t)
{
try
{
emulator->Unpause();
t.get();
}catch(Platform::Exception ^ex)
{
#if _DEBUG
wstring err = ex->Message->Data();
OutputDebugStringW(err.c_str());
//EngineLog(LOG_LEVEL::Error, L"Load savestate: " + err);
#endif
}
});
}
task<void> SuspendAsync(void)
{
return create_task([]()
{
ApplicationDataContainer ^localSettings = ApplicationData::Current->LocalSettings;
if(!IsROMLoaded())
{
localSettings->Values->Remove("ROMPath");
localSettings->Values->Remove("ROMFolderPath");
localSettings->Values->Remove("SelectedSaveStateSlot");
return;
}
int oldSlot = GetSavestateSlot();
SelectSavestateSlot(AUTOSAVESTATE_SLOT);
SaveStateAsync().wait();
SelectSavestateSlot(oldSlot);
localSettings->Values->Insert("ROMPath", dynamic_cast<Windows::Foundation::PropertyValue ^>(Windows::Foundation::PropertyValue::CreateString(ROMFile->Path)));
localSettings->Values->Insert("ROMFolderPath", dynamic_cast<Windows::Foundation::PropertyValue ^>(Windows::Foundation::PropertyValue::CreateString(ROMFolder->Path)));
localSettings->Values->Insert("SelectedSaveStateSlot", dynamic_cast<Windows::Foundation::PropertyValue ^>(Windows::Foundation::PropertyValue::CreateInt32(GetSavestateSlot())));
StoreSettings();
SaveSRAMAsync().wait();
});
}
task<void> RestoreFromApplicationDataAsync(void)
{
return create_task([]()
{
if(IsROMLoaded())
{
throw ref new Platform::Exception(E_FAIL, L"Unable to restore state because a ROM is already loaded.");
}
ApplicationDataContainer ^localSettings = ApplicationData::Current->LocalSettings;
auto values = localSettings->Values;
Platform::String ^romPath = safe_cast<Platform::String ^>(values->Lookup("ROMPath"));
Platform::String ^romFolderPath = safe_cast<Platform::String ^>(values->Lookup("ROMFolderPath"));
auto savestateSlotEntry = safe_cast<Windows::Foundation::IPropertyValue^>(values->Lookup("SelectedSaveStateSlot"));
int savestateSlot = 0;
if(savestateSlotEntry)
{
savestateSlot = savestateSlotEntry->GetInt32();
}
if(!romPath || !romFolderPath)
{
throw ref new Platform::Exception(E_FAIL, L"Unable to restore state after termination.");
}
StorageFile ^romfile = nullptr;
StorageFolder ^romfolder = nullptr;
create_task(StorageFile::GetFileFromPathAsync(romPath)).then([&romfile, romFolderPath](StorageFile ^file)
{
romfile = file;
return StorageFolder::GetFolderFromPathAsync(romFolderPath);
}).then([&romfolder](StorageFolder ^folder)
{
romfolder = folder;
}).wait();
LoadROMAsync(romfile, romfolder).wait();
LoadStateAsync(AUTOSAVESTATE_SLOT).wait();
RestoreSettings();
}).then([](task<void> t)
{
try
{
t.get();
}catch(Platform::Exception ^ex)
{
#if _DEBUG
Platform::String ^str = ex->Message;
std::wstring wstr(str->Begin(), str->End());
OutputDebugStringW(wstr.c_str());
#endif
}
});
return task<void>();
}
void SelectSavestateSlot(int slot)
{
SavestateSlot = slot % MAX_SAVESTATE_SLOTS;
}
int GetSavestateSlot(void)
{
return SavestateSlot;
}
task<Windows::Foundation::Collections::IVector<CheatData ^> ^> LoadCheats(void)
{
Windows::Foundation::Collections::IVector<CheatData ^> ^cheats = ref new Vector<CheatData ^>();
if(!ROMFile || !ROMFolder)
return task<Windows::Foundation::Collections::IVector<CheatData ^> ^>(([cheats](){ return cheats;}));
Platform::String ^name = ROMFile->Name;
const wchar_t *end = name->End();
while(*end != '.') end--;
size_t diff = name->End() - end;
Platform::String ^nameWithoutExt = ref new Platform::String(name->Begin(), name->Length() - diff);
Platform::String ^cheatName = nameWithoutExt->Concat(nameWithoutExt, ".cht");
return create_task([cheatName]()
{
return ROMFolder->GetFileAsync(cheatName);
}).then([](StorageFile ^file)
{
return file->OpenReadAsync();
}).then([cheats](IRandomAccessStreamWithContentType ^stream)
{
auto readStream = stream->GetInputStreamAt(0);
DataReader ^reader = ref new DataReader(readStream);
return create_task([reader, stream, cheats]()
{
create_task(reader->LoadAsync(stream->Size)).wait();
String ^codes = reader->ReadString(stream->Size);
if(codes != nullptr && !codes->IsEmpty())
{
string codesString(codes->Begin(), codes->End());
strreplace(codesString, '\n', '\r');
vector<string> v;
strSplitLines(codesString, v);
for (int i = 0; i < v.size(); i+=3)
{
if(v.size() - i < 3)
continue;
string tmp;
wstring wtmp;
CheatData ^data = ref new CheatData();
tmp = v.at(i);
wtmp = wstring(tmp.begin(), tmp.end());
data->Description = ref new String(wtmp.c_str());
tmp = v.at(i + 1);
strreplace(tmp, '$', '\n'); //replace $ by \n
wtmp = wstring(tmp.begin(), tmp.end());
data->CheatCode = ref new String(wtmp.c_str());
tmp = v.at(i + 2);
data->Enabled = (strcmp(tmp.c_str(), "1") == 0);
cheats->Append(data);
}
}
});
}).then([cheats](task<void> t)
{
try
{
t.get();
}
catch(Platform::Exception ^e)
{
#if _DEBUG
String ^msg = e->Message;
wstring wstr (msg->Begin(), msg->End());
OutputDebugStringW(wstr.c_str());
#endif
}
return cheats;
});
}
task<bool> SaveCheats()
{
if(!ROMFile || !ROMFolder)
return task<bool>(([](){ return false;}));
//ROMCheats = cheats;
Platform::String ^name = ROMFile->Name;
const wchar_t *end = name->End();
while(*end != '.') end--;
size_t diff = name->End() - end;
Platform::String ^nameWithoutExt = ref new Platform::String(name->Begin(), name->Length() - diff);
Platform::String ^cheatName = nameWithoutExt->Concat(nameWithoutExt, ".cht");
return create_task([cheatName]()
{
return ROMFolder->CreateFileAsync(cheatName, CreationCollisionOption::ReplaceExisting);
}).then([](StorageFile ^file)
{
return file->OpenAsync(FileAccessMode::ReadWrite);
}).then([](IRandomAccessStream ^stream)
{
IOutputStream ^outStream = stream->GetOutputStreamAt(0);
DataWriter ^writer = ref new DataWriter(outStream);
writer->UnicodeEncoding = UnicodeEncoding::Utf8;
writer->ByteOrder = ByteOrder::LittleEndian;
for (int i = 0; i < ROMCheats->Size; i++)
{
if(i > 0)
{
writer->WriteString("\n");
}
writer->WriteString(ROMCheats->GetAt(i)->Description);
writer->WriteString("\n");
//replace \n in cheat code by $
wstring code( ROMCheats->GetAt(i)->CheatCode->Begin(), ROMCheats->GetAt(i)->CheatCode->End() );
strreplace(code, '\n', '$');
Platform::String^ pcode = ref new Platform::String(code.c_str());
writer->WriteString(pcode);
writer->WriteString("\n");
writer->WriteString(ROMCheats->GetAt(i)->Enabled ? "1" : "0");
}
return create_task([writer]()
{
create_task(writer->StoreAsync()).wait();
create_task(writer->FlushAsync()).wait();
writer->DetachStream();
});
}).then([]()
{
return ApplyCheats(ROMCheats);
}).then([](task<void> t)
{
try
{
t.get();
return true;
}catch(Platform::Exception ^e)
{
return false;
}
});
}
void ApplyCheats(Windows::Foundation::Collections::IVector<CheatData ^> ^cheats)
{
if (gbaROMLoaded)
ApplyCheatsGBA(cheats);
else
ApplyCheatsGB(cheats);
}
void ApplyCheatsGBA(Windows::Foundation::Collections::IVector<CheatData ^> ^cheats)
{
cheatsDeleteAll(false);
cheatsEnabled = (cheats->Size > 0);
for (int i = 0; i < cheats->Size; i++)
{
auto data = cheats->GetAt(i);
if (!data->Enabled)
continue;
Platform::String ^code = data->CheatCode;
Platform::String ^desc = data->Description;
string codeString(code->Begin(), code->End());
string descString(desc->Begin(), desc->End());
//split the compound code into multiple part
vector<string> codeParts;
strreplace(codeString, '\n', '\r');
strSplitLines(codeString, codeParts);
for (int j = 0; j < codeParts.size(); j++)
{
string code = codeParts.at(j);
if (code.length() == 13)
{
// Code Breaker
cheatsAddCBACode(code.c_str(), descString.c_str());
}
else if (code.length() == 16)
{
//gameshark v1, 2
cheatsAddGSACode(code.c_str(), descString.c_str(), false);
}
else if (code.length() == 17)
{
//gameshark v3
//remove space
code = code.substr(0, 8) + code.substr(9, 8);
cheatsAddGSACode(code.c_str(), descString.c_str(), true);
}
}
}
}
void ApplyCheatsGB(Windows::Foundation::Collections::IVector<CheatData ^> ^cheats)
{
gbCheatRemoveAll();
cheatsEnabled = (cheats->Size > 0);
for (int i = 0; i < cheats->Size; i++)
{
auto data = cheats->GetAt(i);
if (!data->Enabled)
continue;
Platform::String ^code = data->CheatCode;
Platform::String ^desc = data->Description;
string codeString(code->Begin(), code->End());
string descString(desc->Begin(), desc->End());
//split the compound code into multiple part
vector<string> codeParts;
strreplace(codeString, '\n', '\r');
strSplitLines(codeString, codeParts);
for (int j = 0; j < codeParts.size(); j++)
{
string code = codeParts.at(j);
if (code.length() == 11 || code.length() == 7)
{
// GameGenie
gbAddGgCheat(code.c_str(), descString.c_str());
}
else if (code.length() == 8)
{
// Gameshark
gbAddGsCheat(code.c_str(), descString.c_str());
}
}
}
}
task<void> SaveBytesToFileAsync(StorageFile ^file, unsigned char *bytes, size_t length)
{
Platform::String ^name = file->Name;
return create_task([file]()
{
return file->OpenAsync(FileAccessMode::ReadWrite);
}).then([=](IRandomAccessStream ^stream)
{
IOutputStream ^outputStream = stream->GetOutputStreamAt(0L);;
DataWriter ^writer = ref new DataWriter(outputStream);
Platform::Array<unsigned char> ^array = ref new Array<unsigned char>(length);
memcpy(array->Data, bytes, length);
writer->WriteBytes(array);
create_task(writer->StoreAsync()).wait();
writer->DetachStream();
return create_task(outputStream->FlushAsync());
}).then([name](bool b)
{
if(!b)
{
#if _DEBUG
wstring wname(name->Begin(), name->End());
//EngineLog(LOG_LEVEL::Error, wname + L": Error while writing to the file.");
OutputDebugStringW((wname + L": Error while writing to the file.").c_str());
#endif
}
}).then([name](task<void> t)
{
try
{
t.get();
}catch(COMException ^ex)
{
#if _DEBUG
Platform::String ^error = ex->Message;
wstring wname(name->Begin(), name->End());
wstring werror(error->Begin(), error->End());
//EngineLog(LOG_LEVEL::Error, wname + L": " + werror);
OutputDebugStringW((wname + L": " + werror).c_str());
#endif
}
});
}
task<ROMData> GetBytesFromFileAsync(StorageFile ^file)
{
auto inputStream = make_shared<IInputStream ^>();
auto openTask = create_task(file->OpenSequentialReadAsync());
return openTask.then([=] (IInputStream ^stream)
{
*inputStream = stream;
return file->GetBasicPropertiesAsync();
}).then([=](BasicProperties ^properties)
{
Buffer ^buffer = ref new Buffer(properties->Size);
return (*inputStream)->ReadAsync(buffer, properties->Size, InputStreamOptions::None);
})
.then([=](IBuffer ^buffer)
{
DataReader ^reader = DataReader::FromBuffer(buffer);
Array<BYTE> ^bytes = ref new Array<BYTE>(buffer->Length);
reader->ReadBytes(bytes);
BYTE *rawBytes = new BYTE[buffer->Length];
for (int i = 0; i < buffer->Length; i++)
{
rawBytes[i] = bytes[i];
}
ROMData data;
data.Length = buffer->Length;
data.ROM = rawBytes;
return data;
});
}
task<void> SaveHidConfig()
{
return create_task(ApplicationData::Current->LocalFolder->CreateFileAsync("hid-configs.ini", CreationCollisionOption::ReplaceExisting))
.then([](StorageFile ^file)
{
return file->OpenAsync(FileAccessMode::ReadWrite);
}).then([](IRandomAccessStream ^stream)
{
IOutputStream ^outStream = stream->GetOutputStreamAt(0);
DataWriter ^writer = ref new DataWriter(outStream);
writer->UnicodeEncoding = UnicodeEncoding::Utf8;
writer->ByteOrder = ByteOrder::LittleEndian;
for (auto pair : hidConfigs)
{
String^ id = pair->Key;
HIDControllerInput^ config = pair->Value;
writer->WriteString("[" + id + "]\n");
for (auto bpair : config->booleanControlMapping)
{
int bid = bpair->Key;
String^ function = bpair->Value;
writer->WriteString("B_" + bid + ":" + function + "\n");
}
for (auto &ncontrol : config->allNumericControls)
{
String^ out = "N_" + ncontrol->UsagePage + "_" + ncontrol->UsageId + ":" + ncontrol->Type + "_"
+ ncontrol->DefaultValue + "_" + ncontrol->MaximumValue + "_" + ncontrol->Mapping->Size;
for (auto npair : ncontrol->Mapping)
{
int nid = npair->Key;
String^ function = npair->Value;
out += "_" + nid + "_" + function;
}
out += "\n";
writer->WriteString(out);
}
writer->WriteString("\n");
}
return create_task([writer]()
{
create_task(writer->StoreAsync()).wait();
create_task(writer->FlushAsync()).wait();
writer->DetachStream();
});
});
}
task<void> LoadHidConfig()
{
auto reader = make_shared<DataReader ^>();
return create_task(ApplicationData::Current->LocalFolder->GetFileAsync("hid-configs.ini"))
.then([reader](StorageFile ^file)
{
return file->OpenReadAsync();
}).then([reader](IRandomAccessStreamWithContentType ^stream)
{
*reader = ref new DataReader(stream);
return create_task((*reader)->LoadAsync((unsigned int)stream->Size));
}).then([reader](unsigned int bytesRead)
{
String ^text = nullptr;
Map<Platform::String ^, ROMConfig> ^map = ref new Map<Platform::String ^, ROMConfig>();
text = (*reader)->ReadString(bytesRead);
if (text == nullptr)
return;
wstring str(text->Begin(), text->End());
vector<wstring> lines = split(str, L'\n');
for (vector<wstring>::const_iterator i = lines.begin(); i != lines.end(); ++i)
{
wstring line = *i;
int startBraces = firstIndexOf(line, L'[');
if (startBraces == -1)
{
continue;
}
int endBraces = firstIndexOf(line, L']');
if (endBraces == -1)
{
continue;
}
HIDControllerInput^ config = ref new HIDControllerInput();
wstring deviceId = line.substr(startBraces + 1, endBraces - startBraces - 1);
Platform::String^ pDeviceId = ref new Platform::String(deviceId.c_str());
for (++i; i != lines.end() && !stringWhitespace(line = *i); ++i) //look for button mapping until find a blank line
{
int colonIndex = firstIndexOf(line, L':');
if (colonIndex == -1)
{
continue;
}
if (colonIndex + 1 >= line.size())
{
continue;
}
wstring controlName = line.substr(0, colonIndex);
wstring controlValue = line.substr(colonIndex + 1);
vector<wstring> nameParts = split(controlName, L'_');
if (nameParts.at(0) == L"B") //this is a button (boolean control)
{
int buttonId = stoi(nameParts.at(1));
Platform::String^ function = ref new Platform::String(controlValue.c_str());
config->booleanControlMapping->Insert(buttonId, function);
}
else if (nameParts.at(0) == L"N") //numeric control (axis, hat)
{
//usagepage and usageid
int usagePage = stoi(nameParts.at(1));
int usageId = stoi(nameParts.at(2));
HidNumericControlExt^ control = ref new HidNumericControlExt(usagePage, usageId);
//get the control info
vector<wstring> valueParts = split(controlValue, L'_');
control->Type = stoi(valueParts.at(0));
control->DefaultValue = stoi(valueParts.at(1));
control->MaximumValue = stoi(valueParts.at(2));
int Nmapping = stoi(valueParts.at(3));
int index = 4;
for (int j = 0; j < Nmapping; j++)
{
int function_id = stoi(valueParts.at(index));
Platform::String^ function = ref new Platform::String(valueParts.at(index+1).c_str());
control->Mapping->Insert(function_id, function);
index += 2;
}
//add to list
config->allNumericControls->Append(control);
}
}
hidConfigs->Insert(pDeviceId, config);
if (i == lines.end())
{
break;
}
}
}).then([](task<void> t)
{
try
{
t.get();
}
catch (Platform::Exception^)
{
}
});
}
}