melonDS - now also for macOS!


Yep.
If you want to test it, scroll down to the bottom of the post. I’ll be explaining about what needed to be changed for it to work.

This originally started as a little challenge. "It shouldn't be that hard," I thought. However, it wasn't as easy as I would have hoped, but I got there in the end.

- The JIT recompiler

Thanks Generic (aka RSDuck) for helping me out a lot here and guiding me!

Fastmem

It mapped memory using "memfd_create()" on Linux, which didn't exist on macOS. Instead, on macOS shm_open is used to create the fastmem memory.
macOS also didn't have "->gregs" in "uc_mcontext" and no "REG_RIP" either. This has to be changed to "->__ss.__rip" instead.
Then, it would crash with a "bus error" on attempting to load. This was caused because macOS returned "bus error" instead of "segmentation fault", so the signal handler couldn't handle it.
Note: fastmem was disabled because it caused all sorts of errors while trying to boot firmware or run games. If anyone manages to fix it, send a pull request!

The JIT itself

The JIT would build, but at link time it would complain about "ARM_Dispatch" and "ARM_Ret" being undefined. Apparently in the Mach-O format (used in macOS) global function names defined in assembly are required to be prepended by an underscore.
Then it would crash upon booting firmware or trying to load a game. This was caused by the line here which tried to reprotect some memory to make it executable. On macOS, new memory is now mmap'ed instead.

- The OpenGL renderer

macOS complained about not being able to find "GL/gl.h" and "GL/glext.h". These includes had to be changed to "OpenGL/gl3.h" and "OpenGL/gl3ext.h" on macOS and the OpenGL framework was linked.
Also the functions defined by the OpenGL macronator already existed on macOS, which caused "ambiguous reference" errors. The macronator was ifndef'd out.

- Direct Mode

Direct Mode used "AF_PACKET" to get the MAC address, which doesn't exist on macOS. "AF_LINK" was used instead.
The library names of libpcap had to be changed to "libpcap.A.dylib" and "libpcap.dylib" on macOS.

- Binding Keys

This was a simple fix. For some reason, macOS didn't give focus to the buttons in the key binding menu when they were pressed, which meant that they couldn't detect keys. I had to set the focus policy to Qt::StrongFocus to get them to accept focus.

- App Bundle

Now it built fine and it worked, but it came as a Unix executable, not a macOS app bundle. I had to add some lines in CMakeLists.txt to make it build an app bundle.
I also generated a macOS ".icns" icon file for melonDS, so now the icon showed up on the app bundle.

- No libslirp available in Homebrew

Homebrew (the package manager) didn't have libslirp in their repositories, so I created a pull request here which was merged.


Here are the downloads. If you find any issues, make sure you comment here and tell me so I can fix it!
You will have to install the appropriate libraries beforehand with the Homebrew Package Manager.
In Terminal paste the following command to install the required libraries.
brew install qt@5 sdl2 libslirp
NOTE: melonDS now ships macOS releases as of 0.9.1. We recommend you to use that instead.
melonDS 0.9 beta for macOS x86_64
To unzip the above download, you may need to use a program like The Unarchiver.
AsPika2219 says:
Nov 29th 2020
Nice!
Comlud2 says:
Nov 30th 2020
It's always fun to hear about little tech adventures such as this!
Happy for all the Mac users. Nice work!
HoLLy says:
Nov 30th 2020
Does this include the new ARM Macs? I imagine there wouldn't be that much need for the JIT on those.
poudink says:
Nov 30th 2020
No, there very much would. An ARM64 JIT is still a JIT.
Rayyan says:
Nov 30th 2020
Thanks everyone!
This doesn't include the new ARM Macs, simply because I don't have access to one (for x86_64 Macs I can just spin up a VM).
The new ARM Macs have some translation layer called Rosetta 2, but I don't know how well it would work under it.
TBA-NA says:
Nov 30th 2020
Minimum and Recommended System Requirements?
Joel says:
Dec 1st 2020
Based on my experiences with other emulators and apps (including running games through crossover) on my M1 MBP, this shouldn't have any issues performance wise.

However, I did run into an issue with a missing lib:

Crashed Thread: 0

Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY

Termination Reason: DYLD, [0x1] Library missing

Application Specific Information:
dyld: launch, loading dependent libraries

Dyld Error Message:
dyld: Using shared cache: F90EE091-909D-3DD4-A43F-0C0549DC02EC
Library not loaded: /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib
Referenced from: /Volumes/VOLUME/*/melonDS.app/Contents/MacOS/melonDS
Reason: image not found

I will try again later tonight after making sure my SDL2 installation is not glitching up.
Rayyan says:
Dec 1st 2020
Joel: SDL2, Qt and libslirp need to be installed via Homebrew.
Also the M1 chip is on those new ARM64 Macs, and this is built for x86_64 Macs so I presume you are running it through Rosetta 2.
Because you have one of those new Macs, could you test out the build instructions on your Mac (the instructions are in the README on GitHub) and tell me what errors it returns?
Joel says:
Dec 1st 2020
So far that is as far as I can get, but I think I know what the issue is. I’ll test a theory while at work and let you know.

I do have all 3 items installed in homebrew (and Xcode for QT)
Rayyan says:
Dec 1st 2020
How far did you get up to when building it?
What errors did it give?
Joel says:
Dec 1st 2020
Okay, got it to start up finally. I'm not sure what the problem was, but a clean reinstall of macOS 11.1 beta 1 followed by new installs of homebrew and the associated libraries fixed it. I'll run it tonight and let you know how performance is.
Joel says:
Dec 2nd 2020
WaluigiWare64:

The original issue I had due to the recommended location for homebrew on Arm Macs is different than it is for Intel Macs. Once I reset my MacOS install and put homebrew in the Intel location, your pre-built App started up fine.

Building is a different story. I got quite a few warnings and some errors during the make command. Here they are and where they occurred in the build process.

[ 27%] Building CXX object src/CMakeFiles/core.dir/DSi_SPI_TSC.cpp.o
/Users/user/melonDS/src/DSi_NWifi.cpp:191:49: warning: more '%'
conversions than data arguments [-Wformat]
printf("NWifi: unknown hardware type %02X, assuming AR6002\n");
~~~^
/Users/user/melonDS/src/DSi_NWifi.cpp:695:13: warning: using the result
of an assignment as a condition without parentheses [-Wparentheses]
if (len = Host->DataTX(data, len))
~~~~^~~~~~~~~~~~~~~~~~~~~~~~~
/Users/user/melonDS/src/DSi_NWifi.cpp:695:13: note: place parentheses
around the assignment to silence this warning
if (len = Host->DataTX(data, len))
^
( )
/Users/user/melonDS/src/DSi_NWifi.cpp:695:13: note: use '==' to turn this
assignment into an equality comparison
if (len = Host->DataTX(data, len))
^
==
[ 28%] Building CXX object src/CMakeFiles/core.dir/GBACart.cpp.o
/Users/user/melonDS/src/DSi_SD.cpp:983:13: warning: using the result of
an assignment as a condition without parentheses [-Wparentheses]
if (len = Host->DataTX(data, len))
~~~~^~~~~~~~~~~~~~~~~~~~~~~~~
/Users/user/melonDS/src/DSi_SD.cpp:983:13: note: place parentheses around
the assignment to silence this warning
if (len = Host->DataTX(data, len))
^
( )
/Users/user/melonDS/src/DSi_SD.cpp:983:13: note: use '==' to turn this
assignment into an equality comparison
if (len = Host->DataTX(data, len))
^
==

[ 40%] Building CXX object src/CMakeFiles/core.dir/Savestate.cpp.o
/Users/user/melonDS/src/GPU.cpp:1188:58: warning: static_assert with no
message is a C++17 extension [-Wc++17-extensions]
static_assert(VRAMDirtyGranularity == 512);
^
, ""
/Users/user/melonDS/src/GPU2D.cpp:979:51: warning: static_assert with no
message is a C++17 extension [-Wc++17-extensions]
static_assert(GPU::VRAMDirtyGranularity == 512);
^
, ""
/Users/user/melonDS/src/GPU2D.cpp:1352:13: warning: add explicit braces
to avoid dangling else [-Wdangling-else]
DoInterleaveSprites(0x40000 | (i<<16));
^
/Users/user/melonDS/src/GPU2D.cpp:1303:64: note: expanded from macro
'DoInterleaveSprites'
if (Accelerated) InterleaveSprites<DrawPixel_Accel>(prio); else Inte...
^
/Users/user/melonDS/src/GPU2D.cpp:1376:13: warning: add explicit braces
to avoid dangling else [-Wdangling-else]
DoInterleaveSprites(0x40000 | (i<<16))
^
/Users/user/melonDS/src/GPU2D.cpp:1303:64: note: expanded from macro
'DoInterleaveSprites'
if (Accelerated) InterleaveSprites<DrawPixel_Accel>(prio); else Inte...
^
/Users/user/melonDS/src/GPU2D.cpp:1404:13: warning: add explicit braces
to avoid dangling else [-Wdangling-else]
DoInterleaveSprites(0x40000 | (i<<16))
^
/Users/user/melonDS/src/GPU2D.cpp:1303:64: note: expanded from macro
'DoInterleaveSprites'
if (Accelerated) InterleaveSprites<DrawPixel_Accel>(prio); else Inte...
^
[ 41%] Building CXX object src/CMakeFiles/core.dir/SPI.cpp.o
/Users/user/melonDS/src/GPU2D.cpp:2773:33: warning: operator '<<' has
lower precedence than '-'; '-' will be evaluated first
[-Wshift-op-parentheses]
pixelsaddr += (width-1 << 1);
~~~~~^~ ~~
/Users/user/melonDS/src/GPU2D.cpp:2773:33: note: place parentheses around
the '-' expression to silence this warning
pixelsaddr += (width-1 << 1);
^
( )
/Users/user/melonDS/src/NDS.cpp:1432:74: warning: format specifies type
'unsigned long' but the argument has type 'u64' (aka 'unsigned long long')
[-Wformat]
...(!strcmp(cmd, "totalclks")) sprintf(subs, "%lu", GetSysClockCycles(0));
~~~ ^~~~~~~~~~~~~~~~~~~~
%llu
/Users/user/melonDS/src/NDS.cpp:1433:73: warning: format specifies type
'unsigned long' but the argument has type 'u64' (aka 'unsigned long long')
[-Wformat]
...(!strcmp(cmd, "lastclks")) sprintf(subs, "%lu", GetSysClockCycles(1));
~~~ ^~~~~~~~~~~~~~~~~~~~
%llu
/Users/user/melonDS/src/NDS.cpp:1521:5: warning: 'register' storage class
specifier is deprecated and incompatible with C++17
[-Wdeprecated-register]
register u32 timermask = TimerCheckMask[cpu];
^~~~~~~~~

[ 62%] Building CXX object src/CMakeFiles/core.dir/ARMJIT_A64/ARMJIT_Compiler.cpp.o
In file included from /Users/user/melonDS/src/ARMJIT.cpp:14:
In file included from /Users/user/melonDS/src/ARMJIT_Compiler.h:7:
/Users/user/melonDS/src/ARMJIT_A64/ARMJIT_Compiler.h:52:28: warning: &
has lower precedence than ==; == will be evaluated first [-Wparentheses]
{ return IsImm && (Imm & 0xFFF == Imm); }
^~~~~~~~~~~~~~
/Users/user/melonDS/src/ARMJIT_A64/ARMJIT_Compiler.h:52:28: note: place
parentheses around the '==' expression to silence this warning
{ return IsImm && (Imm & 0xFFF == Imm); }
^
( )
/Users/user/melonDS/src/ARMJIT_A64/ARMJIT_Compiler.h:52:28: note: place
parentheses around the & expression to evaluate it first
{ return IsImm && (Imm & 0xFFF == Imm); }
^
( )
/Users/user/melonDS/src/ARMJIT.cpp:29:53: warning: static_assert with no
message is a C++17 extension [-Wc++17-extensions]
static_assert(offsetof(ARM, CPSR) == ARM_CPSR_offset);
^
, ""
/Users/user/melonDS/src/ARMJIT.cpp:30:57: warning: static_assert with no
message is a C++17 extension [-Wc++17-extensions]
static_assert(offsetof(ARM, Cycles) == ARM_Cycles_offset);
^
, ""
/Users/user/melonDS/src/ARMJIT.cpp:31:71: warning: static_assert with no
message is a C++17 extension [-Wc++17-extensions]
static_assert(offsetof(ARM, StopExecution) == ARM_StopExecution_offset);
^
, ""
In file included from /Users/user/melonDS/src/ARMJIT_Memory.cpp:22:
In file included from /Users/user/melonDS/src/ARMJIT_Compiler.h:7:
/Users/user/melonDS/src/ARMJIT_A64/ARMJIT_Compiler.h:52:28: warning: &
has lower precedence than ==; == will be evaluated first [-Wparentheses]
{ return IsImm && (Imm & 0xFFF == Imm); }
^~~~~~~~~~~~~~
/Users/user/melonDS/src/ARMJIT_A64/ARMJIT_Compiler.h:52:28: note: place
parentheses around the '==' expression to silence this warning
{ return IsImm && (Imm & 0xFFF == Imm); }
^
( )
/Users/user/melonDS/src/ARMJIT_A64/ARMJIT_Compiler.h:52:28: note: place
parentheses around the & expression to evaluate it first
{ return IsImm && (Imm & 0xFFF == Imm); }
^
( )
/Users/user/melonDS/src/ARMJIT_Memory.cpp:174:55: error: member reference
type 'struct __darwin_mcontext64 *' is a pointer; did you mean to use
'->'?
desc.EmulatedFaultAddr = (u8*)context->uc_mcontext.fault_address - curArea;
~~~~~~~~~~~~~~~~~~~~^
->
/Users/user/melonDS/src/ARMJIT_Memory.cpp:174:56: error: no member named
'fault_address' in '__darwin_mcontext64'
desc.EmulatedFaultAddr = (u8*)context->uc_mcontext.fault_address - curArea;
~~~~~~~~~~~~~~~~~~~~ ^
/Users/user/melonDS/src/ARMJIT_Memory.cpp:175:45: error: member reference
type 'struct __darwin_mcontext64 *' is a pointer; did you mean to use
'->'?
desc.FaultPC = (u8*)context->uc_mcontext.pc;
~~~~~~~~~~~~~~~~~~~~^
->
/Users/user/melonDS/src/ARMJIT_Memory.cpp:175:46: error: no member named
'pc' in '__darwin_mcontext64'
desc.FaultPC = (u8*)context->uc_mcontext.pc;
~~~~~~~~~~~~~~~~~~~~ ^
/Users/user/melonDS/src/ARMJIT_Memory.cpp:187:29: error: member reference
type 'struct __darwin_mcontext64 *' is a pointer; did you mean to use
'->'?
context->uc_mcontext.pc = (u64)desc.FaultPC;
~~~~~~~~~~~~~~~~~~~~^
->
/Users/user/melonDS/src/ARMJIT_Memory.cpp:187:30: error: no member named
'pc' in '__darwin_mcontext64'
context->uc_mcontext.pc = (u64)desc.FaultPC;
~~~~~~~~~~~~~~~~~~~~ ^
1 warning and 6 errors generated.
make[2]: *** [src/CMakeFiles/core.dir/ARMJIT_Memory.cpp.o] Error 1
make[2]: *** Waiting for unfinished jobs....
In file included from /Users/user/melonDS/src/ARMJIT_A64/ARMJIT_Compiler.cpp:10:
/Users/user/melonDS/src/ARMJIT_A64/ARMJIT_Compiler.h:52:28: warning: &
has lower precedence than ==; == will be evaluated first [-Wparentheses]
{ return IsImm && (Imm & 0xFFF == Imm); }
^~~~~~~~~~~~~~
/Users/user/melonDS/src/ARMJIT_A64/ARMJIT_Compiler.h:52:28: note: place
parentheses around the '==' expression to silence this warning
{ return IsImm && (Imm & 0xFFF == Imm); }
^
( )
/Users/user/melonDS/src/ARMJIT_A64/ARMJIT_Compiler.h:52:28: note: place
parentheses around the & expression to evaluate it first
{ return IsImm && (Imm & 0xFFF == Imm); }
^
( )
/Users/user/melonDS/src/ARMJIT_A64/ARMJIT_Compiler.cpp:16:10: fatal error:
'malloc.h' file not found
#include <malloc.h>
^~~~~~~~~~
1 warning and 1 error generated.
make[2]: *** [src/CMakeFiles/core.dir/ARMJIT_A64/ARMJIT_Compiler.cpp.o] Error 1
4 warnings generated.
make[1]: *** [src/CMakeFiles/core.dir/all] Error 2
make: *** [all] Error 2
Joel says:
Dec 2nd 2020
Using Waluigi's pre-built app -

DS Firmware - Bootable
DSi Firmware - Bootable
GBA games - White screen when booted from the DS Firmware
DS Games - Tested only the Pokemon games so far, but they all seem to be working as expected. FPS varies between 59-61/60 FPS according to the title bar - with and without the JIT enabled. Work in both DS and DSi modes.
Joel says:
Dec 2nd 2020
Additional note:

DSI games - Only have a single game to test, System Flaw. Seems to be working as expected. Like DS games, FPS varies between 59-61/60 FPS according to the title bar - with and without the JIT enabled.
poudink says:
Dec 2nd 2020
melonDS isn't compatible with GBA games
Post a comment
Name:
DO NOT TOUCH