41

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?

bmike
  • 235,889
Wowfunhappy
  • 7,383

1 Answers1

85

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, and I don't understand what it does. But, what if I didn't have to—what if Unity games don't actually need getattrlistbulk for anything important? If that were the case, it might be that getattrlistbulk wouldn't need to actually do anything for a game to run, the function would merely need 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 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 guess why this didn't work. libSystem.B.dylib is an essential system library which contains 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. We can do this by making libSystem.B.dylib a sub-library of our 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, &amp;attrList, &amp;attrBuf, attrBufSize, &amp;count, &amp;basep, &amp;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
Wowfunhappy
  • 7,383
  • 12
    I take my hat off to you for this amazing post on internals for Mavericks - chapeau to a true maverick. There is a group of people collaborating to make older software work on newer OS as well. Both approaches have merits. – bmike Feb 28 '21 at 20:31
  • 4
    For reference purposes, the source for getattrlistbulk as found in OS X 10.10 can be found here. – David Anderson Feb 28 '21 at 21:44
  • 2
    I have to ask: Why not just upgrade to Yosemite? Apple provides a link to download. – David Anderson Feb 28 '21 at 21:58
  • 21
    @DavidAnderson And replace this fast, stable, and exquisitely-designed masterpiece with a headache-inducing iOS ripoff? You must be joking good sir! – Wowfunhappy Feb 28 '21 at 22:26
  • 1
    Since this question is getting lots of attention—if there's a different way I could have architected this fix, I'd be interested to hear about it. Replacing and re-exporting all of libsystem feels hamfisted. Then again, patching someone else's binary is inherently weird, so perhaps it's not surprising that it should lead to weird approaches. I'd also love to know what's actually going on with getattrlistbulk—why do games require it, if replacing it with a dumb stub is so effective? – Wowfunhappy Mar 01 '21 at 15:44
  • 14
    @Wowfunhappy I have to be Sgt. Killjoy and remind the internet that OS X 10.9 is no longer receiving security updates, and yes there has been Mac malware released in the past four years. Even though 10.9 is my favorite OS X too, it is not safe to connect to the internet. – Thegs Mar 01 '21 at 15:46
  • 3
    @Thegs My feeling is that I'm reasonably safe under the circumstances: https://news.ycombinator.com/item?id=23473839 If you have a plausible attack scenario I haven't considered, I'd love to hear about it. Feel free to ping me in chat if it's too much for comments. – Wowfunhappy Mar 01 '21 at 15:54
  • 1
    Why doesn't using the DYLD_INTERPOSE macro to add the __DATA,__interpose section in the binary work? It was my understanding if the __DATA,__interpose segment is present then dyld will use that even when two level namespace is in effect. Or does the original symbol first need to exist and be resolved in order for interposing to work? – 1110101001 Mar 01 '21 at 21:08
  • 2
    @1110101001 "Or does the original symbol first need to exist and be resolved in order for interposing to work?" Yes, exactly, at least from what I can tell—it kept trying to resolve the symbol to interpose it. I need to thank you, though, because it was learning about DYLD_INTERPOSE that made me first look into this. I thought (incorrectly, but w/e) that what worked for the Dictionary would also work here. – Wowfunhappy Mar 01 '21 at 21:34
  • Your list makes me think I'm quite safe on an outdated Windows 7 too. As in, if I avoid visiting any malicious websites and don't run any suspicious looking files, I should be fine, I think? – Clockwork Mar 02 '21 at 13:36
  • 2
    @Clockwork I would encourage you to do the analysis for yourself—what are the possible threat scenarios, and what is the consequence of something going wrong? Windows is a bit more dangerous because it's a more common target. But, IMO some people treat all unpatched systems like they're Windows 95 (which really will get hacked if it's connected to the internet for 20 minutes). – Wowfunhappy Mar 02 '21 at 15:12
  • 2
    @Wowfunhappy That last statement is what makes me feel uncomfortable. Because I'm just a developer, I don''t know whether or not the simple fact of being plugged makes me vulnerable. Even some questions about Windows XP on Security Stack Exchange seem to mostly mention the risks of using the computer and doing a faux pas. – Clockwork Mar 02 '21 at 15:14
  • 1
    @Clockwork: Even the most obsolete and insecure OS can be relatively safe to use if it's behind a firewall that 1) doesn't let any incoming connections through and 2) only allows outgoing connections to specific trusted servers and/or to a trusted proxy that intercepts all data and scans it for potential malware. And which is preferably configured as strictly as possibly, e.g. to block access to any websites not explicitly marked as trusted. But setting up such a firewall in a way that makes the system both secure and usable is far from trivial. – Ilmari Karonen Mar 04 '21 at 00:37