// SpeedBreaker runtime. GPL-3.0-or-later (see COPYING). See memory_status.h. #include "memory_status.h" #include #include #include #include #include #ifdef __APPLE__ #include #include #include #if TARGET_OS_IOS #include #endif #endif namespace memstatus { namespace { constexpr const char* kExtendedVa = "com.apple.developer.kernel.extended-virtual-addressing"; constexpr const char* kIncreasedMemory = "com.apple.developer.kernel.increased-memory-limit"; // The mirrors' errors are kern_return_t values on Apple (Mach memory // entries, kernel/memory.cpp), errno elsewhere. #ifdef __APPLE__ constexpr bool kMachErrors = true; #else constexpr bool kMachErrors = false; #endif std::string ErrorText(const Status& s) { if (s.failure == Failure::AddressSpace) return std::format("reserving 4 GB failed: {} (errno {})", s.errorText.empty() ? "unknown error" : s.errorText, s.error); if (s.failure == Failure::PhysicalMemory) return std::format("mapping the 512 MB of physical memory failed: {} ({})", s.errorText.empty() ? "unknown error" : s.errorText, s.error); return ""; } // "entitlements in the signature: Extended Virtual Addressing yes, // Increased Memory Limit no", or why they aren't known. std::string EntitlementsText(const Status& s) { if (!s.signatureRead) return "entitlements: not read"; return std::format("entitlements in the signature: Extended Virtual Addressing {}, Increased Memory Limit {}", FlagName(s.extendedVirtualAddressing), FlagName(s.increasedMemoryLimit)); } std::string Facts(const Status& s) { std::string text = s.failure == Failure::None ? std::format("guest 4 GB at {:#x}", s.base) : ErrorText(s); if (s.ios) { text += "; " + EntitlementsText(s); if (s.addressSpaceTop) text += std::format("; address space up to {:#x} ({} GB)", s.addressSpaceTop, s.addressSpaceTop >> 30); text += s.memoryLimitMB > 0 ? std::format("; memory limit {} MB", s.memoryLimitMB) : "; memory limit unknown"; } return text; } #if defined(__APPLE__) && TARGET_OS_IOS bool ReadVmInfo(task_vm_info_data_t& info, mach_msg_type_number_t& count) { count = TASK_VM_INFO_COUNT; return task_info(mach_task_self(), TASK_VM_INFO, task_info_t(&info), &count) == KERN_SUCCESS; } #endif } const char* FlagName(Flag flag) { return flag == Flag::Yes ? "yes" : flag == Flag::No ? "no" : "unknown"; } std::string LogLine(const Status& status) { return "[memory] " + Facts(status); } std::string SummaryItem(const Status& status) { return Facts(status); } Message StartupFailure(const Status& s) { Message m; if (s.failure == Failure::None) return m; m.details = "Details: " + Facts(s) + "."; if (!s.ios) { m.title = "SpeedBreaker can't start"; m.paragraphs.push_back(s.failure == Failure::AddressSpace ? "The system refused the 4 GB of address space the game needs to lay out the Xbox 360's memory." : "The system refused to map the Xbox 360's 512 MB of memory into the game's address space."); m.paragraphs.push_back("Close other programs and try again. On Linux, check that no address-space limit applies " "(ulimit -v must say unlimited)."); return m; } m.title = "This copy of SpeedBreaker can't start"; if (s.failure == Failure::PhysicalMemory) { m.paragraphs.push_back("iOS refused to map the Xbox 360's 512 MB of memory into the game's address space."); m.paragraphs.push_back("Restart the device and try again. If it happens again, please report it with a " "screenshot of this page."); return m; } if (s.extendedVirtualAddressing == Flag::Yes) { m.paragraphs.push_back("iOS refused the 4 GB of address space the game needs to lay out the Xbox 360's memory, " "even though this copy has the Extended Virtual Addressing entitlement."); m.paragraphs.push_back("Restart the device and try again. If it happens again, please report it with a " "screenshot of this page."); return m; } // iOS gives an app room for 4 GB with Extended Virtual Addressing, // or with Increased Memory Limit on devices that support it (both // lift its address space past 63 GB); without either it has about // 5 GB in one piece above the system's shared region, and the 4 GB // fits there only if nothing took that space first. A free Apple Account can // never give Extended Virtual Addressing; some sideloading tools // keep Increased Memory Limit for it (scripts/ios/README.md). std::string why = "iOS refused the 4 GB of address space the game needs for the Xbox 360's memory layout. "; if (s.extendedVirtualAddressing == Flag::No && s.increasedMemoryLimit == Flag::No) why += "iOS gives an app that much room with the Extended Virtual Addressing or the Increased Memory Limit " "entitlement, and this copy was signed with neither."; else if (s.extendedVirtualAddressing == Flag::No) why += "This copy was signed with Increased Memory Limit, which gives that room only on devices that support " "it, but not with Extended Virtual Addressing."; else why += "It takes the Extended Virtual Addressing or the Increased Memory Limit entitlement, and iOS didn't " "give this copy the room."; m.paragraphs.push_back(why); m.paragraphs.push_back("This build needs the Extended Virtual Addressing entitlement, which a paid Apple Developer " "Program account can give, or Increased Memory Limit, which AltStore 2.2 or later keeps even " "with a free Apple Account (SideStore 0.7 and some other tools drop it). Sideload " "SpeedBreaker again that way, or build it yourself with Xcode: see scripts/ios/README.md in " "the source."); m.paragraphs.push_back("You can close SpeedBreaker now: swipe it away in the app switcher."); return m; } std::string LowMemoryNotice(const Status& s) { if (!s.ios || s.failure != Failure::None) return ""; bool smallSpace = s.addressSpaceTop != 0 && s.addressSpaceTop < kLargeAddressSpace; bool lowLimit = s.memoryLimitMB > 0 && s.memoryLimitMB < kPeakFootprintMB; if (smallSpace && lowLimit) return std::format("iOS gives SpeedBreaker little room ({} GB of address space, {} MB of memory: no memory " "entitlements): it may close the game in busy scenes", s.addressSpaceTop >> 30, s.memoryLimitMB); if (smallSpace) return std::format("iOS gives SpeedBreaker only {} GB of address space (no memory entitlements): it may close " "the game in busy scenes", s.addressSpaceTop >> 30); if (lowLimit && s.increasedMemoryLimit != Flag::Yes) return std::format("iOS lets SpeedBreaker use only {} MB (no Increased Memory Limit entitlement): it may close " "the game in busy scenes", s.memoryLimitMB); if (lowLimit) return std::format("iOS lets SpeedBreaker use only {} MB on this device: it may close the game in busy scenes", s.memoryLimitMB); return ""; } void Gather(Status& status) { if (status.failure == Failure::AddressSpace || (status.failure == Failure::PhysicalMemory && !kMachErrors)) status.errorText = std::strerror(status.error); #ifdef __APPLE__ else if (status.failure == Failure::PhysicalMemory) status.errorText = mach_error_string(status.error); #endif #if defined(__APPLE__) && TARGET_OS_IOS status.ios = true; #else // NFSMW_TEST_MEMORY_IOS=1: the iOS wording on another system (with // NFSMW_TEST_RESERVE_FAIL, the iOS page), the entitlements read from // this executable's own signature (a Mac build has none: "no"). const char* test = std::getenv("NFSMW_TEST_MEMORY_IOS"); status.ios = test && test[0] == '1'; #endif #ifdef __APPLE__ if (!status.ios) return; if (std::optional e = platform::codesign::ReadOwn()) { using platform::codesign::Lookup; using platform::codesign::Value; status.signatureRead = true; status.extendedVirtualAddressing = Lookup(*e, kExtendedVa) == Value::True ? Flag::Yes : Flag::No; status.increasedMemoryLimit = Lookup(*e, kIncreasedMemory) == Value::True ? Flag::Yes : Flag::No; } #else (void)kExtendedVa; (void)kIncreasedMemory; #endif #if defined(__APPLE__) && TARGET_OS_IOS // The limit iOS kills the app at: what it has (its footprint) plus // what it may still take. The address space's top shows whether // Extended Virtual Addressing took effect. task_vm_info_data_t info{}; mach_msg_type_number_t count = 0; size_t available = os_proc_available_memory(); if (ReadVmInfo(info, count)) { if (available > 0) status.memoryLimitMB = int64_t((uint64_t(info.phys_footprint) + available) >> 20); if (count >= TASK_VM_INFO_REV2_COUNT) status.addressSpaceTop = info.max_address; } #endif } void Poll() { #if defined(__APPLE__) && TARGET_OS_IOS using clock = std::chrono::steady_clock; static clock::time_point s_next; clock::time_point now = clock::now(); if (now < s_next) return; s_next = now + std::chrono::seconds(60); task_vm_info_data_t info{}; mach_msg_type_number_t count = 0; if (!ReadVmInfo(info, count)) return; int64_t peak = count >= TASK_VM_INFO_REV3_COUNT ? info.ledger_phys_footprint_peak : 0; fprintf(stderr, "[memory] footprint %llu MB, peak %lld MB, %zu MB left before iOS's limit\n", (unsigned long long)(info.phys_footprint >> 20), (long long)(peak >> 20), os_proc_available_memory() >> 20); #endif } }