-
Notifications
You must be signed in to change notification settings - Fork 131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add iQue Player support #1731
base: master
Are you sure you want to change the base?
Add iQue Player support #1731
Changes from all commits
7e661b9
c2fdc2d
2923650
a31af04
9335a39
32a2e2a
8b9d6c4
6e9a893
d237b32
032b00d
08f029e
647b2ad
e84f8f0
af21457
a20604a
923bd6e
487bf3c
62dc68d
3f251c8
beca905
a692de8
6a84109
53cbd5e
f46b3b3
665e79e
c3261a7
b74f8e6
85cdfff
50aa742
1f7fbbb
14d5bd9
3fdcce1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
/* Vaguely based on Aes.c -- AES encryption / decryption | ||
2017-01-24 : Igor Pavlov : Public domain */ | ||
|
||
#include <n64/n64.hpp> | ||
|
||
namespace ares::Nintendo64 { | ||
|
||
AES aes; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe the actual algorithm should go to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with this; nall already has a construct for cryptography, so assuming it is the standard algorithm it should go there There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've bound this implementation very closely with the N64 core resident structures, especially If you could give more specific direction on how to make the implementation more generic without going through some glue code I can adjust it accordingly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a very good point, we'll keep it here then if it's specialised to the memory layout! |
||
#include "serialization.cpp" | ||
|
||
static const u8 InvS[256] = { | ||
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, | ||
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, | ||
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, | ||
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, | ||
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, | ||
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, | ||
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, | ||
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, | ||
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, | ||
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, | ||
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, | ||
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, | ||
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, | ||
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, | ||
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, | ||
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D, | ||
}; | ||
|
||
static inline u32 U32(u8 a0, u8 a1, u8 a2, u8 a3) { | ||
return (a0 << 24) | (a1 << 16) | (a2 << 8) | (a3 << 0); | ||
} | ||
|
||
template<u32 n> | ||
static inline u8 GB(u32 x) { | ||
return (x >> (24 - 8 * n)) & 0xFF; | ||
} | ||
|
||
AES::AES() { | ||
#define xtime(x) ((((x) << 1) ^ (((x) & 0x80) ? 0x1B : 0)) & 0xFF) | ||
for (auto i : range(256)) { | ||
u32 a1 = InvS[i]; | ||
u32 a2 = xtime(a1); | ||
u32 a4 = xtime(a2); | ||
u32 a8 = xtime(a4); | ||
u32 a9 = a8 ^ a1; | ||
u32 aB = a8 ^ a2 ^ a1; | ||
u32 aD = a8 ^ a4 ^ a1; | ||
u32 aE = a8 ^ a4 ^ a2; | ||
D[0][i] = U32(aE, a9, aD, aB); | ||
D[1][i] = U32(aB, aE, a9, aD); | ||
D[2][i] = U32(aD, aB, aE, a9); | ||
D[3][i] = U32(a9, aD, aB, aE); | ||
} | ||
#undef xtime | ||
} | ||
|
||
auto AES::decodeCBC(Memory::Writable &mem, u32 pos, u32 numBlocks) -> void { | ||
if constexpr(Accuracy::AES::SIMD) { | ||
__m128i iv = mvIV; | ||
|
||
for (; numBlocks >= 4; numBlocks -= 4, pos += 4 * AES_BLOCK_SIZE) { | ||
__m128i *key = mvKey; | ||
__m128i i0, i1, i2, i3; | ||
__m128i m0, m1, m2, m3; | ||
__m128i t; | ||
|
||
i0 = read128(mem, pos + 0 * AES_BLOCK_SIZE); | ||
i1 = read128(mem, pos + 1 * AES_BLOCK_SIZE); | ||
i2 = read128(mem, pos + 2 * AES_BLOCK_SIZE); | ||
i3 = read128(mem, pos + 3 * AES_BLOCK_SIZE); | ||
|
||
t = *key++; | ||
m0 = _mm_xor_si128(i0, t); | ||
m1 = _mm_xor_si128(i1, t); | ||
m2 = _mm_xor_si128(i2, t); | ||
m3 = _mm_xor_si128(i3, t); | ||
|
||
for (auto r : range(9)) { | ||
t = *key++; | ||
m0 = _mm_aesdec_si128(m0, t); | ||
m1 = _mm_aesdec_si128(m1, t); | ||
m2 = _mm_aesdec_si128(m2, t); | ||
m3 = _mm_aesdec_si128(m3, t); | ||
} | ||
t = *key++; | ||
m0 = _mm_aesdeclast_si128(m0, t); | ||
m1 = _mm_aesdeclast_si128(m1, t); | ||
m2 = _mm_aesdeclast_si128(m2, t); | ||
m3 = _mm_aesdeclast_si128(m3, t); | ||
|
||
t = _mm_xor_si128(m0, iv); iv = i0; write128(mem, pos + 0 * AES_BLOCK_SIZE, t); | ||
t = _mm_xor_si128(m1, iv); iv = i1; write128(mem, pos + 1 * AES_BLOCK_SIZE, t); | ||
t = _mm_xor_si128(m2, iv); iv = i2; write128(mem, pos + 2 * AES_BLOCK_SIZE, t); | ||
t = _mm_xor_si128(m3, iv); iv = i3; write128(mem, pos + 3 * AES_BLOCK_SIZE, t); | ||
} | ||
|
||
while (numBlocks--) { | ||
__m128i *key = mvKey; | ||
__m128i i = read128(mem, pos); | ||
__m128i m = _mm_xor_si128(*key++, i); | ||
|
||
for (auto r : range(9)) | ||
m = _mm_aesdec_si128(m, *key++); | ||
m = _mm_aesdeclast_si128(m, *key++); | ||
|
||
m = _mm_xor_si128(m, iv); iv = i; write128(mem, pos, m); | ||
pos += AES_BLOCK_SIZE; | ||
} | ||
|
||
mvIV = iv; | ||
} | ||
|
||
if constexpr(Accuracy::AES::SISD) { | ||
for (; numBlocks != 0; numBlocks--, pos += AES_BLOCK_SIZE) { | ||
const u32 *key = mKey; | ||
u32 out[4]; | ||
u32 in[4]; | ||
u32 s[4]; | ||
u32 t[4]; | ||
|
||
in[0] = mem.read<Word>(pos + 0 * 4); | ||
in[1] = mem.read<Word>(pos + 1 * 4); | ||
in[2] = mem.read<Word>(pos + 2 * 4); | ||
in[3] = mem.read<Word>(pos + 3 * 4); | ||
|
||
s[0] = in[0] ^ key[0]; | ||
s[1] = in[1] ^ key[1]; | ||
s[2] = in[2] ^ key[2]; | ||
s[3] = in[3] ^ key[3]; | ||
key += 4; | ||
|
||
#define HD(s, i, x) D[x][GB<x>(s[(i - x) & 3])] | ||
#define HD4(s, i) (HD(s, i, 0) ^ HD(s, i, 1) ^ HD(s, i, 2) ^ HD(s, i, 3)) | ||
for (auto r : range(4)) { | ||
t[0] = HD4(s, 0) ^ key[0]; | ||
t[1] = HD4(s, 1) ^ key[1]; | ||
t[2] = HD4(s, 2) ^ key[2]; | ||
t[3] = HD4(s, 3) ^ key[3]; | ||
key += 4; | ||
s[0] = HD4(t, 0) ^ key[0]; | ||
s[1] = HD4(t, 1) ^ key[1]; | ||
s[2] = HD4(t, 2) ^ key[2]; | ||
s[3] = HD4(t, 3) ^ key[3]; | ||
key += 4; | ||
} | ||
t[0] = HD4(s, 0) ^ key[0]; | ||
t[1] = HD4(s, 1) ^ key[1]; | ||
t[2] = HD4(s, 2) ^ key[2]; | ||
t[3] = HD4(s, 3) ^ key[3]; | ||
key += 4; | ||
#undef HD4 | ||
#undef HD | ||
|
||
#define FD(s, i, x) InvS[GB<x>(s[(i - x) & 3])] | ||
#define FD4(s, i) U32(FD(s, i, 0), FD(s, i, 1), FD(s, i, 2), FD(s, i, 3)) | ||
out[0] = mIV[0] ^ FD4(t, 0) ^ key[0]; | ||
out[1] = mIV[1] ^ FD4(t, 1) ^ key[1]; | ||
out[2] = mIV[2] ^ FD4(t, 2) ^ key[2]; | ||
out[3] = mIV[3] ^ FD4(t, 3) ^ key[3]; | ||
#undef FD4 | ||
#undef FD | ||
|
||
mIV[0] = in[0]; | ||
mIV[1] = in[1]; | ||
mIV[2] = in[2]; | ||
mIV[3] = in[3]; | ||
|
||
mem.write<Word>(pos + 0 * 4, out[0]); | ||
mem.write<Word>(pos + 1 * 4, out[1]); | ||
mem.write<Word>(pos + 2 * 4, out[2]); | ||
mem.write<Word>(pos + 3 * 4, out[3]); | ||
} | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
//iQue Player AES-128-CBC Decryption | ||
|
||
class AES { | ||
public: | ||
AES(); | ||
|
||
static constexpr u32 AES_BLOCK_SIZE = 0x10; | ||
|
||
static inline auto swap128(__m128i &v) -> __m128i { | ||
return _mm_shuffle_epi8(v, _mm_set_epi8(12,13,14,15, 8,9,10,11, 4,5,6,7, 0,1,2,3)); | ||
} | ||
|
||
static inline auto read128(Memory::Writable &mem, u32 p) -> __m128i { | ||
return swap128(*(__m128i *)&mem.data[p & mem.maskWord]); | ||
} | ||
|
||
static inline auto write128(Memory::Writable &mem, u32 p, __m128i &v) -> void { | ||
*(__m128i *)&mem.data[p & mem.maskWord] = swap128(v); | ||
} | ||
|
||
inline auto setKey(Memory::Writable& key) -> void { | ||
if constexpr(Accuracy::AES::SIMD) { | ||
for (auto i : range(0xB0 / 0x10)) | ||
mvKey[i] = read128(key, 0x420 + i * 0x10); | ||
} | ||
|
||
if constexpr(Accuracy::AES::SISD) { | ||
for (auto i : range(0xB0 / 4)) | ||
mKey[i] = key.read<Word>(0x420 + i * 4); | ||
} | ||
} | ||
|
||
inline auto setIV(Memory::Writable& iv, u32 offset) -> void { | ||
if constexpr(Accuracy::AES::SIMD) { | ||
mvIV = read128(iv, offset); | ||
} | ||
|
||
if constexpr(Accuracy::AES::SISD) { | ||
for (auto i : range(0x10 / 4)) | ||
mIV[i] = iv.read<Word>(offset + i * 4); | ||
} | ||
} | ||
|
||
auto decodeCBC(Memory::Writable& mem, u32 pos, u32 numBlocks) -> void; | ||
|
||
//serialization.cpp | ||
auto serialize(serializer&) -> void; | ||
|
||
private: | ||
u32 D[4][256]; | ||
union { | ||
u32 mIV[0x10 / 4]; | ||
__m128i mvIV; | ||
}; | ||
union { | ||
u32 mKey[0xB0 / 4]; | ||
__m128i mvKey[0xB0 / 0x10]; | ||
}; | ||
}; | ||
|
||
extern AES aes; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
auto AES::serialize(serializer& s) -> void { | ||
s(D); | ||
s(mIV); | ||
s(mKey); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,9 @@ Gamepad::Gamepad(Node::Port parent) { | |
r = node->append<Node::Input::Button>("R"); | ||
z = node->append<Node::Input::Button>("Z"); | ||
start = node->append<Node::Input::Button>("Start"); | ||
|
||
if(system._BB() && (parent->name() == "Controller Port 1")) | ||
bb_button = node->append<Node::Input::Button>("Power"); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The N64 core misses emulation of the Reset hardware button. I believe that should go into a menu item rather than being a controller button (also because on N64, that's not on the controller). On iQue it's more blurry because the controller is the console, so maybe this implementation is fine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The “reset” option in the GUI will call system.power() with reset = true signify reset instead of hard power cycle; this might be a good place to handle this for both n64 and dd since it works with the ares (soft)reset already. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is definitely the part of the PR that needs the most work - it isn't really clear how this should be handled here, since it's so different to other platforms. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ares doesn't currently support powering a console off really; it is something that should be addressed eventually but as it stands, the only feature we expose is the initial power on (power:reset = false) and reset (power: reset = true) So I'm not sure how to best handle this here... Implementing power:reset=trueshould be done anyway as it allows the 'reset' menu item to work correctly in ares, though! |
||
|
||
Gamepad::~Gamepad() { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,10 @@ auto ControllerPort::load(Node::Object parent) -> void { | |
port->setHotSwappable(true); | ||
port->setAllocate([&](auto name) { return allocate(name); }); | ||
port->setDisconnect([&] { device.reset(); }); | ||
port->setSupported({"Gamepad", "Mouse"}); | ||
if(system._BB() && (name == "Controller Port 1")) | ||
port->setSupported({"Gamepad"}); | ||
else | ||
port->setSupported({"Gamepad", "Mouse"}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe Mouse in not support on iQue at all? Why allowing it for other ports? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is related to the button issue discussed above - the approach used in this draft is a quick hack to get the button working at all so I could test it, and many aspects of it need redoing. |
||
} | ||
|
||
auto ControllerPort::unload() -> void { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,31 +39,33 @@ auto CPU::main() -> void { | |
vi.refreshed = false; | ||
queue.remove(Queue::GDB_Poll); | ||
if(GDB::server.hasClient()) { | ||
queue.insert(Queue::GDB_Poll, (93750000*2)/60/240); | ||
queue.insert(Queue::GDB_Poll, system.frequency()/60/240); | ||
} | ||
} | ||
|
||
auto CPU::gdbPoll() -> void { | ||
if(GDB::server.hasClient()) { | ||
GDB::server.updateLoop(); | ||
queue.insert(Queue::GDB_Poll, (93750000*2)/60/240); | ||
queue.insert(Queue::GDB_Poll, system.frequency()/60/240); | ||
} | ||
} | ||
|
||
auto CPU::synchronize() -> void { | ||
auto clocks = Thread::clock; | ||
Thread::clock = 0; | ||
|
||
mi.clock -= clocks; | ||
vi.clock -= clocks; | ||
ai.clock -= clocks; | ||
rsp.clock -= clocks; | ||
rdp.clock -= clocks; | ||
pif.clock -= clocks; | ||
if(!system._BB()) pif.clock -= clocks; | ||
mi.main(); | ||
vi.main(); | ||
ai.main(); | ||
rsp.main(); | ||
rdp.main(); | ||
pif.main(); | ||
if(!system._BB()) pif.main(); | ||
|
||
queue.step(clocks, [](u32 event) { | ||
switch(event) { | ||
|
@@ -74,6 +76,12 @@ auto CPU::synchronize() -> void { | |
case Queue::SI_DMA_Read: return si.dmaRead(); | ||
case Queue::SI_DMA_Write: return si.dmaWrite(); | ||
case Queue::SI_BUS_Write: return si.writeFinished(); | ||
case Queue::VIRAGE0_Command: return virage0.commandFinished(); | ||
case Queue::VIRAGE1_Command: return virage1.commandFinished(); | ||
case Queue::VIRAGE2_Command: return virage2.commandFinished(); | ||
case Queue::NAND_Command: return pi.nandCommandFinished(); | ||
case Queue::AES_Command: return pi.aesCommandFinished(); | ||
case Queue::BB_RTC_Tick: return pi.bb_rtc.tickClock(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we prefix all of this enums with `BB_"? |
||
case Queue::RTC_Tick: return cartridge.rtc.tick(); | ||
case Queue::DD_Clock_Tick: return dd.rtc.tickClock(); | ||
case Queue::DD_MECHA_Response: return dd.mechaResponse(); | ||
|
@@ -98,7 +106,8 @@ auto CPU::instruction() -> void { | |
return exception.interrupt(); | ||
} | ||
} | ||
if (scc.nmiPending) { | ||
if (scc.nmiPending || scc.nmiStrobe) { | ||
scc.nmiStrobe = 0; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure why we need two different strobe lines here? I guess one should be sufficient? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is because the NMI behaviour described in the CPU manual doesn't match how ares currently implements it (and how the PIF emulation seems to expect it). Really, we need someone familiar with the CPU core to look into it and figure out exactly how it's supposed to work - this was our approach to get the hardware behaviour to work without disturbing the existing code. |
||
debugger.nmi(); | ||
step(1 * 2); | ||
return exception.nmi(); | ||
|
@@ -163,6 +172,14 @@ auto CPU::power(bool reset) -> void { | |
fenv.setRound(float_env::toNearest); | ||
context.setMode(); | ||
|
||
if (system._BB()) { | ||
//Note: iQue divmode is still 1:1.5 but the value reported is different | ||
scc.configuration.systemClockRatio = 1; | ||
|
||
scc.coprocessor.revision = 0x40; | ||
scc.coprocessor.implementation = 0x0B; | ||
} | ||
|
||
if constexpr(Accuracy::CPU::Recompiler) { | ||
auto buffer = ares::Memory::FixedAllocator::get().tryAcquire(64_MiB); | ||
recompiler.allocator.resize(64_MiB, bump_allocator::executable, buffer); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@LukeUsher is this OK? I don't know how to review changes to Platform.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need both show and set here? Shouldn’t the emulator core just call set and the ui handle it accordingly? A config option could be added in the gui to enable or disable LED display
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
showLED
does the job of the config option, it determines whether the LED will be shown in the UI at all. I think it makes more sense for the guest core to inform the UI that it needs the LED to be shown rather than leaving it to a user option? At least, it seems to fit better with a philosophy of not having too many knobs for users to have to turn.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly to the power button, how to handle the activity LED is a pretty open question - it's not really vital information to show to the user, but it is definitely part of the hardware. The solution here wasn't really intended to be merged, I think, but rather just exists to get something to show the LED state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some systems even have multiple software controlled status LEDs that we should show; I believe Sega CD has two,
I think I’ll create a method for cores to add widgets to the status bar in a similar way cores can add menu options for things like changing discs, then we can use that; the approach in the PR can be merged as a temporary implementation until this frame work exists
The long term plan is that the cores can append the required number of LED components and handle rendering them.