~half the changes from Sinamas/GSR 2020 updates
This commit is contained in:
parent
3b804b789a
commit
85ee2af0b2
libgambatte
array.hlibgambatte.vcxprojlibgambatte.vcxproj.filters
src
Interrupter.cpparray.hcpu.cppcpu.hinitstate.cppinterrupter.hinterruptrequester.cppinterruptrequester.h
mem
memory.cppmemory.hsavestate.hsound.cppsound.hsound
channel1.cppchannel1.hchannel2.cppchannel2.hchannel3.cppchannel3.hchannel4.cppchannel4.hduty_unit.cppduty_unit.henvelope_unit.cpplength_counter.cpppsgdef.h
tima.cpptima.hvideo
|
@ -0,0 +1,52 @@
|
|||
/***************************************************************************
|
||||
* Copyright (C) 2008 by Sindre Aamås *
|
||||
* sinamas@users.sourceforge.net *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef ARRAY_H
|
||||
#define ARRAY_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
template<typename T>
|
||||
class SimpleArray : Uncopyable {
|
||||
public:
|
||||
explicit SimpleArray(std::size_t size = 0) : a_(size ? new T[size] : 0) {}
|
||||
~SimpleArray() { delete[] defined_ptr(a_); }
|
||||
void reset(std::size_t size = 0) { delete[] defined_ptr(a_); a_ = size ? new T[size] : 0; }
|
||||
T* get() const { return a_; }
|
||||
operator T* () const { return a_; }
|
||||
|
||||
private:
|
||||
T* a_;
|
||||
};
|
||||
|
||||
class Uncopyable {
|
||||
protected:
|
||||
Uncopyable() {}
|
||||
private:
|
||||
Uncopyable(Uncopyable const&);
|
||||
Uncopyable& operator=(Uncopyable const&);
|
||||
};
|
||||
|
||||
template<class T>
|
||||
inline T* defined_ptr(T* t) {
|
||||
typedef char type_is_defined[sizeof * t ? 1 : -1];
|
||||
(void)sizeof(type_is_defined);
|
||||
return t;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -154,9 +154,11 @@
|
|||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="src\array.h" />
|
||||
<ClInclude Include="include\gambatte.h" />
|
||||
<ClInclude Include="include\gbint.h" />
|
||||
<ClInclude Include="include\loadres.h" />
|
||||
<ClInclude Include="src\interrupter.h" />
|
||||
<ClInclude Include="src\cinterface.h" />
|
||||
<ClInclude Include="src\counterdef.h" />
|
||||
<ClInclude Include="src\cpu.h" />
|
||||
|
@ -180,6 +182,7 @@
|
|||
<ClInclude Include="src\sound\envelope_unit.h" />
|
||||
<ClInclude Include="src\sound\length_counter.h" />
|
||||
<ClInclude Include="src\sound\master_disabler.h" />
|
||||
<ClInclude Include="src\sound\psgdef.h" />
|
||||
<ClInclude Include="src\sound\sound_unit.h" />
|
||||
<ClInclude Include="src\sound\static_output_tester.h" />
|
||||
<ClInclude Include="src\tima.h" />
|
||||
|
@ -192,6 +195,7 @@
|
|||
<ClInclude Include="src\video\sprite_mapper.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\Interrupter.cpp" />
|
||||
<ClCompile Include="src\cinterface.cpp" />
|
||||
<ClCompile Include="src\cpu.cpp" />
|
||||
<ClCompile Include="src\gambatte.cpp" />
|
||||
|
|
|
@ -123,6 +123,15 @@
|
|||
<ClInclude Include="src\video\sprite_mapper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\interrupter.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\array.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\sound\psgdef.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\cinterface.cpp">
|
||||
|
@ -203,5 +212,8 @@
|
|||
<ClCompile Include="src\video\sprite_mapper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\Interrupter.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,103 @@
|
|||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#include "interrupter.h"
|
||||
#include "memory.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
Interrupter::Interrupter(unsigned short& sp, unsigned short& pc, unsigned char& opcode, bool& prefetched)
|
||||
: sp_(sp)
|
||||
, pc_(pc)
|
||||
, opcode_(opcode)
|
||||
, prefetched_(prefetched)
|
||||
{
|
||||
}
|
||||
|
||||
void Interrupter::prefetch(unsigned long cc, Memory& mem) {
|
||||
if (!prefetched_) {
|
||||
opcode_ = mem.read(pc_, cc);
|
||||
pc_ = (pc_ + 1) & 0xFFFF;
|
||||
prefetched_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long Interrupter::interrupt(unsigned long cc, Memory& memory) {
|
||||
// undo prefetch (presumably unconditional on hw).
|
||||
if (prefetched_) {
|
||||
pc_ = (pc_ - 1) & 0xFFFF;
|
||||
prefetched_ = false;
|
||||
}
|
||||
cc += 12;
|
||||
sp_ = (sp_ - 1) & 0xFFFF;
|
||||
memory.write(sp_, pc_ >> 8, cc);
|
||||
cc += 4;
|
||||
|
||||
unsigned const pendingIrqs = memory.pendingIrqs(cc);
|
||||
unsigned const n = pendingIrqs & -pendingIrqs;
|
||||
unsigned address;
|
||||
if (n <= 4) {
|
||||
static unsigned char const lut[] = { 0x00, 0x40, 0x48, 0x48, 0x50 };
|
||||
address = lut[n];
|
||||
}
|
||||
else
|
||||
address = 0x50 + n;
|
||||
|
||||
sp_ = (sp_ - 1) & 0xFFFF;
|
||||
memory.write(sp_, pc_ & 0xFF, cc);
|
||||
memory.ackIrq(n, cc);
|
||||
pc_ = address;
|
||||
cc += 4;
|
||||
|
||||
if (address == 0x40 && !gsCodes_.empty())
|
||||
applyVblankCheats(cc, memory);
|
||||
|
||||
return cc;
|
||||
}
|
||||
|
||||
static int asHex(char c) {
|
||||
return c >= 'A' ? c - 'A' + 0xA : c - '0';
|
||||
}
|
||||
|
||||
void Interrupter::setGameShark(std::string const& codes) {
|
||||
std::string code;
|
||||
gsCodes_.clear();
|
||||
|
||||
for (std::size_t pos = 0; pos < codes.length(); pos += code.length() + 1) {
|
||||
code = codes.substr(pos, codes.find(';', pos) - pos);
|
||||
if (code.length() >= 8) {
|
||||
GsCode gs;
|
||||
gs.type = asHex(code[0]) << 4 | asHex(code[1]);
|
||||
gs.value = (asHex(code[2]) << 4 | asHex(code[3])) & 0xFF;
|
||||
gs.address = (asHex(code[4]) << 4
|
||||
| asHex(code[5])
|
||||
| asHex(code[6]) << 12
|
||||
| asHex(code[7]) << 8) & 0xFFFF;
|
||||
gsCodes_.push_back(gs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Interrupter::applyVblankCheats(unsigned long const cc, Memory& memory) {
|
||||
for (std::size_t i = 0, size = gsCodes_.size(); i < size; ++i) {
|
||||
if (gsCodes_[i].type == 0x01)
|
||||
memory.write(gsCodes_[i].address, gsCodes_[i].value, cc);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/***************************************************************************
|
||||
* Copyright (C) 2008 by Sindre Aamås *
|
||||
* sinamas@users.sourceforge.net *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License version 2 as *
|
||||
* published by the Free Software Foundation. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License version 2 for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* version 2 along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef ARRAY_H
|
||||
#define ARRAY_H
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
template<typename T>
|
||||
class SimpleArray : Uncopyable {
|
||||
public:
|
||||
explicit SimpleArray(std::size_t size = 0) : a_(size ? new T[size] : 0) {}
|
||||
~SimpleArray() { delete[] defined_ptr(a_); }
|
||||
void reset(std::size_t size = 0) { delete[] defined_ptr(a_); a_ = size ? new T[size] : 0; }
|
||||
T* get() const { return a_; }
|
||||
operator T* () const { return a_; }
|
||||
|
||||
private:
|
||||
T* a_;
|
||||
};
|
||||
|
||||
class Uncopyable {
|
||||
protected:
|
||||
Uncopyable() {}
|
||||
private:
|
||||
Uncopyable(Uncopyable const&);
|
||||
Uncopyable& operator=(Uncopyable const&);
|
||||
};
|
||||
|
||||
template<class T>
|
||||
inline T* defined_ptr(T* t) {
|
||||
typedef char type_is_defined[sizeof * t ? 1 : -1];
|
||||
(void)sizeof(type_is_defined);
|
||||
return t;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void defined_delete(T* t) { delete defined_ptr(t); }
|
||||
|
||||
struct defined_deleter { template<class T> static void del(T* p) { defined_delete(p); } };
|
||||
|
||||
#endif
|
|
@ -23,7 +23,7 @@
|
|||
namespace gambatte {
|
||||
|
||||
CPU::CPU()
|
||||
: mem_(sp, pc)
|
||||
: mem_(sp, pc, opcode_, prefetched_)
|
||||
, cycleCounter_(0)
|
||||
, pc(0x100)
|
||||
, sp(0xFFFE)
|
||||
|
@ -38,7 +38,8 @@ CPU::CPU()
|
|||
, e(0xD8)
|
||||
, h(0x01)
|
||||
, l(0x4D)
|
||||
, skip_(false)
|
||||
, opcode_(0)
|
||||
, prefetched_(false)
|
||||
, numInterruptAddresses()
|
||||
, tracecallback(0)
|
||||
{
|
||||
|
@ -103,7 +104,13 @@ void CPU::loadState(SaveState const &state) {
|
|||
cf = cfFromF(state.cpu.f);
|
||||
h = state.cpu.h & 0xFF;
|
||||
l = state.cpu.l & 0xFF;
|
||||
skip_ = state.cpu.skip;
|
||||
opcode_ = state.cpu.opcode;
|
||||
prefetched_ = state.cpu.prefetched;
|
||||
if (state.cpu.skip) {
|
||||
opcode_ = mem_.read(pc, cycleCounter_);
|
||||
prefetched_ = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// The main reasons for the use of macros is to more conveniently be able to tweak
|
||||
|
@ -111,12 +118,11 @@ void CPU::loadState(SaveState const &state) {
|
|||
// time they were written GCC had a tendency to not be able to keep hot variables
|
||||
// in regs if you took an address/reference in an inline function.
|
||||
|
||||
#define bc() ( b << 8 | c )
|
||||
#define de() ( d << 8 | e )
|
||||
#define hl() ( h << 8 | l )
|
||||
#define bc() ( b * 0x100u | c )
|
||||
#define de() ( d * 0x100u | e )
|
||||
#define hl() ( h * 0x100u | l )
|
||||
|
||||
#define READ(dest, addr) do { (dest) = mem_.read(addr, cycleCounter); cycleCounter += 4; } while (0)
|
||||
#define PEEK(dest, addr) do { (dest) = mem_.read(addr, cycleCounter); } while (0)
|
||||
#define PC_READ(dest) do { (dest) = mem_.read_excb(pc, cycleCounter, false); pc = (pc + 1) & 0xFFFF; cycleCounter += 4; } while (0)
|
||||
#define PC_READ_FIRST(dest) do { (dest) = mem_.read_excb(pc, cycleCounter, true); pc = (pc + 1) & 0xFFFF; cycleCounter += 4; } while (0)
|
||||
#define FF_READ(dest, addr) do { (dest) = mem_.ff_read(addr, cycleCounter); cycleCounter += 4; } while (0)
|
||||
|
@ -174,7 +180,7 @@ void CPU::loadState(SaveState const &state) {
|
|||
// Rotate 8-bit register right through CF, store old bit0 in CF, old CF value becomes bit7. Reset SF and HCF, Check ZF:
|
||||
#define rr_r(r) do { \
|
||||
unsigned const oldcf = cf & 0x100; \
|
||||
cf = (r) << 8; \
|
||||
cf = (r) * 0x100u; \
|
||||
(r) = zf = ((r) | oldcf) >> 1; \
|
||||
hf2 = 0; \
|
||||
} while (0)
|
||||
|
@ -190,7 +196,7 @@ void CPU::loadState(SaveState const &state) {
|
|||
// sra r (8 cycles):
|
||||
// Shift 8-bit register right, store old bit0 in CF. bit7=old bit7. Reset SF and HCF, Check ZF:
|
||||
#define sra_r(r) do { \
|
||||
cf = (r) << 8; \
|
||||
cf = (r) * 0x100u; \
|
||||
zf = (r) >> 1; \
|
||||
(r) = zf | ((r) & 0x80); \
|
||||
hf2 = 0; \
|
||||
|
@ -200,7 +206,7 @@ void CPU::loadState(SaveState const &state) {
|
|||
// Shift 8-bit register right, store old bit0 in CF. Reset SF and HCF, Check ZF:
|
||||
#define srl_r(r) do { \
|
||||
zf = (r); \
|
||||
cf = (r) << 8; \
|
||||
cf = (r) * 0x100u; \
|
||||
zf >>= 1; \
|
||||
(r) = zf; \
|
||||
hf2 = 0; \
|
||||
|
@ -261,7 +267,7 @@ void CPU::loadState(SaveState const &state) {
|
|||
unsigned const hl = hl(); \
|
||||
unsigned val; \
|
||||
READ(val, hl); \
|
||||
val &= ~(1 << (n)); \
|
||||
val &= ~(1u << (n)); \
|
||||
WRITE(hl, val); \
|
||||
} while (0)
|
||||
|
||||
|
@ -461,9 +467,8 @@ void CPU::loadState(SaveState const &state) {
|
|||
// rst n (16 Cycles):
|
||||
// Push present address onto stack, jump to address n (one of 00h,08h,10h,18h,20h,28h,30h,38h):
|
||||
#define rst_n(n) do { \
|
||||
cycleCounter += 4; \
|
||||
PUSH(pc >> 8, pc & 0xFF); \
|
||||
pc = n; \
|
||||
push_rr(pc >> 8, pc & 0xFF); \
|
||||
pc = (n); \
|
||||
} while (0)
|
||||
|
||||
// ret (16 cycles):
|
||||
|
@ -474,6 +479,17 @@ void CPU::loadState(SaveState const &state) {
|
|||
PC_MOD(high << 8 | low); \
|
||||
} while (0)
|
||||
|
||||
namespace {
|
||||
unsigned long freeze(Memory & mem, unsigned long cc) {
|
||||
mem.freeze(cc);
|
||||
if (cc < mem.nextEventTime()) {
|
||||
unsigned long cycles = mem.nextEventTime() - cc;
|
||||
cc += cycles + (-cycles & 3);
|
||||
}
|
||||
return cc;
|
||||
}
|
||||
}
|
||||
|
||||
void CPU::process(unsigned long const cycles) {
|
||||
mem_.setEndtime(cycleCounter_, cycles);
|
||||
mem_.updateInput();
|
||||
|
@ -524,7 +540,7 @@ void CPU::process(unsigned long const cycles) {
|
|||
result[8] = toF(hf2, cf, zf);
|
||||
result[9] = h;
|
||||
result[10] = l;
|
||||
result[11] = skip_;
|
||||
result[11] = prefetched_;
|
||||
PC_READ_FIRST(opcode);
|
||||
result[12] = opcode;
|
||||
result[13] = mem_.debugGetLY();
|
||||
|
@ -534,9 +550,13 @@ void CPU::process(unsigned long const cycles) {
|
|||
PC_READ_FIRST(opcode);
|
||||
}
|
||||
|
||||
if (skip_) {
|
||||
pc = (pc - 1) & 0xFFFF;
|
||||
skip_ = false;
|
||||
if (!prefetched_) {
|
||||
PC_READ(opcode);
|
||||
}
|
||||
else {
|
||||
opcode = opcode_;
|
||||
cycleCounter += 4;
|
||||
prefetched_ = false;
|
||||
}
|
||||
|
||||
switch (opcode) {
|
||||
|
@ -607,7 +627,7 @@ void CPU::process(unsigned long const cycles) {
|
|||
// rrca (4 cycles):
|
||||
// Rotate 8-bit register A right, store old bit0 in CF. Reset SF, HCF, ZF:
|
||||
case 0x0F:
|
||||
cf = a << 8 | a;
|
||||
cf = a * 0x100u | a;
|
||||
a = cf >> 1 & 0xFF;
|
||||
hf2 = 0;
|
||||
zf = 1;
|
||||
|
@ -616,14 +636,13 @@ void CPU::process(unsigned long const cycles) {
|
|||
// stop (4 cycles):
|
||||
// Halt CPU and LCD display until button pressed:
|
||||
case 0x10:
|
||||
{
|
||||
cycleCounter = mem_.stop(cycleCounter);
|
||||
|
||||
if (cycleCounter < mem_.nextEventTime()) {
|
||||
unsigned long cycles = mem_.nextEventTime() - cycleCounter;
|
||||
cycleCounter += cycles + (-cycles & 3);
|
||||
}
|
||||
PC_READ(opcode_);
|
||||
cycleCounter = mem_.stop(cycleCounter - 4, prefetched_);
|
||||
if (cycleCounter < mem_.nextEventTime()) {
|
||||
unsigned long cycles = mem_.nextEventTime() - cycleCounter;
|
||||
cycleCounter += cycles + (-cycles & 3);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 0x11:
|
||||
|
@ -687,7 +706,7 @@ void CPU::process(unsigned long const cycles) {
|
|||
case 0x1F:
|
||||
{
|
||||
unsigned oldcf = cf & 0x100;
|
||||
cf = a << 8;
|
||||
cf = a * 0x100u;
|
||||
a = (a | oldcf) >> 1;
|
||||
}
|
||||
|
||||
|
@ -1023,14 +1042,12 @@ void CPU::process(unsigned long const cycles) {
|
|||
|
||||
// halt (4 cycles):
|
||||
case 0x76:
|
||||
if (mem_.ff_read(0x0F, cycleCounter) & mem_.ff_read(0xFF, cycleCounter) & 0x1F) {
|
||||
if (mem_.ime())
|
||||
pc = (pc - 1) & 0xFFFF;
|
||||
else
|
||||
skip_ = true;
|
||||
opcode_ = mem_.read(pc, cycleCounter);
|
||||
if (mem_.pendingIrqs(cycleCounter)) {
|
||||
prefetched_ = true;
|
||||
} else {
|
||||
mem_.halt(cycleCounter);
|
||||
|
||||
prefetched_ = mem_.halt(cycleCounter);
|
||||
cycleCounter += 4 + 4 * !mem_.isCgb();
|
||||
if (cycleCounter < mem_.nextEventTime()) {
|
||||
unsigned long cycles = mem_.nextEventTime() - cycleCounter;
|
||||
cycleCounter += cycles + (-cycles & 3);
|
||||
|
@ -1705,13 +1722,7 @@ void CPU::process(unsigned long const cycles) {
|
|||
break;
|
||||
|
||||
case 0xD3: // not specified. should freeze.
|
||||
mem_.di();
|
||||
cycleCounter = mem_.stop(cycleCounter);
|
||||
|
||||
if (cycleCounter < mem_.nextEventTime()) {
|
||||
unsigned long cycles = mem_.nextEventTime() - cycleCounter;
|
||||
cycleCounter += cycles + (-cycles & 3);
|
||||
}
|
||||
cycleCounter = freeze(mem_, cycleCounter);
|
||||
break;
|
||||
|
||||
// call nc,nn (24;12 cycles):
|
||||
|
@ -1779,13 +1790,7 @@ void CPU::process(unsigned long const cycles) {
|
|||
break;
|
||||
|
||||
case 0xDB: // not specified. should freeze.
|
||||
mem_.di();
|
||||
cycleCounter = mem_.stop(cycleCounter);
|
||||
|
||||
if (cycleCounter < mem_.nextEventTime()) {
|
||||
unsigned long cycles = mem_.nextEventTime() - cycleCounter;
|
||||
cycleCounter += cycles + (-cycles & 3);
|
||||
}
|
||||
cycleCounter = freeze(mem_, cycleCounter);
|
||||
break;
|
||||
|
||||
// call z,nn (24;12 cycles):
|
||||
|
@ -1801,13 +1806,7 @@ void CPU::process(unsigned long const cycles) {
|
|||
|
||||
break;
|
||||
case 0xDD: // not specified. should freeze.
|
||||
mem_.di();
|
||||
cycleCounter = mem_.stop(cycleCounter);
|
||||
|
||||
if (cycleCounter < mem_.nextEventTime()) {
|
||||
unsigned long cycles = mem_.nextEventTime() - cycleCounter;
|
||||
cycleCounter += cycles + (-cycles & 3);
|
||||
}
|
||||
cycleCounter = freeze(mem_, cycleCounter);
|
||||
break;
|
||||
|
||||
case 0xDE:
|
||||
|
@ -1846,13 +1845,7 @@ void CPU::process(unsigned long const cycles) {
|
|||
|
||||
case 0xE3:
|
||||
case 0xE4: // not specified. should freeze.
|
||||
mem_.di();
|
||||
cycleCounter = mem_.stop(cycleCounter);
|
||||
|
||||
if (cycleCounter < mem_.nextEventTime()) {
|
||||
unsigned long cycles = mem_.nextEventTime() - cycleCounter;
|
||||
cycleCounter += cycles + (-cycles & 3);
|
||||
}
|
||||
cycleCounter = freeze(mem_, cycleCounter);
|
||||
break;
|
||||
|
||||
case 0xE5:
|
||||
|
@ -1898,16 +1891,10 @@ void CPU::process(unsigned long const cycles) {
|
|||
|
||||
break;
|
||||
|
||||
case 0xEB:
|
||||
case 0xEC:
|
||||
case 0xEB: // not specified. should freeze.
|
||||
case 0xEC: // not specified. should freeze.
|
||||
case 0xED: // not specified. should freeze.
|
||||
mem_.di();
|
||||
cycleCounter = mem_.stop(cycleCounter);
|
||||
|
||||
if (cycleCounter < mem_.nextEventTime()) {
|
||||
unsigned long cycles = mem_.nextEventTime() - cycleCounter;
|
||||
cycleCounter += cycles + (-cycles & 3);
|
||||
}
|
||||
cycleCounter = freeze(mem_, cycleCounter);
|
||||
break;
|
||||
|
||||
case 0xEE:
|
||||
|
@ -1957,13 +1944,7 @@ void CPU::process(unsigned long const cycles) {
|
|||
break;
|
||||
|
||||
case 0xF4: // not specified. should freeze.
|
||||
mem_.di();
|
||||
cycleCounter = mem_.stop(cycleCounter);
|
||||
|
||||
if (cycleCounter < mem_.nextEventTime()) {
|
||||
unsigned long cycles = mem_.nextEventTime() - cycleCounter;
|
||||
cycleCounter += cycles + (-cycles & 3);
|
||||
}
|
||||
cycleCounter = freeze(mem_, cycleCounter);
|
||||
break;
|
||||
|
||||
case 0xF5:
|
||||
|
@ -2027,15 +2008,9 @@ void CPU::process(unsigned long const cycles) {
|
|||
mem_.ei(cycleCounter);
|
||||
break;
|
||||
|
||||
case 0xFC:
|
||||
case 0xFC: // not specified. should freeze.
|
||||
case 0xFD: // not specified. should freeze
|
||||
mem_.di();
|
||||
cycleCounter = mem_.stop(cycleCounter);
|
||||
|
||||
if (cycleCounter < mem_.nextEventTime()) {
|
||||
unsigned long cycles = mem_.nextEventTime() - cycleCounter;
|
||||
cycleCounter += cycles + (-cycles & 3);
|
||||
}
|
||||
cycleCounter = freeze(mem_, cycleCounter);
|
||||
break;
|
||||
case 0xFE:
|
||||
{
|
||||
|
|
|
@ -115,7 +115,8 @@ private:
|
|||
unsigned short sp;
|
||||
unsigned hf1, hf2, zf, cf;
|
||||
unsigned char a, b, c, d, e, /*f,*/ h, l;
|
||||
bool skip_;
|
||||
unsigned char opcode_;
|
||||
bool prefetched_;
|
||||
|
||||
int *interruptAddresses;
|
||||
int numInterruptAddresses;
|
||||
|
|
|
@ -1178,6 +1178,8 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM
|
|||
state.cpu.f = 0;
|
||||
state.cpu.h = 0;
|
||||
state.cpu.l = 0;
|
||||
state.cpu.opcode = 0x00;
|
||||
state.cpu.prefetched = false;
|
||||
state.cpu.skip = false;
|
||||
state.mem.biosMode = true;
|
||||
state.mem.cgbSwitching = false;
|
||||
|
@ -1199,20 +1201,22 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM
|
|||
state.mem.ioamhram.ptr[0x140] = 0;
|
||||
state.mem.ioamhram.ptr[0x144] = 0x00;
|
||||
|
||||
// DIV, TIMA, and the PSG frame sequencer are clocked by bits of the
|
||||
// cycle counter less divLastUpdate (equivalent to a counter that is
|
||||
// reset on DIV write).
|
||||
state.mem.divLastUpdate = 0;
|
||||
state.mem.timaBasetime = 0;
|
||||
state.mem.timaLastUpdate = 0;
|
||||
state.mem.tmatime = disabled_time;
|
||||
state.mem.nextSerialtime = disabled_time;
|
||||
state.mem.lastOamDmaUpdate = disabled_time;
|
||||
state.mem.unhaltTime = disabled_time;
|
||||
state.mem.minIntTime = 0;
|
||||
state.mem.halttime = 0;
|
||||
state.mem.rombank = 1;
|
||||
state.mem.dmaSource = 0;
|
||||
state.mem.dmaDestination = 0;
|
||||
state.mem.rambank = 0;
|
||||
state.mem.oamDmaPos = 0xFE;
|
||||
state.mem.haltHdmaState = 0;
|
||||
state.mem.IME = false;
|
||||
state.mem.halted = false;
|
||||
state.mem.enableRam = false;
|
||||
|
@ -1269,11 +1273,12 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM
|
|||
|
||||
// spu.cycleCounter >> 12 & 7 represents the frame sequencer position.
|
||||
state.spu.cycleCounter = state.cpu.cycleCounter >> 1;
|
||||
state.spu.lastUpdate = 0;
|
||||
|
||||
state.spu.ch1.sweep.counter = SoundUnit::counter_disabled;
|
||||
state.spu.ch1.sweep.shadow = 0;
|
||||
state.spu.ch1.sweep.nr0 = 0;
|
||||
state.spu.ch1.sweep.negging = false;
|
||||
state.spu.ch1.sweep.neg = false;
|
||||
state.spu.ch1.duty.nextPosUpdate = SoundUnit::counter_disabled;
|
||||
state.spu.ch1.duty.pos = 0;
|
||||
state.spu.ch1.duty.high = false;
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// Copyright (C) 2007 by sinamas <sinamas at users.sourceforge.net>
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License version 2 as
|
||||
// published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License version 2 for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// version 2 along with this program; if not, write to the
|
||||
// Free Software Foundation, Inc.,
|
||||
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
|
||||
#ifndef INTERRUPTER_H
|
||||
#define INTERRUPTER_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
struct GsCode {
|
||||
unsigned short address;
|
||||
unsigned char value;
|
||||
unsigned char type;
|
||||
};
|
||||
|
||||
class Memory;
|
||||
|
||||
class Interrupter {
|
||||
public:
|
||||
Interrupter(unsigned short& sp, unsigned short& pc, unsigned char& opcode, bool& prefetched);
|
||||
void prefetch(unsigned long cc, Memory& mem);
|
||||
unsigned long interrupt(unsigned long cycleCounter, Memory& memory);
|
||||
void setGameShark(std::string const& codes);
|
||||
|
||||
private:
|
||||
unsigned short& sp_;
|
||||
unsigned short& pc_;
|
||||
unsigned char& opcode_;
|
||||
bool& prefetched_;
|
||||
std::vector<GsCode> gsCodes_;
|
||||
|
||||
void applyVblankCheats(unsigned long cc, Memory& mem);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -83,9 +83,14 @@ void InterruptRequester::flagIrq(unsigned bit) {
|
|||
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
|
||||
}
|
||||
|
||||
void InterruptRequester::ackIrq(unsigned bit) {
|
||||
ifreg_ ^= bit;
|
||||
di();
|
||||
void InterruptRequester::flagIrq(unsigned bit, unsigned long cc) {
|
||||
unsigned const prevPending = pendingIrqs();
|
||||
ifreg_ |= bit;
|
||||
|
||||
if (!prevPending && pendingIrqs() && intFlags_.imeOrHalted()) {
|
||||
minIntTime_ = std::max(minIntTime_, cc);
|
||||
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
|
||||
}
|
||||
}
|
||||
|
||||
void InterruptRequester::setIereg(unsigned iereg) {
|
||||
|
@ -108,6 +113,13 @@ void InterruptRequester::setIfreg(unsigned ifreg) {
|
|||
}
|
||||
}
|
||||
|
||||
void InterruptRequester::setMinIntTime(unsigned long cc) {
|
||||
minIntTime_ = cc;
|
||||
|
||||
if (eventTimes_.value(intevent_interrupts) < minIntTime_)
|
||||
eventTimes_.setValue<intevent_interrupts>(minIntTime_);
|
||||
}
|
||||
|
||||
SYNCFUNC(InterruptRequester)
|
||||
{
|
||||
SSS(eventTimes_);
|
||||
|
|
|
@ -52,9 +52,11 @@ public:
|
|||
void halt();
|
||||
void unhalt();
|
||||
void flagIrq(unsigned bit);
|
||||
void ackIrq(unsigned bit);
|
||||
void flagIrq(unsigned bit, unsigned long cc);
|
||||
void ackIrq(unsigned bit) { ifreg_ &= ~bit; }
|
||||
void setIereg(unsigned iereg);
|
||||
void setIfreg(unsigned ifreg);
|
||||
void setMinIntTime(unsigned long cc);
|
||||
|
||||
IntEventId minEventId() const { return static_cast<IntEventId>(eventTimes_.min()); }
|
||||
unsigned long minEventTime() const { return eventTimes_.minValue(); }
|
||||
|
@ -71,9 +73,9 @@ private:
|
|||
bool halted() const { return flags_ & flag_halted; }
|
||||
bool imeOrHalted() const { return flags_; }
|
||||
void setIme() { flags_ |= flag_ime; }
|
||||
void unsetIme() { flags_ &= ~flag_ime; }
|
||||
void unsetIme() { flags_ &= ~(1u * flag_ime); }
|
||||
void setHalted() { flags_ |= flag_halted; }
|
||||
void unsetHalted() { flags_ &= ~flag_halted; }
|
||||
void unsetHalted() { flags_ &= ~(1u * flag_halted); }
|
||||
void set(bool ime, bool halted) { flags_ = halted * flag_halted + ime * flag_ime; }
|
||||
|
||||
private:
|
||||
|
|
|
@ -23,18 +23,18 @@
|
|||
#include <cstring>
|
||||
#include <fstream>
|
||||
|
||||
namespace gambatte {
|
||||
using namespace gambatte;
|
||||
|
||||
namespace {
|
||||
|
||||
static unsigned toMulti64Rombank(unsigned rombank) {
|
||||
unsigned toMulti64Rombank(unsigned rombank) {
|
||||
return (rombank >> 1 & 0x30) | (rombank & 0xF);
|
||||
}
|
||||
|
||||
class DefaultMbc : public Mbc {
|
||||
public:
|
||||
virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned addr, unsigned bank) const {
|
||||
return (addr< 0x4000) == (bank == 0);
|
||||
return (addr < rombank_size()) == (bank == 0);
|
||||
}
|
||||
|
||||
virtual void SyncState(NewState *ns, bool isReader)
|
||||
|
@ -50,8 +50,12 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual unsigned char curRomBank() const {
|
||||
return 1;
|
||||
}
|
||||
|
||||
virtual void romWrite(unsigned const p, unsigned const data, unsigned long const /*cc*/) {
|
||||
if (p < 0x2000) {
|
||||
if (p < rambank_size()) {
|
||||
enableRam_ = (data & 0xF) == 0xA;
|
||||
memptrs_.setRambank(enableRam_ ? MemPtrs::read_en | MemPtrs::write_en : 0, 0);
|
||||
}
|
||||
|
@ -73,12 +77,12 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
static inline unsigned rambanks(MemPtrs const &memptrs) {
|
||||
return std::size_t(memptrs.rambankdataend() - memptrs.rambankdata()) / 0x2000;
|
||||
inline unsigned rambanks(MemPtrs const &memptrs) {
|
||||
return (memptrs.rambankdataend() - memptrs.rambankdata()) / rambank_size();
|
||||
}
|
||||
|
||||
static inline unsigned rombanks(MemPtrs const &memptrs) {
|
||||
return std::size_t(memptrs.romdataend() - memptrs.romdata() ) / 0x4000;
|
||||
inline unsigned rombanks(MemPtrs const &memptrs) {
|
||||
return (memptrs.romdataend() - memptrs.romdata()) / rombank_size();
|
||||
}
|
||||
|
||||
class Mbc1 : public DefaultMbc {
|
||||
|
@ -92,6 +96,10 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual unsigned char curRomBank() const {
|
||||
return rombank_;
|
||||
}
|
||||
|
||||
virtual void romWrite(unsigned const p, unsigned const data, unsigned long const /*cc*/) {
|
||||
switch (p >> 13 & 3) {
|
||||
case 0:
|
||||
|
@ -113,8 +121,7 @@ public:
|
|||
|
||||
break;
|
||||
case 3:
|
||||
// Pretty sure this should take effect immediately, but I have a policy not to change old behavior
|
||||
// unless I have something (eg. a verified test or a game) that justifies it.
|
||||
// Should this take effect immediately rather?
|
||||
rambankMode_ = data & 1;
|
||||
break;
|
||||
}
|
||||
|
@ -143,7 +150,7 @@ private:
|
|||
rambank_ & (rambanks(memptrs_) - 1));
|
||||
}
|
||||
|
||||
void setRombank() const { memptrs_.setRombank(adjustedRombank(rombank_) & (rombanks(memptrs_) - 1)); }
|
||||
void setRombank() const { memptrs_.setRombank(rombank_ & (rombanks(memptrs_) - 1)); }
|
||||
|
||||
|
||||
public:
|
||||
|
@ -166,6 +173,10 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual unsigned char curRomBank() const {
|
||||
return rombank_;
|
||||
}
|
||||
|
||||
virtual void romWrite(unsigned const p, unsigned const data, unsigned long const /*cc*/) {
|
||||
switch (p >> 13 & 3) {
|
||||
case 0:
|
||||
|
@ -238,6 +249,10 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual unsigned char curRomBank() const {
|
||||
return rombank_;
|
||||
}
|
||||
|
||||
virtual void romWrite(unsigned const p, unsigned const data, unsigned long const /*cc*/) {
|
||||
switch (p & 0x6100) {
|
||||
case 0x0000:
|
||||
|
@ -282,6 +297,10 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual unsigned char curRomBank() const {
|
||||
return rombank_;
|
||||
}
|
||||
|
||||
virtual void romWrite(unsigned const p, unsigned const data, unsigned long const cc) {
|
||||
switch (p >> 13 & 3) {
|
||||
case 0:
|
||||
|
@ -459,14 +478,12 @@ private:
|
|||
unsigned char rambank_;
|
||||
bool enableRam_;
|
||||
|
||||
static unsigned adjustedRombank(unsigned bank) { return bank; }
|
||||
|
||||
void setRambank() const {
|
||||
memptrs_.setRambank(enableRam_ ? MemPtrs::read_en | MemPtrs::write_en : 0,
|
||||
rambank_ & (rambanks(memptrs_) - 1));
|
||||
}
|
||||
|
||||
void setRombank() const { memptrs_.setRombank(adjustedRombank(rombank_) & (rombanks(memptrs_) - 1)); }
|
||||
void setRombank() const { memptrs_.setRombank(rombank_ & (rombanks(memptrs_) - 1)); }
|
||||
|
||||
public:
|
||||
virtual void SyncState(NewState *ns, bool isReader)
|
||||
|
@ -477,12 +494,76 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
static bool hasRtc(unsigned headerByte0x147) {
|
||||
std::string stripExtension(std::string const& str) {
|
||||
std::string::size_type const lastDot = str.find_last_of('.');
|
||||
std::string::size_type const lastSlash = str.find_last_of('/');
|
||||
|
||||
if (lastDot != std::string::npos && (lastSlash == std::string::npos || lastSlash < lastDot))
|
||||
return str.substr(0, lastDot);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string stripDir(std::string const& str) {
|
||||
std::string::size_type const lastSlash = str.find_last_of('/');
|
||||
if (lastSlash != std::string::npos)
|
||||
return str.substr(lastSlash + 1);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void enforce8bit(unsigned char* data, std::size_t size) {
|
||||
if (static_cast<unsigned char>(0x100))
|
||||
while (size--)
|
||||
*data++ &= 0xFF;
|
||||
}
|
||||
|
||||
unsigned pow2ceil(unsigned n) {
|
||||
--n;
|
||||
n |= n >> 1;
|
||||
n |= n >> 2;
|
||||
n |= n >> 4;
|
||||
n |= n >> 8;
|
||||
++n;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
bool presumedMulti64Mbc1(unsigned char const header[], unsigned rombanks) {
|
||||
return header[0x147] == 1 && header[0x149] == 0 && rombanks == 64;
|
||||
}
|
||||
|
||||
bool hasBattery(unsigned char headerByte0x147) {
|
||||
switch (headerByte0x147) {
|
||||
case 0x03:
|
||||
case 0x06:
|
||||
case 0x09:
|
||||
case 0x0F:
|
||||
case 0x10:
|
||||
case 0x13:
|
||||
case 0x1B:
|
||||
case 0x1E:
|
||||
case 0xFE: // huc3
|
||||
case 0xFF:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasRtc(unsigned headerByte0x147) {
|
||||
switch (headerByte0x147) {
|
||||
case 0x0F:
|
||||
case 0x10: return true;
|
||||
default: return false;
|
||||
case 0x10:
|
||||
case 0xFE: // huc3
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int asHex(char c) {
|
||||
return c >= 'A' ? c - 'A' + 0xA : c - '0';
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -503,23 +584,6 @@ void Cartridge::loadState(SaveState const &state) {
|
|||
mbc_->loadState(state.mem);
|
||||
}
|
||||
|
||||
static void enforce8bit(unsigned char *data, std::size_t size) {
|
||||
if (static_cast<unsigned char>(0x100))
|
||||
while (size--)
|
||||
*data++ &= 0xFF;
|
||||
}
|
||||
|
||||
static unsigned pow2ceil(unsigned n) {
|
||||
--n;
|
||||
n |= n >> 1;
|
||||
n |= n >> 2;
|
||||
n |= n >> 4;
|
||||
n |= n >> 8;
|
||||
++n;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static bool isMbc2(unsigned char h147) { return h147 == 5 || h147 == 6; }
|
||||
|
||||
static unsigned numRambanksFromH14x(unsigned char h147, unsigned char h149) {
|
||||
|
@ -532,10 +596,6 @@ static unsigned numRambanksFromH14x(unsigned char h147, unsigned char h149) {
|
|||
return 4;
|
||||
}
|
||||
|
||||
static bool presumedMulti64Mbc1(unsigned char const header[], unsigned rombanks) {
|
||||
return header[0x147] == 1 && header[0x149] == 0 && rombanks == 64;
|
||||
}
|
||||
|
||||
LoadRes Cartridge::loadROM(char const *romfiledata, unsigned romfilelength, bool const forceDmg, bool const multicartCompat) {
|
||||
enum Cartridgetype { type_plain,
|
||||
type_mbc1,
|
||||
|
@ -610,17 +670,17 @@ LoadRes Cartridge::loadROM(char const *romfiledata, unsigned romfilelength, bool
|
|||
cgb = !forceDmg;
|
||||
}
|
||||
std::size_t const filesize = romfilelength;
|
||||
rombanks = std::max(pow2ceil(filesize / 0x4000), 2u);
|
||||
rombanks = std::max(pow2ceil(filesize / rombank_size()), 2u);
|
||||
|
||||
mbc_.reset();
|
||||
memptrs_.reset(rombanks, rambanks, cgb ? 8 : 2);
|
||||
rtc_.set(false, 0);
|
||||
|
||||
std::memcpy(memptrs_.romdata(), romfiledata, (filesize / 0x4000) * 0x4000ul);
|
||||
std::memset(memptrs_.romdata() + filesize / 0x4000 * 0x4000ul,
|
||||
std::memcpy(memptrs_.romdata(), romfiledata, (filesize / rombank_size() * rombank_size()));
|
||||
std::memset(memptrs_.romdata() + filesize / rombank_size() * rombank_size(),
|
||||
0xFF,
|
||||
(rombanks - filesize / 0x4000) * 0x4000ul);
|
||||
enforce8bit(memptrs_.romdata(), rombanks * 0x4000ul);
|
||||
(rombanks - filesize / rombank_size()) * rombank_size());
|
||||
enforce8bit(memptrs_.romdata(), rombanks * rombank_size());
|
||||
|
||||
switch (type) {
|
||||
case type_plain: mbc_.reset(new Mbc0(memptrs_)); break;
|
||||
|
@ -642,21 +702,6 @@ LoadRes Cartridge::loadROM(char const *romfiledata, unsigned romfilelength, bool
|
|||
return LOADRES_OK;
|
||||
}
|
||||
|
||||
static bool hasBattery(unsigned char headerByte0x147) {
|
||||
switch (headerByte0x147) {
|
||||
case 0x03:
|
||||
case 0x06:
|
||||
case 0x09:
|
||||
case 0x0F:
|
||||
case 0x10:
|
||||
case 0x13:
|
||||
case 0x1B:
|
||||
case 0x1E:
|
||||
case 0xFF: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Cartridge::loadSavedata(char const *data, unsigned long const cc) {
|
||||
if (hasBattery(memptrs_.romdata()[0x147])) {
|
||||
int length = memptrs_.rambankdataend() - memptrs_.rambankdata();
|
||||
|
@ -747,5 +792,3 @@ SYNCFUNC(Cartridge)
|
|||
SSS(rtc_);
|
||||
TSS(mbc_);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace gambatte {
|
|||
class Mbc {
|
||||
public:
|
||||
virtual ~Mbc() {}
|
||||
virtual unsigned char curRomBank() const = 0;
|
||||
virtual void romWrite(unsigned P, unsigned data, unsigned long cycleCounter) = 0;
|
||||
virtual void loadState(SaveState::Mem const &ss) = 0;
|
||||
virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned address, unsigned rombank) const = 0;
|
||||
|
@ -62,10 +63,11 @@ public:
|
|||
unsigned char * wsrambankptr() const { return memptrs_.wsrambankptr(); }
|
||||
unsigned char * vrambankptr() const { return memptrs_.vrambankptr(); }
|
||||
OamDmaSrc oamDmaSrc() const { return memptrs_.oamDmaSrc(); }
|
||||
bool isInOamDmaConflictArea(unsigned p) const { return memptrs_.isInOamDmaConflictArea(p); }
|
||||
void setVrambank(unsigned bank) { memptrs_.setVrambank(bank); }
|
||||
void setWrambank(unsigned bank) { memptrs_.setWrambank(bank); }
|
||||
void setOamDmaSrc(OamDmaSrc oamDmaSrc) { memptrs_.setOamDmaSrc(oamDmaSrc); }
|
||||
unsigned curRomBank() const { return memptrs_.curRomBank(); }
|
||||
unsigned char curRomBank() const { return mbc_->curRomBank(); }
|
||||
void mbcWrite(unsigned addr, unsigned data, unsigned long const cc) { mbc_->romWrite(addr, data, cc); }
|
||||
bool isCgb() const { return gambatte::isCgb(memptrs_); }
|
||||
void resetCc(unsigned long const oldCc, unsigned long const newCc) { time_.resetCc(oldCc, newCc); }
|
||||
|
|
|
@ -20,7 +20,56 @@
|
|||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace gambatte {
|
||||
using namespace gambatte;
|
||||
|
||||
namespace {
|
||||
|
||||
template <OamDmaSrc src, bool cgb> struct OamDmaConflictMap;
|
||||
template <bool cgb> struct OamDmaConflictMap<oam_dma_src_rom, cgb> { enum { r = 0xFCFF }; };
|
||||
template <bool cgb> struct OamDmaConflictMap<oam_dma_src_sram, cgb> { enum { r = 0xFCFF }; };
|
||||
template <bool cgb> struct OamDmaConflictMap<oam_dma_src_vram, cgb> { enum { r = 0x0300 }; };
|
||||
template <bool cgb> struct OamDmaConflictMap<oam_dma_src_wram, cgb> { enum { r = cgb ? 0xF000 : 0xFCFF }; };
|
||||
template <bool cgb> struct OamDmaConflictMap<oam_dma_src_invalid, cgb> { enum { r = cgb ? 0xFCFF : 0x0000 }; };
|
||||
|
||||
template <bool cgb>
|
||||
bool isInOamDmaConflictArea(OamDmaSrc src, unsigned p)
|
||||
{
|
||||
static unsigned short const m[] = {
|
||||
OamDmaConflictMap<oam_dma_src_rom, cgb>::r,
|
||||
OamDmaConflictMap<oam_dma_src_sram, cgb>::r,
|
||||
OamDmaConflictMap<oam_dma_src_vram, cgb>::r,
|
||||
OamDmaConflictMap<oam_dma_src_wram, cgb>::r,
|
||||
OamDmaConflictMap<oam_dma_src_invalid, cgb>::r,
|
||||
0 };
|
||||
return p < mm_oam_begin && (m[src] >> (p >> 12) & 1);
|
||||
}
|
||||
|
||||
template <OamDmaSrc src, bool cgb>
|
||||
void disconnectOamDmaAreas(unsigned char const* (&rmem)[0x10], unsigned char* (&wmem)[0x10])
|
||||
{
|
||||
if (OamDmaConflictMap<src, cgb>::r & 0x00FF)
|
||||
std::fill_n(rmem, 8, static_cast<unsigned char*>(0));
|
||||
if (OamDmaConflictMap<src, cgb>::r & 0x0C00)
|
||||
rmem[0xB] = rmem[0xA] = wmem[0xB] = wmem[0xA] = 0;
|
||||
if (OamDmaConflictMap<src, cgb>::r & 0x7000)
|
||||
rmem[0xE] = rmem[0xD] = rmem[0xC] = wmem[0xE] = wmem[0xD] = wmem[0xC] = 0;
|
||||
}
|
||||
|
||||
template <bool cgb>
|
||||
void disconnectOamDmaAreas(unsigned char const* (&rmem)[0x10], unsigned char* (&wmem)[0x10],
|
||||
OamDmaSrc src)
|
||||
{
|
||||
switch (src) {
|
||||
case oam_dma_src_rom: disconnectOamDmaAreas<oam_dma_src_rom, cgb>(rmem, wmem); break;
|
||||
case oam_dma_src_sram: disconnectOamDmaAreas<oam_dma_src_sram, cgb>(rmem, wmem); break;
|
||||
case oam_dma_src_vram: disconnectOamDmaAreas<oam_dma_src_vram, cgb>(rmem, wmem); break;
|
||||
case oam_dma_src_wram: disconnectOamDmaAreas<oam_dma_src_wram, cgb>(rmem, wmem); break;
|
||||
case oam_dma_src_invalid: disconnectOamDmaAreas<oam_dma_src_invalid, cgb>(rmem, wmem); break;
|
||||
case oam_dma_src_off: break;
|
||||
}
|
||||
}
|
||||
|
||||
} // unnamed namespace.
|
||||
|
||||
MemPtrs::MemPtrs()
|
||||
: rmem_()
|
||||
|
@ -30,7 +79,6 @@ MemPtrs::MemPtrs()
|
|||
, vrambankptr_(0)
|
||||
, rsrambankptr_(0)
|
||||
, wsrambankptr_(0)
|
||||
, memchunk_(0)
|
||||
, rambankdata_(0)
|
||||
, wramdataend_(0)
|
||||
, oamDmaSrc_(oam_dma_src_off)
|
||||
|
@ -39,32 +87,27 @@ MemPtrs::MemPtrs()
|
|||
{
|
||||
}
|
||||
|
||||
MemPtrs::~MemPtrs() {
|
||||
delete []memchunk_;
|
||||
}
|
||||
|
||||
void MemPtrs::reset(unsigned const rombanks, unsigned const rambanks, unsigned const wrambanks) {
|
||||
delete []memchunk_;
|
||||
memchunk_len =
|
||||
0x4000
|
||||
+ rombanks * 0x4000ul
|
||||
+ 0x4000
|
||||
+ rambanks * 0x2000ul
|
||||
+ wrambanks * 0x1000ul
|
||||
+ 0x4000;
|
||||
memchunk_ = new unsigned char[memchunk_len];
|
||||
int const num_disabled_ram_areas = 2;
|
||||
memchunk_.reset(
|
||||
pre_rom_pad_size()
|
||||
+ rombanks * rombank_size()
|
||||
+ max_num_vrambanks * vrambank_size()
|
||||
+ rambanks * rambank_size()
|
||||
+ wrambanks * wrambank_size()
|
||||
+ num_disabled_ram_areas * rambank_size());
|
||||
|
||||
romdata_[0] = romdata();
|
||||
rambankdata_ = romdata_[0] + rombanks * 0x4000ul + 0x4000;
|
||||
wramdata_[0] = rambankdata_ + rambanks * 0x2000ul;
|
||||
wramdataend_ = wramdata_[0] + wrambanks * 0x1000ul;
|
||||
rambankdata_ = romdata_[0] + rombanks * rombank_size() + max_num_vrambanks * vrambank_size();
|
||||
wramdata_[0] = rambankdata_ + rambanks * rambank_size();
|
||||
wramdataend_ = wramdata_[0] + wrambanks * wrambank_size();
|
||||
|
||||
std::memset(rdisabledRamw(), 0xFF, 0x2000);
|
||||
std::fill_n(rdisabledRamw(), rambank_size(), 0xFF);
|
||||
|
||||
oamDmaSrc_ = oam_dma_src_off;
|
||||
rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0];
|
||||
rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000;
|
||||
rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000;
|
||||
rmem_[0xC] = wmem_[0xC] = wramdata_[0] - mm_wram_begin;
|
||||
rmem_[0xE] = wmem_[0xE] = wramdata_[0] - mm_wram_mirror_begin;
|
||||
setRombank(1);
|
||||
setRambank(0, 0);
|
||||
setVrambank(0);
|
||||
|
@ -76,38 +119,40 @@ void MemPtrs::reset(unsigned const rombanks, unsigned const rambanks, unsigned c
|
|||
}
|
||||
|
||||
void MemPtrs::setRombank0(unsigned bank) {
|
||||
romdata_[0] = romdata() + bank * 0x4000ul;
|
||||
romdata_[0] = romdata() + bank * rombank_size();
|
||||
rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0];
|
||||
disconnectOamDmaAreas();
|
||||
}
|
||||
|
||||
void MemPtrs::setRombank(unsigned bank) {
|
||||
curRomBank_ = bank;
|
||||
romdata_[1] = romdata() + bank * 0x4000ul - 0x4000;
|
||||
romdata_[1] = romdata() + bank * rombank_size() - mm_rom1_begin;
|
||||
rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1];
|
||||
disconnectOamDmaAreas();
|
||||
}
|
||||
|
||||
void MemPtrs::setRambank(unsigned const flags, unsigned const rambank) {
|
||||
unsigned char *srambankptr = 0;
|
||||
unsigned char* srambankptr = 0;
|
||||
if (!(flags & rtc_en)) {
|
||||
srambankptr = rambankdata() != rambankdataend()
|
||||
? rambankdata_ + rambank * 0x2000ul - 0xA000
|
||||
: wdisabledRam() - 0xA000;
|
||||
? rambankdata_ + rambank * rambank_size() - mm_sram_begin
|
||||
: wdisabledRam() - mm_sram_begin;
|
||||
}
|
||||
|
||||
rsrambankptr_ = (flags & read_en) && srambankptr != wdisabledRam() - 0xA000
|
||||
? srambankptr
|
||||
: rdisabledRamw() - 0xA000;
|
||||
wsrambankptr_ = flags & write_en ? srambankptr : wdisabledRam() - 0xA000;
|
||||
rsrambankptr_ = (flags & read_en) && srambankptr != wdisabledRam() - mm_sram_begin
|
||||
? srambankptr
|
||||
: rdisabledRamw() - mm_sram_begin;
|
||||
wsrambankptr_ = flags & write_en
|
||||
? srambankptr
|
||||
: wdisabledRam() - mm_sram_begin;
|
||||
rmem_[0xB] = rmem_[0xA] = rsrambankptr_;
|
||||
wmem_[0xB] = wmem_[0xA] = wsrambankptr_;
|
||||
disconnectOamDmaAreas();
|
||||
}
|
||||
|
||||
void MemPtrs::setWrambank(unsigned bank) {
|
||||
wramdata_[1] = wramdata_[0] + (bank & 0x07 ? bank & 0x07 : 1) * 0x1000;
|
||||
rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000;
|
||||
wramdata_[1] = wramdata_[0] + (bank & 0x07 ? bank & 0x07 : 1) * wrambank_size();
|
||||
rmem_[0xD] = wmem_[0xD] = wramdata_[1] - mm_wram1_begin;
|
||||
disconnectOamDmaAreas();
|
||||
}
|
||||
|
||||
|
@ -116,51 +161,25 @@ void MemPtrs::setOamDmaSrc(OamDmaSrc oamDmaSrc) {
|
|||
rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1];
|
||||
rmem_[0xB] = rmem_[0xA] = rsrambankptr_;
|
||||
wmem_[0xB] = wmem_[0xA] = wsrambankptr_;
|
||||
rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000;
|
||||
rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000;
|
||||
rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000;
|
||||
rmem_[0xC] = wmem_[0xC] = wramdata_[0] - mm_wram_begin;
|
||||
rmem_[0xD] = wmem_[0xD] = wramdata_[1] - mm_wram1_begin;
|
||||
rmem_[0xE] = wmem_[0xE] = wramdata_[0] - mm_wram_mirror_begin;
|
||||
|
||||
oamDmaSrc_ = oamDmaSrc;
|
||||
disconnectOamDmaAreas();
|
||||
}
|
||||
|
||||
void MemPtrs::disconnectOamDmaAreas() {
|
||||
if (isCgb(*this)) {
|
||||
switch (oamDmaSrc_) {
|
||||
case oam_dma_src_rom: // fall through
|
||||
case oam_dma_src_sram:
|
||||
case oam_dma_src_invalid:
|
||||
std::fill(rmem_, rmem_ + 8, static_cast<unsigned char *>(0));
|
||||
rmem_[0xB] = rmem_[0xA] = 0;
|
||||
wmem_[0xB] = wmem_[0xA] = 0;
|
||||
break;
|
||||
case oam_dma_src_vram:
|
||||
break;
|
||||
case oam_dma_src_wram:
|
||||
rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0;
|
||||
wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0;
|
||||
break;
|
||||
case oam_dma_src_off:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (oamDmaSrc_) {
|
||||
case oam_dma_src_rom: // fall through
|
||||
case oam_dma_src_sram:
|
||||
case oam_dma_src_wram:
|
||||
case oam_dma_src_invalid:
|
||||
std::fill(rmem_, rmem_ + 8, static_cast<unsigned char *>(0));
|
||||
rmem_[0xB] = rmem_[0xA] = 0;
|
||||
wmem_[0xB] = wmem_[0xA] = 0;
|
||||
rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0;
|
||||
wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0;
|
||||
break;
|
||||
case oam_dma_src_vram:
|
||||
break;
|
||||
case oam_dma_src_off:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isCgb(*this)
|
||||
? ::disconnectOamDmaAreas<true>(rmem_, wmem_, oamDmaSrc_)
|
||||
: ::disconnectOamDmaAreas<false>(rmem_, wmem_, oamDmaSrc_);
|
||||
}
|
||||
|
||||
bool MemPtrs::isInOamDmaConflictArea(unsigned p) const
|
||||
{
|
||||
return isCgb(*this)
|
||||
? ::isInOamDmaConflictArea<true>(oamDmaSrc_, p)
|
||||
: ::isInOamDmaConflictArea<false>(oamDmaSrc_, p);
|
||||
}
|
||||
|
||||
// all pointers here are relative to memchunk_
|
||||
|
@ -220,4 +239,3 @@ SYNCFUNC(MemPtrs)
|
|||
NSS(curRomBank_);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,46 +20,65 @@
|
|||
#define MEMPTRS_H
|
||||
|
||||
#include "newstate.h"
|
||||
#include "array.h"
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
enum OamDmaSrc { oam_dma_src_rom,
|
||||
oam_dma_src_sram,
|
||||
oam_dma_src_vram,
|
||||
oam_dma_src_wram,
|
||||
oam_dma_src_invalid,
|
||||
oam_dma_src_off, };
|
||||
enum OamDmaSrc {
|
||||
oam_dma_src_rom,
|
||||
oam_dma_src_sram,
|
||||
oam_dma_src_vram,
|
||||
oam_dma_src_wram,
|
||||
oam_dma_src_invalid,
|
||||
oam_dma_src_off };
|
||||
|
||||
enum {
|
||||
mm_rom_begin = 0x0000,
|
||||
mm_rom1_begin = 0x4000,
|
||||
mm_vram_begin = 0x8000,
|
||||
mm_sram_begin = 0xA000,
|
||||
mm_wram_begin = 0xC000,
|
||||
mm_wram1_begin = 0xD000,
|
||||
mm_wram_mirror_begin = 0xE000,
|
||||
mm_oam_begin = 0xFE00,
|
||||
mm_io_begin = 0xFF00,
|
||||
mm_hram_begin = 0xFF80 };
|
||||
|
||||
enum { max_num_vrambanks = 2 };
|
||||
inline std::size_t rambank_size() { return 0x2000; }
|
||||
inline std::size_t rombank_size() { return 0x4000; }
|
||||
inline std::size_t vrambank_size() { return 0x2000; }
|
||||
inline std::size_t wrambank_size() { return 0x1000; }
|
||||
|
||||
class MemPtrs {
|
||||
public:
|
||||
enum RamFlag { read_en = 1, write_en = 2, rtc_en = 4 };
|
||||
|
||||
MemPtrs();
|
||||
~MemPtrs();
|
||||
void reset(unsigned rombanks, unsigned rambanks, unsigned wrambanks);
|
||||
|
||||
unsigned char const * rmem(unsigned area) const { return rmem_[area]; }
|
||||
unsigned char * wmem(unsigned area) const { return wmem_[area]; }
|
||||
unsigned char * vramdata() const { return rambankdata_ - 0x4000; }
|
||||
unsigned char * vramdataend() const { return rambankdata_; }
|
||||
unsigned char * romdata() const { return memchunk_ + 0x4000; }
|
||||
unsigned char * romdata(unsigned area) const { return romdata_[area]; }
|
||||
unsigned char * romdataend() const { return rambankdata_ - 0x4000; }
|
||||
unsigned char * wramdata(unsigned area) const { return wramdata_[area]; }
|
||||
unsigned char * wramdataend() const { return wramdataend_; }
|
||||
unsigned char * rambankdata() const { return rambankdata_; }
|
||||
unsigned char * rambankdataend() const { return wramdata_[0]; }
|
||||
unsigned char const * rdisabledRam() const { return rdisabledRamw(); }
|
||||
unsigned char const * rsrambankptr() const { return rsrambankptr_; }
|
||||
unsigned char * wsrambankptr() const { return wsrambankptr_; }
|
||||
unsigned char * vrambankptr() const { return vrambankptr_; }
|
||||
unsigned char const* rmem(unsigned area) const { return rmem_[area]; }
|
||||
unsigned char* wmem(unsigned area) const { return wmem_[area]; }
|
||||
unsigned char* romdata() const { return memchunk_ + pre_rom_pad_size(); }
|
||||
unsigned char* romdata(unsigned area) const { return romdata_[area]; }
|
||||
unsigned char* romdataend() const { return rambankdata_ - max_num_vrambanks * vrambank_size(); }
|
||||
unsigned char* vramdata() const { return romdataend(); }
|
||||
unsigned char* vramdataend() const { return rambankdata_; }
|
||||
unsigned char* rambankdata() const { return rambankdata_; }
|
||||
unsigned char* rambankdataend() const { return wramdata_[0]; }
|
||||
unsigned char* wramdata(unsigned area) const { return wramdata_[area]; }
|
||||
unsigned char* wramdataend() const { return wramdataend_; }
|
||||
unsigned char const* rdisabledRam() const { return rdisabledRamw(); }
|
||||
unsigned char const* rsrambankptr() const { return rsrambankptr_; }
|
||||
unsigned char* wsrambankptr() const { return wsrambankptr_; }
|
||||
unsigned char* vrambankptr() const { return vrambankptr_; }
|
||||
OamDmaSrc oamDmaSrc() const { return oamDmaSrc_; }
|
||||
unsigned curRomBank() const { return curRomBank_; }
|
||||
bool isInOamDmaConflictArea(unsigned p) const;
|
||||
|
||||
void setRombank0(unsigned bank);
|
||||
void setRombank(unsigned bank);
|
||||
void setRambank(unsigned ramFlags, unsigned rambank);
|
||||
void setVrambank(unsigned bank) { vrambankptr_ = vramdata() + bank * 0x2000ul - 0x8000; }
|
||||
void setVrambank(unsigned bank) { vrambankptr_ = vramdata() + bank * vrambank_size() - mm_vram_begin; }
|
||||
void setWrambank(unsigned bank);
|
||||
void setOamDmaSrc(OamDmaSrc oamDmaSrc);
|
||||
|
||||
|
@ -71,7 +90,7 @@ private:
|
|||
unsigned char *vrambankptr_;
|
||||
unsigned char *rsrambankptr_;
|
||||
unsigned char *wsrambankptr_;
|
||||
unsigned char *memchunk_;
|
||||
SimpleArray<unsigned char> memchunk_;
|
||||
unsigned char *rambankdata_;
|
||||
unsigned char *wramdataend_;
|
||||
OamDmaSrc oamDmaSrc_;
|
||||
|
@ -82,18 +101,19 @@ private:
|
|||
int memchunk_saveoffs;
|
||||
int memchunk_savelen;
|
||||
|
||||
MemPtrs(MemPtrs const &);
|
||||
MemPtrs & operator=(MemPtrs const &);
|
||||
static std::size_t pre_rom_pad_size() { return mm_rom1_begin; }
|
||||
void disconnectOamDmaAreas();
|
||||
unsigned char * rdisabledRamw() const { return wramdataend_ ; }
|
||||
unsigned char * wdisabledRam() const { return wramdataend_ + 0x2000; }
|
||||
unsigned char * rdisabledRamw() const { return wramdataend_; }
|
||||
unsigned char * wdisabledRam() const { return wramdataend_ + rambank_size(); }
|
||||
|
||||
public:
|
||||
template<bool isReader>void SyncState(NewState *ns);
|
||||
};
|
||||
|
||||
inline bool isCgb(MemPtrs const &memptrs) {
|
||||
return memptrs.wramdataend() - memptrs.wramdata(0) == 0x8000;
|
||||
inline bool isCgb(MemPtrs const& memptrs) {
|
||||
int const num_cgb_wrambanks = 8;
|
||||
std::size_t const wramsize = memptrs.wramdataend() - memptrs.wramdata(0);
|
||||
return wramsize == num_cgb_wrambanks * wrambank_size();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,11 +20,27 @@
|
|||
#include "savestate.h"
|
||||
#include "sound.h"
|
||||
#include "video.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace gambatte {
|
||||
using namespace gambatte;
|
||||
|
||||
Memory::Memory(unsigned short &sp, unsigned short &pc)
|
||||
namespace {
|
||||
|
||||
int const oam_size = 4 * lcd_num_oam_entries;
|
||||
|
||||
void decCycles(unsigned long& counter, unsigned long dec) {
|
||||
if (counter != disabled_time)
|
||||
counter -= dec;
|
||||
}
|
||||
|
||||
int serialCntFrom(unsigned long cyclesUntilDone, bool cgbFast) {
|
||||
return cgbFast ? (cyclesUntilDone + 0xF) >> 4 : (cyclesUntilDone + 0x1FF) >> 9;
|
||||
}
|
||||
|
||||
} // unnamed namespace.
|
||||
|
||||
Memory::Memory(Interrupter const& interrupter)
|
||||
: readCallback_(0)
|
||||
, writeCallback_(0)
|
||||
, execCallback_(0)
|
||||
|
@ -35,17 +51,18 @@ Memory::Memory(unsigned short &sp, unsigned short &pc)
|
|||
, divLastUpdate_(0)
|
||||
, lastOamDmaUpdate_(disabled_time)
|
||||
, lcd_(ioamhram_, 0, VideoInterruptRequester(intreq_))
|
||||
, interrupter_(interrupter)
|
||||
, dmaSource_(0)
|
||||
, dmaDestination_(0)
|
||||
, oamDmaPos_(0xFE)
|
||||
, oamDmaPos_(-2u & 0xFF)
|
||||
, oamDmaStartPos_(0)
|
||||
, serialCnt_(0)
|
||||
, blanklcd_(false)
|
||||
, LINKCABLE_(false)
|
||||
, linkClockTrigger_(false)
|
||||
, sp_(sp)
|
||||
, pc_(pc)
|
||||
, haltHdmaState_(hdma_low)
|
||||
{
|
||||
intreq_.setEventTime<intevent_blit>(144 * 456ul);
|
||||
intreq_.setEventTime<intevent_blit>(1l * lcd_vres * lcd_cycles_per_line);
|
||||
intreq_.setEventTime<intevent_end>(0);
|
||||
}
|
||||
|
||||
|
@ -61,11 +78,6 @@ void Memory::setStatePtrs(SaveState &state) {
|
|||
psg_.setStatePtrs(state);
|
||||
}
|
||||
|
||||
|
||||
static int serialCntFrom(unsigned long cyclesUntilDone, bool cgbFast) {
|
||||
return cgbFast ? (cyclesUntilDone + 0xF) >> 4 : (cyclesUntilDone + 0x1FF) >> 9;
|
||||
}
|
||||
|
||||
void Memory::loadState(SaveState const &state) {
|
||||
biosMode_ = state.mem.biosMode;
|
||||
cgbSwitching_ = state.mem.cgbSwitching;
|
||||
|
@ -73,51 +85,55 @@ void Memory::loadState(SaveState const &state) {
|
|||
gbIsCgb_ = state.mem.gbIsCgb;
|
||||
stopped_ = state.mem.stopped;
|
||||
psg_.loadState(state);
|
||||
lcd_.loadState(state, state.mem.oamDmaPos < 0xA0 ? cart_.rdisabledRam() : ioamhram_);
|
||||
lcd_.loadState(state, state.mem.oamDmaPos < oam_size ? cart_.rdisabledRam() : ioamhram_);
|
||||
tima_.loadState(state, TimaInterruptRequester(intreq_));
|
||||
cart_.loadState(state);
|
||||
intreq_.loadState(state);
|
||||
|
||||
divLastUpdate_ = state.mem.divLastUpdate;
|
||||
intreq_.setEventTime<intevent_serial>(state.mem.nextSerialtime > state.cpu.cycleCounter
|
||||
? state.mem.nextSerialtime
|
||||
: state.cpu.cycleCounter);
|
||||
intreq_.setEventTime<intevent_unhalt>(state.mem.unhaltTime);
|
||||
halttime_ = state.mem.halttime;
|
||||
lastOamDmaUpdate_ = state.mem.lastOamDmaUpdate;
|
||||
dmaSource_ = state.mem.dmaSource;
|
||||
dmaDestination_ = state.mem.dmaDestination;
|
||||
oamDmaPos_ = state.mem.oamDmaPos;
|
||||
oamDmaStartPos_ = 0;
|
||||
haltHdmaState_ = static_cast<HdmaState>(std::min(1u * state.mem.haltHdmaState, 1u * hdma_requested));
|
||||
serialCnt_ = intreq_.eventTime(intevent_serial) != disabled_time
|
||||
? serialCntFrom(intreq_.eventTime(intevent_serial) - state.cpu.cycleCounter,
|
||||
ioamhram_[0x102] & isCgb() * 2)
|
||||
: 8;
|
||||
? serialCntFrom(intreq_.eventTime(intevent_serial) - state.cpu.cycleCounter,
|
||||
ioamhram_[0x102] & isCgb() * 2)
|
||||
: 8;
|
||||
|
||||
cart_.setVrambank(ioamhram_[0x14F] & isCgb());
|
||||
cart_.setOamDmaSrc(oam_dma_src_off);
|
||||
cart_.setWrambank(isCgb() && (ioamhram_[0x170] & 0x07) ? ioamhram_[0x170] & 0x07 : 1);
|
||||
|
||||
if (lastOamDmaUpdate_ != disabled_time) {
|
||||
if (lastOamDmaUpdate_ > state.cpu.cycleCounter) {
|
||||
oamDmaStartPos_ = (oamDmaPos_ + (lastOamDmaUpdate_ - state.cpu.cycleCounter) / 4) & 0xFF;
|
||||
lastOamDmaUpdate_ = state.cpu.cycleCounter;
|
||||
}
|
||||
oamDmaInitSetup();
|
||||
|
||||
unsigned oamEventPos = oamDmaPos_ < 0xA0 ? 0xA0 : 0x100;
|
||||
unsigned oamEventPos = oamDmaPos_ < oam_size ? oam_size : oamDmaStartPos_;
|
||||
intreq_.setEventTime<intevent_oam>(
|
||||
lastOamDmaUpdate_ + (oamEventPos - oamDmaPos_) * 4);
|
||||
lastOamDmaUpdate_ + ((oamEventPos - oamDmaPos_) & 0xFF) * 4);
|
||||
}
|
||||
|
||||
intreq_.setEventTime<intevent_blit>(ioamhram_[0x140] & lcdc_en
|
||||
? lcd_.nextMode1IrqTime()
|
||||
: state.cpu.cycleCounter);
|
||||
? lcd_.nextMode1IrqTime()
|
||||
: state.cpu.cycleCounter);
|
||||
blanklcd_ = false;
|
||||
|
||||
if (!isCgb())
|
||||
std::memset(cart_.vramdata() + 0x2000, 0, 0x2000);
|
||||
std::fill_n(cart_.vramdata() + vrambank_size(), vrambank_size(), 0);
|
||||
}
|
||||
|
||||
void Memory::setEndtime(unsigned long cc, unsigned long inc) {
|
||||
if (intreq_.eventTime(intevent_blit) <= cc) {
|
||||
intreq_.setEventTime<intevent_blit>(intreq_.eventTime(intevent_blit)
|
||||
+ (70224 << isDoubleSpeed()));
|
||||
+ (lcd_cycles_per_frame << isDoubleSpeed()));
|
||||
}
|
||||
|
||||
intreq_.setEventTime<intevent_end>(cc + (inc << isDoubleSpeed()));
|
||||
|
@ -129,11 +145,11 @@ void Memory::updateSerial(unsigned long const cc) {
|
|||
if (intreq_.eventTime(intevent_serial) <= cc) {
|
||||
ioamhram_[0x101] = (((ioamhram_[0x101] + 1) << serialCnt_) - 1) & 0xFF;
|
||||
ioamhram_[0x102] &= 0x7F;
|
||||
intreq_.flagIrq(8, intreq_.eventTime(intevent_serial));
|
||||
intreq_.setEventTime<intevent_serial>(disabled_time);
|
||||
intreq_.flagIrq(8);
|
||||
} else {
|
||||
int const targetCnt = serialCntFrom(intreq_.eventTime(intevent_serial) - cc,
|
||||
ioamhram_[0x102] & isCgb() * 2);
|
||||
ioamhram_[0x102] & isCgb() * 2);
|
||||
ioamhram_[0x101] = (((ioamhram_[0x101] + 1) << (serialCnt_ - targetCnt)) - 1) & 0xFF;
|
||||
serialCnt_ = targetCnt;
|
||||
}
|
||||
|
@ -167,11 +183,14 @@ unsigned long Memory::event(unsigned long cc) {
|
|||
|
||||
switch (intreq_.minEventId()) {
|
||||
case intevent_unhalt:
|
||||
if ((lcd_.hdmaIsEnabled() && lcd_.isHdmaPeriod(cc) && haltHdmaState_ == hdma_low)
|
||||
|| haltHdmaState_ == hdma_requested) {
|
||||
flagHdmaReq(intreq_);
|
||||
}
|
||||
|
||||
intreq_.unhalt();
|
||||
intreq_.setEventTime<intevent_unhalt>(disabled_time);
|
||||
nontrivial_ff_write(0xFF04, 0, cc);
|
||||
pc_ = (pc_ + 1) & 0xFFFF;
|
||||
cc += 4;
|
||||
stopped_ = false;
|
||||
break;
|
||||
case intevent_end:
|
||||
intreq_.setEventTime<intevent_end>(disabled_time - 1);
|
||||
|
@ -197,7 +216,7 @@ unsigned long Memory::event(unsigned long cc) {
|
|||
while (cc >= intreq_.minEventTime())
|
||||
cc = event(cc);
|
||||
} else
|
||||
blitTime += 70224 << isDoubleSpeed();
|
||||
blitTime += lcd_cycles_per_frame << isDoubleSpeed();
|
||||
|
||||
blanklcd_ = lcden ^ 1;
|
||||
intreq_.setEventTime<intevent_blit>(blitTime);
|
||||
|
@ -207,77 +226,23 @@ unsigned long Memory::event(unsigned long cc) {
|
|||
updateSerial(cc);
|
||||
break;
|
||||
case intevent_oam:
|
||||
intreq_.setEventTime<intevent_oam>(lastOamDmaUpdate_ == disabled_time
|
||||
? static_cast<unsigned long>(disabled_time)
|
||||
: intreq_.eventTime(intevent_oam) + 0xA0 * 4);
|
||||
if (lastOamDmaUpdate_ != disabled_time) {
|
||||
unsigned const oamEventPos = oamDmaPos_ < oam_size ? oam_size : oamDmaStartPos_;
|
||||
intreq_.setEventTime<intevent_oam>(
|
||||
lastOamDmaUpdate_ + ((oamEventPos - oamDmaPos_) & 0xFF) * 4);
|
||||
}
|
||||
else
|
||||
intreq_.setEventTime<intevent_oam>(disabled_time);
|
||||
|
||||
break;
|
||||
case intevent_dma:
|
||||
{
|
||||
bool const doubleSpeed = isDoubleSpeed();
|
||||
unsigned dmaSrc = dmaSource_;
|
||||
unsigned dmaDest = dmaDestination_;
|
||||
unsigned dmaLength = ((ioamhram_[0x155] & 0x7F) + 0x1) * 0x10;
|
||||
unsigned length = hdmaReqFlagged(intreq_) ? 0x10 : dmaLength;
|
||||
|
||||
ackDmaReq(intreq_);
|
||||
|
||||
if ((static_cast<unsigned long>(dmaDest) + length) & 0x10000) {
|
||||
length = 0x10000 - dmaDest;
|
||||
ioamhram_[0x155] |= 0x80;
|
||||
}
|
||||
|
||||
dmaLength -= length;
|
||||
|
||||
if (!(ioamhram_[0x140] & lcdc_en))
|
||||
dmaLength = 0;
|
||||
|
||||
{
|
||||
unsigned long lOamDmaUpdate = lastOamDmaUpdate_;
|
||||
lastOamDmaUpdate_ = disabled_time;
|
||||
|
||||
while (length--) {
|
||||
unsigned const src = dmaSrc++ & 0xFFFF;
|
||||
unsigned const data = (src & 0xE000) == 0x8000 || src > 0xFDFF
|
||||
? 0xFF
|
||||
: read(src, cc);
|
||||
|
||||
cc += 2 << doubleSpeed;
|
||||
|
||||
if (cc - 3 > lOamDmaUpdate) {
|
||||
oamDmaPos_ = (oamDmaPos_ + 1) & 0xFF;
|
||||
lOamDmaUpdate += 4;
|
||||
|
||||
if (oamDmaPos_ < 0xA0) {
|
||||
if (oamDmaPos_ == 0)
|
||||
startOamDma(lOamDmaUpdate - 1);
|
||||
|
||||
ioamhram_[src & 0xFF] = data;
|
||||
} else if (oamDmaPos_ == 0xA0) {
|
||||
endOamDma(lOamDmaUpdate - 1);
|
||||
lOamDmaUpdate = disabled_time;
|
||||
}
|
||||
}
|
||||
|
||||
nontrivial_write(0x8000 | (dmaDest++ & 0x1FFF), data, cc);
|
||||
}
|
||||
|
||||
lastOamDmaUpdate_ = lOamDmaUpdate;
|
||||
}
|
||||
|
||||
cc += 4;
|
||||
|
||||
dmaSource_ = dmaSrc;
|
||||
dmaDestination_ = dmaDest;
|
||||
ioamhram_[0x155] = ((dmaLength / 0x10 - 0x1) & 0xFF) | (ioamhram_[0x155] & 0x80);
|
||||
|
||||
if ((ioamhram_[0x155] & 0x80) && lcd_.hdmaIsEnabled()) {
|
||||
if (lastOamDmaUpdate_ != disabled_time)
|
||||
updateOamDma(cc);
|
||||
|
||||
lcd_.disableHdma(cc);
|
||||
}
|
||||
interrupter_.prefetch(cc, *this);
|
||||
cc = dma(cc);
|
||||
if (haltHdmaState_ == hdma_requested) {
|
||||
haltHdmaState_ = hdma_low;
|
||||
intreq_.setMinIntTime(cc);
|
||||
cc -= 4;
|
||||
}
|
||||
|
||||
break;
|
||||
case intevent_tima:
|
||||
tima_.doIrqEvent(TimaInterruptRequester(intreq_));
|
||||
|
@ -292,45 +257,21 @@ unsigned long Memory::event(unsigned long cc) {
|
|||
}
|
||||
|
||||
if (halted()) {
|
||||
if (gbIsCgb_ || (!gbIsCgb_ && cc <= halttime_ + 4))
|
||||
cc += 4;
|
||||
cc += 4 * (isCgb() || cc - intreq_.eventTime(intevent_interrupts) < 2);
|
||||
if (cc > lastOamDmaUpdate_)
|
||||
updateOamDma(cc);
|
||||
if ((lcd_.hdmaIsEnabled() && lcd_.isHdmaPeriod(cc) && haltHdmaState_ == hdma_low)
|
||||
|| haltHdmaState_ == hdma_requested) {
|
||||
flagHdmaReq(intreq_);
|
||||
}
|
||||
|
||||
intreq_.unhalt();
|
||||
intreq_.setEventTime<intevent_unhalt>(disabled_time);
|
||||
}
|
||||
|
||||
if (ime()) {
|
||||
cc += 12;
|
||||
|
||||
sp_ = (sp_ - 1) & 0xFFFF;
|
||||
write(sp_, pc_ >> 8, cc);
|
||||
|
||||
cc += 4;
|
||||
|
||||
updateIrqs(cc);
|
||||
unsigned const pendingIrqs = intreq_.pendingIrqs();
|
||||
|
||||
sp_ = (sp_ - 1) & 0xFFFF;
|
||||
write(sp_, pc_ & 0xFF, cc);
|
||||
|
||||
cc += 2;
|
||||
|
||||
unsigned const n = pendingIrqs & -pendingIrqs;
|
||||
unsigned address;
|
||||
if (n == 0) {
|
||||
address = 0;
|
||||
} else if (n <= 4) {
|
||||
static unsigned char const lut[] = { 0x40, 0x48, 0x48, 0x50 };
|
||||
address = lut[n-1];
|
||||
} else
|
||||
address = 0x50 + n;
|
||||
|
||||
updateIrqs(cc);
|
||||
intreq_.ackIrq(n);
|
||||
|
||||
cc += 2;
|
||||
|
||||
pc_ = address;
|
||||
di();
|
||||
cc = interrupter_.interrupt(cc, *this);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -339,38 +280,175 @@ unsigned long Memory::event(unsigned long cc) {
|
|||
return cc;
|
||||
}
|
||||
|
||||
unsigned long Memory::stop(unsigned long cc) {
|
||||
unsigned long Memory::dma(unsigned long cc) {
|
||||
bool const doubleSpeed = isDoubleSpeed();
|
||||
unsigned dmaSrc = dmaSource_;
|
||||
unsigned dmaDest = dmaDestination_;
|
||||
unsigned dmaLength = ((ioamhram_[0x155] & 0x7F) + 1) * 0x10;
|
||||
unsigned length = hdmaReqFlagged(intreq_) ? 0x10 : dmaLength;
|
||||
|
||||
if (1ul * dmaDest + length >= 0x10000) {
|
||||
length = 0x10000 - dmaDest;
|
||||
ioamhram_[0x155] |= 0x80;
|
||||
}
|
||||
|
||||
dmaLength -= length;
|
||||
|
||||
if (!(ioamhram_[0x140] & lcdc_en))
|
||||
dmaLength = 0;
|
||||
|
||||
unsigned long lOamDmaUpdate = lastOamDmaUpdate_;
|
||||
lastOamDmaUpdate_ = disabled_time;
|
||||
|
||||
while (length--) {
|
||||
unsigned const src = dmaSrc++ & 0xFFFF;
|
||||
unsigned const data = (src & -vrambank_size()) == mm_vram_begin || src >= mm_oam_begin
|
||||
? 0xFF
|
||||
: read(src, cc);
|
||||
|
||||
cc += 2 + 2 * doubleSpeed;
|
||||
|
||||
if (cc - 3 > lOamDmaUpdate && !halted()) {
|
||||
oamDmaPos_ = (oamDmaPos_ + 1) & 0xFF;
|
||||
lOamDmaUpdate += 4;
|
||||
if (oamDmaPos_ == oamDmaStartPos_)
|
||||
startOamDma(lOamDmaUpdate);
|
||||
|
||||
if (oamDmaPos_ < oam_size) {
|
||||
ioamhram_[src & 0xFF] = data;
|
||||
}
|
||||
else if (oamDmaPos_ == oam_size) {
|
||||
endOamDma(lOamDmaUpdate);
|
||||
if (oamDmaStartPos_ == 0)
|
||||
lOamDmaUpdate = disabled_time;
|
||||
}
|
||||
}
|
||||
|
||||
nontrivial_write(mm_vram_begin | dmaDest++ % vrambank_size(), data, cc);
|
||||
}
|
||||
|
||||
lastOamDmaUpdate_ = lOamDmaUpdate;
|
||||
ackDmaReq(intreq_);
|
||||
cc += 4;
|
||||
|
||||
if (ioamhram_[0x14D] & isCgb()) {
|
||||
psg_.generateSamples(cc + 4, isDoubleSpeed());
|
||||
lcd_.speedChange((cc + 7) & ~7);
|
||||
cart_.speedChange(cc);
|
||||
ioamhram_[0x14D] ^= 0x81;
|
||||
intreq_.setEventTime<intevent_blit>(ioamhram_[0x140] & lcdc_en
|
||||
? lcd_.nextMode1IrqTime()
|
||||
: cc + (70224 << isDoubleSpeed()));
|
||||
dmaSource_ = dmaSrc;
|
||||
dmaDestination_ = dmaDest;
|
||||
ioamhram_[0x155] = halted()
|
||||
? ioamhram_[0x155] | 0x80
|
||||
: ((dmaLength / 0x10 - 1) & 0xFF) | (ioamhram_[0x155] & 0x80);
|
||||
|
||||
if (intreq_.eventTime(intevent_end) > cc) {
|
||||
intreq_.setEventTime<intevent_end>(cc
|
||||
+ ( isDoubleSpeed()
|
||||
? (intreq_.eventTime(intevent_end) - cc) << 1
|
||||
: (intreq_.eventTime(intevent_end) - cc) >> 1));
|
||||
}
|
||||
intreq_.halt();
|
||||
intreq_.setEventTime<intevent_unhalt>(cc + 0x20000);
|
||||
}
|
||||
else {
|
||||
stopped_ = true;
|
||||
intreq_.halt();
|
||||
if ((ioamhram_[0x155] & 0x80) && lcd_.hdmaIsEnabled()) {
|
||||
if (lastOamDmaUpdate_ != disabled_time)
|
||||
updateOamDma(cc);
|
||||
|
||||
lcd_.disableHdma(cc);
|
||||
}
|
||||
|
||||
return cc;
|
||||
}
|
||||
|
||||
static void decCycles(unsigned long &counter, unsigned long dec) {
|
||||
if (counter != disabled_time)
|
||||
counter -= dec;
|
||||
void Memory::freeze(unsigned long cc) {
|
||||
// permanently halt CPU.
|
||||
// simply halt and clear IE to avoid unhalt from occuring,
|
||||
// which avoids additional state to represent a "frozen" state.
|
||||
nontrivial_ff_write(0xFF, 0, cc);
|
||||
ackDmaReq(intreq_);
|
||||
intreq_.halt();
|
||||
}
|
||||
|
||||
bool Memory::halt(unsigned long cc) {
|
||||
if (lastOamDmaUpdate_ != disabled_time)
|
||||
updateOamDma(cc);
|
||||
|
||||
haltHdmaState_ = lcd_.hdmaIsEnabled() && lcd_.isHdmaPeriod(cc)
|
||||
? hdma_high : hdma_low;
|
||||
bool const hdmaReq = hdmaReqFlagged(intreq_);
|
||||
if (hdmaReq)
|
||||
haltHdmaState_ = hdma_requested;
|
||||
if (lastOamDmaUpdate_ != disabled_time)
|
||||
updateOamDma(cc + 4);
|
||||
|
||||
ackDmaReq(intreq_);
|
||||
intreq_.halt();
|
||||
return hdmaReq;
|
||||
}
|
||||
|
||||
unsigned Memory::pendingIrqs(unsigned long cc) {
|
||||
if (lastOamDmaUpdate_ != disabled_time)
|
||||
updateOamDma(cc);
|
||||
|
||||
updateIrqs(cc);
|
||||
return intreq_.pendingIrqs();
|
||||
}
|
||||
|
||||
void Memory::ackIrq(unsigned bit, unsigned long cc) {
|
||||
if (lastOamDmaUpdate_ != disabled_time)
|
||||
updateOamDma(cc);
|
||||
|
||||
// TODO: adjust/extend IRQ assertion time rather than use the odd cc offsets?
|
||||
// NOTE: a minimum offset of 2 is required for the LCD due to implementation assumptions w.r.t. cc headroom.
|
||||
updateSerial(cc + 3 + isCgb());
|
||||
updateTimaIrq(cc + 2 + isCgb());
|
||||
lcd_.update(cc + 2);
|
||||
intreq_.ackIrq(bit);
|
||||
}
|
||||
|
||||
unsigned long Memory::stop(unsigned long cc, bool &skip) {
|
||||
// FIXME: this is incomplete.
|
||||
intreq_.setEventTime<intevent_unhalt>(cc + 0x20000 + 4);
|
||||
|
||||
// speed change.
|
||||
if (ioamhram_[0x14D] & isCgb()) {
|
||||
tima_.speedChange(TimaInterruptRequester(intreq_));
|
||||
// DIV reset.
|
||||
nontrivial_ff_write(0x04, 0, cc);
|
||||
haltHdmaState_ = lcd_.hdmaIsEnabled() && lcd_.isHdmaPeriod(cc)
|
||||
? hdma_high : hdma_low;
|
||||
skip = hdmaReqFlagged(intreq_);
|
||||
if (skip && isDoubleSpeed())
|
||||
haltHdmaState_ = hdma_requested;
|
||||
unsigned long const cc_ = cc + 8 * !isDoubleSpeed();
|
||||
if (cc_ >= cc + 4) {
|
||||
if (lastOamDmaUpdate_ != disabled_time)
|
||||
updateOamDma(cc + 4);
|
||||
if (!skip || isDoubleSpeed())
|
||||
ackDmaReq(intreq_);
|
||||
intreq_.halt();
|
||||
}
|
||||
psg_.speedChange(cc_, isDoubleSpeed());
|
||||
lcd_.speedChange(cc_);
|
||||
cart_.speedChange(cc_);
|
||||
ioamhram_[0x14D] ^= 0x81;
|
||||
// TODO: perhaps make this a bit nicer?
|
||||
intreq_.setEventTime<intevent_blit>(ioamhram_[0x140] & lcdc_en
|
||||
? lcd_.nextMode1IrqTime()
|
||||
: cc + (lcd_cycles_per_frame << isDoubleSpeed()));
|
||||
if (intreq_.eventTime(intevent_end) > cc_) {
|
||||
intreq_.setEventTime<intevent_end>(cc_
|
||||
+ (isDoubleSpeed()
|
||||
? (intreq_.eventTime(intevent_end) - cc_) * 2
|
||||
: (intreq_.eventTime(intevent_end) - cc_) / 2));
|
||||
}
|
||||
if (cc_ < cc + 4) {
|
||||
if (lastOamDmaUpdate_ != disabled_time)
|
||||
updateOamDma(cc + 4);
|
||||
if (!skip || !isDoubleSpeed())
|
||||
ackDmaReq(intreq_);
|
||||
intreq_.halt();
|
||||
}
|
||||
// ensure that no updates with a previous cc occur.
|
||||
cc += 8;
|
||||
}
|
||||
else {
|
||||
// FIXME: test and implement stop correctly.
|
||||
skip = halt(cc);
|
||||
cc += 4;
|
||||
|
||||
stopped_ = true;
|
||||
intreq_.setEventTime<intevent_unhalt>(disabled_time);
|
||||
}
|
||||
|
||||
return cc;
|
||||
}
|
||||
|
||||
void Memory::decEventCycles(IntEventId eventId, unsigned long dec) {
|
||||
|
@ -384,15 +462,9 @@ unsigned long Memory::resetCounters(unsigned long cc) {
|
|||
|
||||
updateIrqs(cc);
|
||||
|
||||
{
|
||||
unsigned long divinc = (cc - divLastUpdate_) >> 8;
|
||||
ioamhram_[0x104] = (ioamhram_[0x104] + divinc) & 0xFF;
|
||||
divLastUpdate_ += divinc << 8;
|
||||
}
|
||||
|
||||
unsigned long const dec = cc < 0x10000
|
||||
? 0
|
||||
: (cc & ~0x7FFFul) - 0x8000;
|
||||
unsigned long const dec = cc < 0x20000
|
||||
? 0
|
||||
: (cc & -0x10000l) - 0x10000;
|
||||
decCycles(divLastUpdate_, dec);
|
||||
decCycles(lastOamDmaUpdate_, dec);
|
||||
decEventCycles(intevent_serial, dec);
|
||||
|
@ -434,62 +506,68 @@ void Memory::updateOamDma(unsigned long const cc) {
|
|||
unsigned char const *const oamDmaSrc = oamDmaSrcPtr();
|
||||
unsigned cycles = (cc - lastOamDmaUpdate_) >> 2;
|
||||
|
||||
while (cycles--) {
|
||||
if (halted()) {
|
||||
lastOamDmaUpdate_ += 4 * cycles;
|
||||
}
|
||||
else while (cycles--) {
|
||||
oamDmaPos_ = (oamDmaPos_ + 1) & 0xFF;
|
||||
lastOamDmaUpdate_ += 4;
|
||||
if (oamDmaPos_ == oamDmaStartPos_)
|
||||
startOamDma(lastOamDmaUpdate_);
|
||||
|
||||
if (oamDmaPos_ < 0xA0) {
|
||||
if (oamDmaPos_ == 0)
|
||||
startOamDma(lastOamDmaUpdate_ - 1);
|
||||
|
||||
ioamhram_[oamDmaPos_] = oamDmaSrc ? oamDmaSrc[oamDmaPos_] : cart_.rtcRead();
|
||||
} else if (oamDmaPos_ == 0xA0) {
|
||||
endOamDma(lastOamDmaUpdate_ - 1);
|
||||
lastOamDmaUpdate_ = disabled_time;
|
||||
break;
|
||||
if (oamDmaPos_ < oam_size) {
|
||||
ioamhram_[oamDmaPos_] = ((oamDmaSrc) ? oamDmaSrc[oamDmaPos_] : cart_.rtcRead());
|
||||
}
|
||||
else if (oamDmaPos_ == oam_size) {
|
||||
endOamDma(lastOamDmaUpdate_);
|
||||
if (oamDmaStartPos_ == 0) {
|
||||
lastOamDmaUpdate_ = disabled_time;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Memory::oamDmaInitSetup() {
|
||||
if (ioamhram_[0x146] < 0xA0) {
|
||||
cart_.setOamDmaSrc(ioamhram_[0x146] < 0x80 ? oam_dma_src_rom : oam_dma_src_vram);
|
||||
} else if (ioamhram_[0x146] < 0xFE - isCgb() * 0x1E) {
|
||||
cart_.setOamDmaSrc(ioamhram_[0x146] < 0xC0 ? oam_dma_src_sram : oam_dma_src_wram);
|
||||
} else
|
||||
if (ioamhram_[0x146] < mm_sram_begin / 0x100) {
|
||||
cart_.setOamDmaSrc(ioamhram_[0x146] < mm_vram_begin / 0x100 ? oam_dma_src_rom : oam_dma_src_vram);
|
||||
}
|
||||
else if (ioamhram_[0x146] < 0x100 - isCgb() * 0x20) {
|
||||
cart_.setOamDmaSrc(ioamhram_[0x146] < mm_wram_begin / 0x100 ? oam_dma_src_sram : oam_dma_src_wram);
|
||||
}
|
||||
else
|
||||
cart_.setOamDmaSrc(oam_dma_src_invalid);
|
||||
}
|
||||
|
||||
static unsigned char const * oamDmaSrcZero() {
|
||||
static unsigned char zeroMem[0xA0];
|
||||
return zeroMem;
|
||||
}
|
||||
|
||||
unsigned char const * Memory::oamDmaSrcPtr() const {
|
||||
unsigned char const* Memory::oamDmaSrcPtr() const {
|
||||
switch (cart_.oamDmaSrc()) {
|
||||
case oam_dma_src_rom:
|
||||
return cart_.romdata(ioamhram_[0x146] >> 6) + (ioamhram_[0x146] << 8);
|
||||
return cart_.romdata(ioamhram_[0x146] >> 6) + ioamhram_[0x146] * 0x100l;
|
||||
case oam_dma_src_sram:
|
||||
return cart_.rsrambankptr() ? cart_.rsrambankptr() + (ioamhram_[0x146] << 8) : 0;
|
||||
return cart_.rsrambankptr() ? cart_.rsrambankptr() + ioamhram_[0x146] * 0x100l : 0;
|
||||
case oam_dma_src_vram:
|
||||
return cart_.vrambankptr() + (ioamhram_[0x146] << 8);
|
||||
return cart_.vrambankptr() + ioamhram_[0x146] * 0x100l;
|
||||
case oam_dma_src_wram:
|
||||
return cart_.wramdata(ioamhram_[0x146] >> 4 & 1) + (ioamhram_[0x146] << 8 & 0xFFF);
|
||||
return cart_.wramdata(ioamhram_[0x146] >> 4 & 1) + (ioamhram_[0x146] * 0x100l & 0xFFF);
|
||||
case oam_dma_src_invalid:
|
||||
case oam_dma_src_off:
|
||||
break;
|
||||
}
|
||||
|
||||
return ioamhram_[0x146] == 0xFF && !isCgb() ? oamDmaSrcZero() : cart_.rdisabledRam();
|
||||
return cart_.rdisabledRam();
|
||||
}
|
||||
|
||||
void Memory::startOamDma(unsigned long cc) {
|
||||
oamDmaPos_ = 0;
|
||||
oamDmaStartPos_ = 0;
|
||||
lcd_.oamChange(cart_.rdisabledRam(), cc);
|
||||
}
|
||||
|
||||
void Memory::endOamDma(unsigned long cc) {
|
||||
oamDmaPos_ = 0xFE;
|
||||
cart_.setOamDmaSrc(oam_dma_src_off);
|
||||
if (oamDmaStartPos_ == 0) {
|
||||
oamDmaPos_ = -2u & 0xFF;
|
||||
cart_.setOamDmaSrc(oam_dma_src_off);
|
||||
}
|
||||
lcd_.oamChange(ioamhram_, cc);
|
||||
}
|
||||
|
||||
|
@ -506,13 +584,7 @@ unsigned Memory::nontrivial_ff_read(unsigned const p, unsigned long const cc) {
|
|||
updateSerial(cc);
|
||||
break;
|
||||
case 0x04:
|
||||
{
|
||||
unsigned long divcycles = (cc - divLastUpdate_) >> 8;
|
||||
ioamhram_[0x104] = (ioamhram_[0x104] + divcycles) & 0xFF;
|
||||
divLastUpdate_ += divcycles << 8;
|
||||
}
|
||||
|
||||
break;
|
||||
return (cc - tima_.divLastUpdate()) >> 8 & 0xFF;
|
||||
case 0x05:
|
||||
ioamhram_[0x105] = tima_.tima(cc);
|
||||
break;
|
||||
|
@ -561,47 +633,28 @@ unsigned Memory::nontrivial_ff_read(unsigned const p, unsigned long const cc) {
|
|||
return ioamhram_[p + 0x100];
|
||||
}
|
||||
|
||||
static bool isInOamDmaConflictArea(OamDmaSrc const oamDmaSrc, unsigned const p, bool const cgb) {
|
||||
struct Area { unsigned short areaUpper, exceptAreaLower, exceptAreaWidth, pad; };
|
||||
|
||||
static Area const cgbAreas[] = {
|
||||
{ 0xC000, 0x8000, 0x2000, 0 },
|
||||
{ 0xC000, 0x8000, 0x2000, 0 },
|
||||
{ 0xA000, 0x0000, 0x8000, 0 },
|
||||
{ 0xFE00, 0x0000, 0xC000, 0 },
|
||||
{ 0xC000, 0x8000, 0x2000, 0 },
|
||||
{ 0x0000, 0x0000, 0x0000, 0 }
|
||||
};
|
||||
|
||||
static Area const dmgAreas[] = {
|
||||
{ 0xFE00, 0x8000, 0x2000, 0 },
|
||||
{ 0xFE00, 0x8000, 0x2000, 0 },
|
||||
{ 0xA000, 0x0000, 0x8000, 0 },
|
||||
{ 0xFE00, 0x8000, 0x2000, 0 },
|
||||
{ 0xFE00, 0x8000, 0x2000, 0 },
|
||||
{ 0x0000, 0x0000, 0x0000, 0 }
|
||||
};
|
||||
|
||||
Area const *a = cgb ? cgbAreas : dmgAreas;
|
||||
return p < a[oamDmaSrc].areaUpper
|
||||
&& p - a[oamDmaSrc].exceptAreaLower >= a[oamDmaSrc].exceptAreaWidth;
|
||||
}
|
||||
|
||||
unsigned Memory::nontrivial_read(unsigned const p, unsigned long const cc) {
|
||||
if (p < 0xFF80) {
|
||||
if (p < mm_hram_begin) {
|
||||
if (lastOamDmaUpdate_ != disabled_time) {
|
||||
updateOamDma(cc);
|
||||
|
||||
if (isInOamDmaConflictArea(cart_.oamDmaSrc(), p, isCgb()) && oamDmaPos_ < 0xA0)
|
||||
return ioamhram_[oamDmaPos_];
|
||||
if (cart_.isInOamDmaConflictArea(p) && oamDmaPos_ < oam_size) {
|
||||
int const r = isCgb() && cart_.oamDmaSrc() != oam_dma_src_wram && p >= mm_wram_begin
|
||||
? cart_.wramdata(ioamhram_[0x146] >> 4 & 1)[p & 0xFFF]
|
||||
: ioamhram_[oamDmaPos_];
|
||||
if (isCgb() && cart_.oamDmaSrc() == oam_dma_src_vram)
|
||||
ioamhram_[oamDmaPos_] = 0;
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
if (p < 0xC000) {
|
||||
if (p < 0x8000)
|
||||
if (p < mm_wram_begin) {
|
||||
if (p < mm_vram_begin)
|
||||
return cart_.romdata(p >> 14)[p];
|
||||
|
||||
if (p < 0xA000) {
|
||||
if (!lcd_.vramAccessible(cc))
|
||||
if (p < mm_sram_begin) {
|
||||
if (!lcd_.vramReadable(cc))
|
||||
return 0xFF;
|
||||
|
||||
return cart_.vrambankptr()[p];
|
||||
|
@ -613,18 +666,18 @@ unsigned Memory::nontrivial_read(unsigned const p, unsigned long const cc) {
|
|||
return cart_.rtcRead();
|
||||
}
|
||||
|
||||
if (p < 0xFE00)
|
||||
if (p < mm_oam_begin)
|
||||
return cart_.wramdata(p >> 12 & 1)[p & 0xFFF];
|
||||
|
||||
long const ffp = long(p) - 0xFF00;
|
||||
long const ffp = static_cast<long>(p) - mm_io_begin;
|
||||
if (ffp >= 0)
|
||||
return nontrivial_ff_read(ffp, cc);
|
||||
|
||||
if (!lcd_.oamReadable(cc) || oamDmaPos_ < 0xA0)
|
||||
if (!lcd_.oamReadable(cc) || oamDmaPos_ < oam_size)
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
return ioamhram_[p - 0xFE00];
|
||||
return ioamhram_[p - mm_oam_begin];
|
||||
}
|
||||
|
||||
unsigned Memory::nontrivial_peek(unsigned const p) {
|
||||
|
@ -674,17 +727,25 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long
|
|||
|
||||
if ((data & 0x81) == 0x81) {
|
||||
intreq_.setEventTime<intevent_serial>(data & isCgb() * 2
|
||||
? (cc & ~0x07ul) + 0x010 * 8
|
||||
: (cc & ~0xFFul) + 0x200 * 8);
|
||||
? cc - (cc - tima_.divLastUpdate()) % 8 + 0x10 * serialCnt_
|
||||
: cc - (cc - tima_.divLastUpdate()) % 0x100 + 0x200 * serialCnt_);
|
||||
} else
|
||||
intreq_.setEventTime<intevent_serial>(disabled_time);
|
||||
|
||||
data |= 0x7E - isCgb() * 2;
|
||||
break;
|
||||
case 0x04:
|
||||
ioamhram_[0x104] = 0;
|
||||
divLastUpdate_ = cc;
|
||||
tima_.resTac(cc, TimaInterruptRequester(intreq_));
|
||||
if (intreq_.eventTime(intevent_serial) != disabled_time
|
||||
&& intreq_.eventTime(intevent_serial) > cc) {
|
||||
unsigned long const t = intreq_.eventTime(intevent_serial);
|
||||
unsigned long const n = ioamhram_[0x102] & isCgb() * 2
|
||||
? t + (cc - t) % 8 - 2 * ((cc - t) & 4)
|
||||
: t + (cc - t) % 0x100 - 2 * ((cc - t) & 0x80);
|
||||
intreq_.setEventTime<intevent_serial>(std::max(cc, n));
|
||||
}
|
||||
psg_.generateSamples(cc, isDoubleSpeed());
|
||||
psg_.divReset(isDoubleSpeed());
|
||||
tima_.divReset(cc, TimaInterruptRequester(intreq_));
|
||||
return;
|
||||
case 0x05:
|
||||
tima_.setTima(data, cc, TimaInterruptRequester(intreq_));
|
||||
|
@ -697,7 +758,7 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long
|
|||
tima_.setTac(data, cc, TimaInterruptRequester(intreq_), agbMode_);
|
||||
break;
|
||||
case 0x0F:
|
||||
updateIrqs(cc);
|
||||
updateIrqs(cc + 1 + isDoubleSpeed());
|
||||
intreq_.setIfreg(0xE0 | data);
|
||||
return;
|
||||
case 0x10:
|
||||
|
@ -739,7 +800,7 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long
|
|||
return;
|
||||
|
||||
psg_.generateSamples(cc, isDoubleSpeed());
|
||||
psg_.setNr14(data);
|
||||
psg_.setNr14(data, isDoubleSpeed());
|
||||
data |= 0xBF;
|
||||
break;
|
||||
case 0x16:
|
||||
|
@ -773,7 +834,7 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long
|
|||
return;
|
||||
|
||||
psg_.generateSamples(cc, isDoubleSpeed());
|
||||
psg_.setNr24(data);
|
||||
psg_.setNr24(data, isDoubleSpeed());
|
||||
data |= 0xBF;
|
||||
break;
|
||||
case 0x1A:
|
||||
|
@ -866,8 +927,9 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long
|
|||
ff_write(i, 0, cc);
|
||||
|
||||
psg_.setEnabled(false);
|
||||
} else {
|
||||
psg_.reset();
|
||||
}
|
||||
else {
|
||||
psg_.reset(isDoubleSpeed());
|
||||
psg_.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
@ -896,8 +958,7 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long
|
|||
case 0x40:
|
||||
if (ioamhram_[0x140] != data) {
|
||||
if ((ioamhram_[0x140] ^ data) & lcdc_en) {
|
||||
unsigned const lyc = lcd_.getStat(ioamhram_[0x145], cc)
|
||||
& lcdstat_lycflag;
|
||||
unsigned const stat = data & lcdc_en ? ioamhram_[0x141] : lcd_.getStat(ioamhram_[0x145], cc);
|
||||
bool const hdmaEnabled = lcd_.hdmaIsEnabled();
|
||||
|
||||
lcd_.lcdcChange(data, cc);
|
||||
|
@ -905,19 +966,23 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long
|
|||
ioamhram_[0x141] &= 0xF8;
|
||||
|
||||
if (data & lcdc_en) {
|
||||
if (ioamhram_[0x141] & lcdstat_lycirqen && ioamhram_[0x145] == 0 && !(stat & lcdstat_lycflag))
|
||||
intreq_.flagIrq(2);
|
||||
|
||||
intreq_.setEventTime<intevent_blit>(blanklcd_
|
||||
? lcd_.nextMode1IrqTime()
|
||||
: lcd_.nextMode1IrqTime()
|
||||
+ (70224 << isDoubleSpeed()));
|
||||
} else {
|
||||
ioamhram_[0x141] |= lyc;
|
||||
: lcd_.nextMode1IrqTime() + (lcd_cycles_per_frame << isDoubleSpeed()));
|
||||
}
|
||||
else {
|
||||
ioamhram_[0x141] |= stat & lcdstat_lycflag;
|
||||
intreq_.setEventTime<intevent_blit>(
|
||||
cc + (456 * 4 << isDoubleSpeed()));
|
||||
cc + (lcd_cycles_per_line * 4 << isDoubleSpeed()));
|
||||
|
||||
if (hdmaEnabled)
|
||||
flagHdmaReq(intreq_);
|
||||
}
|
||||
} else
|
||||
}
|
||||
else
|
||||
lcd_.lcdcChange(data, cc);
|
||||
|
||||
ioamhram_[0x140] = data;
|
||||
|
@ -926,6 +991,10 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long
|
|||
return;
|
||||
case 0x41:
|
||||
lcd_.lcdstatChange(data, cc);
|
||||
if (!(ioamhram_[0x140] & lcdc_en) && (ioamhram_[0x141] & lcdstat_lycflag)
|
||||
&& (~ioamhram_[0x141] & lcdstat_lycirqen & (isCgb() ? data : -1))) {
|
||||
intreq_.flagIrq(2);
|
||||
}
|
||||
data = (ioamhram_[0x141] & 0x87) | (data & 0x78);
|
||||
break;
|
||||
case 0x42:
|
||||
|
@ -938,11 +1007,9 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long
|
|||
lcd_.lycRegChange(data, cc);
|
||||
break;
|
||||
case 0x46:
|
||||
if (lastOamDmaUpdate_ != disabled_time)
|
||||
endOamDma(cc);
|
||||
|
||||
lastOamDmaUpdate_ = cc;
|
||||
intreq_.setEventTime<intevent_oam>(cc + 8);
|
||||
oamDmaStartPos_ = (oamDmaPos_ + 2) & 0xFF;
|
||||
intreq_.setEventTime<intevent_oam>(std::min(intreq_.eventTime(intevent_oam), cc + 8));
|
||||
ioamhram_[0x146] = data;
|
||||
oamDmaInitSetup();
|
||||
return;
|
||||
|
@ -1033,8 +1100,8 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long
|
|||
if (isCgb()) {
|
||||
unsigned index = ioamhram_[0x168] & 0x3F;
|
||||
lcd_.cgbBgColorChange(index, data, cc);
|
||||
ioamhram_[0x168] = (ioamhram_[0x168] & ~0x3F)
|
||||
| ((index + (ioamhram_[0x168] >> 7)) & 0x3F);
|
||||
ioamhram_[0x168] = (ioamhram_[0x168] & ~0x3Fu)
|
||||
| ((index + (ioamhram_[0x168] >> 7)) & 0x3F);
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -1047,8 +1114,8 @@ void Memory::nontrivial_ff_write(unsigned const p, unsigned data, unsigned long
|
|||
if (isCgb()) {
|
||||
unsigned index = ioamhram_[0x16A] & 0x3F;
|
||||
lcd_.cgbSpColorChange(index, data, cc);
|
||||
ioamhram_[0x16A] = (ioamhram_[0x16A] & ~0x3F)
|
||||
| ((index + (ioamhram_[0x16A] >> 7)) & 0x3F);
|
||||
ioamhram_[0x16A] = (ioamhram_[0x16A] & ~0x3Fu)
|
||||
| ((index + (ioamhram_[0x16A] >> 7)) & 0x3F);
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -1092,38 +1159,56 @@ void Memory::nontrivial_write(unsigned const p, unsigned const data, unsigned lo
|
|||
if (lastOamDmaUpdate_ != disabled_time) {
|
||||
updateOamDma(cc);
|
||||
|
||||
if (isInOamDmaConflictArea(cart_.oamDmaSrc(), p, isCgb()) && oamDmaPos_ < 0xA0) {
|
||||
ioamhram_[oamDmaPos_] = data;
|
||||
if (cart_.isInOamDmaConflictArea(p) && oamDmaPos_ < oam_size) {
|
||||
if (isCgb()) {
|
||||
if (p < mm_wram_begin)
|
||||
ioamhram_[oamDmaPos_] = cart_.oamDmaSrc() != oam_dma_src_vram ? data : 0;
|
||||
else if (cart_.oamDmaSrc() != oam_dma_src_wram)
|
||||
cart_.wramdata(ioamhram_[0x146] >> 4 & 1)[p & 0xFFF] = data;
|
||||
}
|
||||
else {
|
||||
ioamhram_[oamDmaPos_] = cart_.oamDmaSrc() == oam_dma_src_wram
|
||||
? ioamhram_[oamDmaPos_] & data
|
||||
: data;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (p < 0xFE00) {
|
||||
if (p < 0xA000) {
|
||||
if (p < 0x8000) {
|
||||
if (p < mm_oam_begin) {
|
||||
if (p < mm_sram_begin) {
|
||||
if (p < mm_vram_begin) {
|
||||
cart_.mbcWrite(p, data, cc);
|
||||
} else if (lcd_.vramAccessible(cc)) {
|
||||
}
|
||||
else if (lcd_.vramWritable(cc)) {
|
||||
lcd_.vramChange(cc);
|
||||
cart_.vrambankptr()[p] = data;
|
||||
}
|
||||
} else if (p < 0xC000) {
|
||||
}
|
||||
else if (p < mm_wram_begin) {
|
||||
if (cart_.wsrambankptr())
|
||||
cart_.wsrambankptr()[p] = data;
|
||||
else
|
||||
cart_.rtcWrite(data, cc);
|
||||
} else
|
||||
}
|
||||
else
|
||||
cart_.wramdata(p >> 12 & 1)[p & 0xFFF] = data;
|
||||
} else if (p - 0xFF80u >= 0x7Fu) {
|
||||
long const ffp = long(p) - 0xFF00;
|
||||
}
|
||||
else if (p - mm_hram_begin >= 0x7Fu) {
|
||||
long const ffp = static_cast<long>(p) - mm_io_begin;
|
||||
if (ffp < 0) {
|
||||
if (lcd_.oamWritable(cc) && oamDmaPos_ >= 0xA0 && (p < 0xFEA0 || isCgb())) {
|
||||
if (lcd_.oamWritable(cc) && oamDmaPos_ >= oam_size
|
||||
&& (p < mm_oam_begin + oam_size || isCgb())) {
|
||||
lcd_.oamChange(cc);
|
||||
ioamhram_[p - 0xFE00] = data;
|
||||
ioamhram_[p - mm_oam_begin] = data;
|
||||
}
|
||||
} else
|
||||
}
|
||||
else
|
||||
nontrivial_ff_write(ffp, data, cc);
|
||||
} else
|
||||
ioamhram_[p - 0xFE00] = data;
|
||||
}
|
||||
else
|
||||
ioamhram_[p - mm_oam_begin] = data;
|
||||
}
|
||||
|
||||
LoadRes Memory::loadROM(char const *romfiledata, unsigned romfilelength, const bool forceDmg, const bool multicartCompat) {
|
||||
|
@ -1223,5 +1308,3 @@ SYNCFUNC(Memory)
|
|||
NSS(LINKCABLE_);
|
||||
NSS(linkClockTrigger_);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
static unsigned char const agbOverride[0xD] = { 0xFF, 0x00, 0xCD, 0x03, 0x35, 0xAA, 0x31, 0x90, 0x94, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
#include "mem/cartridge.h"
|
||||
#include "interrupter.h"
|
||||
#include "sound.h"
|
||||
#include "tima.h"
|
||||
#include "video.h"
|
||||
|
@ -34,7 +35,7 @@ class FilterInfo;
|
|||
|
||||
class Memory {
|
||||
public:
|
||||
explicit Memory(unsigned short &sp, unsigned short &pc);
|
||||
explicit Memory(Interrupter const &interrupter);
|
||||
~Memory();
|
||||
|
||||
bool loaded() const { return cart_.loaded(); }
|
||||
|
@ -58,7 +59,7 @@ public:
|
|||
|
||||
bool getMemoryArea(int which, unsigned char **data, int *length);
|
||||
|
||||
unsigned long stop(unsigned long cycleCounter);
|
||||
unsigned long stop(unsigned long cycleCounter, bool& skip);
|
||||
bool isCgb() const { return lcd_.isCgb(); }
|
||||
bool ime() const { return intreq_.ime(); }
|
||||
bool halted() const { return intreq_.halted(); }
|
||||
|
@ -73,10 +74,14 @@ public:
|
|||
return (cc - intreq_.eventTime(intevent_blit)) >> isDoubleSpeed();
|
||||
}
|
||||
|
||||
void halt(unsigned long cycleCounter) { halttime_ = cycleCounter; intreq_.halt(); }
|
||||
void freeze(unsigned long cc);
|
||||
bool halt(unsigned long cc);
|
||||
void ei(unsigned long cycleCounter) { if (!ime()) { intreq_.ei(cycleCounter); } }
|
||||
void di() { intreq_.di(); }
|
||||
|
||||
unsigned pendingIrqs(unsigned long cc);
|
||||
void ackIrq(unsigned bit, unsigned long cc);
|
||||
|
||||
unsigned readBios(unsigned p) {
|
||||
if(agbMode_ && p >= 0xF3 && p < 0x100) {
|
||||
return (agbOverride[p-0xF3] + bios_[p]) & 0xFF;
|
||||
|
@ -262,6 +267,10 @@ public:
|
|||
lcd_.setDmgPaletteColor(palNum, colorNum, rgb32);
|
||||
}
|
||||
|
||||
void blackScreen() {
|
||||
lcd_.blackScreen();
|
||||
}
|
||||
|
||||
void setCgbPalette(unsigned *lut);
|
||||
void setTimeMode(bool useCycles, unsigned long const cc) {
|
||||
cart_.setTimeMode(useCycles, cc);
|
||||
|
@ -282,20 +291,20 @@ private:
|
|||
Tima tima_;
|
||||
LCD lcd_;
|
||||
PSG psg_;
|
||||
Interrupter interrupter_;
|
||||
unsigned short dmaSource_;
|
||||
unsigned short dmaDestination_;
|
||||
unsigned char oamDmaPos_;
|
||||
unsigned char oamDmaStartPos_;
|
||||
unsigned char serialCnt_;
|
||||
bool blanklcd_;
|
||||
bool biosMode_;
|
||||
bool cgbSwitching_;
|
||||
bool agbMode_;
|
||||
bool gbIsCgb_;
|
||||
unsigned short &sp_;
|
||||
unsigned short &pc_;
|
||||
unsigned long basetime_;
|
||||
unsigned long halttime_;
|
||||
bool stopped_;
|
||||
enum HdmaState { hdma_low, hdma_high, hdma_requested } haltHdmaState_;
|
||||
|
||||
MemoryCallback readCallback_;
|
||||
MemoryCallback writeCallback_;
|
||||
|
@ -311,6 +320,7 @@ private:
|
|||
void startOamDma(unsigned long cycleCounter);
|
||||
void endOamDma(unsigned long cycleCounter);
|
||||
unsigned char const * oamDmaSrcPtr() const;
|
||||
unsigned long dma(unsigned long cc);
|
||||
unsigned nontrivial_ff_read(unsigned p, unsigned long cycleCounter);
|
||||
unsigned nontrivial_read(unsigned p, unsigned long cycleCounter);
|
||||
void nontrivial_ff_write(unsigned p, unsigned data, unsigned long cycleCounter);
|
||||
|
|
|
@ -54,6 +54,8 @@ struct SaveState {
|
|||
unsigned char f;
|
||||
unsigned char h;
|
||||
unsigned char l;
|
||||
unsigned char opcode;
|
||||
unsigned char /*bool*/ prefetched;
|
||||
unsigned char /*bool*/ skip;
|
||||
} cpu;
|
||||
|
||||
|
@ -63,19 +65,18 @@ struct SaveState {
|
|||
Ptr<unsigned char> wram;
|
||||
Ptr<unsigned char> ioamhram;
|
||||
unsigned long divLastUpdate;
|
||||
unsigned long timaBasetime;
|
||||
unsigned long timaLastUpdate;
|
||||
unsigned long tmatime;
|
||||
unsigned long nextSerialtime;
|
||||
unsigned long lastOamDmaUpdate;
|
||||
unsigned long minIntTime;
|
||||
unsigned long unhaltTime;
|
||||
unsigned long halttime;
|
||||
unsigned short rombank;
|
||||
unsigned short dmaSource;
|
||||
unsigned short dmaDestination;
|
||||
unsigned char rambank;
|
||||
unsigned char oamDmaPos;
|
||||
unsigned char haltHdmaState;
|
||||
unsigned char /*bool*/ IME;
|
||||
unsigned char /*bool*/ halted;
|
||||
unsigned char /*bool*/ enableRam;
|
||||
|
@ -147,7 +148,7 @@ struct SaveState {
|
|||
unsigned long counter;
|
||||
unsigned short shadow;
|
||||
unsigned char nr0;
|
||||
unsigned char /*bool*/ negging;
|
||||
unsigned char /*bool*/ neg;
|
||||
} sweep;
|
||||
Duty duty;
|
||||
Env env;
|
||||
|
@ -188,6 +189,7 @@ struct SaveState {
|
|||
} ch4;
|
||||
|
||||
unsigned long cycleCounter;
|
||||
unsigned char lastUpdate;
|
||||
} spu;
|
||||
|
||||
struct Time {
|
||||
|
|
|
@ -43,12 +43,13 @@ Clock) clock timer on transition to step.
|
|||
|
||||
*/
|
||||
|
||||
namespace gambatte {
|
||||
using namespace gambatte;
|
||||
|
||||
PSG::PSG()
|
||||
: buffer_(0)
|
||||
, bufferPos_(0)
|
||||
, lastUpdate_(0)
|
||||
, cycleCounter_(0)
|
||||
, soVol_(0)
|
||||
, rsum_(0x8000) // initialize to 0x8000 to prevent borrows from high word, xor away later
|
||||
, enabled_(false)
|
||||
|
@ -60,11 +61,41 @@ void PSG::init(bool cgb) {
|
|||
ch3_.init(cgb);
|
||||
}
|
||||
|
||||
void PSG::reset() {
|
||||
void PSG::reset(bool ds) {
|
||||
int const divOffset = lastUpdate_ & ds;
|
||||
unsigned long const cc = cycleCounter_ + divOffset;
|
||||
// cycleCounter >> 12 & 7 represents the frame sequencer position.
|
||||
cycleCounter_ = (cc & 0xFFF) + 2 * (~(cc + 1 + !ds) & 0x800);
|
||||
lastUpdate_ = ((lastUpdate_ + 3) & -4) - !ds;
|
||||
ch1_.reset();
|
||||
ch2_.reset();
|
||||
ch3_.reset();
|
||||
ch4_.reset();
|
||||
ch4_.reset(cycleCounter_);
|
||||
}
|
||||
|
||||
void PSG::divReset(bool ds) {
|
||||
int const divOffset = lastUpdate_ & ds;
|
||||
unsigned long const cc = cycleCounter_ + divOffset;
|
||||
cycleCounter_ = (cc & -0x1000) + 2 * (cc & 0x800) - divOffset;
|
||||
ch1_.resetCc(cc - divOffset, cycleCounter_);
|
||||
ch2_.resetCc(cc - divOffset, cycleCounter_);
|
||||
ch3_.resetCc(cc - divOffset, cycleCounter_);
|
||||
ch4_.resetCc(cc - divOffset, cycleCounter_);
|
||||
}
|
||||
|
||||
void PSG::speedChange(unsigned long const cpuCc, bool const ds) {
|
||||
generateSamples(cpuCc, ds);
|
||||
lastUpdate_ -= ds;
|
||||
// correct for cycles since DIV reset (if any).
|
||||
if (!ds) {
|
||||
unsigned long const cc = cycleCounter_;
|
||||
unsigned const divCycles = cc & 0xFFF;
|
||||
cycleCounter_ = cc - divCycles / 2 - lastUpdate_ % 2;
|
||||
ch1_.resetCc(cc, cycleCounter_);
|
||||
ch2_.resetCc(cc, cycleCounter_);
|
||||
ch3_.resetCc(cc, cycleCounter_);
|
||||
ch4_.resetCc(cc, cycleCounter_);
|
||||
}
|
||||
}
|
||||
|
||||
void PSG::setStatePtrs(SaveState &state) {
|
||||
|
@ -77,23 +108,26 @@ void PSG::loadState(SaveState const &state) {
|
|||
ch3_.loadState(state);
|
||||
ch4_.loadState(state);
|
||||
|
||||
lastUpdate_ = state.cpu.cycleCounter;
|
||||
cycleCounter_ = state.spu.cycleCounter;
|
||||
lastUpdate_ = state.cpu.cycleCounter - (1 - state.spu.lastUpdate) % 4u;
|
||||
setSoVolume(state.mem.ioamhram.get()[0x124]);
|
||||
mapSo(state.mem.ioamhram.get()[0x125]);
|
||||
enabled_ = state.mem.ioamhram.get()[0x126] >> 7 & 1;
|
||||
}
|
||||
|
||||
void PSG::accumulateChannels(unsigned long const cycles) {
|
||||
uint_least32_t *const buf = buffer_ + bufferPos_;
|
||||
std::memset(buf, 0, cycles * sizeof *buf);
|
||||
ch1_.update(buf, soVol_, cycles);
|
||||
ch2_.update(buf, soVol_, cycles);
|
||||
ch3_.update(buf, soVol_, cycles);
|
||||
ch4_.update(buf, soVol_, cycles);
|
||||
inline void PSG::accumulateChannels(unsigned long const cycles) {
|
||||
unsigned long const cc = cycleCounter_;
|
||||
uint_least32_t* const buf = buffer_ + bufferPos_;
|
||||
std::memset(buf, 0, cycles * sizeof * buf);
|
||||
ch1_.update(buf, soVol_, cc, cc + cycles);
|
||||
ch2_.update(buf, soVol_, cc, cc + cycles);
|
||||
ch3_.update(buf, soVol_, cc, cc + cycles);
|
||||
ch4_.update(buf, soVol_, cc, cc + cycles);
|
||||
cycleCounter_ = (cc + cycles) % SoundUnit::counter_max;
|
||||
}
|
||||
|
||||
void PSG::generateSamples(unsigned long const cycleCounter, bool const doubleSpeed) {
|
||||
unsigned long const cycles = (cycleCounter - lastUpdate_) >> (1 + doubleSpeed);
|
||||
void PSG::generateSamples(unsigned long const cpuCc, bool const doubleSpeed) {
|
||||
unsigned long const cycles = (cpuCc - lastUpdate_) >> (1 + doubleSpeed);
|
||||
lastUpdate_ += cycles << (1 + doubleSpeed);
|
||||
|
||||
if (cycles)
|
||||
|
@ -168,10 +202,10 @@ void PSG::setSoVolume(unsigned nr50) {
|
|||
|
||||
void PSG::mapSo(unsigned nr51) {
|
||||
unsigned long so = nr51 * so1Mul() + (nr51 >> 4) * so2Mul();
|
||||
ch1_.setSo((so & 0x00010001) * 0xFFFF);
|
||||
ch2_.setSo((so >> 1 & 0x00010001) * 0xFFFF);
|
||||
ch1_.setSo((so & 0x00010001) * 0xFFFF, cycleCounter_);
|
||||
ch2_.setSo((so >> 1 & 0x00010001) * 0xFFFF, cycleCounter_);
|
||||
ch3_.setSo((so >> 2 & 0x00010001) * 0xFFFF);
|
||||
ch4_.setSo((so >> 3 & 0x00010001) * 0xFFFF);
|
||||
ch4_.setSo((so >> 3 & 0x00010001) * 0xFFFF, cycleCounter_);
|
||||
}
|
||||
|
||||
unsigned PSG::getStatus() const {
|
||||
|
@ -193,5 +227,3 @@ SYNCFUNC(PSG)
|
|||
NSS(rsum_);
|
||||
NSS(enabled_);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,12 +31,14 @@ class PSG {
|
|||
public:
|
||||
PSG();
|
||||
void init(bool cgb);
|
||||
void reset();
|
||||
void reset(bool ds);
|
||||
void divReset(bool ds);
|
||||
void setStatePtrs(SaveState &state);
|
||||
void loadState(SaveState const &state);
|
||||
|
||||
void generateSamples(unsigned long cycleCounter, bool doubleSpeed);
|
||||
void resetCounter(unsigned long newCc, unsigned long oldCc, bool doubleSpeed);
|
||||
void speedChange(unsigned long cc, bool doubleSpeed);
|
||||
std::size_t fillBuffer();
|
||||
void setBuffer(uint_least32_t *buf) { buffer_ = buf; bufferPos_ = 0; }
|
||||
|
||||
|
@ -44,28 +46,28 @@ public:
|
|||
void setEnabled(bool value) { enabled_ = value; }
|
||||
|
||||
void setNr10(unsigned data) { ch1_.setNr0(data); }
|
||||
void setNr11(unsigned data) { ch1_.setNr1(data); }
|
||||
void setNr12(unsigned data) { ch1_.setNr2(data); }
|
||||
void setNr13(unsigned data) { ch1_.setNr3(data); }
|
||||
void setNr14(unsigned data) { ch1_.setNr4(data); }
|
||||
void setNr11(unsigned data) { ch1_.setNr1(data, cycleCounter_); }
|
||||
void setNr12(unsigned data) { ch1_.setNr2(data, cycleCounter_); }
|
||||
void setNr13(unsigned data) { ch1_.setNr3(data, cycleCounter_); }
|
||||
void setNr14(unsigned data, bool ds) { ch1_.setNr4(data, cycleCounter_, !(lastUpdate_ & ds)); }
|
||||
|
||||
void setNr21(unsigned data) { ch2_.setNr1(data); }
|
||||
void setNr22(unsigned data) { ch2_.setNr2(data); }
|
||||
void setNr23(unsigned data) { ch2_.setNr3(data); }
|
||||
void setNr24(unsigned data) { ch2_.setNr4(data); }
|
||||
void setNr21(unsigned data) { ch2_.setNr1(data, cycleCounter_); }
|
||||
void setNr22(unsigned data) { ch2_.setNr2(data, cycleCounter_); }
|
||||
void setNr23(unsigned data) { ch2_.setNr3(data, cycleCounter_); }
|
||||
void setNr24(unsigned data, bool ds) { ch2_.setNr4(data, cycleCounter_, !(lastUpdate_ & ds)); }
|
||||
|
||||
void setNr30(unsigned data) { ch3_.setNr0(data); }
|
||||
void setNr31(unsigned data) { ch3_.setNr1(data); }
|
||||
void setNr31(unsigned data) { ch3_.setNr1(data, cycleCounter_); }
|
||||
void setNr32(unsigned data) { ch3_.setNr2(data); }
|
||||
void setNr33(unsigned data) { ch3_.setNr3(data); }
|
||||
void setNr34(unsigned data) { ch3_.setNr4(data); }
|
||||
unsigned waveRamRead(unsigned index) const { return ch3_.waveRamRead(index); }
|
||||
void waveRamWrite(unsigned index, unsigned data) { ch3_.waveRamWrite(index, data); }
|
||||
void setNr34(unsigned data) { ch3_.setNr4(data, cycleCounter_); }
|
||||
unsigned waveRamRead(unsigned index) const { return ch3_.waveRamRead(index, cycleCounter_); }
|
||||
void waveRamWrite(unsigned index, unsigned data) { ch3_.waveRamWrite(index, data, cycleCounter_); }
|
||||
|
||||
void setNr41(unsigned data) { ch4_.setNr1(data); }
|
||||
void setNr42(unsigned data) { ch4_.setNr2(data); }
|
||||
void setNr43(unsigned data) { ch4_.setNr3(data); }
|
||||
void setNr44(unsigned data) { ch4_.setNr4(data); }
|
||||
void setNr41(unsigned data) { ch4_.setNr1(data, cycleCounter_); }
|
||||
void setNr42(unsigned data) { ch4_.setNr2(data, cycleCounter_); }
|
||||
void setNr43(unsigned data) { ch4_.setNr3(data, cycleCounter_); }
|
||||
void setNr44(unsigned data) { ch4_.setNr4(data, cycleCounter_); }
|
||||
|
||||
void setSoVolume(unsigned nr50);
|
||||
void mapSo(unsigned nr51);
|
||||
|
@ -79,6 +81,7 @@ private:
|
|||
uint_least32_t *buffer_;
|
||||
std::size_t bufferPos_;
|
||||
unsigned long lastUpdate_;
|
||||
unsigned long cycleCounter_;
|
||||
unsigned long soVol_;
|
||||
uint_least32_t rsum_;
|
||||
bool enabled_;
|
||||
|
|
|
@ -17,30 +17,28 @@
|
|||
//
|
||||
|
||||
#include "channel1.h"
|
||||
#include "psgdef.h"
|
||||
#include "../savestate.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace gambatte {
|
||||
using namespace gambatte;
|
||||
|
||||
Channel1::SweepUnit::SweepUnit(MasterDisabler &disabler, DutyUnit &dutyUnit)
|
||||
: disableMaster_(disabler)
|
||||
, dutyUnit_(dutyUnit)
|
||||
, shadow_(0)
|
||||
, nr0_(0)
|
||||
, negging_(false)
|
||||
, neg_(false)
|
||||
, cgb_(false)
|
||||
{
|
||||
}
|
||||
|
||||
unsigned Channel1::SweepUnit::calcFreq() {
|
||||
unsigned freq = shadow_ >> (nr0_ & 0x07);
|
||||
|
||||
if (nr0_ & 0x08) {
|
||||
freq = shadow_ - freq;
|
||||
negging_ = true;
|
||||
} else
|
||||
freq = shadow_ + freq;
|
||||
|
||||
unsigned const freq = nr0_ & psg_nr10_neg
|
||||
? shadow_ - (shadow_ >> (nr0_ & psg_nr10_rsh))
|
||||
: shadow_ + (shadow_ >> (nr0_ & psg_nr10_rsh));
|
||||
if (nr0_ & psg_nr10_neg)
|
||||
neg_ = true;
|
||||
if (freq & 2048)
|
||||
disableMaster_();
|
||||
|
||||
|
@ -48,12 +46,12 @@ unsigned Channel1::SweepUnit::calcFreq() {
|
|||
}
|
||||
|
||||
void Channel1::SweepUnit::event() {
|
||||
unsigned long const period = nr0_ >> 4 & 0x07;
|
||||
unsigned long const period = (nr0_ & psg_nr10_time) / (1u * psg_nr10_time & -psg_nr10_time);
|
||||
|
||||
if (period) {
|
||||
unsigned const freq = calcFreq();
|
||||
|
||||
if (!(freq & 2048) && (nr0_ & 0x07)) {
|
||||
if (!(freq & 2048) && nr0_ & psg_nr10_rsh) {
|
||||
shadow_ = freq;
|
||||
dutyUnit_.setFreq(freq, counter_);
|
||||
calcFreq();
|
||||
|
@ -65,25 +63,25 @@ void Channel1::SweepUnit::event() {
|
|||
}
|
||||
|
||||
void Channel1::SweepUnit::nr0Change(unsigned newNr0) {
|
||||
if (negging_ && !(newNr0 & 0x08))
|
||||
if (neg_ && !(newNr0 & 0x08))
|
||||
disableMaster_();
|
||||
|
||||
nr0_ = newNr0;
|
||||
}
|
||||
|
||||
void Channel1::SweepUnit::nr4Init(unsigned long const cc) {
|
||||
negging_ = false;
|
||||
neg_ = false;
|
||||
shadow_ = dutyUnit_.freq();
|
||||
|
||||
unsigned const period = nr0_ >> 4 & 0x07;
|
||||
unsigned const shift = nr0_ & 0x07;
|
||||
unsigned const period = (nr0_ & psg_nr10_time) / (1u * psg_nr10_time & -psg_nr10_time);
|
||||
unsigned const rsh = nr0_ & psg_nr10_rsh;
|
||||
|
||||
if (period | shift)
|
||||
if (period | rsh)
|
||||
counter_ = ((((cc + 2 + cgb_ * 2) >> 14) + (period ? period : 8)) << 14) + 2;
|
||||
else
|
||||
counter_ = counter_disabled;
|
||||
|
||||
if (shift)
|
||||
if (rsh)
|
||||
calcFreq();
|
||||
}
|
||||
|
||||
|
@ -95,7 +93,7 @@ void Channel1::SweepUnit::loadState(SaveState const &state) {
|
|||
counter_ = std::max(state.spu.ch1.sweep.counter, state.spu.cycleCounter);
|
||||
shadow_ = state.spu.ch1.sweep.shadow;
|
||||
nr0_ = state.spu.ch1.sweep.nr0;
|
||||
negging_ = state.spu.ch1.sweep.negging;
|
||||
neg_ = state.spu.ch1.sweep.neg;
|
||||
}
|
||||
|
||||
template<bool isReader>
|
||||
|
@ -104,7 +102,7 @@ void Channel1::SweepUnit::SyncState(NewState *ns)
|
|||
NSS(counter_);
|
||||
NSS(shadow_);
|
||||
NSS(nr0_);
|
||||
NSS(negging_);
|
||||
NSS(neg_);
|
||||
NSS(cgb_);
|
||||
}
|
||||
|
||||
|
@ -115,7 +113,6 @@ Channel1::Channel1()
|
|||
, envelopeUnit_(staticOutputTest_)
|
||||
, sweepUnit_(disableMaster_, dutyUnit_)
|
||||
, nextEventUnit_(0)
|
||||
, cycleCounter_(0)
|
||||
, soMask_(0)
|
||||
, prevOut_(0)
|
||||
, nr4_(0)
|
||||
|
@ -137,52 +134,48 @@ void Channel1::setNr0(unsigned data) {
|
|||
setEvent();
|
||||
}
|
||||
|
||||
void Channel1::setNr1(unsigned data) {
|
||||
lengthCounter_.nr1Change(data, nr4_, cycleCounter_);
|
||||
dutyUnit_.nr1Change(data, cycleCounter_);
|
||||
void Channel1::setNr1(unsigned data, unsigned long cc) {
|
||||
lengthCounter_.nr1Change(data, nr4_, cc);
|
||||
dutyUnit_.nr1Change(data, cc);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel1::setNr2(unsigned data) {
|
||||
void Channel1::setNr2(unsigned data, unsigned long cc) {
|
||||
if (envelopeUnit_.nr2Change(data))
|
||||
disableMaster_();
|
||||
else
|
||||
staticOutputTest_(cycleCounter_);
|
||||
staticOutputTest_(cc);
|
||||
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel1::setNr3(unsigned data) {
|
||||
dutyUnit_.nr3Change(data, cycleCounter_);
|
||||
void Channel1::setNr3(unsigned data, unsigned long cc) {
|
||||
dutyUnit_.nr3Change(data, cc);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel1::setNr4(unsigned const data) {
|
||||
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
|
||||
void Channel1::setNr4(unsigned data, unsigned long cc, unsigned long ref) {
|
||||
lengthCounter_.nr4Change(nr4_, data, cc);
|
||||
dutyUnit_.nr4Change(data, cc, ref, master_);
|
||||
nr4_ = data;
|
||||
dutyUnit_.nr4Change(data, cycleCounter_, master_);
|
||||
|
||||
if (data & 0x80) { // init-bit
|
||||
nr4_ &= 0x7F;
|
||||
master_ = !envelopeUnit_.nr4Init(cycleCounter_);
|
||||
sweepUnit_.nr4Init(cycleCounter_);
|
||||
staticOutputTest_(cycleCounter_);
|
||||
if (nr4_ & psg_nr4_init) {
|
||||
nr4_ -= psg_nr4_init;
|
||||
master_ = !envelopeUnit_.nr4Init(cc);
|
||||
sweepUnit_.nr4Init(cc);
|
||||
staticOutputTest_(cc);
|
||||
}
|
||||
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel1::setSo(unsigned long soMask) {
|
||||
void Channel1::setSo(unsigned long soMask, unsigned long cc) {
|
||||
soMask_ = soMask;
|
||||
staticOutputTest_(cycleCounter_);
|
||||
staticOutputTest_(cc);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel1::reset() {
|
||||
// cycleCounter >> 12 & 7 represents the frame sequencer position.
|
||||
cycleCounter_ &= 0xFFF;
|
||||
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
|
||||
|
||||
dutyUnit_.reset();
|
||||
envelopeUnit_.reset();
|
||||
sweepUnit_.reset();
|
||||
|
@ -196,58 +189,51 @@ void Channel1::init(bool cgb) {
|
|||
void Channel1::loadState(SaveState const &state) {
|
||||
sweepUnit_.loadState(state);
|
||||
dutyUnit_.loadState(state.spu.ch1.duty, state.mem.ioamhram.get()[0x111],
|
||||
state.spu.ch1.nr4, state.spu.cycleCounter);
|
||||
state.spu.ch1.nr4, state.spu.cycleCounter);
|
||||
envelopeUnit_.loadState(state.spu.ch1.env, state.mem.ioamhram.get()[0x112],
|
||||
state.spu.cycleCounter);
|
||||
state.spu.cycleCounter);
|
||||
lengthCounter_.loadState(state.spu.ch1.lcounter, state.spu.cycleCounter);
|
||||
|
||||
cycleCounter_ = state.spu.cycleCounter;
|
||||
nr4_ = state.spu.ch1.nr4;
|
||||
master_ = state.spu.ch1.master;
|
||||
}
|
||||
|
||||
void Channel1::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
|
||||
void Channel1::update(uint_least32_t* buf, unsigned long const soBaseVol, unsigned long cc, unsigned long const end) {
|
||||
unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0;
|
||||
unsigned long const outLow = outBase * (0 - 15ul);
|
||||
unsigned long const endCycles = cycleCounter_ + cycles;
|
||||
unsigned long const outLow = outBase * -15;
|
||||
|
||||
for (;;) {
|
||||
while (cc < end) {
|
||||
unsigned long const outHigh = master_
|
||||
? outBase * (envelopeUnit_.getVolume() * 2 - 15ul)
|
||||
: outLow;
|
||||
unsigned long const nextMajorEvent = std::min(nextEventUnit_->counter(), endCycles);
|
||||
? outBase * (envelopeUnit_.getVolume() * 2l - 15)
|
||||
: outLow;
|
||||
unsigned long const nextMajorEvent = std::min(nextEventUnit_->counter(), end);
|
||||
unsigned long out = dutyUnit_.isHighState() ? outHigh : outLow;
|
||||
|
||||
while (dutyUnit_.counter() <= nextMajorEvent) {
|
||||
*buf = out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += dutyUnit_.counter() - cycleCounter_;
|
||||
cycleCounter_ = dutyUnit_.counter();
|
||||
|
||||
buf += dutyUnit_.counter() - cc;
|
||||
cc = dutyUnit_.counter();
|
||||
dutyUnit_.event();
|
||||
out = dutyUnit_.isHighState() ? outHigh : outLow;
|
||||
}
|
||||
|
||||
if (cycleCounter_ < nextMajorEvent) {
|
||||
if (cc < nextMajorEvent) {
|
||||
*buf = out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += nextMajorEvent - cycleCounter_;
|
||||
cycleCounter_ = nextMajorEvent;
|
||||
buf += nextMajorEvent - cc;
|
||||
cc = nextMajorEvent;
|
||||
}
|
||||
|
||||
if (nextEventUnit_->counter() == nextMajorEvent) {
|
||||
nextEventUnit_->event();
|
||||
setEvent();
|
||||
} else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cycleCounter_ >= SoundUnit::counter_max) {
|
||||
dutyUnit_.resetCounters(cycleCounter_);
|
||||
lengthCounter_.resetCounters(cycleCounter_);
|
||||
envelopeUnit_.resetCounters(cycleCounter_);
|
||||
sweepUnit_.resetCounters(cycleCounter_);
|
||||
cycleCounter_ -= SoundUnit::counter_max;
|
||||
if (cc >= SoundUnit::counter_max) {
|
||||
dutyUnit_.resetCounters(cc);
|
||||
lengthCounter_.resetCounters(cc);
|
||||
envelopeUnit_.resetCounters(cc);
|
||||
sweepUnit_.resetCounters(cc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -265,12 +251,9 @@ SYNCFUNC(Channel1)
|
|||
EVS(nextEventUnit_, &lengthCounter_, 4);
|
||||
EES(nextEventUnit_, NULL);
|
||||
|
||||
NSS(cycleCounter_);
|
||||
NSS(soMask_);
|
||||
NSS(prevOut_);
|
||||
|
||||
NSS(nr4_);
|
||||
NSS(master_);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,14 +35,15 @@ class Channel1 {
|
|||
public:
|
||||
Channel1();
|
||||
void setNr0(unsigned data);
|
||||
void setNr1(unsigned data);
|
||||
void setNr2(unsigned data);
|
||||
void setNr3(unsigned data);
|
||||
void setNr4(unsigned data);
|
||||
void setSo(unsigned long soMask);
|
||||
void setNr1(unsigned data, unsigned long cc);
|
||||
void setNr2(unsigned data, unsigned long cc);
|
||||
void setNr3(unsigned data, unsigned long cc);
|
||||
void setNr4(unsigned data, unsigned long cc, unsigned long ref);
|
||||
void setSo(unsigned long soMask, unsigned long cc);
|
||||
bool isActive() const { return master_; }
|
||||
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
|
||||
void update(uint_least32_t* buf, unsigned long soBaseVol, unsigned long cc, unsigned long end);
|
||||
void reset();
|
||||
void resetCc(unsigned long cc, unsigned long ncc) { dutyUnit_.resetCc(cc, ncc); }
|
||||
void init(bool cgb);
|
||||
void loadState(SaveState const &state);
|
||||
|
||||
|
@ -54,6 +55,7 @@ private:
|
|||
void nr0Change(unsigned newNr0);
|
||||
void nr4Init(unsigned long cycleCounter);
|
||||
void reset();
|
||||
void resetCc(unsigned long cc, unsigned long ncc) { dutyUnit_.resetCc(cc, ncc); }
|
||||
void init(bool cgb) { cgb_ = cgb; }
|
||||
void loadState(SaveState const &state);
|
||||
|
||||
|
@ -62,7 +64,7 @@ private:
|
|||
DutyUnit &dutyUnit_;
|
||||
unsigned short shadow_;
|
||||
unsigned char nr0_;
|
||||
bool negging_;
|
||||
bool neg_;
|
||||
bool cgb_;
|
||||
|
||||
unsigned calcFreq();
|
||||
|
@ -80,7 +82,6 @@ private:
|
|||
EnvelopeUnit envelopeUnit_;
|
||||
SweepUnit sweepUnit_;
|
||||
SoundUnit *nextEventUnit_;
|
||||
unsigned long cycleCounter_;
|
||||
unsigned long soMask_;
|
||||
unsigned long prevOut_;
|
||||
unsigned char nr4_;
|
||||
|
|
|
@ -17,17 +17,17 @@
|
|||
//
|
||||
|
||||
#include "channel2.h"
|
||||
#include "psgdef.h"
|
||||
#include "../savestate.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace gambatte {
|
||||
using namespace gambatte;
|
||||
|
||||
Channel2::Channel2()
|
||||
: staticOutputTest_(*this, dutyUnit_)
|
||||
, disableMaster_(master_, dutyUnit_)
|
||||
, lengthCounter_(disableMaster_, 0x3F)
|
||||
, envelopeUnit_(staticOutputTest_)
|
||||
, cycleCounter_(0)
|
||||
, soMask_(0)
|
||||
, prevOut_(0)
|
||||
, nr4_(0)
|
||||
|
@ -42,51 +42,47 @@ void Channel2::setEvent() {
|
|||
nextEventUnit = &lengthCounter_;
|
||||
}
|
||||
|
||||
void Channel2::setNr1(unsigned data) {
|
||||
lengthCounter_.nr1Change(data, nr4_, cycleCounter_);
|
||||
dutyUnit_.nr1Change(data, cycleCounter_);
|
||||
void Channel2::setNr1(unsigned data, unsigned long cc) {
|
||||
lengthCounter_.nr1Change(data, nr4_, cc);
|
||||
dutyUnit_.nr1Change(data, cc);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel2::setNr2(unsigned data) {
|
||||
void Channel2::setNr2(unsigned data, unsigned long cc) {
|
||||
if (envelopeUnit_.nr2Change(data))
|
||||
disableMaster_();
|
||||
else
|
||||
staticOutputTest_(cycleCounter_);
|
||||
staticOutputTest_(cc);
|
||||
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel2::setNr3(unsigned data) {
|
||||
dutyUnit_.nr3Change(data, cycleCounter_);
|
||||
void Channel2::setNr3(unsigned data, unsigned long cc) {
|
||||
dutyUnit_.nr3Change(data, cc);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel2::setNr4(unsigned const data) {
|
||||
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
|
||||
void Channel2::setNr4(unsigned data, unsigned long cc, unsigned long ref) {
|
||||
lengthCounter_.nr4Change(nr4_, data, cc);
|
||||
nr4_ = data;
|
||||
dutyUnit_.nr4Change(data, cycleCounter_, master_);
|
||||
|
||||
if (data & 0x80) { // init-bit
|
||||
nr4_ &= 0x7F;
|
||||
master_ = !envelopeUnit_.nr4Init(cycleCounter_);
|
||||
staticOutputTest_(cycleCounter_);
|
||||
if (nr4_ & psg_nr4_init) {
|
||||
nr4_ -= psg_nr4_init;
|
||||
master_ = !envelopeUnit_.nr4Init(cc);
|
||||
staticOutputTest_(cc);
|
||||
}
|
||||
|
||||
dutyUnit_.nr4Change(data, cc, ref, master_);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel2::setSo(unsigned long soMask) {
|
||||
void Channel2::setSo(unsigned long soMask, unsigned long cc) {
|
||||
soMask_ = soMask;
|
||||
staticOutputTest_(cycleCounter_);
|
||||
staticOutputTest_(cc);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel2::reset() {
|
||||
// cycleCounter >> 12 & 7 represents the frame sequencer position.
|
||||
cycleCounter_ &= 0xFFF;
|
||||
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
|
||||
|
||||
dutyUnit_.reset();
|
||||
envelopeUnit_.reset();
|
||||
setEvent();
|
||||
|
@ -94,57 +90,50 @@ void Channel2::reset() {
|
|||
|
||||
void Channel2::loadState(SaveState const &state) {
|
||||
dutyUnit_.loadState(state.spu.ch2.duty, state.mem.ioamhram.get()[0x116],
|
||||
state.spu.ch2.nr4, state.spu.cycleCounter);
|
||||
state.spu.ch2.nr4, state.spu.cycleCounter);
|
||||
envelopeUnit_.loadState(state.spu.ch2.env, state.mem.ioamhram.get()[0x117],
|
||||
state.spu.cycleCounter);
|
||||
state.spu.cycleCounter);
|
||||
lengthCounter_.loadState(state.spu.ch2.lcounter, state.spu.cycleCounter);
|
||||
|
||||
cycleCounter_ = state.spu.cycleCounter;
|
||||
nr4_ = state.spu.ch2.nr4;
|
||||
master_ = state.spu.ch2.master;
|
||||
}
|
||||
|
||||
void Channel2::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
|
||||
void Channel2::update(uint_least32_t* buf, unsigned long const soBaseVol, unsigned long cc, unsigned long const end) {
|
||||
unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0;
|
||||
unsigned long const outLow = outBase * (0 - 15ul);
|
||||
unsigned long const endCycles = cycleCounter_ + cycles;
|
||||
unsigned long const outLow = outBase * -15;
|
||||
|
||||
for (;;) {
|
||||
while (cc < end) {
|
||||
unsigned long const outHigh = master_
|
||||
? outBase * (envelopeUnit_.getVolume() * 2 - 15ul)
|
||||
: outLow;
|
||||
unsigned long const nextMajorEvent = std::min(nextEventUnit->counter(), endCycles);
|
||||
? outBase * (envelopeUnit_.getVolume() * 2l - 15)
|
||||
: outLow;
|
||||
unsigned long const nextMajorEvent = std::min(nextEventUnit->counter(), end);
|
||||
unsigned long out = dutyUnit_.isHighState() ? outHigh : outLow;
|
||||
|
||||
while (dutyUnit_.counter() <= nextMajorEvent) {
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += dutyUnit_.counter() - cycleCounter_;
|
||||
cycleCounter_ = dutyUnit_.counter();
|
||||
|
||||
buf += dutyUnit_.counter() - cc;
|
||||
cc = dutyUnit_.counter();
|
||||
dutyUnit_.event();
|
||||
out = dutyUnit_.isHighState() ? outHigh : outLow;
|
||||
}
|
||||
|
||||
if (cycleCounter_ < nextMajorEvent) {
|
||||
if (cc < nextMajorEvent) {
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += nextMajorEvent - cycleCounter_;
|
||||
cycleCounter_ = nextMajorEvent;
|
||||
buf += nextMajorEvent - cc;
|
||||
cc = nextMajorEvent;
|
||||
}
|
||||
|
||||
if (nextEventUnit->counter() == nextMajorEvent) {
|
||||
nextEventUnit->event();
|
||||
setEvent();
|
||||
} else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cycleCounter_ >= SoundUnit::counter_max) {
|
||||
dutyUnit_.resetCounters(cycleCounter_);
|
||||
lengthCounter_.resetCounters(cycleCounter_);
|
||||
envelopeUnit_.resetCounters(cycleCounter_);
|
||||
cycleCounter_ -= SoundUnit::counter_max;
|
||||
if (cc >= SoundUnit::counter_max) {
|
||||
dutyUnit_.resetCounters(cc);
|
||||
lengthCounter_.resetCounters(cc);
|
||||
envelopeUnit_.resetCounters(cc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,7 +149,6 @@ SYNCFUNC(Channel2)
|
|||
EVS(nextEventUnit, &lengthCounter_, 3);
|
||||
EES(nextEventUnit, NULL);
|
||||
|
||||
NSS(cycleCounter_);
|
||||
NSS(soMask_);
|
||||
NSS(prevOut_);
|
||||
|
||||
|
@ -168,4 +156,3 @@ SYNCFUNC(Channel2)
|
|||
NSS(master_);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,14 +33,15 @@ struct SaveState;
|
|||
class Channel2 {
|
||||
public:
|
||||
Channel2();
|
||||
void setNr1(unsigned data);
|
||||
void setNr2(unsigned data);
|
||||
void setNr3(unsigned data);
|
||||
void setNr4(unsigned data);
|
||||
void setSo(unsigned long soMask);
|
||||
void setNr1(unsigned data, unsigned long cc);
|
||||
void setNr2(unsigned data, unsigned long cc);
|
||||
void setNr3(unsigned data, unsigned long cc);
|
||||
void setNr4(unsigned data, unsigned long cc, unsigned long ref);
|
||||
void setSo(unsigned long soMask, unsigned long cc);
|
||||
bool isActive() const { return master_; }
|
||||
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
|
||||
void update(uint_least32_t* buf, unsigned long soBaseVol, unsigned long cc, unsigned long end);
|
||||
void reset();
|
||||
void resetCc(unsigned long cc, unsigned long ncc) { dutyUnit_.resetCc(cc, ncc); }
|
||||
void loadState(SaveState const &state);
|
||||
|
||||
private:
|
||||
|
@ -52,7 +53,6 @@ private:
|
|||
DutyUnit dutyUnit_;
|
||||
EnvelopeUnit envelopeUnit_;
|
||||
SoundUnit *nextEventUnit;
|
||||
unsigned long cycleCounter_;
|
||||
unsigned long soMask_;
|
||||
unsigned long prevOut_;
|
||||
unsigned char nr4_;
|
||||
|
|
|
@ -17,20 +17,22 @@
|
|||
//
|
||||
|
||||
#include "channel3.h"
|
||||
#include "psgdef.h"
|
||||
#include "../savestate.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
static inline unsigned toPeriod(unsigned nr3, unsigned nr4) {
|
||||
return 0x800 - ((nr4 << 8 & 0x700) | nr3);
|
||||
}
|
||||
using namespace gambatte;
|
||||
|
||||
namespace gambatte {
|
||||
namespace {
|
||||
unsigned toPeriod(unsigned nr3, unsigned nr4) {
|
||||
return 0x800 - ((nr4 << 8 & 0x700) | nr3);
|
||||
}
|
||||
}
|
||||
|
||||
Channel3::Channel3()
|
||||
: disableMaster_(master_, waveCounter_)
|
||||
, lengthCounter_(disableMaster_, 0xFF)
|
||||
, cycleCounter_(0)
|
||||
, soMask_(0)
|
||||
, prevOut_(0)
|
||||
, waveCounter_(SoundUnit::counter_disabled)
|
||||
|
@ -47,25 +49,22 @@ Channel3::Channel3()
|
|||
}
|
||||
|
||||
void Channel3::setNr0(unsigned data) {
|
||||
nr0_ = data & 0x80;
|
||||
|
||||
if (!(data & 0x80))
|
||||
nr0_ = data & psg_nr4_init;
|
||||
if (!nr0_)
|
||||
disableMaster_();
|
||||
}
|
||||
|
||||
void Channel3::setNr2(unsigned data) {
|
||||
rshift_ = (data >> 5 & 3U) - 1;
|
||||
if (rshift_ > 3)
|
||||
rshift_ = 4;
|
||||
rshift_ = std::min((data >> 5 & 3) - 1, 4u);
|
||||
}
|
||||
|
||||
void Channel3::setNr4(unsigned const data) {
|
||||
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
|
||||
nr4_ = data & 0x7F;
|
||||
void Channel3::setNr4(unsigned const data, unsigned long const cc) {
|
||||
lengthCounter_.nr4Change(nr4_, data, cc);
|
||||
nr4_ = data & ~(1u * psg_nr4_init);
|
||||
|
||||
if (data & nr0_/* & 0x80*/) {
|
||||
if (!cgb_ && waveCounter_ == cycleCounter_ + 1) {
|
||||
unsigned const pos = ((wavePos_ + 1) & 0x1F) >> 1;
|
||||
if (data & nr0_) {
|
||||
if (!cgb_ && waveCounter_ == cc + 1) {
|
||||
int const pos = (wavePos_ + 1) / 2 % sizeof waveRam_;
|
||||
|
||||
if (pos < 4)
|
||||
waveRam_[0] = waveRam_[pos];
|
||||
|
@ -75,7 +74,7 @@ void Channel3::setNr4(unsigned const data) {
|
|||
|
||||
master_ = true;
|
||||
wavePos_ = 0;
|
||||
lastReadTime_ = waveCounter_ = cycleCounter_ + toPeriod(nr3_, data) + 3;
|
||||
lastReadTime_ = waveCounter_ = cc + toPeriod(nr3_, data) + 3;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,13 +83,15 @@ void Channel3::setSo(unsigned long soMask) {
|
|||
}
|
||||
|
||||
void Channel3::reset() {
|
||||
// cycleCounter >> 12 & 7 represents the frame sequencer position.
|
||||
cycleCounter_ &= 0xFFF;
|
||||
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
|
||||
|
||||
sampleBuf_ = 0;
|
||||
}
|
||||
|
||||
void Channel3::resetCc(unsigned long cc, unsigned long newCc) {
|
||||
lastReadTime_ -= cc - newCc;
|
||||
if (waveCounter_ != SoundUnit::counter_disabled)
|
||||
waveCounter_ -= cc - newCc;
|
||||
}
|
||||
|
||||
void Channel3::init(bool cgb) {
|
||||
cgb_ = cgb;
|
||||
}
|
||||
|
@ -102,16 +103,15 @@ void Channel3::setStatePtrs(SaveState &state) {
|
|||
void Channel3::loadState(SaveState const &state) {
|
||||
lengthCounter_.loadState(state.spu.ch3.lcounter, state.spu.cycleCounter);
|
||||
|
||||
cycleCounter_ = state.spu.cycleCounter;
|
||||
waveCounter_ = std::max(state.spu.ch3.waveCounter, state.spu.cycleCounter);
|
||||
lastReadTime_ = state.spu.ch3.lastReadTime;
|
||||
nr3_ = state.spu.ch3.nr3;
|
||||
nr4_ = state.spu.ch3.nr4;
|
||||
wavePos_ = state.spu.ch3.wavePos & 0x1F;
|
||||
wavePos_ = state.spu.ch3.wavePos % (2 * sizeof waveRam_);
|
||||
sampleBuf_ = state.spu.ch3.sampleBuf;
|
||||
master_ = state.spu.ch3.master;
|
||||
|
||||
nr0_ = state.mem.ioamhram.get()[0x11A] & 0x80;
|
||||
nr0_ = state.mem.ioamhram.get()[0x11A] & psg_nr4_init;
|
||||
setNr2(state.mem.ioamhram.get()[0x11C]);
|
||||
}
|
||||
|
||||
|
@ -122,77 +122,80 @@ void Channel3::updateWaveCounter(unsigned long const cc) {
|
|||
|
||||
lastReadTime_ = waveCounter_ + periods * period;
|
||||
waveCounter_ = lastReadTime_ + period;
|
||||
|
||||
wavePos_ += periods + 1;
|
||||
wavePos_ &= 0x1F;
|
||||
|
||||
sampleBuf_ = waveRam_[wavePos_ >> 1];
|
||||
wavePos_ = (wavePos_ + periods + 1) % (2 * sizeof waveRam_);
|
||||
sampleBuf_ = waveRam_[wavePos_ / 2];
|
||||
}
|
||||
}
|
||||
|
||||
void Channel3::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
|
||||
unsigned long const outBase = nr0_/* & 0x80*/ ? soBaseVol & soMask_ : 0;
|
||||
void Channel3::update(uint_least32_t* buf, unsigned long const soBaseVol, unsigned long cc, unsigned long const end) {
|
||||
unsigned long const outBase = nr0_ ? soBaseVol & soMask_ : 0;
|
||||
|
||||
if (outBase && rshift_ != 4) {
|
||||
unsigned long const endCycles = cycleCounter_ + cycles;
|
||||
|
||||
for (;;) {
|
||||
while (std::min(waveCounter_, lengthCounter_.counter()) <= end) {
|
||||
unsigned pos = wavePos_;
|
||||
unsigned const period = toPeriod(nr3_, nr4_), rsh = rshift_;
|
||||
unsigned long const nextMajorEvent =
|
||||
std::min(lengthCounter_.counter(), endCycles);
|
||||
std::min(lengthCounter_.counter(), end);
|
||||
unsigned long cnt = waveCounter_, prevOut = prevOut_;
|
||||
unsigned long out = master_
|
||||
? ((sampleBuf_ >> (~wavePos_ << 2 & 4) & 0xF) >> rshift_) * 2 - 15ul
|
||||
: 0 - 15ul;
|
||||
? ((pos % 2 ? sampleBuf_ & 0xF : sampleBuf_ >> 4) >> rsh) * 2l - 15
|
||||
: -15;
|
||||
out *= outBase;
|
||||
|
||||
while (waveCounter_ <= nextMajorEvent) {
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += waveCounter_ - cycleCounter_;
|
||||
cycleCounter_ = waveCounter_;
|
||||
|
||||
lastReadTime_ = waveCounter_;
|
||||
waveCounter_ += toPeriod(nr3_, nr4_);
|
||||
++wavePos_;
|
||||
wavePos_ &= 0x1F;
|
||||
sampleBuf_ = waveRam_[wavePos_ >> 1];
|
||||
out = ((sampleBuf_ >> (~wavePos_ << 2 & 4) & 0xF) >> rshift_) * 2 - 15ul;
|
||||
while (cnt <= nextMajorEvent) {
|
||||
*buf += out - prevOut;
|
||||
prevOut = out;
|
||||
buf += cnt - cc;
|
||||
cc = cnt;
|
||||
cnt += period;
|
||||
++pos;
|
||||
unsigned const s = waveRam_[pos / 2 % sizeof waveRam_];
|
||||
out = ((pos % 2 ? s & 0xF : s >> 4) >> rsh) * 2l - 15;
|
||||
out *= outBase;
|
||||
}
|
||||
|
||||
if (cycleCounter_ < nextMajorEvent) {
|
||||
if (cnt != waveCounter_) {
|
||||
wavePos_ = pos % (2 * sizeof waveRam_);
|
||||
sampleBuf_ = waveRam_[wavePos_ / 2];
|
||||
prevOut_ = prevOut;
|
||||
waveCounter_ = cnt;
|
||||
lastReadTime_ = cc;
|
||||
}
|
||||
if (cc < nextMajorEvent) {
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += nextMajorEvent - cycleCounter_;
|
||||
cycleCounter_ = nextMajorEvent;
|
||||
buf += nextMajorEvent - cc;
|
||||
cc = nextMajorEvent;
|
||||
}
|
||||
|
||||
if (lengthCounter_.counter() == nextMajorEvent) {
|
||||
if (lengthCounter_.counter() == nextMajorEvent)
|
||||
lengthCounter_.event();
|
||||
} else
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
unsigned long const out = outBase * (0 - 15ul);
|
||||
if (cc < end) {
|
||||
unsigned long out = master_
|
||||
? ((wavePos_ % 2 ? sampleBuf_ & 0xF : sampleBuf_ >> 4) >> rshift_) * 2l - 15
|
||||
: -15;
|
||||
out *= outBase;
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
cc = end;
|
||||
}
|
||||
}
|
||||
else {
|
||||
unsigned long const out = outBase * -15;
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
cycleCounter_ += cycles;
|
||||
|
||||
while (lengthCounter_.counter() <= cycleCounter_) {
|
||||
cc = end;
|
||||
while (lengthCounter_.counter() <= cc) {
|
||||
updateWaveCounter(lengthCounter_.counter());
|
||||
lengthCounter_.event();
|
||||
}
|
||||
|
||||
updateWaveCounter(cycleCounter_);
|
||||
updateWaveCounter(cc);
|
||||
}
|
||||
|
||||
if (cycleCounter_ >= SoundUnit::counter_max) {
|
||||
lengthCounter_.resetCounters(cycleCounter_);
|
||||
|
||||
if (cc >= SoundUnit::counter_max) {
|
||||
lengthCounter_.resetCounters(cc);
|
||||
lastReadTime_ -= SoundUnit::counter_max;
|
||||
if (waveCounter_ != SoundUnit::counter_disabled)
|
||||
waveCounter_ -= SoundUnit::counter_max;
|
||||
|
||||
lastReadTime_ -= SoundUnit::counter_max;
|
||||
cycleCounter_ -= SoundUnit::counter_max;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,7 +205,6 @@ SYNCFUNC(Channel3)
|
|||
|
||||
SSS(lengthCounter_);
|
||||
|
||||
NSS(cycleCounter_);
|
||||
NSS(soMask_);
|
||||
NSS(prevOut_);
|
||||
NSS(waveCounter_);
|
||||
|
@ -218,5 +220,3 @@ SYNCFUNC(Channel3)
|
|||
NSS(master_);
|
||||
NSS(cgb_);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,35 +32,37 @@ class Channel3 {
|
|||
public:
|
||||
Channel3();
|
||||
bool isActive() const { return master_; }
|
||||
bool isCgb() const { return cgb_; }
|
||||
void reset();
|
||||
void resetCc(unsigned long cc, unsigned long newCc);
|
||||
void init(bool cgb);
|
||||
void setStatePtrs(SaveState &state);
|
||||
void loadState(const SaveState &state);
|
||||
void loadState(SaveState const &state);
|
||||
void setNr0(unsigned data);
|
||||
void setNr1(unsigned data) { lengthCounter_.nr1Change(data, nr4_, cycleCounter_); }
|
||||
void setNr1(unsigned data, unsigned long cc) { lengthCounter_.nr1Change(data, nr4_, cc); }
|
||||
void setNr2(unsigned data);
|
||||
void setNr3(unsigned data) { nr3_ = data; }
|
||||
void setNr4(unsigned data);
|
||||
void setNr4(unsigned data, unsigned long cc);
|
||||
void setSo(unsigned long soMask);
|
||||
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
|
||||
void update(uint_least32_t* buf, unsigned long soBaseVol, unsigned long cc, unsigned long end);
|
||||
|
||||
unsigned waveRamRead(unsigned index) const {
|
||||
unsigned waveRamRead(unsigned index, unsigned long cc) const {
|
||||
if (master_) {
|
||||
if (!cgb_ && cycleCounter_ != lastReadTime_)
|
||||
if (!cgb_ && cc != lastReadTime_)
|
||||
return 0xFF;
|
||||
|
||||
index = wavePos_ >> 1;
|
||||
index = wavePos_ / 2;
|
||||
}
|
||||
|
||||
return waveRam_[index];
|
||||
}
|
||||
|
||||
void waveRamWrite(unsigned index, unsigned data) {
|
||||
void waveRamWrite(unsigned index, unsigned data, unsigned long cc) {
|
||||
if (master_) {
|
||||
if (!cgb_ && cycleCounter_ != lastReadTime_)
|
||||
if (!cgb_ && cc != lastReadTime_)
|
||||
return;
|
||||
|
||||
index = wavePos_ >> 1;
|
||||
index = wavePos_ / 2;
|
||||
}
|
||||
|
||||
waveRam_[index] = data;
|
||||
|
@ -83,7 +85,6 @@ private:
|
|||
unsigned char waveRam_[0x10];
|
||||
Ch3MasterDisabler disableMaster_;
|
||||
LengthCounter lengthCounter_;
|
||||
unsigned long cycleCounter_;
|
||||
unsigned long soMask_;
|
||||
unsigned long prevOut_;
|
||||
unsigned long waveCounter_;
|
||||
|
|
|
@ -17,23 +17,26 @@
|
|||
//
|
||||
|
||||
#include "channel4.h"
|
||||
#include "psgdef.h"
|
||||
#include "../savestate.h"
|
||||
#include <algorithm>
|
||||
|
||||
static unsigned long toPeriod(unsigned const nr3) {
|
||||
unsigned s = (nr3 >> 4) + 3;
|
||||
unsigned r = nr3 & 7;
|
||||
using namespace gambatte;
|
||||
|
||||
if (!r) {
|
||||
r = 1;
|
||||
--s;
|
||||
namespace {
|
||||
static unsigned long toPeriod(unsigned const nr3) {
|
||||
unsigned s = nr3 / (1u * psg_nr43_s & -psg_nr43_s) + 3;
|
||||
unsigned r = nr3 & psg_nr43_r;
|
||||
|
||||
if (!r) {
|
||||
r = 1;
|
||||
--s;
|
||||
}
|
||||
|
||||
return r << s;
|
||||
}
|
||||
|
||||
return r << s;
|
||||
}
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
Channel4::Lfsr::Lfsr()
|
||||
: backupCounter_(counter_disabled)
|
||||
, reg_(0x7FFF)
|
||||
|
@ -48,17 +51,18 @@ void Channel4::Lfsr::updateBackupCounter(unsigned long const cc) {
|
|||
unsigned long periods = (cc - backupCounter_) / period + 1;
|
||||
backupCounter_ += periods * period;
|
||||
|
||||
if (master_ && nr3_ < 0xE0) {
|
||||
if (nr3_ & 8) {
|
||||
if (master_ && nr3_ < 0xE * (1u * psg_nr43_s & -psg_nr43_s)) {
|
||||
if (nr3_ & psg_nr43_7biten) {
|
||||
while (periods > 6) {
|
||||
unsigned const xored = (reg_ << 1 ^ reg_) & 0x7E;
|
||||
reg_ = (reg_ >> 6 & ~0x7E) | xored | xored << 8;
|
||||
reg_ = (reg_ >> 6 & ~0x7Eu) | xored | xored << 8;
|
||||
periods -= 6;
|
||||
}
|
||||
|
||||
unsigned const xored = ((reg_ ^ reg_ >> 1) << (7 - periods)) & 0x7F;
|
||||
reg_ = (reg_ >> periods & ~(0x80 - (0x80 >> periods))) | xored | xored << 8;
|
||||
} else {
|
||||
reg_ = (reg_ >> periods & ~(0x80u - (0x80 >> periods))) | xored | xored << 8;
|
||||
}
|
||||
else {
|
||||
while (periods > 15) {
|
||||
reg_ = reg_ ^ reg_ >> 1;
|
||||
periods -= 15;
|
||||
|
@ -76,13 +80,13 @@ void Channel4::Lfsr::reviveCounter(unsigned long cc) {
|
|||
}
|
||||
|
||||
inline void Channel4::Lfsr::event() {
|
||||
if (nr3_ < 0xE0) {
|
||||
if (nr3_ < 0xE * (1u * psg_nr43_s & -psg_nr43_s)) {
|
||||
unsigned const shifted = reg_ >> 1;
|
||||
unsigned const xored = (reg_ ^ shifted) & 1;
|
||||
reg_ = shifted | xored << 14;
|
||||
|
||||
if (nr3_ & 8)
|
||||
reg_ = (reg_ & ~0x40) | xored << 6;
|
||||
if (nr3_ & psg_nr43_7biten)
|
||||
reg_ = (reg_ & ~0x40u) | xored << 6;
|
||||
}
|
||||
|
||||
counter_ += toPeriod(nr3_);
|
||||
|
@ -108,6 +112,13 @@ void Channel4::Lfsr::reset(unsigned long cc) {
|
|||
backupCounter_ = cc + toPeriod(nr3_);
|
||||
}
|
||||
|
||||
void Channel4::Lfsr::resetCc(unsigned long cc, unsigned long newCc) {
|
||||
updateBackupCounter(cc);
|
||||
backupCounter_ -= cc - newCc;
|
||||
if (counter_ != counter_disabled)
|
||||
counter_ -= cc - newCc;
|
||||
}
|
||||
|
||||
void Channel4::Lfsr::resetCounters(unsigned long oldCc) {
|
||||
updateBackupCounter(oldCc);
|
||||
backupCounter_ -= counter_max;
|
||||
|
@ -137,7 +148,6 @@ Channel4::Channel4()
|
|||
, lengthCounter_(disableMaster_, 0x3F)
|
||||
, envelopeUnit_(staticOutputTest_)
|
||||
, nextEventUnit_(0)
|
||||
, cycleCounter_(0)
|
||||
, soMask_(0)
|
||||
, prevOut_(0)
|
||||
, nr4_(0)
|
||||
|
@ -152,49 +162,44 @@ void Channel4::setEvent() {
|
|||
nextEventUnit_ = &lengthCounter_;
|
||||
}
|
||||
|
||||
void Channel4::setNr1(unsigned data) {
|
||||
lengthCounter_.nr1Change(data, nr4_, cycleCounter_);
|
||||
void Channel4::setNr1(unsigned data, unsigned long cc) {
|
||||
lengthCounter_.nr1Change(data, nr4_, cc);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel4::setNr2(unsigned data) {
|
||||
void Channel4::setNr2(unsigned data, unsigned long cc) {
|
||||
if (envelopeUnit_.nr2Change(data))
|
||||
disableMaster_();
|
||||
else
|
||||
staticOutputTest_(cycleCounter_);
|
||||
staticOutputTest_(cc);
|
||||
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel4::setNr4(unsigned const data) {
|
||||
lengthCounter_.nr4Change(nr4_, data, cycleCounter_);
|
||||
void Channel4::setNr4(unsigned const data, unsigned long const cc) {
|
||||
lengthCounter_.nr4Change(nr4_, data, cc);
|
||||
nr4_ = data;
|
||||
|
||||
if (data & 0x80) { // init-bit
|
||||
nr4_ &= 0x7F;
|
||||
master_ = !envelopeUnit_.nr4Init(cycleCounter_);
|
||||
|
||||
if (nr4_ & psg_nr4_init) {
|
||||
nr4_ -= psg_nr4_init;
|
||||
master_ = !envelopeUnit_.nr4Init(cc);
|
||||
if (master_)
|
||||
lfsr_.nr4Init(cycleCounter_);
|
||||
lfsr_.nr4Init(cc);
|
||||
|
||||
staticOutputTest_(cycleCounter_);
|
||||
staticOutputTest_(cc);
|
||||
}
|
||||
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel4::setSo(unsigned long soMask) {
|
||||
void Channel4::setSo(unsigned long soMask, unsigned long cc) {
|
||||
soMask_ = soMask;
|
||||
staticOutputTest_(cycleCounter_);
|
||||
staticOutputTest_(cc);
|
||||
setEvent();
|
||||
}
|
||||
|
||||
void Channel4::reset() {
|
||||
// cycleCounter >> 12 & 7 represents the frame sequencer position.
|
||||
cycleCounter_ &= 0xFFF;
|
||||
cycleCounter_ += ~(cycleCounter_ + 2) << 1 & 0x1000;
|
||||
|
||||
lfsr_.reset(cycleCounter_);
|
||||
void Channel4::reset(unsigned long cc) {
|
||||
lfsr_.reset(cc);
|
||||
envelopeUnit_.reset();
|
||||
setEvent();
|
||||
}
|
||||
|
@ -202,53 +207,49 @@ void Channel4::reset() {
|
|||
void Channel4::loadState(SaveState const &state) {
|
||||
lfsr_.loadState(state);
|
||||
envelopeUnit_.loadState(state.spu.ch4.env, state.mem.ioamhram.get()[0x121],
|
||||
state.spu.cycleCounter);
|
||||
state.spu.cycleCounter);
|
||||
lengthCounter_.loadState(state.spu.ch4.lcounter, state.spu.cycleCounter);
|
||||
|
||||
cycleCounter_ = state.spu.cycleCounter;
|
||||
nr4_ = state.spu.ch4.nr4;
|
||||
master_ = state.spu.ch4.master;
|
||||
}
|
||||
|
||||
void Channel4::update(uint_least32_t *buf, unsigned long const soBaseVol, unsigned long cycles) {
|
||||
void Channel4::update(uint_least32_t* buf, unsigned long const soBaseVol, unsigned long cc, unsigned long const end) {
|
||||
unsigned long const outBase = envelopeUnit_.dacIsOn() ? soBaseVol & soMask_ : 0;
|
||||
unsigned long const outLow = outBase * (0 - 15ul);
|
||||
unsigned long const endCycles = cycleCounter_ + cycles;
|
||||
unsigned long const outLow = outBase * -15;
|
||||
|
||||
for (;;) {
|
||||
unsigned long const outHigh = outBase * (envelopeUnit_.getVolume() * 2 - 15ul);
|
||||
unsigned long const nextMajorEvent = std::min(nextEventUnit_->counter(), endCycles);
|
||||
while (cc < end) {
|
||||
unsigned long const outHigh = outBase * (envelopeUnit_.getVolume() * 2l - 15);
|
||||
unsigned long const nextMajorEvent = std::min(nextEventUnit_->counter(), end);
|
||||
unsigned long out = lfsr_.isHighState() ? outHigh : outLow;
|
||||
|
||||
while (lfsr_.counter() <= nextMajorEvent) {
|
||||
if (lfsr_.counter() <= nextMajorEvent) {
|
||||
Lfsr lfsr = lfsr_;
|
||||
while (lfsr.counter() <= nextMajorEvent) {
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += lfsr.counter() - cc;
|
||||
cc = lfsr.counter();
|
||||
lfsr.event();
|
||||
out = lfsr.isHighState() ? outHigh : outLow;
|
||||
}
|
||||
lfsr_ = lfsr;
|
||||
}
|
||||
if (cc < nextMajorEvent) {
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += lfsr_.counter() - cycleCounter_;
|
||||
cycleCounter_ = lfsr_.counter();
|
||||
|
||||
lfsr_.event();
|
||||
out = lfsr_.isHighState() ? outHigh : outLow;
|
||||
buf += nextMajorEvent - cc;
|
||||
cc = nextMajorEvent;
|
||||
}
|
||||
|
||||
if (cycleCounter_ < nextMajorEvent) {
|
||||
*buf += out - prevOut_;
|
||||
prevOut_ = out;
|
||||
buf += nextMajorEvent - cycleCounter_;
|
||||
cycleCounter_ = nextMajorEvent;
|
||||
}
|
||||
|
||||
if (nextEventUnit_->counter() == nextMajorEvent) {
|
||||
nextEventUnit_->event();
|
||||
setEvent();
|
||||
} else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cycleCounter_ >= SoundUnit::counter_max) {
|
||||
lengthCounter_.resetCounters(cycleCounter_);
|
||||
lfsr_.resetCounters(cycleCounter_);
|
||||
envelopeUnit_.resetCounters(cycleCounter_);
|
||||
cycleCounter_ -= SoundUnit::counter_max;
|
||||
if (cc >= SoundUnit::counter_max) {
|
||||
lengthCounter_.resetCounters(cc);
|
||||
lfsr_.resetCounters(cc);
|
||||
envelopeUnit_.resetCounters(cc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,12 +265,9 @@ SYNCFUNC(Channel4)
|
|||
EVS(nextEventUnit_, &lengthCounter_, 3);
|
||||
EES(nextEventUnit_, NULL);
|
||||
|
||||
NSS(cycleCounter_);
|
||||
NSS(soMask_);
|
||||
NSS(prevOut_);
|
||||
|
||||
NSS(nr4_);
|
||||
NSS(master_);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,14 +33,15 @@ struct SaveState;
|
|||
class Channel4 {
|
||||
public:
|
||||
Channel4();
|
||||
void setNr1(unsigned data);
|
||||
void setNr2(unsigned data);
|
||||
void setNr3(unsigned data) { lfsr_.nr3Change(data, cycleCounter_); }
|
||||
void setNr4(unsigned data);
|
||||
void setSo(unsigned long soMask);
|
||||
void setNr1(unsigned data, unsigned long cc);
|
||||
void setNr2(unsigned data, unsigned long cc);
|
||||
void setNr3(unsigned data, unsigned long cc) { lfsr_.nr3Change(data, cc); }
|
||||
void setNr4(unsigned data, unsigned long cc);
|
||||
void setSo(unsigned long soMask, unsigned long cc);
|
||||
bool isActive() const { return master_; }
|
||||
void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles);
|
||||
void reset();
|
||||
void update(uint_least32_t* buf, unsigned long soBaseVol, unsigned long cc, unsigned long end);
|
||||
void reset(unsigned long cc);
|
||||
void resetCc(unsigned long cc, unsigned long newCc) { lfsr_.resetCc(cc, newCc); }
|
||||
void loadState(SaveState const &state);
|
||||
|
||||
private:
|
||||
|
@ -53,6 +54,7 @@ private:
|
|||
void nr3Change(unsigned newNr3, unsigned long cc);
|
||||
void nr4Init(unsigned long cc);
|
||||
void reset(unsigned long cc);
|
||||
void resetCc(unsigned long cc, unsigned long newCc);
|
||||
void loadState(SaveState const &state);
|
||||
void disableMaster() { killCounter(); master_ = false; reg_ = 0x7FFF; }
|
||||
void killCounter() { counter_ = counter_disabled; }
|
||||
|
@ -87,7 +89,6 @@ private:
|
|||
EnvelopeUnit envelopeUnit_;
|
||||
Lfsr lfsr_;
|
||||
SoundUnit *nextEventUnit_;
|
||||
unsigned long cycleCounter_;
|
||||
unsigned long soMask_;
|
||||
unsigned long prevOut_;
|
||||
unsigned char nr4_;
|
||||
|
|
|
@ -17,17 +17,24 @@
|
|||
//
|
||||
|
||||
#include "duty_unit.h"
|
||||
#include "psgdef.h"
|
||||
#include <algorithm>
|
||||
|
||||
static inline bool toOutState(unsigned duty, unsigned pos) {
|
||||
return 0x7EE18180 >> (duty * 8 + pos) & 1;
|
||||
namespace {
|
||||
|
||||
int const duty_pattern_len = 8;
|
||||
|
||||
bool toOutState(unsigned duty, unsigned pos) {
|
||||
return 0x7EE18180 >> (duty * duty_pattern_len + pos) & 1;
|
||||
}
|
||||
|
||||
unsigned toPeriod(unsigned freq) {
|
||||
return (2048 - freq) * 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static inline unsigned toPeriod(unsigned freq) {
|
||||
return (2048 - freq) * 2;
|
||||
}
|
||||
|
||||
namespace gambatte {
|
||||
using namespace gambatte;
|
||||
|
||||
DutyUnit::DutyUnit()
|
||||
: nextPosUpdate_(counter_disabled)
|
||||
|
@ -44,27 +51,26 @@ void DutyUnit::updatePos(unsigned long const cc) {
|
|||
if (cc >= nextPosUpdate_) {
|
||||
unsigned long const inc = (cc - nextPosUpdate_) / period_ + 1;
|
||||
nextPosUpdate_ += period_ * inc;
|
||||
pos_ += inc;
|
||||
pos_ &= 7;
|
||||
pos_ = (pos_ + inc) % duty_pattern_len;
|
||||
high_ = toOutState(duty_, pos_);
|
||||
}
|
||||
}
|
||||
|
||||
void DutyUnit::setCounter() {
|
||||
static unsigned char const nextStateDistance[4 * 8] = {
|
||||
7, 6, 5, 4, 3, 2, 1, 1,
|
||||
1, 6, 5, 4, 3, 2, 1, 2,
|
||||
1, 4, 3, 2, 1, 4, 3, 2,
|
||||
1, 6, 5, 4, 3, 2, 1, 2
|
||||
static unsigned char const nextStateDistance[][duty_pattern_len] = {
|
||||
{ 7, 6, 5, 4, 3, 2, 1, 1 },
|
||||
{ 1, 6, 5, 4, 3, 2, 1, 2 },
|
||||
{ 1, 4, 3, 2, 1, 4, 3, 2 },
|
||||
{ 1, 6, 5, 4, 3, 2, 1, 2 }
|
||||
};
|
||||
|
||||
if (enableEvents_ && nextPosUpdate_ != counter_disabled) {
|
||||
unsigned const npos = (pos_ + 1) & 7;
|
||||
unsigned const npos = (pos_ + 1) % duty_pattern_len;
|
||||
counter_ = nextPosUpdate_;
|
||||
inc_ = nextStateDistance[duty_ * 8 + npos];
|
||||
inc_ = nextStateDistance[duty_][npos];
|
||||
if (toOutState(duty_, npos) == high_) {
|
||||
counter_ += period_ * inc_;
|
||||
inc_ = nextStateDistance[duty_ * 8 + ((npos + inc_) & 7)];
|
||||
inc_ = nextStateDistance[duty_][(npos + inc_) % duty_pattern_len];
|
||||
}
|
||||
} else
|
||||
counter_ = counter_disabled;
|
||||
|
@ -77,16 +83,16 @@ void DutyUnit::setFreq(unsigned newFreq, unsigned long cc) {
|
|||
}
|
||||
|
||||
void DutyUnit::event() {
|
||||
static unsigned char const inc[] = {
|
||||
1, 7,
|
||||
2, 6,
|
||||
4, 4,
|
||||
6, 2,
|
||||
static unsigned char const inc[][2] = {
|
||||
{ 1, 7 },
|
||||
{ 2, 6 },
|
||||
{ 4, 4 },
|
||||
{ 6, 2 }
|
||||
};
|
||||
|
||||
high_ ^= true;
|
||||
counter_ += inc_ * period_;
|
||||
inc_ = inc[duty_ * 2 + high_];
|
||||
inc_ = inc[duty_][high_];
|
||||
}
|
||||
|
||||
void DutyUnit::nr1Change(unsigned newNr1, unsigned long cc) {
|
||||
|
@ -99,12 +105,11 @@ void DutyUnit::nr3Change(unsigned newNr3, unsigned long cc) {
|
|||
setFreq((freq() & 0x700) | newNr3, cc);
|
||||
}
|
||||
|
||||
void DutyUnit::nr4Change(unsigned const newNr4, unsigned long const cc,
|
||||
bool const master) {
|
||||
void DutyUnit::nr4Change(unsigned const newNr4, unsigned long const cc, unsigned long const ref, bool const master) {
|
||||
setFreq((newNr4 << 8 & 0x700) | (freq() & 0xFF), cc);
|
||||
|
||||
if (newNr4 & 0x80) {
|
||||
nextPosUpdate_ = (cc & ~1ul) + period_ + 4 - (master << 1);
|
||||
if (newNr4 & psg_nr4_init) {
|
||||
nextPosUpdate_ = cc - (cc - ref) % 2 + period_ + 4 - (master << 1);
|
||||
setCounter();
|
||||
}
|
||||
}
|
||||
|
@ -116,6 +121,15 @@ void DutyUnit::reset() {
|
|||
setCounter();
|
||||
}
|
||||
|
||||
void DutyUnit::resetCc(unsigned long cc, unsigned long newCc) {
|
||||
if (nextPosUpdate_ == counter_disabled)
|
||||
return;
|
||||
|
||||
updatePos(cc);
|
||||
nextPosUpdate_ -= cc - newCc;
|
||||
setCounter();
|
||||
}
|
||||
|
||||
void DutyUnit::loadState(SaveState::SPU::Duty const &dstate,
|
||||
unsigned const nr1, unsigned const nr4, unsigned long const cc) {
|
||||
nextPosUpdate_ = std::max(dstate.nextPosUpdate, cc);
|
||||
|
@ -127,13 +141,8 @@ void DutyUnit::loadState(SaveState::SPU::Duty const &dstate,
|
|||
setCounter();
|
||||
}
|
||||
|
||||
void DutyUnit::resetCounters(unsigned long const oldCc) {
|
||||
if (nextPosUpdate_ == counter_disabled)
|
||||
return;
|
||||
|
||||
updatePos(oldCc);
|
||||
nextPosUpdate_ -= counter_max;
|
||||
setCounter();
|
||||
void DutyUnit::resetCounters(unsigned long cc) {
|
||||
resetCc(cc, cc - counter_max);
|
||||
}
|
||||
|
||||
void DutyUnit::killCounter() {
|
||||
|
@ -158,5 +167,3 @@ SYNCFUNC(DutyUnit)
|
|||
NSS(high_);
|
||||
NSS(enableEvents_);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -34,8 +34,9 @@ public:
|
|||
bool isHighState() const { return high_; }
|
||||
void nr1Change(unsigned newNr1, unsigned long cc);
|
||||
void nr3Change(unsigned newNr3, unsigned long cc);
|
||||
void nr4Change(unsigned newNr4, unsigned long cc, bool master);
|
||||
void nr4Change(unsigned newNr4, unsigned long cc, unsigned long ref, bool master);
|
||||
void reset();
|
||||
void resetCc(unsigned long cc, unsigned long newCc);
|
||||
void loadState(SaveState::SPU::Duty const &dstate, unsigned nr1, unsigned nr4, unsigned long cc);
|
||||
void killCounter();
|
||||
void reviveCounter(unsigned long cc);
|
||||
|
|
|
@ -17,9 +17,10 @@
|
|||
//
|
||||
|
||||
#include "envelope_unit.h"
|
||||
#include "psgdef.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace gambatte {
|
||||
using namespace gambatte;
|
||||
|
||||
EnvelopeUnit::VolOnOffEvent EnvelopeUnit::nullEvent_;
|
||||
|
||||
|
@ -41,51 +42,53 @@ void EnvelopeUnit::loadState(SaveState::SPU::Env const &estate, unsigned nr2, un
|
|||
}
|
||||
|
||||
void EnvelopeUnit::event() {
|
||||
unsigned long const period = nr2_ & 7;
|
||||
unsigned long const period = nr2_ & psg_nr2_step;
|
||||
|
||||
if (period) {
|
||||
unsigned newVol = volume_;
|
||||
if (nr2_ & 8)
|
||||
if (nr2_ & psg_nr2_inc)
|
||||
++newVol;
|
||||
else
|
||||
--newVol;
|
||||
|
||||
if (newVol < 0x10U) {
|
||||
if (newVol < 0x10) {
|
||||
volume_ = newVol;
|
||||
if (volume_ < 2)
|
||||
volOnOffEvent_(counter_);
|
||||
|
||||
counter_ += period << 15;
|
||||
} else
|
||||
}
|
||||
else
|
||||
counter_ = counter_disabled;
|
||||
} else
|
||||
}
|
||||
else
|
||||
counter_ += 8ul << 15;
|
||||
}
|
||||
|
||||
bool EnvelopeUnit::nr2Change(unsigned const newNr2) {
|
||||
if (!(nr2_ & 7) && counter_ != counter_disabled)
|
||||
if (!(nr2_ & psg_nr2_step) && counter_ != counter_disabled)
|
||||
++volume_;
|
||||
else if (!(nr2_ & 8))
|
||||
else if (!(nr2_ & psg_nr2_inc))
|
||||
volume_ += 2;
|
||||
|
||||
if ((nr2_ ^ newNr2) & 8)
|
||||
if ((nr2_ ^ newNr2) & psg_nr2_inc)
|
||||
volume_ = 0x10 - volume_;
|
||||
|
||||
volume_ &= 0xF;
|
||||
nr2_ = newNr2;
|
||||
return !(newNr2 & 0xF8);
|
||||
return !(newNr2 & (psg_nr2_initvol | psg_nr2_inc));
|
||||
}
|
||||
|
||||
bool EnvelopeUnit::nr4Init(unsigned long const cc) {
|
||||
unsigned long period = nr2_ & 7 ? nr2_ & 7 : 8;
|
||||
unsigned long period = nr2_ & psg_nr2_step ? nr2_ & psg_nr2_step : 8;
|
||||
|
||||
if (((cc + 2) & 0x7000) == 0x0000)
|
||||
++period;
|
||||
|
||||
counter_ = cc - ((cc - 0x1000) & 0x7FFF) + period * 0x8000;
|
||||
|
||||
volume_ = nr2_ >> 4;
|
||||
return !(nr2_ & 0xF8);
|
||||
volume_ = nr2_ / (1u * psg_nr2_initvol & -psg_nr2_initvol);
|
||||
return !(nr2_ & (psg_nr2_initvol | psg_nr2_inc));
|
||||
}
|
||||
|
||||
SYNCFUNC(EnvelopeUnit)
|
||||
|
@ -94,5 +97,3 @@ SYNCFUNC(EnvelopeUnit)
|
|||
NSS(nr2_);
|
||||
NSS(volume_);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,9 +18,10 @@
|
|||
|
||||
#include "length_counter.h"
|
||||
#include "master_disabler.h"
|
||||
#include "psgdef.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace gambatte {
|
||||
using namespace gambatte;
|
||||
|
||||
LengthCounter::LengthCounter(MasterDisabler &disabler, unsigned const mask)
|
||||
: disableMaster_(disabler)
|
||||
|
@ -38,35 +39,30 @@ void LengthCounter::event() {
|
|||
|
||||
void LengthCounter::nr1Change(unsigned const newNr1, unsigned const nr4, unsigned long const cc) {
|
||||
lengthCounter_ = (~newNr1 & lengthMask_) + 1;
|
||||
counter_ = nr4 & 0x40
|
||||
? ((cc >> 13) + lengthCounter_) << 13
|
||||
: static_cast<unsigned long>(counter_disabled);
|
||||
counter_ = nr4 & psg_nr4_lcen
|
||||
? ((cc >> 13) + lengthCounter_) << 13
|
||||
: 1 * counter_disabled;
|
||||
}
|
||||
|
||||
void LengthCounter::nr4Change(unsigned const oldNr4, unsigned const newNr4, unsigned long const cc) {
|
||||
if (counter_ != counter_disabled)
|
||||
lengthCounter_ = (counter_ >> 13) - (cc >> 13);
|
||||
|
||||
{
|
||||
unsigned dec = 0;
|
||||
|
||||
if (newNr4 & 0x40) {
|
||||
dec = ~cc >> 12 & 1;
|
||||
|
||||
if (!(oldNr4 & 0x40) && lengthCounter_) {
|
||||
if (!(lengthCounter_ -= dec))
|
||||
disableMaster_();
|
||||
}
|
||||
unsigned dec = 0;
|
||||
if (newNr4 & psg_nr4_lcen) {
|
||||
dec = ~cc >> 12 & 1;
|
||||
if (!(oldNr4 & psg_nr4_lcen) && lengthCounter_) {
|
||||
if (!(lengthCounter_ -= dec))
|
||||
disableMaster_();
|
||||
}
|
||||
|
||||
if ((newNr4 & 0x80) && !lengthCounter_)
|
||||
lengthCounter_ = lengthMask_ + 1 - dec;
|
||||
}
|
||||
|
||||
if ((newNr4 & 0x40) && lengthCounter_)
|
||||
counter_ = ((cc >> 13) + lengthCounter_) << 13;
|
||||
else
|
||||
counter_ = counter_disabled;
|
||||
if (newNr4 & psg_nr4_init && !lengthCounter_)
|
||||
lengthCounter_ = lengthMask_ + 1 - dec;
|
||||
|
||||
counter_ = newNr4 & psg_nr4_lcen && lengthCounter_
|
||||
? ((cc >> 13) + lengthCounter_) << 13
|
||||
: 1 * counter_disabled;
|
||||
}
|
||||
|
||||
void LengthCounter::loadState(SaveState::SPU::LCounter const &lstate, unsigned long const cc) {
|
||||
|
@ -79,5 +75,3 @@ SYNCFUNC(LengthCounter)
|
|||
NSS(counter_);
|
||||
NSS(lengthCounter_);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
#ifndef PSGDEF_H
|
||||
#define PSGDEF_H
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
enum {
|
||||
psg_nr10_rsh = 0x07,
|
||||
psg_nr10_neg = 0x08,
|
||||
psg_nr10_time = 0x70
|
||||
};
|
||||
|
||||
enum {
|
||||
psg_nr2_step = 0x07,
|
||||
psg_nr2_inc = 0x08,
|
||||
psg_nr2_initvol = 0xF0
|
||||
};
|
||||
|
||||
enum {
|
||||
psg_nr43_r = 0x07,
|
||||
psg_nr43_7biten = 0x08,
|
||||
psg_nr43_s = 0xF0
|
||||
};
|
||||
|
||||
enum {
|
||||
psg_nr4_lcen = 0x40,
|
||||
psg_nr4_init = 0x80
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -19,12 +19,17 @@
|
|||
#include "tima.h"
|
||||
#include "savestate.h"
|
||||
|
||||
static unsigned char const timaClock[4] = { 10, 4, 6, 8 };
|
||||
using namespace gambatte;
|
||||
|
||||
namespace {
|
||||
unsigned char const timaClock[4] = { 10, 4, 6, 8 };
|
||||
}
|
||||
|
||||
namespace gambatte {
|
||||
|
||||
Tima::Tima()
|
||||
: lastUpdate_(0)
|
||||
: divLastUpdate_(0)
|
||||
, lastUpdate_(0)
|
||||
, tmatime_(disabled_time)
|
||||
, tima_(0)
|
||||
, tma_(0)
|
||||
|
@ -33,7 +38,7 @@ Tima::Tima()
|
|||
}
|
||||
|
||||
void Tima::loadState(SaveState const &state, TimaInterruptRequester timaIrq) {
|
||||
basetime_ = state.mem.timaBasetime;
|
||||
divLastUpdate_ = state.mem.divLastUpdate - 0x100l * state.mem.ioamhram.get()[0x104];
|
||||
lastUpdate_ = state.mem.timaLastUpdate;
|
||||
tmatime_ = state.mem.tmatime;
|
||||
tima_ = state.mem.ioamhram.get()[0x105];
|
||||
|
@ -43,8 +48,8 @@ void Tima::loadState(SaveState const &state, TimaInterruptRequester timaIrq) {
|
|||
unsigned long nextIrqEventTime = disabled_time;
|
||||
if (tac_ & 4) {
|
||||
nextIrqEventTime = tmatime_ != disabled_time && tmatime_ > state.cpu.cycleCounter
|
||||
? tmatime_
|
||||
: lastUpdate_ + ((256u - tima_) << timaClock[tac_ & 3]) + 3;
|
||||
? tmatime_
|
||||
: lastUpdate_ + ((256l - tima_) << timaClock[tac_ & 3]) + 3;
|
||||
}
|
||||
|
||||
timaIrq.setNextIrqEventTime(nextIrqEventTime);
|
||||
|
@ -82,7 +87,6 @@ void Tima::updateTima(unsigned long const cc) {
|
|||
if (tmp == 0x100) {
|
||||
tmp = 0;
|
||||
tmatime_ = lastUpdate_ + 3;
|
||||
|
||||
if (cc >= tmatime_) {
|
||||
if (cc >= tmatime_ + 4)
|
||||
tmatime_ = disabled_time;
|
||||
|
@ -102,7 +106,7 @@ void Tima::setTima(unsigned const data, unsigned long const cc, TimaInterruptReq
|
|||
if (tmatime_ - cc < 4)
|
||||
tmatime_ = disabled_time;
|
||||
|
||||
timaIrq.setNextIrqEventTime(lastUpdate_ + ((256u - data) << timaClock[tac_ & 3]) + 3);
|
||||
timaIrq.setNextIrqEventTime(lastUpdate_ + ((256l - data) << timaClock[tac_ & 3]) + 3);
|
||||
}
|
||||
|
||||
tima_ = data;
|
||||
|
@ -122,32 +126,26 @@ void Tima::setTac(unsigned const data, unsigned long const cc, TimaInterruptRequ
|
|||
unsigned long nextIrqEventTime = timaIrq.nextIrqEventTime();
|
||||
|
||||
if (tac_ & 0x04) {
|
||||
updateIrq(cc, timaIrq);
|
||||
updateTima(cc);
|
||||
|
||||
lastUpdate_ -= (1u << (timaClock[tac_ & 3] - 1)) + 3;
|
||||
tmatime_ -= (1u << (timaClock[tac_ & 3] - 1)) + 3;
|
||||
nextIrqEventTime -= (1u << (timaClock[tac_ & 3] - 1)) + 3;
|
||||
|
||||
unsigned const inc = ~(data >> 2 & (cc - divLastUpdate_) >> (timaClock[data & 3] - 1)) & 1;
|
||||
lastUpdate_ -= (inc << (timaClock[tac_ & 3] - 1)) + 3;
|
||||
nextIrqEventTime -= (inc << (timaClock[tac_ & 3] - 1)) + 3;
|
||||
if (cc >= nextIrqEventTime)
|
||||
timaIrq.flagIrq();
|
||||
|
||||
updateTima(cc);
|
||||
|
||||
tmatime_ = disabled_time;
|
||||
nextIrqEventTime = disabled_time;
|
||||
}
|
||||
|
||||
if (data & 4) {
|
||||
unsigned long diff = cc - basetime_;
|
||||
|
||||
if (agbFlag) {
|
||||
unsigned long diff = cc - divLastUpdate_;
|
||||
if (((diff >> (timaClock[tac_ & 3] - 1)) & 1) == 1 && ((diff >> (timaClock[data & 3] - 1)) & 1) == 0)
|
||||
tima_++;
|
||||
}
|
||||
|
||||
lastUpdate_ = basetime_ + ((diff >> timaClock[data & 3]) << timaClock[data & 3]);
|
||||
nextIrqEventTime = lastUpdate_ + ((256u - tima_) << timaClock[data & 3]) + 3;
|
||||
lastUpdate_ = cc - ((cc - divLastUpdate_) & ((1u << timaClock[data & 3]) - 1));
|
||||
nextIrqEventTime = lastUpdate_ + ((256l - tima_) << timaClock[data & 3]) + 3;
|
||||
}
|
||||
|
||||
timaIrq.setNextIrqEventTime(nextIrqEventTime);
|
||||
|
@ -156,12 +154,26 @@ void Tima::setTac(unsigned const data, unsigned long const cc, TimaInterruptRequ
|
|||
tac_ = data;
|
||||
}
|
||||
|
||||
void Tima::resTac(unsigned long const cc, TimaInterruptRequester timaIrq) {
|
||||
basetime_ = cc;
|
||||
|
||||
void Tima::divReset(unsigned long cc, TimaInterruptRequester timaIrq) {
|
||||
if (tac_ & 0x04) {
|
||||
setTac(tac_ & ~0x04, cc, timaIrq, false);
|
||||
setTac(tac_ | 0x04, cc, timaIrq, false);
|
||||
unsigned long nextIrqEventTime = timaIrq.nextIrqEventTime();
|
||||
lastUpdate_ -= (1u << (timaClock[tac_ & 3] - 1)) + 3;
|
||||
nextIrqEventTime -= (1u << (timaClock[tac_ & 3] - 1)) + 3;
|
||||
if (cc >= nextIrqEventTime)
|
||||
timaIrq.flagIrq();
|
||||
|
||||
updateTima(cc);
|
||||
lastUpdate_ = cc;
|
||||
timaIrq.setNextIrqEventTime(lastUpdate_ + ((256l - tima_) << timaClock[tac_ & 3]) + 3);
|
||||
}
|
||||
|
||||
divLastUpdate_ = cc;
|
||||
}
|
||||
|
||||
void Tima::speedChange(TimaInterruptRequester timaIrq) {
|
||||
if ((tac_ & 0x07) >= 0x05) {
|
||||
lastUpdate_ -= 4;
|
||||
timaIrq.setNextIrqEventTime(timaIrq.nextIrqEventTime() - 4);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,15 +185,15 @@ unsigned Tima::tima(unsigned long cc) {
|
|||
}
|
||||
|
||||
void Tima::doIrqEvent(TimaInterruptRequester timaIrq) {
|
||||
timaIrq.flagIrq();
|
||||
timaIrq.flagIrq(timaIrq.nextIrqEventTime());
|
||||
timaIrq.setNextIrqEventTime(timaIrq.nextIrqEventTime()
|
||||
+ ((256u - tma_) << timaClock[tac_ & 3]));
|
||||
+ ((256l - tma_) << timaClock[tac_ & 3]));
|
||||
}
|
||||
|
||||
SYNCFUNC(Tima)
|
||||
{
|
||||
NSS(lastUpdate_);
|
||||
NSS(basetime_);
|
||||
NSS(divLastUpdate_);
|
||||
NSS(tmatime_);
|
||||
NSS(tima_);
|
||||
NSS(tma_);
|
||||
|
|
|
@ -27,6 +27,7 @@ class TimaInterruptRequester {
|
|||
public:
|
||||
explicit TimaInterruptRequester(InterruptRequester &intreq) : intreq_(intreq) {}
|
||||
void flagIrq() const { intreq_.flagIrq(4); }
|
||||
void flagIrq(unsigned long cc) const { intreq_.flagIrq(4, cc); }
|
||||
unsigned long nextIrqEventTime() const { return intreq_.eventTime(intevent_tima); }
|
||||
void setNextIrqEventTime(unsigned long time) const { intreq_.setEventTime<intevent_tima>(time); }
|
||||
|
||||
|
@ -42,12 +43,14 @@ public:
|
|||
void setTima(unsigned tima, unsigned long cc, TimaInterruptRequester timaIrq);
|
||||
void setTma(unsigned tma, unsigned long cc, TimaInterruptRequester timaIrq);
|
||||
void setTac(unsigned tac, unsigned long cc, TimaInterruptRequester timaIrq, bool agbFlag);
|
||||
void resTac(unsigned long cc, TimaInterruptRequester timaIrq);
|
||||
void divReset(unsigned long cc, TimaInterruptRequester);
|
||||
void speedChange(TimaInterruptRequester);
|
||||
unsigned long divLastUpdate() const { return divLastUpdate_; }
|
||||
unsigned tima(unsigned long cc);
|
||||
void doIrqEvent(TimaInterruptRequester timaIrq);
|
||||
|
||||
private:
|
||||
unsigned long basetime_;
|
||||
unsigned long divLastUpdate_;
|
||||
unsigned long lastUpdate_;
|
||||
unsigned long tmatime_;
|
||||
unsigned char tima_;
|
||||
|
|
|
@ -3,18 +3,38 @@
|
|||
|
||||
namespace gambatte {
|
||||
|
||||
enum { lcdc_bgen = 0x01,
|
||||
lcdc_objen = 0x02,
|
||||
lcdc_obj2x = 0x04,
|
||||
lcdc_tdsel = 0x10,
|
||||
lcdc_we = 0x20,
|
||||
lcdc_en = 0x80 };
|
||||
enum {
|
||||
lcdc_bgen = 0x01,
|
||||
lcdc_objen = 0x02,
|
||||
lcdc_obj2x = 0x04,
|
||||
lcdc_bgtmsel = 0x08,
|
||||
lcdc_tdsel = 0x10,
|
||||
lcdc_we = 0x20,
|
||||
lcdc_wtmsel = 0x40,
|
||||
lcdc_en = 0x80
|
||||
};
|
||||
|
||||
enum { lcdstat_lycflag = 0x04,
|
||||
lcdstat_m0irqen = 0x08,
|
||||
lcdstat_m1irqen = 0x10,
|
||||
lcdstat_m2irqen = 0x20,
|
||||
lcdstat_lycirqen = 0x40 };
|
||||
enum {
|
||||
lcdstat_lycflag = 0x04,
|
||||
lcdstat_m0irqen = 0x08,
|
||||
lcdstat_m1irqen = 0x10,
|
||||
lcdstat_m2irqen = 0x20,
|
||||
lcdstat_lycirqen = 0x40
|
||||
};
|
||||
|
||||
enum {
|
||||
lcd_hres = 160,
|
||||
lcd_vres = 144,
|
||||
lcd_lines_per_frame = 154,
|
||||
lcd_max_num_sprites_per_line = 10,
|
||||
lcd_num_oam_entries = 40,
|
||||
lcd_cycles_per_line = 456,
|
||||
lcd_force_signed_enum1 = -1
|
||||
};
|
||||
enum {
|
||||
lcd_cycles_per_frame = 1l * lcd_lines_per_frame * lcd_cycles_per_line,
|
||||
lcd_force_signed_enum2 = -1
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue