How can I run newer Unity games on OS X 10.9 Mavericks?
Most games built with Unity 2018 and Unity 2019 do not work on Mac OS X 10.9. They will launch initially, and occasionally even get as far as the main menu, but invariably crash before entering gameplay.
Dyld Error Message:
Symbol not found: _getattrlistbulk
Referenced from: /Users/USER/Desktop/Sayonara Wild Hearts.app/Contents/MacOS/../Frameworks/UnityPlayer.dylib
Expected in: /usr/lib/libSystem.B.dylib
How can I run these games without updating to an ugly flat version of OS X?
The crash log indicates that the game is looking for a function called getattrlistbulk
. Because this function doesn't exist in Mavericks, the game doesn't know what to do, and crashes. Ergo, in order for the game to run, we'll have to give it what it wants—a copy of the getattrlistbulk
function.
(In other words, we need to write some code. Make sure you have Apple's Xcode Command Line Tools installed!)
getattrlistbulk is a part of the kernel, which means I definitely don't understand what it does. But, what if I don't have to—what if Unity games don't actually need getattrlistbulk
for anything important, and/or have a fallback codepath for unexpected values? If that was the case, it might be that getattrlistbulk
doesn't need to actually do anything for the game to run, it merely needs to exist.
Only one way to find out! Let's give this code a try:
#include <sys/attr.h>
int getattrlistbulk(int dirfd, struct attrlist * attrList, void * attrBuf, size_t attrBufSize, uint64_t options) {
return 0;
}
This copy of getattrlistbulk
will always return 0
, no matter what.
If you save this code as UnityMavericksWorkarounds.m
, you can compile it with:
clang -compatibility_version 9999 -o UnityMavericksWorkarounds.dylib -dynamiclib UnityMavericksWorkarounds.m
Now, we need to make the game load our new UnityMavericksWorkarounds library. This should be easy to do with the DYLD_INSERT_LIBRARIES
environment variable. Run in the Terminal:
DYLD_INSERT_LIBRARIES=UnityMavericksWorkarounds.dylib Sayonara\ Wild\ Hearts.app/Contents/MacOS/Sayonara\ Wild\ Hearts
...and watch the game crash the exact same way it did before:
Dyld Error Message:
Symbol not found: _getattrlistbulk
Referenced from: /Users/USER/Desktop/Sayonara Wild Hearts.app/Contents/MacOS/../Frameworks/UnityPlayer.dylib
Expected in: /usr/lib/libSystem.B.dylib
Why can't UnityPlayer.dylib
find our fancy new getattrlistbulk
function?
Let's look at the crash report again. UnityPlayer isn't expecting getattrlistbulk
to be just anywhere, it's expecting it to be in /usr/lib/libSystem.B.dylib
, and so isn't looking inside of our UnityMavericksWorkarounds library. This is due to a concept called two-level namespaces, which you can and should read more about here. And, although two-level namespacing can be turned off via DYLD_FORCE_FLAT_NAMESPACE=1
, Unity games won't work without it.
Let's try something else. What if we made the game load our library, UnityMavericksWorkarounds.dylib
, in place of libSystem.B.dylib
? Apple's install_name_tool
command makes this easy! Copy UnityMavericksWorkarounds.dylib into the application bundle's Contents/Frameworks
directory, and then run:
install_name_tool -change /usr/lib/libSystem.B.dylib @executable_path/../Frameworks/UnityMavericksWorkarounds.dylib Sayonara\ Wild\ Hearts.app/Contents/Frameworks/UnityPlayer.dylib
(Replace Sayonara\ Wild\ Hearts
with the name of your game.)
Now, try launching the game again, and...
Application Specific Information:
dyld: launch, loading dependent libraries
Dyld Error Message:
Symbol not found: dyld_stub_binder
Referenced from: /Users/USER/Desktop/Sayonara Wild Hearts.app/Contents/MacOS/Sayonara Wild Hearts
Expected in: /Users/USER/Desktop/Sayonara Wild Hearts.app/Contents/MacOS/../Frameworks/UnityMavericksWorkarounds.dylib
in /Users/USER/Desktop/Sayonara Wild Hearts.app/Contents/MacOS/Sayonara Wild Hearts
Hey, at least the crash log changed this time! You can probably already guess why this didn't work. libSystem.B.dylib
is essentially the Mac equivalent of Linux's libc
, and as such, it contains many, many functions. UnityMavericksWorkarounds only contains getattrlistbulk
. And so although the game now has access to getattrlistbulk
, it's missing everything else!
What we want is for our UnityMavericksWorkarounds library to also provide all of the other libSystem functions in addition to its own—which is to say, we should make libSystem.B.dylib
a sub-library of UnityMavericksWorkarounds.dylib
.
I did this using optool:
optool install -c reexport -p /usr/lib/libSystem.B.dylib -t Sayonara\ Wild\ Hearts.app/Contents/Frameworks/UnityMavericksWorkarounds.dylib
(There's probably a cleaner way to link this at compile-time, but I couldn't figure out how, and optool worked.)
Now, try launching the game again, and...
Theoretical FAQs
(Follow-up questions no one has asked, but which they theoretically could.)
Q: Is the return value of 0
important?
A: Yes. I originally tried 1
, but that caused some games to allocate all available memory and crash once none was left.
Q: Will this make all Unity games work properly in Mavericks?
A: No. As far as I'm aware, it will allow any Unity 2018/2019 game to start up, but no one said anything about working properly! Games are complex, and these ones have clearly never been tested on Mavericks before, so they may have other glitches. Timelie is one example of a game which technically works with this fix, but has very severe graphical problems.
Many other games, however, really do seem to run perfectly.
Q: Is it possible to actually reimplement getattrlistbulk
instead of using a stub function?
A: Maybe? The Internet™ says getattrlistbulk
is a replacement for getdirentriesattr
. So, at one point I tried:
#include <sys/attr.h>
#include <unistd.h>
int getattrlistbulk(int dirfd, struct attrlist * attrList, void * attrBuf, size_t attrBufSize, uint64_t options) {
unsigned int count;
unsigned int basep;
unsigned int newState;
return getdirentriesattr(dirfd, &attrList, &attrBuf, attrBufSize, &count, &basep, &newState, options);
}
This was written by pattern-matching the example code for getattrlistbulk
and getdirentriesattr
in the developer documentation. It does work (insofar as games run), but then, so does return 0
, so I really have no way to test the code. Under the circumstances, return 0
seems safer.
Q: Can I get a tl;dr?
A: Sure:
- Copy the first code block in this answer into a file named
UnityMavericksWorkarounds.m
. clang -compatibility_version 9999 -o /Path/To/Game.app/Contents/Frameworks/UnityMavericksWorkarounds.dylib -dynamiclib /Path/To/UnityMavericksWorkarounds.m
install_name_tool -change /usr/lib/libSystem.B.dylib @executable_path/../Frameworks/UnityMavericksWorkarounds.dylib /Path/To/Game.app/Contents/Frameworks/UnityPlayer.dylib
- Download optool.
/Path/To/optool install -c reexport -p /usr/lib/libSystem.B.dylib -t /Path/To/Game.app/Contents/Frameworks/UnityMavericksWorkarounds.dylib