mirror of
https://github.com/ares-emulator/ares.git
synced 2026-11-09 09:19:42 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aafd85789 | ||
|
|
67b0bf7aac | ||
|
|
11d887c99c | ||
|
|
7c03fb208f | ||
|
|
b33edf5f90 | ||
|
|
83369fd40f | ||
|
|
6ee42f25b4 | ||
|
|
fe388a7fcc | ||
|
|
c7d667fa07 | ||
|
|
cc0e405780 | ||
|
|
c21adc7b57 | ||
|
|
232f85e39c | ||
|
|
c951652efb | ||
|
|
f605d52032 | ||
|
|
bdd585a563 | ||
|
|
54fc194182 | ||
|
|
4c39ca23eb | ||
|
|
8ea87c75e4 | ||
|
|
7f0f91b3da | ||
|
|
164f5648bf | ||
|
|
19cce7dbba | ||
|
|
765c0d4181 | ||
|
|
fbc8f22821 | ||
|
|
14f4fc0173 | ||
|
|
6920622b7e | ||
|
|
c42bb2626d | ||
|
|
d45be3297f | ||
|
|
54adf82264 | ||
|
|
7d1bbf7c04 | ||
|
|
d17ae2f36d | ||
|
|
b9f8347d57 | ||
|
|
41af52ec27 | ||
|
|
87daf87fbb | ||
|
|
2a5b9fe74e | ||
|
|
bb91ff1136 | ||
|
|
5d7496c6f1 | ||
|
|
18692c3ced | ||
|
|
bc3af76716 | ||
|
|
03dd55bbda | ||
|
|
ffab36ed99 | ||
|
|
8ad8803765 | ||
|
|
3198e0e88e | ||
|
|
999d858fa7 | ||
|
|
a752515d2d | ||
|
|
2460b01b27 | ||
|
|
2af310528b | ||
|
|
a1be16873f | ||
|
|
8b4f1fa16e | ||
|
|
882fc4df1f | ||
|
|
997077527b | ||
|
|
dd834d2c5d | ||
|
|
50b9786587 | ||
|
|
fbb6e72e02 | ||
|
|
c152ef5639 | ||
|
|
49af8ab6a2 | ||
|
|
68134837d4 | ||
|
|
bb75c2f058 | ||
|
|
105e8da924 | ||
|
|
b0de6c11d2 | ||
|
|
b89a0bcce1 | ||
|
|
04f8b9cb4d | ||
|
|
c139a4d4ad | ||
|
|
28f05b0297 | ||
|
|
39b5d798b9 | ||
|
|
7e5841d84a | ||
|
|
8c3961cd6e | ||
|
|
e6517f5f57 | ||
|
|
cd604bf619 | ||
|
|
85008a663e | ||
|
|
8031dcd3f2 | ||
|
|
f0b8a36b6a | ||
|
|
36aed53124 | ||
|
|
1d8c89a50f | ||
|
|
60007e8853 | ||
|
|
3542371b76 | ||
|
|
a8882a1d89 | ||
|
|
86fa83fc2b | ||
|
|
71ddda52b5 | ||
|
|
be0504e1c2 | ||
|
|
a34a6ab1df | ||
|
|
515cb06729 | ||
|
|
2d852d6a9b | ||
|
|
5b8abb5717 | ||
|
|
496409c199 | ||
|
|
9e6be60b9e | ||
|
|
fdbd036bd1 | ||
|
|
f241c4095d | ||
|
|
094454e52f | ||
|
|
dabeb68387 | ||
|
|
cc82697f00 | ||
|
|
e3f4d6dc0d | ||
|
|
b698206d07 | ||
|
|
7451ccbc98 | ||
|
|
6448413adf | ||
|
|
d91f838e3b | ||
|
|
fc820aba6b | ||
|
|
a61dba8ec8 | ||
|
|
025cde8961 | ||
|
|
2f8659a375 | ||
|
|
f1eff7d2f0 | ||
|
|
0d3caa546c | ||
|
|
e7444fd852 | ||
|
|
55ac593102 | ||
|
|
4ed8a7792c | ||
|
|
161f01fa0d | ||
|
|
606151f79d | ||
|
|
b00b1778d1 | ||
|
|
dc4af1ddad | ||
|
|
6b88fdfbbf | ||
|
|
edf712f2f6 | ||
|
|
70f1cf07c5 | ||
|
|
6d6c5d5fad | ||
|
|
3a44a460bd | ||
|
|
8655d4027f | ||
|
|
271e545a49 | ||
|
|
6b97edc9db | ||
|
|
e4f4a7d657 | ||
|
|
861020eeb7 | ||
|
|
0dc20693e2 | ||
|
|
08746292c7 | ||
|
|
e2636505e2 | ||
|
|
2219f48fca | ||
|
|
91c64bf350 | ||
|
|
6f6786e04f | ||
|
|
7974cabb43 | ||
|
|
a8ae8fe83f | ||
|
|
e7cdf9c046 | ||
|
|
93c397dca7 | ||
|
|
c9f2773685 | ||
|
|
652d653736 | ||
|
|
219dff38ad | ||
|
|
c5ca614492 | ||
|
|
0fca58b98e | ||
|
|
5d04cd3808 | ||
|
|
59a0a898e9 | ||
|
|
a6499c3b49 | ||
|
|
310b665160 | ||
|
|
b96b8ac7da | ||
|
|
d20a0e1b8e | ||
|
|
716d417359 | ||
|
|
1b6893ea49 | ||
|
|
fef2ab4904 | ||
|
|
cbf9f79e5f | ||
|
|
4c4c5460bc | ||
|
|
26d5ac3a80 | ||
|
|
f71568bad4 | ||
|
|
a4ac042f27 | ||
|
|
25e248a6c3 | ||
|
|
f43063803b | ||
|
|
9e20c21b35 | ||
|
|
44ab78ade5 | ||
|
|
c88d2eba79 | ||
|
|
b1b51d3070 | ||
|
|
dc8ac7d3dc | ||
|
|
3feb849778 | ||
|
|
bc1009a5f1 | ||
|
|
1f06bbc156 | ||
|
|
09bb1cbf3e | ||
|
|
b66752b4fc | ||
|
|
97b9490845 | ||
|
|
6e7cf80f6e | ||
|
|
d250931aae | ||
|
|
7163fb4dbb | ||
|
|
838f4c6ad2 | ||
|
|
c5e5c38a8a | ||
|
|
9f98c24394 | ||
|
|
86765b1706 | ||
|
|
2585b4667f | ||
|
|
bc4332bbb9 | ||
|
|
a3d75ad5da | ||
|
|
101ba34f28 | ||
|
|
ece2001360 | ||
|
|
3e913fda53 | ||
|
|
ec29392c11 | ||
|
|
536fe6964f | ||
|
|
18be35d9e0 | ||
|
|
c6596bc468 | ||
|
|
00bdc72562 | ||
|
|
0394fd90ad | ||
|
|
f27d62f841 | ||
|
|
874d32eb42 | ||
|
|
8e10f41188 | ||
|
|
ad98f53f3b | ||
|
|
6e0fa73249 | ||
|
|
722f104fa0 | ||
|
|
d55f80d633 | ||
|
|
649a29debe | ||
|
|
dedc446418 | ||
|
|
16c5da5dc5 | ||
|
|
a0f762985e | ||
|
|
f76dd51134 | ||
|
|
103dd93ae4 | ||
|
|
5f216a7bf5 | ||
|
|
dfa6ec0c79 | ||
|
|
e011cbecbb | ||
|
|
c2b349aeb8 | ||
|
|
6f0f7be867 | ||
|
|
bc86249658 | ||
|
|
0ac4d8220c | ||
|
|
1ce484be52 | ||
|
|
899ac2a2ed | ||
|
|
ca6974b66f | ||
|
|
3d966bda8f | ||
|
|
b11ee3fbdb | ||
|
|
3f8b0cae72 | ||
|
|
8261563a10 | ||
|
|
b15ce04e5d | ||
|
|
7a515cd489 | ||
|
|
5b7dafe70a | ||
|
|
eaa47fbf9c | ||
|
|
bbc0c1db3b | ||
|
|
b35a639e33 | ||
|
|
48b8c8a3ff | ||
|
|
5a80359443 | ||
|
|
67163a9fbe | ||
|
|
0d1c90c616 | ||
|
|
3678a4cc07 | ||
|
|
c0a0f34439 | ||
|
|
640ea5e3fa | ||
|
|
cb40c6487b | ||
|
|
1570af44e2 | ||
|
|
d00c9feeac | ||
|
|
b308958bf0 | ||
|
|
9125d0c79f | ||
|
|
ee80782345 | ||
|
|
b5fd6611f2 | ||
|
|
12558c7ef3 | ||
|
|
99733cda7c | ||
|
|
1482689f09 | ||
|
|
a1d3fc19a2 | ||
|
|
103770a995 | ||
|
|
38a2e62603 | ||
|
|
ae41a43567 | ||
|
|
08a7253e99 | ||
|
|
ba6069686f | ||
|
|
6bb521a207 | ||
|
|
fac53a08ca | ||
|
|
d67a92b2b4 | ||
|
|
ffee6d658d | ||
|
|
58f3d7badf | ||
|
|
388e98fba9 | ||
|
|
f883420714 | ||
|
|
d99f7598e1 | ||
|
|
64bfe58e03 | ||
|
|
d05f7b64b4 | ||
|
|
bc036dcaf6 | ||
|
|
64cebdad77 | ||
|
|
e594251208 | ||
|
|
cea26bd150 | ||
|
|
27905a8fc7 | ||
|
|
2f2704b2b6 | ||
|
|
5afa255ab8 | ||
|
|
daa781706a | ||
|
|
88bd798427 | ||
|
|
d77c8b1c56 | ||
|
|
e07f18c172 | ||
|
|
6bc13e8d66 | ||
|
|
25482bc539 | ||
|
|
847e2a24cf | ||
|
|
514933d459 | ||
|
|
04e4f0ffa0 | ||
|
|
109c669056 | ||
|
|
fb68c74143 | ||
|
|
acdf31b40a | ||
|
|
48c611c90b | ||
|
|
f338f50b4d | ||
|
|
f0b119d90a | ||
|
|
e8c268026c | ||
|
|
9387eab814 | ||
|
|
d769abafd9 | ||
|
|
742ab90d21 | ||
|
|
2a346af4e2 | ||
|
|
df5ebc9245 | ||
|
|
71f17d9a2a | ||
|
|
4e7ab5d3e3 | ||
|
|
e5ffe61a07 | ||
|
|
e601bccdfb | ||
|
|
155c9b4154 | ||
|
|
7b0e29baa9 | ||
|
|
0152831319 | ||
|
|
17ce1bbe0f | ||
|
|
db953dbc62 | ||
|
|
65ed77ecd7 | ||
|
|
bdf4dc9ce1 | ||
|
|
c01f3b885e | ||
|
|
538078f6d0 | ||
|
|
2a4be17f56 | ||
|
|
9d647291fa | ||
|
|
595de2e2aa | ||
|
|
778e1f0b76 | ||
|
|
b5910c75f5 | ||
|
|
23cd8772f5 | ||
|
|
202db872de | ||
|
|
934e9d36c3 | ||
|
|
30066aa336 | ||
|
|
cc4db18707 | ||
|
|
30a3b125c8 | ||
|
|
084d8fb72b | ||
|
|
463d44920b | ||
|
|
adfc78f952 |
No files matched your search
@@ -105,12 +105,12 @@ jobs:
|
||||
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
|
||||
- name: "Build: FreeBSD"
|
||||
if: matrix.platform.name == 'freebsd'
|
||||
uses: cross-platform-actions/action@e8a7b572196ff79ded1979dc2bb9ee67d1ddb252
|
||||
uses: cross-platform-actions/action@492b0c80085400348c599edace11141a4ee73524
|
||||
env:
|
||||
TARGET_PRESET: ${{ matrix.platform.target-cmake-preset }}
|
||||
with:
|
||||
operating_system: freebsd
|
||||
version: 14.3
|
||||
version: '15.0'
|
||||
environment_variables: TARGET_PRESET
|
||||
run: |
|
||||
sudo pkg install -y cmake pkgconf ccache ninja gtk3 libX11 libXext libXrandr libXrender libglvnd alsa-lib libao libudev-devd librashader openal-soft pulseaudio sdl3 vulkan-loader
|
||||
|
||||
@@ -41,6 +41,9 @@ desktop-ui/resource/resource.hpp
|
||||
mia/resource/resource.cpp
|
||||
mia/resource/resource.hpp
|
||||
|
||||
# Exclude Qt generated MOC file
|
||||
hiro/qt/qt.moc
|
||||
|
||||
# Exclude fetched third-party processor test binaries
|
||||
tests/arm7tdmi/tests
|
||||
tests/m68000/tests
|
||||
|
||||
@@ -38,12 +38,18 @@ When started from the command-line, ares accepts a few options.
|
||||
Usage: ./ares [options] game(s)
|
||||
|
||||
--help Displays available options and exit
|
||||
--version Displays the version string of the application
|
||||
--terminal Create new terminal window (Windows only)
|
||||
--fullscreen Start in full screen mode
|
||||
--pseudofullscreen Start in pseudo full screen mode
|
||||
--kiosk Start in minimal UI mode (implies --no-file-prompt)
|
||||
--system system Specify the system name
|
||||
--shader shader Specify a slang shader to load (requires OpenGL or Metal)
|
||||
--setting name=value Specify a value for a setting
|
||||
--dump-all-settings Show a list of all existing settings and exit
|
||||
--no-file-prompt Do not prompt to load (optional) additional roms (eg: 64DD)
|
||||
--settings-file path Specify a settings file override (settings.bml)
|
||||
--save-state slot Specify a save state slot to load (1-9)
|
||||
```
|
||||
|
||||
The --system option is useful when the system type cannot be auto-detected.
|
||||
|
||||
@@ -99,6 +99,18 @@ target_sources(
|
||||
ares/node/video/video.hpp
|
||||
)
|
||||
|
||||
target_sources(
|
||||
ares
|
||||
PRIVATE
|
||||
ares/node/debugger/debugger.hpp
|
||||
ares/node/debugger/graphics.hpp
|
||||
ares/node/debugger/memory.hpp
|
||||
ares/node/debugger/properties.hpp
|
||||
ares/node/debugger/tracer/tracer.hpp
|
||||
ares/node/debugger/tracer/instruction.hpp
|
||||
ares/node/debugger/tracer/notification.hpp
|
||||
)
|
||||
|
||||
if(OS_WINDOWS)
|
||||
include(cmake/os-windows.cmake)
|
||||
elseif(OS_MACOS)
|
||||
|
||||
@@ -19,6 +19,12 @@ auto CPU::unload() -> void {
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
if(io.resetPending) {
|
||||
debugger.interrupt("Reset");
|
||||
reset();
|
||||
io.resetPending = 0;
|
||||
}
|
||||
|
||||
debugger.instruction();
|
||||
instruction();
|
||||
}
|
||||
@@ -31,10 +37,11 @@ auto CPU::step(u32 clocks) -> void {
|
||||
|
||||
auto CPU::power(bool reset) -> void {
|
||||
MOS6502::BCD = 1;
|
||||
MOS6502::power();
|
||||
if(!reset) MOS6502::power();
|
||||
Thread::create(system.frequency() / 3, std::bind_front(&CPU::main, this));
|
||||
|
||||
io = {};
|
||||
io.resetPending = 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,9 +5,11 @@ struct CPU : MOS6502, Thread {
|
||||
//debugger.cpp
|
||||
auto load(Node::Object) -> void;
|
||||
auto instruction() -> void;
|
||||
auto interrupt(string_view) -> void;
|
||||
|
||||
struct Tracer {
|
||||
Node::Debugger::Tracer::Instruction instruction;
|
||||
Node::Debugger::Tracer::Notification interrupt;
|
||||
} tracer;
|
||||
} debugger;
|
||||
|
||||
@@ -39,6 +41,7 @@ struct CPU : MOS6502, Thread {
|
||||
|
||||
//protected:
|
||||
struct IO {
|
||||
n1 resetPending = 0;
|
||||
n1 rdyLine = 1;
|
||||
n32 scanlineCycles = 0;
|
||||
n8 openBus = 0;
|
||||
|
||||
@@ -9,4 +9,8 @@ auto CPU::Debugger::instruction() -> void {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
auto CPU::Debugger::interrupt(string_view type) -> void {
|
||||
if(tracer.interrupt->enabled()) {
|
||||
tracer.interrupt->notify(type);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
auto CPU::serialize(serializer& s) -> void {
|
||||
MOS6502::serialize(s);
|
||||
Thread::serialize(s);
|
||||
s(io.resetPending);
|
||||
s(io.rdyLine);
|
||||
s(io.openBus);
|
||||
}
|
||||
+62
-43
@@ -7,14 +7,24 @@ auto RIOT::writeRam(n8 address, n8 data) -> void {
|
||||
}
|
||||
|
||||
auto RIOT::readIo(n8 address) -> n8 {
|
||||
switch(address.bit(0, 2)) {
|
||||
address &= 0x1f;
|
||||
|
||||
switch(address) {
|
||||
case 0x00: return readPortA();
|
||||
case 0x01: return port[0].direction;
|
||||
case 0x02: return readPortB();
|
||||
case 0x03: return port[1].direction;
|
||||
case 0x04: timer.interruptFlag = address.bit(3); return timer.counter;
|
||||
case 0x04: case 0x06: {
|
||||
n8 data = timer.counter;
|
||||
|
||||
if(timer.interruptFlag && !timer.justWrapped) {
|
||||
timer.interruptFlag = 0;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
case 0x05: case 0x07: {
|
||||
n8 output = 0;
|
||||
n8 output;
|
||||
output.bit(6) = 0; //TODO: PA7 IRQ Flag
|
||||
output.bit(7) = timer.interruptFlag;
|
||||
return output;
|
||||
@@ -26,31 +36,38 @@ auto RIOT::readIo(n8 address) -> n8 {
|
||||
}
|
||||
|
||||
auto RIOT::writeIo(n8 address, n8 data) -> void {
|
||||
address &= 0x1f;
|
||||
|
||||
switch(address) {
|
||||
case 0x00: writePortA(data); return;
|
||||
case 0x01: port[0].direction = data; return;
|
||||
case 0x02: writePortB(data); return;
|
||||
case 0x03: port[1].direction = data; return;
|
||||
case 0x14: case 0x15: case 0x16: case 0x17:
|
||||
case 0x1c: case 0x1d: case 0x1e: case 0x1f: {
|
||||
const n16 intervals[] = {1, 8, 64, 1024};
|
||||
timer.counter = data;
|
||||
timer.interval = intervals[address.bit(0, 1)];
|
||||
timer.reload = timer.interval;
|
||||
timer.interruptFlag = 0;
|
||||
timer.interruptEnable = address.bit(3);
|
||||
return; }
|
||||
case 0x14: reloadTimer(data, 1, 0); return;
|
||||
case 0x15: reloadTimer(data, 8, 0); return;
|
||||
case 0x16: reloadTimer(data, 64, 0); return;
|
||||
case 0x17: reloadTimer(data, 1024, 0); return;
|
||||
case 0x1c: reloadTimer(data, 1, 1); return;
|
||||
case 0x1d: reloadTimer(data, 8, 1); return;
|
||||
case 0x1e: reloadTimer(data, 64, 1); return;
|
||||
case 0x1f: reloadTimer(data, 1024, 1); return;
|
||||
}
|
||||
|
||||
debug(unimplemented, "[RIOT] IO write ",hex(address), " = ", hex(data));
|
||||
debug(unimplemented, "[RIOT] IO write ", hex(address), " = ", hex(data));
|
||||
}
|
||||
|
||||
auto RIOT::readPortA() -> n8 {
|
||||
n8 data;
|
||||
// TODO: Handle when configured as outputs
|
||||
data.bit(0, 3) = controllerPort2.read().bit(0, 4);
|
||||
data.bit(4, 7) = controllerPort1.read().bit(0, 4);
|
||||
return data;
|
||||
n8 output = 0xff;
|
||||
|
||||
n8 input;
|
||||
input.bit(0, 3) = controllerPort2.read().bit(0, 4);
|
||||
input.bit(4, 7) = controllerPort1.read().bit(0, 4);
|
||||
|
||||
for(auto n : range(8)) {
|
||||
output.bit(n) = port[0].direction.bit(n)? port[0].data.bit(n) : input.bit(n);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
auto RIOT::writePortA(n8 data) -> void {
|
||||
@@ -58,39 +75,41 @@ auto RIOT::writePortA(n8 data) -> void {
|
||||
}
|
||||
|
||||
auto RIOT::readPortB() -> n8 {
|
||||
system.controls.poll();
|
||||
system.controls.poll();
|
||||
|
||||
n8 data;
|
||||
n8 data = 0xff;
|
||||
|
||||
for(auto n : range(8)) if(port[1].direction.bit(n)) { data.bit(n) = port[1].data.bit(n); }
|
||||
for(auto n : range(8)) {
|
||||
if(port[1].direction.bit(n)) data.bit(n) = port[1].data.bit(n);
|
||||
}
|
||||
|
||||
// Atari hard-wires these bits as input
|
||||
data.bit(0) = !system.controls.reset->value();
|
||||
data.bit(1) = !system.controls.select->value();
|
||||
// Atari hard-wires these bits as input
|
||||
data.bit(0) = !system.controls.reset->value();
|
||||
data.bit(1) = !system.controls.select->value();
|
||||
|
||||
// These are toggle switches; flip the values on the rising edge
|
||||
if(system.controls.tvType->value() && !tvTypeLatch) {
|
||||
tvType = !tvType;
|
||||
}
|
||||
tvTypeLatch = system.controls.tvType->value();
|
||||
// These are toggle switches; flip the values on the rising edge
|
||||
if(system.controls.tvType->value() && !tvTypeLatch) {
|
||||
tvType = !tvType;
|
||||
}
|
||||
tvTypeLatch = system.controls.tvType->value();
|
||||
|
||||
if(system.controls.leftDifficulty->value() && !leftDifficultyLatch) {
|
||||
leftDifficulty = !leftDifficulty;
|
||||
}
|
||||
leftDifficultyLatch = system.controls.leftDifficulty->value();
|
||||
if(system.controls.leftDifficulty->value() && !leftDifficultyLatch) {
|
||||
leftDifficulty = !leftDifficulty;
|
||||
}
|
||||
leftDifficultyLatch = system.controls.leftDifficulty->value();
|
||||
|
||||
if(system.controls.rightDifficulty->value() && !rightDifficultyLatch) {
|
||||
rightDifficulty = !rightDifficulty;
|
||||
}
|
||||
rightDifficultyLatch = system.controls.rightDifficulty->value();
|
||||
if(system.controls.rightDifficulty->value() && !rightDifficultyLatch) {
|
||||
rightDifficulty = !rightDifficulty;
|
||||
}
|
||||
rightDifficultyLatch = system.controls.rightDifficulty->value();
|
||||
|
||||
data.bit(3) = tvType;
|
||||
data.bit(6) = leftDifficulty;
|
||||
data.bit(7) = rightDifficulty;
|
||||
data.bit(3) = tvType;
|
||||
data.bit(6) = leftDifficulty;
|
||||
data.bit(7) = rightDifficulty;
|
||||
|
||||
return data;
|
||||
return data;
|
||||
}
|
||||
|
||||
auto RIOT::writePortB(n8 data) -> void {
|
||||
port[1].data = data;
|
||||
}
|
||||
port[1].data = data;
|
||||
}
|
||||
+49
-5
@@ -9,7 +9,6 @@ RIOT riot;
|
||||
|
||||
auto RIOT::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("RIOT");
|
||||
|
||||
ram.allocate(128);
|
||||
}
|
||||
|
||||
@@ -18,13 +17,52 @@ auto RIOT::unload() -> void {
|
||||
node.reset();
|
||||
}
|
||||
|
||||
auto RIOT::main() -> void {
|
||||
if(--timer.counter == 0xff) {
|
||||
auto RIOT::reloadTimer(n8 data, n16 interval, n1 interruptEnable) -> void {
|
||||
timer.counter = data;
|
||||
timer.interval = interval;
|
||||
timer.prescaler = interval;
|
||||
timer.interruptEnable = interruptEnable;
|
||||
timer.interruptFlag = 0;
|
||||
timer.underflow = 0;
|
||||
timer.holdZero = 0;
|
||||
timer.justWrapped = 0;
|
||||
}
|
||||
|
||||
auto RIOT::clockTimer() -> void {
|
||||
timer.justWrapped = 0;
|
||||
|
||||
if(timer.prescaler == 0) timer.prescaler = 1;
|
||||
if(--timer.prescaler) return;
|
||||
|
||||
timer.prescaler = timer.interval;
|
||||
|
||||
if(timer.holdZero) {
|
||||
timer.holdZero = 0;
|
||||
timer.underflow = 1;
|
||||
timer.counter = 0xff;
|
||||
timer.interval = 1;
|
||||
timer.prescaler = 1;
|
||||
timer.interruptFlag = 1;
|
||||
timer.justWrapped = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
step(timer.interval);
|
||||
if(timer.counter == 0x00) {
|
||||
timer.holdZero = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
timer.counter--;
|
||||
|
||||
if(timer.underflow) {
|
||||
timer.interval = 1;
|
||||
timer.prescaler = 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto RIOT::main() -> void {
|
||||
clockTimer();
|
||||
step(1);
|
||||
}
|
||||
|
||||
auto RIOT::step(u32 clocks) -> void {
|
||||
@@ -35,13 +73,19 @@ auto RIOT::step(u32 clocks) -> void {
|
||||
auto RIOT::power(bool reset) -> void {
|
||||
Thread::create(system.frequency() / 3, std::bind_front(&RIOT::main, this));
|
||||
timer = {};
|
||||
timer.interval = 1;
|
||||
timer.prescaler = 1;
|
||||
timer.counter = 0x00;
|
||||
|
||||
port[0] = {};
|
||||
port[0].data = 0xff;
|
||||
port[1] = {};
|
||||
port[1].data = 0xff;
|
||||
leftDifficulty = 1;
|
||||
leftDifficultyLatch = 0;
|
||||
rightDifficulty = 1;
|
||||
rightDifficultyLatch = 0;
|
||||
tvType = 1;
|
||||
tvTypeLatch = 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,7 +10,8 @@ struct RIOT : Thread {
|
||||
auto step(u32 clocks) -> void;
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
auto decrementTimer() -> void;
|
||||
auto clockTimer() -> void;
|
||||
auto reloadTimer(n8 data, n16 interval, n1 interruptEnable) -> void;
|
||||
|
||||
//io.cpp
|
||||
auto readRam(n8 address) -> n8;
|
||||
@@ -27,11 +28,14 @@ struct RIOT : Thread {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct {
|
||||
u8 counter;
|
||||
n8 counter;
|
||||
n16 interval;
|
||||
n16 reload;
|
||||
n1 interruptEnable;
|
||||
n1 interruptFlag;
|
||||
n16 prescaler;
|
||||
n1 interruptEnable;
|
||||
n1 interruptFlag;
|
||||
n1 underflow;
|
||||
n1 holdZero;
|
||||
n1 justWrapped;
|
||||
} timer;
|
||||
|
||||
struct Port {
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
//serialization.cpp
|
||||
auto RIOT::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
|
||||
s(ram);
|
||||
|
||||
s(timer.counter);
|
||||
s(timer.interval);
|
||||
s(timer.reload);
|
||||
s(timer.prescaler);
|
||||
s(timer.interruptEnable);
|
||||
s(timer.interruptFlag);
|
||||
s(timer.underflow);
|
||||
s(timer.holdZero);
|
||||
s(timer.justWrapped);
|
||||
|
||||
for(auto n : range(2)) {
|
||||
s(port[n].data);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
static const string SerializerVersion = "v139";
|
||||
static const string SerializerVersion = "v140";
|
||||
|
||||
auto System::serialize(bool synchronize) -> serializer {
|
||||
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
|
||||
|
||||
@@ -28,6 +28,15 @@ struct Instruction : Tracer {
|
||||
for(auto& history : _history) history = ~0ull;
|
||||
}
|
||||
|
||||
auto setEnabled(bool enabled) -> void {
|
||||
Tracer::setTerminal(enabled);
|
||||
if(!enabled) {
|
||||
_omitted = 0;
|
||||
setMask(_mask);
|
||||
setDepth(_depth);
|
||||
}
|
||||
}
|
||||
|
||||
auto address(u64 address) -> bool {
|
||||
address &= ~0ull >> (64 - _addressBits); //mask upper bits of address
|
||||
_address = address;
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace ares::Core {
|
||||
struct System;
|
||||
struct Peripheral;
|
||||
struct Port;
|
||||
struct Tape;
|
||||
namespace Component {
|
||||
struct Component;
|
||||
struct RealTimeClock;
|
||||
@@ -49,6 +50,7 @@ namespace ares::Node {
|
||||
using System = std::shared_ptr<Core::System>;
|
||||
using Peripheral = std::shared_ptr<Core::Peripheral>;
|
||||
using Port = std::shared_ptr<Core::Port>;
|
||||
using Tape = std::shared_ptr<Core::Tape>;
|
||||
namespace Component {
|
||||
using Component = std::shared_ptr<Core::Component::Component>;
|
||||
using RealTimeClock = std::shared_ptr<Core::Component::RealTimeClock>;
|
||||
@@ -102,6 +104,7 @@ namespace ares::Core {
|
||||
#include <ares/node/system.hpp>
|
||||
#include <ares/node/peripheral.hpp>
|
||||
#include <ares/node/port.hpp>
|
||||
#include <ares/node/tape.hpp>
|
||||
namespace Component {
|
||||
#include <ares/node/component/component.hpp>
|
||||
#include <ares/node/component/real-time-clock.hpp>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
struct Tape : Peripheral {
|
||||
DeclareClass(Tape, "tape") using Peripheral::Peripheral;
|
||||
|
||||
~Tape() {
|
||||
unload();
|
||||
}
|
||||
|
||||
auto play() -> void { _playing = true; }
|
||||
auto record() -> void { _recording = true; }
|
||||
auto stop() -> void {
|
||||
_playing = false;
|
||||
_recording = false;
|
||||
}
|
||||
|
||||
auto playing() const -> bool { return _playing; }
|
||||
auto recording() const -> bool { return _recording; }
|
||||
|
||||
auto loaded() const -> bool { return _loaded; }
|
||||
auto load() -> bool {
|
||||
if (!_loaded && _load && _load()) {
|
||||
_loaded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return _loaded;
|
||||
}
|
||||
auto unload() -> void {
|
||||
if (_loaded) {
|
||||
stop();
|
||||
if (_unload) _unload();
|
||||
_loaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
auto setLoad(std::function<bool()> load) -> void { _load = load; }
|
||||
auto setUnload(std::function<void()> unload) -> void { _unload = unload; }
|
||||
|
||||
auto setSupportPlay(bool support) -> void { _supportPlay = support; }
|
||||
auto setSupportRecord(bool support) -> void { _supportRecord = support; }
|
||||
|
||||
auto supportPlay() const -> bool { return _supportPlay; }
|
||||
auto supportRecord() const -> bool { return _supportRecord; }
|
||||
|
||||
auto length() const -> u64 { return _length; }
|
||||
auto setLength(u64 length) -> void { _length = length; }
|
||||
|
||||
auto position() const -> u64 { return _position; }
|
||||
auto setPosition(u64 position) -> void { _position = position; }
|
||||
|
||||
auto frequency() const -> u64 { return _frequency; }
|
||||
auto setFrequency(u64 frequency) -> void { _frequency = frequency; }
|
||||
|
||||
auto serialize(string &output, string depth) -> void override {
|
||||
Peripheral::serialize(output, depth);
|
||||
output.append(depth, " supportPlay: ", _supportPlay, "\n");
|
||||
output.append(depth, " supportRecord: ", _supportRecord, "\n");
|
||||
output.append(depth, " playing: ", _playing, "\n");
|
||||
output.append(depth, " recording: ", _recording, "\n");
|
||||
}
|
||||
auto unserialize(Markup::Node node) -> void override {
|
||||
Peripheral::unserialize(node);
|
||||
_supportPlay = node["supportPlay"].boolean();
|
||||
_supportRecord = node["supportRecord"].boolean();
|
||||
_playing = node["playing"].boolean();
|
||||
_recording = node["recording"].boolean();
|
||||
}
|
||||
|
||||
private:
|
||||
bool _supportPlay = false;
|
||||
bool _supportRecord = false;
|
||||
bool _playing = false;
|
||||
bool _recording = false;
|
||||
bool _loaded = false;
|
||||
u64 _length = 0;
|
||||
u64 _position = 0;
|
||||
u64 _frequency = 1;
|
||||
std::function<bool()> _load;
|
||||
std::function<void()> _unload;
|
||||
};
|
||||
@@ -8,6 +8,9 @@ enum class Event : u32 {
|
||||
Frame,
|
||||
Power,
|
||||
Synchronize,
|
||||
FastForwardOn,
|
||||
FastForwardOff,
|
||||
Shutdown
|
||||
};
|
||||
|
||||
struct Platform {
|
||||
|
||||
@@ -25,12 +25,9 @@ auto MOS6502::reset() -> void {
|
||||
I = 1;
|
||||
PCL = read(vector++);
|
||||
L PCH = read(vector++);
|
||||
resetting = 0;
|
||||
}
|
||||
|
||||
auto MOS6502::instruction() -> void {
|
||||
if(resetting) reset();
|
||||
|
||||
switch(opcode()) {
|
||||
op(0x00, None, addr(Implied), fp(BRK))
|
||||
op(0x01, Load, addr(IndirectX), fp(ORA))
|
||||
|
||||
@@ -35,19 +35,13 @@ namespace ares {
|
||||
#undef N
|
||||
#undef L
|
||||
|
||||
auto MOS6502::power(bool reset) -> void {
|
||||
if(reset) {
|
||||
resetting = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
auto MOS6502::power() -> void {
|
||||
A = 0x00;
|
||||
X = 0x00;
|
||||
Y = 0x00;
|
||||
S = 0x00;
|
||||
P = 0x04;
|
||||
MDR = 0x00;
|
||||
resetting = 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,7 @@ struct MOS6502 {
|
||||
virtual auto readDebugger(n16 addr) -> n8 { return 0; }
|
||||
|
||||
//mos6502.cpp
|
||||
auto power(bool reset = false) -> void;
|
||||
auto power() -> void;
|
||||
|
||||
//memory.cpp
|
||||
auto idle() -> void;
|
||||
@@ -189,7 +189,6 @@ struct MOS6502 {
|
||||
n16 PC;
|
||||
n16 MAR;
|
||||
n8 MDR;
|
||||
n1 resetting = 0;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -13,5 +13,4 @@ auto MOS6502::serialize(serializer& s) -> void {
|
||||
s(PC);
|
||||
s(MAR);
|
||||
s(MDR);
|
||||
s(resetting);
|
||||
}
|
||||
@@ -115,8 +115,10 @@ ares_add_sources(
|
||||
cartridge/board/taito-x1-017.cpp
|
||||
cartridge/board/tengen-80003x.cpp
|
||||
cartridge/board/unl-bmc.cpp
|
||||
cartridge/board/unl-jy.cpp
|
||||
cartridge/board/unl-sachen-74ls374n.cpp
|
||||
cartridge/board/unl-sachen-sa0037.cpp
|
||||
cartridge/board/unl-sc127.cpp
|
||||
cartridge/board/unl-txc.cpp
|
||||
cartridge/board/unrom-512.cpp
|
||||
)
|
||||
@@ -146,6 +148,14 @@ ares_add_sources(
|
||||
controller/gamepad/gamepad.hpp
|
||||
)
|
||||
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
fc
|
||||
INCLUDED #
|
||||
controller/zapper/zapper.cpp
|
||||
controller/zapper/zapper.hpp
|
||||
)
|
||||
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
fc
|
||||
@@ -180,6 +190,8 @@ ares_add_sources(
|
||||
INCLUDED #
|
||||
expansion/family-keyboard/family-keyboard.cpp
|
||||
expansion/family-keyboard/family-keyboard.hpp
|
||||
expansion/family-keyboard/famicom-data-recorder.cpp
|
||||
expansion/family-keyboard/famicom-data-recorder.hpp
|
||||
)
|
||||
|
||||
ares_add_sources(
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ struct APU : Thread {
|
||||
Node::Object node;
|
||||
Node::Audio::Stream stream;
|
||||
|
||||
auto rate() const -> u32 { return Region::PAL() ? 16 : 12; }
|
||||
auto rate() const -> u32 { return system.cpuDivider(); }
|
||||
|
||||
//apu.cpp
|
||||
auto load(Node::Object) -> void;
|
||||
|
||||
@@ -59,8 +59,10 @@ namespace Board {
|
||||
#include "unrom-512.cpp"
|
||||
|
||||
#include "unl-bmc.cpp"
|
||||
#include "unl-jy.cpp"
|
||||
#include "unl-sachen-74ls374n.cpp"
|
||||
#include "unl-sachen-sa0037.cpp"
|
||||
#include "unl-sc127.cpp"
|
||||
#include "unl-txc.cpp"
|
||||
|
||||
auto Interface::create(string board) -> Interface* {
|
||||
@@ -124,8 +126,10 @@ auto Interface::create(string board) -> Interface* {
|
||||
if(!p) p = UNROM512::create(board);
|
||||
|
||||
if(!p) p = BMC::create(board);
|
||||
if(!p) p = JYCompany::create(board);
|
||||
if(!p) p = Sachen74LS374N::create(board);
|
||||
if(!p) p = SachenSA0037::create(board);
|
||||
if(!p) p = Sc127::create(board);
|
||||
if(!p) p = TXC::create(board);
|
||||
|
||||
if(!p) p = new Interface;
|
||||
|
||||
@@ -0,0 +1,548 @@
|
||||
struct JYCompany : Interface {
|
||||
static auto create(string id) -> Interface* {
|
||||
if(id == "UNL-JY-COMPANY-A") return new JYCompany(Revision::JYTypeA);
|
||||
if(id == "UNL-JY-COMPANY-B") return new JYCompany(Revision::JYTypeB);
|
||||
if(id == "UNL-JY-COMPANY-C") return new JYCompany(Revision::JYTypeC);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Memory::Readable<n8> programROM;
|
||||
Memory::Readable<n8> characterROM;
|
||||
|
||||
enum class Revision : u32 {
|
||||
JYTypeA,
|
||||
JYTypeB,
|
||||
JYTypeC,
|
||||
} revision;
|
||||
|
||||
enum class IrqSource : u32 {
|
||||
CpuM2Rise = 0,
|
||||
PpuA12Rise = 1, // unfiltered, eight per scanline
|
||||
PpuRead = 2, // 170 per scanline ?
|
||||
CpuWrite = 3,
|
||||
};
|
||||
|
||||
JYCompany(Revision revision) : revision(revision) {
|
||||
}
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(programROM, "program.rom");
|
||||
Interface::load(characterROM, "character.rom");
|
||||
}
|
||||
|
||||
auto main() -> void override {
|
||||
if(irqSource == IrqSource::CpuM2Rise) tickIrqCounter();
|
||||
if(irqSource == IrqSource::CpuWrite && cpu.isWriting()) tickIrqCounter();
|
||||
cpu.irqLine(irqLine);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto updatePRGBanks() -> void {
|
||||
n8 exPrg = exPrgMode << 6;
|
||||
n8 last = (prgMode & 0x04) ? prgRegs[3] : n8(0x3f);
|
||||
|
||||
switch(prgMode & 0x03) {
|
||||
case 0: // 32KB
|
||||
programBanks[0] = ((last & 0x0f) | (exPrg >> 2)) << 2;
|
||||
programBanks[1] = programBanks[0] + 1;
|
||||
programBanks[2] = programBanks[0] + 2;
|
||||
programBanks[3] = programBanks[0] + 3;
|
||||
programAt6000Bank = (((prgRegs[3] * 4) + 3) & 0x3f) | (exPrg >> 2);
|
||||
break;
|
||||
case 1: // 16KB
|
||||
programBanks[0] = ((prgRegs[1] & 0x1f) | (exPrg >> 1)) << 1;
|
||||
programBanks[1] = programBanks[0] + 1;
|
||||
programBanks[2] = ((last & 0x1f) | (exPrg >> 1)) << 1;
|
||||
programBanks[3] = programBanks[2] + 1;
|
||||
programAt6000Bank = (((prgRegs[3] * 2) + 1) & 0x1f) | (exPrg >> 1);
|
||||
break;
|
||||
case 2: // 8KB
|
||||
programBanks[0] = prgRegs[0] | exPrg;
|
||||
programBanks[1] = prgRegs[1] | exPrg;
|
||||
programBanks[2] = prgRegs[2] | exPrg;
|
||||
programBanks[3] = last | exPrg;
|
||||
programAt6000Bank = prgRegs[3] | exPrg;
|
||||
break;
|
||||
case 3: // 8KB Alt (Scrambled)
|
||||
programBanks[0] = (invertPrgBits(prgRegs[0]) & 0x3f) | exPrg;
|
||||
programBanks[1] = (invertPrgBits(prgRegs[1]) & 0x3f) | exPrg;
|
||||
programBanks[2] = (invertPrgBits(prgRegs[2]) & 0x3f) | exPrg;
|
||||
if(prgMode & 0x04)
|
||||
programBanks[3] = (invertPrgBits(prgRegs[3]) & 0x3f) | exPrg;
|
||||
else
|
||||
programBanks[3] = (invertPrgBits(last) & 0x3f) | exPrg;
|
||||
programAt6000Bank = (invertPrgBits(prgRegs[3]) & 0x3f) | exPrg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// PRG read
|
||||
auto readPRG(n32 address, n8 data) -> n8 override {
|
||||
if(address < 0x5000) return data;
|
||||
// multiply/accumulator registers at $5000-$5FFF
|
||||
if(address >= 0x5000 && address < 0x6000) {
|
||||
if(address < 0x5800) { return data & 0x3f; }
|
||||
switch(address & 0x07) {
|
||||
case 0: return (multiplyValue1 * multiplyValue2) & 0xff;
|
||||
case 1: return ((multiplyValue1 * multiplyValue2) >> 8) & 0xff;
|
||||
case 3: return regRamValue;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
//$6000-$7FFF: optional PRG bank
|
||||
if(address >= 0x6000 && address < 0x8000) {
|
||||
if(!enablePrgAt6000) return data;
|
||||
return programROM.read((u32)programAt6000Bank << 13 | (n13)address);
|
||||
}
|
||||
|
||||
//$8000-$FFFF: PRG banking
|
||||
if(address >= 0x8000) return programROM.read((u32)programBanks[address >> 13 & 0x03] << 13 | (n13)address);
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
// PRG write
|
||||
auto writePRG(n32 address, n8 data) -> void override {
|
||||
if(address < 0x5000) return;
|
||||
|
||||
if(address >= 0x5000 && address < 0x6000) {
|
||||
if(address < 0x5800) return;
|
||||
switch(address & 0x07) {
|
||||
case 0: multiplyValue1 = data; break;
|
||||
case 1: multiplyValue2 = data; break;
|
||||
case 3: regRamValue = data; break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(address < 0x8000) return;
|
||||
|
||||
switch(address & 0xf007) {
|
||||
case 0x8000:
|
||||
case 0x8001:
|
||||
case 0x8002:
|
||||
case 0x8003:
|
||||
case 0x8004:
|
||||
case 0x8005:
|
||||
case 0x8006:
|
||||
case 0x8007:
|
||||
prgRegs[address & 0x03] = data & 0x3f;
|
||||
updatePRGBanks();
|
||||
break;
|
||||
|
||||
case 0x9000:
|
||||
case 0x9001:
|
||||
case 0x9002:
|
||||
case 0x9003:
|
||||
case 0x9004:
|
||||
case 0x9005:
|
||||
case 0x9006:
|
||||
case 0x9007:
|
||||
chrLowRegs[address & 0x07] = data;
|
||||
updateCHRBanks();
|
||||
break;
|
||||
|
||||
case 0xa000:
|
||||
case 0xa001:
|
||||
case 0xa002:
|
||||
case 0xa003:
|
||||
case 0xa004:
|
||||
case 0xa005:
|
||||
case 0xa006:
|
||||
case 0xa007:
|
||||
chrHighRegs[address & 0x07] = data;
|
||||
updateCHRBanks();
|
||||
break;
|
||||
|
||||
case 0xb000:
|
||||
case 0xb001:
|
||||
case 0xb002:
|
||||
case 0xb003:
|
||||
ntLowRegs[address & 0x03] = data;
|
||||
updateCIRAMBanks();
|
||||
break;
|
||||
|
||||
case 0xb004:
|
||||
case 0xb005:
|
||||
case 0xb006:
|
||||
case 0xb007:
|
||||
ntHighRegs[address & 0x03] = data;
|
||||
updateCIRAMBanks();
|
||||
break;
|
||||
|
||||
case 0xc000:
|
||||
if(data & 0x01) {
|
||||
irqEnabled = true;
|
||||
} else {
|
||||
irqEnabled = false;
|
||||
irqLine = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xc001:
|
||||
irqCountDirection = (data >> 6) & 0x03;
|
||||
irqFunkyMode = (data & 0x08) == 0x08;
|
||||
irqSmallPrescaler = ((data >> 2) & 0x01) == 0x01;
|
||||
irqSource = (IrqSource)(data & 0x03);
|
||||
break;
|
||||
|
||||
case 0xc002:
|
||||
irqEnabled = false;
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xc003: irqEnabled = true; break;
|
||||
case 0xc004: irqPrescaler = data ^ irqXorReg; break;
|
||||
case 0xc005: irqCounter = data ^ irqXorReg; break;
|
||||
case 0xc006: irqXorReg = data; break;
|
||||
case 0xc007: irqFunkyModeReg = data; break;
|
||||
|
||||
case 0xd000:
|
||||
prgMode = data & 0x07;
|
||||
chrMode = (data >> 3) & 0x03;
|
||||
advancedNtControl = (data & 0x20) == 0x20;
|
||||
disableNtRam = (data & 0x40) == 0x40;
|
||||
enablePrgAt6000 = (data & 0x80) == 0x80;
|
||||
|
||||
updatePRGBanks();
|
||||
updateCHRBanks();
|
||||
updateCIRAMBanks();
|
||||
break;
|
||||
|
||||
case 0xd001:
|
||||
mirroringReg = data & 0x03;
|
||||
updateCIRAMBanks();
|
||||
break;
|
||||
|
||||
case 0xd002:
|
||||
ntRamSelectBit = data & 0x80;
|
||||
updateCIRAMBanks();
|
||||
break;
|
||||
|
||||
case 0xd003:
|
||||
mirrorChr = (data & 0x80) == 0x80;
|
||||
chrBlockMode = (data & 0x20) == 0x00;
|
||||
chrBlock = (data & 0x01) | ((data & 0x18) >> 2);
|
||||
exPrgMode = (data & 0x06) >> 1;
|
||||
updatePRGBanks();
|
||||
updateCHRBanks();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto getCHRReg(u32 index) -> n16 {
|
||||
if(chrMode >= 2 && mirrorChr) {
|
||||
if(index == 2)
|
||||
index = 0;
|
||||
else if(index == 3)
|
||||
index = 1;
|
||||
}
|
||||
|
||||
if(chrBlockMode) {
|
||||
switch(chrMode) {
|
||||
default:
|
||||
case 0: return (chrLowRegs[index] & 0x1f) | (chrBlock << 5);
|
||||
case 1: return (chrLowRegs[index] & 0x3f) | (chrBlock << 6);
|
||||
case 2: return (chrLowRegs[index] & 0x7f) | (chrBlock << 7);
|
||||
case 3: return (chrLowRegs[index] & 0xff) | (chrBlock << 8);
|
||||
}
|
||||
} else {
|
||||
return chrLowRegs[index] | (chrHighRegs[index] << 8);
|
||||
}
|
||||
}
|
||||
|
||||
auto updateCHRBanks() -> void {
|
||||
n16 bank;
|
||||
switch(chrMode) {
|
||||
case 0:
|
||||
bank = getCHRReg(0) << 3;
|
||||
for(auto b : range(8)) characterBank[0 + b] = bank + b;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
bank = getCHRReg(chrLatch[0]) << 2;
|
||||
for(auto b : range(4)) characterBank[0 + b] = bank + b;
|
||||
|
||||
bank = getCHRReg(chrLatch[1]) << 2;
|
||||
for(auto b : range(4)) characterBank[4 + b] = bank + b;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
bank = getCHRReg(0) << 1;
|
||||
for(auto b : range(2)) characterBank[0 + b] = bank + b;
|
||||
|
||||
bank = getCHRReg(2) << 1;
|
||||
for(auto b : range(2)) characterBank[2 + b] = bank + b;
|
||||
|
||||
bank = getCHRReg(4) << 1;
|
||||
for(auto b : range(2)) characterBank[4 + b] = bank + b;
|
||||
|
||||
bank = getCHRReg(6) << 1;
|
||||
for(auto b : range(2)) characterBank[6 + b] = bank + b;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
for(auto i : range(8)) characterBank[i] = getCHRReg(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto updateCIRAMBanks() -> void {
|
||||
if(useAdvancedNt()) {
|
||||
for(auto i : range(4)) ntBanks[i] = (ntLowRegs[i] & 0x01);
|
||||
} else {
|
||||
switch(mirroringReg) {
|
||||
case 0:
|
||||
ntBanks[0] = ntBanks[2] = 0;
|
||||
ntBanks[1] = ntBanks[3] = 1;
|
||||
break;
|
||||
case 1:
|
||||
ntBanks[0] = ntBanks[1] = 0;
|
||||
ntBanks[2] = ntBanks[3] = 1;
|
||||
break;
|
||||
case 2: ntBanks[0] = ntBanks[1] = ntBanks[2] = ntBanks[3] = 0; break;
|
||||
case 3: ntBanks[0] = ntBanks[1] = ntBanks[2] = ntBanks[3] = 1; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto addressCHR(n32 address) const -> n32 {
|
||||
return (characterBank[address >> 10 & 0x07] << 10) | (n10)address;
|
||||
}
|
||||
|
||||
auto addressCIRAM(n32 address) const -> n32 {
|
||||
return (ntBanks[(address >> 10) & 0x03] << 10) | (n10)address;
|
||||
}
|
||||
|
||||
auto readCHR(n32 address, n8 data) -> n8 override {
|
||||
if(irqSource == IrqSource::PpuRead && !ppu.isRenderingRead()) tickIrqCounter();
|
||||
if(address & 0x2000) {
|
||||
if(useAdvancedNt()) {
|
||||
n8 ntIndex = (address >> 10) & 0x03;
|
||||
if(disableNtRam || (ntLowRegs[ntIndex] & 0x80) != (ntRamSelectBit & 0x80)) {
|
||||
// This behavior only affects reads, not writes.
|
||||
// Additional info: https://forums.nesdev.com/viewtopic.php?f=3&t=17198
|
||||
u16 chrPage = ntLowRegs[ntIndex] | ntHighRegs[ntIndex] << 8;
|
||||
return characterROM.read(chrPage << 10 | (n10)address);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return ppu.readCIRAM(addressCIRAM(address));
|
||||
}
|
||||
return characterROM.read(addressCHR(address));
|
||||
}
|
||||
|
||||
auto writeCHR(n32 address, n8 data) -> void override {
|
||||
if(address & 0x2000) return ppu.writeCIRAM(addressCIRAM(address), data);
|
||||
}
|
||||
|
||||
auto ppuAddressBus(n14 address) -> void override {
|
||||
if(irqSource == IrqSource::PpuA12Rise && (address & 0x1000) && !(characterAddress & 0x1000)) tickIrqCounter();
|
||||
characterAddress = address;
|
||||
|
||||
if(revision == Revision::JYTypeC) {
|
||||
switch(address & 0x0ff0) {
|
||||
case 0x0fd0:
|
||||
chrLatch[address >> 12 & 0x01] = (address >> 10 & 0x04);
|
||||
updateCHRBanks();
|
||||
break;
|
||||
case 0x0fe0:
|
||||
chrLatch[address >> 12 & 0x01] = (address >> 10 & 0x04) | 0x02;
|
||||
updateCHRBanks();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
for(auto& r : prgRegs) r = 0;
|
||||
exPrgMode = 0;
|
||||
prgMode = 0;
|
||||
enablePrgAt6000 = false;
|
||||
programAt6000Bank = 0;
|
||||
programBanks[0] = 0;
|
||||
updatePRGBanks();
|
||||
|
||||
for(auto& r : chrLowRegs) r = 0;
|
||||
for(auto& r : chrHighRegs) r = 0;
|
||||
chrLatch[0] = 0;
|
||||
chrLatch[1] = 4;
|
||||
chrMode = 0;
|
||||
chrBlockMode = false;
|
||||
chrBlock = 0;
|
||||
mirrorChr = false;
|
||||
for(auto& c : characterBank) c = 0;
|
||||
updateCHRBanks();
|
||||
|
||||
mirroringReg = 0;
|
||||
advancedNtControl = false;
|
||||
disableNtRam = false;
|
||||
ntRamSelectBit = 0;
|
||||
for(auto& r : ntLowRegs) r = 0;
|
||||
for(auto& r : ntHighRegs) r = 0;
|
||||
for(auto& c : ntBanks) c = 0;
|
||||
updateCIRAMBanks();
|
||||
|
||||
irqEnabled = false;
|
||||
irqSource = IrqSource::CpuM2Rise;
|
||||
irqCountDirection = 0;
|
||||
irqFunkyMode = false;
|
||||
irqFunkyModeReg = 0;
|
||||
irqSmallPrescaler = false;
|
||||
irqPrescaler = 0;
|
||||
irqCounter = 0;
|
||||
irqXorReg = 0;
|
||||
irqLine = 0;
|
||||
characterAddress = 0;
|
||||
|
||||
multiplyValue1 = 0;
|
||||
multiplyValue2 = 0;
|
||||
regRamValue = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(prgRegs);
|
||||
s(exPrgMode);
|
||||
s(prgMode);
|
||||
s(enablePrgAt6000);
|
||||
s(programAt6000Bank);
|
||||
s(programBanks);
|
||||
|
||||
s(chrLowRegs);
|
||||
s(chrHighRegs);
|
||||
s(chrLatch);
|
||||
s(chrBlockMode);
|
||||
s(chrBlock);
|
||||
s(mirrorChr);
|
||||
s(characterBank);
|
||||
|
||||
s(mirroringReg);
|
||||
s(advancedNtControl);
|
||||
s(disableNtRam);
|
||||
s(ntRamSelectBit);
|
||||
s(ntLowRegs);
|
||||
s(ntHighRegs);
|
||||
s(ntBanks);
|
||||
|
||||
s(irqEnabled);
|
||||
s(irqSource);
|
||||
s(irqCountDirection);
|
||||
s(irqFunkyMode);
|
||||
s(irqFunkyModeReg);
|
||||
s(irqSmallPrescaler);
|
||||
s(irqPrescaler);
|
||||
s(irqCounter);
|
||||
s(irqXorReg);
|
||||
s(irqLine);
|
||||
s(characterAddress);
|
||||
|
||||
s(multiplyValue1);
|
||||
s(multiplyValue2);
|
||||
s(regRamValue);
|
||||
}
|
||||
|
||||
auto useAdvancedNt() const -> bool {
|
||||
// Mapper 211 always uses advanced NT control
|
||||
// Mapper 090 never uses it
|
||||
// Mapper 209 uses the advancedNtControl flag
|
||||
if(revision == Revision::JYTypeA) return false;
|
||||
if(revision == Revision::JYTypeB) return true;
|
||||
return advancedNtControl;
|
||||
}
|
||||
|
||||
auto invertPrgBits(n8 reg) -> n8 {
|
||||
return (reg & 0x01) << 6 | (reg & 0x7e) >> 1;
|
||||
}
|
||||
|
||||
auto tickIrqCounter() -> void {
|
||||
bool clockIrqCounter = false;
|
||||
n8 mask = irqSmallPrescaler ? (n8)0x07 : (n8)0xff;
|
||||
n8 prescaler = irqPrescaler & mask;
|
||||
|
||||
if(irqCountDirection == 0x01) { // Up
|
||||
if(prescaler == mask) {
|
||||
clockIrqCounter = true;
|
||||
prescaler = 0;
|
||||
} else {
|
||||
prescaler++;
|
||||
}
|
||||
} else if(irqCountDirection == 0x02) { // Down
|
||||
if(prescaler == 0) {
|
||||
clockIrqCounter = true;
|
||||
prescaler = mask;
|
||||
} else {
|
||||
prescaler--;
|
||||
}
|
||||
}
|
||||
irqPrescaler = (irqPrescaler & ~mask) | (prescaler & mask);
|
||||
|
||||
if(clockIrqCounter) {
|
||||
if(irqCountDirection == 0x01) { // Up
|
||||
if(irqCounter == 0xff) {
|
||||
irqCounter = 0;
|
||||
if(irqEnabled) irqLine = 1;
|
||||
} else {
|
||||
irqCounter++;
|
||||
}
|
||||
} else if(irqCountDirection == 0x02) { // Down
|
||||
if(irqCounter == 0) {
|
||||
irqCounter = 0xff;
|
||||
if(irqEnabled) irqLine = 1;
|
||||
} else {
|
||||
irqCounter--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PRG registers
|
||||
n8 prgRegs[4];
|
||||
n3 prgMode;
|
||||
n2 exPrgMode;
|
||||
n1 enablePrgAt6000;
|
||||
n8 programAt6000Bank; // bank at $6000-$7FFF when enabled
|
||||
n8 programBanks[4]; // 0. $8000-$9FFF
|
||||
// 1. $A000-$BFFF
|
||||
// 2. $C000-$DFFF
|
||||
// 3. $E000-$FFFF
|
||||
|
||||
// CHR registers
|
||||
n8 chrLowRegs[8];
|
||||
n8 chrHighRegs[8];
|
||||
n8 chrLatch[2];
|
||||
n2 chrMode;
|
||||
n1 chrBlockMode;
|
||||
n3 chrBlock;
|
||||
n1 mirrorChr;
|
||||
n16 characterBank[8];
|
||||
|
||||
// Mirroring / Nametable
|
||||
n2 mirroringReg;
|
||||
n1 advancedNtControl;
|
||||
n1 disableNtRam;
|
||||
n8 ntRamSelectBit;
|
||||
n8 ntLowRegs[4];
|
||||
n8 ntHighRegs[4];
|
||||
n2 ntBanks[4];
|
||||
|
||||
// IRQ
|
||||
n1 irqEnabled;
|
||||
IrqSource irqSource;
|
||||
n2 irqCountDirection;
|
||||
n1 irqFunkyMode;
|
||||
n8 irqFunkyModeReg;
|
||||
n1 irqSmallPrescaler;
|
||||
n8 irqPrescaler;
|
||||
n8 irqCounter;
|
||||
n8 irqXorReg;
|
||||
n1 irqLine;
|
||||
n16 characterAddress;
|
||||
|
||||
// Misc
|
||||
n8 multiplyValue1;
|
||||
n8 multiplyValue2;
|
||||
n8 regRamValue;
|
||||
};
|
||||
@@ -0,0 +1,133 @@
|
||||
struct Sc127 : Interface {
|
||||
static auto create(string id) -> Interface* {
|
||||
// Wario Land II (Kirby hack)
|
||||
if(id == "UNL-SC-127") return new Sc127;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Memory::Readable<n8> programROM;
|
||||
Memory::Writable<n8> programRAM;
|
||||
Memory::Readable<n8> characterROM;
|
||||
|
||||
auto load() -> void override {
|
||||
Interface::load(programROM, "program.rom");
|
||||
Interface::load(programRAM, "save.ram");
|
||||
Interface::load(characterROM, "character.rom");
|
||||
}
|
||||
|
||||
auto save() -> void override {
|
||||
Interface::save(programRAM, "save.ram");
|
||||
}
|
||||
|
||||
auto main() -> void override {
|
||||
if(irqDelay) irqDelay--;
|
||||
cpu.irqLine(irqLine);
|
||||
tick();
|
||||
}
|
||||
|
||||
auto readPRG(n32 address, n8 data) -> n8 override {
|
||||
if(address < 0x6000) return data;
|
||||
if(address < 0x8000) {
|
||||
if(!programRAM) return data;
|
||||
return programRAM.read((n13)address);
|
||||
}
|
||||
|
||||
n8 bank = programBank[(address >> 13) & 3];
|
||||
return programROM.read(bank << 13 | (n13)address);
|
||||
}
|
||||
|
||||
auto writePRG(n32 address, n8 data) -> void override {
|
||||
if(address < 0x6000) return;
|
||||
if(address < 0x8000) {
|
||||
if(!programRAM) return;
|
||||
return programRAM.write((n13)address, data);
|
||||
}
|
||||
|
||||
switch(address & 0xf007) {
|
||||
case 0x8000:
|
||||
case 0x8001:
|
||||
case 0x8002:
|
||||
case 0x8003: programBank[address & 3] = data; break;
|
||||
|
||||
case 0x9000:
|
||||
case 0x9001:
|
||||
case 0x9002:
|
||||
case 0x9003:
|
||||
case 0x9004:
|
||||
case 0x9005:
|
||||
case 0x9006:
|
||||
case 0x9007: characterBank[address & 7] = data; break;
|
||||
|
||||
case 0xc002:
|
||||
irqEnable = 0;
|
||||
irqLine = 0;
|
||||
break;
|
||||
|
||||
case 0xc003: irqEnable = 1; break;
|
||||
|
||||
case 0xc005: irqCounter = data; break;
|
||||
|
||||
case 0xd001: mirror = data.bit(0); break;
|
||||
}
|
||||
}
|
||||
|
||||
auto addressCHR(n32 address) const -> n32 {
|
||||
return characterBank[address >> 10 & 7] << 10 | (n10)address;
|
||||
}
|
||||
|
||||
auto addressCIRAM(n32 address) const -> n32 {
|
||||
return address >> mirror & 0x0400 | (n10)address;
|
||||
}
|
||||
|
||||
auto readCHR(n32 address, n8 data) -> n8 override {
|
||||
if(address & 0x2000) return ppu.readCIRAM(addressCIRAM(address));
|
||||
return characterROM.read(addressCHR(address));
|
||||
}
|
||||
|
||||
auto writeCHR(n32 address, n8 data) -> void override {
|
||||
if(address & 0x2000) return ppu.writeCIRAM(addressCIRAM(address), data);
|
||||
}
|
||||
|
||||
//A12 rising edge detection with delay filtering (Mesen A12Watcher style)
|
||||
auto ppuAddressBus(n14 address) -> void override {
|
||||
if(!(characterAddress & 0x1000) && (address & 0x1000)) {
|
||||
if(irqDelay == 0) {
|
||||
if(irqEnable) {
|
||||
irqCounter--;
|
||||
if(irqCounter == 0) {
|
||||
irqEnable = 0;
|
||||
irqLine = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
irqDelay = 6;
|
||||
}
|
||||
characterAddress = address;
|
||||
}
|
||||
|
||||
auto power() -> void override {
|
||||
programBank[3] = 0xff;
|
||||
characterBank[0] = 0;
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
s(programRAM);
|
||||
s(programBank);
|
||||
s(characterBank);
|
||||
s(mirror);
|
||||
s(irqCounter);
|
||||
s(irqEnable);
|
||||
s(irqDelay);
|
||||
s(irqLine);
|
||||
s(characterAddress);
|
||||
}
|
||||
|
||||
n8 programBank[4];
|
||||
n8 characterBank[8];
|
||||
n1 mirror; // 1: horizontal, 0: vertical
|
||||
n8 irqCounter;
|
||||
n1 irqEnable;
|
||||
n8 irqDelay;
|
||||
n1 irqLine;
|
||||
n16 characterAddress;
|
||||
};
|
||||
@@ -5,7 +5,7 @@ struct Cartridge : Thread {
|
||||
Node::Peripheral node;
|
||||
VFS::Pak pak;
|
||||
|
||||
auto rate() const -> u32 { return Region::PAL() ? 16 : 12; }
|
||||
auto rate() const -> u32 { return system.cpuDivider(); }
|
||||
auto title() const -> string { return information.title; }
|
||||
auto region() const -> string { return information.region; }
|
||||
|
||||
|
||||
+14
-3
@@ -24,8 +24,17 @@ auto CPU::unload() -> void {
|
||||
|
||||
auto CPU::main() -> void {
|
||||
if(io.interruptPending) {
|
||||
debugger.interrupt("IRQ");
|
||||
interrupt();
|
||||
if(io.resetPending) {
|
||||
debugger.interrupt("Reset");
|
||||
reset();
|
||||
io.resetPending = 0;
|
||||
} else if(io.nmiPending) {
|
||||
debugger.interrupt("NMI");
|
||||
interrupt();
|
||||
} else {
|
||||
debugger.interrupt("IRQ");
|
||||
interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
debugger.instruction();
|
||||
@@ -41,7 +50,7 @@ auto CPU::step(u32 clocks) -> void {
|
||||
|
||||
auto CPU::power(bool reset) -> void {
|
||||
MOS6502::BCD = 0;
|
||||
MOS6502::power(reset);
|
||||
if(!reset) MOS6502::power();
|
||||
Thread::create(system.frequency(), std::bind_front(&CPU::main, this));
|
||||
|
||||
if(!reset) {
|
||||
@@ -49,6 +58,8 @@ auto CPU::power(bool reset) -> void {
|
||||
}
|
||||
|
||||
io = {};
|
||||
io.resetPending = 1;
|
||||
io.interruptPending = 1;
|
||||
}
|
||||
|
||||
}
|
||||
+7
-1
@@ -18,7 +18,7 @@ struct CPU : MOS6502, Thread {
|
||||
} tracer;
|
||||
} debugger;
|
||||
|
||||
auto rate() const -> u32 { return Region::PAL() ? 16 : 12; }
|
||||
auto rate() const -> u32 { return system.cpuDivider(); }
|
||||
|
||||
//cpu.cpp
|
||||
auto load(Node::Object) -> void;
|
||||
@@ -56,9 +56,14 @@ struct CPU : MOS6502, Thread {
|
||||
auto irqLine(bool) -> void;
|
||||
auto apuLine(bool) -> void;
|
||||
|
||||
auto isWriting() -> bool {
|
||||
return io.rwLine == 0;
|
||||
}
|
||||
|
||||
//protected:
|
||||
struct IO {
|
||||
n1 interruptPending;
|
||||
n1 resetPending;
|
||||
n1 nmiPending;
|
||||
n1 nmiLine;
|
||||
n1 irqLine;
|
||||
@@ -69,6 +74,7 @@ struct CPU : MOS6502, Thread {
|
||||
n1 oamDMAPending;
|
||||
n8 oamDMAPage;
|
||||
n8 openBus;
|
||||
n1 rwLine;
|
||||
} io;
|
||||
};
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ auto CPU::readIO(n16 address) -> n8 {
|
||||
case 0x4016: {
|
||||
auto port1 = controllerPort1.data();
|
||||
auto port3 = expansionPort.read1();
|
||||
platform->input(system.controls.microphone);
|
||||
if(system.controls.microphone) platform->input(system.controls.microphone);
|
||||
data.bit(0) = port1.bit(0);
|
||||
data.bit(1) = port3.bit(0);
|
||||
data.bit(2) = system.controls.microphone->value() ? random().bit(0) : 0;
|
||||
data.bit(2) = system.controls.microphone && system.controls.microphone->value() ? random().bit(0) : 0;
|
||||
data.bit(3) = port1.bit(1);
|
||||
data.bit(4) = port1.bit(2);
|
||||
return data;
|
||||
|
||||
@@ -3,6 +3,7 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
s(ram);
|
||||
s(io.interruptPending);
|
||||
s(io.resetPending);
|
||||
s(io.nmiPending);
|
||||
s(io.nmiLine);
|
||||
s(io.irqLine);
|
||||
|
||||
@@ -10,17 +10,19 @@ auto CPU::read(n16 address) -> n8 {
|
||||
}
|
||||
|
||||
auto CPU::write(n16 address, n8 data) -> void {
|
||||
io.rwLine = 1;
|
||||
writeBus(address, data);
|
||||
io.openBus = data;
|
||||
step(rate());
|
||||
io.rwLine = 0;
|
||||
}
|
||||
|
||||
auto CPU::lastCycle() -> void {
|
||||
io.interruptPending = ((io.irqLine | io.apuLine) & !P.i) | io.nmiPending;
|
||||
io.interruptPending = irqPending() | io.nmiPending;
|
||||
}
|
||||
|
||||
auto CPU::cancelNmi() -> void {
|
||||
io.interruptPending = ((io.irqLine | io.apuLine) & !P.i);
|
||||
io.interruptPending = irqPending();
|
||||
}
|
||||
|
||||
auto CPU::delayIrq() -> void {
|
||||
@@ -33,6 +35,7 @@ auto CPU::irqPending() -> bool {
|
||||
|
||||
auto CPU::nmi(n16& vector) -> void {
|
||||
if(io.nmiPending) {
|
||||
if(irqPending()) debugger.interrupt("Haijack IRQ by NMI");
|
||||
io.nmiPending = false;
|
||||
vector = 0xfffa;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ struct Expansion {
|
||||
// required for emulating the EPSM.
|
||||
virtual auto write(n8 data) -> void {}
|
||||
virtual auto writeIO(n16 address, n8 data) -> void {}
|
||||
virtual auto serialize(serializer&) -> void {}
|
||||
};
|
||||
|
||||
#include "port.hpp"
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
FamicomDataRecorder::FamicomDataRecorder(Node::Port port) {
|
||||
node = port->append<Node::Tape>("Famicom Data Recorder");
|
||||
|
||||
node->setSupportPlay(true);
|
||||
node->setSupportRecord(true);
|
||||
node->setLoad([&] { return load(); });
|
||||
node->setUnload([&] { unload(); });
|
||||
|
||||
node->setPosition(0);
|
||||
node->setLength(0);
|
||||
node->setFrequency(44100);
|
||||
|
||||
data.reset();
|
||||
output = 0;
|
||||
input = 0;
|
||||
speed = system.frequency() / node->frequency();
|
||||
|
||||
Thread::create(system.frequency(), std::bind_front(&FamicomDataRecorder::main, this));
|
||||
}
|
||||
|
||||
FamicomDataRecorder::~FamicomDataRecorder() {
|
||||
Thread::destroy();
|
||||
node.reset();
|
||||
}
|
||||
|
||||
auto FamicomDataRecorder::read() -> n1 {
|
||||
if (!node->playing())
|
||||
return 0;
|
||||
return output;
|
||||
}
|
||||
|
||||
auto FamicomDataRecorder::write(n1 data) -> void {
|
||||
if (!node->recording())
|
||||
return;
|
||||
input = data;
|
||||
}
|
||||
|
||||
auto FamicomDataRecorder::load() -> bool {
|
||||
if (!node->setPak(pak = platform->pak(node)))
|
||||
return false;
|
||||
|
||||
node->setPosition(0);
|
||||
node->setLength(pak->attribute("length").natural());
|
||||
node->setFrequency(pak->attribute("frequency").natural());
|
||||
|
||||
if (node->length() != 0) {
|
||||
auto fd = pak->read("program.tape");
|
||||
if (!fd)
|
||||
return false;
|
||||
data.allocate(node->length());
|
||||
data.load(fd);
|
||||
fd.reset();
|
||||
}
|
||||
|
||||
range = pak->attribute("range").natural();
|
||||
speed = system.frequency() / node->frequency();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto FamicomDataRecorder::unload() -> void {
|
||||
if (!pak)
|
||||
return;
|
||||
|
||||
if (data.size() != 0) {
|
||||
auto fd = pak->write("program.tape");
|
||||
fd->resize(data.size() * sizeof(u64));
|
||||
data.save(fd);
|
||||
fd.reset();
|
||||
}
|
||||
|
||||
node->setFrequency(44100);
|
||||
node->setPosition(0);
|
||||
node->setLength(0);
|
||||
|
||||
pak.reset();
|
||||
output = 0;
|
||||
input = 0;
|
||||
range = 0;
|
||||
speed = system.frequency() / 44100;
|
||||
data.reset();
|
||||
}
|
||||
|
||||
auto FamicomDataRecorder::main() -> void {
|
||||
u64 position = node->position();
|
||||
u64 length = node->length();
|
||||
|
||||
if (node->playing()) {
|
||||
if (position >= length) {
|
||||
node->stop();
|
||||
return step();
|
||||
}
|
||||
|
||||
output = data.read(position++) > range / 2 ? 1 : 0;
|
||||
node->setPosition(position);
|
||||
return step();
|
||||
}
|
||||
|
||||
if (node->recording()) {
|
||||
u64 frequency = node->frequency();
|
||||
if (length <= position) {
|
||||
auto fd = pak->write("program.tape");
|
||||
fd->resize(data.size() * sizeof(u64));
|
||||
data.save(fd);
|
||||
fd.reset();
|
||||
|
||||
length = (position / frequency + 1) * frequency;
|
||||
fd = pak->read("program.tape");
|
||||
data.allocate(length, 0);
|
||||
data.load(fd);
|
||||
fd.reset();
|
||||
node->setLength(length);
|
||||
}
|
||||
|
||||
data[position++] = (range >> 1) + (input ? 1 : 0);
|
||||
node->setPosition(position);
|
||||
return step();
|
||||
}
|
||||
|
||||
return step();
|
||||
}
|
||||
|
||||
auto FamicomDataRecorder::step() -> void {
|
||||
Thread::step(speed);
|
||||
Thread::synchronize();
|
||||
}
|
||||
|
||||
auto FamicomDataRecorder::serialize(serializer& s) -> void {
|
||||
u64 position = node ? node->position() : 0;
|
||||
u64 length = node ? node->length() : 0;
|
||||
u64 frequency = node ? node->frequency() : 44100;
|
||||
bool playing = node ? node->playing() : false;
|
||||
bool recording = node ? node->recording() : false;
|
||||
|
||||
Thread::serialize(s);
|
||||
s(position);
|
||||
s(length);
|
||||
s(frequency);
|
||||
s(playing);
|
||||
s(recording);
|
||||
s(output);
|
||||
s(input);
|
||||
s(speed);
|
||||
s(range);
|
||||
s(data);
|
||||
|
||||
if(s.reading() && node) {
|
||||
node->setPosition(position);
|
||||
node->setLength(length);
|
||||
node->setFrequency(frequency);
|
||||
node->stop();
|
||||
if(playing) node->play();
|
||||
if(recording) node->record();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
struct FamicomDataRecorder : Tape, Thread {
|
||||
Node::Tape node;
|
||||
|
||||
explicit FamicomDataRecorder(Node::Port);
|
||||
~FamicomDataRecorder() override;
|
||||
|
||||
auto read() -> n1 override;
|
||||
auto write(n1 data) -> void override;
|
||||
|
||||
auto load() -> bool;
|
||||
auto unload() -> void;
|
||||
|
||||
auto main() -> void;
|
||||
auto step() -> void;
|
||||
auto serialize(serializer&) -> void override;
|
||||
|
||||
private:
|
||||
VFS::Pak pak;
|
||||
|
||||
n1 output;
|
||||
n1 input;
|
||||
u64 speed;
|
||||
u64 range;
|
||||
|
||||
Memory::Writable<u64> data;
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "famicom-data-recorder.cpp"
|
||||
|
||||
FamilyKeyboard::FamilyKeyboard(Node::Port parent) {
|
||||
node = parent->append<Node::Peripheral>("Family Keyboard");
|
||||
|
||||
@@ -80,12 +82,13 @@ FamilyKeyboard::FamilyKeyboard(Node::Port parent) {
|
||||
key.down = node->append<Node::Input::Button>("Down");
|
||||
key.left = node->append<Node::Input::Button>("Left");
|
||||
key.right = node->append<Node::Input::Button>("Right");
|
||||
|
||||
tape = std::make_unique<FamicomDataRecorder>(parent);
|
||||
}
|
||||
|
||||
auto FamilyKeyboard::read1() -> n1 {
|
||||
n1 data;
|
||||
//data recorder (unsupported)
|
||||
return data;
|
||||
if (!latch.bit(2)) return 0b0;
|
||||
return tape->read();
|
||||
}
|
||||
|
||||
auto FamilyKeyboard::read2() -> n5 {
|
||||
@@ -219,4 +222,14 @@ auto FamilyKeyboard::write(n8 data) -> void {
|
||||
if(column && !latch.bit(1)) row = (row + 1) % 10;
|
||||
column = latch.bit(1);
|
||||
if(latch.bit(0)) row = 0;
|
||||
|
||||
tape->write(data.bit(0));
|
||||
}
|
||||
|
||||
auto FamilyKeyboard::serialize(serializer& s) -> void {
|
||||
s(latch);
|
||||
s(column);
|
||||
s(row);
|
||||
|
||||
tape->serialize(s);
|
||||
}
|
||||
@@ -1,3 +1,10 @@
|
||||
struct Tape {
|
||||
virtual ~Tape() = default;
|
||||
virtual auto read() -> n1 = 0;
|
||||
virtual auto write(n1 data) -> void = 0;
|
||||
virtual auto serialize(serializer&) -> void {}
|
||||
};
|
||||
|
||||
struct FamilyKeyboard : Expansion {
|
||||
struct Key {
|
||||
Node::Input::Button f1, f2, f3, f4, f5, f6, f7, f8;
|
||||
@@ -14,9 +21,13 @@ struct FamilyKeyboard : Expansion {
|
||||
auto read1() -> n1 override;
|
||||
auto read2() -> n5 override;
|
||||
auto write(n8 data) -> void override;
|
||||
auto serialize(serializer&) -> void override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Tape> tape;
|
||||
n3 latch;
|
||||
n1 column;
|
||||
n4 row;
|
||||
};
|
||||
|
||||
#include "famicom-data-recorder.hpp"
|
||||
@@ -26,4 +26,5 @@ auto ExpansionPort::allocate(string name) -> Node::Peripheral {
|
||||
}
|
||||
|
||||
auto ExpansionPort::serialize(serializer& s) -> void {
|
||||
if(device) device->serialize(s);
|
||||
}
|
||||
+2
-1
@@ -19,7 +19,8 @@ namespace ares::Famicom {
|
||||
struct Region {
|
||||
static inline auto NTSCJ() -> bool;
|
||||
static inline auto NTSCU() -> bool;
|
||||
static inline auto PAL() -> bool;
|
||||
static inline auto PAL() -> bool;
|
||||
static inline auto Dendy() -> bool;
|
||||
};
|
||||
|
||||
#include <fc/controller/controller.hpp>
|
||||
|
||||
@@ -54,7 +54,9 @@ auto PPU::readIO(n16 address) -> n8 {
|
||||
|
||||
address = io.busAddress;
|
||||
result = var.latchData;
|
||||
io.renderingRead = 0;
|
||||
var.latchData = cartridge.readCHR(address);
|
||||
io.renderingRead = 1;
|
||||
if (address >= 0x3f00)
|
||||
result = readCGRAM(address) | (io.mdr & 0xc0);
|
||||
|
||||
|
||||
+6
-5
@@ -23,7 +23,7 @@ auto PPU::load(Node::Object parent) -> void {
|
||||
screen->colors(1 << 9, std::bind_front(&PPU::color, this));
|
||||
screen->setSize(283, displayHeight());
|
||||
screen->setScale(1.0, 1.0);
|
||||
Region::PAL() ? screen->setAspect(55.0, 43.0) :screen->setAspect(8.0, 7.0);
|
||||
(Region::PAL() || Region::Dendy()) ? screen->setAspect(55.0, 43.0) : screen->setAspect(8.0, 7.0);
|
||||
screen->refreshRateHint(system.frequency() / rate(), 341, vlines());
|
||||
|
||||
debugger.load(node);
|
||||
@@ -61,10 +61,11 @@ auto PPU::step(u32 clocks) -> void {
|
||||
if (enable() && (io.ly < 240 || io.ly == L - 1))
|
||||
cycleScroll();
|
||||
|
||||
if(io.ly == 240 && io.lx == 1) io.busAddress = var.address, cartridge.ppuAddressBus(io.busAddress);
|
||||
if(io.ly == 240 && io.lx == 340) io.nmiHold = 1;
|
||||
if(io.ly == 241 && io.lx == 0) io.nmiFlag = io.nmiHold;
|
||||
if(io.ly == 241 && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
|
||||
u32 vbs = vblankScanline();
|
||||
if(io.ly == vbs - 1 && io.lx == 1) io.busAddress = var.address, cartridge.ppuAddressBus(io.busAddress);
|
||||
if(io.ly == vbs - 1 && io.lx == 340) io.nmiHold = 1;
|
||||
if(io.ly == vbs && io.lx == 0) io.nmiFlag = io.nmiHold;
|
||||
if(io.ly == vbs && io.lx == 2) cpu.nmiLine(io.nmiEnable && io.nmiFlag);
|
||||
|
||||
if(io.ly == L-2 && io.lx == 340) io.spriteZeroHit = 0, sprite.spriteOverflow = 0;
|
||||
|
||||
|
||||
+9
-2
@@ -19,9 +19,10 @@ struct PPU : Thread {
|
||||
} memory;
|
||||
} debugger;
|
||||
|
||||
auto rate() const -> u32 { return Region::PAL() ? 5 : 4; }
|
||||
auto vlines() const -> u32 { return Region::PAL() ? 312 : 262; }
|
||||
auto rate() const -> u32 { return Region::PAL() || Region::Dendy() ? 5 : 4; }
|
||||
auto vlines() const -> u32 { return Region::PAL() || Region::Dendy() ? 312 : 262; }
|
||||
auto displayHeight() const -> u32 { return Region::PAL() ? 288 : 242; }
|
||||
auto vblankScanline() const -> u32 { return Region::Dendy() ? 291 : 241; }
|
||||
|
||||
//ppu.cpp
|
||||
auto load(Node::Object) -> void;
|
||||
@@ -71,6 +72,10 @@ struct PPU : Thread {
|
||||
auto cycleSpriteEvaluation() -> void;
|
||||
auto cyclePrepareSpriteEvaluation() -> void;
|
||||
|
||||
auto isRenderingRead() const -> bool {
|
||||
return io.renderingRead;
|
||||
}
|
||||
|
||||
struct ScrollRegisters {
|
||||
n15 data;
|
||||
|
||||
@@ -121,6 +126,8 @@ struct PPU : Thread {
|
||||
n1 nmiHold;
|
||||
n1 nmiFlag;
|
||||
|
||||
n1 renderingRead;
|
||||
|
||||
//$2000
|
||||
n6 vramIncrement = 1; //1 or 32
|
||||
n16 spriteAddress; //0x0000 or 0x1000
|
||||
|
||||
@@ -72,7 +72,7 @@ auto PPU::renderPixel() -> void {
|
||||
color = io.emphasis << 6 | readCGRAM((n5)var.address);
|
||||
}
|
||||
|
||||
if(Region::PAL())
|
||||
if(Region::PAL() || Region::Dendy())
|
||||
output[(x + 18) % 283] = color;
|
||||
else
|
||||
output[(x + 16) % 283] = color;
|
||||
@@ -188,7 +188,7 @@ auto PPU::renderScanline() -> void {
|
||||
|
||||
//337-338
|
||||
loadCHR(0x2000 | (n12)var.address);
|
||||
bool skip = !Region::PAL() && enable() && io.field == 1 && io.ly == vlines() - 1;
|
||||
bool skip = !Region::PAL() && !Region::Dendy() && enable() && io.field == 1 && io.ly == vlines() - 1;
|
||||
step(2);
|
||||
|
||||
//339
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
auto System::Controls::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("Controls");
|
||||
|
||||
reset = node->append<Node::Input::Button>("Reset");
|
||||
microphone = node->append<Node::Input::Button>("Microphone");
|
||||
reset = node->append<Node::Input::Button>("Reset");
|
||||
if(system.region() != System::Region::Dendy) {
|
||||
microphone = node->append<Node::Input::Button>("Microphone");
|
||||
}
|
||||
}
|
||||
|
||||
auto System::Controls::unload() -> void {
|
||||
microphone.reset();
|
||||
reset.reset();
|
||||
node.reset();
|
||||
}
|
||||
|
||||
auto System::Controls::poll() -> void {
|
||||
platform->input(reset);
|
||||
platform->input(microphone);
|
||||
if(microphone) platform->input(microphone);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
static const string SerializerVersion = "v151";
|
||||
static const string SerializerVersion = "v153";
|
||||
|
||||
auto System::serialize(bool synchronize) -> serializer {
|
||||
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
|
||||
@@ -46,4 +46,5 @@ auto System::serialize(serializer& s, bool synchronize) -> void {
|
||||
if(fds.present) s(fds);
|
||||
s(controllerPort1);
|
||||
s(controllerPort2);
|
||||
expansionPort.serialize(s);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ auto enumerate() -> std::vector<string> {
|
||||
"[Nintendo] Famicom (NTSC-J)",
|
||||
"[Nintendo] Famicom (NTSC-U)",
|
||||
"[Nintendo] Famicom (PAL)",
|
||||
"[Dendy] Dendy",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,19 +48,28 @@ auto System::load(Node::System& root, string name) -> bool {
|
||||
|
||||
information = {};
|
||||
if(name.find("NTSC-J")) {
|
||||
information.name = "Famicom";
|
||||
information.region = Region::NTSCJ;
|
||||
information.frequency = Constants::Colorburst::NTSC * 6.0;
|
||||
information.name = "Famicom";
|
||||
information.region = Region::NTSCJ;
|
||||
information.frequency = Constants::Colorburst::NTSC * 6.0;
|
||||
information.cpuDivider = 12;
|
||||
}
|
||||
if(name.find("NTSC-U")) {
|
||||
information.name = "Famicom";
|
||||
information.region = Region::NTSCU;
|
||||
information.frequency = Constants::Colorburst::NTSC * 6.0;
|
||||
information.name = "Famicom";
|
||||
information.region = Region::NTSCU;
|
||||
information.frequency = Constants::Colorburst::NTSC * 6.0;
|
||||
information.cpuDivider = 12;
|
||||
}
|
||||
if(name.find("PAL")) {
|
||||
information.name = "Famicom";
|
||||
information.region = Region::PAL;
|
||||
information.frequency = Constants::Colorburst::PAL * 6.0;
|
||||
information.name = "Famicom";
|
||||
information.region = Region::PAL;
|
||||
information.frequency = Constants::Colorburst::PAL * 6.0;
|
||||
information.cpuDivider = 16;
|
||||
}
|
||||
if(name.find("Dendy")) {
|
||||
information.name = "Famicom";
|
||||
information.region = Region::Dendy;
|
||||
information.frequency = Constants::Colorburst::PAL * 6.0;
|
||||
information.cpuDivider = 15;
|
||||
}
|
||||
|
||||
node = std::make_shared<Core::System>(information.name);
|
||||
@@ -94,6 +104,7 @@ auto System::save() -> void {
|
||||
auto System::unload() -> void {
|
||||
if(!node) return;
|
||||
save();
|
||||
controls.unload();
|
||||
cpu.unload();
|
||||
apu.unload();
|
||||
ppu.unload();
|
||||
|
||||
@@ -10,14 +10,16 @@ struct System {
|
||||
|
||||
//controls.cpp
|
||||
auto load(Node::Object) -> void;
|
||||
auto unload() -> void;
|
||||
auto poll() -> void;
|
||||
} controls;
|
||||
|
||||
enum class Region : u32 { NTSCJ, NTSCU, PAL };
|
||||
enum class Region : u32 { NTSCJ, NTSCU, PAL, Dendy };
|
||||
|
||||
auto name() const -> string { return information.name; }
|
||||
auto region() const -> Region { return information.region; }
|
||||
auto frequency() const -> f64 { return information.frequency; }
|
||||
auto name() const -> string { return information.name; }
|
||||
auto region() const -> Region { return information.region; }
|
||||
auto frequency() const -> f64 { return information.frequency; }
|
||||
auto cpuDivider() const -> u8 { return information.cpuDivider; }
|
||||
|
||||
//system.cpp
|
||||
auto game() -> string;
|
||||
@@ -34,9 +36,10 @@ struct System {
|
||||
|
||||
private:
|
||||
struct Information {
|
||||
string name = "Famicom";
|
||||
Region region = Region::NTSCJ;
|
||||
f64 frequency = Constants::Colorburst::NTSC * 6.0;
|
||||
string name = "Famicom";
|
||||
Region region = Region::NTSCJ;
|
||||
f64 frequency = Constants::Colorburst::NTSC * 6.0;
|
||||
u8 cpuDivider = 12;
|
||||
} information;
|
||||
|
||||
//serialization.cpp
|
||||
@@ -47,4 +50,5 @@ extern System system;
|
||||
|
||||
auto Region::NTSCJ() -> bool { return system.region() == System::Region::NTSCJ; }
|
||||
auto Region::NTSCU() -> bool { return system.region() == System::Region::NTSCU; }
|
||||
auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }
|
||||
auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }
|
||||
auto Region::Dendy() -> bool { return system.region() == System::Region::Dendy; }
|
||||
@@ -36,6 +36,20 @@ ares_add_sources(
|
||||
apu/wave.cpp
|
||||
)
|
||||
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
gb
|
||||
INCLUDED #
|
||||
ppu/cgb.cpp
|
||||
ppu/color.cpp
|
||||
ppu/debugger.cpp
|
||||
ppu/dmg.cpp
|
||||
ppu/io.cpp
|
||||
ppu/ppu.hpp
|
||||
ppu/serialization.cpp
|
||||
ppu/timing.cpp
|
||||
)
|
||||
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
gb
|
||||
|
||||
+1
-1
@@ -142,7 +142,7 @@ auto APU::readIO(n32 address) -> n8 {
|
||||
|
||||
}
|
||||
|
||||
return cpu.openBus.get(Byte, address);
|
||||
return cpu.mdr >> (8 * (address & 3));
|
||||
}
|
||||
|
||||
auto APU::writeIO(n32 address, n8 data) -> void {
|
||||
|
||||
@@ -57,7 +57,7 @@ auto APU::serialize(serializer& s) -> void {
|
||||
s(wave.frequency);
|
||||
s(wave.counter);
|
||||
s(wave.initialize);
|
||||
s(wave.pattern);
|
||||
for(auto& p : wave.pattern) s(p);
|
||||
s(wave.enable);
|
||||
s(wave.output);
|
||||
s(wave.patternaddr);
|
||||
@@ -85,8 +85,8 @@ auto APU::serialize(serializer& s) -> void {
|
||||
s(sequencer.volume);
|
||||
s(sequencer.lvolume);
|
||||
s(sequencer.rvolume);
|
||||
s(sequencer.lenable);
|
||||
s(sequencer.renable);
|
||||
for(auto& l :sequencer.lenable) s(l);
|
||||
for(auto& r: sequencer.renable) s(r);
|
||||
s(sequencer.masterenable);
|
||||
s(sequencer.base);
|
||||
s(sequencer.step);
|
||||
@@ -97,7 +97,7 @@ auto APU::serialize(serializer& s) -> void {
|
||||
|
||||
for(auto& f : fifo) {
|
||||
s(f.buffer);
|
||||
s(f.samples);
|
||||
for(auto& sa : f.samples) s(sa);
|
||||
s(f.active);
|
||||
s(f.output);
|
||||
s(f.rdoffset);
|
||||
|
||||
@@ -66,7 +66,7 @@ auto APU::Wave::write(u32 address, n8 byte) -> void {
|
||||
}
|
||||
|
||||
auto APU::Wave::freeBank() const -> n1 {
|
||||
return bank && apu.sequencer.masterenable;
|
||||
return !(bank && apu.sequencer.masterenable);
|
||||
}
|
||||
|
||||
auto APU::Wave::readRAM(u32 address) const -> n8 {
|
||||
|
||||
@@ -12,6 +12,7 @@ auto Cartridge::MROM::serialize(serializer& s) -> void {
|
||||
s(mask);
|
||||
s(pageAddr);
|
||||
s(burst);
|
||||
s(mirror);
|
||||
}
|
||||
|
||||
auto Cartridge::SRAM::serialize(serializer& s) -> void {
|
||||
|
||||
+31
-53
@@ -12,7 +12,7 @@ inline auto CPU::getBus(u32 mode, n32 address) -> n32 {
|
||||
ARM7TDMI::irq = irq.synchronizer;
|
||||
context.romAccess = false;
|
||||
}
|
||||
u32 word;
|
||||
u32 word = mdr;
|
||||
|
||||
if(memory.biosSwap && address < 0x0400'0000) address ^= 0x0200'0000;
|
||||
|
||||
@@ -23,33 +23,36 @@ inline auto CPU::getBus(u32 mode, n32 address) -> n32 {
|
||||
word = bios.read(mode, address);
|
||||
break;
|
||||
|
||||
case 0x02: word = readEWRAM<UseDebugger>(mode, address); break;
|
||||
case 0x02:
|
||||
if(memory.ewram) word = readEWRAM<UseDebugger>(mode, address);
|
||||
else word = readIWRAM<UseDebugger>(mode, address);
|
||||
break;
|
||||
|
||||
case 0x03: word = readIWRAM<UseDebugger>(mode, address); break;
|
||||
|
||||
case 0x04:
|
||||
if constexpr(!UseDebugger) prefetchStep(1);
|
||||
if((address & 0xffff'fc00) == 0x0400'0000) word = bus.io[address & 0x3ff]->readIO(mode, address);
|
||||
else if((address & 0xff00'fffc) == 0x0400'0800) word = ((IO*)this)->readIO(mode, 0x0400'0800 | (address & 3));
|
||||
else return openBus.get(mode, address);
|
||||
break;
|
||||
|
||||
case 0x05: word = readPRAM<UseDebugger>(mode, address); break;
|
||||
case 0x06: word = readVRAM<UseDebugger>(mode, address); break;
|
||||
|
||||
case 0x07:
|
||||
if constexpr(!UseDebugger) prefetchStep(1);
|
||||
word = ppu.readOAM(mode, address);
|
||||
break;
|
||||
case 0x07: word = readOAM<UseDebugger>(mode, address); break;
|
||||
|
||||
case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d:
|
||||
if constexpr(UseDebugger) return readROM<true>(mode, address);
|
||||
context.burstActive = checkBurst<IsDMA>(mode);
|
||||
context.romAccess = true;
|
||||
if(mode & Prefetch && wait.prefetch) {
|
||||
if(address == prefetch.addr && (!prefetch.empty() || prefetch.ahead)) {
|
||||
prefetchStepInternal(1);
|
||||
if(mode & Prefetch) {
|
||||
if((address & 0x1fffe) && wait.prefetch && address == prefetch.addr && (!prefetch.empty() || prefetch.ahead)) {
|
||||
prefetchStep(1);
|
||||
word = prefetchRead();
|
||||
if(mode & Word) word |= prefetchRead() << 16;
|
||||
if(mode & Word) {
|
||||
word |= prefetchRead() << 16;
|
||||
} else {
|
||||
word |= word << 16;
|
||||
}
|
||||
} else {
|
||||
if(mode & Word) address &= ~3; //prevents misaligned PC from reading incorrect values
|
||||
prefetchSync(mode, address);
|
||||
@@ -65,19 +68,22 @@ inline auto CPU::getBus(u32 mode, n32 address) -> n32 {
|
||||
case 0x0e: case 0x0f:
|
||||
if constexpr(!UseDebugger) prefetchReset();
|
||||
if constexpr(!UseDebugger) step(waitCartridge(address, false));
|
||||
word = cartridge.readBackup(address) * 0x01010101;
|
||||
word = cartridge.readBackup(address);
|
||||
word |= word << 8;
|
||||
word |= word << 16;
|
||||
break;
|
||||
|
||||
default:
|
||||
if constexpr(!UseDebugger) prefetchStep(1);
|
||||
return openBus.get(mode, address);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
openBus.set(mode, address, word);
|
||||
|
||||
if(auto result = platform->cheat(address)) return *result;
|
||||
return word;
|
||||
if constexpr(!UseDebugger) mdr = word;
|
||||
if(mode & Word) address &= ~3;
|
||||
if(mode & Half) address &= ~1;
|
||||
return word >> (8 * (address & 3));
|
||||
}
|
||||
|
||||
auto CPU::get(u32 mode, n32 address) -> n32 {
|
||||
@@ -102,6 +108,8 @@ auto CPU::setBus(u32 mode, n32 address, n32 word) -> void {
|
||||
|
||||
if(memory.biosSwap && address < 0x0400'0000) address ^= 0x0200'0000;
|
||||
|
||||
mdr = word;
|
||||
|
||||
switch(address >> 24) {
|
||||
|
||||
case 0x00: case 0x01:
|
||||
@@ -109,7 +117,11 @@ auto CPU::setBus(u32 mode, n32 address, n32 word) -> void {
|
||||
bios.write(mode, address, word);
|
||||
break;
|
||||
|
||||
case 0x02: writeEWRAM(mode, address, word); break;
|
||||
case 0x02:
|
||||
if(memory.ewram) writeEWRAM(mode, address, word);
|
||||
else writeIWRAM(mode, address, word);
|
||||
break;
|
||||
|
||||
case 0x03: writeIWRAM(mode, address, word); break;
|
||||
|
||||
case 0x04:
|
||||
@@ -120,12 +132,7 @@ auto CPU::setBus(u32 mode, n32 address, n32 word) -> void {
|
||||
|
||||
case 0x05: writePRAM(mode, address, word); break;
|
||||
case 0x06: writeVRAM(mode, address, word); break;
|
||||
|
||||
case 0x07:
|
||||
prefetchStep(1);
|
||||
synchronize(ppu);
|
||||
ppu.writeOAM(mode, address, word);
|
||||
break;
|
||||
case 0x07: writeOAM(mode, address, word); break;
|
||||
|
||||
case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d:
|
||||
context.burstActive = checkBurst<IsDMA>(mode);
|
||||
@@ -149,7 +156,6 @@ auto CPU::setBus(u32 mode, n32 address, n32 word) -> void {
|
||||
|
||||
}
|
||||
|
||||
openBus.set(mode, address, word);
|
||||
if constexpr(!IsDMA) {
|
||||
if(!context.romAccess) cartridge.mrom.burst = false;
|
||||
}
|
||||
@@ -195,31 +201,3 @@ auto CPU::checkBurst(u32 mode) -> bool {
|
||||
if constexpr(IsDMA) return dmac.romBurst;
|
||||
return !ARM7TDMI::nonsequential;
|
||||
}
|
||||
|
||||
auto CPU::OpenBus::get(u32 mode, n32 address) -> n32 {
|
||||
if(mode & Word) address &= ~3;
|
||||
if(mode & Half) address &= ~1;
|
||||
return data >> (8 * (address & 3));
|
||||
}
|
||||
|
||||
auto CPU::OpenBus::set(u32 mode, n32 address, n32 word) -> void {
|
||||
if(address >> 24 == 0x3) {
|
||||
//open bus from IWRAM only overwrites part of the last IWRAM value accessed
|
||||
if(mode & Word) {
|
||||
iwramData = word;
|
||||
} else if(mode & Half) {
|
||||
if(address & 2) {
|
||||
iwramData.bit(16,31) = (n16)word;
|
||||
} else {
|
||||
iwramData.bit( 0,15) = (n16)word;
|
||||
}
|
||||
} else if(mode & Byte) {
|
||||
iwramData.byte(address & 3) = (n8)word;
|
||||
}
|
||||
data = iwramData;
|
||||
} else {
|
||||
if(mode & Byte) word = (word & 0xff) * 0x01010101;
|
||||
if(mode & Half) word = (word & 0xffff) * 0x00010001;
|
||||
data = word;
|
||||
}
|
||||
}
|
||||
@@ -12,5 +12,5 @@ auto CPU::Coprocessor::debugMCR() -> void {
|
||||
}
|
||||
|
||||
auto CPU::Coprocessor::debugMRC() -> n32 {
|
||||
return cpu.openBus.get(Word, 0);
|
||||
return cpu.mdr;
|
||||
}
|
||||
@@ -53,7 +53,6 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
|
||||
//prefetch.cpp
|
||||
auto prefetchSync(u32 mode, n32 address) -> void;
|
||||
auto prefetchStepInternal(u32 clocks) -> void;
|
||||
auto prefetchStep(u32 clocks) -> void;
|
||||
auto prefetchReset() -> void;
|
||||
auto prefetchRead() -> n16;
|
||||
@@ -88,6 +87,9 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
template<bool UseDebugger> auto readVRAM(u32 mode, n32 address) -> n32;
|
||||
auto writeVRAM(u32 mode, n32 address, n32 word) -> void;
|
||||
|
||||
template<bool UseDebugger> auto readOAM(u32 mode, n32 address) -> n32;
|
||||
auto writeOAM(u32 mode, n32 address, n32 word) -> void;
|
||||
|
||||
template<bool UseDebugger> auto readROM(u32 mode, n32 address) -> n32;
|
||||
auto writeROM(u32 mode, n32 address, n32 word) -> void;
|
||||
|
||||
@@ -202,8 +204,10 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
n1 enable;
|
||||
n1 condition;
|
||||
n1 flag[10];
|
||||
n1 keyStates[10];
|
||||
|
||||
n1 conditionMet;
|
||||
n1 irq;
|
||||
} keypad;
|
||||
|
||||
struct Joybus {
|
||||
@@ -255,17 +259,9 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
n4 unknown2;
|
||||
} memory;
|
||||
|
||||
struct OpenBus {
|
||||
auto get(u32 mode, n32 address) -> n32;
|
||||
auto set(u32 mode, n32 address, n32 word) -> void;
|
||||
n32 data;
|
||||
n32 iwramData;
|
||||
} openBus;
|
||||
|
||||
struct {
|
||||
auto empty() const { return addr == load; }
|
||||
auto full() const { return load - addr == 16; }
|
||||
auto size() const { return (load - addr) >> 1; }
|
||||
|
||||
n16 slot[8];
|
||||
n32 addr; //read location of slot buffer
|
||||
@@ -293,6 +289,9 @@ struct CPU : ARM7TDMI, Thread, IO {
|
||||
n1 burstActive;
|
||||
n32 hcounter;
|
||||
} context;
|
||||
|
||||
n32 iwramBus;
|
||||
n32 mdr;
|
||||
};
|
||||
|
||||
extern CPU cpu;
|
||||
@@ -59,7 +59,10 @@ auto CPU::DMAC::Channel::read() -> void {
|
||||
if(mode & Word) addr &= ~3;
|
||||
if(mode & Half) addr &= ~1;
|
||||
latch.data = cpu.getDMA(mode, addr);
|
||||
if(mode & Half) latch.data |= latch.data << 16;
|
||||
if(mode & Half) {
|
||||
latch.data &= 0xffff;
|
||||
latch.data |= latch.data << 16;
|
||||
}
|
||||
}
|
||||
|
||||
switch(sourceMode) {
|
||||
@@ -93,8 +96,11 @@ auto CPU::DMAC::Channel::write() -> void {
|
||||
if(!latch.length()) {
|
||||
active = false;
|
||||
if(targetMode == 3) latch.target = target;
|
||||
if(repeat == 1) latch.length = length;
|
||||
if(repeat == 0) enable = false;
|
||||
if(repeat == 0 || timingMode == 0) {
|
||||
enable = false;
|
||||
} else {
|
||||
latch.length = length;
|
||||
}
|
||||
}
|
||||
|
||||
cpu.dmac.writeCycle = false;
|
||||
|
||||
+1
-1
@@ -245,7 +245,7 @@ auto CPU::readIO(n32 address) -> n8 {
|
||||
|
||||
}
|
||||
|
||||
return cpu.openBus.get(Byte, address);
|
||||
return mdr >> (8 * (address & 3));
|
||||
}
|
||||
|
||||
auto CPU::writeIO(n32 address, n8 data) -> void {
|
||||
|
||||
@@ -14,11 +14,14 @@ auto CPU::Keypad::run() -> void {
|
||||
|
||||
conditionMet = condition; //0 = OR, 1 = AND
|
||||
for(u32 index : range(10)) {
|
||||
if(lookup[index] != keyStates[index]) irq = false; //force IRQ line low temporarily when key state changes
|
||||
keyStates[index] = lookup[index];
|
||||
if(!flag[index]) continue;
|
||||
n1 input = lookup[index];
|
||||
if(condition == 0) conditionMet |= input;
|
||||
if(condition == 1) conditionMet &= input;
|
||||
}
|
||||
|
||||
if(conditionMet && enable) cpu.setInterruptFlag(CPU::Interrupt::Keypad);
|
||||
bool irqPrev = irq;
|
||||
irq = conditionMet && enable;
|
||||
if(irq && !irqPrev) cpu.setInterruptFlag(CPU::Interrupt::Keypad);
|
||||
}
|
||||
+56
-27
@@ -1,25 +1,33 @@
|
||||
template <bool UseDebugger>
|
||||
auto CPU::readIWRAM(u32 mode, n32 address) -> n32 {
|
||||
if constexpr(!UseDebugger) prefetchStep(1);
|
||||
n32 word;
|
||||
if constexpr(UseDebugger) {
|
||||
address &= 0x7ffc;
|
||||
return iwram[address + 0] << 0 | iwram[address + 1] << 8 | iwram[address + 2] << 16 | iwram[address + 3] << 24;
|
||||
}
|
||||
prefetchStep(1);
|
||||
if(mode & Word) {
|
||||
address &= 0x7ffc;
|
||||
word = iwram[address + 0] << 0 | iwram[address + 1] << 8 | iwram[address + 2] << 16 | iwram[address + 3] << 24;
|
||||
iwramBus = iwram[address + 0] << 0 | iwram[address + 1] << 8 | iwram[address + 2] << 16 | iwram[address + 3] << 24;
|
||||
} else if(mode & Half) {
|
||||
address &= 0x7ffe;
|
||||
word = iwram[address + 0] << 0 | iwram[address + 1] << 8;
|
||||
n16 half = iwram[address + 0] << 0 | iwram[address + 1] << 8;
|
||||
if(address & 2) {
|
||||
iwramBus.bit(16,31) = half;
|
||||
} else {
|
||||
iwramBus.bit( 0,15) = half;
|
||||
}
|
||||
} else {
|
||||
address &= 0x7fff;
|
||||
word = iwram[address];
|
||||
iwramBus.byte(address & 3) = iwram[address];
|
||||
}
|
||||
|
||||
return word;
|
||||
return iwramBus;
|
||||
}
|
||||
|
||||
auto CPU::writeIWRAM(u32 mode, n32 address, n32 word) -> void {
|
||||
prefetchStep(1);
|
||||
if(mode & Word) {
|
||||
address &= 0x7ffc;
|
||||
iwramBus = word;
|
||||
iwram[address + 0] = word >> 0;
|
||||
iwram[address + 1] = word >> 8;
|
||||
iwram[address + 2] = word >> 16;
|
||||
@@ -27,33 +35,31 @@ auto CPU::writeIWRAM(u32 mode, n32 address, n32 word) -> void {
|
||||
return;
|
||||
} else if(mode & Half) {
|
||||
address &= 0x7ffe;
|
||||
if(address & 2) {
|
||||
iwramBus.bit(16,31) = word;
|
||||
} else {
|
||||
iwramBus.bit( 0,15) = word;
|
||||
}
|
||||
iwram[address + 0] = word >> 0;
|
||||
iwram[address + 1] = word >> 8;
|
||||
} else {
|
||||
address &= 0x7fff;
|
||||
iwramBus.byte(address & 3) = word;
|
||||
iwram[address] = word;
|
||||
}
|
||||
}
|
||||
|
||||
template <bool UseDebugger>
|
||||
auto CPU::readEWRAM(u32 mode, n32 address) -> n32 {
|
||||
if(!memory.ewram) return readIWRAM<UseDebugger>(mode, address);
|
||||
if(mode & Word) return readEWRAM<UseDebugger>(Half, address &~ 2) << 0 | readEWRAM<UseDebugger>(Half, address | 2) << 16;
|
||||
if(mode & Word) return (n16)readEWRAM<UseDebugger>(Half, address & ~2) << 0 | (n16)readEWRAM<UseDebugger>(Half, address | 2) << 16;
|
||||
|
||||
if constexpr(!UseDebugger) prefetchStep(16 - memory.ewramWait);
|
||||
n16 half;
|
||||
address &= 0x3ffff;
|
||||
if(mode & Half) {
|
||||
half = ewram[address & ~1] << 0 | ewram[address | 1] << 8;
|
||||
} else {
|
||||
half = ewram[address];
|
||||
}
|
||||
|
||||
return half;
|
||||
n16 half = ewram[address & ~1] << 0 | ewram[address | 1] << 8;
|
||||
return half << 0 | half << 16;
|
||||
}
|
||||
|
||||
auto CPU::writeEWRAM(u32 mode, n32 address, n32 word) -> void {
|
||||
if(!memory.ewram) return writeIWRAM(mode, address, word);
|
||||
if(mode & Word) {
|
||||
writeEWRAM(Half, address &~2, word >> 0);
|
||||
writeEWRAM(Half, address | 2, word >> 16);
|
||||
@@ -72,7 +78,7 @@ auto CPU::writeEWRAM(u32 mode, n32 address, n32 word) -> void {
|
||||
|
||||
template <bool UseDebugger>
|
||||
auto CPU::readPRAM(u32 mode, n32 address) -> n32 {
|
||||
if(mode & Word) return readPRAM<UseDebugger>(Half, address & ~2) << 0 | readPRAM<UseDebugger>(Half, address | 2) << 16;
|
||||
if(mode & Word) return (n16)readPRAM<UseDebugger>(Half, address & ~2) << 0 | (n16)readPRAM<UseDebugger>(Half, address | 2) << 16;
|
||||
|
||||
//stall until PPU is no longer accessing PRAM (minimum 1 cycle)
|
||||
if constexpr(!UseDebugger) {
|
||||
@@ -82,7 +88,8 @@ auto CPU::readPRAM(u32 mode, n32 address) -> n32 {
|
||||
} while(ppu.pramContention());
|
||||
}
|
||||
|
||||
return ppu.readPRAM(mode, address);
|
||||
n16 half = ppu.readPRAM(mode, address);
|
||||
return half << 0 | half << 16;
|
||||
}
|
||||
|
||||
auto CPU::writePRAM(u32 mode, n32 address, n32 word) -> void {
|
||||
@@ -103,7 +110,7 @@ auto CPU::writePRAM(u32 mode, n32 address, n32 word) -> void {
|
||||
|
||||
template <bool UseDebugger>
|
||||
auto CPU::readVRAM(u32 mode, n32 address) -> n32 {
|
||||
if(mode & Word) return readVRAM<UseDebugger>(Half, address & ~2) << 0 | readVRAM<UseDebugger>(Half, address | 2) << 16;
|
||||
if(mode & Word) return (n16)readVRAM<UseDebugger>(Half, address & ~2) << 0 | (n16)readVRAM<UseDebugger>(Half, address | 2) << 16;
|
||||
|
||||
//stall until PPU is no longer accessing VRAM (minimum 1 cycle)
|
||||
if constexpr(!UseDebugger) {
|
||||
@@ -113,7 +120,8 @@ auto CPU::readVRAM(u32 mode, n32 address) -> n32 {
|
||||
} while(ppu.vramContention(address));
|
||||
}
|
||||
|
||||
return ppu.readVRAM(mode, address);
|
||||
n16 half = ppu.readVRAM(mode, address);
|
||||
return half << 0 | half << 16;
|
||||
}
|
||||
|
||||
auto CPU::writeVRAM(u32 mode, n32 address, n32 word) -> void {
|
||||
@@ -132,12 +140,35 @@ auto CPU::writeVRAM(u32 mode, n32 address, n32 word) -> void {
|
||||
ppu.writeVRAM(mode, address, word);
|
||||
}
|
||||
|
||||
template <bool UseDebugger>
|
||||
auto CPU::readOAM(u32 mode, n32 address) -> n32 {
|
||||
//stall until PPU is no longer accessing OAM (minimum 1 cycle)
|
||||
if constexpr(!UseDebugger) {
|
||||
do {
|
||||
prefetchStep(1);
|
||||
synchronize(ppu);
|
||||
} while(ppu.oamContention());
|
||||
}
|
||||
|
||||
return ppu.readOAM(mode, address);
|
||||
}
|
||||
|
||||
auto CPU::writeOAM(u32 mode, n32 address, n32 word) -> void {
|
||||
//stall until PPU is no longer accessing OAM (minimum 1 cycle)
|
||||
do {
|
||||
prefetchStep(1);
|
||||
synchronize(ppu);
|
||||
} while(ppu.oamContention());
|
||||
|
||||
ppu.writeOAM(mode, address, word);
|
||||
}
|
||||
|
||||
template<bool UseDebugger>
|
||||
auto CPU::readROM(u32 mode, n32 address) -> n32 {
|
||||
if(mode & Word) {
|
||||
n32 word = 0;
|
||||
word |= readROM<UseDebugger>(Half, address & ~2) << 0;
|
||||
word |= readROM<UseDebugger>(Half, address | 2) << 16;
|
||||
word |= (n16)readROM<UseDebugger>(Half, address & ~2) << 0;
|
||||
word |= (n16)readROM<UseDebugger>(Half, address | 2) << 16;
|
||||
return word;
|
||||
}
|
||||
|
||||
@@ -147,9 +178,7 @@ auto CPU::readROM(u32 mode, n32 address) -> n32 {
|
||||
}
|
||||
n16 half = cartridge.readRom<UseDebugger>(address);
|
||||
if constexpr(!UseDebugger) context.burstActive = true;
|
||||
|
||||
if(mode & Byte) return half.byte(address & 1);
|
||||
return half;
|
||||
return half << 0 | half << 16;
|
||||
}
|
||||
|
||||
auto CPU::writeROM(u32 mode, n32 address, n32 word) -> void {
|
||||
|
||||
@@ -9,7 +9,7 @@ auto CPU::prefetchSync(u32 mode, n32 address) -> void {
|
||||
prefetch.wait = waitCartridge(prefetch.load, true);
|
||||
}
|
||||
|
||||
auto CPU::prefetchStepInternal(u32 clocks) -> void {
|
||||
auto CPU::prefetchStep(u32 clocks) -> void {
|
||||
step(clocks);
|
||||
if(!wait.prefetch || prefetch.stopped) return;
|
||||
|
||||
@@ -19,19 +19,17 @@ auto CPU::prefetchStepInternal(u32 clocks) -> void {
|
||||
prefetch.ahead = false;
|
||||
break;
|
||||
}
|
||||
prefetch.ahead = true;
|
||||
if(--prefetch.wait) continue;
|
||||
cartridge.startBurst(prefetch.load); //todo: only start burst when necessary
|
||||
prefetch.slot[prefetch.load >> 1 & 7] = cartridge.readRom<false>(prefetch.load);
|
||||
prefetch.load += 2;
|
||||
if(prefetch.load & 0x1fffe) {
|
||||
cartridge.startBurst(prefetch.load); //todo: only start burst when necessary
|
||||
prefetch.slot[prefetch.load >> 1 & 7] = cartridge.readRom<false>(prefetch.load);
|
||||
prefetch.load += 2;
|
||||
}
|
||||
prefetch.wait = waitCartridge(prefetch.load, true);
|
||||
}
|
||||
}
|
||||
|
||||
auto CPU::prefetchStep(u32 clocks) -> void {
|
||||
if(wait.prefetch && !prefetch.stopped && prefetch.empty()) prefetch.ahead = true;
|
||||
prefetchStepInternal(clocks);
|
||||
}
|
||||
|
||||
auto CPU::prefetchReset() -> void {
|
||||
if(prefetch.wait == 1) step(1);
|
||||
|
||||
@@ -47,7 +45,7 @@ auto CPU::prefetchRead() -> n16 {
|
||||
prefetch.stopped = false;
|
||||
prefetch.wait = waitCartridge(prefetch.load, false);
|
||||
}
|
||||
if(prefetch.empty()) prefetchStepInternal(prefetch.wait);
|
||||
if(prefetch.empty()) prefetchStep(prefetch.wait);
|
||||
|
||||
n16 word = prefetch.slot[prefetch.addr >> 1 & 7];
|
||||
prefetch.addr += 2;
|
||||
|
||||
@@ -66,7 +66,9 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
s(keypad.enable);
|
||||
s(keypad.condition);
|
||||
for(auto& flag : keypad.flag) s(flag);
|
||||
for(auto& keyState : keypad.keyStates) s(keyState);
|
||||
s(keypad.conditionMet);
|
||||
s(keypad.irq);
|
||||
|
||||
s(joybus.sc);
|
||||
s(joybus.sd);
|
||||
@@ -106,10 +108,7 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
s(memory.ewramWait);
|
||||
s(memory.unknown2);
|
||||
|
||||
s(openBus.data);
|
||||
s(openBus.iwramData);
|
||||
|
||||
s(prefetch.slot);
|
||||
for(auto& slot : prefetch.slot) s(slot);
|
||||
s(prefetch.addr);
|
||||
s(prefetch.load);
|
||||
s(prefetch.wait);
|
||||
@@ -125,4 +124,7 @@ auto CPU::serialize(serializer& s) -> void {
|
||||
s(context.busLocked);
|
||||
s(context.burstActive);
|
||||
s(context.hcounter);
|
||||
|
||||
s(iwramBus);
|
||||
s(mdr);
|
||||
}
|
||||
@@ -20,7 +20,7 @@ auto Display::readIO(n32 address) -> n8 {
|
||||
|
||||
}
|
||||
|
||||
return cpu.openBus.get(Byte, address);
|
||||
return cpu.mdr >> (8 * (address & 3));
|
||||
}
|
||||
|
||||
auto Display::writeIO(n32 address, n8 data) -> void {
|
||||
|
||||
@@ -17,8 +17,11 @@ auto IO::readIO(u32 mode, n32 address) -> n32 {
|
||||
address &= ~1;
|
||||
word.byte(0) = readIO(address + 0);
|
||||
word.byte(1) = readIO(address + 1);
|
||||
word |= word << 16;
|
||||
} else if(mode & Byte) {
|
||||
word.byte(0) = readIO(address + 0);
|
||||
word = readIO(address + 0);
|
||||
word |= word << 8;
|
||||
word |= word << 16;
|
||||
}
|
||||
|
||||
return word;
|
||||
@@ -42,7 +45,7 @@ auto IO::writeIO(u32 mode, n32 address, n32 word) -> void {
|
||||
|
||||
struct UnmappedIO : IO {
|
||||
auto readIO(n32 address) -> n8 override {
|
||||
return cpu.openBus.get(Byte, address);
|
||||
return cpu.mdr >> (8 * (address & 3));
|
||||
}
|
||||
|
||||
auto writeIO(n32 address, n8 byte) -> void override {
|
||||
@@ -51,24 +54,6 @@ struct UnmappedIO : IO {
|
||||
|
||||
static UnmappedIO unmappedIO;
|
||||
|
||||
auto Bus::mirror(n32 address, n32 size) -> n32 {
|
||||
n32 base = 0;
|
||||
if(size) {
|
||||
n32 mask = 1 << 27; //28-bit bus
|
||||
while(address >= size) {
|
||||
while(!(address & mask)) mask >>= 1;
|
||||
address -= mask;
|
||||
if(size > mask) {
|
||||
size -= mask;
|
||||
base += mask;
|
||||
}
|
||||
mask >>= 1;
|
||||
}
|
||||
base += address;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
auto Bus::power() -> void {
|
||||
for(u32 n : range(0x400)) io[n] = &unmappedIO;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ struct IO {
|
||||
};
|
||||
|
||||
struct Bus {
|
||||
static auto mirror(n32 address, n32 size) -> n32;
|
||||
|
||||
auto power() -> void;
|
||||
|
||||
IO* io[0x400] = {nullptr};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
auto Player::serialize(serializer& s) -> void {
|
||||
Thread::serialize(s);
|
||||
|
||||
s(status.enable);
|
||||
s(status.rumble);
|
||||
|
||||
|
||||
+17
-32
@@ -13,13 +13,22 @@ auto PPU::Background::scanline(u32 y) -> void {
|
||||
mosaicOffset = 0;
|
||||
for(auto& pixel : output) pixel = {};
|
||||
latch.active = false;
|
||||
if(!io.mosaic || (y % (1 + io.mosaicHeight)) == 0) {
|
||||
vmosaic = y;
|
||||
affine.hmosaic = io.lx;
|
||||
affine.vmosaic = io.ly;
|
||||
io.lx += io.pb;
|
||||
io.ly += io.pd;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::Background::outputPixel(u32 x, u32 y) -> void {
|
||||
//horizontal mosaic
|
||||
if(!io.mosaic || !mosaicOffset) {
|
||||
if(!mosaicOffset) {
|
||||
mosaicOffset = 1 + io.mosaicWidth;
|
||||
mosaic = output[x];
|
||||
mosaicLatch = output[x];
|
||||
} else if(!io.mosaic) {
|
||||
mosaicLatch = output[x];
|
||||
}
|
||||
mosaicOffset--;
|
||||
}
|
||||
@@ -50,12 +59,6 @@ auto PPU::Background::run(s32 x, u32 y) -> void {
|
||||
auto PPU::Background::linearFetchTileMap(s32 x, u32 y) -> void {
|
||||
if(ppu.blank() || !io.enable[0]) return;
|
||||
|
||||
if(x == -7) {
|
||||
if(!io.mosaic || (y % (1 + io.mosaicHeight)) == 0) {
|
||||
vmosaic = y;
|
||||
}
|
||||
}
|
||||
|
||||
fx = x + io.hoffset;
|
||||
fy = vmosaic + io.voffset;
|
||||
|
||||
@@ -128,12 +131,8 @@ auto PPU::Background::affineFetchTileMap(u32 x, u32 y) -> void {
|
||||
if(ppu.blank() || !io.enable[0]) return;
|
||||
|
||||
if(x == 0) {
|
||||
if(!io.mosaic || (y % (1 + io.mosaicHeight)) == 0) {
|
||||
hmosaic = io.lx;
|
||||
vmosaic = io.ly;
|
||||
}
|
||||
fx = hmosaic;
|
||||
fy = vmosaic;
|
||||
fx = affine.hmosaic;
|
||||
fy = affine.vmosaic;
|
||||
}
|
||||
|
||||
affine.screenSize = 16 << io.screenSize;
|
||||
@@ -167,23 +166,14 @@ auto PPU::Background::affineFetchTileData(u32 x, u32 y) -> void {
|
||||
|
||||
fx += io.pa;
|
||||
fy += io.pc;
|
||||
|
||||
if(x == 239) {
|
||||
io.lx += io.pb;
|
||||
io.ly += io.pd;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::Background::bitmap(u32 x, u32 y) -> void {
|
||||
if(ppu.blank() || !io.enable[0]) return;
|
||||
|
||||
if(x == 0) {
|
||||
if(!io.mosaic || (y % (1 + io.mosaicHeight)) == 0) {
|
||||
hmosaic = io.lx;
|
||||
vmosaic = io.ly;
|
||||
}
|
||||
fx = hmosaic;
|
||||
fy = vmosaic;
|
||||
fx = affine.hmosaic;
|
||||
fy = affine.vmosaic;
|
||||
}
|
||||
|
||||
n1 depth = io.mode != 4; //0 = 8-bit (mode 4); 1 = 15-bit (mode 3, mode 5)
|
||||
@@ -212,11 +202,6 @@ auto PPU::Background::bitmap(u32 x, u32 y) -> void {
|
||||
|
||||
fx += io.pa;
|
||||
fy += io.pc;
|
||||
|
||||
if(x == 239) {
|
||||
io.lx += io.pb;
|
||||
io.ly += io.pd;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::Background::power(u32 id) -> void {
|
||||
@@ -224,10 +209,10 @@ auto PPU::Background::power(u32 id) -> void {
|
||||
|
||||
io = {};
|
||||
latch = {};
|
||||
affine = {};
|
||||
for(auto& pixel : output) pixel = {};
|
||||
mosaic = {};
|
||||
mosaicLatch = {};
|
||||
mosaicOffset = 0;
|
||||
hmosaic = 0;
|
||||
vmosaic = 0;
|
||||
fx = 0;
|
||||
fy = 0;
|
||||
|
||||
@@ -13,17 +13,17 @@ auto PPU::DAC::upperLayer(u32 x, u32 y) -> void {
|
||||
n1 active[6] = {true, true, true, true, true, true}; //enable all layers if no windows are enabled
|
||||
if(ppu.window0.io.enable || ppu.window1.io.enable || ppu.window2.io.enable) {
|
||||
memory::copy(&active, &ppu.window3.io.active, sizeof(active));
|
||||
if(ppu.window2.io.enable && ppu.window2.output) memory::copy(&active, &ppu.window2.io.active, sizeof(active));
|
||||
if(ppu.window1.io.enable && ppu.window1.output) memory::copy(&active, &ppu.window1.io.active, sizeof(active));
|
||||
if(ppu.window0.io.enable && ppu.window0.output) memory::copy(&active, &ppu.window0.io.active, sizeof(active));
|
||||
if(ppu.window2.io.enable && ppu.window2.output[x]) memory::copy(&active, &ppu.window2.io.active, sizeof(active));
|
||||
if(ppu.window1.io.enable && ppu.window1.output[x]) memory::copy(&active, &ppu.window1.io.active, sizeof(active));
|
||||
if(ppu.window0.io.enable && ppu.window0.output[x]) memory::copy(&active, &ppu.window0.io.active, sizeof(active));
|
||||
}
|
||||
|
||||
//priority sorting: find topmost two pixels
|
||||
layers[OBJ] = ppu.objects.mosaic;
|
||||
layers[BG0] = ppu.bg0.mosaic;
|
||||
layers[BG1] = ppu.bg1.mosaic;
|
||||
layers[BG2] = ppu.bg2.mosaic;
|
||||
layers[BG3] = ppu.bg3.mosaic;
|
||||
layers[OBJ] = ppu.objects.mosaicLatch;
|
||||
layers[BG0] = ppu.bg0.mosaicLatch;
|
||||
layers[BG1] = ppu.bg1.mosaicLatch;
|
||||
layers[BG2] = ppu.bg2.mosaicLatch;
|
||||
layers[BG3] = ppu.bg3.mosaicLatch;
|
||||
layers[SFX] = {true, 3, 0};
|
||||
|
||||
aboveLayer = 5;
|
||||
|
||||
@@ -9,7 +9,7 @@ auto PPU::Debugger::load(Node::Object parent) -> void {
|
||||
});
|
||||
|
||||
memory.pram = parent->append<Node::Debugger::Memory>("PPU PRAM");
|
||||
memory.pram->setSize(ppu.pram.size());
|
||||
memory.pram->setSize(ppu.pram.size() * 2);
|
||||
memory.pram->setRead([&](u32 address) -> u8 {
|
||||
return ppu.pram[address >> 1].byte(address & 1);
|
||||
});
|
||||
@@ -17,6 +17,15 @@ auto PPU::Debugger::load(Node::Object parent) -> void {
|
||||
ppu.pram[address >> 1].byte(address & 1) = data;
|
||||
});
|
||||
|
||||
memory.oam = parent->append<Node::Debugger::Memory>("PPU OAM");
|
||||
memory.oam->setSize(ppu.oam.size() * 2);
|
||||
memory.oam->setRead([&](u32 address) -> u8 {
|
||||
return ppu.oam[address >> 1].byte(address & 1);
|
||||
});
|
||||
memory.oam->setWrite([&](u32 address, u8 data) -> void {
|
||||
ppu.oam[address >> 1].byte(address & 1) = data;
|
||||
});
|
||||
|
||||
graphics.tiles4bpp = parent->append<Node::Debugger::Graphics>("4 BPP Tiles");
|
||||
graphics.tiles4bpp->setSize(512, 384);
|
||||
graphics.tiles4bpp->setCapture([&]() -> std::vector<u32> {
|
||||
@@ -134,6 +143,7 @@ auto PPU::Debugger::load(Node::Object parent) -> void {
|
||||
auto PPU::Debugger::unload(Node::Object parent) -> void {
|
||||
parent->remove(memory.vram);
|
||||
parent->remove(memory.pram);
|
||||
parent->remove(memory.oam);
|
||||
parent->remove(graphics.tiles4bpp);
|
||||
parent->remove(graphics.tiles8bpp);
|
||||
parent->remove(graphics.mode3);
|
||||
@@ -141,6 +151,7 @@ auto PPU::Debugger::unload(Node::Object parent) -> void {
|
||||
parent->remove(graphics.mode5);
|
||||
memory.vram.reset();
|
||||
memory.pram.reset();
|
||||
memory.oam.reset();
|
||||
graphics.tiles4bpp.reset();
|
||||
graphics.tiles8bpp.reset();
|
||||
graphics.mode3.reset();
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ auto PPU::readIO(n32 address) -> n8 {
|
||||
|
||||
}
|
||||
|
||||
return cpu.openBus.get(Byte, address);
|
||||
return cpu.mdr >> (8 * (address & 3));
|
||||
}
|
||||
|
||||
auto PPU::writeIO(n32 address, n8 data) -> void {
|
||||
|
||||
+21
-111
@@ -1,8 +1,12 @@
|
||||
auto PPU::releaseBus() -> void {
|
||||
auto PPU::bgReleaseBus() -> void {
|
||||
pramAccessed = false;
|
||||
vramAccessedBG = false;
|
||||
}
|
||||
|
||||
auto PPU::objReleaseBus() -> void {
|
||||
oamAccessed = false;
|
||||
}
|
||||
|
||||
auto PPU::pramContention() -> bool {
|
||||
if(accurate) return pramAccessed;
|
||||
|
||||
@@ -17,34 +21,36 @@ auto PPU::pramContention() -> bool {
|
||||
}
|
||||
|
||||
auto PPU::vramContention(n32 address) -> bool {
|
||||
//todo: implement OBJ VRAM contention
|
||||
address &= 0x1ffff;
|
||||
if(Background::IO::mode < 3 && address >= 0x10000) return false;
|
||||
else if(address >= 0x14000) return false;
|
||||
return vramAccessedBG;
|
||||
}
|
||||
|
||||
auto PPU::oamContention() -> bool {
|
||||
return oamAccessed;
|
||||
}
|
||||
|
||||
auto PPU::readVRAM_BG(u32 mode, n32 address) -> n16 {
|
||||
if(Background::IO::mode < 3 && address >= 0x10000) return 0;
|
||||
else if(address >= 0x14000) return 0;
|
||||
vramAccessedBG = true;
|
||||
|
||||
return readVRAM(mode, address);
|
||||
n16 half = readVRAM(mode, address);
|
||||
if(mode & Byte) {
|
||||
if(address & 1) half >>= 8;
|
||||
half = (n8)half;
|
||||
}
|
||||
return half;
|
||||
}
|
||||
|
||||
auto PPU::readVRAM(u32 mode, n32 address) -> n16 {
|
||||
address &= 0x1ffff;
|
||||
if(Background::IO::mode >= 3 && address < 0x1c000 && address >= 0x18000) return 0;
|
||||
|
||||
address &= (address & 0x10000) ? 0x17fff : 0x0ffff;
|
||||
|
||||
if(mode & Half) {
|
||||
address &= ~1;
|
||||
return vram[address + 0] << 0 | vram[address + 1] << 8;
|
||||
} else if(mode & Byte) {
|
||||
return vram[address];
|
||||
}
|
||||
|
||||
unreachable;
|
||||
address &= (address & 0x10000) ? 0x17ffe : 0x0fffe;
|
||||
return vram[address + 0] << 0 | vram[address + 1] << 8;
|
||||
}
|
||||
|
||||
auto PPU::writeVRAM(u32 mode, n32 address, n16 half) -> void {
|
||||
@@ -69,7 +75,6 @@ auto PPU::writeVRAM(u32 mode, n32 address, n16 half) -> void {
|
||||
}
|
||||
|
||||
auto PPU::readPRAM(u32 mode, n32 address) -> n16 {
|
||||
if(mode & Byte) return readPRAM(Half, address) >> ((address & 1) * 8);
|
||||
return pram[address >> 1 & 511];
|
||||
}
|
||||
|
||||
@@ -83,49 +88,8 @@ auto PPU::writePRAM(u32 mode, n32 address, n16 half) -> void {
|
||||
}
|
||||
|
||||
auto PPU::readOAM(u32 mode, n32 address) -> n32 {
|
||||
if(mode & Word) return readOAM(Half, address & ~2) << 0 | readOAM(Half, address | 2) << 16;
|
||||
if(mode & Byte) return readOAM(Half, address) >> ((address & 1) * 8);
|
||||
|
||||
auto& obj = object[address >> 3 & 127];
|
||||
auto& par = objectParam[address >> 5 & 31];
|
||||
|
||||
switch(address & 6) {
|
||||
|
||||
case 0: return (
|
||||
(obj.y << 0)
|
||||
| (obj.affine << 8)
|
||||
| (obj.affineSize << 9)
|
||||
| (obj.mode << 10)
|
||||
| (obj.mosaic << 12)
|
||||
| (obj.colors << 13)
|
||||
| (obj.shape << 14)
|
||||
);
|
||||
|
||||
case 2: return (
|
||||
(obj.x << 0)
|
||||
| (obj.affineParam << 9)
|
||||
| (obj.hflip << 12)
|
||||
| (obj.vflip << 13)
|
||||
| (obj.size << 14)
|
||||
);
|
||||
|
||||
case 4: return (
|
||||
(obj.character << 0)
|
||||
| (obj.priority << 10)
|
||||
| (obj.palette << 12)
|
||||
);
|
||||
|
||||
case 6:
|
||||
switch(address >> 3 & 3) {
|
||||
case 0: return par.pa;
|
||||
case 1: return par.pb;
|
||||
case 2: return par.pc;
|
||||
case 3: return par.pd;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
unreachable;
|
||||
address = (n10)address;
|
||||
return oam[address >> 1 & ~1] << 0 | oam[address >> 1 | 1] << 16;
|
||||
}
|
||||
|
||||
auto PPU::writeOAM(u32 mode, n32 address, n32 word) -> void {
|
||||
@@ -136,61 +100,7 @@ auto PPU::writeOAM(u32 mode, n32 address, n32 word) -> void {
|
||||
}
|
||||
|
||||
if(mode & Byte) return; //8-bit writes to OAM are ignored
|
||||
|
||||
auto& obj = object[address >> 3 & 127];
|
||||
auto& par = objectParam[address >> 5 & 31];
|
||||
switch(address & 6) {
|
||||
|
||||
case 0:
|
||||
obj.y = word >> 0;
|
||||
obj.affine = word >> 8;
|
||||
obj.affineSize = word >> 9;
|
||||
obj.mode = word >> 10;
|
||||
obj.mosaic = word >> 12;
|
||||
obj.colors = word >> 13;
|
||||
obj.shape = word >> 14;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
obj.x = word >> 0;
|
||||
obj.affineParam = word >> 9;
|
||||
obj.hflip = word >> 12;
|
||||
obj.vflip = word >> 13;
|
||||
obj.size = word >> 14;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
obj.character = word >> 0;
|
||||
obj.priority = word >> 10;
|
||||
obj.palette = word >> 12;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
switch(address >> 3 & 3) {
|
||||
case 0: par.pa = word; break;
|
||||
case 1: par.pb = word; break;
|
||||
case 2: par.pc = word; break;
|
||||
case 3: par.pd = word; break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static u32 widths[] = {
|
||||
8, 16, 32, 64,
|
||||
16, 32, 32, 64,
|
||||
8, 8, 16, 32,
|
||||
8, 8, 8, 8, //invalid modes
|
||||
};
|
||||
|
||||
static u32 heights[] = {
|
||||
8, 16, 32, 64,
|
||||
8, 8, 16, 32,
|
||||
16, 32, 32, 64,
|
||||
8, 8, 8, 8, //invalid modes
|
||||
};
|
||||
|
||||
obj.width = widths [obj.shape * 4 + obj.size];
|
||||
obj.height = heights[obj.shape * 4 + obj.size];
|
||||
oam[address >> 1 & 511] = word;
|
||||
}
|
||||
|
||||
auto PPU::readObjectVRAM(u32 address) const -> n8 {
|
||||
|
||||
+204
-86
@@ -3,117 +3,235 @@ auto PPU::Objects::setEnable(n1 status) -> void {
|
||||
for(auto& flag : io.enable) flag &= status;
|
||||
}
|
||||
|
||||
auto PPU::Objects::goToNext() -> void {
|
||||
if(!(++objIndex)) active = false;
|
||||
state = State::ReadA01;
|
||||
}
|
||||
|
||||
auto PPU::Objects::readA01(u32 y) -> void {
|
||||
static const u32 widths[] = {
|
||||
8, 16, 32, 64,
|
||||
16, 32, 32, 64,
|
||||
8, 8, 16, 32,
|
||||
8, 8, 8, 8, //invalid shapes
|
||||
};
|
||||
|
||||
static const u32 heights[] = {
|
||||
8, 16, 32, 64,
|
||||
8, 8, 16, 32,
|
||||
16, 32, 32, 64,
|
||||
8, 8, 8, 8, //invalid shapes
|
||||
};
|
||||
|
||||
n16 attr0 = ppu.oam[objIndex << 2 | 0];
|
||||
n8 ypos = attr0 >> 0;
|
||||
latch.affine = attr0 >> 8;
|
||||
latch.affineSize = attr0 >> 9;
|
||||
latch.mode = attr0 >> 10;
|
||||
latch.mosaic = attr0 >> 12;
|
||||
latch.colors = attr0 >> 13; //0 = 16, 1 = 256
|
||||
n2 shape = attr0 >> 14; //0 = square, 1 = horizontal, 2 = vertical
|
||||
|
||||
n16 attr1 = ppu.oam[objIndex << 2 | 1];
|
||||
latch.x = attr1 >> 0;
|
||||
latch.affineParam = attr1 >> 9;
|
||||
latch.hflip = attr1 >> 12;
|
||||
latch.vflip = attr1 >> 13;
|
||||
n2 size = attr1 >> 14;
|
||||
|
||||
latch.width = widths [shape * 4 + size];
|
||||
latch.height = heights[shape * 4 + size];
|
||||
|
||||
latch.py = y - ypos;
|
||||
n9 pxMax = latch.x + (latch.width << latch.affineSize);
|
||||
if((latch.affine == 0 && latch.affineSize == 1) || (latch.py >= latch.height << latch.affineSize) || (latch.x >= 240 && pxMax >= 240)) {
|
||||
//object is hidden - skip rendering
|
||||
goToNext();
|
||||
} else {
|
||||
if(latch.mosaic) {
|
||||
latch.py = ypos >= 160 || mosaicY - ypos >= 0 ? u32(mosaicY - ypos) : 0;
|
||||
}
|
||||
state = State::ReadA2;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::Objects::readA2() -> void {
|
||||
n16 attr2 = ppu.oam[objIndex << 2 | 2];
|
||||
latch.character = attr2 >> 0;
|
||||
latch.priority = attr2 >> 10;
|
||||
latch.palette = attr2 >> 12;
|
||||
|
||||
if(!latch.affine) {
|
||||
drawObject(renderY);
|
||||
} else {
|
||||
state = State::ReadPA;
|
||||
}
|
||||
}
|
||||
|
||||
auto PPU::Objects::drawObject(u32 y) -> void {
|
||||
auto& buffer = lineBuffers[y & 1];
|
||||
|
||||
//center-of-sprite coordinates
|
||||
i16 centerX = latch.width >> 1;
|
||||
i16 centerY = latch.height >> 1;
|
||||
|
||||
//origin coordinates (top-left of sprite)
|
||||
i28 originX = -(centerX << latch.affineSize);
|
||||
i28 originY = -(centerY << latch.affineSize) + latch.py;
|
||||
|
||||
//fractional pixel coordinates
|
||||
i28 fx = originX * latch.pa + originY * latch.pb;
|
||||
i28 fy = originX * latch.pc + originY * latch.pd;
|
||||
|
||||
for(u32 px : range(latch.width << latch.affineSize)) {
|
||||
//calculate address within tile
|
||||
u32 sx, sy;
|
||||
if(!latch.affine) {
|
||||
sx = px ^ (latch.hflip ? latch.width - 1 : 0);
|
||||
sy = latch.py ^ (latch.vflip ? latch.height - 1 : 0);
|
||||
} else {
|
||||
sx = (fx >> 8) + centerX;
|
||||
sy = (fy >> 8) + centerY;
|
||||
}
|
||||
n6 subTileAddr = ((sy & 7) * 8 + (sx & 7)) >> !latch.colors;
|
||||
|
||||
//calculate address of tile
|
||||
n10 tileAddr;
|
||||
if(io.mapping) {
|
||||
u32 offset = (sy >> 3) * (latch.width >> 3) + (sx >> 3);
|
||||
tileAddr = latch.character + (offset << latch.colors);
|
||||
} else {
|
||||
n5 row = (latch.character >> 5) + (sy >> 3);
|
||||
n5 rowEntry = latch.character + ((sx >> 3) << latch.colors);
|
||||
tileAddr = (row << 5) + rowEntry;
|
||||
}
|
||||
|
||||
//output pixel
|
||||
n8 color = ppu.readObjectVRAM((tileAddr << 5) + subTileAddr);
|
||||
if(latch.colors == 0) color = sx & 1 ? color >> 4 : color & 15;
|
||||
n9 bx = latch.x + px;
|
||||
if(bx < 240 && sx < latch.width && sy < latch.height) {
|
||||
if(latch.mode & 2) {
|
||||
if(color) {
|
||||
buffer[bx].window = true;
|
||||
}
|
||||
} else if(!buffer[bx].enable || latch.priority < buffer[bx].priority) {
|
||||
buffer[bx].priority = latch.priority; //updated regardless of transparency
|
||||
buffer[bx].mosaic = latch.mosaic; //updated regardless of transparency
|
||||
if(color) {
|
||||
if(latch.colors == 0) color = latch.palette * 16 + color;
|
||||
buffer[bx].enable = true;
|
||||
buffer[bx].color = 256 + color;
|
||||
buffer[bx].translucent = latch.mode == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fx += latch.pa;
|
||||
fy += latch.pc;
|
||||
}
|
||||
|
||||
goToNext();
|
||||
}
|
||||
|
||||
auto PPU::Objects::step() -> void {
|
||||
if(!active) return;
|
||||
|
||||
if(activeCycle) {
|
||||
switch(state) {
|
||||
case State::ReadA01:
|
||||
readA01(renderY);
|
||||
break;
|
||||
case State::ReadA2:
|
||||
readA2();
|
||||
break;
|
||||
case State::ReadPA:
|
||||
latch.pa = ppu.oam[latch.affineParam << 4 | 0x3];
|
||||
state = State::ReadPB;
|
||||
break;
|
||||
case State::ReadPB:
|
||||
latch.pb = ppu.oam[latch.affineParam << 4 | 0x7];
|
||||
state = State::ReadPC;
|
||||
break;
|
||||
case State::ReadPC:
|
||||
latch.pc = ppu.oam[latch.affineParam << 4 | 0xb];
|
||||
state = State::ReadPD;
|
||||
break;
|
||||
case State::ReadPD:
|
||||
latch.pd = ppu.oam[latch.affineParam << 4 | 0xf];
|
||||
drawObject(renderY);
|
||||
break;
|
||||
}
|
||||
ppu.oamAccessed = true;
|
||||
}
|
||||
activeCycle = !activeCycle;
|
||||
}
|
||||
|
||||
auto PPU::Objects::scanline(u32 y) -> void {
|
||||
if(y >= 160) return;
|
||||
|
||||
mosaicOffset = 0;
|
||||
hmosaicOffset = io.mosaicWidth;
|
||||
if(y == 0 || vmosaicOffset == io.mosaicHeight) {
|
||||
vmosaicOffset = 0;
|
||||
mosaicY = y;
|
||||
} else {
|
||||
vmosaicOffset++;
|
||||
}
|
||||
|
||||
auto& buffer = lineBuffers[y & 1];
|
||||
for(auto& pixel : buffer) pixel = {};
|
||||
if(ppu.io.forceBlank[1] || cpu.stopped() || !io.enable[1]) return; //checks if display conditions will be met next scanline
|
||||
|
||||
for(auto& object : ppu.object) {
|
||||
n8 py = y - object.y;
|
||||
if(object.affine == 0 && object.affineSize == 1) continue; //hidden
|
||||
if(py >= object.height << object.affineSize) continue; //offscreen
|
||||
renderY = y;
|
||||
objIndex = 0;
|
||||
active = true;
|
||||
activeCycle = true;
|
||||
state = State::ReadA01;
|
||||
}
|
||||
|
||||
if(object.mosaic && io.mosaicHeight) {
|
||||
s32 mosaicY = (y / (1 + io.mosaicHeight)) * (1 + io.mosaicHeight);
|
||||
py = object.y >= 160 || mosaicY - object.y >= 0 ? u32(mosaicY - object.y) : 0;
|
||||
}
|
||||
|
||||
i16 pa = ppu.objectParam[object.affineParam].pa;
|
||||
i16 pb = ppu.objectParam[object.affineParam].pb;
|
||||
i16 pc = ppu.objectParam[object.affineParam].pc;
|
||||
i16 pd = ppu.objectParam[object.affineParam].pd;
|
||||
|
||||
//center-of-sprite coordinates
|
||||
i16 centerX = object.width >> 1;
|
||||
i16 centerY = object.height >> 1;
|
||||
|
||||
//origin coordinates (top-left of sprite)
|
||||
i28 originX = -(centerX << object.affineSize);
|
||||
i28 originY = -(centerY << object.affineSize) + py;
|
||||
|
||||
//fractional pixel coordinates
|
||||
i28 fx = originX * pa + originY * pb;
|
||||
i28 fy = originX * pc + originY * pd;
|
||||
|
||||
for(u32 px : range(object.width << object.affineSize)) {
|
||||
//calculate address within tile
|
||||
u32 sx, sy;
|
||||
if(!object.affine) {
|
||||
sx = px ^ (object.hflip ? object.width - 1 : 0);
|
||||
sy = py ^ (object.vflip ? object.height - 1 : 0);
|
||||
} else {
|
||||
sx = (fx >> 8) + centerX;
|
||||
sy = (fy >> 8) + centerY;
|
||||
}
|
||||
n6 subTileAddr = ((sy & 7) * 8 + (sx & 7)) >> !object.colors;
|
||||
|
||||
//calculate address of tile
|
||||
n10 tileAddr;
|
||||
if(io.mapping) {
|
||||
u32 offset = (sy >> 3) * (object.width >> 3) + (sx >> 3);
|
||||
tileAddr = object.character + (offset << object.colors);
|
||||
} else {
|
||||
n5 row = (object.character >> 5) + (sy >> 3);
|
||||
n5 rowEntry = object.character + ((sx >> 3) << object.colors);
|
||||
tileAddr = (row << 5) + rowEntry;
|
||||
}
|
||||
|
||||
//output pixel
|
||||
n8 color = ppu.readObjectVRAM((tileAddr << 5) + subTileAddr);
|
||||
if(object.colors == 0) color = sx & 1 ? color >> 4 : color & 15;
|
||||
n9 bx = object.x + px;
|
||||
if(bx < 240 && sx < object.width && sy < object.height) {
|
||||
if(object.mode & 2) {
|
||||
if(color) {
|
||||
buffer[bx].window = true;
|
||||
}
|
||||
} else if(!buffer[bx].enable || object.priority < buffer[bx].priority) {
|
||||
buffer[bx].priority = object.priority; //updated regardless of transparency
|
||||
buffer[bx].mosaic = object.mosaic; //updated regardless of transparency
|
||||
if(color) {
|
||||
if(object.colors == 0) color = object.palette * 16 + color;
|
||||
buffer[bx].enable = true;
|
||||
buffer[bx].color = 256 + color;
|
||||
buffer[bx].translucent = object.mode == 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fx += pa;
|
||||
fy += pc;
|
||||
}
|
||||
}
|
||||
auto PPU::Objects::renderScanline(u32 y) -> void {
|
||||
scanline(y);
|
||||
for(auto _ : range(1232)) step();
|
||||
active = false;
|
||||
ppu.objReleaseBus();
|
||||
}
|
||||
|
||||
auto PPU::Objects::outputPixel(u32 x, u32 y) -> void {
|
||||
output = {};
|
||||
if(ppu.blank() || !io.enable[0]) {
|
||||
mosaic = {};
|
||||
mosaicLatch = {};
|
||||
return;
|
||||
}
|
||||
|
||||
auto& buffer = lineBuffers[y & 1];
|
||||
output = buffer[x];
|
||||
|
||||
//horizontal mosaic
|
||||
if(!mosaicOffset) {
|
||||
mosaicOffset = 1 + io.mosaicWidth;
|
||||
mosaic = output;
|
||||
} else if(!mosaic.mosaic || !output.mosaic) {
|
||||
mosaic = output;
|
||||
|
||||
if(hmosaicOffset == io.mosaicWidth) {
|
||||
hmosaicOffset = 0;
|
||||
mosaicLatch = output;
|
||||
} else {
|
||||
hmosaicOffset++;
|
||||
}
|
||||
|
||||
if(!mosaicLatch.mosaic || !output.mosaic || (output.priority < mosaicLatch.priority)) {
|
||||
mosaicLatch = output;
|
||||
}
|
||||
mosaicOffset--;
|
||||
}
|
||||
|
||||
auto PPU::Objects::power() -> void {
|
||||
io = {};
|
||||
latch = {};
|
||||
for(auto& buffer : lineBuffers) {
|
||||
for(auto& pixel : buffer) pixel = {};
|
||||
}
|
||||
output = {};
|
||||
mosaic = {};
|
||||
mosaicOffset = 0;
|
||||
mosaicLatch = {};
|
||||
renderY = 0;
|
||||
mosaicY = 0;
|
||||
hmosaicOffset = 0;
|
||||
vmosaicOffset = 0;
|
||||
objIndex = 0;
|
||||
active = false;
|
||||
activeCycle = false;
|
||||
state = State::ReadA01;
|
||||
}
|
||||
+39
-19
@@ -20,6 +20,7 @@ auto PPU::setAccurate(bool value) -> void {
|
||||
auto PPU::load(Node::Object parent) -> void {
|
||||
vram.allocate(96_KiB);
|
||||
pram.allocate(512);
|
||||
oam.allocate(512);
|
||||
|
||||
node = parent->append<Node::Object>("PPU");
|
||||
|
||||
@@ -72,8 +73,12 @@ inline auto PPU::blank() -> bool {
|
||||
}
|
||||
|
||||
auto PPU::step(u32 clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
Thread::synchronize(cpu, display);
|
||||
for(auto _ : range(clocks)) {
|
||||
objects.step();
|
||||
Thread::step(1);
|
||||
Thread::synchronize(cpu, display);
|
||||
objReleaseBus();
|
||||
}
|
||||
}
|
||||
|
||||
template<s32 Cycle>
|
||||
@@ -108,16 +113,18 @@ auto PPU::cycleBitmap(u32 x, u32 y) -> void {
|
||||
if(mode >= 3 && mode <= 5) bg2.bitmap(x, y);
|
||||
}
|
||||
|
||||
auto PPU::cycleWindow(u32 x, u32 y) -> void {
|
||||
window0.run(x, y);
|
||||
window1.run(x, y);
|
||||
}
|
||||
|
||||
auto PPU::cycleUpperLayer(u32 x, u32 y) -> void {
|
||||
ppu.bg0.outputPixel(x, y);
|
||||
ppu.bg1.outputPixel(x, y);
|
||||
ppu.bg2.outputPixel(x, y);
|
||||
ppu.bg3.outputPixel(x, y);
|
||||
ppu.objects.outputPixel(x, y);
|
||||
window0.run(x, y);
|
||||
window1.run(x, y);
|
||||
window2.output = objects.output.window;
|
||||
window3.output = true;
|
||||
window2.output[x] = objects.output.window;
|
||||
dac.upperLayer(x, y);
|
||||
}
|
||||
|
||||
@@ -127,10 +134,11 @@ auto PPU::cycle(u32 y) -> void {
|
||||
if constexpr(Cycle >= 3 && Cycle <= 1005 ) cycleLinearMap<(Cycle - 3) & 3>((Cycle - 31) >> 2, y);
|
||||
if constexpr(Cycle >= 31 && Cycle <= 1005 ) cycleAffine<(Cycle - 31) & 3>((Cycle - 31) >> 2, y);
|
||||
if constexpr(Cycle >= 31 && Cycle <= 1005 && (Cycle - 31) % 4 == 3) cycleBitmap((Cycle - 31) >> 2, y);
|
||||
if constexpr(Cycle >= 3 && Cycle <= 1026 && (Cycle - 3) % 4 == 0) cycleWindow((Cycle - 3) / 4, y);
|
||||
if constexpr(Cycle >= 46 && Cycle <= 1005 && (Cycle - 46) % 4 == 0) cycleUpperLayer((Cycle - 46) / 4, y);
|
||||
if constexpr(Cycle >= 46 && Cycle <= 1005 && (Cycle - 46) % 4 == 2) dac.lowerLayer((Cycle - 46) / 4, y);
|
||||
step(1);
|
||||
releaseBus();
|
||||
bgReleaseBus();
|
||||
}
|
||||
|
||||
auto PPU::main() -> void {
|
||||
@@ -144,8 +152,6 @@ auto PPU::main() -> void {
|
||||
bg3.io.ly = bg3.io.y;
|
||||
}
|
||||
|
||||
step(3);
|
||||
|
||||
u32 y = display.io.vcounter;
|
||||
memory::move(io.forceBlank, io.forceBlank + 1, sizeof(io.forceBlank) - 1);
|
||||
memory::move(bg0.io.enable, bg0.io.enable + 1, sizeof(bg0.io.enable) - 1);
|
||||
@@ -157,9 +163,12 @@ auto PPU::main() -> void {
|
||||
bg1.scanline(y);
|
||||
bg2.scanline(y);
|
||||
bg3.scanline(y);
|
||||
objects.scanline((y + 1) % 228);
|
||||
window0.scanline(y);
|
||||
window1.scanline(y);
|
||||
dac.scanline(y);
|
||||
|
||||
step(4);
|
||||
|
||||
if(y < 160) {
|
||||
if(accurate) {
|
||||
#define cycles01(index) cycle<index>(y)
|
||||
@@ -178,9 +187,12 @@ auto PPU::main() -> void {
|
||||
|
||||
//cycle 31 - start rendering backgrounds unconditionally
|
||||
cycles01( 31);
|
||||
cycles02( 32);
|
||||
cycles04( 34);
|
||||
cycles08( 38);
|
||||
cycles08( 32);
|
||||
|
||||
//cycle 40 - start rendering sprites
|
||||
objects.scanline((y + 1) % 228);
|
||||
cycles02( 40);
|
||||
cycles04( 42);
|
||||
|
||||
//cycle 46 - start pixel output
|
||||
cycles64( 46);
|
||||
@@ -213,24 +225,34 @@ auto PPU::main() -> void {
|
||||
#undef cycles64
|
||||
} else {
|
||||
step(renderingCycle);
|
||||
objects.renderScanline((y + 1) % 228);
|
||||
for(s32 x : range(247)) {
|
||||
bg0.run(x - 7, y);
|
||||
bg1.run(x - 7, y);
|
||||
bg2.run(x - 7, y);
|
||||
bg3.run(x - 7, y);
|
||||
}
|
||||
for(u32 x : range(256)) cycleWindow(x, y);
|
||||
for(u32 x : range(240)) {
|
||||
cycleUpperLayer(x, y);
|
||||
dac.lowerLayer(x, y);
|
||||
}
|
||||
releaseBus();
|
||||
bgReleaseBus();
|
||||
step(1035 - renderingCycle);
|
||||
}
|
||||
} else {
|
||||
step(1035);
|
||||
if(accurate) {
|
||||
step(37);
|
||||
objects.scanline((y + 1) % 228);
|
||||
step(998);
|
||||
} else {
|
||||
step(renderingCycle);
|
||||
objects.renderScanline((y + 1) % 228);
|
||||
step(1035 - renderingCycle);
|
||||
}
|
||||
}
|
||||
|
||||
step(194);
|
||||
step(193);
|
||||
}
|
||||
|
||||
auto PPU::frame() -> void {
|
||||
@@ -250,8 +272,6 @@ auto PPU::power() -> void {
|
||||
for(u32 n = 0; n < 1024; n += 2) writeOAM(Half, n, 0x0000);
|
||||
|
||||
io = {};
|
||||
for(auto& object : this->object) object = {};
|
||||
for(auto& param : this->objectParam) param = {};
|
||||
|
||||
bg0.power(BG0);
|
||||
bg1.power(BG1);
|
||||
@@ -264,7 +284,7 @@ auto PPU::power() -> void {
|
||||
window3.power(OUT);
|
||||
dac.power();
|
||||
|
||||
renderingCycle = 43; //by default, render at first cycle of pixel output
|
||||
renderingCycle = 42; //by default, render at first cycle of pixel output
|
||||
string gameID;
|
||||
for(u32 index : range(4)) {
|
||||
n32 address = 0xac + index;
|
||||
|
||||
+60
-46
@@ -6,6 +6,7 @@ struct PPU : Thread, IO {
|
||||
Node::Setting::String rotation;
|
||||
Memory::Writable<n8 > vram; //96KB
|
||||
Memory::Writable<n16> pram;
|
||||
Memory::Writable<n16> oam;
|
||||
|
||||
bool accurate;
|
||||
|
||||
@@ -17,6 +18,7 @@ struct PPU : Thread, IO {
|
||||
struct Memory {
|
||||
Node::Debugger::Memory vram;
|
||||
Node::Debugger::Memory pram;
|
||||
Node::Debugger::Memory oam;
|
||||
} memory;
|
||||
|
||||
struct Graphics {
|
||||
@@ -41,6 +43,7 @@ struct PPU : Thread, IO {
|
||||
template<s32> auto cycleLinearRender(s32 x, u32 y) -> void;
|
||||
template<s32> auto cycleAffine(u32 x, u32 y) -> void;
|
||||
auto cycleBitmap(u32 x, u32 y) -> void;
|
||||
auto cycleWindow(u32 x, u32 y) -> void;
|
||||
auto cycleUpperLayer(u32 x, u32 y) -> void;
|
||||
template<s32> auto cycle(u32 y) -> void;
|
||||
auto main() -> void;
|
||||
@@ -54,9 +57,11 @@ struct PPU : Thread, IO {
|
||||
auto writeIO(n32 address, n8 byte) -> void;
|
||||
|
||||
//memory.cpp
|
||||
auto releaseBus() -> void;
|
||||
auto bgReleaseBus() -> void;
|
||||
auto objReleaseBus() -> void;
|
||||
auto pramContention() -> bool;
|
||||
auto vramContention(n32 address) -> bool;
|
||||
auto oamContention() -> bool;
|
||||
|
||||
auto readVRAM(u32 mode, n32 address) -> n16;
|
||||
auto readVRAM_BG(u32 mode, n32 address) -> n16;
|
||||
@@ -169,6 +174,8 @@ private:
|
||||
struct Affine {
|
||||
u32 screenSize;
|
||||
u32 screenWrap;
|
||||
u32 hmosaic;
|
||||
u32 vmosaic;
|
||||
u32 cx;
|
||||
u32 cy;
|
||||
u32 tx;
|
||||
@@ -177,10 +184,8 @@ private:
|
||||
} affine;
|
||||
|
||||
Pixel output[240];
|
||||
Pixel mosaic;
|
||||
Pixel mosaicLatch;
|
||||
u32 mosaicOffset;
|
||||
|
||||
u32 hmosaic;
|
||||
u32 vmosaic;
|
||||
|
||||
i28 fx;
|
||||
@@ -190,7 +195,13 @@ private:
|
||||
struct Objects {
|
||||
//object.cpp
|
||||
auto setEnable(n1 status) -> void;
|
||||
auto goToNext() -> void;
|
||||
auto readA01(u32 y) -> void;
|
||||
auto readA2() -> void;
|
||||
auto drawObject(u32 y) -> void;
|
||||
auto step() -> void;
|
||||
auto scanline(u32 y) -> void;
|
||||
auto renderScanline(u32 y) -> void;
|
||||
auto outputPixel(u32 x, u32 y) -> void;
|
||||
auto power() -> void;
|
||||
|
||||
@@ -202,18 +213,55 @@ private:
|
||||
|
||||
n1 hblank; //1 = allow access to OAM during Hblank
|
||||
n1 mapping; //0 = two-dimensional, 1 = one-dimensional
|
||||
n5 mosaicWidth;
|
||||
n5 mosaicHeight;
|
||||
n4 mosaicWidth;
|
||||
n4 mosaicHeight;
|
||||
} io;
|
||||
|
||||
struct Latch {
|
||||
n1 affine;
|
||||
n1 affineSize;
|
||||
n2 mode;
|
||||
n1 mosaic;
|
||||
n1 colors;
|
||||
|
||||
n9 x;
|
||||
n5 affineParam;
|
||||
n1 hflip;
|
||||
n1 vflip;
|
||||
|
||||
n10 character;
|
||||
n2 priority;
|
||||
n4 palette;
|
||||
|
||||
n32 width;
|
||||
n32 height;
|
||||
n8 py;
|
||||
|
||||
i16 pa;
|
||||
i16 pb;
|
||||
i16 pc;
|
||||
i16 pd;
|
||||
} latch;
|
||||
|
||||
Pixel lineBuffers[2][240];
|
||||
Pixel output;
|
||||
Pixel mosaic;
|
||||
u32 mosaicOffset;
|
||||
Pixel mosaicLatch;
|
||||
u32 renderY;
|
||||
s32 mosaicY;
|
||||
n4 hmosaicOffset;
|
||||
n4 vmosaicOffset;
|
||||
n7 objIndex;
|
||||
bool active;
|
||||
bool activeCycle;
|
||||
|
||||
enum class State : u32 {
|
||||
ReadA01, ReadA2, ReadPA, ReadPB, ReadPC, ReadPD
|
||||
} state;
|
||||
} objects;
|
||||
|
||||
struct Window {
|
||||
//window.cpp
|
||||
auto scanline(u32 y) -> void;
|
||||
auto run(u32 x, u32 y) -> void;
|
||||
auto power(u32 id) -> void;
|
||||
|
||||
@@ -233,7 +281,9 @@ private:
|
||||
n8 y2;
|
||||
} io;
|
||||
|
||||
n1 output; //IN0, IN1, IN2 only
|
||||
n1 output[256]; //IN0, IN1, IN2 only
|
||||
n1 h; //IN0, IN1 only
|
||||
n1 v; //IN0, IN1 only
|
||||
} window0, window1, window2, window3;
|
||||
|
||||
struct DAC {
|
||||
@@ -268,45 +318,9 @@ private:
|
||||
u32* line = nullptr;
|
||||
} dac;
|
||||
|
||||
struct Object {
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
n8 y;
|
||||
n1 affine;
|
||||
n1 affineSize;
|
||||
n2 mode;
|
||||
n1 mosaic;
|
||||
n1 colors; //0 = 16, 1 = 256
|
||||
n2 shape; //0 = square, 1 = horizontal, 2 = vertical
|
||||
|
||||
n9 x;
|
||||
n5 affineParam;
|
||||
n1 hflip;
|
||||
n1 vflip;
|
||||
n2 size;
|
||||
|
||||
n10 character;
|
||||
n2 priority;
|
||||
n4 palette;
|
||||
|
||||
//ancillary data
|
||||
n32 width;
|
||||
n32 height;
|
||||
} object[128];
|
||||
|
||||
struct ObjectParam {
|
||||
//serialization.cpp
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
i16 pa;
|
||||
i16 pb;
|
||||
i16 pc;
|
||||
i16 pd;
|
||||
} objectParam[32];
|
||||
|
||||
bool pramAccessed;
|
||||
bool vramAccessedBG;
|
||||
bool oamAccessed;
|
||||
n32 renderingCycle;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ auto PPU::serialize(serializer& s) -> void {
|
||||
|
||||
s(vram);
|
||||
s(pram);
|
||||
s(oam);
|
||||
|
||||
s(io.gameBoyColorMode);
|
||||
for(auto& flag : io.forceBlank) s(flag);
|
||||
@@ -22,11 +23,10 @@ auto PPU::serialize(serializer& s) -> void {
|
||||
s(window2);
|
||||
s(window3);
|
||||
s(dac);
|
||||
for(auto& object : this->object) s(object);
|
||||
for(auto& param : this->objectParam) s(param);
|
||||
|
||||
s(pramAccessed);
|
||||
s(vramAccessedBG);
|
||||
s(oamAccessed);
|
||||
}
|
||||
|
||||
auto PPU::Background::serialize(serializer& s) -> void {
|
||||
@@ -53,7 +53,6 @@ auto PPU::Background::serialize(serializer& s) -> void {
|
||||
s(io.ly);
|
||||
|
||||
s(mosaicOffset);
|
||||
s(hmosaic);
|
||||
s(vmosaic);
|
||||
s(fx);
|
||||
s(fy);
|
||||
@@ -66,7 +65,15 @@ auto PPU::Objects::serialize(serializer& s) -> void {
|
||||
s(io.mosaicWidth);
|
||||
s(io.mosaicHeight);
|
||||
|
||||
s(mosaicOffset);
|
||||
s(renderY);
|
||||
s(mosaicY);
|
||||
s(hmosaicOffset);
|
||||
s(vmosaicOffset);
|
||||
s(objIndex);
|
||||
s(active);
|
||||
s(activeCycle);
|
||||
|
||||
s(state);
|
||||
}
|
||||
|
||||
auto PPU::Window::serialize(serializer& s) -> void {
|
||||
@@ -79,7 +86,9 @@ auto PPU::Window::serialize(serializer& s) -> void {
|
||||
s(io.y1);
|
||||
s(io.y2);
|
||||
|
||||
s(output);
|
||||
for(auto& flag : output) s(flag);
|
||||
s(h);
|
||||
s(v);
|
||||
}
|
||||
|
||||
auto PPU::DAC::serialize(serializer& s) -> void {
|
||||
@@ -90,30 +99,3 @@ auto PPU::DAC::serialize(serializer& s) -> void {
|
||||
s(io.blendEVB);
|
||||
s(io.blendEVY);
|
||||
}
|
||||
|
||||
auto PPU::Object::serialize(serializer& s) -> void {
|
||||
s(y);
|
||||
s(affine);
|
||||
s(affineSize);
|
||||
s(mode);
|
||||
s(mosaic);
|
||||
s(colors);
|
||||
s(shape);
|
||||
s(x);
|
||||
s(affineParam);
|
||||
s(hflip);
|
||||
s(vflip);
|
||||
s(size);
|
||||
s(character);
|
||||
s(priority);
|
||||
s(palette);
|
||||
s(width);
|
||||
s(height);
|
||||
}
|
||||
|
||||
auto PPU::ObjectParam::serialize(serializer& s) -> void {
|
||||
s(pa);
|
||||
s(pb);
|
||||
s(pc);
|
||||
s(pd);
|
||||
}
|
||||
+11
-8
@@ -1,16 +1,19 @@
|
||||
auto PPU::Window::scanline(u32 y) -> void {
|
||||
if(y == io.y1) v = true;
|
||||
if(y == io.y2) v = false;
|
||||
}
|
||||
|
||||
auto PPU::Window::run(u32 x, u32 y) -> void {
|
||||
auto x1 = io.x1, x2 = io.x2;
|
||||
auto y1 = io.y1, y2 = io.y2;
|
||||
|
||||
if(x2 < x1 || x2 > 240) x2 = 240;
|
||||
if(y2 < y1 || y2 > 160) y2 = 160;
|
||||
|
||||
output = (x >= x1 && x < x2 && y >= y1 && y < y2);
|
||||
if(x == io.x1) h = true;
|
||||
if(x == io.x2) h = false;
|
||||
output[x] = h && v;
|
||||
}
|
||||
|
||||
auto PPU::Window::power(u32 id) -> void {
|
||||
this->id = id;
|
||||
|
||||
io = {};
|
||||
output = 0;
|
||||
for(auto& flag : output) flag = 0;
|
||||
h = 0;
|
||||
v = 0;
|
||||
}
|
||||
@@ -16,9 +16,7 @@ auto BIOS::readROM(n25 address) -> n32 {
|
||||
|
||||
auto BIOS::read(u32 mode, n25 address) -> n32 {
|
||||
//unmapped memory
|
||||
if(address >= 0x0000'4000) {
|
||||
return cpu.openBus.get(mode, address); //0000'4000-01ff'ffff
|
||||
}
|
||||
if(address >= 0x0000'4000) return cpu.mdr; //0000'4000-01ff'ffff
|
||||
|
||||
//GBA BIOS is read-protected; only the BIOS itself can read its own memory
|
||||
//when accessed elsewhere; this should return the last value read by the BIOS program
|
||||
@@ -27,10 +25,7 @@ auto BIOS::read(u32 mode, n25 address) -> n32 {
|
||||
} else {
|
||||
if(cpu.processor.r15 > 0x01ff'ffff && cpu.processor.r15 < 0x0200'4000) mdr = readROM(address);
|
||||
}
|
||||
|
||||
if(mode & Word) address &= ~3;
|
||||
if(mode & Half) address &= ~1;
|
||||
return mdr >> (8 * (address & 3));
|
||||
return mdr;
|
||||
}
|
||||
|
||||
auto BIOS::write(u32 mode, n25 address, n32 word) -> void {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
static const string SerializerVersion = "v146.1";
|
||||
static const string SerializerVersion = "v148.7";
|
||||
|
||||
auto System::serialize(bool synchronize) -> serializer {
|
||||
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
|
||||
|
||||
@@ -107,6 +107,14 @@ ares_add_sources(
|
||||
controller/fighting-pad/fighting-pad.hpp
|
||||
)
|
||||
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
md
|
||||
INCLUDED #
|
||||
controller/mega-mouse/mega-mouse.cpp
|
||||
controller/mega-mouse/mega-mouse.hpp
|
||||
)
|
||||
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
md
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ auto MCD::CDC::clock() -> void {
|
||||
auto MCD::CDC::decode(s32 sector) -> void {
|
||||
if(!decoder.enable || !mcd.fd) return;
|
||||
|
||||
auto [minute, second, frame] = CD::MSF(sector);
|
||||
auto [minute, second, frame] = CD::MSF::fromLBA(sector);
|
||||
header.minute = minute / 10 << 4 | minute % 10;
|
||||
header.second = second / 10 << 4 | second % 10;
|
||||
header.frame = frame / 10 << 4 | frame % 10;
|
||||
@@ -32,7 +32,7 @@ auto MCD::CDC::decode(s32 sector) -> void {
|
||||
transfer.target += 2352;
|
||||
|
||||
//the sync header is written at the tail instead of head.
|
||||
mcd.fd->seek((abs(mcd.cdd.session.leadIn.lba) + sector) * 2448);
|
||||
mcd.fd->seek(2448ull * (CD::LeadInSectors + CD::LBAtoABA(sector)));
|
||||
for(u32 index = 0; index < 12; index += 2) {
|
||||
ram[n13(transfer.pointer + index + 2340 >> 1)] = mcd.fd->readm(2);
|
||||
}
|
||||
|
||||
+14
-15
@@ -180,7 +180,7 @@ auto MCD::CDD::sample() -> void {
|
||||
if (io.status == Status::Playing) {
|
||||
if (MegaLD() || session.tracks[io.track].isAudio()) {
|
||||
if (session.tracks[io.track].isAudio()) {
|
||||
mcd.fd->seek((abs(session.leadIn.lba) + io.sector) * 2448 + io.sample);
|
||||
mcd.fd->seek(2448ull * (CD::LeadInSectors + CD::LBAtoABA(io.sector)) + io.sample);
|
||||
digitalSampleLeft = mcd.fd->readl(2);
|
||||
digitalSampleRight = mcd.fd->readl(2);
|
||||
}
|
||||
@@ -301,8 +301,7 @@ auto MCD::CDD::position(s32 sector) -> double {
|
||||
|
||||
auto MCD::CDD::readSubcode() -> void {
|
||||
if(!mcd.fd) return;
|
||||
|
||||
mcd.fd->seek(((abs(mcd.cdd.session.leadIn.lba) + io.sector) * 2448) + 2352);
|
||||
mcd.fd->seek(2448ull * (CD::LeadInSectors + CD::LBAtoABA(io.sector)) + 2352);
|
||||
std::vector<u8> subchannel;
|
||||
subchannel.resize(96);
|
||||
mcd.fd->read({subchannel.data(), 96});
|
||||
@@ -360,7 +359,7 @@ auto MCD::CDD::process() -> void {
|
||||
switch(command[3]) {
|
||||
|
||||
case Request::AbsoluteTime: {
|
||||
auto [minute, second, frame] = CD::MSF(io.sector);
|
||||
auto [minute, second, frame] = CD::MSF::fromLBA(io.sector);
|
||||
status[1] = command[3];
|
||||
status[2] = minute / 10; status[3] = minute % 10;
|
||||
status[4] = second / 10; status[5] = second % 10;
|
||||
@@ -369,7 +368,7 @@ auto MCD::CDD::process() -> void {
|
||||
} break;
|
||||
|
||||
case Request::RelativeTime: {
|
||||
auto [minute, second, frame] = CD::MSF(io.sector - session.tracks[io.track].indices[1].lba);
|
||||
auto [minute, second, frame] = CD::MSF::fromABA(io.sector - session.tracks[io.track].indices[1].lba); //fromABA to offset by 2s
|
||||
status[1] = command[3];
|
||||
status[2] = minute / 10; status[3] = minute % 10;
|
||||
status[4] = second / 10; status[5] = second % 10;
|
||||
@@ -388,7 +387,7 @@ auto MCD::CDD::process() -> void {
|
||||
} break;
|
||||
|
||||
case Request::DiscCompletionTime: { //time in mm:ss:ff
|
||||
auto [minute, second, frame] = CD::MSF(session.leadOut.lba);
|
||||
auto [minute, second, frame] = CD::MSF::fromLBA(session.leadOut.lba);
|
||||
status[1] = command[3];
|
||||
status[2] = minute / 10; status[3] = minute % 10;
|
||||
status[4] = second / 10; status[5] = second % 10;
|
||||
@@ -409,7 +408,7 @@ auto MCD::CDD::process() -> void {
|
||||
case Request::TrackStartTime: {
|
||||
if(command[4] > 9 || command[5] > 9) break;
|
||||
u32 track = command[4] * 10 + command[5];
|
||||
auto [minute, second, frame] = CD::MSF(session.tracks[track].indices[1].lba);
|
||||
auto [minute, second, frame] = CD::MSF::fromLBA(session.tracks[track].indices[1].lba);
|
||||
status[1] = command[3];
|
||||
status[2] = minute / 10; status[3] = minute % 10;
|
||||
status[4] = second / 10; status[5] = second % 10;
|
||||
@@ -434,7 +433,7 @@ auto MCD::CDD::process() -> void {
|
||||
u32 minute = command[2] * 10 + command[3];
|
||||
u32 second = command[4] * 10 + command[5];
|
||||
u32 frame = command[6] * 10 + command[7];
|
||||
s32 lba = minute * 60 * 75 + second * 75 + frame - 3;
|
||||
s32 lba = CD::MSF(minute, second, frame).toLBA() - 3;
|
||||
|
||||
counter = 0;
|
||||
io.status = Status::Seeking;
|
||||
@@ -454,7 +453,7 @@ auto MCD::CDD::process() -> void {
|
||||
u32 minute = command[2] * 10 + command[3];
|
||||
u32 second = command[4] * 10 + command[5];
|
||||
u32 frame = command[6] * 10 + command[7];
|
||||
s32 lba = minute * 60 * 75 + second * 75 + frame - 3;
|
||||
s32 lba = CD::MSF(minute, second, frame).toLBA() - 3;
|
||||
|
||||
counter = 0;
|
||||
io.status = Status::Seeking;
|
||||
@@ -652,28 +651,28 @@ auto MCD::CDD::getCurrentSector() -> s32 {
|
||||
}
|
||||
|
||||
auto MCD::CDD::getCurrentTimecode(u8& minute, u8& second, u8& frame) -> void {
|
||||
auto [lminute, lsecond, lframe] = CD::MSF(io.sector);
|
||||
auto [lminute, lsecond, lframe] = CD::MSF::fromLBA(io.sector);
|
||||
minute = lminute;
|
||||
second = lsecond;
|
||||
frame = lframe;
|
||||
}
|
||||
|
||||
auto MCD::CDD::getCurrentTrackRelativeTimecode(u8& minute, u8& second, u8& frame) -> void {
|
||||
auto [lminute, lsecond, lframe] = CD::MSF(io.sector - session.tracks[io.track].indices[1].lba);
|
||||
auto [lminute, lsecond, lframe] = CD::MSF::fromABA(io.sector - session.tracks[io.track].indices[1].lba);
|
||||
minute = lminute;
|
||||
second = lsecond;
|
||||
frame = lframe;
|
||||
}
|
||||
|
||||
auto MCD::CDD::getLeadOutTimecode(u8& minute, u8& second, u8& frame) -> void {
|
||||
auto [lminute, lsecond, lframe] = CD::MSF(session.leadOut.lba);
|
||||
auto [lminute, lsecond, lframe] = CD::MSF::fromLBA(session.leadOut.lba);
|
||||
minute = lminute;
|
||||
second = lsecond;
|
||||
frame = lframe;
|
||||
}
|
||||
|
||||
auto MCD::CDD::getTrackTocData(n7 track, u8& flags, u8& minute, u8& second, u8& frame) -> void {
|
||||
auto [lminute, lsecond, lframe] = CD::MSF(session.tracks[track].indices[1].lba);
|
||||
auto [lminute, lsecond, lframe] = CD::MSF::fromLBA(session.tracks[track].indices[1].lba);
|
||||
minute = lminute;
|
||||
second = lsecond;
|
||||
frame = lframe;
|
||||
@@ -681,8 +680,8 @@ auto MCD::CDD::getTrackTocData(n7 track, u8& flags, u8& minute, u8& second, u8&
|
||||
}
|
||||
|
||||
auto MCD::CDD::lbaFromTime(u8 hour, u8 minute, u8 second, u8 frame) -> s32 {
|
||||
s32 lba = ((((((s32)hour * 60) + (s32)minute) * 60) + (s32)second) * 75) + (s32)frame;
|
||||
return lba;
|
||||
s32 aba = ((((((s32)hour * 60) + (s32)minute) * 60) + (s32)second) * 75) + (s32)frame;
|
||||
return aba - CD::Track1Pregap;
|
||||
}
|
||||
|
||||
auto MCD::CDD::isTrackAudio(n7 track) -> bool {
|
||||
|
||||
@@ -2456,6 +2456,8 @@ auto MCD::LD::zeroBasedFrameIndexFromLba(s32 lba, bool processLeadIn) -> s32 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
lba = CD::LBAtoABA(lba);
|
||||
|
||||
// Turn the lba sector number into a frame number. Since there are 30 frames of video per second, and 75 sectors of CD
|
||||
// data per second, this will work well enough.
|
||||
auto frameIndex = (s32)std::round(((double)lba / 75.0) * videoFramesPerSecond);
|
||||
@@ -2466,7 +2468,7 @@ auto MCD::LD::zeroBasedFrameIndexFromLba(s32 lba, bool processLeadIn) -> s32 {
|
||||
// Convert the ZERO-BASED frame number to an LBA.
|
||||
auto MCD::LD::lbaFromZeroBasedFrameIndex(s32 frameIndex) -> s32 {
|
||||
auto lba = (s32)std::round(((double)frameIndex / videoFramesPerSecond) * 75.0);
|
||||
return lba;
|
||||
return CD::ABAtoLBA(lba);
|
||||
}
|
||||
|
||||
auto MCD::LD::VideoTimeToRedbookTime(u8& hours, u8& minutes, u8& seconds, u8& frames) -> void {
|
||||
|
||||
@@ -58,4 +58,4 @@ auto Region::PAL() -> bool { return system.region() == System::Region::PAL; }
|
||||
|
||||
auto Mega32X() -> bool { return system.mega32X(); }
|
||||
auto MegaCD() -> bool { return system.megaCD(); }
|
||||
auto MegaLD() -> bool { return system.megaLD(); }
|
||||
auto MegaLD() -> bool { return system.megaLD(); }
|
||||
@@ -67,7 +67,7 @@ auto VDP::main() -> void {
|
||||
} else if(hcounter() == 0x80) {
|
||||
if(vcounter() < screenHeight() && !runAhead()) {
|
||||
render();
|
||||
if(Mega32X()) m32x.vdp.scanline(pixels() + 18, vcounter()); //approx 3 and 1/4 pixel offset
|
||||
if(Mega32X()) m32x.vdp.scanline(pixels() + 13, vcounter()); //approx 3 and 1/4 pixel offset in H40 pixels
|
||||
}
|
||||
|
||||
step(768);
|
||||
@@ -81,7 +81,7 @@ auto VDP::main() -> void {
|
||||
vedge();
|
||||
|
||||
if(vcounter() == 0) {
|
||||
latch.interlace = io.interlaceMode == 3;
|
||||
latch.interlace = io.interlaceMode.bit(0);
|
||||
latch.overscan = io.overscan;
|
||||
frame();
|
||||
state.field ^= 1;
|
||||
|
||||
@@ -168,7 +168,7 @@ auto VDP::main() -> void {
|
||||
if(h40()) mainH40();
|
||||
if(vcounter() == state.bottomline) {
|
||||
screen->setColorBleedWidth(latch.displayWidth ? 4 : 5);
|
||||
latch.interlace = io.interlaceMode == 3;
|
||||
latch.interlace = io.interlaceMode.bit(0);
|
||||
latch.overscan = io.overscan;
|
||||
frame();
|
||||
state.field ^= 1;
|
||||
@@ -184,7 +184,7 @@ auto VDP::mainH32() -> void {
|
||||
sprite.begin();
|
||||
if(dac.pixels) {
|
||||
blocks<false, true>();
|
||||
if(Mega32X()) m32x.vdp.scanline(pixels + 18, vcounter()); //approx 3 and 1/4 pixel offset
|
||||
if(Mega32X()) m32x.vdp.scanline(pixels + 13, vcounter()); //approx 3 and 1/4 pixel offset in H40 pixels
|
||||
if(MegaLD()) mcd.ld.scanline(dac.pixels, vcounter());
|
||||
} else {
|
||||
blocks<false, false>();
|
||||
|
||||
@@ -82,6 +82,9 @@ auto PSG::writeIO(n1 port, n8 data) -> void {
|
||||
|
||||
// This bit controls which port is read through PSG register 14
|
||||
controllerMux.select(data.bit(6));
|
||||
|
||||
// This bit drives the cassette output signal.
|
||||
tapeDeck.write(data.bit(7));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
static const string SerializerVersion = "v132";
|
||||
static const string SerializerVersion = "v133";
|
||||
|
||||
auto System::serialize(bool synchronize) -> serializer {
|
||||
if(synchronize) scheduler.enter(Scheduler::Mode::Synchronize);
|
||||
@@ -47,5 +47,6 @@ auto System::serialize(serializer& s, bool synchronize) -> void {
|
||||
expansion.serialize(s);
|
||||
controllerPort1.serialize(s);
|
||||
controllerPort2.serialize(s);
|
||||
tapeDeck.tray.tape.serialize(s);
|
||||
rtc.serialize(s);
|
||||
}
|
||||
@@ -6,17 +6,10 @@ TapeDeck::TapeDeck(string name) : name(name) {
|
||||
auto TapeDeck::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Peripheral>(name);
|
||||
|
||||
play = node->append<Node::Setting::Boolean>("Playing", false, [&](auto value) {
|
||||
if(value == 0) state.playing = 0;
|
||||
if(value == 1) state.playing = 1;
|
||||
});
|
||||
play->setDynamic(true);
|
||||
|
||||
tray.load(node);
|
||||
}
|
||||
|
||||
auto TapeDeck::power() -> void {
|
||||
state = {};
|
||||
}
|
||||
|
||||
auto TapeDeck::unload() -> void {
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
struct TapeDeck {
|
||||
Node::Peripheral node;
|
||||
Node::Setting::Boolean play;
|
||||
TapeDeckTray tray;
|
||||
|
||||
TapeDeck(string name);
|
||||
|
||||
auto playing() -> bool { return state.playing; }
|
||||
auto read() -> u1 { return state.output; }
|
||||
auto playing() -> bool { return tray.tape.node && tray.tape.node->playing(); }
|
||||
auto read() -> u1 { return tray.tape.read(); }
|
||||
auto write(n1 data) -> void { tray.tape.write(data); }
|
||||
|
||||
auto load(Node::Object) -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
const string name;
|
||||
|
||||
struct {
|
||||
bool playing;
|
||||
u1 output;
|
||||
} state;
|
||||
};
|
||||
|
||||
extern TapeDeck tapeDeck;
|
||||
+142
-32
@@ -6,56 +6,103 @@ namespace ares::MSX {
|
||||
#include "tray.cpp"
|
||||
|
||||
auto Tape::allocate(Node::Port parent) -> Node::Peripheral {
|
||||
node = parent->append<Node::Peripheral>("MSX Tape");
|
||||
position = 0;
|
||||
length = 0;
|
||||
node = parent->append<Node::Tape>("MSX Tape");
|
||||
node->setSupportPlay(true);
|
||||
node->setSupportRecord(false);
|
||||
node->setLoad([&] { return load(); });
|
||||
node->setUnload([&] { unload(); });
|
||||
node->setPosition(0);
|
||||
node->setLength(0);
|
||||
node->setFrequency(44100);
|
||||
|
||||
range = 0;
|
||||
output = 0;
|
||||
input = 0;
|
||||
|
||||
stream = node->append<Node::Audio::Stream>("Audio");
|
||||
stream->setChannels(1);
|
||||
stream->setFrequency(node->frequency());
|
||||
Thread::create(node->frequency(), [&] { Tape::main(); });
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
auto Tape::connect() -> void {
|
||||
if(!node->setPak(pak = platform->pak(node))) return;
|
||||
auto Tape::connect() -> bool {
|
||||
return node && node->load();
|
||||
}
|
||||
|
||||
auto Tape::load() -> bool {
|
||||
if(!node->setPak(pak = platform->pak(node))) return false;
|
||||
|
||||
information = {};
|
||||
information.title = pak->attribute("title");
|
||||
|
||||
auto fd = pak->read("program.tape");
|
||||
if(!fd) return disconnect();
|
||||
|
||||
data.allocate(fd->size());
|
||||
data.load(fd);
|
||||
fd.reset();
|
||||
|
||||
range = pak->attribute("range").natural();
|
||||
frequency = pak->attribute("frequency").natural();
|
||||
length = pak->attribute("length").natural();
|
||||
pak->setAttribute("modified", false);
|
||||
node->setFrequency(pak->attribute("frequency").natural());
|
||||
node->setLength(pak->attribute("length").natural());
|
||||
node->setPosition(0);
|
||||
node->setSupportRecord(pak->attribute("writable").boolean());
|
||||
stream->setFrequency(node->frequency());
|
||||
Thread::setFrequency(node->frequency());
|
||||
|
||||
stream = node->append<Node::Audio::Stream>("Audio");
|
||||
stream->setChannels(1);
|
||||
stream->setFrequency(frequency);
|
||||
Thread::create(frequency, [&] { Tape::main(); });
|
||||
if(node->length() != 0) {
|
||||
auto fd = pak->read("program.tape");
|
||||
if(!fd) return false;
|
||||
data.allocate(node->length());
|
||||
data.load(fd);
|
||||
fd.reset();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Tape::main() -> void {
|
||||
if (!tapeDeck.state.playing) {
|
||||
stream->frame(0.0f);
|
||||
u64 position = node->position();
|
||||
u64 length = node->length();
|
||||
|
||||
if(node->playing()) {
|
||||
if(position >= length) {
|
||||
output = 0;
|
||||
node->stop();
|
||||
step(1);
|
||||
return;
|
||||
}
|
||||
|
||||
u64 sample = data.read(position++);
|
||||
node->setPosition(position);
|
||||
|
||||
stream->frame((float)sample / (float)range);
|
||||
output = sample > (range / 2);
|
||||
step(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (position > length) {
|
||||
tapeDeck.state.playing = 0;
|
||||
tapeDeck.play->setValue(false);
|
||||
if(node->recording()) {
|
||||
if(length <= position) {
|
||||
auto fd = pak->write("program.tape");
|
||||
fd->resize(data.size() * sizeof(u64));
|
||||
data.save(fd);
|
||||
fd.reset();
|
||||
|
||||
length = (position / node->frequency() + 1) * node->frequency();
|
||||
fd = pak->read("program.tape");
|
||||
data.allocate(length, 0);
|
||||
data.load(fd);
|
||||
fd.reset();
|
||||
node->setLength(length);
|
||||
}
|
||||
|
||||
data[position++] = (range >> 1) + (input ? 1 : 0);
|
||||
pak->setAttribute("modified", true);
|
||||
node->setPosition(position);
|
||||
stream->frame(input ? 1.0f : 0.0f);
|
||||
step(1);
|
||||
return;
|
||||
}
|
||||
|
||||
u64 sample = data.read(position);
|
||||
position++;
|
||||
|
||||
stream->frame((float)sample / (float)range);
|
||||
tapeDeck.state.output = sample > (range / 2);
|
||||
|
||||
output = 0;
|
||||
stream->frame(0.0f);
|
||||
step(1);
|
||||
}
|
||||
|
||||
@@ -66,11 +113,74 @@ auto Tape::step(uint clocks) -> void {
|
||||
|
||||
auto Tape::disconnect() -> void {
|
||||
if(!node) return;
|
||||
node->unload();
|
||||
Thread::destroy();
|
||||
data.reset();
|
||||
stream = {};
|
||||
node = {};
|
||||
position = 0;
|
||||
length = 0;
|
||||
}
|
||||
|
||||
auto Tape::unload() -> void {
|
||||
if(!pak) return;
|
||||
|
||||
if(pak->attribute("modified").boolean() && data.size() != 0) {
|
||||
auto fd = pak->write("program.tape");
|
||||
fd->resize(data.size() * sizeof(u64));
|
||||
data.save(fd);
|
||||
fd.reset();
|
||||
}
|
||||
|
||||
data.reset();
|
||||
information = {};
|
||||
pak.reset();
|
||||
node->setSupportRecord(false);
|
||||
node->setFrequency(44100);
|
||||
stream->setFrequency(node->frequency());
|
||||
Thread::setFrequency(node->frequency());
|
||||
node->setPosition(0);
|
||||
node->setLength(0);
|
||||
range = 0;
|
||||
output = 0;
|
||||
input = 0;
|
||||
}
|
||||
|
||||
auto Tape::read() -> n1 {
|
||||
if(!node || !node->playing()) return 0;
|
||||
return output;
|
||||
}
|
||||
|
||||
auto Tape::write(n1 data) -> void {
|
||||
if(!node || !node->recording()) return;
|
||||
input = data;
|
||||
}
|
||||
|
||||
auto Tape::serialize(serializer& s) -> void {
|
||||
u64 position = node ? node->position() : 0;
|
||||
u64 length = node ? node->length() : 0;
|
||||
u64 frequency = node ? node->frequency() : 44100;
|
||||
bool playing = node ? node->playing() : false;
|
||||
bool recording = node ? node->recording() : false;
|
||||
|
||||
Thread::serialize(s);
|
||||
s(position);
|
||||
s(length);
|
||||
s(frequency);
|
||||
s(playing);
|
||||
s(recording);
|
||||
s(range);
|
||||
s(output);
|
||||
s(input);
|
||||
s(data);
|
||||
|
||||
if(s.reading() && node) {
|
||||
node->setPosition(position);
|
||||
node->setLength(length);
|
||||
node->setFrequency(frequency);
|
||||
stream->setFrequency(frequency);
|
||||
Thread::setFrequency(frequency);
|
||||
node->stop();
|
||||
if(playing) node->play();
|
||||
if(recording) node->record();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,18 +1,23 @@
|
||||
struct Tape : Thread {
|
||||
Node::Peripheral node;
|
||||
Node::Tape node;
|
||||
Node::Audio::Stream stream;
|
||||
|
||||
auto title() const -> string { return information.title; }
|
||||
|
||||
//tape.cpp
|
||||
auto allocate(Node::Port) -> Node::Peripheral;
|
||||
auto connect() -> void;
|
||||
auto connect() -> bool;
|
||||
auto disconnect() -> void;
|
||||
auto load() -> bool;
|
||||
auto unload() -> void;
|
||||
auto read() -> n1;
|
||||
auto write(n1 data) -> void;
|
||||
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
|
||||
auto save() -> void;
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
private:
|
||||
struct Information {
|
||||
@@ -20,10 +25,9 @@ private:
|
||||
} information;
|
||||
|
||||
VFS::Pak pak;
|
||||
u64 position;
|
||||
u64 length;
|
||||
u64 range;
|
||||
u64 frequency;
|
||||
n1 output;
|
||||
n1 input;
|
||||
Memory::Writable<u64> data;
|
||||
|
||||
};
|
||||
|
||||
+29
-1
@@ -42,6 +42,7 @@ ares_add_sources(
|
||||
n64
|
||||
INCLUDED #
|
||||
n64.hpp
|
||||
accuracy.hpp
|
||||
CMakeLists.txt
|
||||
)
|
||||
|
||||
@@ -156,6 +157,8 @@ ares_add_sources(
|
||||
cpu/interpreter.cpp
|
||||
cpu/memory.cpp
|
||||
cpu/recompiler.cpp
|
||||
cpu/recompiler-fpu.cpp
|
||||
cpu/recompiler-ipu.cpp
|
||||
cpu/serialization.cpp
|
||||
cpu/tlb.cpp
|
||||
)
|
||||
@@ -360,12 +363,16 @@ endif()
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
n64
|
||||
PRIMARY #
|
||||
INCLUDED #
|
||||
vulkan/parallel-rdp/parallel-rdp/luts.hpp
|
||||
vulkan/parallel-rdp/parallel-rdp/command_ring.hpp
|
||||
vulkan/parallel-rdp/parallel-rdp/rdp_common.hpp
|
||||
vulkan/parallel-rdp/parallel-rdp/rdp_data_structures.hpp
|
||||
vulkan/parallel-rdp/parallel-rdp/rdp_device.hpp
|
||||
vulkan/parallel-rdp/parallel-rdp/rdp_dump_write.hpp
|
||||
vulkan/parallel-rdp/parallel-rdp/rdp_renderer.hpp
|
||||
vulkan/parallel-rdp/parallel-rdp/video_interface.hpp
|
||||
vulkan/parallel-rdp/parallel-rdp/worker_thread.hpp
|
||||
vulkan/parallel-rdp/vulkan/buffer.hpp
|
||||
vulkan/parallel-rdp/vulkan/buffer_pool.hpp
|
||||
vulkan/parallel-rdp/vulkan/command_buffer.hpp
|
||||
@@ -388,6 +395,15 @@ ares_add_sources(
|
||||
vulkan/parallel-rdp/vulkan/semaphore_manager.hpp
|
||||
vulkan/parallel-rdp/vulkan/shader.hpp
|
||||
vulkan/parallel-rdp/vulkan/texture/texture_format.hpp
|
||||
vulkan/parallel-rdp/vulkan/quirks.hpp
|
||||
vulkan/parallel-rdp/vulkan/vulkan_common.hpp
|
||||
vulkan/parallel-rdp/vulkan/limits.hpp
|
||||
vulkan/parallel-rdp/vulkan/wsi.hpp
|
||||
vulkan/parallel-rdp/vulkan/type_to_string.hpp
|
||||
vulkan/parallel-rdp/vulkan/vulkan_headers.hpp
|
||||
vulkan/parallel-rdp/vulkan/vulkan_prerotate.hpp
|
||||
vulkan/parallel-rdp/vulkan/format.hpp
|
||||
vulkan/parallel-rdp/vulkan/device_fossilize.hpp
|
||||
vulkan/parallel-rdp/util/arena_allocator.hpp
|
||||
vulkan/parallel-rdp/util/logging.hpp
|
||||
vulkan/parallel-rdp/util/thread_id.hpp
|
||||
@@ -396,6 +412,18 @@ ares_add_sources(
|
||||
vulkan/parallel-rdp/util/timeline_trace_file.hpp
|
||||
vulkan/parallel-rdp/util/thread_name.hpp
|
||||
vulkan/parallel-rdp/util/environment.hpp
|
||||
vulkan/parallel-rdp/util/hash.hpp
|
||||
vulkan/parallel-rdp/util/intrusive.hpp
|
||||
vulkan/parallel-rdp/util/intrusive_hash_map.hpp
|
||||
vulkan/parallel-rdp/util/read_write_lock.hpp
|
||||
vulkan/parallel-rdp/util/bitops.hpp
|
||||
vulkan/parallel-rdp/util/small_vector.hpp
|
||||
vulkan/parallel-rdp/util/intrusive_list.hpp
|
||||
vulkan/parallel-rdp/util/object_pool.hpp
|
||||
vulkan/parallel-rdp/util/temporary_hashmap.hpp
|
||||
vulkan/parallel-rdp/util/enum_cast.hpp
|
||||
vulkan/parallel-rdp/util/stack_allocator.hpp
|
||||
vulkan/parallel-rdp/util/dynamic_array.hpp
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
|
||||
@@ -6,6 +6,9 @@ struct Accuracy {
|
||||
static constexpr bool Interpreter = 0 | Reference | !recompiler::generic::supported;
|
||||
static constexpr bool Recompiler = !Interpreter;
|
||||
|
||||
//Maximum number of cycles to run the CPU without synchronization
|
||||
static constexpr s64 JitInterleaving = 2048 * 2;
|
||||
|
||||
//exceptions when the CPU accesses unaligned memory addresses
|
||||
static constexpr bool AddressErrors = 1 | Reference;
|
||||
};
|
||||
|
||||
+29
-19
@@ -26,47 +26,57 @@ auto AI::unload() -> void {
|
||||
|
||||
auto AI::main() -> void {
|
||||
while(Thread::clock < 0) {
|
||||
f64 left = 0, right = 0;
|
||||
sample(left, right);
|
||||
stream->frame(left, right);
|
||||
sample();
|
||||
stream->frame(dac.left, dac.right);
|
||||
step(dac.period);
|
||||
}
|
||||
}
|
||||
|
||||
auto AI::sample(f64& left, f64& right) -> void {
|
||||
if(io.dmaCount == 0) return;
|
||||
auto AI::sample() -> void {
|
||||
bool active = false;
|
||||
|
||||
if(io.dmaLength[0] && io.dmaEnable) {
|
||||
if(io.dmaCount && io.dmaLength[0] && io.dmaEnable) {
|
||||
io.dmaAddress[0].bit(13,23) += io.dmaAddressCarry;
|
||||
auto data = rdram.ram.read<Word>(io.dmaAddress[0], "AI");
|
||||
auto l = s16(data >> 16);
|
||||
auto r = s16(data >> 0);
|
||||
left = l / 32768.0;
|
||||
right = r / 32768.0;
|
||||
auto data = rdram.ram.read<Word>(io.dmaAddress[0], RBusDevice::AI_DMA);
|
||||
dac.left = (s16)(data >> 16) / 32768.0;
|
||||
dac.right = (s16)(data >> 0) / 32768.0;
|
||||
|
||||
io.dmaAddress[0].bit(0,12) += 4;
|
||||
io.dmaAddressCarry = io.dmaAddress[0].bit(0,12) == 0;
|
||||
io.dmaLength[0] -= 4;
|
||||
io.dmaAddressCarry = io.dmaAddress[0].bit(0,12) == 0;
|
||||
io.dmaLength[0] -= 4;
|
||||
active = true;
|
||||
}
|
||||
if(!io.dmaLength[0]) {
|
||||
|
||||
if(io.dmaCount && io.dmaLength[0] == 0) {
|
||||
if(--io.dmaCount) {
|
||||
io.dmaAddress[0] = io.dmaAddress[1];
|
||||
io.dmaLength [0] = io.dmaLength [1];
|
||||
io.dmaLength[0] = io.dmaLength[1];
|
||||
io.dmaOriginPc[0] = io.dmaOriginPc[1];
|
||||
mi.raise(MI::IRQ::AI);
|
||||
}
|
||||
}
|
||||
|
||||
if(!active) {
|
||||
dac.left *= dac.decayFactor;
|
||||
dac.right *= dac.decayFactor;
|
||||
if(fabs(dac.left) < 1e-7) dac.left = 0.0;
|
||||
if(fabs(dac.right) < 1e-7) dac.right = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
auto AI::updateDecay() -> void {
|
||||
dac.decayFactor = exp(-1.0 / (dac.frequency * 0.003));
|
||||
}
|
||||
|
||||
auto AI::power(bool reset) -> void {
|
||||
Thread::reset();
|
||||
|
||||
fifo[0] = {};
|
||||
fifo[1] = {};
|
||||
io = {};
|
||||
dac.left = 0.0;
|
||||
dac.right = 0.0;
|
||||
dac.frequency = 44100;
|
||||
dac.precision = 16;
|
||||
dac.period = system.frequency() / dac.frequency;
|
||||
dac.period = system.frequency() / dac.frequency;
|
||||
updateDecay();
|
||||
}
|
||||
|
||||
}
|
||||
+5
-1
@@ -18,8 +18,9 @@ struct AI : Thread, Memory::RCP<AI> {
|
||||
auto load(Node::Object) -> void;
|
||||
auto unload() -> void;
|
||||
auto main() -> void;
|
||||
auto sample(f64& left, f64& right) -> void;
|
||||
auto sample() -> void;
|
||||
auto power(bool reset) -> void;
|
||||
auto updateDecay() -> void;
|
||||
|
||||
//io.cpp
|
||||
auto readWord(u32 address, Thread& thread) -> u32;
|
||||
@@ -47,6 +48,9 @@ struct AI : Thread, Memory::RCP<AI> {
|
||||
u32 frequency;
|
||||
u32 precision;
|
||||
u32 period;
|
||||
f64 left;
|
||||
f64 right;
|
||||
f64 decayFactor;
|
||||
} dac;
|
||||
};
|
||||
|
||||
|
||||
+5
-1
@@ -15,6 +15,7 @@ auto AI::readWord(u32 address, Thread& thread) -> u32 {
|
||||
data.bit(25) = io.dmaEnable;
|
||||
data.bit(30) = io.dmaCount > 0;
|
||||
data.bit(31) = io.dmaCount > 1;
|
||||
cpu.forceSynchronize();
|
||||
}
|
||||
|
||||
debugger.io(Read, address, data);
|
||||
@@ -59,7 +60,10 @@ auto AI::writeWord(u32 address, u32 data_, Thread& thread) -> void {
|
||||
io.dacRate = data.bit(0,13);
|
||||
dac.frequency = max(1, system.videoFrequency() / (io.dacRate + 1));
|
||||
dac.period = system.frequency() / dac.frequency;
|
||||
if(frequency != dac.frequency) stream->setFrequency(dac.frequency);
|
||||
if(frequency != dac.frequency) {
|
||||
stream->setFrequency(dac.frequency);
|
||||
updateDecay();
|
||||
}
|
||||
}
|
||||
|
||||
if(address == 5) {
|
||||
|
||||
@@ -16,8 +16,12 @@ auto AI::serialize(serializer& s) -> void {
|
||||
s(dac.frequency);
|
||||
s(dac.precision);
|
||||
s(dac.period);
|
||||
s(dac.left);
|
||||
s(dac.right);
|
||||
s(dac.decayFactor);
|
||||
|
||||
if(s.reading() && stream->frequency() != dac.frequency) {
|
||||
stream->setFrequency(dac.frequency);
|
||||
updateDecay();
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ namespace ares::Nintendo64 {
|
||||
vram.allocate(4_KiB);
|
||||
pram.allocate(4_KiB);
|
||||
controls.load(parent);
|
||||
mahjong.load(parent);
|
||||
gameConfig.reset();
|
||||
dipSwitchNode = parent->append<Node::Object>("DIP Switches");
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ struct Aleck64 {
|
||||
Writable(Aleck64& self) : self(self) {}
|
||||
|
||||
template<u32 Size>
|
||||
auto writeBurst(u32 address, u32 *value, const char *peripheral) -> void {
|
||||
auto writeBurst(u32 address, u32 *value, RBusDevice device) -> void {
|
||||
address = address & 0x00ff'ffff;
|
||||
if(address >= size) return;
|
||||
Memory::Writable::write<Word>(address | 0x00, value[0]);
|
||||
@@ -24,7 +24,7 @@ struct Aleck64 {
|
||||
}
|
||||
|
||||
template<u32 Size>
|
||||
auto readBurst(u32 address, u32 *value, const char *peripheral) -> void {
|
||||
auto readBurst(u32 address, u32 *value, RBusDevice device) -> void {
|
||||
address = address & 0x00ff'ffff;
|
||||
if(address >= size) {
|
||||
value[0] = value[1] = value[2] = value[3] = 0;
|
||||
@@ -96,6 +96,7 @@ struct Aleck64 {
|
||||
}
|
||||
|
||||
controls.poll();
|
||||
mahjong.poll();
|
||||
if(address <= 0xc080'0fff) {
|
||||
n32 value = 0xffff'ffff;
|
||||
|
||||
@@ -184,36 +185,46 @@ struct Aleck64 {
|
||||
Node::Input::Button p2start;
|
||||
Node::Input::Button p2coin;
|
||||
|
||||
Node::Input::Button mahjongA;
|
||||
Node::Input::Button mahjongB;
|
||||
Node::Input::Button mahjongC;
|
||||
Node::Input::Button mahjongD;
|
||||
Node::Input::Button mahjongE;
|
||||
Node::Input::Button mahjongF;
|
||||
Node::Input::Button mahjongG;
|
||||
Node::Input::Button mahjongH;
|
||||
Node::Input::Button mahjongI;
|
||||
Node::Input::Button mahjongJ;
|
||||
Node::Input::Button mahjongK;
|
||||
Node::Input::Button mahjongL;
|
||||
Node::Input::Button mahjongM;
|
||||
Node::Input::Button mahjongN;
|
||||
Node::Input::Button mahjongKan;
|
||||
Node::Input::Button mahjongPon;
|
||||
Node::Input::Button mahjongChi;
|
||||
Node::Input::Button mahjongReach;
|
||||
Node::Input::Button mahjongRon;
|
||||
|
||||
//controls.cpp
|
||||
auto load(Node::Object) -> void;
|
||||
auto poll() -> void;
|
||||
|
||||
auto controllerButton(int playerIndex, string button) -> bool;
|
||||
auto controllerAxis(int playerIndex, string axis) -> s64;
|
||||
auto mahjong(n8 row) -> n8;
|
||||
|
||||
} controls{*this};
|
||||
|
||||
struct Mahjong {
|
||||
Aleck64& self;
|
||||
Node::Object node;
|
||||
|
||||
Node::Input::Button a;
|
||||
Node::Input::Button b;
|
||||
Node::Input::Button c;
|
||||
Node::Input::Button d;
|
||||
Node::Input::Button e;
|
||||
Node::Input::Button f;
|
||||
Node::Input::Button g;
|
||||
Node::Input::Button h;
|
||||
Node::Input::Button i;
|
||||
Node::Input::Button j;
|
||||
Node::Input::Button k;
|
||||
Node::Input::Button l;
|
||||
Node::Input::Button m;
|
||||
Node::Input::Button n;
|
||||
Node::Input::Button kan;
|
||||
Node::Input::Button pon;
|
||||
Node::Input::Button chi;
|
||||
Node::Input::Button reach;
|
||||
Node::Input::Button ron;
|
||||
|
||||
//controls.cpp
|
||||
auto load(Node::Object) -> void;
|
||||
auto poll() -> void;
|
||||
auto read(n8 row) -> n8;
|
||||
|
||||
} mahjong{*this};
|
||||
|
||||
struct VDP {
|
||||
auto readWord(u32 address) -> u32;
|
||||
auto writeWord(u32 address, n32 data) -> void;
|
||||
|
||||
@@ -32,25 +32,6 @@ auto Aleck64::Controls::load(Node::Object parent) -> void {
|
||||
p2[n] = node->append<Node::Input::Button>(name);
|
||||
}
|
||||
|
||||
mahjongA = node->append<Node::Input::Button>("Mahjong A");
|
||||
mahjongB = node->append<Node::Input::Button>("Mahjong B");
|
||||
mahjongC = node->append<Node::Input::Button>("Mahjong C");
|
||||
mahjongD = node->append<Node::Input::Button>("Mahjong D");
|
||||
mahjongE = node->append<Node::Input::Button>("Mahjong E");
|
||||
mahjongF = node->append<Node::Input::Button>("Mahjong F");
|
||||
mahjongG = node->append<Node::Input::Button>("Mahjong G");
|
||||
mahjongH = node->append<Node::Input::Button>("Mahjong H");
|
||||
mahjongI = node->append<Node::Input::Button>("Mahjong I");
|
||||
mahjongJ = node->append<Node::Input::Button>("Mahjong J");
|
||||
mahjongK = node->append<Node::Input::Button>("Mahjong K");
|
||||
mahjongL = node->append<Node::Input::Button>("Mahjong L");
|
||||
mahjongM = node->append<Node::Input::Button>("Mahjong M");
|
||||
mahjongN = node->append<Node::Input::Button>("Mahjong N");
|
||||
mahjongKan = node->append<Node::Input::Button>("Mahjong カン");
|
||||
mahjongPon = node->append<Node::Input::Button>("Mahjong ポン");
|
||||
mahjongChi = node->append<Node::Input::Button>("Mahjong チー");
|
||||
mahjongReach = node->append<Node::Input::Button>("Mahjong リーチ");
|
||||
mahjongRon = node->append<Node::Input::Button>("Mahjong ロン");
|
||||
}
|
||||
|
||||
auto Aleck64::Controls::poll() -> void {
|
||||
@@ -82,26 +63,6 @@ auto Aleck64::Controls::poll() -> void {
|
||||
for(auto n : range(9)) {
|
||||
platform->input(p2[n]);
|
||||
}
|
||||
|
||||
platform->input(mahjongA);
|
||||
platform->input(mahjongB);
|
||||
platform->input(mahjongC);
|
||||
platform->input(mahjongD);
|
||||
platform->input(mahjongE);
|
||||
platform->input(mahjongF);
|
||||
platform->input(mahjongG);
|
||||
platform->input(mahjongH);
|
||||
platform->input(mahjongI);
|
||||
platform->input(mahjongJ);
|
||||
platform->input(mahjongK);
|
||||
platform->input(mahjongL);
|
||||
platform->input(mahjongM);
|
||||
platform->input(mahjongN);
|
||||
platform->input(mahjongKan);
|
||||
platform->input(mahjongPon);
|
||||
platform->input(mahjongChi);
|
||||
platform->input(mahjongReach);
|
||||
platform->input(mahjongRon);
|
||||
}
|
||||
|
||||
auto Aleck64::Controls::controllerButton(int playerIndex, string button) -> bool {
|
||||
@@ -146,40 +107,86 @@ auto Aleck64::Controls::controllerAxis(int playerIndex, string axis) -> s64 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto Aleck64::Controls::mahjong(n8 row) -> n8 {
|
||||
auto Aleck64::Mahjong::load(Node::Object parent) -> void {
|
||||
node = parent->append<Node::Object>("Mahjong");
|
||||
|
||||
a = node->append<Node::Input::Button>("A");
|
||||
b = node->append<Node::Input::Button>("B");
|
||||
c = node->append<Node::Input::Button>("C");
|
||||
d = node->append<Node::Input::Button>("D");
|
||||
e = node->append<Node::Input::Button>("E");
|
||||
f = node->append<Node::Input::Button>("F");
|
||||
g = node->append<Node::Input::Button>("G");
|
||||
h = node->append<Node::Input::Button>("H");
|
||||
i = node->append<Node::Input::Button>("I");
|
||||
j = node->append<Node::Input::Button>("J");
|
||||
k = node->append<Node::Input::Button>("K");
|
||||
l = node->append<Node::Input::Button>("L");
|
||||
m = node->append<Node::Input::Button>("M");
|
||||
n = node->append<Node::Input::Button>("N");
|
||||
kan = node->append<Node::Input::Button>("カン");
|
||||
pon = node->append<Node::Input::Button>("ポン");
|
||||
chi = node->append<Node::Input::Button>("チー");
|
||||
reach = node->append<Node::Input::Button>("リーチ");
|
||||
ron = node->append<Node::Input::Button>("ロン");
|
||||
}
|
||||
|
||||
auto Aleck64::Mahjong::poll() -> void {
|
||||
platform->input(a);
|
||||
platform->input(b);
|
||||
platform->input(c);
|
||||
platform->input(d);
|
||||
platform->input(e);
|
||||
platform->input(f);
|
||||
platform->input(g);
|
||||
platform->input(h);
|
||||
platform->input(i);
|
||||
platform->input(j);
|
||||
platform->input(k);
|
||||
platform->input(l);
|
||||
platform->input(m);
|
||||
platform->input(n);
|
||||
platform->input(kan);
|
||||
platform->input(pon);
|
||||
platform->input(chi);
|
||||
platform->input(reach);
|
||||
platform->input(ron);
|
||||
}
|
||||
|
||||
auto Aleck64::Mahjong::read(n8 row) -> n8 {
|
||||
n8 value = 0xff;
|
||||
|
||||
if(row.bit(0)) {
|
||||
value.bit(1) &= !aleck64.controls.mahjongB->value();
|
||||
value.bit(2) &= !aleck64.controls.mahjongF->value();
|
||||
value.bit(3) &= !aleck64.controls.mahjongJ->value();
|
||||
value.bit(4) &= !aleck64.controls.mahjongN->value();
|
||||
value.bit(5) &= !aleck64.controls.mahjongReach->value();
|
||||
value.bit(1) &= !b->value();
|
||||
value.bit(2) &= !f->value();
|
||||
value.bit(3) &= !j->value();
|
||||
value.bit(4) &= !n->value();
|
||||
value.bit(5) &= !reach->value();
|
||||
}
|
||||
|
||||
if(row.bit(1)) {
|
||||
value.bit(0) &= !aleck64.controls.p1start->value();
|
||||
value.bit(1) &= !aleck64.controls.mahjongA->value();
|
||||
value.bit(2) &= !aleck64.controls.mahjongE->value();
|
||||
value.bit(3) &= !aleck64.controls.mahjongI->value();
|
||||
value.bit(4) &= !aleck64.controls.mahjongM->value();
|
||||
value.bit(5) &= !aleck64.controls.mahjongKan->value();
|
||||
value.bit(1) &= !a->value();
|
||||
value.bit(2) &= !e->value();
|
||||
value.bit(3) &= !i->value();
|
||||
value.bit(4) &= !m->value();
|
||||
value.bit(5) &= !kan->value();
|
||||
}
|
||||
|
||||
if(row.bit(2)) {
|
||||
value.bit(1) &= !aleck64.controls.mahjongC->value();
|
||||
value.bit(2) &= !aleck64.controls.mahjongG->value();
|
||||
value.bit(3) &= !aleck64.controls.mahjongK->value();
|
||||
value.bit(4) &= !aleck64.controls.mahjongChi->value();
|
||||
value.bit(5) &= !aleck64.controls.mahjongRon->value();
|
||||
value.bit(1) &= !c->value();
|
||||
value.bit(2) &= !g->value();
|
||||
value.bit(3) &= !k->value();
|
||||
value.bit(4) &= !chi->value();
|
||||
value.bit(5) &= !ron->value();
|
||||
}
|
||||
|
||||
if(row.bit(3)) {
|
||||
value.bit(1) &= !aleck64.controls.mahjongD->value();
|
||||
value.bit(2) &= !aleck64.controls.mahjongH->value();
|
||||
value.bit(3) &= !aleck64.controls.mahjongL->value();
|
||||
value.bit(4) &= !aleck64.controls.mahjongPon->value();
|
||||
value.bit(1) &= !d->value();
|
||||
value.bit(2) &= !h->value();
|
||||
value.bit(3) &= !l->value();
|
||||
value.bit(4) &= !pon->value();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ struct hipai : Aleck64::GameConfig {
|
||||
|
||||
auto readExpansionPort() -> u32 {
|
||||
n32 value = 0xffff'ffff;
|
||||
value.bit(16, 23) = aleck64.controls.mahjong(mahjongRow);
|
||||
value.bit(16, 23) = aleck64.mahjong.read(mahjongRow);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 1363 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user