@@ -32,6 +32,20 @@ internal sealed class RuntimeHost : IDisposable
3232 [ DllImport ( NativeScriptLibrary , EntryPoint = nameof ( runtime_set_local_folder ) ) ]
3333 private static extern void runtime_set_local_folder ( [ MarshalAs ( UnmanagedType . LPUTF8Str ) ] string localFolder ) ;
3434
35+ // Escape hatch for apps packed with `nsbundle_pack --key-hex <hex>` (custom-key
36+ // app.nsbundle containers). Must be called before runtime_init if used at all — apps
37+ // packed with the default pepper (no --key-hex) never need to call this. Not wired to a
38+ // default call site here; an app author supplies their own key material and calls this
39+ // from Initialize() before runtime_init(...).
40+ [ DllImport ( NativeScriptLibrary , EntryPoint = nameof ( runtime_set_bundle_key ) ) ]
41+ private static extern int runtime_set_bundle_key ( [ MarshalAs ( UnmanagedType . LPUTF8Str ) ] string keyHex ) ;
42+
43+ [ DllImport ( NativeScriptLibrary , EntryPoint = nameof ( runtime_read_protected_file ) ) ]
44+ private static extern IntPtr runtime_read_protected_file ( [ MarshalAs ( UnmanagedType . LPUTF8Str ) ] string virtualPath ) ;
45+
46+ [ DllImport ( NativeScriptLibrary , EntryPoint = nameof ( runtime_free_protected_string ) ) ]
47+ private static extern void runtime_free_protected_string ( IntPtr ptr ) ;
48+
3549 [ DllImport ( NativeScriptLibrary , EntryPoint = nameof ( runtime_get_last_js_error ) ) ]
3650 private static extern IntPtr runtime_get_last_js_error ( ) ;
3751
@@ -191,6 +205,27 @@ private static bool ConsumeDebugBreakMarker()
191205 }
192206#endif
193207
208+ /// Reads a file that may live inside a sealed app.nsbundle instead of on disk: tries the
209+ /// protected-VFS native call first (cheap no-op when no bundle is loaded), falls back to
210+ /// the real filesystem. `path` is whatever candidate the caller already built for
211+ /// `File.Exists`/`File.ReadAllText` — the native side strips it down to the bundle's
212+ /// virtual (app/App-relative) path itself, so no extra bookkeeping is needed here.
213+ private static string TryReadVirtual ( string path )
214+ {
215+ IntPtr ptr = IntPtr . Zero ;
216+ try
217+ {
218+ ptr = runtime_read_protected_file ( path ) ;
219+ return ptr == IntPtr . Zero ? null : Marshal . PtrToStringUTF8 ( ptr ) ;
220+ }
221+ catch { return null ; }
222+ finally { if ( ptr != IntPtr . Zero ) runtime_free_protected_string ( ptr ) ; }
223+ }
224+
225+ private static bool VExists ( string path ) => TryReadVirtual ( path ) != null || File . Exists ( path ) ;
226+
227+ private static string VReadAllText ( string path ) => TryReadVirtual ( path ) ?? File . ReadAllText ( path ) ;
228+
194229 public void RunMainScript ( )
195230 {
196231 if ( ! _initialized )
@@ -208,7 +243,7 @@ public void RunMainScript()
208243 foreach ( var chunkName in new [ ] { "runtime.js" , "vendor.js" } )
209244 {
210245 var chunkPath = Path . Combine ( dir , chunkName ) ;
211- if ( File . Exists ( chunkPath ) &&
246+ if ( VExists ( chunkPath ) &&
212247 ! string . Equals ( chunkPath , Path . GetFullPath ( entryPath ) , StringComparison . OrdinalIgnoreCase ) )
213248 {
214249 chunks . Add ( chunkPath ) ;
@@ -218,7 +253,7 @@ public void RunMainScript()
218253
219254 foreach ( var scriptPath in chunks )
220255 {
221- var script = File . ReadAllText ( Path . GetFullPath ( scriptPath ) ) ;
256+ var script = VReadAllText ( Path . GetFullPath ( scriptPath ) ) ;
222257 try
223258 {
224259 runtime_runscript ( _runtime , script , Path . GetFileName ( scriptPath ) ) ;
@@ -260,7 +295,7 @@ private static string ResolveEntryScriptPath()
260295 foreach ( var dir in appDirCandidates )
261296 {
262297 var candidate = Path . Combine ( dir , "package.json" ) ;
263- if ( File . Exists ( candidate ) )
298+ if ( VExists ( candidate ) )
264299 {
265300 packageJsonPath = candidate ;
266301 resolvedBaseDir = dir ;
@@ -269,7 +304,7 @@ private static string ResolveEntryScriptPath()
269304 }
270305
271306 // Also accept package.json at the project root (parent of bin/).
272- if ( packageJsonPath == null && File . Exists ( Path . Combine ( parentDir , "package.json" ) ) )
307+ if ( packageJsonPath == null && VExists ( Path . Combine ( parentDir , "package.json" ) ) )
273308 {
274309 packageJsonPath = Path . Combine ( parentDir , "package.json" ) ;
275310 resolvedBaseDir = parentDir ;
@@ -278,7 +313,7 @@ private static string ResolveEntryScriptPath()
278313 string Fallback ( ) =>
279314 appDirCandidates
280315 . SelectMany ( d => new [ ] { Path . Combine ( d , "bundle.js" ) , Path . Combine ( d , "bundle.mjs" ) } )
281- . FirstOrDefault ( File . Exists ) ;
316+ . FirstOrDefault ( VExists ) ;
282317
283318 if ( packageJsonPath == null )
284319 return Fallback ( ) ;
@@ -304,7 +339,7 @@ string Fallback() =>
304339
305340 private static RuntimePackageConfig ParsePackageConfig ( string packageJsonPath )
306341 {
307- using var doc = JsonDocument . Parse ( File . ReadAllText ( packageJsonPath ) ) ;
342+ using var doc = JsonDocument . Parse ( VReadAllText ( packageJsonPath ) ) ;
308343 var config = new RuntimePackageConfig ( ) ;
309344 if ( doc . RootElement . TryGetProperty ( "main" , out var main ) && main . ValueKind == JsonValueKind . String )
310345 config . Main = main . GetString ( ) ;
@@ -321,11 +356,11 @@ private static string ResolveScriptPath(string baseDir, string scriptPath)
321356 foreach ( var candidate in new [ ] { normalized , normalized + ".js" , normalized + ".mjs" } )
322357 {
323358 var direct = Path . IsPathRooted ( candidate ) ? candidate : Path . Combine ( baseDir , candidate ) ;
324- if ( File . Exists ( direct ) ) return direct ;
359+ if ( VExists ( direct ) ) return direct ;
325360 var appLower = Path . Combine ( baseDir , "app" , candidate ) ;
326- if ( File . Exists ( appLower ) ) return appLower ;
361+ if ( VExists ( appLower ) ) return appLower ;
327362 var appUpper = Path . Combine ( baseDir , "App" , candidate ) ;
328- if ( File . Exists ( appUpper ) ) return appUpper ;
363+ if ( VExists ( appUpper ) ) return appUpper ;
329364 }
330365 return null ;
331366 }
0 commit comments