Merge branch 'master' into port/wii

This commit is contained in:
Jeffrey Pfau 2015-06-20 03:25:01 -07:00
commit 52c66de694
30 changed files with 1716 additions and 1454 deletions

View File

@ -21,6 +21,7 @@ Features:
- Finer control over FPS target
- Holdable shortcut for rewinding one frame at a time
- Ability to boot directly into the BIOS
- Preliminary support for yanking out the game pak while a game is running
Bugfixes:
- ARM7: Fix SWI and IRQ timings
- GBA Audio: Force audio FIFOs to 32-bit
@ -46,6 +47,7 @@ Bugfixes:
- GBA: Fix calls to endian-independent loadstores
- GBA Video: Fix windows not affecting sprites
- VFS: Fix line-reading to return proper values
- GBA Memory: Fix load/store multiple video memory waitstates
Misc:
- Qt: Handle saving input settings better
- Debugger: Free watchpoints in addition to breakpoints
@ -76,6 +78,9 @@ Misc:
- Qt: Rename "Fullscreen" to "Toggle fullscreen"
- Qt: Don't save window size when entering fullscreen
- Qt: Make the default fullscreen binding for Windows be Alt-Enter
- GBA Video: Refactor software renderer into separate files
- ARM7: Add emulation for Undefined CPU mode
- GBA: More accurate cycle estimation for ROM prefetch and flash save chips
0.2.1: (2015-05-13)
Bugfixes:

View File

@ -23,7 +23,7 @@ file(GLOB GBA_CHEATS_SRC ${CMAKE_SOURCE_DIR}/src/gba/cheats/*.c)
file(GLOB GBA_RR_SRC ${CMAKE_SOURCE_DIR}/src/gba/rr/*.c)
file(GLOB GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/*.c)
file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs])
file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/video-software.c)
file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/*.c)
file(GLOB SIO_SRC ${CMAKE_SOURCE_DIR}/src/gba/sio/lockstep.c)
file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.c)
list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c)

35
PORTING.md Normal file
View File

@ -0,0 +1,35 @@
Porting
=======
Porting is preferentially done upstream so as to avoid fragmenting the codebase into individually unmergeable forks. As such, precaution must be taken to keep changes separate enough to not interfere with other ports, while still maintaining the ability to add new port-specific code seamlessly.
Folders for each port should be under the `src/platform` folder, and make minimally invasive changes to the rest of the tree. If any changes are needed, try to make sure they are generic and have the ability to be ironed out in the future. For example, if a function doesn't work on a specific platform, maybe a way to make that function more portable should be added.
The general porting process involves branching `master`, making the needed changes, and, when the port is mature enough to not have major effects to other ports, merged into `port/crucible`. The crucible is used for mixing upcoming ports to make sure they aren't fragile when `master` merges into it every so often. At this time, the crucible hasn't yet been merged into `master`, but in the future this may occur regularly. Until then, if a port is to get merged into master, make sure the changes to each port occur on the port-specific branch before being merged into `port/crucible`.
Port-specific TODO
------------------
The ports are vaguely usable, but by no means should be considered stable.
### 3DS
* Add menu
* Add audio
* Thread support testing
* Make it faster
* ARMv6 dynarec
* Hardware acceleration
### PSP
* Add menu
* Add audio
* Thread support
* Make it faster
* MIPS dynarec
* Hardware acceleration
### Wii
* Add menu
* Add audio
* Thread support
* Clean up video detection

View File

@ -190,6 +190,26 @@ void ARMRaiseSWI(struct ARMCore* cpu) {
cpu->cycles += currentCycles;
}
void ARMRaiseUndefined(struct ARMCore* cpu) {
union PSR cpsr = cpu->cpsr;
int instructionWidth;
if (cpu->executionMode == MODE_THUMB) {
instructionWidth = WORD_SIZE_THUMB;
} else {
instructionWidth = WORD_SIZE_ARM;
}
ARMSetPrivilegeMode(cpu, MODE_UNDEFINED);
cpu->cpsr.priv = MODE_UNDEFINED;
cpu->gprs[ARM_LR] = cpu->gprs[ARM_PC] - instructionWidth;
cpu->gprs[ARM_PC] = BASE_UNDEF;
int currentCycles = 0;
ARM_WRITE_PC;
_ARMSetMode(cpu, MODE_ARM);
cpu->spsr = cpsr;
cpu->cpsr.i = 1;
cpu->cycles += currentCycles;
}
static inline void ARMStep(struct ARMCore* cpu) {
uint32_t opcode = cpu->prefetch[0];
cpu->prefetch[0] = cpu->prefetch[1];

View File

@ -172,6 +172,7 @@ void ARMReset(struct ARMCore* cpu);
void ARMSetPrivilegeMode(struct ARMCore*, enum PrivilegeMode);
void ARMRaiseIRQ(struct ARMCore*);
void ARMRaiseSWI(struct ARMCore*);
void ARMRaiseUndefined(struct ARMCore*);
void ARMRun(struct ARMCore* cpu);
void ARMRunLoop(struct ARMCore* cpu);

View File

@ -96,14 +96,22 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
gba->performingDMA = false;
}
void GBADestroy(struct GBA* gba) {
void GBAUnloadROM(struct GBA* gba) {
if (gba->pristineRom == gba->memory.rom) {
gba->memory.rom = 0;
} else {
mappedMemoryFree(gba->pristineRom, gba->pristineRomSize);
}
if (gba->romVf) {
gba->romVf->unmap(gba->romVf, gba->pristineRom, gba->pristineRomSize);
gba->pristineRom = 0;
gba->romVf = 0;
}
}
void GBADestroy(struct GBA* gba) {
GBAUnloadROM(gba);
if (gba->biosVf) {
gba->biosVf->unmap(gba->biosVf, gba->memory.bios, SIZE_BIOS);
@ -140,6 +148,11 @@ void GBAReset(struct ARMCore* cpu) {
if (!gba->rr || (!gba->rr->isPlaying(gba->rr) && !gba->rr->isRecording(gba->rr))) {
GBASavedataUnmask(&gba->memory.savedata);
}
if (gba->yankedRomSize) {
gba->memory.romSize = gba->yankedRomSize;
gba->yankedRomSize = 0;
}
GBAMemoryReset(gba);
GBAVideoReset(&gba->video);
GBAAudioReset(&gba->audio);
@ -365,6 +378,7 @@ void GBADetachDebugger(struct GBA* gba) {
}
void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char* fname) {
GBAUnloadROM(gba);
gba->romVf = vf;
gba->pristineRomSize = vf->size(vf);
vf->seek(vf, 0, SEEK_SET);
@ -376,6 +390,7 @@ void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char
GBALog(gba, GBA_LOG_WARN, "Couldn't map ROM");
return;
}
gba->yankedRomSize = 0;
gba->memory.rom = gba->pristineRom;
gba->activeFile = fname;
gba->memory.romSize = gba->pristineRomSize;
@ -385,6 +400,12 @@ void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char
// TODO: error check
}
void GBAYankROM(struct GBA* gba) {
gba->yankedRomSize = gba->memory.romSize;
gba->memory.romSize = 0;
GBARaiseIRQ(gba, IRQ_GAMEPAK);
}
void GBALoadBIOS(struct GBA* gba, struct VFile* vf) {
gba->biosVf = vf;
uint32_t* bios = vf->map(vf, SIZE_BIOS, MAP_READ);
@ -491,10 +512,6 @@ void GBAWriteIE(struct GBA* gba, uint16_t value) {
GBALog(gba, GBA_LOG_STUB, "Keypad interrupts not implemented");
}
if (value & (1 << IRQ_GAMEPAK)) {
GBALog(gba, GBA_LOG_STUB, "Gamepak interrupts not implemented");
}
if (gba->memory.io[REG_IME >> 1] && value & gba->memory.io[REG_IF >> 1]) {
ARMRaiseIRQ(gba->cpu);
}
@ -650,7 +667,7 @@ void GBAGetGameTitle(struct GBA* gba, char* out) {
void GBAHitStub(struct ARMCore* cpu, uint32_t opcode) {
struct GBA* gba = (struct GBA*) cpu->master;
enum GBALogLevel level = GBA_LOG_FATAL;
enum GBALogLevel level = GBA_LOG_ERROR;
if (gba->debugger) {
level = GBA_LOG_STUB;
struct DebuggerEntryInfo info = {
@ -664,13 +681,17 @@ void GBAHitStub(struct ARMCore* cpu, uint32_t opcode) {
void GBAIllegal(struct ARMCore* cpu, uint32_t opcode) {
struct GBA* gba = (struct GBA*) cpu->master;
GBALog(gba, GBA_LOG_WARN, "Illegal opcode: %08x", opcode);
if (!gba->yankedRomSize) {
GBALog(gba, GBA_LOG_WARN, "Illegal opcode: %08x", opcode);
}
if (gba->debugger) {
struct DebuggerEntryInfo info = {
.address = _ARMPCAddress(cpu),
.opcode = opcode
};
ARMDebuggerEnter(gba->debugger, DEBUGGER_ENTER_ILLEGAL_OP, &info);
} else {
ARMRaiseUndefined(cpu);
}
}

View File

@ -146,6 +146,7 @@ struct GBA {
struct GBARRContext* rr;
void* pristineRom;
size_t pristineRomSize;
size_t yankedRomSize;
uint32_t romCrc32;
struct VFile* romVf;
struct VFile* biosVf;
@ -206,6 +207,8 @@ void GBASetBreakpoint(struct GBA* gba, struct ARMComponent* component, uint32_t
void GBAClearBreakpoint(struct GBA* gba, uint32_t address, enum ExecutionMode mode, uint32_t opcode);
void GBALoadROM(struct GBA* gba, struct VFile* vf, struct VFile* sav, const char* fname);
void GBAYankROM(struct GBA* gba);
void GBAUnloadROM(struct GBA* gba);
void GBALoadBIOS(struct GBA* gba, struct VFile* vf);
void GBAApplyPatch(struct GBA* gba, struct Patch* patch);

View File

@ -18,15 +18,15 @@
static uint32_t _popcount32(unsigned bits);
static void _pristineCow(struct GBA* gba);
static uint32_t _deadbeef[2] = { 0xDEADBEEF, 0xFEEDFACE };
static uint32_t _deadbeef[1] = { 0xE710B710 }; // Illegal instruction on both ARM and Thumb
static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t region);
static void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info);
static const char GBA_BASE_WAITSTATES[16] = { 0, 0, 2, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4 };
static const char GBA_BASE_WAITSTATES_32[16] = { 0, 0, 5, 0, 0, 0, 0, 0, 7, 7, 9, 9, 13, 13, 9 };
static const char GBA_BASE_WAITSTATES_32[16] = { 0, 0, 5, 0, 0, 1, 1, 0, 7, 7, 9, 9, 13, 13, 9 };
static const char GBA_BASE_WAITSTATES_SEQ[16] = { 0, 0, 2, 0, 0, 0, 0, 0, 2, 2, 4, 4, 8, 8, 4 };
static const char GBA_BASE_WAITSTATES_SEQ_32[16] = { 0, 0, 5, 0, 0, 0, 0, 0, 5, 5, 9, 9, 17, 17, 9 };
static const char GBA_BASE_WAITSTATES_SEQ_32[16] = { 0, 0, 5, 0, 0, 1, 1, 0, 5, 5, 9, 9, 17, 17, 9 };
static const char GBA_ROM_WAITSTATES[] = { 4, 3, 2, 8 };
static const char GBA_ROM_WAITSTATES_SEQ[] = { 2, 1, 4, 1, 8, 1 };
static const int DMA_OFFSET[] = { 1, -1, 0, 1 };
@ -270,18 +270,20 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
}
// Fall through
default:
memory->activeRegion = 0;
memory->activeRegion = -1;
cpu->memory.activeRegion = _deadbeef;
cpu->memory.activeMask = 0;
GBALog(gba, GBA_LOG_FATAL, "Jumped to invalid address");
break;
if (!gba->yankedRomSize) {
GBALog(gba, GBA_LOG_FATAL, "Jumped to invalid address");
}
return;
}
cpu->memory.activeSeqCycles32 = memory->waitstatesPrefetchSeq32[memory->activeRegion];
cpu->memory.activeSeqCycles16 = memory->waitstatesPrefetchSeq16[memory->activeRegion];
cpu->memory.activeNonseqCycles32 = memory->waitstatesPrefetchNonseq32[memory->activeRegion];
cpu->memory.activeNonseqCycles16 = memory->waitstatesPrefetchNonseq16[memory->activeRegion];
cpu->memory.activeUncachedCycles32 = memory->waitstatesNonseq32[memory->activeRegion];
cpu->memory.activeUncachedCycles16 = memory->waitstatesNonseq16[memory->activeRegion];
cpu->memory.activeSeqCycles32 = memory->waitstatesPrefetchSeq32[newRegion];
cpu->memory.activeSeqCycles16 = memory->waitstatesPrefetchSeq16[newRegion];
cpu->memory.activeNonseqCycles32 = memory->waitstatesPrefetchNonseq32[newRegion];
cpu->memory.activeNonseqCycles16 = memory->waitstatesPrefetchNonseq16[newRegion];
cpu->memory.activeUncachedCycles32 = memory->waitstatesNonseq32[newRegion];
cpu->memory.activeUncachedCycles16 = memory->waitstatesNonseq16[newRegion];
}
#define LOAD_BAD \
@ -334,7 +336,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
#define LOAD_PALETTE_RAM \
LOAD_32(value, address & (SIZE_PALETTE_RAM - 4), gba->video.palette); \
++wait;
wait += waitstatesRegion[REGION_PALETTE_RAM];
#define LOAD_VRAM \
if ((address & 0x0001FFFF) < SIZE_VRAM) { \
@ -342,7 +344,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
} else { \
LOAD_32(value, address & 0x00017FFC, gba->video.renderer->vram); \
} \
++wait;
wait += waitstatesRegion[REGION_VRAM];
#define LOAD_OAM LOAD_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw);
@ -611,7 +613,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
#define STORE_PALETTE_RAM \
STORE_32(value, address & (SIZE_PALETTE_RAM - 4), gba->video.palette); \
gba->video.renderer->writePalette(gba->video.renderer, (address & (SIZE_PALETTE_RAM - 4)) + 2, value >> 16); \
++wait; \
wait += waitstatesRegion[REGION_PALETTE_RAM]; \
gba->video.renderer->writePalette(gba->video.renderer, address & (SIZE_PALETTE_RAM - 4), value);
#define STORE_VRAM \
@ -620,7 +622,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
} else { \
STORE_32(value, address & 0x00017FFC, gba->video.renderer->vram); \
} \
++wait;
wait += waitstatesRegion[REGION_VRAM];
#define STORE_OAM \
STORE_32(value, address & (SIZE_OAM - 4), gba->video.oam.raw); \
@ -1268,21 +1270,23 @@ void GBAAdjustWaitstates(struct GBA* gba, uint16_t parameters) {
memory->waitstatesPrefetchNonseq32[REGION_CART1] = memory->waitstatesPrefetchNonseq32[REGION_CART1_EX] = memory->waitstatesNonseq32[REGION_CART1];
memory->waitstatesPrefetchNonseq32[REGION_CART2] = memory->waitstatesPrefetchNonseq32[REGION_CART2_EX] = memory->waitstatesNonseq32[REGION_CART2];
} else {
memory->waitstatesPrefetchSeq16[REGION_CART0] = memory->waitstatesPrefetchSeq16[REGION_CART0_EX] = 0;
memory->waitstatesPrefetchSeq16[REGION_CART1] = memory->waitstatesPrefetchSeq16[REGION_CART1_EX] = 0;
memory->waitstatesPrefetchSeq16[REGION_CART2] = memory->waitstatesPrefetchSeq16[REGION_CART2_EX] = 0;
// Assume it stalls one cycle to pull a value from the prefetch
// This needs more research to tell if it's accurate or not
memory->waitstatesPrefetchSeq16[REGION_CART0] = memory->waitstatesPrefetchSeq16[REGION_CART0_EX] = 1;
memory->waitstatesPrefetchSeq16[REGION_CART1] = memory->waitstatesPrefetchSeq16[REGION_CART1_EX] = 1;
memory->waitstatesPrefetchSeq16[REGION_CART2] = memory->waitstatesPrefetchSeq16[REGION_CART2_EX] = 1;
memory->waitstatesPrefetchSeq32[REGION_CART0] = memory->waitstatesPrefetchSeq32[REGION_CART0_EX] = 0;
memory->waitstatesPrefetchSeq32[REGION_CART1] = memory->waitstatesPrefetchSeq32[REGION_CART1_EX] = 0;
memory->waitstatesPrefetchSeq32[REGION_CART2] = memory->waitstatesPrefetchSeq32[REGION_CART2_EX] = 0;
memory->waitstatesPrefetchSeq32[REGION_CART0] = memory->waitstatesPrefetchSeq32[REGION_CART0_EX] = 2;
memory->waitstatesPrefetchSeq32[REGION_CART1] = memory->waitstatesPrefetchSeq32[REGION_CART1_EX] = 2;
memory->waitstatesPrefetchSeq32[REGION_CART2] = memory->waitstatesPrefetchSeq32[REGION_CART2_EX] = 2;
memory->waitstatesPrefetchNonseq16[REGION_CART0] = memory->waitstatesPrefetchNonseq16[REGION_CART0_EX] = 0;
memory->waitstatesPrefetchNonseq16[REGION_CART1] = memory->waitstatesPrefetchNonseq16[REGION_CART1_EX] = 0;
memory->waitstatesPrefetchNonseq16[REGION_CART2] = memory->waitstatesPrefetchNonseq16[REGION_CART2_EX] = 0;
memory->waitstatesPrefetchNonseq16[REGION_CART0] = memory->waitstatesPrefetchNonseq16[REGION_CART0_EX] = 1;
memory->waitstatesPrefetchNonseq16[REGION_CART1] = memory->waitstatesPrefetchNonseq16[REGION_CART1_EX] = 1;
memory->waitstatesPrefetchNonseq16[REGION_CART2] = memory->waitstatesPrefetchNonseq16[REGION_CART2_EX] = 1;
memory->waitstatesPrefetchNonseq32[REGION_CART0] = memory->waitstatesPrefetchNonseq32[REGION_CART0_EX] = 0;
memory->waitstatesPrefetchNonseq32[REGION_CART1] = memory->waitstatesPrefetchNonseq32[REGION_CART1_EX] = 0;
memory->waitstatesPrefetchNonseq32[REGION_CART2] = memory->waitstatesPrefetchNonseq32[REGION_CART2_EX] = 0;
memory->waitstatesPrefetchNonseq32[REGION_CART0] = memory->waitstatesPrefetchNonseq32[REGION_CART0_EX] = 2;
memory->waitstatesPrefetchNonseq32[REGION_CART1] = memory->waitstatesPrefetchNonseq32[REGION_CART1_EX] = 2;
memory->waitstatesPrefetchNonseq32[REGION_CART2] = memory->waitstatesPrefetchNonseq32[REGION_CART2_EX] = 2;
}
cpu->memory.activeSeqCycles32 = memory->waitstatesPrefetchSeq32[memory->activeRegion];

View File

@ -0,0 +1,195 @@
/* Copyright (c) 2013-2015 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 "software-private.h"
#include "gba/gba.h"
void GBAVideoSoftwareRendererDrawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) {
int sizeAdjusted = 0x8000 << background->size;
BACKGROUND_BITMAP_INIT;
uint32_t screenBase = background->screenBase;
uint32_t charBase = background->charBase;
uint8_t mapData;
uint8_t tileData = 0;
int outX;
uint32_t* pixel;
for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) {
x += background->dx;
y += background->dy;
if (!mosaicWait) {
if (background->overflow) {
localX = x & (sizeAdjusted - 1);
localY = y & (sizeAdjusted - 1);
} else if ((x | y) & ~(sizeAdjusted - 1)) {
continue;
} else {
localX = x;
localY = y;
}
mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)];
tileData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)];
mosaicWait = mosaicH;
} else {
--mosaicWait;
}
uint32_t current = *pixel;
if (tileData && IS_WRITABLE(current)) {
if (!objwinSlowPath) {
_compositeBlendNoObjwin(renderer, pixel, palette[tileData] | flags, current);
} else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) {
color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette;
unsigned mergedFlags = flags;
if (current & FLAG_OBJWIN) {
mergedFlags = objwinFlags;
}
_compositeBlendObjwin(renderer, pixel, currentPalette[tileData] | mergedFlags, current);
}
}
}
}
void GBAVideoSoftwareRendererDrawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) {
BACKGROUND_BITMAP_INIT;
uint32_t color = renderer->normalPalette[0];
int outX;
uint32_t* pixel;
for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) {
BACKGROUND_BITMAP_ITERATE(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
if (!mosaicWait) {
LOAD_16(color, ((localX >> 8) + (localY >> 8) * VIDEO_HORIZONTAL_PIXELS) << 1, renderer->d.vram);
#ifndef COLOR_16_BIT
unsigned color32;
color32 = 0;
color32 |= (color << 3) & 0xF8;
color32 |= (color << 6) & 0xF800;
color32 |= (color << 9) & 0xF80000;
color = color32;
#elif COLOR_5_6_5
uint16_t color16 = 0;
color16 |= (color & 0x001F) << 11;
color16 |= (color & 0x03E0) << 1;
color16 |= (color & 0x7C00) >> 10;
color = color16;
#endif
mosaicWait = mosaicH;
} else {
--mosaicWait;
}
uint32_t current = *pixel;
if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) {
unsigned mergedFlags = flags;
if (current & FLAG_OBJWIN) {
mergedFlags = objwinFlags;
}
if (!variant) {
_compositeBlendObjwin(renderer, pixel, color | mergedFlags, current);
} else if (renderer->blendEffect == BLEND_BRIGHTEN) {
_compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | mergedFlags, current);
} else if (renderer->blendEffect == BLEND_DARKEN) {
_compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | mergedFlags, current);
}
}
}
}
void GBAVideoSoftwareRendererDrawBackgroundMode4(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) {
BACKGROUND_BITMAP_INIT;
uint16_t color = renderer->normalPalette[0];
uint32_t offset = 0;
if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) {
offset = 0xA000;
}
int outX;
uint32_t* pixel;
for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) {
BACKGROUND_BITMAP_ITERATE(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
if (!mosaicWait) {
color = ((uint8_t*)renderer->d.vram)[offset + (localX >> 8) + (localY >> 8) * VIDEO_HORIZONTAL_PIXELS];
mosaicWait = mosaicH;
} else {
--mosaicWait;
}
uint32_t current = *pixel;
if (color && IS_WRITABLE(current)) {
if (!objwinSlowPath) {
_compositeBlendNoObjwin(renderer, pixel, palette[color] | flags, current);
} else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) {
color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette;
unsigned mergedFlags = flags;
if (current & FLAG_OBJWIN) {
mergedFlags = objwinFlags;
}
_compositeBlendObjwin(renderer, pixel, currentPalette[color] | mergedFlags, current);
}
}
}
}
void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) {
BACKGROUND_BITMAP_INIT;
uint32_t color = renderer->normalPalette[0];
uint32_t offset = 0;
if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) {
offset = 0xA000;
}
int outX;
uint32_t* pixel;
for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) {
BACKGROUND_BITMAP_ITERATE(160, 128);
if (!mosaicWait) {
LOAD_16(color, offset + (localX >> 8) * 2 + (localY >> 8) * 320, renderer->d.vram);
#ifndef COLOR_16_BIT
unsigned color32 = 0;
color32 |= (color << 9) & 0xF80000;
color32 |= (color << 3) & 0xF8;
color32 |= (color << 6) & 0xF800;
color = color32;
#elif COLOR_5_6_5
uint16_t color16 = 0;
color16 |= (color & 0x001F) << 11;
color16 |= (color & 0x03E0) << 1;
color16 |= (color & 0x7C00) >> 10;
color = color16;
#endif
mosaicWait = mosaicH;
} else {
--mosaicWait;
}
uint32_t current = *pixel;
if (!objwinSlowPath || !(current & FLAG_OBJWIN) != objwinOnly) {
unsigned mergedFlags = flags;
if (current & FLAG_OBJWIN) {
mergedFlags = objwinFlags;
}
if (!variant) {
_compositeBlendObjwin(renderer, pixel, color | mergedFlags, current);
} else if (renderer->blendEffect == BLEND_BRIGHTEN) {
_compositeBlendObjwin(renderer, pixel, _brighten(color, renderer->bldy) | mergedFlags, current);
} else if (renderer->blendEffect == BLEND_DARKEN) {
_compositeBlendObjwin(renderer, pixel, _darken(color, renderer->bldy) | mergedFlags, current);
}
}
}
}

View File

@ -0,0 +1,538 @@
/* Copyright (c) 2013-2015 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 "software-private.h"
#include "gba/gba.h"
#define BACKGROUND_TEXT_SELECT_CHARACTER \
localX = tileX * 8 + inX; \
xBase = localX & 0xF8; \
if (background->size & 1) { \
xBase += (localX & 0x100) << 5; \
} \
screenBase = yBase + (xBase >> 3); \
LOAD_16(mapData, screenBase << 1, vram); \
localY = inY & 0x7; \
if (GBA_TEXT_MAP_VFLIP(mapData)) { \
localY = 7 - localY; \
}
#define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_16(BLEND, OBJWIN) \
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
palette = &mainPalette[paletteData]; \
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
LOAD_32(tileData, charBase, vram); \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
tileData >>= 4 * mod8; \
for (; outX < end; ++outX, ++pixel) { \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
} \
} else { \
for (outX = end - 1; outX >= renderer->start; --outX) { \
uint32_t* pixel = &renderer->row[outX]; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
} \
}
#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_16(BLEND, OBJWIN) \
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
LOAD_32(tileData, charBase, vram); \
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
palette = &mainPalette[paletteData]; \
pixel = &renderer->row[outX]; \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
if (outX < renderer->start) { \
tileData >>= 4 * (renderer->start - outX); \
outX = renderer->start; \
pixel = &renderer->row[outX]; \
} \
for (; outX < renderer->end; ++outX, ++pixel) { \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
} \
} else { \
tileData >>= 4 * (0x8 - mod8); \
int end = renderer->end - 8; \
if (end < -1) { \
end = -1; \
} \
outX = renderer->end - 1; \
pixel = &renderer->row[outX]; \
for (; outX > end; --outX, --pixel) { \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
} \
/* Needed for consistency checks */ \
if (VIDEO_CHECKS) { \
outX = renderer->end; \
pixel = &renderer->row[outX]; \
} \
}
#define DRAW_BACKGROUND_MODE_0_MOSAIC_16(BLEND, OBJWIN) \
x = inX & 7; \
if (mosaicWait) { \
int baseX = x - (mosaicH - mosaicWait); \
if (baseX < 0) { \
int disturbX = (16 + baseX) >> 3; \
inX -= disturbX << 3; \
BACKGROUND_TEXT_SELECT_CHARACTER; \
baseX -= disturbX << 3; \
inX += disturbX << 3; \
} else { \
BACKGROUND_TEXT_SELECT_CHARACTER; \
} \
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
if (UNLIKELY(charBase >= 0x10000)) { \
carryData = 0; \
} else { \
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
palette = &mainPalette[paletteData]; \
LOAD_32(tileData, charBase, vram); \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
tileData >>= 4 * baseX; \
} else { \
tileData >>= 4 * (7 - baseX); \
} \
tileData &= 0xF; \
tileData |= tileData << 4; \
tileData |= tileData << 8; \
tileData |= tileData << 12; \
tileData |= tileData << 16; \
tileData |= tileData << 20; \
tileData |= tileData << 24; \
tileData |= tileData << 28; \
carryData = tileData; \
} \
} \
for (; length; ++tileX) { \
BACKGROUND_TEXT_SELECT_CHARACTER; \
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
tileData = carryData; \
for (; x < 8 && length; ++x, --length) { \
if (!mosaicWait) { \
if (UNLIKELY(charBase >= 0x10000)) { \
carryData = 0; \
} else { \
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
palette = &mainPalette[paletteData]; \
LOAD_32(tileData, charBase, vram); \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
tileData >>= x * 4; \
} else { \
tileData >>= (7 - x) * 4; \
} \
tileData &= 0xF; \
tileData |= tileData << 4; \
tileData |= tileData << 8; \
tileData |= tileData << 12; \
tileData |= tileData << 16; \
tileData |= tileData << 20; \
tileData |= tileData << 24; \
tileData |= tileData << 28; \
carryData = tileData; \
} \
mosaicWait = mosaicH; \
} \
--mosaicWait; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
++pixel; \
} \
x = 0; \
}
#define DRAW_BACKGROUND_MODE_0_TILES_16(BLEND, OBJWIN) \
for (; tileX < tileEnd; ++tileX) { \
BACKGROUND_TEXT_SELECT_CHARACTER; \
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
palette = &mainPalette[paletteData]; \
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) + (localY << 2); \
if (UNLIKELY(charBase >= 0x10000)) { \
pixel += 8; \
continue; \
} \
LOAD_32(tileData, charBase, vram); \
if (tileData) { \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
++pixel; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
++pixel; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
++pixel; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
++pixel; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
++pixel; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
++pixel; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
++pixel; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
++pixel; \
} else { \
pixel += 7; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
--pixel; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
--pixel; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
--pixel; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
--pixel; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
--pixel; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
--pixel; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
--pixel; \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
pixel += 8; \
} \
} else { \
pixel += 8; \
} \
}
#define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_256(BLEND, OBJWIN) \
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \
int end2 = end - 4; \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
int shift = inX & 0x3; \
if (LIKELY(charBase < 0x10000)) { \
if (end2 > outX) { \
LOAD_32(tileData, charBase, vram); \
tileData >>= 8 * shift; \
shift = 0; \
for (; outX < end2; ++outX, ++pixel) { \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
} \
} \
} \
\
if (LIKELY(charBase < 0x10000)) { \
LOAD_32(tileData, charBase + 4, vram); \
tileData >>= 8 * shift; \
for (; outX < end; ++outX, ++pixel) { \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
} \
} \
} else { \
int start = outX; \
outX = end - 1; \
pixel = &renderer->row[outX]; \
if (LIKELY(charBase < 0x10000)) { \
if (end2 > start) { \
LOAD_32(tileData, charBase, vram); \
for (; outX >= end2; --outX, --pixel) { \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
} \
charBase += 4; \
} \
} \
\
if (LIKELY(charBase < 0x10000)) { \
LOAD_32(tileData, charBase, vram); \
for (; outX >= renderer->start; --outX, --pixel) { \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
} \
} \
outX = end; \
pixel = &renderer->row[outX]; \
}
#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_256(BLEND, OBJWIN) \
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \
if (UNLIKELY(charBase >= 0x10000)) { \
return; \
} \
int end = mod8 - 4; \
pixel = &renderer->row[outX]; \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
if (end > 0) { \
LOAD_32(tileData, charBase, vram); \
for (; outX < renderer->end - end; ++outX, ++pixel) { \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
} \
charBase += 4; \
} \
\
LOAD_32(tileData, charBase, vram); \
for (; outX < renderer->end; ++outX, ++pixel) { \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
} \
} else { \
int shift = (8 - mod8) & 0x3; \
int start = outX; \
outX = renderer->end - 1; \
pixel = &renderer->row[outX]; \
if (end > 0) { \
LOAD_32(tileData, charBase, vram); \
tileData >>= 8 * shift; \
for (; outX >= start + 4; --outX, --pixel) { \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
} \
shift = 0; \
} \
\
LOAD_32(tileData, charBase + 4, vram); \
tileData >>= 8 * shift; \
for (; outX >= start; --outX, --pixel) { \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
} \
/* Needed for consistency checks */ \
if (VIDEO_CHECKS) { \
outX = renderer->end; \
pixel = &renderer->row[outX]; \
} \
}
#define DRAW_BACKGROUND_MODE_0_TILES_256(BLEND, OBJWIN) \
for (; tileX < tileEnd; ++tileX) { \
BACKGROUND_TEXT_SELECT_CHARACTER; \
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \
if (UNLIKELY(charBase >= 0x10000)) { \
pixel += 8; \
continue; \
} \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
LOAD_32(tileData, charBase, vram); \
if (tileData) { \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
++pixel; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
++pixel; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
++pixel; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
++pixel; \
} else { \
pixel += 4; \
} \
LOAD_32(tileData, charBase + 4, vram); \
if (tileData) { \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
++pixel; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
++pixel; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
++pixel; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
++pixel; \
} else { \
pixel += 4; \
} \
} else { \
LOAD_32(tileData, charBase + 4, vram); \
if (tileData) { \
pixel += 3; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
--pixel; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
--pixel; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
--pixel; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
} \
pixel += 4; \
LOAD_32(tileData, charBase, vram); \
if (tileData) { \
pixel += 3; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
--pixel; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
--pixel; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
--pixel; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
} \
pixel += 4; \
} \
}
#define DRAW_BACKGROUND_MODE_0_MOSAIC_256(BLEND, OBJWIN) \
for (; tileX < tileEnd; ++tileX) { \
BACKGROUND_TEXT_SELECT_CHARACTER; \
charBase = (background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) + (localY << 3); \
tileData = carryData; \
for (x = 0; x < 8; ++x) { \
if (!mosaicWait) { \
if (UNLIKELY(charBase >= 0x10000)) { \
carryData = 0; \
} else { \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
if (x >= 4) { \
LOAD_32(tileData, charBase + 4, vram); \
tileData >>= (x - 4) * 8; \
} else { \
LOAD_32(tileData, charBase, vram); \
tileData >>= x * 8; \
} \
} else { \
if (x >= 4) { \
LOAD_32(tileData, charBase, vram); \
tileData >>= (7 - x) * 8; \
} else { \
LOAD_32(tileData, charBase + 4, vram); \
tileData >>= (3 - x) * 8; \
} \
} \
tileData &= 0xFF; \
carryData = tileData; \
} \
mosaicWait = mosaicH; \
} \
tileData |= tileData << 8; \
--mosaicWait; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
++pixel; \
} \
}
#define DRAW_BACKGROUND_MODE_0(BPP, BLEND, OBJWIN) \
uint32_t* pixel = &renderer->row[outX]; \
if (background->mosaic && GBAMosaicControlGetBgH(renderer->mosaic)) { \
int mosaicH = GBAMosaicControlGetBgH(renderer->mosaic) + 1; \
int x; \
int mosaicWait = (mosaicH - outX + VIDEO_HORIZONTAL_PIXELS * mosaicH) % mosaicH; \
int carryData = 0; \
paletteData = 0; /* Quiets compiler warning */ \
DRAW_BACKGROUND_MODE_0_MOSAIC_ ## BPP (BLEND, OBJWIN) \
return; \
} \
\
if (inX & 0x7) { \
BACKGROUND_TEXT_SELECT_CHARACTER; \
\
int mod8 = inX & 0x7; \
int end = outX + 0x8 - mod8; \
if (end > renderer->end) { \
end = renderer->end; \
} \
if (UNLIKELY(end == outX)) { \
return; \
} \
if (UNLIKELY(end < outX)) { \
GBALog(0, GBA_LOG_DANGER, "Out of bounds background draw!"); \
return; \
} \
DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_ ## BPP (BLEND, OBJWIN) \
outX = end; \
if (tileX < tileEnd) { \
++tileX; \
} else if (VIDEO_CHECKS && UNLIKELY(tileX > tileEnd)) { \
GBALog(0, GBA_LOG_FATAL, "Invariant doesn't hold in background draw! tileX (%u) > tileEnd (%u)", tileX, tileEnd); \
return; \
} \
length -= end - renderer->start; \
} \
/*! TODO: Make sure these lines can be removed */ \
/*!*/ pixel = &renderer->row[outX]; \
outX += (tileEnd - tileX) * 8; \
/*!*/ if (VIDEO_CHECKS && UNLIKELY(outX > VIDEO_HORIZONTAL_PIXELS)) { \
/*!*/ GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw would occur!"); \
/*!*/ return; \
/*!*/ } \
DRAW_BACKGROUND_MODE_0_TILES_ ## BPP (BLEND, OBJWIN) \
if (length & 0x7) { \
BACKGROUND_TEXT_SELECT_CHARACTER; \
\
int mod8 = length & 0x7; \
if (VIDEO_CHECKS && UNLIKELY(outX + mod8 != renderer->end)) { \
GBALog(0, GBA_LOG_FATAL, "Invariant doesn't hold in background draw!"); \
return; \
} \
DRAW_BACKGROUND_MODE_0_TILE_PREFIX_ ## BPP (BLEND, OBJWIN) \
} \
if (VIDEO_CHECKS && UNLIKELY(&renderer->row[outX] != pixel)) { \
GBALog(0, GBA_LOG_FATAL, "Background draw ended in the wrong place! Diff: %" PRIXPTR, &renderer->row[outX] - pixel); \
} \
if (VIDEO_CHECKS && UNLIKELY(outX > VIDEO_HORIZONTAL_PIXELS)) { \
GBALog(0, GBA_LOG_FATAL, "Out of bounds background draw occurred!"); \
return; \
}
void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
int inX = renderer->start + background->x;
int length = renderer->end - renderer->start;
if (background->mosaic) {
int mosaicV = GBAMosaicControlGetBgV(renderer->mosaic) + 1;
y -= y % mosaicV;
}
int inY = y + background->y;
uint16_t mapData;
unsigned yBase = inY & 0xF8;
if (background->size == 2) {
yBase += inY & 0x100;
} else if (background->size == 3) {
yBase += (inY & 0x100) << 1;
}
yBase = (background->screenBase >> 1) + (yBase << 2);
int localX;
int localY;
unsigned xBase;
int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND;
flags |= FLAG_TARGET_2 * background->target2;
int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed));
objwinFlags |= flags;
flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed));
if (renderer->blda == 0x10 && renderer->bldb == 0) {
flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2);
objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \
}
uint32_t screenBase;
uint32_t charBase;
int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
color_t* mainPalette = renderer->normalPalette;
if (variant) {
mainPalette = renderer->variantPalette;
}
color_t* palette = mainPalette;
PREPARE_OBJWIN;
int outX = renderer->start;
uint32_t tileData;
uint32_t current;
int pixelData;
int paletteData;
int tileX = 0;
int tileEnd = ((length + inX) >> 3) - (inX >> 3);
uint16_t* vram = renderer->d.vram;
if (!objwinSlowPath) {
if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) {
if (!background->multipalette) {
DRAW_BACKGROUND_MODE_0(16, NoBlend, NO_OBJWIN);
} else {
DRAW_BACKGROUND_MODE_0(256, NoBlend, NO_OBJWIN);
}
} else {
if (!background->multipalette) {
DRAW_BACKGROUND_MODE_0(16, Blend, NO_OBJWIN);
} else {
DRAW_BACKGROUND_MODE_0(256, Blend, NO_OBJWIN);
}
}
} else {
if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) {
if (!background->multipalette) {
DRAW_BACKGROUND_MODE_0(16, NoBlend, OBJWIN);
} else {
DRAW_BACKGROUND_MODE_0(256, NoBlend, OBJWIN);
}
} else {
if (!background->multipalette) {
DRAW_BACKGROUND_MODE_0(16, Blend, OBJWIN);
} else {
DRAW_BACKGROUND_MODE_0(256, Blend, OBJWIN);
}
}
}
}

View File

@ -0,0 +1,269 @@
/* Copyright (c) 2013-2015 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 "software-private.h"
#define SPRITE_NORMAL_LOOP(DEPTH, TYPE) \
SPRITE_YBASE_ ## DEPTH(inY); \
unsigned tileData; \
for (; outX < condition; ++outX, inX += xOffset) { \
if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \
continue; \
} \
SPRITE_XBASE_ ## DEPTH(inX); \
SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(inX); \
}
#define SPRITE_MOSAIC_LOOP(DEPTH, TYPE) \
SPRITE_YBASE_ ## DEPTH(inY); \
unsigned tileData; \
if (outX % mosaicH) { \
if (!inX && xOffset > 0) { \
inX = mosaicH - (outX % mosaicH); \
outX += mosaicH - (outX % mosaicH); \
} else if (inX == width - xOffset) { \
inX = mosaicH + (outX % mosaicH); \
outX += mosaicH - (outX % mosaicH); \
} \
} \
for (; outX < condition; ++outX, inX += xOffset) { \
if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \
continue; \
} \
int localX = inX - xOffset * (outX % mosaicH); \
if (localX < 0 || localX > width - 1) { \
continue; \
} \
SPRITE_XBASE_ ## DEPTH(localX); \
SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \
}
#define SPRITE_TRANSFORMED_LOOP(DEPTH, TYPE) \
unsigned tileData; \
for (; outX < x + totalWidth && outX < end; ++outX, ++inX) { \
if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \
continue; \
} \
xAccum += mat.a; \
yAccum += mat.c; \
int localX = (xAccum >> 8) + (width >> 1); \
int localY = (yAccum >> 8) + (height >> 1); \
\
if (localX < 0 || localX >= width || localY < 0 || localY >= height) { \
continue; \
} \
\
SPRITE_YBASE_ ## DEPTH(localY); \
SPRITE_XBASE_ ## DEPTH(localX); \
SPRITE_DRAW_PIXEL_ ## DEPTH ## _ ## TYPE(localX); \
}
#define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2);
#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width >> 1 : 0x80) + (localY & 0x7) * 4;
#define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
current = renderer->spriteLayer[outX]; \
if ((current & FLAG_ORDER_MASK) > flags) { \
if (tileData) { \
renderer->spriteLayer[outX] = palette[tileData] | flags; \
} else if (current != FLAG_UNWRITTEN) { \
renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \
} \
}
#define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
if (tileData) { \
renderer->row[outX] |= FLAG_OBJWIN; \
}
#define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6);
#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width : 0x80) + (localY & 0x7) * 8;
#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
current = renderer->spriteLayer[outX]; \
if ((current & FLAG_ORDER_MASK) > flags) { \
if (tileData) { \
renderer->spriteLayer[outX] = palette[tileData] | flags; \
} else if (current != FLAG_UNWRITTEN) { \
renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \
} \
}
#define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
if (tileData) { \
renderer->row[outX] |= FLAG_OBJWIN; \
}
int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y) {
int width = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][0];
int height = GBAVideoObjSizes[GBAObjAttributesAGetShape(sprite->a) * 4 + GBAObjAttributesBGetSize(sprite->b)][1];
int start = renderer->start;
int end = renderer->end;
uint32_t flags = GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY;
flags |= FLAG_TARGET_1 * ((GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT);
flags |= FLAG_OBJWIN * (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN);
int32_t x = GBAObjAttributesBGetX(sprite->b) << 23;
x >>= 23;
uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1];
unsigned charBase = GBAObjAttributesCGetTile(sprite->c) * 0x20;
int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) {
int target2 = renderer->target2Bd << 4;
target2 |= renderer->bg[0].target2 << (renderer->bg[0].priority);
target2 |= renderer->bg[1].target2 << (renderer->bg[1].priority);
target2 |= renderer->bg[2].target2 << (renderer->bg[2].priority);
target2 |= renderer->bg[3].target2 << (renderer->bg[3].priority);
if (GBAObjAttributesCGetPriority(sprite->c) < target2) {
variant = 0;
}
}
color_t* palette = &renderer->normalPalette[0x100];
if (variant) {
palette = &renderer->variantPalette[0x100];
}
int inY = y - (int) GBAObjAttributesAGetY(sprite->a);
uint32_t current;
if (GBAObjAttributesAIsTransformed(sprite->a)) {
int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a);
int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a);
struct GBAOAMMatrix mat;
LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a);
LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b);
LOAD_16(mat.c, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].c);
LOAD_16(mat.d, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].d);
if (inY < 0) {
inY += 256;
}
int outX = x >= start ? x : start;
int inX = outX - x;
int xAccum = mat.a * (inX - 1 - (totalWidth >> 1)) + mat.b * (inY - (totalHeight >> 1));
int yAccum = mat.c * (inX - 1 - (totalWidth >> 1)) + mat.d * (inY - (totalHeight >> 1));
if (!GBAObjAttributesAIs256Color(sprite->a)) {
palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4];
if (flags & FLAG_OBJWIN) {
SPRITE_TRANSFORMED_LOOP(16, OBJWIN);
} else {
SPRITE_TRANSFORMED_LOOP(16, NORMAL);
}
} else {
if (flags & FLAG_OBJWIN) {
SPRITE_TRANSFORMED_LOOP(256, OBJWIN);
} else {
SPRITE_TRANSFORMED_LOOP(256, NORMAL);
}
}
} else {
int outX = x >= start ? x : start;
int condition = x + width;
int mosaicH = 1;
if (GBAObjAttributesAIsMosaic(sprite->a)) {
mosaicH = GBAMosaicControlGetObjH(renderer->mosaic) + 1;
if (condition % mosaicH) {
condition += mosaicH - (condition % mosaicH);
}
}
if ((int) GBAObjAttributesAGetY(sprite->a) + height - 256 >= 0) {
inY += 256;
}
if (GBAObjAttributesBIsVFlip(sprite->b)) {
inY = height - inY - 1;
}
if (end < condition) {
condition = end;
}
int inX = outX - x;
int xOffset = 1;
if (GBAObjAttributesBIsHFlip(sprite->b)) {
inX = width - inX - 1;
xOffset = -1;
}
if (!GBAObjAttributesAIs256Color(sprite->a)) {
palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4];
if (flags & FLAG_OBJWIN) {
SPRITE_NORMAL_LOOP(16, OBJWIN);
} else if (GBAObjAttributesAIsMosaic(sprite->a)) {
SPRITE_MOSAIC_LOOP(16, NORMAL);
} else {
SPRITE_NORMAL_LOOP(16, NORMAL);
}
} else {
if (flags & FLAG_OBJWIN) {
SPRITE_NORMAL_LOOP(256, OBJWIN);
} else if (GBAObjAttributesAIsMosaic(sprite->a)) {
SPRITE_MOSAIC_LOOP(256, NORMAL);
} else {
SPRITE_NORMAL_LOOP(256, NORMAL);
}
}
}
return 1;
}
void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority) {
int x;
uint32_t* pixel = &renderer->row[renderer->start];
uint32_t flags = FLAG_TARGET_2 * renderer->target2Obj;
int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt);
bool objwinDisable = false;
bool objwinOnly = false;
if (objwinSlowPath) {
objwinDisable = !GBAWindowControlIsObjEnable(renderer->objwin.packed);
objwinOnly = !objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed);
if (objwinDisable && !GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) {
return;
}
if (objwinDisable) {
for (x = renderer->start; x < renderer->end; ++x, ++pixel) {
uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
uint32_t current = *pixel;
if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && !(current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
_compositeBlendObjwin(renderer, pixel, color | flags, current);
}
}
return;
} else if (objwinOnly) {
for (x = renderer->start; x < renderer->end; ++x, ++pixel) {
uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
uint32_t current = *pixel;
if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (current & FLAG_OBJWIN) && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
_compositeBlendObjwin(renderer, pixel, color | flags, current);
}
}
return;
} else {
for (x = renderer->start; x < renderer->end; ++x, ++pixel) {
uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
uint32_t current = *pixel;
if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
_compositeBlendObjwin(renderer, pixel, color | flags, current);
}
}
return;
}
} else if (!GBAWindowControlIsObjEnable(renderer->currentWindow.packed)) {
return;
}
for (x = renderer->start; x < renderer->end; ++x, ++pixel) {
uint32_t color = renderer->spriteLayer[x] & ~FLAG_OBJWIN;
uint32_t current = *pixel;
if ((color & FLAG_UNWRITTEN) != FLAG_UNWRITTEN && (color & FLAG_PRIORITY) >> OFFSET_PRIORITY == priority) {
_compositeBlendNoObjwin(renderer, pixel, color | flags, current);
}
}
}

View File

@ -0,0 +1,320 @@
/* Copyright (c) 2013-2015 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/. */
#ifndef SOFTWARE_PRIVATE_H
#define SOFTWARE_PRIVATE_H
#include "video-software.h"
#ifdef NDEBUG
#define VIDEO_CHECKS false
#else
#define VIDEO_CHECKS true
#endif
void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
void GBAVideoSoftwareRendererDrawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
void GBAVideoSoftwareRendererDrawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
void GBAVideoSoftwareRendererDrawBackgroundMode4(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y);
int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* renderer, struct GBAObj* sprite, int y);
void GBAVideoSoftwareRendererPostprocessSprite(struct GBAVideoSoftwareRenderer* renderer, unsigned priority);
static inline unsigned _brighten(unsigned color, int y);
static inline unsigned _darken(unsigned color, int y);
static unsigned _mix(int weightA, unsigned colorA, int weightB, unsigned colorB);
// We stash the priority on the top bits so we can do a one-operator comparison
// The lower the number, the higher the priority, and sprites take precendence over backgrounds
// We want to do special processing if the color pixel is target 1, however
static inline void _compositeBlendObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) {
if (color >= current) {
if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) {
color = _mix(renderer->blda, current, renderer->bldb, color);
} else {
color = current & 0x00FFFFFF;
}
} else {
color = (color & ~FLAG_TARGET_2) | (current & FLAG_OBJWIN);
}
*pixel = color;
}
static inline void _compositeBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) {
if (color >= current) {
if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) {
color = _mix(renderer->blda, current, renderer->bldb, color);
} else {
color = current & 0x00FFFFFF;
}
} else {
color = color & ~FLAG_TARGET_2;
}
*pixel = color;
}
static inline void _compositeNoBlendObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) {
UNUSED(renderer);
if (color < current) {
*pixel = color | (current & FLAG_OBJWIN);
}
}
static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) {
UNUSED(renderer);
if (color < current) {
*pixel = color;
}
}
#define COMPOSITE_16_OBJWIN(BLEND) \
if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \
unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[paletteData | pixelData] : palette[pixelData]; \
unsigned mergedFlags = flags; \
if (current & FLAG_OBJWIN) { \
mergedFlags = objwinFlags; \
} \
_composite ## BLEND ## Objwin(renderer, pixel, color | mergedFlags, current); \
}
#define COMPOSITE_16_NO_OBJWIN(BLEND) \
_composite ## BLEND ## NoObjwin(renderer, pixel, palette[pixelData] | flags, current);
#define COMPOSITE_256_OBJWIN(BLEND) \
if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { \
unsigned color = (current & FLAG_OBJWIN) ? objwinPalette[pixelData] : palette[pixelData]; \
unsigned mergedFlags = flags; \
if (current & FLAG_OBJWIN) { \
mergedFlags = objwinFlags; \
} \
_composite ## BLEND ## Objwin(renderer, pixel, color | mergedFlags, current); \
}
#define COMPOSITE_256_NO_OBJWIN(BLEND) \
COMPOSITE_16_NO_OBJWIN(BLEND)
#define BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN) \
pixelData = tileData & 0xF; \
current = *pixel; \
if (pixelData && IS_WRITABLE(current)) { \
COMPOSITE_16_ ## OBJWIN (BLEND); \
} \
tileData >>= 4;
#define BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN) \
pixelData = tileData & 0xFF; \
current = *pixel; \
if (pixelData && IS_WRITABLE(current)) { \
COMPOSITE_256_ ## OBJWIN (BLEND); \
} \
tileData >>= 8;
// TODO: Remove UNUSEDs after implementing OBJWIN for modes 3 - 5
#define PREPARE_OBJWIN \
int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt); \
int objwinOnly = 0; \
int objwinForceEnable = 0; \
UNUSED(objwinForceEnable); \
color_t* objwinPalette = renderer->normalPalette; \
UNUSED(objwinPalette); \
if (objwinSlowPath) { \
if (background->target1 && GBAWindowControlIsBlendEnable(renderer->objwin.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN)) { \
objwinPalette = renderer->variantPalette; \
} \
switch (background->index) { \
case 0: \
objwinForceEnable = GBAWindowControlIsBg0Enable(renderer->objwin.packed) && GBAWindowControlIsBg0Enable(renderer->currentWindow.packed); \
objwinOnly = !GBAWindowControlIsBg0Enable(renderer->objwin.packed); \
break; \
case 1: \
objwinForceEnable = GBAWindowControlIsBg1Enable(renderer->objwin.packed) && GBAWindowControlIsBg1Enable(renderer->currentWindow.packed); \
objwinOnly = !GBAWindowControlIsBg1Enable(renderer->objwin.packed); \
break; \
case 2: \
objwinForceEnable = GBAWindowControlIsBg2Enable(renderer->objwin.packed) && GBAWindowControlIsBg2Enable(renderer->currentWindow.packed); \
objwinOnly = !GBAWindowControlIsBg2Enable(renderer->objwin.packed); \
break; \
case 3: \
objwinForceEnable = GBAWindowControlIsBg3Enable(renderer->objwin.packed) && GBAWindowControlIsBg3Enable(renderer->currentWindow.packed); \
objwinOnly = !GBAWindowControlIsBg3Enable(renderer->objwin.packed); \
break; \
} \
}
#define BACKGROUND_BITMAP_INIT \
int32_t x = background->sx + (renderer->start - 1) * background->dx; \
int32_t y = background->sy + (renderer->start - 1) * background->dy; \
int mosaicH = 0; \
int mosaicWait = 0; \
if (background->mosaic) { \
int mosaicV = GBAMosaicControlGetBgV(renderer->mosaic) + 1; \
y -= (inY % mosaicV) * background->dmy; \
x -= (inY % mosaicV) * background->dmx; \
mosaicH = GBAMosaicControlGetBgH(renderer->mosaic); \
mosaicWait = renderer->start % (mosaicH + 1); \
} \
int32_t localX; \
int32_t localY; \
\
int flags = (background->priority << OFFSET_PRIORITY) | (background->index << OFFSET_INDEX) | FLAG_IS_BACKGROUND; \
flags |= FLAG_TARGET_2 * background->target2; \
int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed)); \
objwinFlags |= flags; \
flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); \
if (renderer->blda == 0x10 && renderer->bldb == 0) { \
flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \
objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \
} \
int variant = background->target1 && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); \
color_t* palette = renderer->normalPalette; \
if (variant) { \
palette = renderer->variantPalette; \
} \
UNUSED(palette); \
PREPARE_OBJWIN;
#define BACKGROUND_BITMAP_ITERATE(W, H) \
x += background->dx; \
y += background->dy; \
\
if (x < 0 || y < 0 || (x >> 8) >= W || (y >> 8) >= H) { \
continue; \
} else { \
localX = x; \
localY = y; \
}
static inline unsigned _brighten(unsigned color, int y) {
unsigned c = 0;
unsigned a;
#ifdef COLOR_16_BIT
a = color & 0x1F;
c |= (a + ((0x1F - a) * y) / 16) & 0x1F;
#ifdef COLOR_5_6_5
a = color & 0x7C0;
c |= (a + ((0x7C0 - a) * y) / 16) & 0x7C0;
a = color & 0xF800;
c |= (a + ((0xF800 - a) * y) / 16) & 0xF800;
#else
a = color & 0x3E0;
c |= (a + ((0x3E0 - a) * y) / 16) & 0x3E0;
a = color & 0x7C00;
c |= (a + ((0x7C00 - a) * y) / 16) & 0x7C00;
#endif
#else
a = color & 0xF8;
c |= (a + ((0xF8 - a) * y) / 16) & 0xF8;
a = color & 0xF800;
c |= (a + ((0xF800 - a) * y) / 16) & 0xF800;
a = color & 0xF80000;
c |= (a + ((0xF80000 - a) * y) / 16) & 0xF80000;
#endif
return c;
}
static inline unsigned _darken(unsigned color, int y) {
unsigned c = 0;
unsigned a;
#ifdef COLOR_16_BIT
a = color & 0x1F;
c |= (a - (a * y) / 16) & 0x1F;
#ifdef COLOR_5_6_5
a = color & 0x7C0;
c |= (a - (a * y) / 16) & 0x7C0;
a = color & 0xF800;
c |= (a - (a * y) / 16) & 0xF800;
#else
a = color & 0x3E0;
c |= (a - (a * y) / 16) & 0x3E0;
a = color & 0x7C00;
c |= (a - (a * y) / 16) & 0x7C00;
#endif
#else
a = color & 0xF8;
c |= (a - (a * y) / 16) & 0xF8;
a = color & 0xF800;
c |= (a - (a * y) / 16) & 0xF800;
a = color & 0xF80000;
c |= (a - (a * y) / 16) & 0xF80000;
#endif
return c;
}
static unsigned _mix(int weightA, unsigned colorA, int weightB, unsigned colorB) {
unsigned c = 0;
unsigned a, b;
#ifdef COLOR_16_BIT
#ifdef COLOR_5_6_5
a = colorA & 0xF81F;
b = colorB & 0xF81F;
a |= (colorA & 0x7C0) << 16;
b |= (colorB & 0x7C0) << 16;
c = ((a * weightA + b * weightB) / 16);
if (c & 0x08000000) {
c = (c & ~0x0FC00000) | 0x07C00000;
}
if (c & 0x0020) {
c = (c & ~0x003F) | 0x001F;
}
if (c & 0x10000) {
c = (c & ~0x1F800) | 0xF800;
}
c = (c & 0xF81F) | ((c >> 16) & 0x07C0);
#else
a = colorA & 0x7C1F;
b = colorB & 0x7C1F;
a |= (colorA & 0x3E0) << 16;
b |= (colorB & 0x3E0) << 16;
c = ((a * weightA + b * weightB) / 16);
if (c & 0x04000000) {
c = (c & ~0x07E00000) | 0x03E00000;
}
if (c & 0x0020) {
c = (c & ~0x003F) | 0x001F;
}
if (c & 0x10000) {
c = (c & ~0x1F800) | 0xF800;
}
c = (c & 0x7C1F) | ((c >> 16) & 0x03E0);
#endif
#else
a = colorA & 0xF8;
b = colorB & 0xF8;
c |= ((a * weightA + b * weightB) / 16) & 0x1F8;
if (c & 0x00000100) {
c = 0x000000F8;
}
a = colorA & 0xF800;
b = colorB & 0xF800;
c |= ((a * weightA + b * weightB) / 16) & 0x1F800;
if (c & 0x00010000) {
c = (c & 0x000000F8) | 0x0000F800;
}
a = colorA & 0xF80000;
b = colorB & 0xF80000;
c |= ((a * weightA + b * weightB) / 16) & 0x1F80000;
if (c & 0x01000000) {
c = (c & 0x0000F8F8) | 0x00F80000;
}
#endif
return c;
}
#endif

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,11 @@
#include <errno.h>
#include <fcntl.h>
// Some testing was done here...
// Erase cycles can vary greatly.
// Some games may vary anywhere between about 2000 cycles to up to 30000 cycles. (Observed on a Macronix (09C2) chip).
// Other games vary from very little, with a fairly solid 20500 cycle count. (Observed on a SST (D4BF) chip).
// An average estimation is as follows.
#define FLASH_SETTLE_CYCLES 18000
static void _flashSwitchBank(struct GBASavedata* savedata, int bank);
@ -234,7 +239,9 @@ uint8_t GBASavedataReadFlash(struct GBASavedata* savedata, uint16_t address) {
}
}
if (savedata->dust > 0 && (address >> 12) == savedata->settling) {
--savedata->dust;
// Give some overhead for waitstates and the comparison
// This estimation can probably be improved
savedata->dust -= 10;
return 0x5F;
}
return savedata->currentBank[address];

View File

@ -61,67 +61,71 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
}
}
void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
bool error = false;
if (state->versionMagic != GBA_SAVESTATE_MAGIC) {
GBALog(gba, GBA_LOG_WARN, "Invalid or too new savestate");
return;
error = true;
}
if (state->biosChecksum != gba->biosChecksum) {
GBALog(gba, GBA_LOG_WARN, "Savestate created using a different version of the BIOS");
if (state->cpu.gprs[ARM_PC] < SIZE_BIOS && state->cpu.gprs[ARM_PC] >= 0x20) {
return;
error = true;
}
}
if (gba->memory.rom && (state->id != ((struct GBACartridge*) gba->memory.rom)->id || memcmp(state->title, ((struct GBACartridge*) gba->memory.rom)->title, sizeof(state->title)))) {
GBALog(gba, GBA_LOG_WARN, "Savestate is for a different game");
return;
error = true;
} else if (!gba->memory.rom && state->id != 0) {
GBALog(gba, GBA_LOG_WARN, "Savestate is for a game, but no game loaded");
return;
error = true;
}
if (state->romCrc32 != gba->romCrc32) {
GBALog(gba, GBA_LOG_WARN, "Savestate is for a different version of the game");
}
if (state->cpu.cycles < 0) {
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are negative");
return;
error = true;
}
if (state->video.eventDiff < 0) {
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: video eventDiff is negative");
return;
error = true;
}
if (state->video.nextHblank - state->video.eventDiff < 0) {
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank is negative");
return;
error = true;
}
if (state->timers[0].overflowInterval < 0 || state->timers[1].overflowInterval < 0 || state->timers[2].overflowInterval < 0 || state->timers[3].overflowInterval < 0) {
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: overflowInterval is negative");
return;
error = true;
}
if (state->audio.eventDiff < 0) {
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio eventDiff is negative");
return;
error = true;
}
if (state->audio.ch1.envelopeNextStep < 0 || state->audio.ch1.waveNextStep < 0 || state->audio.ch1.sweepNextStep < 0 || state->audio.ch1.nextEvent < 0) {
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 1 register is negative");
return;
error = true;
}
if (state->audio.ch2.envelopeNextStep < 0 || state->audio.ch2.waveNextStep < 0 || state->audio.ch2.nextEvent < 0) {
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 2 register is negative");
return;
error = true;
}
if (state->audio.ch3.nextEvent < 0) {
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 3 register is negative");
return;
error = true;
}
if (state->audio.ch4.envelopeNextStep < 0 || state->audio.ch4.nextEvent < 0) {
GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 4 register is negative");
return;
error = true;
}
int region = (state->cpu.gprs[ARM_PC] >> BASE_OFFSET);
if ((region == REGION_CART0 || region == REGION_CART1 || region == REGION_CART2) && ((state->cpu.gprs[ARM_PC] - WORD_SIZE_ARM) & SIZE_CART0) >= gba->memory.romSize - WORD_SIZE_ARM) {
GBALog(gba, GBA_LOG_WARN, "Savestate created using a differently sized version of the ROM");
return;
error = true;
}
if (error) {
return false;
}
memcpy(gba->cpu->gprs, state->cpu.gprs, sizeof(gba->cpu->gprs));
gba->cpu->cpsr = state->cpu.cpsr;
@ -166,6 +170,7 @@ void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
if (gba->rr) {
gba->rr->stateLoaded(gba->rr, state);
}
return true;
}
struct VFile* GBAGetState(struct GBA* gba, struct VDir* dir, int slot, bool write) {
@ -218,8 +223,7 @@ static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
struct GBASerializedState state;
uLongf len = sizeof(state);
uncompress((Bytef*) &state, &len, chunk->data, chunk->size);
GBADeserialize(png_get_user_chunk_ptr(png), &state);
return 1;
return GBADeserialize(png_get_user_chunk_ptr(png), &state);
}
static bool _loadPNGState(struct GBA* gba, struct VFile* vf) {
@ -254,6 +258,8 @@ bool GBASaveState(struct GBAThread* threadContext, struct VDir* dir, int slot, b
vf->close(vf);
if (success) {
GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i saved", slot);
} else {
GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i failed to save", slot);
}
return success;
}
@ -268,6 +274,8 @@ bool GBALoadState(struct GBAThread* threadContext, struct VDir* dir, int slot) {
vf->close(vf);
if (success) {
GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i loaded", slot);
} else {
GBALog(threadContext->gba, GBA_LOG_STATUS, "State %i failed to load", slot);
}
return success;
}
@ -304,9 +312,9 @@ bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf) {
if (!state) {
return false;
}
GBADeserialize(gba, state);
bool success = GBADeserialize(gba, state);
vf->unmap(vf, state, sizeof(struct GBASerializedState));
return true;
return success;
}
struct GBASerializedState* GBAAllocateState(void) {

View File

@ -321,7 +321,7 @@ struct VDir;
struct GBAThread;
void GBASerialize(struct GBA* gba, struct GBASerializedState* state);
void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state);
bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state);
bool GBASaveState(struct GBAThread* thread, struct VDir* dir, int slot, bool screenshot);
bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot);

View File

@ -23,6 +23,8 @@
#include <signal.h>
static void _loadGameDir(struct GBAThread* threadContext);
static const float _defaultFPSTarget = 60.f;
#ifndef DISABLE_THREADING
@ -352,18 +354,7 @@ void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread*
threadContext->gameDir = VDirOpen(args->fname);
threadContext->stateDir = threadContext->gameDir;
} else {
threadContext->rom = VFileOpen(args->fname, O_RDONLY);
threadContext->gameDir = 0;
#if USE_LIBZIP
if (!threadContext->gameDir) {
threadContext->gameDir = VDirOpenZip(args->fname, 0);
}
#endif
#if USE_LZMA
if (!threadContext->gameDir) {
threadContext->gameDir = VDirOpen7z(args->fname, 0);
}
#endif
GBAThreadLoadROM(threadContext, args->fname);
}
threadContext->fname = args->fname;
threadContext->patch = VFileOpen(args->patch, O_RDONLY);
@ -398,25 +389,7 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
}
if (threadContext->gameDir) {
threadContext->gameDir->rewind(threadContext->gameDir);
struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir);
while (dirent) {
struct Patch patchTemp;
struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY);
if (!vf) {
dirent = threadContext->gameDir->listNext(threadContext->gameDir);
continue;
}
if (!threadContext->rom && GBAIsROM(vf)) {
threadContext->rom = vf;
} else if (!threadContext->patch && loadPatch(vf, &patchTemp)) {
threadContext->patch = vf;
} else {
vf->close(vf);
}
dirent = threadContext->gameDir->listNext(threadContext->gameDir);
}
_loadGameDir(threadContext);
}
if (!threadContext->rom && !bootBios) {
@ -678,6 +651,67 @@ void GBAThreadPauseFromThread(struct GBAThread* threadContext) {
GBASyncSetVideoSync(&threadContext->sync, frameOn);
}
void GBAThreadLoadROM(struct GBAThread* threadContext, const char* fname) {
threadContext->rom = VFileOpen(fname, O_RDONLY);
threadContext->gameDir = 0;
#if USE_LIBZIP
if (!threadContext->gameDir) {
threadContext->gameDir = VDirOpenZip(fname, 0);
}
#endif
#if USE_LZMA
if (!threadContext->gameDir) {
threadContext->gameDir = VDirOpen7z(fname, 0);
}
#endif
}
static void _loadGameDir(struct GBAThread* threadContext) {
threadContext->gameDir->rewind(threadContext->gameDir);
struct VDirEntry* dirent = threadContext->gameDir->listNext(threadContext->gameDir);
while (dirent) {
struct Patch patchTemp;
struct VFile* vf = threadContext->gameDir->openFile(threadContext->gameDir, dirent->name(dirent), O_RDONLY);
if (!vf) {
dirent = threadContext->gameDir->listNext(threadContext->gameDir);
continue;
}
if (!threadContext->rom && GBAIsROM(vf)) {
threadContext->rom = vf;
} else if (!threadContext->patch && loadPatch(vf, &patchTemp)) {
threadContext->patch = vf;
} else {
vf->close(vf);
}
dirent = threadContext->gameDir->listNext(threadContext->gameDir);
}
}
void GBAThreadReplaceROM(struct GBAThread* threadContext, const char* fname) {
GBAUnloadROM(threadContext->gba);
if (threadContext->rom) {
threadContext->rom->close(threadContext->rom);
threadContext->rom = 0;
}
if (threadContext->save) {
threadContext->save->close(threadContext->save);
threadContext->save = 0;
}
GBAThreadLoadROM(threadContext, fname);
if(threadContext->gameDir) {
_loadGameDir(threadContext);
}
threadContext->fname = fname;
threadContext->save = VDirOptionalOpenFile(threadContext->stateDir, threadContext->fname, "sram", ".sav", O_CREAT | O_RDWR);
GBARaiseIRQ(threadContext->gba, IRQ_GAMEPAK);
GBALoadROM(threadContext->gba, threadContext->rom, threadContext->save, threadContext->fname);
}
#ifdef USE_PTHREADS
struct GBAThread* GBAThreadGetContext(void) {
pthread_once(&_contextOnce, _createTLS);

View File

@ -126,6 +126,9 @@ void GBAThreadTogglePause(struct GBAThread* threadContext);
void GBAThreadPauseFromThread(struct GBAThread* threadContext);
struct GBAThread* GBAThreadGetContext(void);
void GBAThreadLoadROM(struct GBAThread* threadContext, const char* fname);
void GBAThreadReplaceROM(struct GBAThread* threadContext, const char* fname);
#ifdef USE_PNG
void GBAThreadTakeScreenshot(struct GBAThread* threadContext);
#endif

View File

@ -23,6 +23,25 @@ static void GBAVideoDummyRendererDrawScanline(struct GBAVideoRenderer* renderer,
static void GBAVideoDummyRendererFinishFrame(struct GBAVideoRenderer* renderer);
static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels);
const int GBAVideoObjSizes[16][2] = {
{ 8, 8 },
{ 16, 16 },
{ 32, 32 },
{ 64, 64 },
{ 16, 8 },
{ 32, 8 },
{ 32, 16 },
{ 64, 32 },
{ 8, 16 },
{ 8, 32 },
{ 16, 32 },
{ 32, 64 },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 },
{ 0, 0 },
};
static struct GBAVideoRenderer dummyRenderer = {
.init = GBAVideoDummyRendererInit,
.reset = GBAVideoDummyRendererReset,

View File

@ -215,4 +215,6 @@ struct GBASerializedState;
void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state);
void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state);
extern const int GBAVideoObjSizes[16][2];
#endif

View File

@ -42,6 +42,9 @@ GBAApp::GBAApp(int& argc, char* argv[])
QApplication::setApplicationVersion(projectVersion);
Window* w = new Window(&m_configController);
connect(w, &Window::destroyed, [this]() {
m_windows[0] = nullptr;
});
m_windows[0] = w;
#ifndef Q_OS_MAC
@ -79,7 +82,11 @@ Window* GBAApp::newWindow() {
return nullptr;
}
Window* w = new Window(&m_configController, m_multiplayer.attached());
m_windows[m_multiplayer.attached()] = w;
int windowId = m_multiplayer.attached();
connect(w, &Window::destroyed, [this, windowId]() {
m_windows[windowId] = nullptr;
});
m_windows[windowId] = w;
w->setAttribute(Qt::WA_DeleteOnClose);
#ifndef Q_OS_MAC
w->show();

View File

@ -282,17 +282,7 @@ void GameController::openGame(bool biosOnly) {
m_threadContext.gameDir = VDirOpen(m_threadContext.fname);
m_threadContext.stateDir = m_threadContext.gameDir;
} else {
m_threadContext.rom = VFileOpen(m_threadContext.fname, O_RDONLY);
#if USE_LIBZIP
if (!m_threadContext.gameDir) {
m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0);
}
#endif
#if USE_LZMA
if (!m_threadContext.gameDir) {
m_threadContext.gameDir = VDirOpen7z(m_threadContext.fname, 0);
}
#endif
GBAThreadLoadROM(&m_threadContext, m_threadContext.fname);
}
}
@ -325,6 +315,28 @@ void GameController::loadBIOS(const QString& path) {
}
}
void GameController::yankPak() {
if (!m_gameOpen) {
return;
}
threadInterrupt();
GBAYankROM(m_threadContext.gba);
threadContinue();
}
void GameController::replaceGame(const QString& path) {
if (!m_gameOpen) {
return;
}
m_fname = path;
threadInterrupt();
m_threadContext.fname = strdup(m_fname.toLocal8Bit().constData());
GBAThreadReplaceROM(&m_threadContext, m_threadContext.fname);
threadContinue();
}
void GameController::loadPatch(const QString& path) {
if (m_gameOpen) {
closeGame();
@ -561,9 +573,10 @@ void GameController::loadState(int slot) {
}
GBARunOnThread(&m_threadContext, [](GBAThread* context) {
GameController* controller = static_cast<GameController*>(context->userData);
GBALoadState(context, context->stateDir, controller->m_stateSlot);
controller->stateLoaded(context);
controller->frameAvailable(controller->m_drawContext);
if (GBALoadState(context, context->stateDir, controller->m_stateSlot)) {
controller->stateLoaded(context);
controller->frameAvailable(controller->m_drawContext);
}
});
}

View File

@ -97,6 +97,8 @@ signals:
public slots:
void loadGame(const QString& path, bool dirmode = false);
void loadBIOS(const QString& path);
void yankPak();
void replaceGame(const QString& path);
void setSkipBIOS(bool);
void setUseBIOS(bool);
void loadPatch(const QString& path);

View File

@ -68,6 +68,7 @@ Window::Window(ConfigController* config, int playerId, QWidget* parent)
{
setFocusPolicy(Qt::StrongFocus);
setAcceptDrops(true);
setAttribute(Qt::WA_DeleteOnClose);
m_controller = new GameController(this);
m_controller->setInputController(&m_inputController);
m_controller->setOverrides(m_config->overrides());
@ -229,6 +230,24 @@ void Window::selectROM() {
}
}
void Window::replaceROM() {
QStringList formats{
"*.gba",
#ifdef USE_LIBZIP
"*.zip",
#endif
#ifdef USE_LZMA
"*.7z",
#endif
"*.rom",
"*.bin"};
QString filter = tr("Game Boy Advance ROMs (%1)").arg(formats.join(QChar(' ')));
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), filter);
if (!filename.isEmpty()) {
m_controller->replaceGame(filename);
}
}
void Window::selectBIOS() {
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select BIOS"));
if (!filename.isEmpty()) {
@ -614,6 +633,8 @@ void Window::setupMenu(QMenuBar* menubar) {
addControlledAction(fileMenu, fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())), "loadPatch");
addControlledAction(fileMenu, fileMenu->addAction(tr("Boot BIOS"), m_controller, SLOT(bootBIOS())), "bootBIOS");
addControlledAction(fileMenu, fileMenu->addAction(tr("Replace ROM..."), this, SLOT(replaceROM())), "replaceROM");
m_mruMenu = fileMenu->addMenu(tr("Recent"));
fileMenu->addSeparator();
@ -697,6 +718,11 @@ void Window::setupMenu(QMenuBar* menubar) {
connect(shutdown, SIGNAL(triggered()), m_controller, SLOT(closeGame()));
m_gameActions.append(shutdown);
addControlledAction(emulationMenu, shutdown, "shutdown");
QAction* yank = new QAction(tr("Yank game pak"), emulationMenu);
connect(yank, SIGNAL(triggered()), m_controller, SLOT(yankPak()));
m_gameActions.append(yank);
addControlledAction(emulationMenu, yank, "yank");
emulationMenu->addSeparator();
QAction* pause = new QAction(tr("&Pause"), emulationMenu);

View File

@ -65,6 +65,8 @@ public slots:
void loadConfig();
void saveConfig();
void replaceROM();
void importSharkport();
void exportSharkport();

View File

@ -5,6 +5,36 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "vfs.h"
struct VFile* VFileOpen(const char* path, int flags) {
#ifdef USE_VFS_FILE
const char* chflags;
switch (flags & O_ACCMODE) {
case O_WRONLY:
if (flags & O_APPEND) {
chflags = "ab";
} else {
chflags = "wb";
}
break;
case O_RDWR:
if (flags & O_APPEND) {
chflags = "a+b";
} else if (flags & O_TRUNC) {
chflags = "w+b";
} else {
chflags = "r+b";
}
break;
case O_RDONLY:
chflags = "rb";
break;
}
return VFileFOpen(path, chflags);
#else
return VFileOpenFD(path, flags);
#endif
}
ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size) {
ssize_t bytesRead = 0;
while (bytesRead < size - 1) {
@ -50,3 +80,31 @@ ssize_t VFileRead16LE(struct VFile* vf, void* hword) {
}
return r;
}
struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode) {
char path[PATH_MAX];
path[PATH_MAX - 1] = '\0';
struct VFile* vf;
if (!dir) {
if (!realPath) {
return 0;
}
char* dotPoint = strrchr(realPath, '.');
if (dotPoint - realPath + 1 >= PATH_MAX - 1) {
return 0;
}
if (dotPoint > strrchr(realPath, '/')) {
int len = dotPoint - realPath;
strncpy(path, realPath, len);
path[len] = 0;
strncat(path + len, suffix, PATH_MAX - len - 1);
} else {
snprintf(path, PATH_MAX - 1, "%s%s", realPath, suffix);
}
vf = VFileOpen(path, mode);
} else {
snprintf(path, PATH_MAX - 1, "%s%s", prefix, suffix);
vf = dir->openFile(dir, path, mode);
}
return vf;
}

View File

@ -53,6 +53,8 @@ struct VDir {
};
struct VFile* VFileOpen(const char* path, int flags);
struct VFile* VFileOpenFD(const char* path, int flags);
struct VFile* VFileFOpen(const char* path, const char* mode);
struct VFile* VFileFromFD(int fd);
struct VFile* VFileFromMemory(void* mem, size_t size);

View File

@ -190,31 +190,3 @@ const char* _vdeName(struct VDirEntry* vde) {
}
return 0;
}
struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode) {
char path[PATH_MAX];
path[PATH_MAX - 1] = '\0';
struct VFile* vf;
if (!dir) {
if (!realPath) {
return 0;
}
char* dotPoint = strrchr(realPath, '.');
if (dotPoint - realPath + 1 >= PATH_MAX - 1) {
return 0;
}
if (dotPoint > strrchr(realPath, '/')) {
int len = dotPoint - realPath;
strncpy(path, realPath, len);
path[len] = 0;
strncat(path + len, suffix, PATH_MAX - len - 1);
} else {
snprintf(path, PATH_MAX - 1, "%s%s", realPath, suffix);
}
vf = VFileOpen(path, mode);
} else {
snprintf(path, PATH_MAX - 1, "%s%s", prefix, suffix);
vf = dir->openFile(dir, path, mode);
}
return vf;
}

View File

@ -29,7 +29,7 @@ static void _vfdUnmap(struct VFile* vf, void* memory, size_t size);
static void _vfdTruncate(struct VFile* vf, size_t size);
static ssize_t _vfdSize(struct VFile* vf);
struct VFile* VFileOpen(const char* path, int flags) {
struct VFile* VFileOpenFD(const char* path, int flags) {
if (!path) {
return 0;
}

View File

@ -7,6 +7,7 @@
#include "util/memory.h"
#include <errno.h>
#include <stdio.h>
struct VFileFILE {
@ -28,6 +29,9 @@ struct VFile* VFileFOpen(const char* path, const char* mode) {
return 0;
}
FILE* file = fopen(path, mode);
if (!file && errno == ENOENT && strcmp(mode, "r+b") == 0) {
file = fopen(path, "w+b");
}
return VFileFromFILE(file);
}