# Referencia de la API pública (`OmsiLaunch.Api`)
> Traducción de la [página original en inglés](https://github.com/lmonteirotech/OmsiLaunch/blob/v0.1.0-beta.3/docs/reference/public-api.md) de OmsiLaunch 0.1.0-beta3. La página en inglés es la referencia normativa: si hay diferencias, prevalecen la página en inglés y el código.
Esta página es la referencia normativa de la API pública administrada de OmsiLaunch 0.1.0-beta3: el ensamblado `OmsiLaunch.Api` (contratos) y el punto de entrada para integradores `OmsiLaunchService` en `OmsiLaunch.Core`. Documenta únicamente lo que hace el código actual. Todo lo que un integrador puede llamar, recibir u observar se enumera aquí con su nivel de estabilidad; todo lo que no aparece en esta página no es una superficie de integración.
El [inventario de la API pública](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/public-api-inventory/index.md) generado enumera cada tipo y miembro público de `OmsiLaunch.Api`, `OmsiLaunch.Core` y `OmsiLaunch.Process` con su firma y su estabilidad; un gate de documentación (control de calidad de la documentación) falla cuando el inventario y los ensamblados difieren. Esta página explica la semántica.
Páginas relacionadas: [referencia de LaunchSpec](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/launchspec/index.md), [códigos de error](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/errors/index.md), [ciclo de vida de la sesión](https://omsilaunch.omsimods.com.br/es-latam/docs/concepts/session-lifecycle/index.md), [transacciones y recuperación](https://omsilaunch.omsimods.com.br/es-latam/docs/concepts/transactions-and-recovery/index.md), [control de runtime](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/runtime-control/index.md), [capacidades](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/capabilities/index.md), [plano de control local](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/local-control/index.md), [códigos de salida](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/exit-codes/index.md), [estado de la validación en runtime](https://omsilaunch.omsimods.com.br/es-latam/docs/status/runtime-validation-status/index.md).
## Vocabulario de estabilidad
| Nivel | Significado en esta página |
| --- | --- |
| `STABLE_BETA` | El contrato está congelado para la línea de protocolo 0.1 y la ruta está validada en runtime en `research/reports/OMSILAUNCH-RUNTIME-VALIDATION-MATRIX.md`. |
| `EXPERIMENTAL` | Se puede llamar y está probado, pero el contrato o la evidencia de runtime pueden cambiar antes de que pase a ser estable. |
| `PARTIAL` | Presente en el contrato; solo una parte del comportamiento está implementada o validada (el texto indica qué parte). |
| `INTERNAL` | Es público en el ensamblado por razones técnicas (el puente comparte el tipo), pero no es una superficie de integración; puede cambiar sin previo aviso. |
| `UNAVAILABLE` | Presente en el contrato, pero el build actual lo rechaza. |
## Descripción general de los ensamblados
| Ensamblado | Función para los integradores |
| --- | --- |
| `OmsiLaunch.Api` | Contratos puros: records, enums, `IOmsiLaunch`, registro de capacidades, catálogo de errores, formatos de transmisión, helpers de D3D. No contiene ningún `IntPtr`, `nint`, handle de Win32, dirección nativa ni objeto de proceso. |
| `OmsiLaunch.Core` | `OmsiLaunchService` (la implementación de `IOmsiLaunch`), `OmsiLaunchRuntimePaths`, `SessionPlanner`, `LaunchValidation`, `SessionProfileCompiler`. |
| `OmsiLaunch.Process` | `IRuntimePlatform` y `CurrentWindowsX64Platform` (el único adaptador de plataforma), `InstallationLease`. Necesarios para construir el servicio. |
| `OmsiLaunch.Configuration`, `OmsiLaunch.Content`, `OmsiLaunch.Interop`, `OmsiLaunch.Plugin`, `OmsiLaunch.Builds.Omsi23004` | Ensamblados de implementación. Sus tipos públicos son `INTERNAL` para los integradores. |
## Punto de entrada: `OmsiLaunchService` y `OmsiLaunchRuntimePaths`
```csharp
public sealed record OmsiLaunchRuntimePaths(string PluginBuildDirectory, string NativeBridgePath, string? ReleaseManifestPath = null);
public sealed class OmsiLaunchService : IOmsiLaunch
{
public OmsiLaunchService(IRuntimePlatform platform, OmsiLaunchRuntimePaths runtimePaths);
}
```
| Parámetro | Valor válido | No válido / predeterminado |
| --- | --- | --- |
| `platform` | `new CurrentWindowsX64Platform()` (espacio de nombres `OmsiLaunch.Process`). Detecta la plataforma, crea el proceso de OMSI con `CreateProcessW`, espera a que termine y lo finaliza. | No se distribuye ninguna otra implementación. Un `IRuntimePlatform` personalizado es `INTERNAL`. |
| `PluginBuildDirectory` | Directorio que contiene los archivos de referencia del cierre del plugin permanente: `OmsiLaunch.Plugin.opl`, `OmsiLaunch.PluginNE.dll`, `OmsiLaunch.Plugin.deps.json`, `OmsiLaunch.Plugin.runtimeconfig.json` y cada `OmsiLaunch.*.dll` del cierre administrado (debe incluir `OmsiLaunch.Plugin.dll`). En un paquete instalado es `\plugins`. | Directorio o archivo faltante: `PlanSessionAsync` devuelve un plan no ejecutable con `OL_E_RUNTIME_ARTIFACT_MISSING`. |
| `NativeBridgePath` | Ruta de `OmsiLaunch.Native.x86.dll` (en el paquete: `\plugins\OmsiLaunch.Native.x86.dll`). | Igual que en la fila anterior. |
| `ReleaseManifestPath` | `release-manifest.json` junto a `OmsiLaunch.exe`, cuando existe. Proporciona el SHA-256 esperado de cada archivo de `plugins/` (`plugin.integrity.reference = manifest`). | `null` (estructura de desarrollo): de los archivos instalados solo se verifica su presencia y su coherencia interna con respecto al cierre de referencia (`plugin.integrity.reference = self`). Manifiesto mal formado: `OL_E_RELEASE_MANIFEST_INVALID`. |
El servicio lee estas rutas en cada `PlanSessionAsync` y `StartSessionAsync`; nunca copia, prepara ni elimina archivos del plugin (consulte [plugin permanente](https://omsilaunch.omsimods.com.br/es-latam/docs/concepts/permanent-plugin/index.md)). La CLI construye el servicio exactamente así (`tools/OmsiLaunch.Cli/Program.cs`):
```csharp
using OmsiLaunch.Api;
using OmsiLaunch.Core;
using OmsiLaunch.Process;
var package = AppContext.BaseDirectory; // directory that contains OmsiLaunch.exe
var plugins = Path.Combine(package, "plugins");
var manifest = Path.Combine(package, "release-manifest.json");
IOmsiLaunch launch = new OmsiLaunchService(
new CurrentWindowsX64Platform(),
new OmsiLaunchRuntimePaths(plugins, Path.Combine(plugins, "OmsiLaunch.Native.x86.dll"), File.Exists(manifest) ? manifest : null));
```
Cree un único servicio por proceso y compártalo. Estabilidad: `STABLE_BETA`.
## Reglas de propiedad de la sesión
| Regla | Detalle |
| --- | --- |
| Un propietario por instalación | `StartSessionAsync` adquiere el lease de la instalación (bloqueo exclusivo), un semáforo con nombre `Local\OmsiLaunch.Installation.` (SHA-256 de la ruta raíz completa en mayúsculas), y lo mantiene hasta que el supervisor haya restaurado la instalación. Un segundo inicio sobre la misma raíz desde cualquier proceso de la misma sesión de inicio de sesión de Windows falla con `OL_E_INSTALLATION_BUSY` (se informa como una sesión `Failed`, consulte `StartSessionAsync`). El lease es por sesión de inicio de sesión de Windows, no entre sesiones de inicio de sesión distintas, y no se libera mientras otro proceso tenga un handle abierto hacia él (riesgo aceptado). |
| Los handles son locales al proceso | `SessionHandle` encapsula el `Guid` de la sesión. Solo tiene sentido para la instancia de `OmsiLaunchService` que lo devolvió. Un handle construido a partir de un `Guid` conocido en otro proceso (o en otra instancia del servicio) produce `KeyNotFoundException`. El control entre procesos pasa por el [plano de control local](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/local-control/index.md), no por handles. |
| Llame siempre a `CloseAsync` | A partir de `StartSessionAsync`, el proceso es dueño de una transacción duradera. `CloseAsync` solicita la detención canónica cuando hace falta, espera al supervisor (salida del proceso, restauración exacta, liberación del lease) y olvida la sesión. Debe llamarse en todas las rutas de salida, incluso después de un estado `Failed`. Sin esa llamada, la entrada de la sesión permanece en memoria; la restauración en sí la realiza el supervisor de todos modos. |
| Las sesiones fallidas siguen siendo sesiones | Un inicio que falla después de que `StartSessionAsync` retornó informa `SessionState.Failed`; el handle sigue siendo válido para `GetStatusAsync`/`WaitForAsync` hasta `CloseAsync`. |
| Los planes se vuelven a verificar | `StartSessionAsync` vuelve a calcular el hash de `Omsi.exe` y vuelve a planificar la especificación; un plan que ya no es ejecutable se rechaza con `OL_E_PLAN_NOT_RUNNABLE`. |
## `IOmsiLaunch`
```csharp
public interface IOmsiLaunch
{
Task PlanSessionAsync(LaunchSpec spec, CancellationToken cancellationToken = default);
Task StartSessionAsync(SessionPlan plan, CancellationToken cancellationToken = default);
Task GetStatusAsync(SessionHandle session, CancellationToken cancellationToken = default);
Task WaitForAsync(SessionHandle session, SessionState state, TimeSpan timeout, CancellationToken cancellationToken = default);
Task StopAsync(SessionHandle session, CancellationToken cancellationToken = default);
Task CloseAsync(SessionHandle session, CancellationToken cancellationToken = default);
Task ExecuteRuntimeAsync(SessionHandle session, RuntimeCommand command, TimeSpan timeout, CancellationToken cancellationToken = default);
Task> GetCapabilitiesAsync(InstallationSpec installation, CancellationToken cancellationToken = default);
Task> DiscoverAsync(InstallationSpec installation, ContentQueryKind query, OptionalValue scope = default, CancellationToken cancellationToken = default);
Task RecoverPendingAsync(InstallationSpec installation, bool restore, CancellationToken cancellationToken = default);
}
```
Hechos comunes a todos los métodos:
- Los handles desconocidos o ya cerrados lanzan `KeyNotFoundException` ("Unknown OmsiLaunch session.").
- Ningún método requiere una sesión en estado Running, salvo `ExecuteRuntimeAsync`.
- Las excepciones que llevan un código de OmsiLaunch colocan el código al comienzo de `Exception.Message` (`"OL_E_PLAN_NOT_RUNNABLE: ..."`). La CLI extrae los códigos de los mensajes de la misma forma (`CliProgram.Classify`).
- Build compatible: solo `Omsi23004_692EBFBF` (más el hash de la lista de permitidos de Steam LAA, que se acepta; el gameplay no está validado, ya que requiere una instalación genuina de Steam). Consulte [compatibilidad](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/compatibility/index.md).
### Ejemplo mínimo completo
```csharp
var none = new Dictionary>();
var spec = new LaunchSpec(
Installation: new InstallationSpec(@"C:\OMSI 2"),
World: new WorldSpec(WorldMode.NewMap, OptionalValue.Set(@"maps\Grundorf\global.cfg"), OptionalValue.Unset, OptionalValue.Set(1)),
Date: new DateSpec(DateTimeMode.Unset, OptionalValue.Unset),
Time: new TimeSpec(DateTimeMode.Unset, OptionalValue.Unset),
PlayerVehicle: OptionalValue.Unset,
Environment: new EnvironmentSpec(none, none, none, none, none, none, none, none),
Behavior: new LaunchBehaviorSpec());
var plan = await launch.PlanSessionAsync(spec);
if (!plan.IsRunnable) { foreach (var d in plan.Diagnostics) Console.WriteLine($"{d.Code}: {d.Message}"); return; }
var session = await launch.StartSessionAsync(plan);
try
{
var status = await launch.WaitForAsync(session, SessionState.Running, TimeSpan.FromSeconds(plan.Spec.Behavior.StartupTimeoutSeconds + 5));
if (status.State == SessionState.Running)
{
var time = await launch.ExecuteRuntimeAsync(session, new RuntimeCommand(session.SessionId, 1, "time.read"), TimeSpan.FromSeconds(5));
Console.WriteLine(time.Succeeded ? $"{time.Values!["hour"]}:{time.Values["minute"]}" : time.ErrorCode);
await launch.StopAsync(session);
}
var final = await launch.WaitForAsync(session, SessionState.Completed, Timeout.InfiniteTimeSpan);
Console.WriteLine(final.State); // Completed, or Failed with diagnostics
}
finally
{
await launch.CloseAsync(session); // always
}
```
### `PlanSessionAsync`
| Aspecto | Detalle |
| --- | --- |
| Firma | `Task PlanSessionAsync(LaunchSpec spec, CancellationToken cancellationToken = default)` |
| Propósito | Compilar un `LaunchSpec` en un `SessionPlan` sin iniciar OMSI: validar la especificación, detectar la plataforma, obtener la huella de `Omsi.exe`, resolver las identidades de contenido, calcular las mutaciones de archivos planificadas, enumerar las capacidades requeridas y las no compatibles, y decidir `IsRunnable`. Capacidad pública `session.plan`. |
| Parámetros | `spec`: un `LaunchSpec` completamente poblado (consulte la [referencia de LaunchSpec](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/launchspec/index.md)). `Installation`, `World`, `Date`, `Time`, `Environment` (los ocho diccionarios) y `Behavior` no deben ser null; los miembros opcionales pueden ser `null`. `RootPath` debe ser un directorio absoluto; una raíz vacía se registra como `OL_E_INSTALLATION_NOT_FOUND`, pero la sonda de plataforma sobre una ruta vacía lanza `ArgumentException` antes de que se devuelva el plan, por lo que nunca debe pasarse una raíz vacía. |
| Devuelve | `SessionPlan` con un `SessionId` nuevo, `BuildProfileId = "Omsi23004_692EBFBF"` (siempre esta constante, incluso cuando el archivo ejecutable no coincide), el `Spec` de entrada, `Platform`, `ResolvedContent`, `TouchedFiles`, `RuntimeArtifacts` (rutas de destino `plugins\OmsiLaunch.*` más `"OmsiLaunch startup handoff v4"`), `RequiredCapabilities`, `UnsupportedRequestedFeatures`, `PlannedMutations`, `Diagnostics`, `IsRunnable`. `IsRunnable` es `true` exactamente cuando ningún código de diagnóstico comienza con `OL_E_`. Los diagnósticos informativos (`plugin.integrity.reference` con el mensaje `self` o `manifest`, `session_profile.selected`) nunca vuelven no ejecutable un plan. |
| Errores incluidos en el resultado | Todo error de planificación es un diagnóstico, no una excepción: `OL_E_INSTALLATION_NOT_FOUND`, `OL_E_INSTALLATION_NOT_WRITABLE`, `OL_E_UNSUPPORTED_OPERATING_SYSTEM`, `OL_E_UNSUPPORTED_BUILD`, `OL_E_MAP_NOT_FOUND`, `OL_E_ENTRYPOINT_NOT_FOUND`, `OL_E_ENTRYPOINT_REQUIRED`, `OL_E_SITUATION_NOT_FOUND`, `OL_E_SITUATION_MAP_NOT_FOUND`, `OL_E_VEHICLE_NOT_FOUND`, `OL_E_REPAINT_NOT_FOUND`, `OL_E_HOF_NOT_FOUND`, `OL_E_DATE_TIME_APPLY_FAILED`, `OL_E_INVALID_ARGUMENT`, `OL_E_CAPABILITY_UNAVAILABLE`, `OL_E_UNKNOWN_SETTING`, `OL_E_SETTING_NOT_WRITABLE`, `OL_E_SESSION_PRESENTATION_INVALID` (el mensaje lleva el código de splash/ITX), `OL_E_PERMANENT_PLUGIN_MISSING`, `OL_E_PERMANENT_PLUGIN_HASH_MISMATCH`, `OL_E_PERMANENT_PLUGIN_MANIFEST_INCOMPLETE` (el cierre del plugin instalado en `plugins\` se verifica contra el manifiesto de la release durante la planificación), `OL_E_RUNTIME_ARTIFACT_MISSING` (el mensaje puede llevar `OL_E_RELEASE_MANIFEST_INVALID`). Condiciones completas: [reglas de validación de LaunchSpec](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/launchspec/index.md#validation-rules-and-non-runnable-diagnostics). |
| Excepciones | `OperationCanceledException` si el token ya está cancelado al entrar (el único punto de verificación); `ArgumentException`/`NotSupportedException` para rutas raíz sintácticamente no válidas; `NullReferenceException`/`ArgumentNullException` para miembros obligatorios null; `System.Text.Json.JsonException` para un manifiesto de release sintácticamente no válido. |
| Cancelación | Se verifica una vez al entrar. Después, la planificación es trabajo sincrónico sobre el sistema de archivos. |
| Requiere una sesión en ejecución | No. |
| Modifica el estado de OMSI | No. |
| Modifica el sistema de archivos | No (lee `Omsi.exe`, los archivos de contenido, el cierre del plugin y el manifiesto). Los valores de los ajustes no se validan aquí (solo la existencia de la clave y si se puede escribir); un valor no válido falla al iniciar con `OL_E_INVALID_SETTING_VALUE`. |
| Transacción / restauración | Ninguna. |
| Limitaciones | Solicitar cualquier modo de `Date`/`Time`/`Year` distinto de `Unset`, cualquier modo de `Weather` distinto de `Unset`, cualquier campo de `PlayerVehicle`, documentos de `Input`, `EntrypointIdentity` o `WorldMode.LastMapState` produce `OL_E_CAPABILITY_UNAVAILABLE` y un plan no ejecutable en este build (entradas `STATICALLY_PARTIAL` / `UNSUPPORTED_FOR_CURRENT_PROFILE` en `UnsupportedRequestedFeatures`). |
| Estabilidad | `STABLE_BETA`. |
| Ejemplo | `var plan = await launch.PlanSessionAsync(spec); Console.WriteLine(plan.IsRunnable ? "READY" : string.Join(", ", plan.Diagnostics.Where(d => d.Code.StartsWith("OL_E_")).Select(d => d.Code)));` |
### `StartSessionAsync`
| Aspecto | Detalle |
| --- | --- |
| Firma | `Task StartSessionAsync(SessionPlan plan, CancellationToken cancellationToken = default)` |
| Propósito | Iniciar una sesión transaccional administrada de OMSI a partir de un plan ejecutable: adquirir el lease de la instalación, recuperar un journal (registro de la transacción) obsoleto, validar el cierre del plugin permanente, tomar el snapshot y aplicar los overlays de los archivos de la sesión, crear el handoff de arranque, el slot de telemetría y el buzón de runtime, iniciar `Omsi.exe`, registrar el proceso en el journal y entregar la sesión a un supervisor en segundo plano. Capacidad pública `session.start`. |
| Parámetros | `plan`: un `SessionPlan` con `IsRunnable == true`. La especificación contenida en el plan se vuelve a planificar; del plan del llamador solo se conserva `plan.SessionId`. `plan.Spec.Behavior.StartupTimeoutSeconds` debe estar en el rango 1..600. |
| Devuelve | `SessionHandle(plan.SessionId)` en cuanto `Omsi.exe` se creó y quedó registrado (estado `WaitingForPlugin`), o en cuanto la ruta de inicio falló (estado `Failed`). No espera al gameplay; use `WaitForAsync(session, SessionState.Running, ...)`. |
| Excepciones | `InvalidOperationException("OL_E_PLAN_NOT_RUNNABLE")` cuando `plan.IsRunnable` es false; `InvalidOperationException("OL_E_PLAN_NOT_RUNNABLE: ")` cuando el plan recalculado no es ejecutable (por ejemplo, `Omsi.exe` cambió, se eliminó contenido o falta el cierre del plugin); `InvalidOperationException("Duplicate session id.")` cuando todavía hay registrada una sesión con el mismo id (llame primero a `CloseAsync`); `ArgumentOutOfRangeException` cuando `StartupTimeoutSeconds` está fuera del rango 1..600; `OperationCanceledException` cuando se cancela antes o durante la nueva planificación; además de todo lo que lanza `PlanSessionAsync`. En todos los casos con excepción, no se registra ninguna sesión. |
| Errores incluidos en el resultado | Cualquier fallo posterior a la nueva planificación se captura dentro de la ruta de inicio: la sesión se registra, su estado es `Failed` y sus diagnósticos contienen `OL_E_START_SESSION`, cuyo mensaje es el mensaje interno (que comienza con el código interno cuando lo hay): `OL_E_INSTALLATION_BUSY` (lease ocupado o un proceso de OMSI registrado en el journal que sigue vivo), `OL_E_PERMANENT_PLUGIN_MISSING`, `OL_E_PERMANENT_PLUGIN_MANIFEST_INCOMPLETE`, `OL_E_PERMANENT_PLUGIN_HASH_MISMATCH`, `OL_E_RELEASE_MANIFEST_INVALID`, `OL_E_SPLASH_ASSET_MISSING`, `OL_E_SPLASH_ASSET_DIRECTORY_MISSING`, `OL_E_SPLASH_FORMAT_UNSUPPORTED`, `OL_E_ITX_PROFILE_REQUIRED`, `OL_E_ITX_PROFILE_MISSING`, `OL_E_ITX_PROFILE_INVALID`, `OL_E_ITX_TARGET_OUTSIDE_TEXTURE_PATH`, `OL_E_UNKNOWN_SETTING`, `OL_E_SETTING_NOT_WRITABLE`, `OL_E_INVALID_SETTING_VALUE`, `OL_E_CLOSECHECK_REMOVE_FAILED`, `OL_E_RECOVERY_BACKUP_CORRUPT`, `OL_E_RECOVERY_ABSENT_OWNERSHIP_MISMATCH`, `OL_E_RECOVERY_ABSENT_OWNERSHIP_UNVERIFIED` (solo cuando el reintento diferido con los overlays de esta sesión sigue sin poder demostrar la propiedad), `OL_E_RECOVERY_JOURNAL_REMOVE_FAILED`, `OL_E_PROCESS_START_FAILED`, `OL_E_PROCESS_CREATION_TIME_FAILED`. La limpieza puede agregar `OL_E_PROCESS_CLEANUP_FAILED`, `OL_E_RESTORE_DEFERRED` (salida de OMSI no confirmada; el journal se conserva) u `OL_E_RESTORE_FAILED`. Los fallos posteriores los informa el supervisor (consulte [ciclo de vida de la sesión](https://omsilaunch.omsimods.com.br/es-latam/docs/concepts/session-lifecycle/index.md)). |
| Cancelación | Antes o durante la nueva planificación: lanza una excepción. Después, el token se pasa a la transacción y a la creación del proceso; una cancelación en ese punto se trata como cualquier fallo de inicio (`Failed` + `OL_E_START_SESSION: The operation was canceled.`), el proceso (si se creó) se finaliza y la instalación se restaura. |
| Requiere una sesión en ejecución | No. |
| Modifica el estado de OMSI | Sí: crea el proceso de OMSI con las variables de entorno `OMSILAUNCH_SESSION_ID`, `OMSILAUNCH_HANDOFF_NAME`, `OMSILAUNCH_TELEMETRY_NAME`, `OMSILAUNCH_RUNTIME_CHANNEL`, `OMSILAUNCH_INTERNET_TEXTURES_MODE`. |
| Modifica el sistema de archivos | Sí, dentro de la raíz de la instalación: `.omsilaunch\diagnostics\-host.log` (retención: las 50 sesiones más recientes), `.omsilaunch\journal.json`, `.omsilaunch\backup\\*.bin`, `.omsilaunch\assets\splash\*.bmp` (se copian una vez para la pantalla de presentación (splash) administrada), overlays de la sesión (parches de `options.cfg`, `GUI\NewSplashscreen_*.bmp`, `Texture\standard.itx`), eliminaciones de la sesión (destinos ITX, `Texture\standard.ipr`, `closecheck`) y la eliminación permanente de un `closecheck` obsoleto preexistente cuando `SuppressStaleClosecheckWarning` es true (diagnóstico `closecheck.stale-removed`). |
| Transacción / restauración | Abre la transacción (`Prepared` → `Applied` → `RuntimeDeployed` → `HandoffCreated` → `ProcessStarted`). Toda ruta de salida de la sesión termina en una restauración. Consulte [transacciones y recuperación](https://omsilaunch.omsimods.com.br/es-latam/docs/concepts/transactions-and-recovery/index.md). |
| Limitaciones | Solo `WorldMode.NewMap` con `PresentedEntrypointIndex` y `WorldMode.SavedSituation` llegan al gameplay. `WorldMode.LastMapState` es `UNAVAILABLE`. Las solicitudes de fecha/hora/clima/vehículo del jugador/entrada nunca llegan a este método, porque son no ejecutables en el momento de la planificación. |
| Estabilidad | `STABLE_BETA` (los ciclos de vida NEW_MAP y SAVED_SITUATION están validados en runtime). |
| Ejemplo | `var session = await launch.StartSessionAsync(plan); var s = await launch.GetStatusAsync(session); if (s.State == SessionState.Failed) Console.WriteLine(s.Diagnostics.Last(d => d.Code.StartsWith("OL_E_")).Message);` |
### `GetStatusAsync`
| Aspecto | Detalle |
| --- | --- |
| Firma | `Task GetStatusAsync(SessionHandle session, CancellationToken cancellationToken = default)` |
| Propósito | Leer el estado semántico del ciclo de vida, los diagnósticos recopilados hasta el momento y la lista acotada de eventos de runtime. Capacidad pública `session.status`. |
| Parámetros | `session`: un handle devuelto por `StartSessionAsync` que todavía no se cerró. |
| Devuelve | `SessionStatus(SessionId, State, Diagnostics, RuntimeEvents)`: un snapshot inmutable (los arreglos se copian bajo el bloqueo de la sesión). `RuntimeEvents` nunca es `null` para una sesión viva. |
| Excepciones | `KeyNotFoundException` para handles desconocidos o cerrados. En cualquier otro caso nunca lanza excepciones. |
| Cancelación | El token se ignora (la llamada se completa de forma sincrónica). |
| Requiere una sesión en ejecución | No. |
| Modifica OMSI / sistema de archivos / transacción | No / No / Ninguna. |
| Estabilidad | `STABLE_BETA`. |
| Ejemplo | `var status = await launch.GetStatusAsync(session); Console.WriteLine($"{status.State} events={status.RuntimeEvents!.Count}");` |
### `WaitForAsync`
| Aspecto | Detalle |
| --- | --- |
| Firma | `Task WaitForAsync(SessionHandle session, SessionState state, TimeSpan timeout, CancellationToken cancellationToken = default)` |
| Propósito | Consultar periódicamente (cada 100 ms) hasta que la sesión esté en `state` o en un estado terminal (`Completed`, `Failed`), o hasta que transcurra el timeout; luego devolver el estado actual. |
| Parámetros | `state`: cualquier `SessionState`. Esperar un estado transitorio que ya pasó (o que nunca se establece, consulte [ciclo de vida de la sesión](https://omsilaunch.omsimods.com.br/es-latam/docs/concepts/session-lifecycle/index.md)) espera hasta un estado terminal o hasta el timeout. `timeout`: cualquier `TimeSpan` no negativo o `Timeout.InfiniteTimeSpan`. |
| Devuelve | El estado en el momento en que terminó la espera. Al vencer el timeout se devuelve el estado, no una excepción: verifique `State` usted mismo. Una espera de `Running` que termina en `Failed` retorna de inmediato con los diagnósticos del fallo. |
| Excepciones | `KeyNotFoundException`; `OperationCanceledException` cuando se cancela el token del llamador (solo se propaga la cancelación del llamador; el timeout interno no). |
| Cancelación | El token del llamador se respeta en cada intervalo de 100 ms. |
| Requiere una sesión en ejecución | No. |
| Modifica OMSI / sistema de archivos / transacción | No / No / Ninguna. |
| Estabilidad | `STABLE_BETA`. |
| Ejemplo | `var running = await launch.WaitForAsync(session, SessionState.Running, TimeSpan.FromSeconds(185)); if (running.State != SessionState.Running) { /* timed out or Failed */ }` |
### `StopAsync`
| Aspecto | Detalle |
| --- | --- |
| Firma | `Task StopAsync(SessionHandle session, CancellationToken cancellationToken = default)` |
| Propósito | Solicitar la detención canónica. Establece el flag de detención y retorna de inmediato; el supervisor detecta el flag dentro de su ciclo de 100 ms, llama a `TerminateProcess` sobre `Omsi.exe`, espera la salida, marca el journal como `ProcessExited`, restaura cada archivo propiedad de la sesión y libera el lease. Se trata de una finalización forzada: la rutina de cierre propia de OMSI no se ejecuta y OMSI no reescribe `options.cfg` al salir (deliberado; protege la transacción). El cierre cooperativo mediante `WM_CLOSE` no está implementado (decisión de producto; en el cierre de validación en runtime, OMSI no se cerró dentro de los 30 s posteriores a `WM_CLOSE`, `L05b`). Capacidad pública `session.stop`. |
| Parámetros | `session`. |
| Devuelve | Una tarea completada; no espera la finalización ni la restauración. Use `WaitForAsync(session, SessionState.Completed, ...)` para observar la finalización. |
| Excepciones | `KeyNotFoundException`. |
| Cancelación | El token se ignora. |
| Requiere una sesión en ejecución | No. Es idempotente; una detención solicitada antes de que arranque el supervisor se respeta en cuanto este arranca; una detención sobre una sesión terminal no tiene efecto. |
| Modifica el estado de OMSI | Sí: finaliza el proceso de OMSI (código de salida 1). |
| Modifica el sistema de archivos | Indirectamente: desencadena la restauración, la eliminación del journal y la eliminación de las copias de seguridad por parte del supervisor. |
| Transacción / restauración | Desencadena `ProcessExited` → `Restoring` → `Restored`. Los cambios del lado del runtime realizados mediante `ExecuteRuntimeAsync` (escrituras del reloj, vehículos generados, variables de script, texturas D3D) no se restauran; desaparecen con el proceso. |
| Estabilidad | `STABLE_BETA`. |
| Ejemplo | `await launch.StopAsync(session); var done = await launch.WaitForAsync(session, SessionState.Completed, TimeSpan.FromMinutes(1));` |
### `CloseAsync`
| Aspecto | Detalle |
| --- | --- |
| Firma | `Task CloseAsync(SessionHandle session, CancellationToken cancellationToken = default)` |
| Propósito | Liberar el handle del consumidor sin dejar abandonada la transacción: si la sesión no es terminal, solicitar la detención canónica; luego esperar la tarea de ciclo de vida del supervisor (salida del proceso, restauración, liberación del lease); por último, olvidar la sesión. |
| Parámetros | `session`. |
| Devuelve | Se completa cuando la sesión es terminal y se eliminó. Después de que retorna, el handle es desconocido (`KeyNotFoundException` en cualquier llamada posterior, incluida una segunda llamada a `CloseAsync`). |
| Excepciones | `KeyNotFoundException`; `OperationCanceledException` si el llamador cancela mientras espera al supervisor. En ese caso la sesión no se elimina y el supervisor sigue ejecutándose; llame de nuevo a `CloseAsync`. |
| Cancelación | Se aplica solo a la espera; nunca cancela la restauración. |
| Requiere una sesión en ejecución | No. |
| Modifica el estado de OMSI | Sí, cuando la sesión sigue viva (igual que `StopAsync`). |
| Modifica el sistema de archivos | Indirectamente (restauración por parte del supervisor). |
| Transacción / restauración | Garantiza que la transacción se lleve hasta el final antes de liberar el handle (cuando el supervisor llegó a iniciarse). Para una sesión que falló antes de que se iniciara el supervisor, la ruta de inicio ya restauró o informó `OL_E_RESTORE_DEFERRED`. |
| Estabilidad | `STABLE_BETA`. |
| Ejemplo | `try { ... } finally { await launch.CloseAsync(session); }` |
### `ExecuteRuntimeAsync`
| Aspecto | Detalle |
| --- | --- |
| Firma | `Task ExecuteRuntimeAsync(SessionHandle session, RuntimeCommand command, TimeSpan timeout, CancellationToken cancellationToken = default)` |
| Propósito | Ejecutar una operación pública de runtime dentro del proceso de OMSI en ejecución a través del buzón de la sesión de una sola solicitud en curso (mapeado en memoria, 64 KiB, solicitud vinculada al id de sesión y al id de solicitud). El plugin ejecuta la operación en el thread de la interfaz de OMSI. Catálogo de operaciones: [control de runtime](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/runtime-control/index.md) y [capacidades](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/capabilities/index.md). |
| Parámetros | `command.SessionId` debe ser igual a `session.SessionId`. `command.RequestId`: un `ulong` elegido por el llamador; use un contador estrictamente creciente por proceso (los helpers de D3D comienzan en 30 000, el propietario de la CLI en 10 001/50 000). `command.Operation`: un id de operación pública de `PublicCapabilityRegistry.PublicRuntimeOperationIds` (por ejemplo, `time.read`, `road-vehicle.read`, `d3d.texture.create`). `command.Arguments`: valores de cadena indexados por nombres ordinales; los nombres obligatorios de cada operación provienen de `PublicCapabilityRegistry.GetRuntimeArguments`. `timeout`: se mide desde el momento en que la solicitud se deposita en el buzón (la espera en cola detrás de otro comando en curso no se cuenta). La CLI usa 5 s (15 s para `road-vehicles.spawn`) como propietario y 8 s / 30 s como cliente. |
| Orden de las verificaciones | 1. Validación del registro (antes de buscar la sesión): operación desconocida o `internal.*` → resultado `Succeeded=false, ErrorCode=OL_E_RUNTIME_OPERATION_UNKNOWN`; argumento obligatorio faltante (ausente o solo con espacios en blanco) → `OL_E_RUNTIME_ARGUMENT_REQUIRED`. 2. Búsqueda de la sesión → `KeyNotFoundException`. 3. `command.SessionId != session.SessionId` → `InvalidOperationException("OL_E_RUNTIME_SESSION_MISMATCH")`. 4. Estado distinto de `Running` → `InvalidOperationException("OL_E_SESSION_NOT_RUNNING")`. 5. Solicitud al buzón. 6. Se eliminan los valores del resultado cuya clave comienza con `internal_` o termina en `_address`, `_pointer`, `_vmt`. |
| Devuelve | `RuntimeCommandResult(SessionId, RequestId, Succeeded, ErrorCode, Values)`. En caso de éxito, `Values` contiene las cadenas semánticas de la operación (documentadas para cada operación en [control de runtime](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/runtime-control/index.md)). |
| Errores incluidos en el resultado | `OL_E_RUNTIME_OPERATION_UNKNOWN`, `OL_E_RUNTIME_ARGUMENT_REQUIRED` (registro); `OL_E_RUNTIME_RESPONSE_TOO_LARGE` (el resultado del plugin excedió el buzón; los resultados de listas acotadas, en cambio, se acortan con `truncated=true`); `OL_E_RUNTIME_SETTING_NOT_PERSISTENT` (`weather.set`, siempre); cada código `OL_E_D3D_*` (con `Values["detail"]` y `Values["native_status"]`); y `OL_E_RUNTIME_OPERATION_FAILED` para cualquier otro fallo del lado del plugin. En este último caso, el código específico no está en `ErrorCode`: es el primer token de `Values["detail"]` (por ejemplo, `detail = "OL_E_RUNTIME_OBJECT_HANDLE_STALE"`, `exception = "InvalidOperationException"`). Códigos que llegan de esta forma: `OL_E_RUNTIME_OPERATION_UNAVAILABLE`, `OL_E_RUNTIME_ARGUMENT_REQUIRED` (verificaciones del lado del plugin), `OL_E_RUNTIME_VALUE_OUT_OF_RANGE`, `OL_E_RUNTIME_VALUE_INVALID`, `OL_E_RUNTIME_OBJECT_HANDLE_REQUIRED`, `OL_E_RUNTIME_OBJECT_HANDLE_STALE`, `OL_E_RUNTIME_SCRIPT_OBJECT_UNAVAILABLE`, `OL_E_RUNTIME_VARIABLE_NOT_FOUND`, `OL_E_RUNTIME_VARIABLE_UNAVAILABLE`, `OL_E_RUNTIME_STRING_VARIABLE_NOT_FOUND`, `OL_E_RUNTIME_CONSTANT_NOT_FOUND`, `OL_E_RUNTIME_CONSTANTS_UNAVAILABLE`, `OL_E_RUNTIME_CURVE_NOT_FOUND`, `OL_E_RUNTIME_CURVE_EMPTY`, `OL_E_RUNTIME_CURVE_DEGENERATE`, `OL_E_RUNTIME_CURVE_INVALID`, `OL_E_RUNTIME_HOF_UNAVAILABLE`, `OL_E_RUNTIME_PLAYER_VEHICLE_UNAVAILABLE`, `OL_E_CAMERA_PRESET_FAMILY_UNSUPPORTED`, `OL_E_TIME_APPLY_FAILED`, `OL_E_RUNTIME_BUS_IDENTITY_INVALID`, `OL_E_MAKEVEHICLE_BUS_NOT_FOUND`, `OL_E_MAKEVEHICLE_DELTA_ZERO`, `OL_E_MAKEVEHICLE_DELTA_MULTIPLE`, `OL_E_MAKEVEHICLE_NATIVE_FAILED`, `OL_E_RUNTIME_CREATED_OBJECT_NOT_IN_COLLECTION`, `OL_E_RUNTIME_CREATED_OBJECT_INVALID`, `OL_E_PLACE_RANDOM_BUS_FAILED`, `OL_E_RUNTIME_SETTING_UNAVAILABLE`. Consulte [códigos de error](https://omsilaunch.omsimods.com.br/es-latam/docs/reference/errors/index.md). |
| Excepciones | `KeyNotFoundException`; `InvalidOperationException` con `OL_E_RUNTIME_SESSION_MISMATCH`, `OL_E_SESSION_NOT_RUNNING`, `OL_E_RUNTIME_CHANNEL_CLOSED` (el supervisor ya liberó el buzón), `OL_E_RUNTIME_CHANNEL_BUSY` (el slot todavía contiene una solicitud abandonada), `OL_E_RUNTIME_REQUEST_ID_REUSED` (todavía hay en el slot una respuesta obsoleta para el mismo id de solicitud); `TimeoutException("OL_E_RUNTIME_REQUEST_TIMEOUT")`; `InvalidDataException("OL_E_RUNTIME_RESPONSE_INVALID")` (respuesta dañada, ajena o que no coincide); `ArgumentOutOfRangeException` cuando la solicitud serializada excede el buzón; `OperationCanceledException`. |
| Cancelación | Se respeta mientras se espera el gate por sesión y cada 20 ms mientras se consulta la respuesta. Cancelar con la solicitud en curso no restablece el slot: la siguiente llamada sobre esa sesión puede fallar con `OL_E_RUNTIME_CHANNEL_BUSY` hasta que el plugin publique su respuesta (que entonces se descarta por obsoleta). Es preferible usar el timeout; un timeout restablece el slot y una respuesta tardía se detecta y se descarta. |
| Requiere una sesión en ejecución | Sí (`SessionState.Running`); de lo contrario se lanza `OL_E_SESSION_NOT_RUNNING`. El buzón existe hasta que el supervisor lo libera durante la restauración. |
| Modifica el estado de OMSI | Depende de la operación: las operaciones `Read` no lo modifican; las operaciones `Write`/`Action` (`time.set`, `camera.set`, `camera.lock`, `camera.unlock`, `road-vehicles.spawn`, `road-vehicles.place-random`, `vehicle.variable.set`, `d3d.texture.*`) modifican estado dentro del proceso que no se restaura. |
| Modifica el sistema de archivos | El host no escribe nada. OMSI puede escribir sus propios archivos como consecuencia (sin seguimiento). |
| Transacción / restauración | Ninguna. |
| Limitaciones | Un solo comando en curso por sesión (las llamadas sobre la misma sesión se serializan). La solicitud y la respuesta están limitadas cada una a 64 KiB menos 8 bytes; las cargas de píxeles D3D, a 48 KiB. `internal.road-vehicles.make-basic` es `INTERNAL` e inaccesible. `weather.set` es `UNAVAILABLE`. `timetable.logs.read` no es una lista acotada y puede devolver `OL_E_RUNTIME_RESPONSE_TOO_LARGE` con horarios grandes. `camera.lock` es `EXPERIMENTAL`; requiere un vehículo del jugador y está validado en runtime (`CAM01`), aunque la cadena `RuntimeValidation` del registro todavía indica `STATICALLY_VALIDATED`. Los handles (`rv-NNNNNN`, `hb-NNNNNN`, `d3dtex--`) tienen alcance de sesión. |
| Estabilidad | Transporte y contrato: `STABLE_BETA`; la estabilidad de cada operación sigue a `PublicCapabilityRegistry` (`PublicStableBeta` → `STABLE_BETA`, `PublicExperimental` → `EXPERIMENTAL`), con las excepciones indicadas arriba. |
| Ejemplo | `var r = await launch.ExecuteRuntimeAsync(session, new RuntimeCommand(session.SessionId, 42, "road-vehicle.read", new Dictionary { ["handle"] = "rv-000001" }), TimeSpan.FromSeconds(5)); if (!r.Succeeded) Console.WriteLine($"{r.ErrorCode} {r.Values?["detail"]}");` |
### `GetCapabilitiesAsync`
| Aspecto | Detalle |
| --- | --- |
| Firma | `Task> GetCapabilitiesAsync(InstallationSpec installation, CancellationToken cancellationToken = default)` |
| Propósito | Devolver el inventario de evidencias del producto para una instalación: una lista fija de entradas `Capability(Name, Available, EvidenceState, Reason)` mantenida en `OmsiLaunchService`. Solo `runtime.current-windows-x64` se calcula (a partir de la detección de la plataforma); todas las demás entradas son constantes. |
| Parámetros | `installation.RootPath`: directorio usado para la sonda de plataforma (para que se considere escribible, el directorio debe existir, no ser de solo lectura y contener `plugins\`). `ExpectedExecutableSha256` se ignora. |
| Devuelve | 51 entradas, por ejemplo `runtime.time.read` (`RUNTIME_VALIDATED`), `runtime.weather.write` (`false`, `RUNTIME_PARTIAL`), `world.last-map-state` (`false`, `UNSUPPORTED_FOR_CURRENT_PROFILE`), `world.date.explicit` (`false`, `STATICALLY_PARTIAL`), `content.maps` (`STATICALLY_VALIDATED`), `runtime.d3d.lifecycle.reset` (`IMPLEMENTED_NOT_RUNTIME_VALIDATED`). |
| Diferencia con `PublicCapabilityRegistry` | `PublicCapabilityRegistry.All` es el catálogo de la superficie de control en tiempo de compilación (36 descriptores con clasificación, tipo, rutas de API y de CLI y argumentos obligatorios) que la API y la CLI hacen cumplir; no depende de la instalación. `GetCapabilitiesAsync` es un informe de evidencia de runtime (estado de validación y motivos). Use el registro para decidir qué puede llamar; use esta lista para decidir qué se ha demostrado. Ninguna de las dos listas se deriva de la otra. |
| Excepciones | `OperationCanceledException` al entrar; `ArgumentException` para una ruta raíz vacía. |
| Cancelación | Se verifica una vez al entrar. |
| Requiere una sesión en ejecución | No. |
| Modifica OMSI / sistema de archivos / transacción | No / No / Ninguna. |
| Estabilidad | Contrato de la llamada: `STABLE_BETA`; el contenido de la lista es un inventario mantenido a mano: `PARTIAL`. |
| Ejemplo | `foreach (var c in await launch.GetCapabilitiesAsync(new InstallationSpec(root))) Console.WriteLine($"{c.Name} {c.Available} {c.EvidenceState} {c.Reason}");` |
### `DiscoverAsync`
| Aspecto | Detalle |
| --- | --- |
| Firma | `Task> DiscoverAsync(InstallationSpec installation, ContentQueryKind query, OptionalValue scope = default, CancellationToken cancellationToken = default)` |
| Propósito | Enumerar el contenido instalado y devolver identidades canónicas que se pueden usar en un `LaunchSpec`. El descubrimiento omite los puntos de reanálisis (reparse points), de modo que los ciclos de junctions no pueden bloquearlo; lee los archivos de OMSI como Windows-1252 (respeta UTF-8/UTF-16 marcados con BOM) y nunca sigue vínculos simbólicos. |
| Parámetros | `query` y `scope` según la tabla siguiente. `scope` es obligatorio para `Entrypoints` (identidad del mapa), `Repaints`, `FleetNumbers`, `Registrations` (identidad del vehículo). |
| Devuelve | Lista ordenada de `ContentIdentity(Identity, Kind, DisplayName)`. Las identidades son rutas relativas a la instalación con barras invertidas; las comparaciones no distinguen mayúsculas de minúsculas. |
| Excepciones | `OperationCanceledException` al entrar; `ArgumentException` cuando se consulta `Entrypoints` sin un alcance o la raíz está vacía; `FileNotFoundException` (sin código `OL_E_`; la CLI lo asigna a `OL_E_NOT_FOUND`) cuando el mapa o el vehículo del alcance no está instalado. Una raíz o un directorio de contenido faltantes producen una lista vacía, no un error. |
| Cancelación | Se verifica una vez al entrar. |
| Requiere una sesión en ejecución | No. |
| Modifica OMSI / sistema de archivos / transacción | No / No / Ninguna. |
| Estabilidad | `Maps`, `Situations`, `Vehicles`: `STABLE_BETA` (todos los planes validados en runtime se resuelven a través de ellos). `Entrypoints`, `Repaints`, `Hofs`, `FleetNumbers`, `Registrations`, `Addons`: `EXPERIMENTAL` (solo evidencia estática). |
| Ejemplo | `var maps = await launch.DiscoverAsync(new InstallationSpec(root), ContentQueryKind.Maps); var entries = await launch.DiscoverAsync(new InstallationSpec(root), ContentQueryKind.Entrypoints, OptionalValue.Set(maps[0].Identity));` |
Valores de `ContentQueryKind` y resultados:
| Valor | Alcance | `Identity` | `Kind` | `DisplayName` |
| --- | --- | --- | --- | --- |
| `Maps` | ninguno | `maps\\global.cfg` | `map` | nombre del directorio del mapa |
| `Situations` | ninguno | `situations\...\.osn` | `situation` | identidad del mapa referenciada por el `.osn` (puede ser `null`) |
| `Vehicles` | ninguno | `Vehicles\...\.bus` | `vehicle` | `[friendlyname]` o nombre del archivo |
| `Repaints` | identidad del vehículo (obligatoria; sin ella: lista vacía) | `#item:` | `repaint` | nombre de `[item]` |
| `Hofs` | ninguno | `Vehicles\...\.hof` | `hof` | `null` |
| `FleetNumbers` | identidad del vehículo (obligatoria; sin ella: lista vacía) | ruta de origen de `[number]` relativa al vehículo | `fleet-number` | `null` |
| `Registrations` | identidad del vehículo (obligatoria; sin ella: lista vacía) | `registration_automatic` / `registration_list` / `registration_free` | `registration` | primera línea de valor (`null` para free) |
| `Addons` | ninguno | `Addons\` | `addon` | `directory-only` |
| `Entrypoints` | identidad del mapa (obligatoria; sin ella: `ArgumentException`) | `