diff --git a/.efrocachemap b/.efrocachemap
index c0c92410..df0edb7a 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -4072,50 +4072,50 @@
"build/assets/workspace/ninjafightplug.py": "https://files.ballistica.net/cache/ba1/5c/81/461b88dcd868f050d85aa8ac3bc1",
"build/assets/workspace/onslaughtplug.py": "https://files.ballistica.net/cache/ba1/1c/02/97064ab6cd7f4e0018c4e7fa8282",
"build/assets/workspace/runaroundplug.py": "https://files.ballistica.net/cache/ba1/68/b6/f21cabe2df5f604ef02fee02082e",
- "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/68/7c/74c09ec913597f35144b96053009",
- "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/89/9d/87cf3cdc2c240ec8e25fcd61ed57",
- "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/ba/f7/a86319927e51b179754b69acd857",
- "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/c3/6c/3061d2ebe6e14ec7d2ebd18c827b",
- "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/95/a3/95dfe918215819d40876ee12b3dc",
- "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/e7/28/c8cf8efae1495a92de677778bb6d",
- "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/cb/09/12fe948541ac3f6f6283dbf0ad0b",
- "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/51/43/2f684a1118da9e4f9ee432ec8c26",
- "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/d4/87/a6271a09560cd91044e7f79834fd",
- "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/2a/95/5328b86298e1b57fb85e426b013a",
- "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/76/74/ecc7d5f6a5ed7a0afab7afbb5b91",
- "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/04/8b/124c760f2fd24d4e4a9a17fdf4e6",
- "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/20/9a/29a1009635cd8923db9cade71b13",
- "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/83/ee/5e815a581095bc5cc4e9f4b0c520",
- "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/be/b3/c5305d1412d213cd773dcb85c068",
- "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/36/17/ea670f5195251b7b926b7b271488",
- "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/96/1a/f93a21b95491173b164a7cd01baf",
- "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/fe/0b/f35d87df25f40f23fb2b5fb93761",
- "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/f8/97/01818b4ad16c448391b95c2004d9",
- "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/ed/b2/33919181729b50a7ecac78f09492",
- "build/prefab/lib/linux_arm64_gui/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/dc/91/77662ff3e58d11094603c3b8e303",
- "build/prefab/lib/linux_arm64_gui/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/1f/c5/5a2395414d21346171dae3e72067",
- "build/prefab/lib/linux_arm64_server/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/c5/74/f37ea38be58d445217bfa88a5c3c",
- "build/prefab/lib/linux_arm64_server/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/ac/9c/6dc16d4174635eeb7c916fe23878",
- "build/prefab/lib/linux_x86_64_gui/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/fa/79/0d67fbb9e833ff62a1a806737394",
- "build/prefab/lib/linux_x86_64_gui/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/9b/4a/0c7236caba4af58d15b12d457143",
- "build/prefab/lib/linux_x86_64_server/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/54/65/a037ac1a73d762fe799565e80c6d",
- "build/prefab/lib/linux_x86_64_server/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/87/06/b904d91b4e626b2d86ce71195ddc",
- "build/prefab/lib/mac_arm64_gui/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/21/a5/dc2068e1ef6a612677378e012fed",
- "build/prefab/lib/mac_arm64_gui/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/a9/e6/ccae60dae0635f5395ed28e71e5d",
- "build/prefab/lib/mac_arm64_server/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/c8/fc/445867c82da1f97b564311ad2e2c",
- "build/prefab/lib/mac_arm64_server/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/72/5f/1f7fc31a970053a0df9b59c2e1ab",
- "build/prefab/lib/mac_x86_64_gui/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/62/e1/c1c636605d7463120de9be515109",
- "build/prefab/lib/mac_x86_64_gui/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/47/cc/f9f14c3cca969fa0f68ca710d0ef",
- "build/prefab/lib/mac_x86_64_server/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/44/17/d7986eea4cfa67bc4807e625336c",
- "build/prefab/lib/mac_x86_64_server/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/b9/75/bc30ec3c5809f50c7fcfeeb22366",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericInternal.lib": "https://files.ballistica.net/cache/ba1/95/e2/5ea3c13f415acac9305957e52a83",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/2f/4e/e70a4ed89c0a141869731f5e4f3d",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/a5/05/b81eebf9967c1fd384491aa6d7b5",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/14/89/ef3fccc36104c981cbdbe63922c2",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericInternal.lib": "https://files.ballistica.net/cache/ba1/37/46/201fd9dd8664196208630a914e3a",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/40/f4/dca259b6584a5bc4e30db2d32672",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/67/90/01bbdbd77eca2f7bdc5b23620490",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/4f/eb/956ea5233f189d9d623b96408b04",
+ "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/0a/9f/72d8058f278d01809cfb27398bd9",
+ "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/0b/98/7d6e875a926d9b5e81a7affb4c16",
+ "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/cd/cf/a65a79458bd1df8a07e59f25f0de",
+ "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/f0/4c/3809b97686aafbeb70916568b092",
+ "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/2c/ea/a0f6149d5af960d5b3ba9d306715",
+ "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/7b/a5/3e20fbd85c2272364a01f96617f6",
+ "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/15/60/6ec17098f49ecc4eda4c5bbf4b0f",
+ "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/a9/25/0805d553a190963904f02dbbea25",
+ "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/8d/a4/b8e3de1146627f9557373d7093e9",
+ "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/c3/26/a73cf8fff288093290046d4a4ff4",
+ "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/1e/43/ffabb6e5619be0a5eaf4b31488ff",
+ "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/f3/5a/68a30e20094b1ee0857ce8b41df0",
+ "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/38/6d/2ec7b519b5079f70978fc985d66b",
+ "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/e2/ba/68636d02d975b46893d3267ff6da",
+ "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/0c/20/7b74002f2de6cc4cb8e87177b5ab",
+ "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/79/41/4d8058a9ddbb594777f63b1de553",
+ "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/68/dd/afd69b785c6f5ad54dbd23ad3f34",
+ "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/20/25/2ce73a6b1e4287cc90c3c0da32f7",
+ "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/5c/ca/4442330079c51f557ec9453908df",
+ "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/12/8c/d74204c683c6f8786887e6b1bea0",
+ "build/prefab/lib/linux_arm64_gui/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/02/42/47d4fc13f6f1466a93c0e34de43a",
+ "build/prefab/lib/linux_arm64_gui/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/92/31/e6ff7894f966d1baf0ceaf875b86",
+ "build/prefab/lib/linux_arm64_server/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/60/c9/358c66abf612b400f9da4b159bb8",
+ "build/prefab/lib/linux_arm64_server/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/87/e9/101ee8f1e4eccf5079a9451d13a3",
+ "build/prefab/lib/linux_x86_64_gui/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/28/ae/dd5e4068c838c3b656a0d166c58a",
+ "build/prefab/lib/linux_x86_64_gui/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/03/58/a4c0bdcdc2d5884da733abd27b1a",
+ "build/prefab/lib/linux_x86_64_server/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/c8/a5/c6a11df6e69c07943f68dc52d5c7",
+ "build/prefab/lib/linux_x86_64_server/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/00/38/12291441ed17a360a75d5c3cd6d6",
+ "build/prefab/lib/mac_arm64_gui/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/97/e9/46ea6200e3ff631f9d6e15112f5c",
+ "build/prefab/lib/mac_arm64_gui/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/dd/b5/97842446ead8366ee4291294e9da",
+ "build/prefab/lib/mac_arm64_server/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/be/a9/85f8283d2f34b612647b748f0cc6",
+ "build/prefab/lib/mac_arm64_server/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/c4/1e/778ca01438a1e787ed9758853fb6",
+ "build/prefab/lib/mac_x86_64_gui/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/48/2f/ae6e343be8de66bfacea476369ac",
+ "build/prefab/lib/mac_x86_64_gui/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/d2/43/b998930c810740e511f3037ea554",
+ "build/prefab/lib/mac_x86_64_server/debug/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/27/94/34b396fd7f6c82de24b99f7647fb",
+ "build/prefab/lib/mac_x86_64_server/release/libballisticakit_internal.a": "https://files.ballistica.net/cache/ba1/5f/22/4b6cd6c44444902e666528963f88",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericInternal.lib": "https://files.ballistica.net/cache/ba1/fc/24/b7fc8e3ff1cfcbdaf03c932f124b",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/67/46/d0d7c68ec085c758eb17f90ed55e",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/4f/cd/be387a2777f339c1af4948e693b8",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/61/51/291c437bd4c6ee5465f3fe06e032",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericInternal.lib": "https://files.ballistica.net/cache/ba1/29/f1/d5a916bcc8e54da59533651e3971",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/a5/7d/36b80257b5e52eb0192c574be77d",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/42/b0/679bdb56b72278366d10cfc36346",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/f9/65/29faf8a8a7c123a847710e2e9fbe",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "https://files.ballistica.net/cache/ba1/52/c6/c11130af7b10d6c0321add5518fa",
"src/assets/ba_data/python/babase/_mgen/enums.py": "https://files.ballistica.net/cache/ba1/38/c3/1dedd5e74f2508efc5974c8815a1",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "https://files.ballistica.net/cache/ba1/b4/3d/e352190a0e5673d101c0f3ee3ad2",
diff --git a/ballisticakit-cmake/CMakeLists.txt b/ballisticakit-cmake/CMakeLists.txt
index 2663e90e..3089f15c 100644
--- a/ballisticakit-cmake/CMakeLists.txt
+++ b/ballisticakit-cmake/CMakeLists.txt
@@ -382,8 +382,6 @@ add_executable(ballisticakit
${BA_SRC_ROOT}/ballistica/base/platform/support/min_sdl_key_names.h
${BA_SRC_ROOT}/ballistica/base/platform/windows/base_platform_windows.cc
${BA_SRC_ROOT}/ballistica/base/platform/windows/base_platform_windows.h
- ${BA_SRC_ROOT}/ballistica/base/platform/windows/base_platform_windows_oculus.cc
- ${BA_SRC_ROOT}/ballistica/base/platform/windows/base_platform_windows_oculus.h
${BA_SRC_ROOT}/ballistica/base/python/base_python.cc
${BA_SRC_ROOT}/ballistica/base/python/base_python.h
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_app_timer.cc
@@ -432,6 +430,7 @@ add_executable(ballisticakit
${BA_SRC_ROOT}/ballistica/classic/support/v1_account.h
${BA_SRC_ROOT}/ballistica/core/core.cc
${BA_SRC_ROOT}/ballistica/core/core.h
+ ${BA_SRC_ROOT}/ballistica/core/platform/apple/core_platform_apple.cc
${BA_SRC_ROOT}/ballistica/core/platform/apple/core_platform_apple.h
${BA_SRC_ROOT}/ballistica/core/platform/core_platform.cc
${BA_SRC_ROOT}/ballistica/core/platform/core_platform.h
diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj
index 45654711..5e2795db 100644
--- a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj
+++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj
@@ -373,8 +373,6 @@
-
-
@@ -423,6 +421,7 @@
+
diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters
index 1e7f4f7f..7c35f19c 100644
--- a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters
+++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters
@@ -553,12 +553,6 @@
ballistica\base\platform\windows
-
- ballistica\base\platform\windows
-
-
- ballistica\base\platform\windows
-
ballistica\base\python
@@ -703,6 +697,9 @@
ballistica\core
+
+ ballistica\core\platform\apple
+
ballistica\core\platform\apple
diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj
index 944c1614..344037c0 100644
--- a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj
+++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj
@@ -368,8 +368,6 @@
-
-
@@ -418,6 +416,7 @@
+
diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters
index 1e7f4f7f..7c35f19c 100644
--- a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters
+++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters
@@ -553,12 +553,6 @@
ballistica\base\platform\windows
-
- ballistica\base\platform\windows
-
-
- ballistica\base\platform\windows
-
ballistica\base\python
@@ -703,6 +697,9 @@
ballistica\core
+
+ ballistica\core\platform\apple
+
ballistica\core\platform\apple
diff --git a/src/ballistica/base/platform/apple/base_platform_apple.h b/src/ballistica/base/platform/apple/base_platform_apple.h
index 50cf78cc..ba5bd063 100644
--- a/src/ballistica/base/platform/apple/base_platform_apple.h
+++ b/src/ballistica/base/platform/apple/base_platform_apple.h
@@ -1,4 +1,4 @@
-// Released under the MIT License. See LICENSE for details.
+// Copyright (c) 2011-2022 Eric Froemling
#ifndef BALLISTICA_BASE_PLATFORM_APPLE_BASE_PLATFORM_APPLE_H_
#define BALLISTICA_BASE_PLATFORM_APPLE_BASE_PLATFORM_APPLE_H_
diff --git a/src/ballistica/core/platform/apple/core_platform_apple.cc b/src/ballistica/core/platform/apple/core_platform_apple.cc
new file mode 100644
index 00000000..c860e5ba
--- /dev/null
+++ b/src/ballistica/core/platform/apple/core_platform_apple.cc
@@ -0,0 +1,443 @@
+// Released under the MIT License. See LICENSE for details.
+
+#if BA_OSTYPE_MACOS || BA_OSTYPE_IOS_TVOS
+#include "ballistica/core/platform/apple/core_platform_apple.h"
+
+#if BA_XCODE_BUILD
+#include
+#endif
+#include
+
+#if BA_XCODE_BUILD
+#include "ballistica/core/platform/apple/apple_utils.h"
+#endif
+
+namespace ballistica::core {
+
+#if BA_OSTYPE_MACOS && BA_XCODE_BUILD && BA_SDL_BUILD
+extern void DoSetCursor(bool show);
+#endif
+
+CorePlatformApple::CorePlatformApple() = default;
+
+auto CorePlatformApple::GetDeviceV1AccountUUIDPrefix() -> std::string {
+#if BA_OSTYPE_MACOS
+ return "m";
+#elif BA_OSTYPE_IOS_TVOS
+ return "i";
+#else
+#error FIXME
+#endif
+}
+
+// Legacy for device-accounts; don't modify this code.
+auto CorePlatformApple::GetRealLegacyDeviceUUID(std::string* uuid) -> bool {
+#if BA_OSTYPE_MACOS && BA_XCODE_BUILD
+ *uuid = AppleUtils::GetMacUUID();
+ return true;
+#endif
+#if BA_OSTYPE_IOS_TVOS
+ *uuid = AppleUtils::GetIOSUUID();
+ return true;
+#endif
+ return false;
+}
+
+#if BA_OSTYPE_MACOS && !BA_XCODE_BUILD
+// A fallback function to grab IOPlatformUUID
+// (for builds where we don't have access to swift/objc stuff).
+static auto GetMacUUIDFallback() -> std::string {
+ char buffer[1024];
+
+ // This will get us a full line like "IOPlatformUUID" = "VALUE". We
+ // could trim it down to just the value, but it shouldn't hurt anything
+ // to just hash the full line.
+ if (FILE* inproc = popen(
+ "ioreg -d2 -c IOPlatformExpertDevice | grep IOPlatformUUID", "r")) {
+ auto size{fread(buffer, 1, sizeof(buffer) - 1, inproc)};
+ fclose(inproc);
+ assert(size < sizeof(buffer));
+ buffer[size] = 0;
+ return buffer;
+ } else {
+ throw Exception("Unable to access IOPlatformUUID");
+ }
+}
+#endif // BA_OSTYPE_MACOS && !BA_XCODE_BUILD
+
+// For semi-permanent public-uuid hashes; can modify this if we
+// find better sources.
+auto CorePlatformApple::GetDeviceUUIDInputs() -> std::list {
+ std::list out;
+#if BA_OSTYPE_MACOS
+#if BA_XCODE_BUILD
+ out.push_back(AppleUtils::GetMacUUID());
+#else // BA_XCODE_BUILD
+ out.push_back(GetMacUUIDFallback());
+#endif // BA_XCODE_BUILD
+#endif // BA_OSTYPE_MACOS
+
+#if BA_OSTYPE_IOS_TVOS
+ out.push_back(AppleUtils::GetIOSUUID());
+#endif
+ return out;
+}
+
+auto CorePlatformApple::GenerateUUID() -> std::string {
+ char buffer[100];
+ uuid_t uuid;
+ uuid_generate(uuid);
+ uuid_unparse(uuid, buffer);
+ return buffer;
+}
+
+auto CorePlatformApple::DoGetConfigDirectoryMonolithicDefault()
+ -> std::optional {
+#if BA_OSTYPE_IOS_TVOS
+ // FIXME - this doesn't seem right.
+ printf("FIXME: get proper default-config-dir\n");
+ return std::string(getenv("HOME")) + "/Library";
+#elif BA_OSTYPE_MACOS && BA_XCODE_BUILD
+ return AppleUtils::GetApplicationSupportPath() + "/BallisticaKit";
+#else
+ return CorePlatform::DoGetConfigDirectoryMonolithicDefault();
+#endif
+}
+
+auto CorePlatformApple::GetLocale() -> std::string {
+#if BA_XCODE_BUILD
+ return AppleUtils::GetLocaleString();
+#else
+ return CorePlatform::GetLocale();
+#endif
+}
+
+auto CorePlatformApple::DoGetDeviceName() -> std::string {
+#if BA_OSTYPE_MACOS && BA_XCODE_BUILD
+ return AppleUtils::GetDeviceName();
+#else
+ return CorePlatform::DoGetDeviceName();
+#endif
+}
+
+auto CorePlatformApple::DoHasTouchScreen() -> bool {
+#if BA_OSTYPE_IOS
+ return true;
+#else
+ return false;
+#endif
+}
+
+auto CorePlatformApple::GetUIScale() -> UIScale {
+#if BA_OSTYPE_IOS
+ if (AppleUtils::IsTablet()) {
+ return UIScale::kMedium;
+ } else {
+ return UIScale::kSmall;
+ }
+#else
+ // default case handles mac/tvos
+ return CorePlatform::GetUIScale();
+#endif
+}
+
+auto CorePlatformApple::IsRunningOnDesktop() -> bool {
+#if BA_OSTYPE_IOS_TVOS
+ return false;
+#else
+ return true;
+#endif
+}
+
+void CorePlatformApple::DisplayLog(const std::string& name, LogLevel level,
+ const std::string& msg) {
+#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD
+
+ // HMM: do we want to use proper logging APIs here or simple printing?
+ // AppleUtils::NSLogStr(msg);
+ CorePlatform::DisplayLog(name, level, msg);
+#else
+
+ // Fall back to default handler...
+ CorePlatform::DisplayLog(name, level, msg);
+#endif
+}
+
+auto CorePlatformApple::DoGetDataDirectoryMonolithicDefault() -> std::string {
+#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD
+ // On Apple package-y builds use our resources dir.
+ return AppleUtils::GetResourcesPath();
+#else
+ // Fall back to default.
+ return CorePlatform::DoGetDataDirectoryMonolithicDefault();
+#endif
+}
+
+void CorePlatformApple::GetTextBoundsAndWidth(const std::string& text, Rect* r,
+ float* width) {
+#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD
+ AppleUtils::GetTextBoundsAndWidth(text, r, width);
+#else
+ CorePlatform::GetTextBoundsAndWidth(text, r, width);
+#endif
+}
+
+void CorePlatformApple::FreeTextTexture(void* tex) {
+#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD
+ AppleUtils::FreeTextTexture(tex);
+#else
+ CorePlatform::FreeTextTexture(tex);
+#endif
+}
+
+auto CorePlatformApple::CreateTextTexture(
+ int width, int height, const std::vector& strings,
+ const std::vector& positions, const std::vector& widths,
+ float scale) -> void* {
+#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD
+ return AppleUtils::CreateTextTexture(width, height, strings, positions,
+ widths, scale);
+#else
+ return CorePlatform::CreateTextTexture(width, height, strings, positions,
+ widths, scale);
+#endif
+}
+
+auto CorePlatformApple::GetTextTextureData(void* tex) -> uint8_t* {
+#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD
+ return AppleUtils::GetTextTextureData(tex);
+#else
+ return CorePlatform::GetTextTextureData(tex);
+#endif
+}
+
+void CorePlatformApple::SubmitScore(const std::string& game,
+ const std::string& version, int64_t score) {
+#if BA_USE_GAME_CENTER
+ AppleUtils::SubmitScore(game, version, score);
+#else
+ CorePlatform::SubmitScore(game, version, score);
+#endif
+}
+
+void CorePlatformApple::ReportAchievement(const std::string& achievement) {
+#if BA_USE_GAME_CENTER
+ AppleUtils::ReportAchievement(achievement);
+#else
+ CorePlatform::ReportAchievement(achievement);
+#endif
+}
+
+void CorePlatformApple::ResetAchievements() {
+#if BA_USE_GAME_CENTER
+ AppleUtils::ResetGameCenterAchievements();
+#else
+ CorePlatform::ResetAchievements();
+#endif
+}
+
+auto CorePlatformApple::HaveLeaderboard(const std::string& game,
+ const std::string& config) -> bool {
+#if BA_USE_GAME_CENTER
+ return AppleUtils::HaveGameCenterLeaderboard(game, config);
+#else
+ return CorePlatform::HaveLeaderboard(game, config);
+#endif
+}
+
+void CorePlatformApple::ShowOnlineScoreUI(const std::string& show,
+ const std::string& game,
+ const std::string& game_version) {
+#if BA_USE_GAME_CENTER
+ AppleUtils::ShowOnlineScoreUI(show, game, game_version);
+#else
+ CorePlatform::ShowOnlineScoreUI(show, game, game_version);
+#endif
+}
+
+auto CorePlatformApple::NewAutoReleasePool() -> void* {
+#if BA_XCODE_BUILD
+ return AppleUtils::NewAutoReleasePool();
+#else
+ return CorePlatform::NewAutoReleasePool();
+#endif
+}
+
+void CorePlatformApple::DrainAutoReleasePool(void* pool) {
+#if BA_XCODE_BUILD
+ AppleUtils::DrainAutoReleasePool(pool);
+#else
+ CorePlatform::DrainAutoReleasePool(pool);
+#endif
+}
+
+void CorePlatformApple::GameCenterLogin() {
+#if BA_USE_GAME_CENTER
+ AppleUtils::DoGameCenterLogin();
+#else
+ CorePlatform::GameCenterLogin();
+#endif
+}
+
+auto CorePlatformApple::IsOSPlayingMusic() -> bool {
+#if BA_XCODE_BUILD
+ return AppleUtils::IsMusicPlaying();
+#else
+ return CorePlatform::IsOSPlayingMusic();
+#endif
+}
+
+void CorePlatformApple::SetHardwareCursorVisible(bool visible) {
+ // Set our nifty custom hardware cursor on mac;
+ // otherwise fall back to default.
+#if BA_OSTYPE_MACOS && BA_XCODE_BUILD && !BA_HEADLESS_BUILD && BA_SDL_BUILD
+ DoSetCursor(visible);
+#else
+ return CorePlatform::SetHardwareCursorVisible(visible);
+#endif
+}
+
+void CorePlatformApple::QuitApp() {
+#if BA_OSTYPE_MACOS && BA_XCODE_BUILD && !BA_HEADLESS_BUILD
+ AppleUtils::Quit(); // will post a cocoa terminate
+#else
+ CorePlatform::QuitApp();
+#endif
+}
+
+void CorePlatformApple::OpenFileExternally(const std::string& path) {
+#if BA_XCODE_BUILD
+ AppleUtils::EditTextFile(path.c_str());
+#else
+ CorePlatform::OpenFileExternally(path);
+#endif
+}
+
+void CorePlatformApple::OpenDirExternally(const std::string& path) {
+#if BA_OSTYPE_MACOS
+ std::string cmd = std::string("open \"") + path + "\"";
+ int result = system(cmd.c_str());
+ if (result != 0) {
+ Log(LogLevel::kError, "Got return value " + std::to_string(result)
+ + " on open cmd '" + cmd + "'");
+ }
+#else
+ CorePlatform::OpenDirExternally(path);
+#endif
+}
+
+void CorePlatformApple::MacMusicAppInit() {
+#if BA_OSTYPE_MACOS && BA_XCODE_BUILD
+ AppleUtils::MacMusicAppInit();
+#else
+ CorePlatform::MacMusicAppInit();
+#endif
+}
+auto CorePlatformApple::MacMusicAppGetVolume() -> int {
+#if BA_OSTYPE_MACOS && BA_XCODE_BUILD
+ return static_cast(AppleUtils::MacMusicAppGetVolume());
+#else
+ return CorePlatform::MacMusicAppGetVolume();
+#endif
+}
+void CorePlatformApple::MacMusicAppSetVolume(int volume) {
+#if BA_OSTYPE_MACOS && BA_XCODE_BUILD
+ AppleUtils::MacMusicAppSetVolume(volume);
+#else
+ CorePlatform::MacMusicAppSetVolume(volume);
+#endif
+}
+void CorePlatformApple::MacMusicAppGetLibrarySource() {
+#if BA_OSTYPE_MACOS && BA_XCODE_BUILD
+ AppleUtils::MacMusicAppGetLibrarySource();
+#else
+ CorePlatform::MacMusicAppGetLibrarySource();
+#endif
+}
+void CorePlatformApple::MacMusicAppStop() {
+#if BA_OSTYPE_MACOS && BA_XCODE_BUILD
+ AppleUtils::MacMusicAppStop();
+#else
+ CorePlatform::MacMusicAppStop();
+#endif
+}
+auto CorePlatformApple::MacMusicAppPlayPlaylist(const std::string& playlist)
+ -> bool {
+#if BA_OSTYPE_MACOS && BA_XCODE_BUILD
+ return AppleUtils::MacMusicAppPlayPlaylist(playlist.c_str());
+#else
+ return CorePlatform::MacMusicAppPlayPlaylist(playlist);
+#endif
+}
+auto CorePlatformApple::MacMusicAppGetPlaylists() -> std::list {
+#if BA_OSTYPE_MACOS && BA_XCODE_BUILD
+ return AppleUtils::MacMusicAppGetPlaylists();
+#else
+ return CorePlatform::MacMusicAppGetPlaylists();
+#endif
+}
+
+auto CorePlatformApple::IsEventPushMode() -> bool {
+// We operate in push mode in our xcode builds (except headless).
+#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD
+ return true;
+#else
+ return CorePlatform::IsEventPushMode();
+#endif
+}
+
+auto CorePlatformApple::GetPlatformName() -> std::string {
+#if BA_OSTYPE_MACOS
+ return "mac";
+#elif BA_OSTYPE_IOS_TVOS
+ return "ios";
+#else
+#error FIXME
+#endif
+}
+
+auto CorePlatformApple::GetSubplatformName() -> std::string {
+#if BA_TEST_BUILD
+ return "test";
+#elif BA_XCODE_BUILD
+ return "appstore";
+#else
+ return "";
+#endif
+}
+
+auto CorePlatformApple::DoClipboardIsSupported() -> bool {
+#if BA_XCODE_BUILD
+ return AppleUtils::ClipboardIsSupported();
+#else
+ return CorePlatform::DoClipboardIsSupported();
+#endif // BA_XCODE_BUILD
+}
+
+auto CorePlatformApple::DoClipboardHasText() -> bool {
+#if BA_XCODE_BUILD
+ return AppleUtils::ClipboardHasText();
+#else
+ return CorePlatform::DoClipboardHasText();
+#endif // BA_XCODE_BUILD
+}
+
+void CorePlatformApple::DoClipboardSetText(const std::string& text) {
+#if BA_XCODE_BUILD
+ AppleUtils::ClipboardSetText(text);
+#else
+ CorePlatform::DoClipboardSetText(text);
+#endif // BA_XCODE_BUILD
+}
+
+auto CorePlatformApple::DoClipboardGetText() -> std::string {
+#if BA_XCODE_BUILD
+ return AppleUtils::ClipboardGetText();
+#else
+ return CorePlatform::DoClipboardGetText();
+#endif // BA_XCODE_BUILD
+}
+
+} // namespace ballistica::core
+
+#endif // BA_OSTYPE_MACOS || BA_OSTYPE_IOS_TVOS