mirror of
https://github.com/libretro/Mu.git
synced 2026-09-24 23:56:23 +00:00
Prepare to add virtual sdcard access, add libretro button mappings
This commit is contained in:
@@ -141,19 +141,24 @@ void retro_reset(void)
|
||||
|
||||
void retro_run(void)
|
||||
{
|
||||
/*
|
||||
key_state_t ks;
|
||||
|
||||
input_poll_cb();
|
||||
|
||||
ks.up = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP);
|
||||
ks.right = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT);
|
||||
ks.down = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN);
|
||||
ks.left = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT);
|
||||
ks.start = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START);
|
||||
ks.select = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT);
|
||||
|
||||
*/
|
||||
//dpad
|
||||
palmInput.buttonUp = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP);
|
||||
palmInput.buttonRight = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT);
|
||||
palmInput.buttonDown = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN);
|
||||
palmInput.buttonLeft = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT);
|
||||
palmInput.buttonSelect = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R);
|
||||
|
||||
//app buttons
|
||||
palmInput.buttonCalender = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y);
|
||||
palmInput.buttonAddress = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X);
|
||||
palmInput.buttonTodo = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B);
|
||||
palmInput.buttonNotes = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A);
|
||||
|
||||
//special buttons
|
||||
palmInput.buttonPower = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START);
|
||||
palmInput.buttonContrast = input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT);
|
||||
|
||||
emulateFrame();
|
||||
video_cb(palmFramebuffer, LR_SCREENWIDTH, LR_SCREENHEIGHT, LR_SCREENWIDTH * sizeof(uint16_t));
|
||||
@@ -167,16 +172,18 @@ bool retro_load_game(const struct retro_game_info *info)
|
||||
FILE* romFile;
|
||||
size_t bytesRead;
|
||||
struct retro_input_descriptor desc[] = {
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "OS Menu" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "Calender" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "Address Book" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "ToDo" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Notes" },
|
||||
{ 0 },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Dpad Left" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Dpad Up" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Dpad Down" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Dpad Right" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "Dpad Middle" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Power" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Contrast" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "Notes" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "ToDo" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_X, "Address Book" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_Y, "Calender" },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc);
|
||||
@@ -250,23 +257,13 @@ bool retro_unserialize(const void *data, size_t size)
|
||||
|
||||
void *retro_get_memory_data(unsigned id)
|
||||
{
|
||||
/*
|
||||
if (id != RETRO_MEMORY_SAVE_RAM)
|
||||
return NULL;
|
||||
|
||||
return game_data();
|
||||
*/
|
||||
//palm m515 is too complicated for sram, use savestates
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t retro_get_memory_size(unsigned id)
|
||||
{
|
||||
/*
|
||||
if (id != RETRO_MEMORY_SAVE_RAM)
|
||||
return 0;
|
||||
|
||||
return game_data_size();
|
||||
*/
|
||||
//palm m515 is too complicated for sram, use savestates
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
52
src/bps/global.h
Normal file
52
src/bps/global.h
Normal file
@@ -0,0 +1,52 @@
|
||||
//Module name: Floating IPS, global header
|
||||
//Author: Alcaro
|
||||
//Date: June 18, 2015
|
||||
//Licence: GPL v3.0 or higher
|
||||
|
||||
#ifndef struct_mem
|
||||
#define struct_mem
|
||||
|
||||
//the standard library can be assumed to exist
|
||||
#include <stddef.h>//size_t, SIZE_MAX
|
||||
#include <stdint.h>//uint8_t
|
||||
|
||||
#ifndef SIZE_MAX
|
||||
#define SIZE_MAX ((size_t)-1)
|
||||
#endif
|
||||
|
||||
struct mem {
|
||||
unsigned char * ptr;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
#if defined(FLIPS_WINDOWS)
|
||||
#define LPCWSTR const wchar_t *
|
||||
#else
|
||||
#define LPCWSTR const char *
|
||||
#endif
|
||||
|
||||
class file {
|
||||
public:
|
||||
static file* create(LPCWSTR filename);
|
||||
|
||||
virtual size_t len() = 0;
|
||||
virtual bool read(uint8_t* target, size_t start, size_t len) = 0;
|
||||
|
||||
static struct mem read(LPCWSTR filename); // provided by Flips core
|
||||
struct mem read(); // provided by Flips core
|
||||
|
||||
virtual ~file() {}
|
||||
};
|
||||
|
||||
class filewrite {
|
||||
public:
|
||||
static filewrite* create(LPCWSTR filename);
|
||||
|
||||
virtual bool append(const uint8_t* data, size_t len) = 0;
|
||||
|
||||
static bool write(LPCWSTR filename, struct mem data); // provided by Flips core
|
||||
|
||||
virtual ~filewrite() {}
|
||||
};
|
||||
|
||||
#endif
|
||||
521
src/bps/libbps.cpp
Normal file
521
src/bps/libbps.cpp
Normal file
@@ -0,0 +1,521 @@
|
||||
//Module name: libbps
|
||||
//Author: Alcaro
|
||||
//Date: December 20, 2014
|
||||
//Licence: GPL v3.0 or higher
|
||||
|
||||
#include "libbps.h"
|
||||
|
||||
#include <stdlib.h>//malloc, realloc, free
|
||||
#include <string.h>//memcpy, memset
|
||||
#include <stdint.h>//uint8_t, uint32_t
|
||||
#include "crc32.h"//crc32
|
||||
|
||||
static uint32_t read32(uint8_t * ptr)
|
||||
{
|
||||
uint32_t out;
|
||||
out =ptr[0];
|
||||
out|=ptr[1]<<8;
|
||||
out|=ptr[2]<<16;
|
||||
out|=ptr[3]<<24;
|
||||
return out;
|
||||
}
|
||||
|
||||
enum { SourceRead, TargetRead, SourceCopy, TargetCopy };
|
||||
|
||||
#define error(which) do { error=which; goto exit; } while(0)
|
||||
#define assert_sum(a,b) do { if (SIZE_MAX-(a)<(b)) error(bps_too_big); } while(0)
|
||||
#define assert_shift(a,b) do { if (SIZE_MAX>>(b)<(a)) error(bps_too_big); } while(0)
|
||||
enum bpserror bps_apply(struct mem patch, struct mem in, struct mem * out, struct mem * metadata, bool accept_wrong_input)
|
||||
{
|
||||
enum bpserror error = bps_ok;
|
||||
out->len=0;
|
||||
out->ptr=NULL;
|
||||
if (metadata)
|
||||
{
|
||||
metadata->len=0;
|
||||
metadata->ptr=NULL;
|
||||
}
|
||||
if (patch.len<4+3+12) return bps_broken;
|
||||
|
||||
if (true)
|
||||
{
|
||||
#define read8() (*(patchat++))
|
||||
#define decodeto(var) \
|
||||
do { \
|
||||
var=0; \
|
||||
unsigned int shift=0; \
|
||||
while (true) \
|
||||
{ \
|
||||
uint8_t next=read8(); \
|
||||
assert_shift(next&0x7F, shift); \
|
||||
size_t addthis=(next&0x7F)<<shift; \
|
||||
assert_sum(var, addthis); \
|
||||
var+=addthis; \
|
||||
if (next&0x80) break; \
|
||||
shift+=7; \
|
||||
assert_sum(var, 1U<<shift); \
|
||||
var+=1<<shift; \
|
||||
} \
|
||||
} while(false)
|
||||
#define write8(byte) (*(outat++)=byte)
|
||||
|
||||
const uint8_t * patchat=patch.ptr;
|
||||
const uint8_t * patchend=patch.ptr+patch.len-12;
|
||||
|
||||
if (read8()!='B') error(bps_broken);
|
||||
if (read8()!='P') error(bps_broken);
|
||||
if (read8()!='S') error(bps_broken);
|
||||
if (read8()!='1') error(bps_broken);
|
||||
|
||||
uint32_t crc_in_e = read32(patch.ptr+patch.len-12);
|
||||
uint32_t crc_out_e = read32(patch.ptr+patch.len-8);
|
||||
uint32_t crc_patch_e = read32(patch.ptr+patch.len-4);
|
||||
|
||||
uint32_t crc_in_a = crc32(in.ptr, in.len);
|
||||
uint32_t crc_patch_a = crc32(patch.ptr, patch.len-4);
|
||||
|
||||
if (crc_patch_a != crc_patch_e) error(bps_broken);
|
||||
|
||||
size_t inlen;
|
||||
decodeto(inlen);
|
||||
|
||||
size_t outlen;
|
||||
decodeto(outlen);
|
||||
|
||||
if (inlen!=in.len || crc_in_a!=crc_in_e)
|
||||
{
|
||||
if (in.len==outlen && crc_in_a==crc_out_e) error=bps_to_output;
|
||||
else error=bps_not_this;
|
||||
if (!accept_wrong_input) goto exit;
|
||||
}
|
||||
|
||||
out->len=outlen;
|
||||
out->ptr=(uint8_t*)malloc(outlen);
|
||||
|
||||
const uint8_t * instart=in.ptr;
|
||||
const uint8_t * inreadat=in.ptr;
|
||||
const uint8_t * inend=in.ptr+in.len;
|
||||
|
||||
uint8_t * outstart=out->ptr;
|
||||
uint8_t * outreadat=out->ptr;
|
||||
uint8_t * outat=out->ptr;
|
||||
uint8_t * outend=out->ptr+out->len;
|
||||
|
||||
size_t metadatalen;
|
||||
decodeto(metadatalen);
|
||||
|
||||
if (metadata && metadatalen)
|
||||
{
|
||||
metadata->len=metadatalen;
|
||||
metadata->ptr=(uint8_t*)malloc(metadatalen+1);
|
||||
for (size_t i=0;i<metadatalen;i++) metadata->ptr[i]=read8();
|
||||
metadata->ptr[metadatalen]='\0';//just to be on the safe side - that metadata is assumed to be text, might as well terminate it
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i=0;i<metadatalen;i++) (void)read8();
|
||||
}
|
||||
|
||||
while (patchat<patchend)
|
||||
{
|
||||
size_t thisinstr;
|
||||
decodeto(thisinstr);
|
||||
size_t length=(thisinstr>>2)+1;
|
||||
int action=(thisinstr&3);
|
||||
if (outat+length>outend) error(bps_broken);
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case SourceRead:
|
||||
{
|
||||
if (outat-outstart+length > in.len) error(bps_broken);
|
||||
for (size_t i=0;i<length;i++)
|
||||
{
|
||||
size_t pos = outat-outstart; // don't inline, write8 changes outat
|
||||
write8(instart[pos]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TargetRead:
|
||||
{
|
||||
if (patchat+length>patchend) error(bps_broken);
|
||||
for (size_t i=0;i<length;i++) write8(read8());
|
||||
}
|
||||
break;
|
||||
case SourceCopy:
|
||||
{
|
||||
size_t encodeddistance;
|
||||
decodeto(encodeddistance);
|
||||
size_t distance=encodeddistance>>1;
|
||||
if ((encodeddistance&1)==0) inreadat+=distance;
|
||||
else inreadat-=distance;
|
||||
|
||||
if (inreadat<instart || inreadat+length>inend) error(bps_broken);
|
||||
for (size_t i=0;i<length;i++) write8(*inreadat++);
|
||||
}
|
||||
break;
|
||||
case TargetCopy:
|
||||
{
|
||||
size_t encodeddistance;
|
||||
decodeto(encodeddistance);
|
||||
size_t distance=encodeddistance>>1;
|
||||
if ((encodeddistance&1)==0) outreadat+=distance;
|
||||
else outreadat-=distance;
|
||||
|
||||
if (outreadat<outstart || outreadat>=outat || outreadat+length>outend) error(bps_broken);
|
||||
for (size_t i=0;i<length;i++) write8(*outreadat++);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (patchat!=patchend) error(bps_broken);
|
||||
if (outat!=outend) error(bps_broken);
|
||||
|
||||
uint32_t crc_out_a = crc32(out->ptr, out->len);
|
||||
|
||||
if (crc_out_a!=crc_out_e)
|
||||
{
|
||||
error=bps_not_this;
|
||||
if (!accept_wrong_input) goto exit;
|
||||
}
|
||||
return error;
|
||||
#undef read8
|
||||
#undef decodeto
|
||||
#undef write8
|
||||
}
|
||||
|
||||
exit:
|
||||
free(out->ptr);
|
||||
out->len=0;
|
||||
out->ptr=NULL;
|
||||
if (metadata)
|
||||
{
|
||||
free(metadata->ptr);
|
||||
metadata->len=0;
|
||||
metadata->ptr=NULL;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
#define write(val) \
|
||||
do { \
|
||||
out[outlen++]=(val); \
|
||||
if (outlen==outbuflen) \
|
||||
{ \
|
||||
outbuflen*=2; \
|
||||
out=(uint8_t*)realloc(out, outbuflen); \
|
||||
} \
|
||||
} while(0)
|
||||
#define write32(val) \
|
||||
do { \
|
||||
uint32_t tmp=(val); \
|
||||
write(tmp); \
|
||||
write(tmp>>8); \
|
||||
write(tmp>>16); \
|
||||
write(tmp>>24); \
|
||||
} while(0)
|
||||
#define writenum(val) \
|
||||
do { \
|
||||
size_t tmpval=(val); \
|
||||
while (true) \
|
||||
{ \
|
||||
uint8_t tmpbyte=(tmpval&0x7F); \
|
||||
tmpval>>=7; \
|
||||
if (!tmpval) \
|
||||
{ \
|
||||
write(tmpbyte|0x80); \
|
||||
break; \
|
||||
} \
|
||||
write(tmpbyte); \
|
||||
tmpval--; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
enum bpserror bps_create_linear(struct mem sourcemem, struct mem targetmem, struct mem metadata, struct mem * patchmem)
|
||||
{
|
||||
if (sourcemem.len>=(SIZE_MAX>>2) - 16) return bps_too_big;//the 16 is just to be on the safe side, I don't think it's needed.
|
||||
if (targetmem.len>=(SIZE_MAX>>2) - 16) return bps_too_big;
|
||||
|
||||
const uint8_t * source=sourcemem.ptr;
|
||||
const uint8_t * sourceend=sourcemem.ptr+sourcemem.len;
|
||||
if (sourcemem.len>targetmem.len) sourceend=sourcemem.ptr+targetmem.len;
|
||||
const uint8_t * targetbegin=targetmem.ptr;
|
||||
const uint8_t * target=targetmem.ptr;
|
||||
const uint8_t * targetend=targetmem.ptr+targetmem.len;
|
||||
|
||||
const uint8_t * targetcopypos=targetbegin;
|
||||
|
||||
size_t outbuflen=4096;
|
||||
uint8_t * out=(uint8_t*)malloc(outbuflen);
|
||||
size_t outlen=0;
|
||||
write('B');
|
||||
write('P');
|
||||
write('S');
|
||||
write('1');
|
||||
writenum(sourcemem.len);
|
||||
writenum(targetmem.len);
|
||||
writenum(metadata.len);
|
||||
for (size_t i=0;i<metadata.len;i++) write(metadata.ptr[i]);
|
||||
|
||||
size_t mainContentPos=outlen;
|
||||
|
||||
const uint8_t * lastknownchange=targetbegin;
|
||||
while (target<targetend)
|
||||
{
|
||||
size_t numunchanged=0;
|
||||
while (source+numunchanged<sourceend && source[numunchanged]==target[numunchanged]) numunchanged++;
|
||||
if (numunchanged>1)
|
||||
{
|
||||
//assert_shift((numunchanged-1), 2);
|
||||
writenum((numunchanged-1)<<2 | 0);//SourceRead
|
||||
source+=numunchanged;
|
||||
target+=numunchanged;
|
||||
}
|
||||
|
||||
size_t numchanged=0;
|
||||
if (lastknownchange>target) numchanged=lastknownchange-target;
|
||||
while ((source+numchanged>=sourceend ||
|
||||
source[numchanged]!=target[numchanged] ||
|
||||
source[numchanged+1]!=target[numchanged+1] ||
|
||||
source[numchanged+2]!=target[numchanged+2]) &&
|
||||
target+numchanged<targetend)
|
||||
{
|
||||
numchanged++;
|
||||
if (source+numchanged>=sourceend) numchanged=targetend-target;
|
||||
}
|
||||
lastknownchange=target+numchanged;
|
||||
if (numchanged)
|
||||
{
|
||||
//assert_shift((numchanged-1), 2);
|
||||
size_t rle1start=(target==targetbegin);
|
||||
while (true)
|
||||
{
|
||||
if (
|
||||
target[rle1start-1]==target[rle1start+0] &&
|
||||
target[rle1start+0]==target[rle1start+1] &&
|
||||
target[rle1start+1]==target[rle1start+2] &&
|
||||
target[rle1start+2]==target[rle1start+3])
|
||||
{
|
||||
numchanged=rle1start;
|
||||
break;
|
||||
}
|
||||
if (
|
||||
target[rle1start-2]==target[rle1start+0] &&
|
||||
target[rle1start-1]==target[rle1start+1] &&
|
||||
target[rle1start+0]==target[rle1start+2] &&
|
||||
target[rle1start+1]==target[rle1start+3] &&
|
||||
target[rle1start+2]==target[rle1start+4])
|
||||
{
|
||||
numchanged=rle1start;
|
||||
break;
|
||||
}
|
||||
if (rle1start+3>=numchanged) break;
|
||||
rle1start++;
|
||||
}
|
||||
if (numchanged)
|
||||
{
|
||||
writenum((numchanged-1)<<2 | TargetRead);
|
||||
for (size_t i=0;i<numchanged;i++)
|
||||
{
|
||||
write(target[i]);
|
||||
}
|
||||
source+=numchanged;
|
||||
target+=numchanged;
|
||||
}
|
||||
if (target[-2]==target[0] && target[-1]==target[1] && target[0]==target[2])
|
||||
{
|
||||
//two-byte RLE
|
||||
size_t rlelen=0;
|
||||
while (target+rlelen<targetend && target[0]==target[rlelen+0] && target[1]==target[rlelen+1]) rlelen+=2;
|
||||
writenum((rlelen-1)<<2 | TargetCopy);
|
||||
writenum((target-targetcopypos-2)<<1);
|
||||
source+=rlelen;
|
||||
target+=rlelen;
|
||||
targetcopypos=target-2;
|
||||
}
|
||||
else if (target[-1]==target[0] && target[0]==target[1])
|
||||
{
|
||||
//one-byte RLE
|
||||
size_t rlelen=0;
|
||||
while (target+rlelen<targetend && target[0]==target[rlelen]) rlelen++;
|
||||
writenum((rlelen-1)<<2 | TargetCopy);
|
||||
writenum((target-targetcopypos-1)<<1);
|
||||
source+=rlelen;
|
||||
target+=rlelen;
|
||||
targetcopypos=target-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write32(crc32(sourcemem.ptr, sourcemem.len));
|
||||
write32(crc32(targetmem.ptr, targetmem.len));
|
||||
write32(crc32(out, outlen));
|
||||
|
||||
patchmem->ptr=out;
|
||||
patchmem->len=outlen;
|
||||
|
||||
//while this may look like it can be fooled by a patch containing one of any other command, it
|
||||
// can't, because the ones that aren't SourceRead requires an argument.
|
||||
size_t i;
|
||||
for (i=mainContentPos;(out[i]&0x80)==0x00;i++) {}
|
||||
if (i==outlen-12-1) return bps_identical;
|
||||
|
||||
return bps_ok;
|
||||
}
|
||||
|
||||
#undef write_nocrc
|
||||
#undef write
|
||||
#undef writenum
|
||||
|
||||
enum bpserror bps_get_checksums(file* patch, uint32_t * inromsum, uint32_t * outromsum, uint32_t * patchsum)
|
||||
{
|
||||
size_t len = patch->len();
|
||||
if (len<4+3+12) return bps_broken;
|
||||
|
||||
uint8_t verify[4];
|
||||
if (!patch->read(verify, 0, 4) || memcmp(verify, "BPS1", 4)) return bps_broken;
|
||||
|
||||
uint8_t checksums[12];
|
||||
if (!patch->read(checksums, len-12, 12)) return bps_broken;
|
||||
if (inromsum) *inromsum =read32(checksums+0);
|
||||
if (outromsum) *outromsum=read32(checksums+4);
|
||||
if (patchsum) *patchsum =read32(checksums+8);
|
||||
return bps_ok;
|
||||
}
|
||||
|
||||
void bps_free(struct mem mem)
|
||||
{
|
||||
free(mem.ptr);
|
||||
}
|
||||
|
||||
#if 0
|
||||
#warning Disable this in release versions.
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
//Congratulations, you found the undocumented feature! It compares two equivalent BPS patches and
|
||||
// tells where each one is more compact. (It crashes or gives bogus answers on invalid or
|
||||
// non-equivalent patches.) Have fun.
|
||||
void bps_compare(struct mem patch1mem, struct mem patch2mem)
|
||||
{
|
||||
const uint8_t * patch[2]={patch1mem.ptr, patch2mem.ptr};
|
||||
size_t patchpos[2]={0,0};
|
||||
size_t patchlen[2]={patch1mem.len-12, patch2mem.len-12};
|
||||
size_t patchoutpos[2]={0,0};
|
||||
|
||||
size_t patchcopypos[2][4]={0,0};//[0] and [1] are unused, but this is just debug code, it doesn't need to be neat.
|
||||
|
||||
#define read8(id) (patch[id][patchpos[id]++])
|
||||
#define decodeto(id, var) \
|
||||
do { \
|
||||
var=0; \
|
||||
int shift=0; \
|
||||
while (true) \
|
||||
{ \
|
||||
uint8_t next=read8(id); \
|
||||
size_t addthis=(next&0x7F)<<shift; \
|
||||
var+=addthis; \
|
||||
if (next&0x80) break; \
|
||||
shift+=7; \
|
||||
var+=1<<shift; \
|
||||
} \
|
||||
} while(false)
|
||||
|
||||
size_t lastmatch=0;
|
||||
size_t patchposatmatch[2]={0,0};
|
||||
|
||||
size_t outlen;
|
||||
patch[0]+=4; patch[1]+=4;//BPS1
|
||||
size_t tempuint;
|
||||
decodeto(0, tempuint); decodeto(1, tempuint);//source-size
|
||||
decodeto(0, outlen); decodeto(1, outlen);//target-size
|
||||
decodeto(0, tempuint); patch[0]+=tempuint;//metadata
|
||||
decodeto(1, tempuint); patch[1]+=tempuint;//metadata
|
||||
|
||||
bool show=false;
|
||||
while (patchpos[0]<patchlen[0] && patchpos[1]<patchlen[1])
|
||||
{
|
||||
bool step[2]={(patchoutpos[0]<=patchoutpos[1]), (patchoutpos[0]>=patchoutpos[1])};
|
||||
char describe[2][256];
|
||||
for (int i=0;i<2;i++)
|
||||
{
|
||||
if (step[i])
|
||||
{
|
||||
size_t patchposstart=patchpos[i];
|
||||
decodeto(i, tempuint);
|
||||
size_t len=(tempuint>>2)+1;
|
||||
patchoutpos[i]+=len;
|
||||
int action=(tempuint&3);
|
||||
//enum { SourceRead, TargetRead, SourceCopy, TargetCopy };
|
||||
const char * actionnames[]={"SourceRead", "TargetRead", "SourceCopy", "TargetCopy"};
|
||||
if (action==TargetRead) patchpos[i]+=len;
|
||||
if (action==SourceCopy || action==TargetCopy)
|
||||
{
|
||||
decodeto(i, tempuint);
|
||||
int delta = tempuint>>1;
|
||||
if (tempuint&1) delta=-delta;
|
||||
patchcopypos[i][action]+=delta;
|
||||
sprintf(describe[i], "%s from %i (%+i) for %i in %i", actionnames[action], patchcopypos[i][action], delta, len, patchpos[i]-patchposstart);
|
||||
patchcopypos[i][action]+=len;
|
||||
}
|
||||
else sprintf(describe[i], "%s from %i for %i in %i", actionnames[action], patchoutpos[i], len, patchpos[i]-patchposstart);
|
||||
if (!step[i^1])
|
||||
{
|
||||
printf("%i: %s\n", i+1, describe[i]);
|
||||
show=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (step[0] && step[1])
|
||||
{
|
||||
if (!strcmp(describe[0], describe[1])) /*printf("3: %s\n", describe[0])*/;
|
||||
else
|
||||
{
|
||||
printf("1: %s\n2: %s\n", describe[0], describe[1]);
|
||||
show=true;
|
||||
}
|
||||
}
|
||||
if (patchoutpos[0]==patchoutpos[1])
|
||||
{
|
||||
size_t used[2]={patchpos[0]-patchposatmatch[0], patchpos[1]-patchposatmatch[1]};
|
||||
char which='=';
|
||||
if (used[0]<used[1]) which='+';
|
||||
if (used[0]>used[1]) which='-';
|
||||
if (show)
|
||||
{
|
||||
printf("%c: %i,%i bytes since last match (%i)\n", which, used[0], used[1], patchoutpos[0]);
|
||||
show=false;
|
||||
}
|
||||
patchposatmatch[0]=patchpos[0];
|
||||
patchposatmatch[1]=patchpos[1];
|
||||
lastmatch=patchoutpos[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct mem ReadWholeFile(const char * filename)
|
||||
{
|
||||
struct mem null = {NULL, 0};
|
||||
|
||||
FILE * file=fopen(filename, "rb");
|
||||
if (!file) return null;
|
||||
fseek(file, 0, SEEK_END);
|
||||
size_t len=ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
unsigned char * data=(unsigned char*)malloc(len);
|
||||
size_t truelen=fread(data, 1,len, file);
|
||||
fclose(file);
|
||||
if (len!=truelen)
|
||||
{
|
||||
free(data);
|
||||
return null;
|
||||
}
|
||||
|
||||
struct mem ret = { (unsigned char*)data, len };
|
||||
return ret;
|
||||
}
|
||||
int main(int argc,char**argv)
|
||||
{
|
||||
bps_compare(ReadWholeFile(argv[1]),ReadWholeFile(argv[2]));
|
||||
}
|
||||
#endif
|
||||
70
src/bps/libbps.h
Normal file
70
src/bps/libbps.h
Normal file
@@ -0,0 +1,70 @@
|
||||
//Module name: libbps
|
||||
//Author: Alcaro
|
||||
//Date: June 18, 2015
|
||||
//Licence: GPL v3.0 or higher
|
||||
|
||||
#include "global.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef __cplusplus
|
||||
#include <stdbool.h>//bool; if this file does not exist (hi msvc), remove it and uncomment the following three lines.
|
||||
//#define bool int
|
||||
//#define true 1
|
||||
//#define false 0
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum bpserror {
|
||||
bps_ok,//Patch applied or created successfully.
|
||||
|
||||
bps_to_output,//You attempted to apply a patch to its output.
|
||||
bps_not_this, //This is not the intended input file for this patch.
|
||||
bps_broken, //This is not a BPS patch, or it's malformed somehow.
|
||||
|
||||
bps_identical, //The input files are identical.
|
||||
bps_too_big, //Somehow, you're asking for something a size_t can't represent.
|
||||
bps_out_of_mem,//Memory allocation failure.
|
||||
bps_canceled, //The callback returned false.
|
||||
|
||||
bps_shut_the_fuck_up_gcc//This one isn't used, it's just to kill a stray comma warning.
|
||||
};
|
||||
|
||||
//Applies the given BPS patch to the given ROM and puts it in 'out'. Metadata, if present and
|
||||
// requested ('metadata'!=NULL), is also returned. Send both to bps_free when you're done with them.
|
||||
//If accept_wrong_input is true, it may return bps_to_output or bps_not_this, while putting non-NULL in out/metadata.
|
||||
enum bpserror bps_apply(struct mem patch, struct mem in, struct mem * out, struct mem * metadata, bool accept_wrong_input);
|
||||
|
||||
//Creates a BPS patch that converts source to target and stores it to patch. It is safe to give
|
||||
// {NULL,0} as metadata.
|
||||
enum bpserror bps_create_linear(struct mem source, struct mem target, struct mem metadata, struct mem * patch);
|
||||
|
||||
//Very similar to bps_create_linear; the difference is that this one takes longer to run, but
|
||||
// generates smaller patches.
|
||||
//Because it can take much longer, a progress meter is supplied; total is guaranteed to be constant
|
||||
// between every call until this function returns, done is guaranteed to increase between each
|
||||
// call, and done/total is an approximate percentage counter. Anything else is undefined; for
|
||||
// example, progress may or may not be called for done=0, progress may or may not be called for
|
||||
// done=total, done may or may not increase by the same amount between each call, and the duration
|
||||
// between each call may or may not be constant. In fact, it can
|
||||
//To cancel the patch creation, return false from the callback.
|
||||
//It is safe to pass in NULL for the progress indicator if you're not interested. If the callback is
|
||||
// NULL, it can obviously not be canceled that way (though if it's a CLI program, you can always
|
||||
// Ctrl-C it).
|
||||
//The 'moremem' flag makes it use about twice as much memory (9*(source+target) instead of 5*), but is usually slightly faster.
|
||||
enum bpserror bps_create_delta(file* source, file* target, struct mem metadata, struct mem * patch,
|
||||
bool (*progress)(void* userdata, size_t done, size_t total), void* userdata,
|
||||
bool moremem);
|
||||
|
||||
enum bpserror bps_get_checksums(file* patch, uint32_t * inromsum, uint32_t * outromsum, uint32_t * patchsum);
|
||||
|
||||
//Frees the memory returned in the output parameters of the above. Do not call it twice on the same
|
||||
// input, nor on anything you got from anywhere else. bps_free is guaranteed to be equivalent to
|
||||
// calling stdlib.h's free() on mem.ptr.
|
||||
void bps_free(struct mem mem);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -36,6 +36,19 @@ double palmCrystalCycles;//how many cycles before toggling the 32.768 kHz cry
|
||||
double palmCycleCounter;//can be greater then 0 if too many cycles where run
|
||||
double palmClockMultiplier;//used by the emulator to overclock the emulated palm
|
||||
|
||||
uint64_t (*emulatorGetSysTime)();
|
||||
uint64_t* (*emulatorGetSdCardStateChunkList)(uint64_t sessionId, uint64_t stateId);//returns the bps chunkIds for a stateId in the order they need to be applied
|
||||
void (*emulatorSetSdCardStateChunkList)(uint64_t sessionId, uint64_t stateId, uint64_t* data);//sets the bps chunkIds for a stateId in the order they need to be applied
|
||||
uint8_t* (*emulatorGetSdCardChunk)(uint64_t sessionId, uint64_t chunkId);
|
||||
void (*emulatorSetSdCardChunk)(uint64_t sessionId, uint64_t chunkId, uint8_t* data, uint64_t size);
|
||||
|
||||
|
||||
static inline bool allSdCardCallbacksPresent(){
|
||||
if(emulatorGetSysTime && emulatorGetSdCardStateChunkList && emulatorSetSdCardStateChunkList && emulatorGetSdCardChunk && emulatorSetSdCardChunk)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void emulatorInit(uint8_t* palmRomDump, uint32_t specialFeatures){
|
||||
//cpu
|
||||
@@ -72,9 +85,10 @@ void emulatorInit(uint8_t* palmRomDump, uint32_t specialFeatures){
|
||||
palmInput.touchscreenY = 0;
|
||||
palmInput.touchscreenTouched = false;
|
||||
|
||||
palmSdCard.getSdCardChunk = NULL;
|
||||
palmSdCard.setSdCardChunk = NULL;
|
||||
palmSdCard.sessionId = 0x0000000000000000;
|
||||
palmSdCard.stateId = 0x0000000000000000;
|
||||
palmSdCard.size = 0;
|
||||
palmSdCard.type = CARD_NONE;
|
||||
palmSdCard.inserted = false;
|
||||
|
||||
palmMisc.powerButtonLed = false;
|
||||
@@ -105,11 +119,26 @@ void emulatorSetRtc(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t se
|
||||
setRtc(days, hours, minutes, seconds);
|
||||
}
|
||||
|
||||
uint32_t emulatorSetSdCard(uint64_t size, uint8_t type){
|
||||
if(!allSdCardCallbacksPresent())
|
||||
return EMU_ERROR_CALLBACKS_NOT_SET;
|
||||
|
||||
palmSdCard.sessionId = emulatorGetSysTime();
|
||||
palmSdCard.stateId = 0x0000000000000000;//set when saving state
|
||||
palmSdCard.size = size;
|
||||
palmSdCard.type = type;
|
||||
palmSdCard.inserted = true;
|
||||
|
||||
return EMU_ERROR_NONE;
|
||||
}
|
||||
|
||||
uint32_t emulatorGetStateSize(){
|
||||
return m68k_context_size() + RAM_SIZE + REG_SIZE + sizeof(misc_hw_t) + TOTAL_MEMORY_BANKS + sizeof(double) * 5 + 1;
|
||||
return m68k_context_size() + RAM_SIZE + REG_SIZE + TOTAL_MEMORY_BANKS + sizeof(misc_hw_t) + sizeof(sdcard_t) + sizeof(double) * 4 + sizeof(uint32_t) * 2 + 1;
|
||||
}
|
||||
|
||||
void emulatorSaveState(uint8_t* data){
|
||||
//save sdcard here
|
||||
|
||||
uint32_t offset = 0;
|
||||
m68k_get_context(data + offset);
|
||||
offset += m68k_context_size();
|
||||
@@ -121,6 +150,8 @@ void emulatorSaveState(uint8_t* data){
|
||||
offset += TOTAL_MEMORY_BANKS;
|
||||
memcpy(data + offset, &palmMisc, sizeof(misc_hw_t));
|
||||
offset += sizeof(misc_hw_t);
|
||||
memcpy(data + offset, &palmSdCard, sizeof(sdcard_t));
|
||||
offset += sizeof(sdcard_t);
|
||||
//input is not part of savestates
|
||||
memcpy(data + offset, &palmSpecialFeatures, sizeof(uint32_t));
|
||||
offset += sizeof(uint32_t);
|
||||
@@ -139,6 +170,8 @@ void emulatorSaveState(uint8_t* data){
|
||||
}
|
||||
|
||||
void emulatorLoadState(uint8_t* data){
|
||||
//load sdcard here
|
||||
|
||||
uint32_t offset = 0;
|
||||
m68k_set_context(data + offset);
|
||||
offset += m68k_context_size();
|
||||
@@ -150,6 +183,8 @@ void emulatorLoadState(uint8_t* data){
|
||||
offset += TOTAL_MEMORY_BANKS;
|
||||
memcpy(&palmMisc, data + offset, sizeof(misc_hw_t));
|
||||
offset += sizeof(misc_hw_t);
|
||||
memcpy(&palmSdCard, data + offset, sizeof(sdcard_t));
|
||||
offset += sizeof(sdcard_t);
|
||||
//input is not part of savestates
|
||||
memcpy(&palmSpecialFeatures, data + offset, sizeof(uint32_t));
|
||||
offset += sizeof(uint32_t);
|
||||
|
||||
@@ -8,33 +8,62 @@
|
||||
#include "emuFeatureRegistersSpec.h"
|
||||
|
||||
//emu errors
|
||||
enum emu_error_t{
|
||||
enum{
|
||||
EMU_ERROR_NONE = 0,
|
||||
EMU_ERROR_NOT_IMPLEMENTED,
|
||||
EMU_ERROR_CALLBACKS_NOT_SET,
|
||||
EMU_ERROR_NOT_DOCUMENTED
|
||||
};
|
||||
|
||||
//sdcard types
|
||||
enum{
|
||||
CARD_NONE = 0,
|
||||
CARD_SD,
|
||||
CARD_MMC
|
||||
};
|
||||
|
||||
//memory banks
|
||||
enum{
|
||||
EMPTY_BANK = 0,
|
||||
RAM_BANK,
|
||||
ROM_BANK,
|
||||
REG_BANK,
|
||||
SED1376_REG_BANK,
|
||||
SED1376_FB_BANK
|
||||
};
|
||||
|
||||
//types
|
||||
typedef struct{
|
||||
bool buttonUp;
|
||||
bool buttonDown;
|
||||
bool buttonLeft;//only used in hybrid mode
|
||||
bool buttonRight;//only used in hybrid mode
|
||||
bool buttonSelect;//only used in hybrid mode
|
||||
|
||||
bool buttonCalender;//hw button 1
|
||||
bool buttonAddress;//hw button 2
|
||||
bool buttonTodo;//hw button 3
|
||||
bool buttonNotes;//hw button 4
|
||||
|
||||
bool buttonPower;
|
||||
bool buttonContrast;
|
||||
|
||||
uint16_t touchscreenX;
|
||||
uint16_t touchscreenY;
|
||||
bool touchscreenTouched;
|
||||
}input_t;
|
||||
|
||||
typedef struct{
|
||||
void (*getSdCardChunk)(uint8_t* data, uint32_t size);
|
||||
void (*setSdCardChunk)(uint8_t* data, uint32_t size);
|
||||
//the sdcard is stored as a directory structure with an /(SESSIONID)/sdcard(STATEID).info and /(SESSIONID)/sdcard(CHUNKID).bps
|
||||
//each emulator instance has its own sdcard SESSIONID directory if it has an sdcard
|
||||
//sdcard(STATEID).info specifys the bps files to used
|
||||
//every savestate a new bps file is created with the changes made to the sdcard since the last savestate
|
||||
//the first bps is a patch over a buffer of sdcard size filled with 0x00
|
||||
//the frontend provides file access but the emulator does all the patching
|
||||
uint64_t sessionId;//64 bit system time when emulator starts
|
||||
uint64_t stateId;//64 bit system time when the savestate was taken
|
||||
uint64_t size;
|
||||
uint8_t type;
|
||||
bool inserted;
|
||||
}sdcard_t;
|
||||
|
||||
@@ -71,12 +100,6 @@ typedef struct{
|
||||
#define END_BANK(address, size) (START_BANK(address) + NUM_BANKS(size))
|
||||
#define BANK_IN_RANGE(bank, address, size) (bank >= START_BANK(address) && bank <= END_BANK(address, size))
|
||||
#define TOTAL_MEMORY_BANKS 0x10000
|
||||
#define EMPTY_BANK 0
|
||||
#define RAM_BANK 1
|
||||
#define ROM_BANK 2
|
||||
#define REG_BANK 3
|
||||
#define SED1376_REG_BANK 4
|
||||
#define SED1376_FB_BANK 5
|
||||
|
||||
//memory chip addresses
|
||||
#define RAM_START_ADDRESS 0x00000000
|
||||
@@ -104,10 +127,18 @@ extern double palmCrystalCycles;
|
||||
extern double palmCycleCounter;
|
||||
extern double palmClockMultiplier;
|
||||
|
||||
//callbacks
|
||||
uint64_t (*emulatorGetSysTime)();
|
||||
uint64_t* (*emulatorGetSdCardStateChunkList)(uint64_t sessionId, uint64_t stateId);//returns the bps chunkIds for a stateId in the order they need to be applied
|
||||
void (*emulatorSetSdCardStateChunkList)(uint64_t sessionId, uint64_t stateId, uint64_t* data);//sets the bps chunkIds for a stateId in the order they need to be applied
|
||||
uint8_t* (*emulatorGetSdCardChunk)(uint64_t sessionId, uint64_t chunkId);
|
||||
void (*emulatorSetSdCardChunk)(uint64_t sessionId, uint64_t chunkId, uint8_t* data, uint64_t size);
|
||||
|
||||
//functions
|
||||
void emulatorInit(uint8_t* palmRomDump, uint32_t specialFeatures);
|
||||
void emulatorReset();
|
||||
void emulatorSetRtc(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds);
|
||||
uint32_t emulatorSetSdCard(uint64_t size, uint8_t type);
|
||||
uint32_t emulatorGetStateSize();
|
||||
void emulatorSaveState(uint8_t* data);
|
||||
void emulatorLoadState(uint8_t* data);
|
||||
|
||||
@@ -2,6 +2,7 @@ EMU_SOURCES := $(EMU_PATH)/emulator.c \
|
||||
$(EMU_PATH)/memoryAccess.c \
|
||||
$(EMU_PATH)/hardwareRegisters.c \
|
||||
$(EMU_PATH)/sed1376.c \
|
||||
$(EMU_PATH)/sdcard.c \
|
||||
$(EMU_PATH)/silkscreen.c \
|
||||
$(EMU_PATH)/cpu32Opcodes.c \
|
||||
$(EMU_PATH)/m68k/m68kops.c \
|
||||
|
||||
1
src/sdcard.c
Normal file
1
src/sdcard.c
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
src/sdcard.h
Normal file
1
src/sdcard.h
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
25
tests/HwRegTest17.txt
Normal file
25
tests/HwRegTest17.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
New CPU frequency of:33161216.000000 cycles per second.
|
||||
New clk32 cycle count of :1012.000000.
|
||||
Cpu Wrote 8 bits of 0x00000000 to register 0x0439.
|
||||
Cpu Wrote 8 bits of 0x00000020 to register 0x0449.
|
||||
Cpu Wrote 8 bits of 0x00000000 to register 0x0A27.
|
||||
Cpu Wrote 16 bits of 0x00008000 to register 0x0100.
|
||||
Cpu Wrote 16 bits of 0x000001AB to register 0x0110.
|
||||
Cpu Wrote 16 bits of 0x0000FFC0 to register 0x0102.
|
||||
Cpu Wrote 16 bits of 0x000001F3 to register 0x0112.
|
||||
Cpu Wrote 16 bits of 0x00008200 to register 0x0104.
|
||||
Cpu Wrote 16 bits of 0x00000101 to register 0x0114.
|
||||
Cpu Wrote 16 bits of 0x00000000 to register 0x0106.
|
||||
Cpu Wrote 16 bits of 0x00001E83 to register 0x0116.
|
||||
Cpu Wrote 16 bits of 0x00000040 to register 0x010A.
|
||||
Cpu Wrote 16 bits of 0x00001A81 to register 0x0116.
|
||||
Cpu Read 16 bits from register 0x0310.
|
||||
Cpu Read 8 bits from register 0x0418.
|
||||
Cpu Read 8 bits from register 0x0440.
|
||||
Cpu Read 8 bits from register 0x0441.
|
||||
Cpu Read 8 bits from register 0x0441.
|
||||
New CPU frequency of:33161216.000000 cycles per second.
|
||||
New clk32 cycle count of :1012.000000.
|
||||
Cpu Read 16 bits from register 0x0C02.
|
||||
New CPU frequency of:33161216.000000 cycles per second.
|
||||
New clk32 cycle count of :1012.000000.
|
||||
@@ -15,6 +15,7 @@ DRAMC write enable
|
||||
All of DRAMMC
|
||||
Chip select registers
|
||||
Setting the battery dead bit(PDDATA 0x80) if the emulated battery percent gets too low
|
||||
endian safe savestates
|
||||
|
||||
Fixed:
|
||||
System Control Register, 0xXXFFF000 all upper banks are registers mode
|
||||
|
||||
Reference in New Issue
Block a user