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

Problem Report Screenshot

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...

It works!


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:

  1. Copy the first code block in this answer into a file named UnityMavericksWorkarounds.m.
  2. clang -compatibility_version 9999 -o /Path/To/Game.app/Contents/Frameworks/UnityMavericksWorkarounds.dylib -dynamiclib /Path/To/UnityMavericksWorkarounds.m
  3. install_name_tool -change /usr/lib/libSystem.B.dylib @executable_path/../Frameworks/UnityMavericksWorkarounds.dylib /Path/To/Game.app/Contents/Frameworks/UnityPlayer.dylib
  4. Download optool.
  5. /Path/To/optool install -c reexport -p /usr/lib/libSystem.B.dylib -t /Path/To/Game.app/Contents/Frameworks/UnityMavericksWorkarounds.dylib