Skip to content

Fix CFPreferences bundle ID matching, memmove NULL handling, and pthread inheritsched - #1

Open
j92580498-max wants to merge 102 commits into
trunkfrom
branches
Open

Fix CFPreferences bundle ID matching, memmove NULL handling, and pthread inheritsched#1
j92580498-max wants to merge 102 commits into
trunkfrom
branches

Conversation

@j92580498-max

Copy link
Copy Markdown
Owner

This PR fixes several issues identified from touchHLE logs running PopCap games (PvZ iPad/iPhone):

Fixes

1. CFPreferences: Reverse-DNS bundle ID matching

Problem: Games use shortened bundle IDs like com.popcap.pvz and com.popcap.pvzipad instead of the full bundle ID (com.popcap.ios.chs.PvZGreatWall, com.popcap.ios.chs.PvZiPad).

Solution: Added reverse-DNS prefix matching so that com.popcap.pvz* is accepted as the current app. Per Apple docs, applicationID should be in Java package name form.

2. memmove: NULL source handling

Problem: memmove(dest, NULL, size) was being skipped with a warning, but real iOS behavior for this edge case (triggered by corrupted std::string internals) should zero-fill the destination.

Solution: When src=0 and dest != 0, perform zero-fill of destination instead of skipping entirely.

3. pthread: attr_setinheritsched / attr_getinheritsched

Problem: pthread_attr_setinheritsched was a TODO stub, and pthread_attr_getinheritsched was not exported.

Solution: Implemented both functions per Apple pthread(3) manpage. Added inheritsched field to pthread_attr_t struct, set/get with proper EINVAL validation.

4. CFURLCreateStringByReplacingPercentEscapes

Problem: Was stubbed with TODO, causing potential crashes.

Solution: Implemented proper percent-decoding using NSString stringByRemovingPercentEncoding.

Testing

  • Fixes warnings in PvZ iPad (com.popcap.pvzipad) and PvZ iPhone (com.popcap.pvz) logs
  • Reduces NULL-page memmove warnings
  • Proper pthread scheduling attribute support per POSIX/Darwin spec

References

  • Apple CFPreferencesCopyAppValue docs: applicationID must be Java package name form
  • Apple pthread(3) manpage: pthread_attr_setinheritsched, pthread_attr_getinheritsched
  • Apple CFURL docs: CFURLCreateStringByReplacingPercentEscapes

cursoragent and others added 30 commits May 25, 2026 16:30
Includes setup notes for both the Rust emulator (touchHLE) and the
AppDB (Python/FastAPI), covering build dependencies, RUSTFLAGS
requirements, and service startup commands.

Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>
* gles1_on_gl2: convert types in glGet*v queries instead of asserting

The GLES 1.1 spec (section 6.1.2 "Data Conversions") requires
glGetIntegerv/glGetFloatv/glGetBooleanv to perform type conversion
when the queried state's native type does not match the requested
type. Our gles1_on_gl2 layer previously asserted that the type
matched (with one POINT_SIZE_MAX exception), which made any guest
app that queries e.g. a float-typed state via glGetIntegerv crash
the whole emulator.

"Pyramid Mummy" (com.magicbone.pyramidrun) triggers exactly this
during startup: it calls glGetIntegerv on a non-int pname and we
panic at src/gles/gles1_on_gl2.rs:845. Fix this by mirroring the
fan-out used by GetFixedv: query the underlying state at its native
type and convert each component per the spec rules.

- GetIntegerv: Boolean -> 0/1, Float -> round-to-nearest clamped to
  GLint, FloatSpecial (normalized colors etc.) -> scaled to the
  GLint range.
- GetBooleanv: any non-zero int/float maps to GL_TRUE.
- GetFloatv: integer state widens to float; boolean maps to 0.0/1.0.

Unknown ParamType variants fall through to the host driver rather
than aborting, matching the rest of the layer's defensive style.

Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>

* objc: initialise phantom host-object buffers via T::default() instead of raw zeroes

The objc "phantom host object" fallback in src/objc/objects.rs is used
by ObjC::borrow / ObjC::borrow_mut whenever a guest object has no
real host-side record (or the record can't be downcast to the requested
type). It used to hand out a *zeroed* buffer of size_of::<T>() and
transmute it to &T / &mut T. That was unsound for any T whose zero
bit-pattern is not a valid instance — most notably types containing a
hashbrown HashMap, whose internal `ctrl` pointer must point at the
`Group::static_empty()` sentinel rather than null.

"Pyramid Mummy" hit this: after my preceding fix uncrashed the
gles1_on_gl2 GetIntegerv assert, the Unity runtime ended up calling
-[NSDictionary initWithDictionary:] with an object that isn't a real
dictionary, the phantom fallback returned a zeroed DictionaryHostObject,
and `HashMap::values()` SIGSEGV'd in
`hashbrown::raw::RawIterRange::new` via an SSE2 _mm_load_si128 from a
null `ctrl` pointer.

Fix the phantom path by requiring `T: Default` for borrow / borrow_mut
and writing a real `T::default()` value into the leaked buffer the first
time it's allocated. This guarantees the returned reference always
points at a valid instance of T regardless of which fields T contains.

To satisfy the new bound this commit adds `Default` implementations
(via `#[derive(Default)]` where possible, otherwise a small manual
impl) to all host object types reachable through borrow/borrow_mut, and
to a handful of helper types they embed (CGAffineTransform,
CATransform3D, CFUUIDBytes, UuidBytes, GuestPathBuf, GuestFunction,
SEL, CMAcceleration, CMRotationRate, FontKind, PredicateKind,
InputStreamBacking, OutputStreamBacking, CGContextSubclass,
CGBitmapContextData, Image, font::Font). A few enums get a
hand-written impl choosing a sensible "empty" variant; a few struct
fields whose underlying types come from external crates and don't
implement Default (rusttype::Font, nibarchive::NIBArchive,
std::time::Instant in CMMotionManagerHostObject) are wrapped in
`Option<...>` with the real allocation paths populating them via
`Some(...)`.

As a defence-in-depth measure, init_with_dictionary_common now also
short-circuits on a nil source dictionary instead of relying on the
phantom path producing a valid empty HashMap; this matches Apple's
documented behaviour for -[NSDictionary initWithDictionary:nil].

Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>
Replace the manual git clone steps (which hardcoded the
j92580498-max/touchHLE remote and failed when the workflow ran from
a fork or branch that did not exist on that remote, e.g.
'fatal: couldn't find remote ref refs/heads/cursor/...') with
actions/checkout, so the repository and ref that triggered the
workflow are always checked out correctly. Submodules are now fetched
via the checkout action's submodules: recursive option, removing the
need for a separate 'git submodule update --init' step.

Per request, all action references now point at their default branch
(@main) instead of pinned major-version tags.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>
The release CI build was failing on all targets (Android, Windows, macOS)
with E0277 errors because borrow/borrow_mut require T: Default, but
UIStoryboardHostObject did not derive Default. All of its fields (id,
String, HashMap) already implement Default, so a derive is sufficient.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>
The quick options menu already had a close button at the top-right
corner with a '×' glyph, but it was invisible:

  - 'buttonWithType:UIButtonTypeRoundedRect' does not actually apply
    the rounded-rect appearance, so the button kept the default
    white title color on a clear background, which is invisible
    against the white menu.
  - The 20x20 frame combined with a 30pt font also made the glyph
    too large for the bounds.

Enlarge the button to 36x36, drop the font size to 28pt, and
explicitly give the button a gray background, white title color
and rounded corners so the close button is clearly visible and
tappable.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>
objc::borrow and borrow_mut require HostObject types to implement
Default for phantom-fallback when the runtime is asked to access a
missing or wrong-typed object. NSPipeHostObject and NSCacheHostObject
were added without these impls, causing 29 E0277 compile errors.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>
* ci: publish Hypertel v1.0.x release every 5 commits

Add scripts to detect when five commits have landed since the last
v1.0.* tag (or since Hypertel release automation was introduced) and
extend the build workflow to upload macOS, Windows, and Android
artifacts as a GitHub release named Hypertel.

Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>

* ci: require all builds for Hypertel release and add commit changelog

Only publish when macOS, Windows, and Android jobs succeed. Verify
artifacts exist before packaging. Include linked commit list in the
release notes.

Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>
* ci: publish Hypertel v1.0.x release every 5 commits

Add scripts to detect when five commits have landed since the last
v1.0.* tag (or since Hypertel release automation was introduced) and
extend the build workflow to upload macOS, Windows, and Android
artifacts as a GitHub release named Hypertel.

Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>

* ci: require all builds for Hypertel release and add commit changelog

Only publish when macOS, Windows, and Android jobs succeed. Verify
artifacts exist before packaging. Include linked commit list in the
release notes.

Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>

* ci: allow manual Hypertel release via workflow_dispatch checkbox

Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>

* fix: resolve Windows artifact path after CI download

upload-artifact flattens touchHLE_windows_bundle/ so touchHLE.exe is at
artifacts/windows/touchHLE.exe, not under touchHLE_windows_bundle/.

Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>

* fix: manual Hypertel release changelog uses latest 5 commits

Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>

* ci: publish Hypertel releases with softprops/action-gh-release@master

Package zips and release notes in a script; create the GitHub release
and upload assets via softprops/action-gh-release@master.

Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>

* ci: include version in Hypertel release title

Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>
… ABI (#10)

The objc borrow() fallback for missing objects requires T: Default.
CFRunLoopSourceHostObject was missing it, causing 13 E0277 errors when
building the library (including the Android Gradle cargo-ndk step).

When cross-compiling dynarmic for Android, set CMAKE_ANDROID_ARCH_ABI to
arm64-v8a so CMake detects arm64 instead of 32-bit arm, which otherwise
breaks 64-bit floating-point bit helpers in vendored dynarmic.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>
* Fix crashes and implement missing runtime APIs found in logs

- bundle: don't panic on non-string MinimumOSVersion (number/real); return Option<String>
- font: make Font::from_vec fallible; CGFontCreateWithDataProvider returns NULL on bad data (per Apple docs)
- NSPropertyListSerialization: implement +propertyListWithData:options:format:error: (iOS 4.0+) with real NSError
- objc runtime: implement objc_alloc and objc_autorelease (libobjc ARC fast paths) and export them

* objc: implement missing objc_retainBlock (fixes pre-existing trunk build break)

objc_retainBlock was imported and exported in src/objc.rs but never
defined, so the crate failed to compile (E0432). Per Apple's objc4
runtime, objc_retainBlock(x) wraps _Block_copy(x); mirror touchHLE's
_Block_copy behaviour (returns the same pointer).

* Implement more documented runtime stubs from logs (real, per Apple docs)

- GCD: real dispatch_queue_set_specific / dispatch_queue_get_specific /
  dispatch_get_specific backed by a (queue,key)->value store + current-queue
  tracking (was: call to unimplemented _dispatch_queue_set_specific)
- objc: objc_unsafeClaimAutoreleasedReturnValue ARC fast path (was: return-0 stub)
- UIViewController: real extendedLayoutIncludesOpaqueBars property + setter
  (was: 'does not respond to selector setExtendedLayoutIncludesOpaqueBars:')

* Implement remaining documented runtime stubs (real, per Apple docs)

- Foundation: real NSIndexSet class (indexSetWithIndexesInRange:, indexSetWithIndex:,
  count, containsIndex:, firstIndex/lastIndex, indexGreater/LessThanIndex:, equality,
  copy) — was 'Class NSIndexSet is unimplemented'
- CoreText: CTFontManagerRegisterGraphicsFont(font, error) — was return-0 stub
- CoreFoundation: CFErrorCopyDescription forwards to NSError -localizedDescription
  (toll-free bridged) — was return-0 stub

* Fix two guest-triggered hard crashes (GameStop, Reckless Getaway)

- objc/classes.rs: a class and its metaclass always share the same name in
  Apple's runtime (class_getName). The previous code only harmonized the two
  names when one was a synthetic placeholder for an unreadable name, and
  asserted otherwise — which aborted the whole VM for GameStop_iOS, whose
  class/metaclass names were both readable yet mismatched. Make harmonization
  unconditional (class side canonical) and drop the assert.

- foundation/ns_string.rs: NSMutableString -setString:nil and -appendFormat:nil
  asserted on nil and crashed the emulator (e.g. Reckless Getaway hit
  'assertion left != right'). Apple raises NSInvalidArgumentException but never
  aborts the process; log a warning and no-op instead, matching the lenient
  handling already used for the other NSMutableString implementation.

* Foundation: add missing framework constants and real NSSet member:/setWithObjects:count:

- Register ~55 previously-unhandled host constants in foundation STUB_CONSTANTS
  with documented Apple values: UIKeyInput* arrows/Escape, kCAAlignment*,
  AVAudioTimePitchAlgorithm*, kCMFormatDescriptionExtension_Depth, CVPixelBuffer
  pool keys, VTDecompression keys, kCTFontPostScriptNameKey, kCGPDFContext*,
  GKSessionErrorDomain, PKPaymentNetwork*, MKLaunchOptions*, MKMapRectNull,
  UIActivityTypePostToTencentWeibo, NSExtensionHost*/UIScene* notifications,
  NSMetadata/NSURL ubiquitous-item keys, MPNowPlayingInfoProperty*, AVMetadata*,
  kCGColorSpaceExtended(Linear)SRGB, AVCaptureExposureDurationCurrent (CMTime
  invalid), AVCaptureISOCurrent (-1.0f), and __NSArray0__/__NSDictionary0__
  empty-collection singletons backed by real retained objects.
- NSSet/NSMutableSet: implement -member: (real isEqual: lookup per Apple docs),
  +setWithObjects:count: and -initWithObjects:count: (C-array initialisers),
  mirroring the existing NSArray implementations.

* dyld constants: fix leading-underscore symbol keys + add CAEmitterLayer/UITableViewIndexSearch/AVEncoder constants

Constant export keys are matched against raw Mach-O symbol names, which
carry a leading underscore (e.g. _matrix_identity_float4x4). Many entries
in foundation/avfoundation STUB_CONSTANTS were written without it and so
never resolved (logs still showed them as unhandled). Prefix all such keys
with '_', fix ___NSArray0__/___NSDictionary0__ (triple underscore), and add
CAEmitterLayer shape/render-mode keys, UITableViewIndexSearch and
AVEncoderBitRatePerChannelKey.

---------

Co-authored-by: j92580498-max <j92580498@gmail.com>
Co-authored-by: busik007 <busik0877@gmail.com>
Co-authored-by: busik007 <busik0877@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>
The script declared #!/bin/sh but used bash-only [[ ]] tests, which fail
on Debian/Ubuntu CI where /bin/sh is dash. Convert conditionals to POSIX
[ ] syntax so the Hypertel release packaging step runs correctly.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>
The publish-hypertel-release job already packages artifacts and creates
the GitHub release via softprops/action-gh-release. The follow-up step
that called publish-hypertel-release.sh failed on the flattened Windows
artifact path and would have duplicated the release upload.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>
Landscape games like Call of Duty: Zombies call setStatusBarOrientation:
after launch and rotate their own OpenGL drawing. touchHLE was also
rotating frames in present_renderbuffer, which produced a 90° sideways
image in a landscape window.

Only apply present-time rotation on iPhone when UIKit has already applied
the autorotation affine transform to the EAGL view (landscape launch path,
e.g. Resident Evil Degeneration with --landscape-left). Skip it when the
view transform is still identity so self-rotating games display correctly.

Also honor Info.plist UIInterfaceOrientation for initial window setup when
portrait is supported, and add --landscape-left to COD Zombies defaults so
the window matches the game from the first frame.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: goodvids56 <goodvids56@users.noreply.github.com>
goodvids56 and others added 27 commits June 3, 2026 00:56
Port changes from touchHLE/touchHLE trunk commits:
- 4a950ccf: Add AIFF audio type hint handling in AudioFileOpenURL
- ea1d0347: Account for nil first object in NSArray arrayWithObjects:

Skipped 98de7d8a (NSTimeZone -abbreviation stub) because Hypertle already
implements abbreviation lookup via ZONE_TABLE.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…ng (#24)

* Recover the app delegate when UIApplicationMain gets no usable delegate class

Some apps (e.g. "The Steak" / Charlie the Steak) reach UIApplicationMain
without a usable delegate class name: the argument is nil, or it was built
from a class reference that resolved to nil (NSStringFromClass returns nil,
which decodes to the empty string). With no main-nib connection setting the
delegate either, the application was left with a nil delegate, so
application:didFinishLaunchingWithOptions: was never delivered and the app
stayed frozen on its launch image.

Add a fallback: when the normal delegate resolution yields nothing, discover
the app's delegate class by scanning the loaded classes for one that declares
a UIApplicationDelegate launch method, and instantiate that. This only runs
when the app would otherwise have no delegate at all, so it cannot affect
apps that launch correctly today.

- objc: add ObjC::class_declaring_instance_method, which finds a registered
  class declaring a given instance method, skipping substituted/placeholder
  classes and metaclasses. Selection is deterministic and prefers
  app-delegate-like names so an unrelated class cannot shadow the real one.
- UIApplicationMain: resolve the delegate from the explicit class name when
  usable (now via try_get_known_class, so an unknown name falls through to
  discovery instead of installing a broken placeholder), otherwise fall back
  to the discovered delegate class; log clearly when none can be determined.
  Also drop the leftover DIAG_CALLER debug prints.

https://claude.ai/code/session_01FQFBWFcV8FV2YgLJnE73gJ

* Fix headless-mode panic when an app queries preferred languages/region

`get_preferred_language_codes` / `get_preferred_country_codes` route through
`Environment::on_parent_stack_in_coroutine`, which unwraps `self.window`.
That window is absent in headless mode, so an app that calls
`[NSLocale preferredLanguages]` (a very common thing to do during launch)
panicked with an `Option::unwrap()` on `None`, even though the closures here
do not actually use the window.

Short-circuit both helpers when there is no window and report no preference,
which the caller already handles by falling back to English. The windowed
path is unchanged.

* NSCalendar: implement +autoupdatingCurrentCalendar

Apps commonly call `[NSCalendar autoupdatingCurrentCalendar]` during launch
(e.g. for date math). It was unimplemented, so the runtime returned nil and
the app then did calendar/date work against a nil object. Apple's
autoupdating calendar only differs from `currentCalendar` in that it tracks
later changes to the user's settings, which touchHLE has none of, so forward
to `currentCalendar`.

* Don't panic in the main run loop when running headless

Once an app fully finishes launching it enters the main NSRunLoop, which
unconditionally calls `uikit::handle_events` and
`core_animation::recomposite_if_necessary`. Both reach for the window, which
is absent in headless mode, so they panicked with "Tried to do something that
needs a window..." (the stale comment in `handle_events` even claimed it was
never called headless). Guard both: with no window there are no input events
to drain and nothing to composite, so return immediately. This makes headless
mode able to run an app past launch (useful for automated testing); windowed
behaviour is unchanged.

* UINavigationBar: make it a real UIView subclass

`UINavigationBar` is declared `: UIView`, but `UINavigationBarHostObject` was
a plain `HostObject` with no embedded `UIViewHostObject`. As a result every
`UIView` message sent to a nav bar (frame, subviews, layout, hidden, ...)
failed to borrow the view host object and silently fell back to a zeroed
phantom, so the nav bar had no real view state. Observed with "The Steak",
whose launch path touches a nav bar during third-party SDK setup.

Embed the superclass host object via `impl_HostObject_with_superclass!`,
initialize it in `+allocWithZone:`, and chain `-init`/`-initWithFrame:`/
`-initWithCoder:` to `super` so the backing layer and view state are set up
like any other UIView subclass.

Also make the wrong-type `borrow` fallback report the object's actual host
type, which is what made this mismatch diagnosable in the first place.

---------

Co-authored-by: Claude <noreply@anthropic.com>
touchHLE renders UIAlertView as a *blocking* SDL2 system dialog, whereas
real iOS -[UIAlertView show] is asynchronous and returns immediately.
Outfit7 titles such as Talking Angela create a UIAlertView with an empty
(or nil) title *and* message as a transient placeholder and dismiss it
programmatically once background work finishes. Presenting a blocking
modal for such an alert froze the app behind an empty dialog box that the
user could never meaningfully act on.

When both title and message are empty, skip the native dialog and dismiss
the alert asynchronously (delivering any delegate callbacks) so the guest
run loop keeps going. Content-bearing alerts still show the SDL2 dialog as
before.

Verified by running the Talking Angela IPA: previously the app dead-ended
at the empty UIAlertView; it now skips both empty alerts and proceeds to
load the ChatScript engine and run the game loop.

https://claude.ai/code/session_01TpoV3dfiQLfQwPwbxnvDX2

Co-authored-by: Claude <noreply@anthropic.com>
Text containing Arabic characters previously rendered as tofu boxes
because none of the bundled Latin or Noto Sans CJK fonts contain Arabic
glyphs. This adds Noto Sans Arabic (regular + bold, SIL OFL) as a
fallback and extends UIFont's script detection to route Arabic text to
it, mirroring the existing CJK fallback.

- Bundle NotoSansArabic-Regular.ttf and NotoSansArabic-Bold.ttf
- Add Font::sans_regular_ar() / sans_bold_ar() loaders
- Refactor get_font() with is_cjk_char()/is_arabic_char() helpers
  covering Arabic, Arabic Supplement, Arabic Extended-A and Arabic
  Presentation Forms-A/B blocks
- Document the new fonts in the fonts README and license notice

Co-authored-by: Claude <noreply@anthropic.com>
Text containing Arabic was laid out by rusttype in logical order,
left-to-right, with no contextual shaping. That made Arabic words appear
reversed and made each letter render in its isolated, disconnected form.

Add a self-contained shaping/reordering pass that runs on each line just
before it is handed to rusttype:

- Contextual joining: map Arabic letters to their Unicode presentation
  forms (isolated/initial/medial/final), including mandatory lam-alef
  ligatures. The bundled Noto Sans Arabic font maps these forms in its
  cmap, so no GSUB support is required.
- Bidirectional reordering: a simplified UAX #9 pass reverses RTL runs
  into visual order while keeping Latin/number runs and combining marks
  correctly placed.

The transform is applied at the two rusttype layout call sites in
font.rs (width measurement and drawing) so measured and drawn widths stay
consistent. Non-Arabic lines are returned untouched and borrowed, so
existing behaviour is unchanged. No new dependencies are added.

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
…ules (#29)

ns_log and ns_objc_runtime only export C functions (NSLog/NSLogv and the
NSStringFrom*/NSClassFromString family) and define no Objective-C classes,
so they have no CLASSES const. The CLASSES aggregation list erroneously
referenced ns_log::CLASSES and ns_objc_runtime::CLASSES, causing two
E0425 'cannot find value CLASSES' errors. Their FUNCTIONS are already
correctly registered in the FUNCTIONS list.

Co-authored-by: Claude <noreply@anthropic.com>
…c I/O (#158)

* Implement POSIX aio family to fix apps that stream resources via async I/O

aio_read/aio_error/aio_return (plus aio_write/suspend/cancel/fsync) were
previously missing, so dyld installed return-0 stubs. Apps that load their
resource files through POSIX async I/O (e.g. Plants vs. Zombies HD) therefore
saw every request report success with zero bytes transferred, leaving the
destination buffers empty. That produced a black screen followed by an early
crash (an empty buffer led to a bogus malloc((size_t)-1)).

These are now implemented synchronously, reusing the regular positional
pread/pwrite paths and stashing each result keyed by the guest struct aiocb
pointer so aio_error/aio_return report the real completion status. The aiocb
prefix is modelled as #[repr(C, packed)] to match the iOS armv7 ABI, where
off_t is only 4-byte aligned (fildes 0, offset 4, buf 12, nbytes 16).

Verified by running the PvZ HD IPA under HyperHLE: the app no longer crashes,
aio now reads real data (incl. the ~1.4 MB resource pack), all 204 'Failed to
load particle' errors are gone, and the render loop stays alive past frame 60.

* aio: warn when an app requests unsupported completion notification

The synchronous aio implementation handles SIGEV_NONE (poll via
aio_error/aio_return) perfectly, but does not yet deliver SIGEV_SIGNAL or
SIGEV_THREAD completion callbacks. Read the aiocb's sigev_notify and log a
warning in that case so a future app relying on async notification produces a
diagnosable message instead of silently blocking. (PvZ HD uses SIGEV_NONE, so
this is purely defensive.)

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Add workflow to download and run an IPA via touchHLE

* Run downloaded IPA on Linux, Windows and macOS

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Add CI workflow for testing iOS apps under touchHLE (#30)

* Add workflow to download and run an IPA via touchHLE

* Run downloaded IPA on Linux, Windows and macOS

---------

Co-authored-by: Claude <noreply@anthropic.com>

* CI: provide software OpenGL on Windows so apps can launch (#31)

The 'Download and run IPA' workflow aborted on the Windows runner with
'Couldn't create OpenGL ES 1.1 context!'. Windows GitHub Actions runners
only expose Microsoft's GDI generic OpenGL 1.1, which cannot satisfy the
OpenGL 2.1 context that touchHLE's GLES1-on-GL2 fallback requires, so both
the native GLES path and the GL2 fallback fail and the emulator panics
before the app ever runs.

Drop Mesa3D's llvmpipe software renderer (a per-app opengl32.dll plus its
dependencies) next to touchHLE.exe and force GALLIUM_DRIVER=llvmpipe so a
complete OpenGL implementation is available without a GPU. Linux already
gets llvmpipe via libgl1-mesa-dri and macOS ships a usable system OpenGL,
so this is only needed on Windows.

Also restore diagnostics in libc exit()/abort(): they previously exited
silently, so run logs ended abruptly with no explanation. They now log the
exit/abort (with a guest stack trace on abort) before quitting.

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Updated decoding logic to align with Symphonia 0.6 changes, including adjustments to probe, track access, codec parameters, and decoder creation.
Updated alGetBufferi function parameter from *const ALint to *mut ALint to match OpenAL specification.
Updated GetBufferi function to accept a mutable pointer for the value parameter, aligning with OpenAL API requirements.
…etBufferi pointer (#161)

* Add CI workflow for testing iOS apps under touchHLE (#30)

* Add workflow to download and run an IPA via touchHLE

* Run downloaded IPA on Linux, Windows and macOS

---------

Co-authored-by: Claude <noreply@anthropic.com>

* CI: provide software OpenGL on Windows so apps can launch (#31)

The 'Download and run IPA' workflow aborted on the Windows runner with
'Couldn't create OpenGL ES 1.1 context!'. Windows GitHub Actions runners
only expose Microsoft's GDI generic OpenGL 1.1, which cannot satisfy the
OpenGL 2.1 context that touchHLE's GLES1-on-GL2 fallback requires, so both
the native GLES path and the GL2 fallback fail and the emulator panics
before the app ever runs.

Drop Mesa3D's llvmpipe software renderer (a per-app opengl32.dll plus its
dependencies) next to touchHLE.exe and force GALLIUM_DRIVER=llvmpipe so a
complete OpenGL implementation is available without a GPU. Linux already
gets llvmpipe via libgl1-mesa-dri and macOS ships a usable system OpenGL,
so this is only needed on Windows.

Also restore diagnostics in libc exit()/abort(): they previously exited
silently, so run logs ended abruptly with no explanation. They now log the
exit/abort (with a guest stack trace on abort) before quitting.

Co-authored-by: Claude <noreply@anthropic.com>

* Fix build: port symphonia_formats to Symphonia 0.6 API and alGetBufferi pointer (#32)

The touchHLE lib failed to compile against symphonia 0.6.0, which
reorganized its API relative to the 0.5-era code the module was written
for:

- probe() now returns the boxed FormatReader directly (no ProbeResult
  .format field)
- Hint moved to symphonia::core::formats::probe
- Track::codec_params is Option<CodecParameters> (an enum with an Audio
  variant carrying AudioCodecParameters)
- decoders are created via CodecRegistry::make_audio_decoder with
  AudioDecoderOptions
- next_packet() returns Result<Option<Packet>> (Ok(None) = end of stream)
- decoded audio is a GenericAudioBufferRef exposing
  copy_bytes_to_vec_interleaved_as::<i16>()
- SignalSpec/SampleBuffer are gone; AudioSpec (rate()/channels()) is used

Also fix alGetBufferi to obtain a mutable pointer via ptr_at_mut, as
GetBufferi writes the queried value back (matching alGetSourcei).

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
…(#162)

* Add CI workflow for testing iOS apps under touchHLE (#30)

* Add workflow to download and run an IPA via touchHLE

* Run downloaded IPA on Linux, Windows and macOS

---------

Co-authored-by: Claude <noreply@anthropic.com>

* CI: provide software OpenGL on Windows so apps can launch (#31)

The 'Download and run IPA' workflow aborted on the Windows runner with
'Couldn't create OpenGL ES 1.1 context!'. Windows GitHub Actions runners
only expose Microsoft's GDI generic OpenGL 1.1, which cannot satisfy the
OpenGL 2.1 context that touchHLE's GLES1-on-GL2 fallback requires, so both
the native GLES path and the GL2 fallback fail and the emulator panics
before the app ever runs.

Drop Mesa3D's llvmpipe software renderer (a per-app opengl32.dll plus its
dependencies) next to touchHLE.exe and force GALLIUM_DRIVER=llvmpipe so a
complete OpenGL implementation is available without a GPU. Linux already
gets llvmpipe via libgl1-mesa-dri and macOS ships a usable system OpenGL,
so this is only needed on Windows.

Also restore diagnostics in libc exit()/abort(): they previously exited
silently, so run logs ended abruptly with no explanation. They now log the
exit/abort (with a guest stack trace on abort) before quitting.

Co-authored-by: Claude <noreply@anthropic.com>

* Fix build: port symphonia_formats to Symphonia 0.6 API and alGetBufferi pointer (#32)

The touchHLE lib failed to compile against symphonia 0.6.0, which
reorganized its API relative to the 0.5-era code the module was written
for:

- probe() now returns the boxed FormatReader directly (no ProbeResult
  .format field)
- Hint moved to symphonia::core::formats::probe
- Track::codec_params is Option<CodecParameters> (an enum with an Audio
  variant carrying AudioCodecParameters)
- decoders are created via CodecRegistry::make_audio_decoder with
  AudioDecoderOptions
- next_packet() returns Result<Option<Packet>> (Ok(None) = end of stream)
- decoded audio is a GenericAudioBufferRef exposing
  copy_bytes_to_vec_interleaved_as::<i16>()
- SignalSpec/SampleBuffer are gone; AudioSpec (rate()/channels()) is used

Also fix alGetBufferi to obtain a mutable pointer via ptr_at_mut, as
GetBufferi writes the queried value back (matching alGetSourcei).

Co-authored-by: Claude <noreply@anthropic.com>

* Fix NSKeyedArchiver infinite recursion crash + collection round-trip

Fixes the Talking Angela crash (App called exit with a garbage pointer
argument after the objc_msgSend depth guard tripped and corrupted string
objects). Root cause and related issues:

- ns_keyed_archiver: NSString's encodeWithCoder: allocated a fresh NSString
  for its contents and re-encoded that via encodeObject:forKey:. Since the
  new object is itself an NSString, encode_object kept calling encodeWithCoder:
  on each freshly-created string, recursing infinitely. This blew the
  objc_msgSend recursion guard (or the native stack), corrupted string
  objects, and eventually crashed the guest. NSString is now stored inline as
  a plist string in $objects, matching what the decoder already expects.

- ns_array / ns_dictionary: the archiver wrote collections in a bespoke layout
  (NS.objects.0, NS.objects.1, NS.count for arrays; a single object reference
  for dictionaries) that the decoder — which follows Apple's inline UID-array
  format — could not read, so touchHLE-produced archives round-tripped to
  empty collections. Both now emit NS.objects / NS.keys as inline arrays of
  UID references via the new encode_objects_as_uid_array helper.

- ns_array: add -[NSArray isEqual:], which delegates to isEqualToArray: for
  NSArray operands. Without it the NSObject pointer-identity default made two
  distinct-but-equal arrays (e.g. an array and its unarchived copy) compare
  unequal, breaking dictionary/array equality after a round-trip.

- foundation: remove duplicate constant exports (e.g.
  _AVPlayerItemTimeJumpedNotification, _PHImageErrorKey, several SK*/kSec*),
  which made the no_duplicate_constants unit test fail and would emit
  duplicate C declarations in generated link stubs.

- dyld: dump_host_symbols now emits a sanitized C identifier with an asm()
  label for symbols whose names aren't valid C identifiers (e.g.
  OBJC_IVAR_$_NSObject.isa), so the integration-test link stubs compile.

https://claude.ai/code/session_016jmFdZ859mfXeJZ1eubdEH

---------

Co-authored-by: Claude <noreply@anthropic.com>
…mount) (#163)

* Add CI workflow for testing iOS apps under touchHLE (#30)

* Add workflow to download and run an IPA via touchHLE

* Run downloaded IPA on Linux, Windows and macOS

---------

Co-authored-by: Claude <noreply@anthropic.com>

* CI: provide software OpenGL on Windows so apps can launch (#31)

The 'Download and run IPA' workflow aborted on the Windows runner with
'Couldn't create OpenGL ES 1.1 context!'. Windows GitHub Actions runners
only expose Microsoft's GDI generic OpenGL 1.1, which cannot satisfy the
OpenGL 2.1 context that touchHLE's GLES1-on-GL2 fallback requires, so both
the native GLES path and the GL2 fallback fail and the emulator panics
before the app ever runs.

Drop Mesa3D's llvmpipe software renderer (a per-app opengl32.dll plus its
dependencies) next to touchHLE.exe and force GALLIUM_DRIVER=llvmpipe so a
complete OpenGL implementation is available without a GPU. Linux already
gets llvmpipe via libgl1-mesa-dri and macOS ships a usable system OpenGL,
so this is only needed on Windows.

Also restore diagnostics in libc exit()/abort(): they previously exited
silently, so run logs ended abruptly with no explanation. They now log the
exit/abort (with a guest stack trace on abort) before quitting.

Co-authored-by: Claude <noreply@anthropic.com>

* Fix build: port symphonia_formats to Symphonia 0.6 API and alGetBufferi pointer (#32)

The touchHLE lib failed to compile against symphonia 0.6.0, which
reorganized its API relative to the 0.5-era code the module was written
for:

- probe() now returns the boxed FormatReader directly (no ProbeResult
  .format field)
- Hint moved to symphonia::core::formats::probe
- Track::codec_params is Option<CodecParameters> (an enum with an Audio
  variant carrying AudioCodecParameters)
- decoders are created via CodecRegistry::make_audio_decoder with
  AudioDecoderOptions
- next_packet() returns Result<Option<Packet>> (Ok(None) = end of stream)
- decoded audio is a GenericAudioBufferRef exposing
  copy_bytes_to_vec_interleaved_as::<i16>()
- SignalSpec/SampleBuffer are gone; AudioSpec (rate()/channels()) is used

Also fix alGetBufferi to obtain a mutable pointer via ptr_at_mut, as
GetBufferi writes the queried value back (matching alGetSourcei).

Co-authored-by: Claude <noreply@anthropic.com>

* Fix infinite loop constructing std::string from NULL char* (Turbo Dismount)

Some apps (e.g. Turbo Dismount) hang at startup, spinning forever while
mem::memmove logs "memmove with likely-negative size (0xffffffff = -1);
src=0x0" over and over.

Root cause: the app builds std::strings in a loop from a table that
contains a NULL `const char*` entry under touchHLE. libstdc++ constructs
`std::string(const char*)` via `_S_construct(NULL, NULL + npos)`, detects
the NULL pointer, and calls `std::__throw_logic_error`. touchHLE already
neuters the `__throw_*` helpers to a bare `BX LR` so guest C++ "soft"
failures don't abort the host. But for this path the neutered throw simply
falls through into the normal copy code with `beg == NULL` and
`end - beg == npos`, producing `memcpy(dest, NULL, npos)` and a string
whose length is npos. The oversized copy is skipped by Mem::memmove, but
the resulting corrupt string leaves the app's construction loop unable to
make progress — an effectively infinite hang.

The documented intent of the throw-neutering is for std::string(NULL) to
behave like an empty string. Make that actually happen: retarget the
NULL-pointer branch inside libstdc++'s char `_S_construct` so it jumps to
the function's existing empty-string path (which returns
`_S_empty_rep()._M_refdata()`) instead of the throw / oversized-copy path.
The patch locates the branch by instruction pattern and verifies the
expected encodings, skipping with a warning if the bundled libstdc++ ever
changes, so it can never silently mis-patch.

Verified with com.secretexit.trdismount 1.2.7: the "likely-negative size"
spam (previously 400k+ occurrences in seconds, pegging a CPU core) drops
to zero and the app proceeds past startup.

https://claude.ai/code/session_0112hTEYgwBzuThq3tqwhno1

---------

Co-authored-by: Claude <noreply@anthropic.com>
…ead inheritsched

This commit fixes several issues identified from touchHLE logs running PopCap games (PvZ iPad/iPhone):

1. CFPreferences: Added reverse-DNS prefix matching for shortened bundle IDs
   - Games use 'com.popcap.pvz' and 'com.popcap.pvzipad' instead of full bundle IDs
   - Per Apple docs, applicationID should be in Java package name form

2. memmove: Zero-fill destination when src is NULL instead of skipping
   - Real iOS behavior for corrupted std::string internals
   - Fixes warnings in PvZ logs

3. pthread: Implemented pthread_attr_setinheritsched and pthread_attr_getinheritsched
   - Added inheritsched field to pthread_attr_t struct
   - Per Apple pthread(3) manpage with proper EINVAL validation

4. CFURL: Implemented CFURLCreateStringByReplacingPercentEscapes
   - Using NSString stringByRemovingPercentEncoding instead of stub

References:
- Apple CFPreferencesCopyAppValue docs
- Apple pthread(3) manpage
- Apple CFURL docs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants