mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
64c3c82c2a
1
CHANGES
1
CHANGES
|
@ -65,6 +65,7 @@ Emulation fixes:
|
|||
- GB Video: Draw SGB border pieces that overlap GB graphics (fixes mgba.io/i/1339)
|
||||
- GBA: Improve timing when not booting from BIOS
|
||||
- GBA: Fix expected entry point for multiboot ELFs (fixes mgba.io/i/2450)
|
||||
- GBA: Fix booting multiboot ROMs with no JOY entrypoint
|
||||
- GBA BIOS: Work around IRQ handling hiccup in Mario & Luigi (fixes mgba.io/i/1059)
|
||||
- GBA BIOS: Initial HLE timing estimation of UnLz77 functions (fixes mgba.io/i/2141)
|
||||
- GBA DMA: Fix DMA source direction bits being cleared (fixes mgba.io/i/2410)
|
||||
|
|
|
@ -58,6 +58,7 @@ The following mappers are fully supported:
|
|||
- Pokémon Jade/Diamond (unlicensed)
|
||||
- BBD (unlicensed MBC5-like)
|
||||
- Hitek (unlicensed MBC5-like)
|
||||
- Sachen MMC1
|
||||
|
||||
The following mappers are partially supported:
|
||||
|
||||
|
@ -67,6 +68,7 @@ The following mappers are partially supported:
|
|||
- TAMA5 (missing RTC support)
|
||||
- HuC-1 (missing IR support)
|
||||
- HuC-3 (missing IR support)
|
||||
- Sachen MMC2 (missing alternate wiring support)
|
||||
|
||||
### Planned features
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
uint32_t hash32(const void* key, int len, uint32_t seed);
|
||||
uint32_t hash32(const void* key, size_t len, uint32_t seed);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
|
|
|
@ -11,13 +11,27 @@
|
|||
CXX_GUARD_START
|
||||
|
||||
struct TableList;
|
||||
typedef uint32_t (*HashFunction)(const void* key, size_t len, uint32_t seed);
|
||||
|
||||
struct TableFunctions {
|
||||
void (*deinitializer)(void*);
|
||||
HashFunction hash;
|
||||
bool (*equal)(const void*, const void*);
|
||||
void* (*ref)(void*);
|
||||
void (*deref)(void*);
|
||||
};
|
||||
|
||||
struct Table {
|
||||
struct TableList* table;
|
||||
size_t tableSize;
|
||||
size_t size;
|
||||
void (*deinitializer)(void*);
|
||||
uint32_t seed;
|
||||
struct TableFunctions fn;
|
||||
};
|
||||
|
||||
struct TableIterator {
|
||||
size_t bucket;
|
||||
size_t entry;
|
||||
};
|
||||
|
||||
void TableInit(struct Table*, size_t initialSize, void (*deinitializer)(void*));
|
||||
|
@ -32,26 +46,48 @@ void TableClear(struct Table*);
|
|||
void TableEnumerate(const struct Table*, void (*handler)(uint32_t key, void* value, void* user), void* user);
|
||||
size_t TableSize(const struct Table*);
|
||||
|
||||
bool TableIteratorStart(const struct Table*, struct TableIterator*);
|
||||
bool TableIteratorNext(const struct Table*, struct TableIterator*);
|
||||
uint32_t TableIteratorGetKey(const struct Table*, const struct TableIterator*);
|
||||
void* TableIteratorGetValue(const struct Table*, const struct TableIterator*);
|
||||
bool TableIteratorLookup(const struct Table*, struct TableIterator*, uint32_t key);
|
||||
|
||||
void HashTableInit(struct Table* table, size_t initialSize, void (*deinitializer)(void*));
|
||||
void HashTableInitCustom(struct Table* table, size_t initialSize, const struct TableFunctions* funcs);
|
||||
void HashTableDeinit(struct Table* table);
|
||||
|
||||
void* HashTableLookup(const struct Table*, const char* key);
|
||||
void* HashTableLookupBinary(const struct Table*, const void* key, size_t keylen);
|
||||
void* HashTableLookupCustom(const struct Table*, void* key);
|
||||
void HashTableInsert(struct Table*, const char* key, void* value);
|
||||
void HashTableInsertBinary(struct Table*, const void* key, size_t keylen, void* value);
|
||||
void HashTableInsertCustom(struct Table*, void* key, void* value);
|
||||
|
||||
void HashTableRemove(struct Table*, const char* key);
|
||||
void HashTableRemoveBinary(struct Table*, const void* key, size_t keylen);
|
||||
void HashTableRemoveCustom(struct Table*, void* key);
|
||||
void HashTableClear(struct Table*);
|
||||
|
||||
void HashTableEnumerate(const struct Table*, void (*handler)(const char* key, void* value, void* user), void* user);
|
||||
void HashTableEnumerateBinary(const struct Table*, void (*handler)(const char* key, size_t keylen, void* value, void* user), void* user);
|
||||
void HashTableEnumerateCustom(const struct Table*, void (*handler)(const char* key, void* value, void* user), void* user);
|
||||
const char* HashTableSearch(const struct Table* table, bool (*predicate)(const char* key, const void* value, const void* user), const void* user);
|
||||
const char* HashTableSearchPointer(const struct Table* table, const void* value);
|
||||
const char* HashTableSearchData(const struct Table* table, const void* value, size_t bytes);
|
||||
const char* HashTableSearchString(const struct Table* table, const char* value);
|
||||
size_t HashTableSize(const struct Table*);
|
||||
|
||||
bool HashTableIteratorStart(const struct Table*, struct TableIterator*);
|
||||
bool HashTableIteratorNext(const struct Table*, struct TableIterator*);
|
||||
const char* HashTableIteratorGetKey(const struct Table*, const struct TableIterator*);
|
||||
const void* HashTableIteratorGetBinaryKey(const struct Table*, const struct TableIterator*);
|
||||
size_t HashTableIteratorGetBinaryKeyLen(const struct Table*, const struct TableIterator*);
|
||||
void* HashTableIteratorGetCustomKey(const struct Table*, const struct TableIterator*);
|
||||
void* HashTableIteratorGetValue(const struct Table*, const struct TableIterator*);
|
||||
bool HashTableIteratorLookup(const struct Table*, struct TableIterator*, const char* key);
|
||||
bool HashTableIteratorLookupBinary(const struct Table*, struct TableIterator*, const void* key, size_t keylen);
|
||||
bool HashTableIteratorLookupCustom(const struct Table*, struct TableIterator*, void* key);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
||||
|
|
|
@ -140,6 +140,10 @@ struct mCore {
|
|||
size_t (*listMemoryBlocks)(const struct mCore*, const struct mCoreMemoryBlock**);
|
||||
void* (*getMemoryBlock)(struct mCore*, size_t id, size_t* sizeOut);
|
||||
|
||||
size_t (*listRegisters)(const struct mCore*, const struct mCoreRegisterInfo**);
|
||||
bool (*readRegister)(const struct mCore*, const char* name, void* out);
|
||||
bool (*writeRegister)(struct mCore*, const char* name, const void* in);
|
||||
|
||||
#ifdef USE_DEBUGGERS
|
||||
bool (*supportsDebuggerType)(struct mCore*, enum mDebuggerType);
|
||||
struct mDebuggerPlatform* (*debuggerPlatform)(struct mCore*);
|
||||
|
|
|
@ -287,6 +287,21 @@ struct mCoreMemoryBlock {
|
|||
uint32_t segmentStart;
|
||||
};
|
||||
|
||||
enum mCoreRegisterType {
|
||||
mCORE_REGISTER_GPR = 0,
|
||||
mCORE_REGISTER_FPR,
|
||||
mCORE_REGISTER_FLAGS,
|
||||
mCORE_REGISTER_SIMD,
|
||||
};
|
||||
|
||||
struct mCoreRegisterInfo {
|
||||
const char* name;
|
||||
const char** aliases;
|
||||
unsigned width;
|
||||
uint32_t mask;
|
||||
enum mCoreRegisterType type;
|
||||
};
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
||||
|
|
|
@ -118,8 +118,6 @@ struct mDebuggerPlatform {
|
|||
|
||||
void (*trace)(struct mDebuggerPlatform*, char* out, size_t* length);
|
||||
|
||||
bool (*getRegister)(struct mDebuggerPlatform*, const char* name, int32_t* value);
|
||||
bool (*setRegister)(struct mDebuggerPlatform*, const char* name, int32_t value);
|
||||
bool (*lookupIdentifier)(struct mDebuggerPlatform*, const char* name, int32_t* value, int* segment);
|
||||
|
||||
uint32_t (*getStackTraceMode)(struct mDebuggerPlatform*);
|
||||
|
|
|
@ -241,8 +241,6 @@ static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform*);
|
|||
static void ARMDebuggerTrace(struct mDebuggerPlatform*, char* out, size_t* length);
|
||||
static void ARMDebuggerFormatRegisters(struct ARMRegisterFile* regs, char* out, size_t* length);
|
||||
static void ARMDebuggerFrameFormatRegisters(struct mStackFrame* frame, char* out, size_t* length);
|
||||
static bool ARMDebuggerGetRegister(struct mDebuggerPlatform*, const char* name, int32_t* value);
|
||||
static bool ARMDebuggerSetRegister(struct mDebuggerPlatform*, const char* name, int32_t value);
|
||||
static uint32_t ARMDebuggerGetStackTraceMode(struct mDebuggerPlatform*);
|
||||
static void ARMDebuggerSetStackTraceMode(struct mDebuggerPlatform*, uint32_t);
|
||||
static bool ARMDebuggerUpdateStackTrace(struct mDebuggerPlatform* d);
|
||||
|
@ -260,8 +258,6 @@ struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void) {
|
|||
platform->checkBreakpoints = ARMDebuggerCheckBreakpoints;
|
||||
platform->hasBreakpoints = ARMDebuggerHasBreakpoints;
|
||||
platform->trace = ARMDebuggerTrace;
|
||||
platform->getRegister = ARMDebuggerGetRegister;
|
||||
platform->setRegister = ARMDebuggerSetRegister;
|
||||
platform->getStackTraceMode = ARMDebuggerGetStackTraceMode;
|
||||
platform->setStackTraceMode = ARMDebuggerSetStackTraceMode;
|
||||
platform->updateStackTrace = ARMDebuggerUpdateStackTrace;
|
||||
|
@ -510,82 +506,6 @@ static void ARMDebuggerFrameFormatRegisters(struct mStackFrame* frame, char* out
|
|||
ARMDebuggerFormatRegisters(frame->regs, out, length);
|
||||
}
|
||||
|
||||
bool ARMDebuggerGetRegister(struct mDebuggerPlatform* d, const char* name, int32_t* value) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) d;
|
||||
struct ARMCore* cpu = debugger->cpu;
|
||||
|
||||
if (strcmp(name, "sp") == 0) {
|
||||
*value = cpu->gprs[ARM_SP];
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "lr") == 0) {
|
||||
*value = cpu->gprs[ARM_LR];
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "pc") == 0) {
|
||||
*value = cpu->gprs[ARM_PC];
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "cpsr") == 0) {
|
||||
*value = cpu->cpsr.packed;
|
||||
return true;
|
||||
}
|
||||
// TODO: test if mode has SPSR
|
||||
if (strcmp(name, "spsr") == 0) {
|
||||
*value = cpu->spsr.packed;
|
||||
return true;
|
||||
}
|
||||
if (name[0] == 'r') {
|
||||
char* end;
|
||||
uint32_t reg = strtoul(&name[1], &end, 10);
|
||||
if (reg <= ARM_PC) {
|
||||
*value = cpu->gprs[reg];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ARMDebuggerSetRegister(struct mDebuggerPlatform* d, const char* name, int32_t value) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) d;
|
||||
struct ARMCore* cpu = debugger->cpu;
|
||||
|
||||
if (strcmp(name, "sp") == 0) {
|
||||
cpu->gprs[ARM_SP] = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "lr") == 0) {
|
||||
cpu->gprs[ARM_LR] = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "pc") == 0) {
|
||||
cpu->gprs[ARM_PC] = value;
|
||||
if (cpu->executionMode == MODE_ARM) {
|
||||
ARMWritePC(cpu);
|
||||
} else {
|
||||
ThumbWritePC(cpu);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (name[0] == 'r') {
|
||||
char* end;
|
||||
uint32_t reg = strtoul(&name[1], &end, 10);
|
||||
if (reg > ARM_PC) {
|
||||
return false;
|
||||
}
|
||||
cpu->gprs[reg] = value;
|
||||
if (reg == ARM_PC) {
|
||||
if (cpu->executionMode == MODE_ARM) {
|
||||
ARMWritePC(cpu);
|
||||
} else {
|
||||
ThumbWritePC(cpu);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint32_t ARMDebuggerGetStackTraceMode(struct mDebuggerPlatform* d) {
|
||||
struct ARMDebugger* debugger = (struct ARMDebugger*) d;
|
||||
return debugger->stackTraceMode;
|
||||
|
|
|
@ -306,7 +306,7 @@ ATTRIBUTE_NOINLINE static void _neutralS(struct ARMCore* cpu, int32_t d) {
|
|||
SHIFTER(cpu, opcode); \
|
||||
int rd = (opcode >> 12) & 0xF; \
|
||||
int rn = (opcode >> 16) & 0xF; \
|
||||
int32_t n = cpu->gprs[rn]; \
|
||||
int32_t n ATTRIBUTE_UNUSED = cpu->gprs[rn]; \
|
||||
if (UNLIKELY(rn == ARM_PC && (opcode & 0x02000010) == 0x00000010)) { \
|
||||
n += WORD_SIZE_ARM; \
|
||||
} \
|
||||
|
@ -421,7 +421,7 @@ ATTRIBUTE_NOINLINE static void _neutralS(struct ARMCore* cpu, int32_t d) {
|
|||
uint32_t address; \
|
||||
int rn = (opcode >> 16) & 0xF; \
|
||||
int rd = (opcode >> 12) & 0xF; \
|
||||
int32_t d = cpu->gprs[rd]; \
|
||||
int32_t d ATTRIBUTE_UNUSED = cpu->gprs[rd]; \
|
||||
if (UNLIKELY(rd == ARM_PC)) { \
|
||||
d += WORD_SIZE_ARM; \
|
||||
} \
|
||||
|
|
|
@ -452,7 +452,7 @@ static void _writeRegister(struct CLIDebugger* debugger, struct CLIDebugVector*
|
|||
debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS);
|
||||
return;
|
||||
}
|
||||
if (!debugger->d.platform->setRegister(debugger->d.platform, dv->charValue, dv->next->intValue)) {
|
||||
if (!debugger->d.core->writeRegister(debugger->d.core, dv->charValue, &dv->next->intValue)) {
|
||||
debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ bool mDebuggerLookupIdentifier(struct mDebugger* debugger, const char* name, int
|
|||
if (debugger->core->lookupIdentifier(debugger->core, name, value, segment)) {
|
||||
return true;
|
||||
}
|
||||
if (debugger->platform && debugger->platform->getRegister(debugger->platform, name, value)) {
|
||||
if (debugger->platform && debugger->core->readRegister(debugger->core, name, value)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
155
src/gb/core.c
155
src/gb/core.c
|
@ -65,6 +65,23 @@ static const struct mCoreMemoryBlock _GBCMemoryBlocks[] = {
|
|||
{ GB_BASE_HRAM, "hram", "HRAM", "High RAM", GB_BASE_HRAM, GB_BASE_HRAM + GB_SIZE_HRAM, GB_SIZE_HRAM, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED },
|
||||
};
|
||||
|
||||
static const struct mCoreRegisterInfo _GBRegisters[] = {
|
||||
{ "b", NULL, 1, 0xFF, mCORE_REGISTER_GPR },
|
||||
{ "c", NULL, 1, 0xFF, mCORE_REGISTER_GPR },
|
||||
{ "d", NULL, 1, 0xFF, mCORE_REGISTER_GPR },
|
||||
{ "e", NULL, 1, 0xFF, mCORE_REGISTER_GPR },
|
||||
{ "h", NULL, 1, 0xFF, mCORE_REGISTER_GPR },
|
||||
{ "l", NULL, 1, 0xFF, mCORE_REGISTER_GPR },
|
||||
{ "a", NULL, 1, 0xFF, mCORE_REGISTER_GPR },
|
||||
{ "f", NULL, 1, 0xF0, mCORE_REGISTER_GPR },
|
||||
{ "bc", NULL, 2, 0xFFFF, mCORE_REGISTER_GPR },
|
||||
{ "de", NULL, 2, 0xFFFF, mCORE_REGISTER_GPR },
|
||||
{ "hl", NULL, 2, 0xFFFF, mCORE_REGISTER_GPR },
|
||||
{ "af", NULL, 2, 0xFFF0, mCORE_REGISTER_GPR },
|
||||
{ "pc", NULL, 2, 0xFFFF, mCORE_REGISTER_GPR },
|
||||
{ "sp", NULL, 2, 0xFFFF, mCORE_REGISTER_GPR },
|
||||
};
|
||||
|
||||
struct mVideoLogContext;
|
||||
struct GBCore {
|
||||
struct mCore d;
|
||||
|
@ -860,6 +877,141 @@ void* _GBGetMemoryBlock(struct mCore* core, size_t id, size_t* sizeOut) {
|
|||
}
|
||||
}
|
||||
|
||||
static size_t _GBCoreListRegisters(const struct mCore* core, const struct mCoreRegisterInfo** list) {
|
||||
UNUSED(core);
|
||||
*list = _GBRegisters;
|
||||
return sizeof(_GBRegisters) / sizeof(*_GBRegisters);
|
||||
}
|
||||
|
||||
static bool _GBCoreReadRegister(const struct mCore* core, const char* name, void* out) {
|
||||
struct SM83Core* cpu = core->cpu;
|
||||
uint16_t* value16 = out;
|
||||
uint8_t* value8 = out;
|
||||
|
||||
if (strcmp(name, "b") == 0) {
|
||||
*value8 = cpu->b;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "c") == 0) {
|
||||
*value8 = cpu->c;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "d") == 0) {
|
||||
*value8 = cpu->d;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "e") == 0) {
|
||||
*value8 = cpu->e;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "a") == 0) {
|
||||
*value8 = cpu->a;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "f") == 0) {
|
||||
*value8 = cpu->f.packed;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "h") == 0) {
|
||||
*value8 = cpu->h;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "l") == 0) {
|
||||
*value8 = cpu->l;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "bc") == 0) {
|
||||
*value16 = cpu->bc;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "de") == 0) {
|
||||
*value16 = cpu->de;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "hl") == 0) {
|
||||
*value16 = cpu->hl;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "af") == 0) {
|
||||
*value16 = cpu->af;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "pc") == 0) {
|
||||
*value16 = cpu->pc;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "sp") == 0) {
|
||||
*value16 = cpu->sp;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _GBCoreWriteRegister(struct mCore* core, const char* name, const void* in) {
|
||||
struct SM83Core* cpu = core->cpu;
|
||||
uint32_t value = *(uint32_t*) in;
|
||||
|
||||
if (strcmp(name, "b") == 0) {
|
||||
cpu->b = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "c") == 0) {
|
||||
cpu->c = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "d") == 0) {
|
||||
cpu->d = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "e") == 0) {
|
||||
cpu->e = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "h") == 0) {
|
||||
cpu->h = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "l") == 0) {
|
||||
cpu->l = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "a") == 0) {
|
||||
cpu->a = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "f") == 0) {
|
||||
cpu->f.packed = value & 0xF0;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "bc") == 0) {
|
||||
cpu->bc = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "de") == 0) {
|
||||
cpu->de = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "hl") == 0) {
|
||||
cpu->hl = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "af") == 0) {
|
||||
cpu->af = value;
|
||||
cpu->f.packed &= 0xF0;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "pc") == 0) {
|
||||
cpu->pc = value;
|
||||
cpu->memory.setActiveRegion(cpu, cpu->pc);
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "sp") == 0) {
|
||||
cpu->sp = value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_DEBUGGERS
|
||||
static bool _GBCoreSupportsDebuggerType(struct mCore* core, enum mDebuggerType type) {
|
||||
UNUSED(core);
|
||||
|
@ -1142,6 +1294,9 @@ struct mCore* GBCoreCreate(void) {
|
|||
core->rawWrite32 = _GBCoreRawWrite32;
|
||||
core->listMemoryBlocks = _GBListMemoryBlocks;
|
||||
core->getMemoryBlock = _GBGetMemoryBlock;
|
||||
core->listRegisters = _GBCoreListRegisters;
|
||||
core->readRegister = _GBCoreReadRegister;
|
||||
core->writeRegister = _GBCoreWriteRegister;
|
||||
#ifdef USE_DEBUGGERS
|
||||
core->supportsDebuggerType = _GBCoreSupportsDebuggerType;
|
||||
core->debuggerPlatform = _GBCoreDebuggerPlatform;
|
||||
|
|
170
src/gba/core.c
170
src/gba/core.c
|
@ -8,6 +8,7 @@
|
|||
#include <mgba/core/core.h>
|
||||
#include <mgba/core/log.h>
|
||||
#include <mgba/internal/arm/debugger/debugger.h>
|
||||
#include <mgba/internal/arm/isa-inlines.h>
|
||||
#include <mgba/internal/debugger/symbols.h>
|
||||
#include <mgba/internal/gba/cheats.h>
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
|
@ -32,6 +33,7 @@
|
|||
#include <mgba-util/memory.h>
|
||||
#include <mgba-util/patch.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifndef MINIMAL_CORE
|
||||
#include <mgba/internal/gba/input.h>
|
||||
|
@ -131,6 +133,32 @@ static const struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = {
|
|||
{ REGION_CART_SRAM_MIRROR, "eeprom", "EEPROM", "EEPROM (8kiB)", 0, SIZE_CART_EEPROM, SIZE_CART_EEPROM, mCORE_MEMORY_RW },
|
||||
};
|
||||
|
||||
static const struct mCoreRegisterInfo _GBARegisters[] = {
|
||||
{ "r0", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "r1", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "r2", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "r3", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "r4", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "r5", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "r6", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "r7", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "r8", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "r9", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "r10", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "r11", NULL, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "r12", (const char*[]) { "ip", NULL }, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "sp", (const char*[]) { "r13", NULL }, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "lr", (const char*[]) { "r14", NULL }, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "pc", (const char*[]) { "r15", NULL }, 4, 0xFFFFFFFF, mCORE_REGISTER_GPR },
|
||||
{ "cpsr", NULL, 4, 0xF00000FF, mCORE_REGISTER_FLAGS },
|
||||
{ "spsr", NULL, 4, 0xF00000FF, mCORE_REGISTER_FLAGS },
|
||||
{ "spsr_irq", NULL, 4, 0xF00000FF, mCORE_REGISTER_FLAGS },
|
||||
{ "spsr_fiq", NULL, 4, 0xF00000FF, mCORE_REGISTER_FLAGS },
|
||||
{ "spsr_svc", NULL, 4, 0xF00000FF, mCORE_REGISTER_FLAGS },
|
||||
{ "spsr_abt", NULL, 4, 0xF00000FF, mCORE_REGISTER_FLAGS },
|
||||
{ "spsr_und", NULL, 4, 0xF00000FF, mCORE_REGISTER_FLAGS },
|
||||
};
|
||||
|
||||
struct mVideoLogContext;
|
||||
|
||||
#define CPU_COMPONENT_AUDIO_MIXER CPU_COMPONENT_MISC_1
|
||||
|
@ -861,7 +889,7 @@ static void _GBACoreRawWrite32(struct mCore* core, uint32_t address, int segment
|
|||
GBAPatch32(cpu, address, value, NULL);
|
||||
}
|
||||
|
||||
size_t _GBAListMemoryBlocks(const struct mCore* core, const struct mCoreMemoryBlock** blocks) {
|
||||
size_t _GBACoreListMemoryBlocks(const struct mCore* core, const struct mCoreMemoryBlock** blocks) {
|
||||
const struct GBA* gba = core->board;
|
||||
switch (gba->memory.savedata.type) {
|
||||
case SAVEDATA_SRAM:
|
||||
|
@ -882,7 +910,7 @@ size_t _GBAListMemoryBlocks(const struct mCore* core, const struct mCoreMemoryBl
|
|||
}
|
||||
}
|
||||
|
||||
void* _GBAGetMemoryBlock(struct mCore* core, size_t id, size_t* sizeOut) {
|
||||
void* _GBACoreGetMemoryBlock(struct mCore* core, size_t id, size_t* sizeOut) {
|
||||
struct GBA* gba = core->board;
|
||||
switch (id) {
|
||||
default:
|
||||
|
@ -922,6 +950,137 @@ void* _GBAGetMemoryBlock(struct mCore* core, size_t id, size_t* sizeOut) {
|
|||
}
|
||||
}
|
||||
|
||||
static size_t _GBACoreListRegisters(const struct mCore* core, const struct mCoreRegisterInfo** list) {
|
||||
UNUSED(core);
|
||||
*list = _GBARegisters;
|
||||
return sizeof(_GBARegisters) / sizeof(*_GBARegisters);
|
||||
}
|
||||
|
||||
static bool _GBACoreReadRegister(const struct mCore* core, const char* name, void* out) {
|
||||
struct ARMCore* cpu = core->cpu;
|
||||
int32_t* value = out;
|
||||
switch (name[0]) {
|
||||
case 'r':
|
||||
case 'R':
|
||||
++name;
|
||||
break;
|
||||
case 'c':
|
||||
case 'C':
|
||||
if (strcmp(name, "cpsr") == 0 || strcmp(name, "CPSR") == 0) {
|
||||
*value = cpu->cpsr.packed;
|
||||
_ARMReadCPSR(cpu);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case 'i':
|
||||
case 'I':
|
||||
if (strcmp(name, "ip") == 0 || strcmp(name, "IP") == 0) {
|
||||
*value = cpu->gprs[12];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case 's':
|
||||
case 'S':
|
||||
if (strcmp(name, "sp") == 0 || strcmp(name, "SP") == 0) {
|
||||
*value = cpu->gprs[ARM_SP];
|
||||
return true;
|
||||
}
|
||||
// TODO: SPSR
|
||||
return false;
|
||||
case 'l':
|
||||
case 'L':
|
||||
if (strcmp(name, "lr") == 0 || strcmp(name, "LR") == 0) {
|
||||
*value = cpu->gprs[ARM_LR];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case 'p':
|
||||
case 'P':
|
||||
if (strcmp(name, "pc") == 0 || strcmp(name, "PC") == 0) {
|
||||
*value = cpu->gprs[ARM_PC];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
char* parseEnd;
|
||||
errno = 0;
|
||||
unsigned long regId = strtoul(name, &parseEnd, 10);
|
||||
if (errno || regId > 15 || *parseEnd) {
|
||||
return false;
|
||||
}
|
||||
*value = cpu->gprs[regId];
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _GBACoreWriteRegister(struct mCore* core, const char* name, const void* in) {
|
||||
struct ARMCore* cpu = core->cpu;
|
||||
int32_t value = *(const int32_t*) in;
|
||||
switch (name[0]) {
|
||||
case 'r':
|
||||
case 'R':
|
||||
++name;
|
||||
break;
|
||||
case 'c':
|
||||
case 'C':
|
||||
if (strcmp(name, "cpsr") == 0) {
|
||||
cpu->cpsr.packed = value & 0xF00000FF;
|
||||
_ARMReadCPSR(cpu);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case 'i':
|
||||
case 'I':
|
||||
if (strcmp(name, "ip") == 0 || strcmp(name, "IP") == 0) {
|
||||
cpu->gprs[12] = value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case 's':
|
||||
case 'S':
|
||||
if (strcmp(name, "sp") == 0 || strcmp(name, "SP") == 0) {
|
||||
cpu->gprs[ARM_SP] = value;
|
||||
return true;
|
||||
}
|
||||
// TODO: SPSR
|
||||
return false;
|
||||
case 'l':
|
||||
case 'L':
|
||||
if (strcmp(name, "lr") == 0 || strcmp(name, "LR") == 0) {
|
||||
cpu->gprs[ARM_LR] = value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case 'p':
|
||||
case 'P':
|
||||
if (strcmp(name, "pc") == 0 || strcmp(name, "PC") == 0) {
|
||||
name = "15";
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
char* parseEnd;
|
||||
errno = 0;
|
||||
unsigned long regId = strtoul(name, &parseEnd, 10);
|
||||
if (errno || regId > 15 || *parseEnd) {
|
||||
return false;
|
||||
}
|
||||
cpu->gprs[regId] = value;
|
||||
if (regId == ARM_PC) {
|
||||
if (cpu->cpsr.t) {
|
||||
ThumbWritePC(cpu);
|
||||
} else {
|
||||
ARMWritePC(cpu);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef USE_DEBUGGERS
|
||||
static bool _GBACoreSupportsDebuggerType(struct mCore* core, enum mDebuggerType type) {
|
||||
UNUSED(core);
|
||||
|
@ -1257,8 +1416,11 @@ struct mCore* GBACoreCreate(void) {
|
|||
core->rawWrite8 = _GBACoreRawWrite8;
|
||||
core->rawWrite16 = _GBACoreRawWrite16;
|
||||
core->rawWrite32 = _GBACoreRawWrite32;
|
||||
core->listMemoryBlocks = _GBAListMemoryBlocks;
|
||||
core->getMemoryBlock = _GBAGetMemoryBlock;
|
||||
core->listMemoryBlocks = _GBACoreListMemoryBlocks;
|
||||
core->getMemoryBlock = _GBACoreGetMemoryBlock;
|
||||
core->listRegisters = _GBACoreListRegisters;
|
||||
core->readRegister = _GBACoreReadRegister;
|
||||
core->writeRegister = _GBACoreWriteRegister;
|
||||
#ifdef USE_DEBUGGERS
|
||||
core->supportsDebuggerType = _GBACoreSupportsDebuggerType;
|
||||
core->debuggerPlatform = _GBACoreDebuggerPlatform;
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
#include <mgba/internal/gba/memory.h>
|
||||
|
||||
const uint8_t hleBios[SIZE_BIOS] = {
|
||||
0x06, 0x00, 0x00, 0xea, 0x66, 0x00, 0x00, 0xea, 0x0c, 0x00, 0x00, 0xea,
|
||||
0xd3, 0x00, 0x00, 0xea, 0x66, 0x00, 0x00, 0xea, 0x0c, 0x00, 0x00, 0xea,
|
||||
0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe1,
|
||||
0x59, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x58, 0x01, 0x9f, 0xe5,
|
||||
0x00, 0x10, 0x90, 0xe5, 0x00, 0x00, 0x51, 0xe3, 0x02, 0x03, 0xa0, 0x03,
|
||||
0x10, 0xff, 0x2f, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1,
|
||||
0x59, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xe3, 0x01, 0xd3, 0xa0, 0x03,
|
||||
0x20, 0xd0, 0x4d, 0x02, 0x00, 0x58, 0x2d, 0xe9, 0x02, 0xc0, 0x5e, 0xe5,
|
||||
0xd4, 0xb0, 0xa0, 0xe3, 0x0c, 0xc1, 0x9b, 0xe7, 0xd2, 0xbf, 0xa0, 0xe3,
|
||||
|
@ -35,7 +35,7 @@ const uint8_t hleBios[SIZE_BIOS] = {
|
|||
0xb0, 0x01, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00,
|
||||
0xb0, 0x01, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00,
|
||||
0xb0, 0x01, 0x00, 0x00, 0xb0, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00,
|
||||
0xc0, 0x00, 0x00, 0x02, 0x0f, 0x50, 0x2d, 0xe9, 0x01, 0x03, 0xa0, 0xe3,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0f, 0x50, 0x2d, 0xe9, 0x01, 0x03, 0xa0, 0xe3,
|
||||
0x00, 0xe0, 0x8f, 0xe2, 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8,
|
||||
0x04, 0xf0, 0x5e, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x02, 0xc0, 0x5e, 0xe5,
|
||||
0x04, 0xf0, 0x5e, 0xe2, 0x00, 0x00, 0x00, 0x00, 0x04, 0xe0, 0xa0, 0x03,
|
||||
|
@ -73,5 +73,10 @@ const uint8_t hleBios[SIZE_BIOS] = {
|
|||
0x01, 0xa0, 0xa0, 0xe1, 0xfa, 0x07, 0xa0, 0xe8, 0xfa, 0x07, 0xa0, 0xe8,
|
||||
0xfa, 0x07, 0xa0, 0xe8, 0xfa, 0x07, 0xa0, 0xe8, 0x00, 0x10, 0xa0, 0xe3,
|
||||
0xf0, 0x07, 0xbd, 0xe8, 0x1e, 0xff, 0x2f, 0xe1, 0xb0, 0x01, 0x00, 0x00,
|
||||
0x04, 0xb0, 0x5b, 0xe2, 0xfd, 0xff, 0xff, 0x8a, 0x1e, 0xff, 0x2f, 0xe1
|
||||
0x04, 0xb0, 0x5b, 0xe2, 0xfd, 0xff, 0xff, 0x8a, 0x1e, 0xff, 0x2f, 0xe1,
|
||||
0xc2, 0x03, 0xa0, 0xe3, 0x03, 0x10, 0x50, 0xe4, 0x00, 0x00, 0x51, 0xe3,
|
||||
0x00, 0x10, 0xa0, 0x13, 0x10, 0xff, 0x2f, 0x11, 0x1c, 0x00, 0x9f, 0xe5,
|
||||
0x00, 0x10, 0x90, 0xe5, 0x00, 0x00, 0x51, 0xe3, 0x00, 0x10, 0xa0, 0xe3,
|
||||
0x10, 0xff, 0x2f, 0x11, 0xc0, 0x00, 0x40, 0xe2, 0x10, 0xff, 0x2f, 0xe1,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x29, 0xe1, 0xc0, 0x00, 0x00, 0x02
|
||||
};
|
||||
|
|
|
@ -16,16 +16,14 @@ nop
|
|||
b irqBase
|
||||
b fiqBase
|
||||
|
||||
resetBase:
|
||||
ldr r0, =0x20000C0
|
||||
ldr r1, [r0]
|
||||
cmp r1, #0
|
||||
moveq r0, #0x8000000
|
||||
bx r0
|
||||
.word 0
|
||||
.word 0xE129F000
|
||||
|
||||
.word 0 @ Padding for back-compat
|
||||
.word 0
|
||||
.word 0
|
||||
.word 0
|
||||
.word 0
|
||||
.word 0
|
||||
.word 0
|
||||
.word 0
|
||||
|
||||
swiBase:
|
||||
cmp sp, #0
|
||||
|
@ -113,6 +111,7 @@ swiTable:
|
|||
.word SoundDriverGetJumpList @ 0x2A
|
||||
|
||||
.ltorg
|
||||
.word 0 @ Padding for back-compat
|
||||
|
||||
irqBase:
|
||||
stmfd sp!, {r0-r3, r12, lr}
|
||||
|
@ -313,3 +312,19 @@ StallCall:
|
|||
subs r11, #4
|
||||
bhi StallCall
|
||||
bx lr
|
||||
|
||||
resetBase:
|
||||
mov r0, #0x8000003
|
||||
ldrb r1, [r0], #-3
|
||||
cmp r1, #0
|
||||
movne r1, #0
|
||||
bxne r0
|
||||
ldr r0, =0x20000C0
|
||||
ldr r1, [r0]
|
||||
cmp r1, #0
|
||||
mov r1, #0
|
||||
bxne r0
|
||||
sub r0, #0xC0
|
||||
bx r0
|
||||
.word 0
|
||||
.word 0xE129F000
|
||||
|
|
|
@ -934,6 +934,11 @@ void GBAVideoGLRendererReset(struct GBAVideoRenderer* renderer) {
|
|||
bg->offsetY = 0;
|
||||
}
|
||||
|
||||
glRenderer->winN[0].offsetX = 0;
|
||||
glRenderer->winN[0].offsetY = 0;
|
||||
glRenderer->winN[1].offsetX = 0;
|
||||
glRenderer->winN[1].offsetY = 0;
|
||||
|
||||
for (i = 0; i < 512; ++i) {
|
||||
int r = M_R5(glRenderer->d.palette[i]);
|
||||
int g = M_G5(glRenderer->d.palette[i]) << 1;
|
||||
|
|
|
@ -7,22 +7,57 @@ function(find_feature FEATURE_NAME FEATURE_REQUIRES)
|
|||
set(${FEATURE_NAME} OFF PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
if(ARGV2)
|
||||
set(VERSION "${ARGV2}")
|
||||
set(FIND_VERSION "${VERSION}" EXACT)
|
||||
set(PKG_CONFIG_VERSION_CHECK " >=${VERSION}")
|
||||
else()
|
||||
set(VERSION)
|
||||
set(FIND_VERSION)
|
||||
set(PKG_CONFIG_VERSION_CHECK)
|
||||
endif()
|
||||
foreach(NAMES ${FEATURE_REQUIRES})
|
||||
string(REPLACE "|" ";" NAMELIST "${NAMES}")
|
||||
set(FOUND OFF)
|
||||
foreach(REQUIRE ${NAMELIST})
|
||||
if(NOT ${REQUIRE}_FOUND)
|
||||
find_package(${REQUIRE} QUIET)
|
||||
find_package(${REQUIRE} ${FIND_VERSION} QUIET)
|
||||
if(NOT ${REQUIRE}_FOUND)
|
||||
pkg_search_module(${REQUIRE} ${REQUIRE})
|
||||
pkg_search_module(${REQUIRE} "${REQUIRE}${PKG_CONFIG_VERSION_CHECK}")
|
||||
endif()
|
||||
endif()
|
||||
if(${REQUIRE}_FOUND)
|
||||
string(TOUPPER ${REQUIRE} UREQUIRE)
|
||||
set(${UREQUIRE}_CFLAGS_OTHER ${${REQUIRE}_CFLAGS_OTHER} PARENT_SCOPE)
|
||||
set(${UREQUIRE}_FOUND ${${REQUIRE}_FOUND} PARENT_SCOPE)
|
||||
set(${UREQUIRE}_INCLUDE_DIRS ${${REQUIRE}_INCLUDE_DIRS} PARENT_SCOPE)
|
||||
set(${UREQUIRE}_VERSION_STRING ${${REQUIRE}_VERSION_STRING} PARENT_SCOPE)
|
||||
if(DEFINED ${REQUIRE}_CFLAGS_OTHER)
|
||||
set(${UREQUIRE}_CFLAGS_OTHER ${${REQUIRE}_CFLAGS_OTHER} PARENT_SCOPE)
|
||||
elseif(DEFINED ${UREQUIRE}_CFLAGS_OTHER)
|
||||
set(${UREQUIRE}_CFLAGS_OTHER ${${UREQUIRE}_CFLAGS_OTHER} PARENT_SCOPE)
|
||||
endif()
|
||||
if(DEFINED ${REQUIRE}_FOUND)
|
||||
set(${UREQUIRE}_FOUND ${${REQUIRE}_FOUND} PARENT_SCOPE)
|
||||
elseif(DEFINED ${UREQUIRE}_FOUND)
|
||||
set(${UREQUIRE}_FOUND ${${UREQUIRE}_FOUND} PARENT_SCOPE)
|
||||
endif()
|
||||
if(DEFINED ${REQUIRE}_INCLUDE_DIRS)
|
||||
set(${UREQUIRE}_INCLUDE_DIRS ${${REQUIRE}_INCLUDE_DIRS} PARENT_SCOPE)
|
||||
elseif(DEFINED ${UREQUIRE}_INCLUDE_DIRS)
|
||||
set(${UREQUIRE}_INCLUDE_DIRS ${${UREQUIRE}_INCLUDE_DIRS} PARENT_SCOPE)
|
||||
endif()
|
||||
if(DEFINED ${REQUIRE}_VERSION_STRING)
|
||||
set(${UREQUIRE}_VERSION_STRING ${${REQUIRE}_VERSION_STRING} PARENT_SCOPE)
|
||||
elseif(DEFINED ${UREQUIRE}_VERSION_STRING)
|
||||
set(${UREQUIRE}_VERSION_STRING ${${UREQUIRE}_VERSION_STRING} PARENT_SCOPE)
|
||||
endif()
|
||||
if(DEFINED ${REQUIRE}_VERSION_MAJOR)
|
||||
set(${UREQUIRE}_VERSION_MAJOR ${${REQUIRE}_VERSION_MAJOR} PARENT_SCOPE)
|
||||
elseif(DEFINED ${UREQUIRE}_VERSION_MAJOR)
|
||||
set(${UREQUIRE}_VERSION_MAJOR ${${UREQUIRE}_VERSION_MAJOR} PARENT_SCOPE)
|
||||
endif()
|
||||
if(DEFINED ${REQUIRE}_VERSION_MINOR)
|
||||
set(${UREQUIRE}_VERSION_MINOR ${${REQUIRE}_VERSION_MINOR} PARENT_SCOPE)
|
||||
elseif(DEFINED ${UREQUIRE}_VERSION_MINOR)
|
||||
set(${UREQUIRE}_VERSION_MINOR ${${UREQUIRE}_VERSION_MINOR} PARENT_SCOPE)
|
||||
endif()
|
||||
if (APPLE)
|
||||
set(IS_FRAMEWORK OFF)
|
||||
set(LIBS)
|
||||
|
|
|
@ -107,6 +107,7 @@ set(SOURCE_FILES
|
|||
LogController.cpp
|
||||
LogConfigModel.cpp
|
||||
LogView.cpp
|
||||
LogWidget.cpp
|
||||
MapView.cpp
|
||||
MemoryDump.cpp
|
||||
MemoryModel.cpp
|
||||
|
|
|
@ -858,6 +858,15 @@ void CoreController::yankPak() {
|
|||
}
|
||||
}
|
||||
|
||||
void CoreController::addKey(int key) {
|
||||
m_activeKeys |= 1 << key;
|
||||
}
|
||||
|
||||
void CoreController::clearKey(int key) {
|
||||
m_activeKeys &= ~(1 << key);
|
||||
m_removedKeys |= 1 << key;
|
||||
}
|
||||
|
||||
#ifdef USE_PNG
|
||||
void CoreController::screenshot() {
|
||||
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
|
||||
|
@ -1133,7 +1142,10 @@ void CoreController::setFramebufferHandle(int fb) {
|
|||
}
|
||||
|
||||
void CoreController::updateKeys() {
|
||||
int activeKeys = m_inputController->updateAutofire() | m_inputController->pollEvents();
|
||||
int polledKeys = m_inputController->pollEvents() | m_inputController->updateAutofire();
|
||||
int activeKeys = m_activeKeys | polledKeys;
|
||||
activeKeys |= m_threadContext.core->getKeys(m_threadContext.core) & ~m_removedKeys;
|
||||
m_removedKeys = polledKeys;
|
||||
m_threadContext.core->setKeys(m_threadContext.core, activeKeys);
|
||||
}
|
||||
|
||||
|
|
|
@ -261,6 +261,7 @@ private:
|
|||
QMutex m_bufferMutex;
|
||||
|
||||
int m_activeKeys = 0;
|
||||
int m_removedKeys = 0;
|
||||
bool m_autofire[32] = {};
|
||||
int m_autofireStatus[32] = {};
|
||||
int m_autofireThreshold = 1;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#include "DebuggerConsoleController.h"
|
||||
#include "GBAApp.h"
|
||||
|
||||
#include <QScrollBar>
|
||||
#include <QKeyEvent>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
|
@ -19,23 +19,16 @@ DebuggerConsole::DebuggerConsole(DebuggerConsoleController* controller, QWidget*
|
|||
m_ui.setupUi(this);
|
||||
|
||||
m_ui.prompt->installEventFilter(this);
|
||||
m_ui.log->setFont(GBAApp::app()->monospaceFont());
|
||||
m_ui.prompt->setFont(GBAApp::app()->monospaceFont());
|
||||
|
||||
connect(m_ui.prompt, &QLineEdit::returnPressed, this, &DebuggerConsole::postLine);
|
||||
connect(controller, &DebuggerConsoleController::log, this, &DebuggerConsole::log);
|
||||
connect(controller, &DebuggerConsoleController::log, m_ui.log, &LogWidget::log);
|
||||
connect(m_ui.breakpoint, &QAbstractButton::clicked, controller, &DebuggerController::attach);
|
||||
connect(m_ui.breakpoint, &QAbstractButton::clicked, controller, &DebuggerController::breakInto);
|
||||
|
||||
controller->historyLoad();
|
||||
}
|
||||
|
||||
void DebuggerConsole::log(const QString& line) {
|
||||
m_ui.log->moveCursor(QTextCursor::End);
|
||||
m_ui.log->insertPlainText(line);
|
||||
m_ui.log->verticalScrollBar()->setValue(m_ui.log->verticalScrollBar()->maximum());
|
||||
}
|
||||
|
||||
void DebuggerConsole::postLine() {
|
||||
m_consoleController->attach();
|
||||
QString line = m_ui.prompt->text();
|
||||
|
@ -44,7 +37,7 @@ void DebuggerConsole::postLine() {
|
|||
m_consoleController->enterLine(QString("\n"));
|
||||
} else {
|
||||
m_historyOffset = 0;
|
||||
log(QString("> %1\n").arg(line));
|
||||
m_ui.log->log(QString("> %1\n").arg(line));
|
||||
m_consoleController->enterLine(line);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ public:
|
|||
DebuggerConsole(DebuggerConsoleController* controller, QWidget* parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void log(const QString&);
|
||||
void postLine();
|
||||
|
||||
protected:
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QPlainTextEdit" name="log">
|
||||
<widget class="QGBA::LogWidget" name="log">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
@ -37,6 +37,13 @@
|
|||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QGBA::LogWidget</class>
|
||||
<extends>QPlainTextEdit</extends>
|
||||
<header>LogWidget.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "LogWidget.h"
|
||||
|
||||
#include "GBAApp.h"
|
||||
|
||||
#include <QScrollBar>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
LogWidget::LogWidget(QWidget* parent)
|
||||
: QTextEdit(parent)
|
||||
{
|
||||
setFont(GBAApp::app()->monospaceFont());
|
||||
}
|
||||
|
||||
void LogWidget::log(const QString& line) {
|
||||
moveCursor(QTextCursor::End);
|
||||
insertPlainText(line);
|
||||
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include <QTextEdit>
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class LogWidget : public QTextEdit {
|
||||
public:
|
||||
LogWidget(QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void log(const QString&);
|
||||
};
|
||||
|
||||
}
|
|
@ -72,8 +72,6 @@ static void SM83DebuggerListWatchpoints(struct mDebuggerPlatform*, struct mWatch
|
|||
static void SM83DebuggerCheckBreakpoints(struct mDebuggerPlatform*);
|
||||
static bool SM83DebuggerHasBreakpoints(struct mDebuggerPlatform*);
|
||||
static void SM83DebuggerTrace(struct mDebuggerPlatform*, char* out, size_t* length);
|
||||
static bool SM83DebuggerGetRegister(struct mDebuggerPlatform*, const char* name, int32_t* value);
|
||||
static bool SM83DebuggerSetRegister(struct mDebuggerPlatform*, const char* name, int32_t value);
|
||||
|
||||
struct mDebuggerPlatform* SM83DebuggerPlatformCreate(void) {
|
||||
struct SM83Debugger* platform = malloc(sizeof(struct SM83Debugger));
|
||||
|
@ -88,8 +86,6 @@ struct mDebuggerPlatform* SM83DebuggerPlatformCreate(void) {
|
|||
platform->d.checkBreakpoints = SM83DebuggerCheckBreakpoints;
|
||||
platform->d.hasBreakpoints = SM83DebuggerHasBreakpoints;
|
||||
platform->d.trace = SM83DebuggerTrace;
|
||||
platform->d.getRegister = SM83DebuggerGetRegister;
|
||||
platform->d.setRegister = SM83DebuggerSetRegister;
|
||||
platform->d.getStackTraceMode = NULL;
|
||||
platform->d.setStackTraceMode = NULL;
|
||||
platform->d.updateStackTrace = NULL;
|
||||
|
@ -227,131 +223,3 @@ static void SM83DebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* le
|
|||
cpu->d, cpu->e, cpu->h, cpu->l,
|
||||
cpu->sp, cpu->memory.currentSegment(cpu, cpu->pc), cpu->pc, disassembly);
|
||||
}
|
||||
|
||||
bool SM83DebuggerGetRegister(struct mDebuggerPlatform* d, const char* name, int32_t* value) {
|
||||
struct SM83Debugger* debugger = (struct SM83Debugger*) d;
|
||||
struct SM83Core* cpu = debugger->cpu;
|
||||
|
||||
if (strcmp(name, "a") == 0) {
|
||||
*value = cpu->a;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "b") == 0) {
|
||||
*value = cpu->b;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "c") == 0) {
|
||||
*value = cpu->c;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "d") == 0) {
|
||||
*value = cpu->d;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "e") == 0) {
|
||||
*value = cpu->e;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "h") == 0) {
|
||||
*value = cpu->h;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "l") == 0) {
|
||||
*value = cpu->l;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "bc") == 0) {
|
||||
*value = cpu->bc;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "de") == 0) {
|
||||
*value = cpu->de;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "hl") == 0) {
|
||||
*value = cpu->hl;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "af") == 0) {
|
||||
*value = cpu->af;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "pc") == 0) {
|
||||
*value = cpu->pc;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "sp") == 0) {
|
||||
*value = cpu->sp;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "f") == 0) {
|
||||
*value = cpu->f.packed;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SM83DebuggerSetRegister(struct mDebuggerPlatform* d, const char* name, int32_t value) {
|
||||
struct SM83Debugger* debugger = (struct SM83Debugger*) d;
|
||||
struct SM83Core* cpu = debugger->cpu;
|
||||
|
||||
if (strcmp(name, "a") == 0) {
|
||||
cpu->a = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "b") == 0) {
|
||||
cpu->b = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "c") == 0) {
|
||||
cpu->c = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "d") == 0) {
|
||||
cpu->d = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "e") == 0) {
|
||||
cpu->e = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "h") == 0) {
|
||||
cpu->h = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "l") == 0) {
|
||||
cpu->l = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "bc") == 0) {
|
||||
cpu->bc = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "de") == 0) {
|
||||
cpu->de = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "hl") == 0) {
|
||||
cpu->hl = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "af") == 0) {
|
||||
cpu->af = value;
|
||||
cpu->f.packed &= 0xF0;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "pc") == 0) {
|
||||
cpu->pc = value;
|
||||
cpu->memory.setActiveRegion(cpu, cpu->pc);
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "sp") == 0) {
|
||||
cpu->sp = value;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "f") == 0) {
|
||||
cpu->f.packed = value & 0xF0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ set(GUI_FILES
|
|||
set(TEST_FILES
|
||||
test/string-parser.c
|
||||
test/string-utf8.c
|
||||
test/table.c
|
||||
test/text-codec.c
|
||||
test/vfs.c)
|
||||
|
||||
|
|
113
src/util/hash.c
113
src/util/hash.c
|
@ -15,11 +15,11 @@
|
|||
|
||||
#define FORCE_INLINE inline __attribute__((always_inline))
|
||||
|
||||
static inline uint32_t rotl32 ( uint32_t x, int8_t r ) {
|
||||
return (x << r) | (x >> (32 - r));
|
||||
static inline uint32_t rotl32(uint32_t x, int8_t r) {
|
||||
return (x << r) | (x >> (32 - r));
|
||||
}
|
||||
|
||||
#define ROTL32(x,y) rotl32(x,y)
|
||||
#define ROTL32(x, y) rotl32(x, y)
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -27,81 +27,82 @@ static inline uint32_t rotl32 ( uint32_t x, int8_t r ) {
|
|||
// Block read - if your platform needs to do endian-swapping or can only
|
||||
// handle aligned reads, do the conversion here
|
||||
|
||||
static FORCE_INLINE uint32_t getblock32 ( const uint32_t * p, int i ) {
|
||||
uint32_t ret;
|
||||
LOAD_32LE(ret, i << 2, p);
|
||||
return ret;
|
||||
static FORCE_INLINE uint32_t getblock32(const uint32_t* p, ssize_t i) {
|
||||
uint32_t ret;
|
||||
LOAD_32LE(ret, i << 2, p);
|
||||
return ret;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Finalization mix - force all bits of a hash block to avalanche
|
||||
|
||||
static FORCE_INLINE uint32_t fmix32 (uint32_t h) {
|
||||
h ^= h >> 16;
|
||||
h *= 0x85ebca6b;
|
||||
h ^= h >> 13;
|
||||
h *= 0xc2b2ae35;
|
||||
h ^= h >> 16;
|
||||
static FORCE_INLINE uint32_t fmix32(uint32_t h) {
|
||||
h ^= h >> 16;
|
||||
h *= 0x85ebca6b;
|
||||
h ^= h >> 13;
|
||||
h *= 0xc2b2ae35;
|
||||
h ^= h >> 16;
|
||||
|
||||
return h;
|
||||
return h;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
uint32_t hash32(const void* key, int len, uint32_t seed) {
|
||||
const uint8_t * data = (const uint8_t*)key;
|
||||
const int nblocks = len / 4;
|
||||
uint32_t hash32(const void* key, size_t len, uint32_t seed) {
|
||||
const uint8_t* data = (const uint8_t*) key;
|
||||
const int nblocks = len / 4;
|
||||
|
||||
uint32_t h1 = seed;
|
||||
uint32_t h1 = seed;
|
||||
|
||||
const uint32_t c1 = 0xcc9e2d51;
|
||||
const uint32_t c2 = 0x1b873593;
|
||||
const uint32_t c1 = 0xcc9e2d51;
|
||||
const uint32_t c2 = 0x1b873593;
|
||||
|
||||
//----------
|
||||
// body
|
||||
//----------
|
||||
// body
|
||||
|
||||
const uint32_t * blocks = (const uint32_t *)(data + nblocks*4);
|
||||
const uint32_t* blocks = (const uint32_t*)(data + nblocks * 4);
|
||||
|
||||
int i;
|
||||
for(i = -nblocks; i; i++)
|
||||
{
|
||||
uint32_t k1 = getblock32(blocks,i);
|
||||
int i;
|
||||
for (i = -nblocks; i; i++) {
|
||||
uint32_t k1 = getblock32(blocks, i);
|
||||
|
||||
k1 *= c1;
|
||||
k1 = ROTL32(k1,15);
|
||||
k1 *= c2;
|
||||
|
||||
h1 ^= k1;
|
||||
h1 = ROTL32(h1,13);
|
||||
h1 = h1*5+0xe6546b64;
|
||||
}
|
||||
k1 *= c1;
|
||||
k1 = ROTL32(k1, 15);
|
||||
k1 *= c2;
|
||||
|
||||
h1 ^= k1;
|
||||
h1 = ROTL32(h1, 13);
|
||||
h1 = h1 * 5 + 0xe6546b64;
|
||||
}
|
||||
|
||||
//----------
|
||||
// tail
|
||||
//----------
|
||||
// tail
|
||||
|
||||
const uint8_t * tail = (const uint8_t*)(data + nblocks*4);
|
||||
const uint8_t* tail = (const uint8_t*)(data + nblocks * 4);
|
||||
|
||||
uint32_t k1 = 0;
|
||||
uint32_t k1 = 0;
|
||||
|
||||
switch(len & 3)
|
||||
{
|
||||
case 3:
|
||||
k1 ^= tail[2] << 16;
|
||||
// Fall through
|
||||
case 2:
|
||||
k1 ^= tail[1] << 8;
|
||||
// Fall through
|
||||
case 1:
|
||||
k1 ^= tail[0];
|
||||
k1 *= c1; k1 = ROTL32(k1,15); k1 *= c2; h1 ^= k1;
|
||||
};
|
||||
switch(len & 3) {
|
||||
case 3:
|
||||
k1 ^= tail[2] << 16;
|
||||
// Fall through
|
||||
case 2:
|
||||
k1 ^= tail[1] << 8;
|
||||
// Fall through
|
||||
case 1:
|
||||
k1 ^= tail[0];
|
||||
k1 *= c1;
|
||||
k1 = ROTL32(k1, 15);
|
||||
k1 *= c2;
|
||||
h1 ^= k1;
|
||||
};
|
||||
|
||||
//----------
|
||||
// finalization
|
||||
//----------
|
||||
// finalization
|
||||
|
||||
h1 ^= len;
|
||||
h1 ^= len;
|
||||
|
||||
h1 = fmix32(h1);
|
||||
h1 = fmix32(h1);
|
||||
|
||||
return h1;
|
||||
return h1;
|
||||
}
|
||||
|
|
301
src/util/table.c
301
src/util/table.c
|
@ -16,6 +16,7 @@
|
|||
#define TABLE_COMPARATOR(LIST, INDEX) LIST->list[(INDEX)].key == key
|
||||
#define HASH_TABLE_STRNCMP_COMPARATOR(LIST, INDEX) LIST->list[(INDEX)].key == hash && strncmp(LIST->list[(INDEX)].stringKey, key, LIST->list[(INDEX)].keylen) == 0
|
||||
#define HASH_TABLE_MEMCMP_COMPARATOR(LIST, INDEX) LIST->list[(INDEX)].key == hash && LIST->list[(INDEX)].keylen == keylen && memcmp(LIST->list[(INDEX)].stringKey, key, LIST->list[(INDEX)].keylen) == 0
|
||||
#define HASH_TABLE_CUSTOM_COMPARATOR(LIST, INDEX) LIST->list[(INDEX)].key == hash && table->fn.equal(LIST->list[(INDEX)].stringKey, key)
|
||||
|
||||
#define TABLE_LOOKUP_START(COMPARATOR, LIST) \
|
||||
size_t i; \
|
||||
|
@ -68,9 +69,13 @@ static struct TableList* _resizeAsNeeded(struct Table* table, struct TableList*
|
|||
static void _removeItemFromList(struct Table* table, struct TableList* list, size_t item) {
|
||||
--list->nEntries;
|
||||
--table->size;
|
||||
free(list->list[item].stringKey);
|
||||
if (table->deinitializer) {
|
||||
table->deinitializer(list->list[item].value);
|
||||
if (table->fn.deref) {
|
||||
table->fn.deref(list->list[item].stringKey);
|
||||
} else {
|
||||
free(list->list[item].stringKey);
|
||||
}
|
||||
if (table->fn.deinitializer) {
|
||||
table->fn.deinitializer(list->list[item].value);
|
||||
}
|
||||
if (item != list->nEntries) {
|
||||
list->list[item] = list->list[list->nEntries];
|
||||
|
@ -80,13 +85,19 @@ static void _removeItemFromList(struct Table* table, struct TableList* list, siz
|
|||
static void _rebalance(struct Table* table) {
|
||||
struct Table newTable;
|
||||
TableInit(&newTable, table->tableSize * REBALANCE_THRESHOLD, NULL);
|
||||
memcpy(&newTable.fn, &table->fn, sizeof(newTable.fn));
|
||||
newTable.seed = table->seed * 134775813 + 1;
|
||||
size_t i;
|
||||
for (i = 0; i < table->tableSize; ++i) {
|
||||
const struct TableList* list = &table->table[i];
|
||||
struct TableList* list = &table->table[i];
|
||||
size_t j;
|
||||
for (j = 0; j < list->nEntries; ++j) {
|
||||
HashTableInsertBinaryMoveKey(&newTable, list->list[j].stringKey, list->list[j].keylen, list->list[j].value);
|
||||
if (!table->fn.equal) {
|
||||
HashTableInsertBinaryMoveKey(&newTable, list->list[j].stringKey, list->list[j].keylen, list->list[j].value);
|
||||
} else {
|
||||
HashTableInsertCustom(&newTable, list->list[j].stringKey, list->list[j].value);
|
||||
table->fn.deref(list->list[j].stringKey);
|
||||
}
|
||||
}
|
||||
free(list->list);
|
||||
}
|
||||
|
@ -105,7 +116,9 @@ void TableInit(struct Table* table, size_t initialSize, void (*deinitializer)(vo
|
|||
table->tableSize = initialSize;
|
||||
table->table = calloc(table->tableSize, sizeof(struct TableList));
|
||||
table->size = 0;
|
||||
table->deinitializer = deinitializer;
|
||||
table->fn = (struct TableFunctions) {
|
||||
.deinitializer = deinitializer
|
||||
};
|
||||
table->seed = 0;
|
||||
|
||||
size_t i;
|
||||
|
@ -122,9 +135,13 @@ void TableDeinit(struct Table* table) {
|
|||
struct TableList* list = &table->table[i];
|
||||
size_t j;
|
||||
for (j = 0; j < list->nEntries; ++j) {
|
||||
free(list->list[j].stringKey);
|
||||
if (table->deinitializer) {
|
||||
table->deinitializer(list->list[j].value);
|
||||
if (table->fn.deref) {
|
||||
table->fn.deref(list->list[j].stringKey);
|
||||
} else {
|
||||
free(list->list[j].stringKey);
|
||||
}
|
||||
if (table->fn.deinitializer) {
|
||||
table->fn.deinitializer(list->list[j].value);
|
||||
}
|
||||
}
|
||||
free(list->list);
|
||||
|
@ -146,8 +163,8 @@ void TableInsert(struct Table* table, uint32_t key, void* value) {
|
|||
struct TableList* list = _getList(table, key);
|
||||
TABLE_LOOKUP_START(TABLE_COMPARATOR, list) {
|
||||
if (value != lookupResult->value) {
|
||||
if (table->deinitializer) {
|
||||
table->deinitializer(lookupResult->value);
|
||||
if (table->fn.deinitializer) {
|
||||
table->fn.deinitializer(lookupResult->value);
|
||||
}
|
||||
lookupResult->value = value;
|
||||
}
|
||||
|
@ -172,10 +189,10 @@ void TableClear(struct Table* table) {
|
|||
size_t i;
|
||||
for (i = 0; i < table->tableSize; ++i) {
|
||||
struct TableList* list = &table->table[i];
|
||||
if (table->deinitializer) {
|
||||
if (table->fn.deinitializer) {
|
||||
size_t j;
|
||||
for (j = 0; j < list->nEntries; ++j) {
|
||||
table->deinitializer(list->list[j].value);
|
||||
table->fn.deinitializer(list->list[j].value);
|
||||
}
|
||||
}
|
||||
free(list->list);
|
||||
|
@ -200,17 +217,73 @@ size_t TableSize(const struct Table* table) {
|
|||
return table->size;
|
||||
}
|
||||
|
||||
bool TableIteratorStart(const struct Table* table, struct TableIterator* iter) {
|
||||
iter->entry = 0;
|
||||
for (iter->bucket = 0; iter->bucket < table->tableSize; ++iter->bucket) {
|
||||
if (table->table[iter->bucket].nEntries) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return iter->bucket < table->tableSize;
|
||||
}
|
||||
|
||||
bool TableIteratorNext(const struct Table* table, struct TableIterator* iter) {
|
||||
if (iter->entry + 1 < table->table[iter->bucket].nEntries) {
|
||||
++iter->entry;
|
||||
return true;
|
||||
}
|
||||
if (iter->bucket + 1 < table->tableSize) {
|
||||
iter->entry = 0;
|
||||
for (++iter->bucket; iter->bucket < table->tableSize; ++iter->bucket) {
|
||||
if (table->table[iter->bucket].nEntries) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return iter->bucket < table->tableSize;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t TableIteratorGetKey(const struct Table* table, const struct TableIterator* iter) {
|
||||
return table->table[iter->bucket].list[iter->entry].key;
|
||||
}
|
||||
|
||||
void* TableIteratorGetValue(const struct Table* table, const struct TableIterator* iter) {
|
||||
return table->table[iter->bucket].list[iter->entry].value;
|
||||
}
|
||||
|
||||
bool TableIteratorLookup(const struct Table* table, struct TableIterator* iter, uint32_t key) {
|
||||
uint32_t bucket = key & (table->tableSize - 1);
|
||||
const struct TableList* list = &table->table[bucket];
|
||||
TABLE_LOOKUP_START(TABLE_COMPARATOR, list) {
|
||||
iter->bucket = bucket;
|
||||
iter->entry = i;
|
||||
return true;
|
||||
} TABLE_LOOKUP_END;
|
||||
return false;
|
||||
}
|
||||
|
||||
void HashTableInit(struct Table* table, size_t initialSize, void (*deinitializer)(void*)) {
|
||||
TableInit(table, initialSize, deinitializer);
|
||||
table->seed = 1;
|
||||
}
|
||||
|
||||
void HashTableInitCustom(struct Table* table, size_t initialSize, const struct TableFunctions* funcs) {
|
||||
HashTableInit(table, initialSize, NULL);
|
||||
table->fn = *funcs;
|
||||
}
|
||||
|
||||
void HashTableDeinit(struct Table* table) {
|
||||
TableDeinit(table);
|
||||
}
|
||||
|
||||
void* HashTableLookup(const struct Table* table, const char* key) {
|
||||
uint32_t hash = hash32(key, strlen(key), table->seed);
|
||||
uint32_t hash;
|
||||
if (table->fn.hash) {
|
||||
hash = table->fn.hash(key, strlen(key), table->seed);
|
||||
} else {
|
||||
hash = hash32(key, strlen(key), table->seed);
|
||||
}
|
||||
const struct TableList* list = _getConstList(table, hash);
|
||||
TABLE_LOOKUP_START(HASH_TABLE_STRNCMP_COMPARATOR, list) {
|
||||
return lookupResult->value;
|
||||
|
@ -219,7 +292,12 @@ void* HashTableLookup(const struct Table* table, const char* key) {
|
|||
}
|
||||
|
||||
void* HashTableLookupBinary(const struct Table* table, const void* key, size_t keylen) {
|
||||
uint32_t hash = hash32(key, keylen, table->seed);
|
||||
uint32_t hash;
|
||||
if (table->fn.hash) {
|
||||
hash = table->fn.hash(key, keylen, table->seed);
|
||||
} else {
|
||||
hash = hash32(key, keylen, table->seed);
|
||||
}
|
||||
const struct TableList* list = _getConstList(table, hash);
|
||||
TABLE_LOOKUP_START(HASH_TABLE_MEMCMP_COMPARATOR, list) {
|
||||
return lookupResult->value;
|
||||
|
@ -227,18 +305,36 @@ void* HashTableLookupBinary(const struct Table* table, const void* key, size_t k
|
|||
return 0;
|
||||
}
|
||||
|
||||
void* HashTableLookupCustom(const struct Table* table, void* key) {
|
||||
uint32_t hash = table->fn.hash(key, 0, table->seed);
|
||||
const struct TableList* list = _getConstList(table, hash);
|
||||
TABLE_LOOKUP_START(HASH_TABLE_CUSTOM_COMPARATOR, list) {
|
||||
return lookupResult->value;
|
||||
} TABLE_LOOKUP_END;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void HashTableInsert(struct Table* table, const char* key, void* value) {
|
||||
uint32_t hash = hash32(key, strlen(key), table->seed);
|
||||
uint32_t hash;
|
||||
if (table->fn.hash) {
|
||||
hash = table->fn.hash(key, strlen(key), table->seed);
|
||||
} else {
|
||||
hash = hash32(key, strlen(key), table->seed);
|
||||
}
|
||||
struct TableList* list = _getList(table, hash);
|
||||
if (table->size >= table->tableSize * REBALANCE_THRESHOLD) {
|
||||
_rebalance(table);
|
||||
hash = hash32(key, strlen(key), table->seed);
|
||||
if (table->fn.hash) {
|
||||
hash = table->fn.hash(key, strlen(key), table->seed);
|
||||
} else {
|
||||
hash = hash32(key, strlen(key), table->seed);
|
||||
}
|
||||
list = _getList(table, hash);
|
||||
}
|
||||
TABLE_LOOKUP_START(HASH_TABLE_STRNCMP_COMPARATOR, list) {
|
||||
if (value != lookupResult->value) {
|
||||
if (table->deinitializer) {
|
||||
table->deinitializer(lookupResult->value);
|
||||
if (table->fn.deinitializer) {
|
||||
table->fn.deinitializer(lookupResult->value);
|
||||
}
|
||||
lookupResult->value = value;
|
||||
}
|
||||
|
@ -254,17 +350,26 @@ void HashTableInsert(struct Table* table, const char* key, void* value) {
|
|||
}
|
||||
|
||||
void HashTableInsertBinary(struct Table* table, const void* key, size_t keylen, void* value) {
|
||||
uint32_t hash = hash32(key, keylen, table->seed);
|
||||
uint32_t hash;
|
||||
if (table->fn.hash) {
|
||||
hash = table->fn.hash(key, keylen, table->seed);
|
||||
} else {
|
||||
hash = hash32(key, keylen, table->seed);
|
||||
}
|
||||
struct TableList* list = _getList(table, hash);
|
||||
if (table->size >= table->tableSize * REBALANCE_THRESHOLD) {
|
||||
_rebalance(table);
|
||||
hash = hash32(key, keylen, table->seed);
|
||||
if (table->fn.hash) {
|
||||
hash = table->fn.hash(key, keylen, table->seed);
|
||||
} else {
|
||||
hash = hash32(key, keylen, table->seed);
|
||||
}
|
||||
list = _getList(table, hash);
|
||||
}
|
||||
TABLE_LOOKUP_START(HASH_TABLE_MEMCMP_COMPARATOR, list) {
|
||||
if (value != lookupResult->value) {
|
||||
if (table->deinitializer) {
|
||||
table->deinitializer(lookupResult->value);
|
||||
if (table->fn.deinitializer) {
|
||||
table->fn.deinitializer(lookupResult->value);
|
||||
}
|
||||
lookupResult->value = value;
|
||||
}
|
||||
|
@ -280,18 +385,53 @@ void HashTableInsertBinary(struct Table* table, const void* key, size_t keylen,
|
|||
++table->size;
|
||||
}
|
||||
|
||||
void HashTableInsertBinaryMoveKey(struct Table* table, void* key, size_t keylen, void* value) {
|
||||
uint32_t hash = hash32(key, keylen, table->seed);
|
||||
void HashTableInsertCustom(struct Table* table, void* key, void* value) {
|
||||
uint32_t hash = table->fn.hash(key, 0, table->seed);
|
||||
struct TableList* list = _getList(table, hash);
|
||||
if (table->size >= table->tableSize * REBALANCE_THRESHOLD) {
|
||||
_rebalance(table);
|
||||
hash = table->fn.hash(key, 0, table->seed);
|
||||
list = _getList(table, hash);
|
||||
}
|
||||
TABLE_LOOKUP_START(HASH_TABLE_CUSTOM_COMPARATOR, list) {
|
||||
if (value != lookupResult->value) {
|
||||
if (table->fn.deinitializer) {
|
||||
table->fn.deinitializer(lookupResult->value);
|
||||
}
|
||||
lookupResult->value = value;
|
||||
}
|
||||
return;
|
||||
} TABLE_LOOKUP_END;
|
||||
list = _resizeAsNeeded(table, list, hash);
|
||||
list->list[list->nEntries].key = hash;
|
||||
list->list[list->nEntries].stringKey = table->fn.ref(key);
|
||||
list->list[list->nEntries].keylen = 0;
|
||||
list->list[list->nEntries].value = value;
|
||||
++list->nEntries;
|
||||
++table->size;
|
||||
}
|
||||
|
||||
void HashTableInsertBinaryMoveKey(struct Table* table, void* key, size_t keylen, void* value) {
|
||||
uint32_t hash;
|
||||
if (table->fn.hash) {
|
||||
hash = table->fn.hash(key, keylen, table->seed);
|
||||
} else {
|
||||
hash = hash32(key, keylen, table->seed);
|
||||
}
|
||||
struct TableList* list = _getList(table, hash);
|
||||
if (table->size >= table->tableSize * REBALANCE_THRESHOLD) {
|
||||
_rebalance(table);
|
||||
if (table->fn.hash) {
|
||||
hash = table->fn.hash(key, keylen, table->seed);
|
||||
} else {
|
||||
hash = hash32(key, keylen, table->seed);
|
||||
}
|
||||
list = _getList(table, hash);
|
||||
}
|
||||
TABLE_LOOKUP_START(HASH_TABLE_MEMCMP_COMPARATOR, list) {
|
||||
if (value != lookupResult->value) {
|
||||
if (table->deinitializer) {
|
||||
table->deinitializer(lookupResult->value);
|
||||
if (table->fn.deinitializer) {
|
||||
table->fn.deinitializer(lookupResult->value);
|
||||
}
|
||||
lookupResult->value = value;
|
||||
}
|
||||
|
@ -307,7 +447,13 @@ void HashTableInsertBinaryMoveKey(struct Table* table, void* key, size_t keylen,
|
|||
}
|
||||
|
||||
void HashTableRemove(struct Table* table, const char* key) {
|
||||
uint32_t hash = hash32(key, strlen(key), table->seed);
|
||||
uint32_t hash;
|
||||
|
||||
if (table->fn.hash) {
|
||||
hash = table->fn.hash(key, strlen(key), table->seed);
|
||||
} else {
|
||||
hash = hash32(key, strlen(key), table->seed);
|
||||
}
|
||||
struct TableList* list = _getList(table, hash);
|
||||
TABLE_LOOKUP_START(HASH_TABLE_STRNCMP_COMPARATOR, list) {
|
||||
_removeItemFromList(table, list, i); // TODO: Move i out of the macro
|
||||
|
@ -315,23 +461,40 @@ void HashTableRemove(struct Table* table, const char* key) {
|
|||
}
|
||||
|
||||
void HashTableRemoveBinary(struct Table* table, const void* key, size_t keylen) {
|
||||
uint32_t hash = hash32(key, keylen, table->seed);
|
||||
uint32_t hash;
|
||||
if (table->fn.hash) {
|
||||
hash = table->fn.hash(key, keylen, table->seed);
|
||||
} else {
|
||||
hash = hash32(key, keylen, table->seed);
|
||||
}
|
||||
struct TableList* list = _getList(table, hash);
|
||||
TABLE_LOOKUP_START(HASH_TABLE_MEMCMP_COMPARATOR, list) {
|
||||
_removeItemFromList(table, list, i); // TODO: Move i out of the macro
|
||||
} TABLE_LOOKUP_END;
|
||||
}
|
||||
|
||||
void HashTableRemoveCustom(struct Table* table, void* key) {
|
||||
uint32_t hash = table->fn.hash(key, 0, table->seed);
|
||||
struct TableList* list = _getList(table, hash);
|
||||
TABLE_LOOKUP_START(HASH_TABLE_CUSTOM_COMPARATOR, list) {
|
||||
_removeItemFromList(table, list, i); // TODO: Move i out of the macro
|
||||
} TABLE_LOOKUP_END;
|
||||
}
|
||||
|
||||
void HashTableClear(struct Table* table) {
|
||||
size_t i;
|
||||
for (i = 0; i < table->tableSize; ++i) {
|
||||
struct TableList* list = &table->table[i];
|
||||
size_t j;
|
||||
for (j = 0; j < list->nEntries; ++j) {
|
||||
if (table->deinitializer) {
|
||||
table->deinitializer(list->list[j].value);
|
||||
if (table->fn.deinitializer) {
|
||||
table->fn.deinitializer(list->list[j].value);
|
||||
}
|
||||
if (table->fn.deref) {
|
||||
table->fn.deref(list->list[j].stringKey);
|
||||
} else {
|
||||
free(list->list[j].stringKey);
|
||||
}
|
||||
free(list->list[j].stringKey);
|
||||
}
|
||||
free(list->list);
|
||||
list->listSize = LIST_INITIAL_SIZE;
|
||||
|
@ -414,3 +577,77 @@ const char* HashTableSearchString(const struct Table* table, const char* value)
|
|||
size_t HashTableSize(const struct Table* table) {
|
||||
return table->size;
|
||||
}
|
||||
|
||||
bool HashTableIteratorStart(const struct Table* table, struct TableIterator* iter) {
|
||||
return TableIteratorStart(table, iter);
|
||||
}
|
||||
|
||||
bool HashTableIteratorNext(const struct Table* table, struct TableIterator* iter) {
|
||||
return TableIteratorNext(table, iter);
|
||||
}
|
||||
|
||||
const char* HashTableIteratorGetKey(const struct Table* table, const struct TableIterator* iter) {
|
||||
return table->table[iter->bucket].list[iter->entry].stringKey;
|
||||
}
|
||||
|
||||
const void* HashTableIteratorGetBinaryKey(const struct Table* table, const struct TableIterator* iter) {
|
||||
return table->table[iter->bucket].list[iter->entry].stringKey;
|
||||
}
|
||||
|
||||
size_t HashTableIteratorGetBinaryKeyLen(const struct Table* table, const struct TableIterator* iter) {
|
||||
return table->table[iter->bucket].list[iter->entry].keylen;
|
||||
}
|
||||
|
||||
void* HashTableIteratorGetCustomKey(const struct Table* table, const struct TableIterator* iter) {
|
||||
return (char*) table->table[iter->bucket].list[iter->entry].stringKey;
|
||||
}
|
||||
|
||||
void* HashTableIteratorGetValue(const struct Table* table, const struct TableIterator* iter) {
|
||||
return TableIteratorGetValue(table, iter);
|
||||
}
|
||||
|
||||
bool HashTableIteratorLookup(const struct Table* table, struct TableIterator* iter, const char* key) {
|
||||
uint32_t hash;
|
||||
if (table->fn.hash) {
|
||||
hash = table->fn.hash(key, strlen(key), table->seed);
|
||||
} else {
|
||||
hash = hash32(key, strlen(key), table->seed);
|
||||
}
|
||||
uint32_t bucket = hash & (table->tableSize - 1);
|
||||
const struct TableList* list = &table->table[bucket];
|
||||
TABLE_LOOKUP_START(HASH_TABLE_STRNCMP_COMPARATOR, list) {
|
||||
iter->bucket = bucket;
|
||||
iter->entry = i;
|
||||
return true;
|
||||
} TABLE_LOOKUP_END;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HashTableIteratorLookupBinary(const struct Table* table, struct TableIterator* iter, const void* key, size_t keylen) {
|
||||
uint32_t hash;
|
||||
if (table->fn.hash) {
|
||||
hash = table->fn.hash(key, keylen, table->seed);
|
||||
} else {
|
||||
hash = hash32(key, keylen, table->seed);
|
||||
}
|
||||
uint32_t bucket = hash & (table->tableSize - 1);
|
||||
const struct TableList* list = &table->table[bucket];
|
||||
TABLE_LOOKUP_START(HASH_TABLE_MEMCMP_COMPARATOR, list) {
|
||||
iter->bucket = bucket;
|
||||
iter->entry = i;
|
||||
return true;
|
||||
} TABLE_LOOKUP_END;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HashTableIteratorLookupCustom(const struct Table* table, struct TableIterator* iter, void* key) {
|
||||
uint32_t hash = table->fn.hash(key, 0, table->seed);
|
||||
uint32_t bucket = hash & (table->tableSize - 1);
|
||||
const struct TableList* list = &table->table[bucket];
|
||||
TABLE_LOOKUP_START(HASH_TABLE_CUSTOM_COMPARATOR, list) {
|
||||
iter->bucket = bucket;
|
||||
iter->entry = i;
|
||||
return true;
|
||||
} TABLE_LOOKUP_END;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "util/test/suite.h"
|
||||
|
||||
#include <mgba-util/table.h>
|
||||
|
||||
M_TEST_DEFINE(basic) {
|
||||
struct Table table;
|
||||
TableInit(&table, 0, NULL);
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < 5000; ++i) {
|
||||
TableInsert(&table, i, (void*) i);
|
||||
}
|
||||
|
||||
for (i = 0; i < 5000; ++i) {
|
||||
assert_int_equal(i, (size_t) TableLookup(&table, i));
|
||||
}
|
||||
|
||||
TableDeinit(&table);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(iterator) {
|
||||
struct Table table;
|
||||
struct TableIterator iter;
|
||||
|
||||
TableInit(&table, 0, NULL);
|
||||
assert_false(TableIteratorStart(&table, &iter));
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < 32; ++i) {
|
||||
TableInsert(&table, i, (void*) i);
|
||||
}
|
||||
|
||||
assert_true(TableIteratorStart(&table, &iter));
|
||||
uint32_t mask = 0;
|
||||
while (true) {
|
||||
assert_int_equal(TableIteratorGetKey(&table, &iter), (uintptr_t) TableIteratorGetValue(&table, &iter));
|
||||
mask ^= 1 << TableIteratorGetKey(&table, &iter);
|
||||
if (!TableIteratorNext(&table, &iter)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_int_equal(mask, 0xFFFFFFFFU);
|
||||
|
||||
TableDeinit(&table);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(iteratorLookup) {
|
||||
struct Table table;
|
||||
struct TableIterator iter;
|
||||
|
||||
TableInit(&table, 0, NULL);
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < 500; ++i) {
|
||||
TableInsert(&table, (i * 0x5DEECE66D) >> 16, (void*) i);
|
||||
}
|
||||
|
||||
for (i = 0; i < 500; ++i) {
|
||||
assert_true(TableIteratorLookup(&table, &iter, (i * 0x5DEECE66D) >> 16));
|
||||
assert_int_equal(TableIteratorGetKey(&table, &iter), (i * 0x5DEECE66D) >> 16);
|
||||
assert_int_equal((uintptr_t) TableIteratorGetValue(&table, &iter), i);
|
||||
}
|
||||
for (i = 1000; i < 1200; ++i) {
|
||||
assert_false(TableIteratorLookup(&table, &iter, (i * 0x5DEECE66D) >> 16));
|
||||
}
|
||||
|
||||
TableDeinit(&table);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(hash) {
|
||||
struct Table table;
|
||||
HashTableInit(&table, 0, NULL);
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < 5000; ++i) {
|
||||
char buffer[16];
|
||||
snprintf(buffer, sizeof(buffer), "%"PRIz"i", i);
|
||||
HashTableInsert(&table, buffer, (void*) i);
|
||||
}
|
||||
|
||||
for (i = 0; i < 5000; ++i) {
|
||||
char buffer[16];
|
||||
snprintf(buffer, sizeof(buffer), "%"PRIz"i", i);
|
||||
assert_int_equal(i, (size_t) HashTableLookup(&table, buffer));
|
||||
}
|
||||
|
||||
HashTableDeinit(&table);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(hashIterator) {
|
||||
struct Table table;
|
||||
struct TableIterator iter;
|
||||
char buf[18];
|
||||
|
||||
HashTableInit(&table, 0, NULL);
|
||||
assert_false(HashTableIteratorStart(&table, &iter));
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < 32; ++i) {
|
||||
snprintf(buf, sizeof(buf), "%zu", i);
|
||||
HashTableInsert(&table, buf, (void*) i);
|
||||
}
|
||||
|
||||
assert_true(TableIteratorStart(&table, &iter));
|
||||
uint32_t mask = 0;
|
||||
while (true) {
|
||||
assert_int_equal(atoi(HashTableIteratorGetKey(&table, &iter)), (uintptr_t) HashTableIteratorGetValue(&table, &iter));
|
||||
mask ^= 1 << atoi(HashTableIteratorGetKey(&table, &iter));
|
||||
if (!HashTableIteratorNext(&table, &iter)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert_int_equal(mask, 0xFFFFFFFFU);
|
||||
|
||||
HashTableDeinit(&table);
|
||||
}
|
||||
|
||||
|
||||
M_TEST_DEFINE(hashIteratorLookup) {
|
||||
struct Table table;
|
||||
struct TableIterator iter;
|
||||
char buf[18];
|
||||
|
||||
HashTableInit(&table, 0, NULL);
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < 500; ++i) {
|
||||
snprintf(buf, sizeof(buf), "%zu", (i * 0x5DEECE66D) >> 4);
|
||||
HashTableInsert(&table, buf, (void*) i);
|
||||
}
|
||||
|
||||
for (i = 0; i < 500; ++i) {
|
||||
snprintf(buf, sizeof(buf), "%zu", (i * 0x5DEECE66D) >> 4);
|
||||
assert_true(HashTableIteratorLookup(&table, &iter, buf));
|
||||
assert_string_equal(HashTableIteratorGetKey(&table, &iter), buf);
|
||||
assert_int_equal((uintptr_t) HashTableIteratorGetValue(&table, &iter), i);
|
||||
}
|
||||
for (i = 1000; i < 1200; ++i) {
|
||||
snprintf(buf, sizeof(buf), "%zu", (i * 0x5DEECE66D) >> 4);
|
||||
assert_false(HashTableIteratorLookup(&table, &iter, buf));
|
||||
}
|
||||
|
||||
HashTableDeinit(&table);
|
||||
}
|
||||
|
||||
M_TEST_SUITE_DEFINE(Table,
|
||||
cmocka_unit_test(basic),
|
||||
cmocka_unit_test(iterator),
|
||||
cmocka_unit_test(iteratorLookup),
|
||||
cmocka_unit_test(hash),
|
||||
cmocka_unit_test(hashIterator),
|
||||
cmocka_unit_test(hashIteratorLookup),
|
||||
)
|
Loading…
Reference in New Issue