Referencia de la API pública (OmsiLaunch.Api)

Documentación de la versión v0.1.0-beta.3Ver fuente en GitHub

Traducción de la página original en inglés de OmsiLaunch 0.1.0-beta3. La página en inglés es la referencia normativa: si difieren, prevalecen la página en inglés y el código.

Esta página es la referencia normativa de la API pública gestionada 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 figura aquí con su nivel de estabilidad; lo que no aparece no es una superficie de integración.

El inventario de la API pública generado enumera cada tipo y miembro público de OmsiLaunch.Api, OmsiLaunch.Core y OmsiLaunch.Process con su firma y su estabilidad; un control de documentación falla cuando el inventario y los ensamblados difieren. Esta página explica la semántica.

Páginas relacionadas: referencia de LaunchSpec, códigos de error, ciclo de vida de la sesión, transacciones y recuperación, control de runtime, capacidades, plano de control local, códigos de salida, estado de la validación en runtime.

Vocabulario de estabilidad#

NivelSignificado en esta página
STABLE_BETAEl 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.
EXPERIMENTALSe puede llamar y está probado, pero el contrato o la evidencia de runtime pueden cambiar antes de que pase a ser estable.
PARTIALPresente en el contrato; solo una parte del comportamiento está implementada o validada (el texto indica qué parte).
INTERNALPúblico en el ensamblado por motivos técnicos (el puente comparte el tipo), pero no es una superficie de integración; puede cambiar sin previo aviso.
UNAVAILABLEPresente en el contrato, pero la compilación actual lo rechaza.

Visión general de los ensamblados#

EnsambladoFunción para los integradores
OmsiLaunch.ApiContratos puros: records, enums, IOmsiLaunch, registro de capacidades, catálogo de errores, formatos de transmisión, utilidades D3D. No contiene ningún IntPtr, nint, handle de Win32, dirección nativa ni objeto de proceso.
OmsiLaunch.CoreOmsiLaunchService (la implementación de IOmsiLaunch), OmsiLaunchRuntimePaths, SessionPlanner, LaunchValidation, SessionProfileCompiler.
OmsiLaunch.ProcessIRuntimePlatform y CurrentWindowsX64Platform (el único adaptador de plataforma), InstallationLease. Necesarios para construir el servicio.
OmsiLaunch.Configuration, OmsiLaunch.Content, OmsiLaunch.Interop, OmsiLaunch.Plugin, OmsiLaunch.Builds.Omsi23004Ensamblados de implementación. Sus tipos públicos son INTERNAL para los integradores.

Punto de entrada: OmsiLaunchService y OmsiLaunchRuntimePaths#

public sealed record OmsiLaunchRuntimePaths(string PluginBuildDirectory, string NativeBridgePath, string? ReleaseManifestPath = null);
public sealed class OmsiLaunchService : IOmsiLaunch
{
    public OmsiLaunchService(IRuntimePlatform platform, OmsiLaunchRuntimePaths runtimePaths);
}
ParámetroValor válidoNo válido / predeterminado
platformnew CurrentWindowsX64Platform() (espacio de nombres OmsiLaunch.Process). Detecta la plataforma, crea el proceso de OMSI con CreateProcessW, espera a que termine y lo termina.No se distribuye ninguna otra implementación. Un IRuntimePlatform personalizado es INTERNAL.
PluginBuildDirectoryDirectorio que contiene los archivos de referencia del conjunto de archivos del plugin permanente: OmsiLaunch.Plugin.opl, OmsiLaunch.PluginNE.dll, OmsiLaunch.Plugin.deps.json, OmsiLaunch.Plugin.runtimeconfig.json y cada OmsiLaunch.*.dll del conjunto gestionado (debe incluir OmsiLaunch.Plugin.dll). En un paquete instalado es <package>\plugins.Directorio o archivo ausente: PlanSessionAsync devuelve un plan no ejecutable con OL_E_RUNTIME_ARTIFACT_MISSING.
NativeBridgePathRuta de OmsiLaunch.Native.x86.dll (en el paquete: <package>\plugins\OmsiLaunch.Native.x86.dll).Igual que en la fila anterior.
ReleaseManifestPathrelease-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 comprueba su presencia y su coherencia interna con el conjunto 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 (consulta plugin permanente). La CLI construye el servicio exactamente así (tools/OmsiLaunch.Cli/Program.cs):

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));

Crea un único servicio por proceso y compártelo. Estabilidad: STABLE_BETA.

Reglas de propiedad de la sesión#

ReglaDetalle
Un propietario por instalaciónStartSessionAsync adquiere el lease de la instalación, un semáforo con nombre Local\OmsiLaunch.Installation.<SHA-256 of the upper-cased full root path>, y lo mantiene hasta que el supervisor ha restaurado la instalación. Un segundo inicio sobre la misma raíz desde cualquier proceso de la misma sesión de inicio de Windows falla con OL_E_INSTALLATION_BUSY (notificado como una sesión Failed, consulta StartSessionAsync). El lease es por sesión de inicio de Windows, no abarca varias sesiones de inicio, y no se libera mientras otro proceso mantenga un handle hacia él (riesgo aceptado).
Los handles son locales al procesoSessionHandle envuelve 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, no por handles.
Llama siempre a CloseAsyncDesde StartSessionAsync en adelante, el proceso es propietario de una transacción duradera. CloseAsync solicita la detención canónica cuando es necesario, 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 ella, la entrada de la sesión permanece en memoria; la restauración en sí la realiza el supervisor en cualquier caso.
Las sesiones fallidas siguen siendo sesionesUn inicio que falla después de que StartSessionAsync haya retornado notifica SessionState.Failed; el handle sigue siendo válido para GetStatusAsync/WaitForAsync hasta CloseAsync.
Los planes se vuelven a comprobarStartSessionAsync 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#

public interface IOmsiLaunch
{
    Task<SessionPlan> PlanSessionAsync(LaunchSpec spec, CancellationToken cancellationToken = default);
    Task<SessionHandle> StartSessionAsync(SessionPlan plan, CancellationToken cancellationToken = default);
    Task<SessionStatus> GetStatusAsync(SessionHandle session, CancellationToken cancellationToken = default);
    Task<SessionStatus> 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<RuntimeCommandResult> ExecuteRuntimeAsync(SessionHandle session, RuntimeCommand command, TimeSpan timeout, CancellationToken cancellationToken = default);
    Task<IReadOnlyList<Capability>> GetCapabilitiesAsync(InstallationSpec installation, CancellationToken cancellationToken = default);
    Task<IReadOnlyList<ContentIdentity>> DiscoverAsync(InstallationSpec installation, ContentQueryKind query, OptionalValue<string> scope = default, CancellationToken cancellationToken = default);
    Task<RecoveryStatus> 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 ejecución (Running), salvo ExecuteRuntimeAsync.
  • Las excepciones que llevan un código de OmsiLaunch colocan el código al principio de Exception.Message ("OL_E_PLAN_NOT_RUNNABLE: ..."). La CLI extrae los códigos de los mensajes de la misma manera (CliProgram.Classify).
  • Build compatible: solo Omsi23004_692EBFBF (más el hash de la lista de permitidos de Steam LAA, aceptado; la fase de juego no está validada, requiere una instalación de Steam auténtica). Consulta compatibilidad.

Ejemplo mínimo completo#

var none = new Dictionary<string, OptionalValue<string>>();
var spec = new LaunchSpec(
    Installation: new InstallationSpec(@"C:\OMSI 2"),
    World: new WorldSpec(WorldMode.NewMap, OptionalValue<string>.Set(@"maps\Grundorf\global.cfg"), OptionalValue<string>.Unset, OptionalValue<int>.Set(1)),
    Date: new DateSpec(DateTimeMode.Unset, OptionalValue<SemanticDate>.Unset),
    Time: new TimeSpec(DateTimeMode.Unset, OptionalValue<SemanticTime>.Unset),
    PlayerVehicle: OptionalValue<PlayerVehicleSpec>.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#

AspectoDetalle
FirmaTask<SessionPlan> PlanSessionAsync(LaunchSpec spec, CancellationToken cancellationToken = default)
FinalidadCompilar 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 no compatibles, y decidir IsRunnable. Capacidad pública session.plan.
Parámetrosspec: un LaunchSpec completamente rellenado (consulta la referencia de LaunchSpec). Installation, World, Date, Time, Environment (los ocho diccionarios) y Behavior no deben ser null; los miembros opcionales pueden ser null. RootPath debería 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, así que nunca pases una raíz vacía.
DevuelveSessionPlan con un SessionId nuevo, BuildProfileId = "Omsi23004_692EBFBF" (siempre esta constante, incluso cuando el 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 empieza por OL_E_. Los diagnósticos informativos (plugin.integrity.reference con el mensaje self o manifest, session_profile.selected) nunca hacen que un plan deje de ser ejecutable.
Errores incluidos en el resultadoTodo 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 conjunto de archivos del plugin instalado en plugins\ se verifica contra el manifiesto de la versión 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.
LanzaOperationCanceledException si el token ya está cancelado a la entrada (el único punto de comprobació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 la versión sintácticamente no válido.
CancelaciónSe comprueba una vez a la entrada. Después, la planificación es trabajo síncrono sobre el sistema de archivos.
Requiere una sesión en ejecuciónNo.
Modifica el estado de OMSINo.
Modifica el sistema de archivosNo (lee Omsi.exe, archivos de contenido, el conjunto de archivos del plugin y el manifiesto). Aquí no se validan los valores de los ajustes (solo la existencia de la clave y si se puede escribir); un valor no válido falla en el inicio con OL_E_INVALID_SETTING_VALUE.
Transacción / restauraciónNinguna.
LimitacionesSolicitar 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 esta build (entradas STATICALLY_PARTIAL / UNSUPPORTED_FOR_CURRENT_PROFILE en UnsupportedRequestedFeatures).
EstabilidadSTABLE_BETA.
Ejemplovar 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#

AspectoDetalle
FirmaTask<SessionHandle> StartSessionAsync(SessionPlan plan, CancellationToken cancellationToken = default)
FinalidadIniciar una sesión gestionada y transaccional de OMSI a partir de un plan ejecutable: adquirir el lease de la instalación, recuperar un diario obsoleto, validar el conjunto de archivos del plugin permanente, tomar el snapshot de los archivos de sesión y aplicar sus overlays, crear el handoff de arranque, el slot de telemetría y el buzón de runtime, iniciar Omsi.exe, registrar el proceso en el diario y entregar la sesión a un supervisor en segundo plano. Capacidad pública session.start.
Parámetrosplan: 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 entre 1..600.
DevuelveSessionHandle(plan.SessionId) en cuanto Omsi.exe se ha creado y registrado (estado WaitingForPlugin), o en cuanto la ruta de inicio ha fallado (estado Failed). No espera a la fase de juego; usa WaitForAsync(session, SessionState.Running, ...).
LanzaInvalidOperationException("OL_E_PLAN_NOT_RUNNABLE") cuando plan.IsRunnable es false; InvalidOperationException("OL_E_PLAN_NOT_RUNNABLE: <codes>") cuando el nuevo plan no es ejecutable (por ejemplo, Omsi.exe ha cambiado, se ha eliminado contenido o falta el conjunto de archivos del plugin); InvalidOperationException("Duplicate session id.") cuando todavía está registrada una sesión con el mismo id (llama antes a CloseAsync); ArgumentOutOfRangeException cuando StartupTimeoutSeconds está fuera de 1..600; OperationCanceledException cuando se cancela antes o durante la nueva planificación; y todo lo que lanza PlanSessionAsync. En todos los casos en que se lanza una excepción no se registra ninguna sesión.
Errores incluidos en el resultadoCualquier 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 empieza por el código interno cuando lo hay): OL_E_INSTALLATION_BUSY (lease ocupado o un proceso de OMSI registrado en el diario 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 añadir OL_E_PROCESS_CLEANUP_FAILED, OL_E_RESTORE_DEFERRED (salida de OMSI no confirmada; el diario se conserva) u OL_E_RESTORE_FAILED. Los fallos posteriores los notifica el supervisor (consulta ciclo de vida de la sesión).
CancelaciónAntes 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 ha creado) se termina y la instalación se restaura.
Requiere una sesión en ejecuciónNo.
Modifica el estado de OMSISí: 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 archivosSí, dentro de la raíz de la instalación: .omsilaunch\diagnostics\<sessionId>-host.log (retención: las 50 sesiones más recientes), .omsilaunch\journal.json, .omsilaunch\backup\<sessionId>\*.bin, .omsilaunch\assets\splash\*.bmp (se copia una vez para el splash gestionado), 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 eliminación permanente de un closecheck obsoleto preexistente cuando SuppressStaleClosecheckWarning es true (diagnóstico closecheck.stale-removed).
Transacción / restauraciónAbre la transacción (Prepared → Applied → RuntimeDeployed → HandoffCreated → ProcessStarted). Todas las rutas de salida de la sesión terminan en una restauración. Consulta transacciones y recuperación.
LimitacionesSolo WorldMode.NewMap con PresentedEntrypointIndex y WorldMode.SavedSituation llegan a la fase de juego. WorldMode.LastMapState es UNAVAILABLE. Las solicitudes de fecha/hora/meteorología/vehículo del jugador/entrada nunca llegan a este método, porque no son ejecutables en el momento de la planificación.
EstabilidadSTABLE_BETA (los ciclos de vida NEW_MAP y SAVED_SITUATION están validados en runtime).
Ejemplovar 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#

AspectoDetalle
FirmaTask<SessionStatus> GetStatusAsync(SessionHandle session, CancellationToken cancellationToken = default)
FinalidadLeer el estado semántico del ciclo de vida, los diagnósticos recogidos hasta el momento y la lista acotada de eventos de runtime. Capacidad pública session.status.
Parámetrossession: un handle devuelto por StartSessionAsync y todavía no cerrado.
DevuelveSessionStatus(SessionId, State, Diagnostics, RuntimeEvents): un snapshot inmutable (las matrices se copian bajo el bloqueo de la sesión). RuntimeEvents nunca es null para una sesión activa.
LanzaKeyNotFoundException para handles desconocidos o cerrados. En los demás casos, nunca lanza excepciones.
CancelaciónEl token se ignora (la llamada se completa de forma síncrona).
Requiere una sesión en ejecuciónNo.
Modifica OMSI / sistema de archivos / transacciónNo / No / Ninguna.
EstabilidadSTABLE_BETA.
Ejemplovar status = await launch.GetStatusAsync(session); Console.WriteLine($"{status.State} events={status.RuntimeEvents!.Count}");

WaitForAsync#

AspectoDetalle
FirmaTask<SessionStatus> WaitForAsync(SessionHandle session, SessionState state, TimeSpan timeout, CancellationToken cancellationToken = default)
FinalidadSondear (cada 100 ms) hasta que la sesión esté en state o en un estado terminal (Completed, Failed), o hasta que venza el timeout; después, devolver el estado actual.
Parámetrosstate: cualquier SessionState. Esperar un estado transitorio que ya se ha superado (o que nunca se establece, consulta ciclo de vida de la sesión) espera hasta un estado terminal o hasta el timeout. timeout: cualquier TimeSpan no negativo o Timeout.InfiniteTimeSpan.
DevuelveEl estado en el momento en que terminó la espera. Si vence el timeout, se devuelve el estado, no una excepción: comprueba State tú mismo. Una espera de Running que termina en Failed retorna inmediatamente con los diagnósticos del fallo.
LanzaKeyNotFoundException; OperationCanceledException cuando se cancela el token del llamador (solo se propaga la cancelación del llamador; el timeout interno no).
CancelaciónEl token del llamador se respeta en cada intervalo de 100 ms.
Requiere una sesión en ejecuciónNo.
Modifica OMSI / sistema de archivos / transacciónNo / No / Ninguna.
EstabilidadSTABLE_BETA.
Ejemplovar running = await launch.WaitForAsync(session, SessionState.Running, TimeSpan.FromSeconds(185)); if (running.State != SessionState.Running) { /* timed out or Failed */ }

StopAsync#

AspectoDetalle
FirmaTask StopAsync(SessionHandle session, CancellationToken cancellationToken = default)
FinalidadSolicitar la detención canónica. Establece el flag de detención y retorna inmediatamente; el supervisor detecta el flag dentro de su bucle de 100 ms, llama a TerminateProcess sobre Omsi.exe, espera a la salida, marca el diario como ProcessExited, restaura todos los archivos propiedad de la sesión y libera el lease. Se trata de una terminació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 con WM_CLOSE no está implementado (decisión de producto; en el cierre de validación en runtime, OMSI no se cerró en los 30 s siguientes a WM_CLOSE, L05b). Capacidad pública session.stop.
Parámetrossession.
DevuelveUna tarea completada; no espera a la terminación ni a la restauración. Usa WaitForAsync(session, SessionState.Completed, ...) para observar la finalización.
LanzaKeyNotFoundException.
CancelaciónEl token se ignora.
Requiere una sesión en ejecuciónNo. Idempotente; una detención solicitada antes de que arranque el supervisor se atiende en cuanto arranca; una detención sobre una sesión terminal no hace nada.
Modifica el estado de OMSISí: termina el proceso de OMSI (código de salida 1).
Modifica el sistema de archivosIndirectamente: desencadena la restauración, la eliminación del diario y la eliminación de las copias de seguridad por parte del supervisor.
Transacción / restauraciónDesencadena 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.
EstabilidadSTABLE_BETA.
Ejemploawait launch.StopAsync(session); var done = await launch.WaitForAsync(session, SessionState.Completed, TimeSpan.FromMinutes(1));

CloseAsync#

AspectoDetalle
FirmaTask CloseAsync(SessionHandle session, CancellationToken cancellationToken = default)
FinalidadLiberar el handle del consumidor sin dejar abandonada la transacción: si la sesión no es terminal, solicitar la detención canónica; después, esperar la tarea del ciclo de vida del supervisor (salida del proceso, restauración, liberación del lease); por último, olvidar la sesión.
Parámetrossession.
DevuelveSe completa cuando la sesión es terminal y se ha eliminado. Una vez que retorna, el handle es desconocido (KeyNotFoundException en cualquier llamada posterior, incluida una segunda CloseAsync).
LanzaKeyNotFoundException; OperationCanceledException si el llamador cancela mientras se espera al supervisor. En ese caso la sesión no se elimina y el supervisor sigue en ejecución; vuelve a llamar a CloseAsync.
CancelaciónSe aplica solo a la espera; nunca cancela la restauración.
Requiere una sesión en ejecuciónNo.
Modifica el estado de OMSISí, cuando la sesión sigue activa (igual que StopAsync).
Modifica el sistema de archivosIndirectamente (restauración por parte del supervisor).
Transacción / restauraciónGarantiza que la transacción se lleva hasta su finalización antes de liberar el handle (cuando se llegó a iniciar el supervisor). Para una sesión que falló antes de que arrancara el supervisor, la ruta de inicio ya ha restaurado o ha notificado OL_E_RESTORE_DEFERRED.
EstabilidadSTABLE_BETA.
Ejemplotry { ... } finally { await launch.CloseAsync(session); }

ExecuteRuntimeAsync#

AspectoDetalle
FirmaTask<RuntimeCommandResult> ExecuteRuntimeAsync(SessionHandle session, RuntimeCommand command, TimeSpan timeout, CancellationToken cancellationToken = default)
FinalidadEjecutar una operación de runtime pública dentro del proceso de OMSI en ejecución a través del buzón de la sesión, de una sola solicitud en curso (asignado 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 UI de OMSI. Catálogo de operaciones: control de runtime y capacidades.
Parámetroscommand.SessionId debe ser igual a session.SessionId. command.RequestId: ulong elegido por el llamador; usa un contador estrictamente creciente por proceso (las utilidades D3D empiezan 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 se obtienen de PublicCapabilityRegistry.GetRuntimeArguments. timeout: se mide desde el momento en que la solicitud se deposita en el buzón (no cuenta la espera en cola detrás de otro comando en curso). La CLI usa 5 s (15 s para road-vehicles.spawn) como propietario y 8 s / 30 s como cliente.
Orden de las comprobaciones1. Validación del registro (antes de buscar la sesión): operación desconocida o internal.* → resultado Succeeded=false, ErrorCode=OL_E_RUNTIME_OPERATION_UNKNOWN; falta un argumento obligatorio (ausente o solo 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 empieza por internal_ o termina en _address, _pointer, _vmt.
DevuelveRuntimeCommandResult(SessionId, RequestId, Succeeded, ErrorCode, Values). Si tiene éxito, Values contiene las cadenas semánticas de la operación (documentadas por operación en control de runtime).
Errores incluidos en el resultadoOL_E_RUNTIME_OPERATION_UNKNOWN, OL_E_RUNTIME_ARGUMENT_REQUIRED (registro); OL_E_RUNTIME_RESPONSE_TOO_LARGE (el resultado del plugin superó el buzón; los resultados de listas acotadas se acortan con truncated=true en su lugar); OL_E_RUNTIME_SETTING_NOT_PERSISTENT (weather.set, siempre); todos los códigos 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 concreto 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 (comprobaciones 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. Consulta códigos de error.
LanzaKeyNotFoundException; InvalidOperationException con OL_E_RUNTIME_SESSION_MISMATCH, OL_E_SESSION_NOT_RUNNING, OL_E_RUNTIME_CHANNEL_CLOSED (buzón ya liberado por el supervisor), OL_E_RUNTIME_CHANNEL_BUSY (el slot aún contiene una solicitud abandonada), OL_E_RUNTIME_REQUEST_ID_REUSED (en el slot sigue habiendo una respuesta obsoleta para el mismo id de solicitud); TimeoutException("OL_E_RUNTIME_REQUEST_TIMEOUT"); InvalidDataException("OL_E_RUNTIME_RESPONSE_INVALID") (respuesta corrupta, ajena o que no coincide); ArgumentOutOfRangeException cuando la solicitud serializada supera el buzón; OperationCanceledException.
CancelaciónSe respeta mientras se espera la barrera por sesión y cada 20 ms mientras se sondea 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 el timeout; un timeout restablece el slot y una respuesta tardía se detecta y se descarta.
Requiere una sesión en ejecuciónSí (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 OMSIDepende de la operación: las operaciones Read no; 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 archivosEl host no escribe nada. OMSI puede escribir sus propios archivos como consecuencia (no se rastrean).
Transacción / restauraciónNinguna.
LimitacionesUn 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 está acotada y puede devolver OL_E_RUNTIME_RESPONSE_TOO_LARGE con horarios grandes. camera.lock es EXPERIMENTAL; necesita un vehículo del jugador y está validada en runtime (CAM01), aunque la cadena RuntimeValidation del registro sigue indicando STATICALLY_VALIDATED. Los handles (rv-NNNNNN, hb-NNNNNN, d3dtex-<session>-<hex>) tienen ámbito de sesión.
EstabilidadTransporte y contrato STABLE_BETA; la estabilidad de cada operación sigue PublicCapabilityRegistry (PublicStableBeta → STABLE_BETA, PublicExperimental → EXPERIMENTAL), con las excepciones indicadas arriba.
Ejemplovar r = await launch.ExecuteRuntimeAsync(session, new RuntimeCommand(session.SessionId, 42, "road-vehicle.read", new Dictionary<string, string> { ["handle"] = "rv-000001" }), TimeSpan.FromSeconds(5)); if (!r.Succeeded) Console.WriteLine($"{r.ErrorCode} {r.Values?["detail"]}");

GetCapabilitiesAsync#

AspectoDetalle
FirmaTask<IReadOnlyList<Capability>> GetCapabilitiesAsync(InstallationSpec installation, CancellationToken cancellationToken = default)
FinalidadDevolver 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ámetrosinstallation.RootPath: directorio usado para la sonda de plataforma (para que se pueda escribir, el directorio debe existir, no ser de solo lectura y contener plugins\). ExpectedExecutableSha256 se ignora.
Devuelve51 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 PublicCapabilityRegistryPublicCapabilityRegistry.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 aplican; no depende de la instalación. GetCapabilitiesAsync es un informe de evidencia de runtime (estado de validación y motivos). Usa el registro para decidir qué puedes llamar; usa esta lista para decidir qué se ha demostrado. Ninguna de las dos listas se deriva de la otra.
LanzaOperationCanceledException a la entrada; ArgumentException para una ruta raíz vacía.
CancelaciónSe comprueba una vez a la entrada.
Requiere una sesión en ejecuciónNo.
Modifica OMSI / sistema de archivos / transacciónNo / No / Ninguna.
EstabilidadContrato de la llamada STABLE_BETA; el contenido de la lista es un inventario mantenido a mano: PARTIAL.
Ejemploforeach (var c in await launch.GetCapabilitiesAsync(new InstallationSpec(root))) Console.WriteLine($"{c.Name} {c.Available} {c.EvidenceState} {c.Reason}");

DiscoverAsync#

AspectoDetalle
FirmaTask<IReadOnlyList<ContentIdentity>> DiscoverAsync(InstallationSpec installation, ContentQueryKind query, OptionalValue<string> scope = default, CancellationToken cancellationToken = default)
FinalidadEnumerar el contenido instalado y devolver identidades canónicas que se pueden usar en un LaunchSpec. El descubrimiento omite los puntos de reanálisis (los ciclos de uniones no pueden bloquearlo), lee los archivos de OMSI como Windows-1252 (se respetan UTF-8/UTF-16 marcados con BOM) y nunca sigue vínculos simbólicos.
Parámetrosquery y scope según la tabla siguiente. scope es obligatorio para Entrypoints (identidad del mapa), Repaints, FleetNumbers, Registrations (identidad del vehículo).
DevuelveLista 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.
LanzaOperationCanceledException a la entrada; ArgumentException cuando se consulta Entrypoints sin ámbito 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 ámbito no está instalado. Una raíz o un directorio de contenido inexistente produce una lista vacía, no un error.
CancelaciónSe comprueba una vez a la entrada.
Requiere una sesión en ejecuciónNo.
Modifica OMSI / sistema de archivos / transacciónNo / No / Ninguna.
EstabilidadMaps, Situations, Vehicles: STABLE_BETA (todo plan validado en runtime se resuelve a través de ellos). Entrypoints, Repaints, Hofs, FleetNumbers, Registrations, Addons: EXPERIMENTAL (solo evidencia estática).
Ejemplovar maps = await launch.DiscoverAsync(new InstallationSpec(root), ContentQueryKind.Maps); var entries = await launch.DiscoverAsync(new InstallationSpec(root), ContentQueryKind.Entrypoints, OptionalValue<string>.Set(maps[0].Identity));

Valores de ContentQueryKind y resultados:

ValorÁmbitoIdentityKindDisplayName
Mapsningunomaps\<dir>\global.cfgmapnombre del directorio del mapa
Situationsningunosituations\...\<file>.osnsituationidentidad del mapa al que hace referencia el .osn (puede ser null)
VehiclesningunoVehicles\...\<file>.busvehicle[friendlyname] o nombre del archivo
Repaintsidentidad del vehículo (obligatoria; sin ella: lista vacía)<cti path>#item:<ordinal>repaintnombre de [item]
HofsningunoVehicles\...\<file>.hofhofnull
FleetNumbersidentidad del vehículo (obligatoria; sin ella: lista vacía)ruta de origen de [number] relativa al vehículofleet-numbernull
Registrationsidentidad del vehículo (obligatoria; sin ella: lista vacía)registration_automatic / registration_list / registration_freeregistrationprimera línea de valor (null para free)
AddonsningunoAddons\<dir>addondirectory-only
Entrypointsidentidad del mapa (obligatoria; sin ella: ArgumentException)<map identity>#entrypoint:<SHA-256 of the 12-line record>entrypointetiqueta del punto de entrada

Las identidades de punto de entrada sirven solo para el descubrimiento: la ruta de lanzamiento usa PresentedEntrypointIndex; pasar un EntrypointIdentity hace que el plan no sea ejecutable en esta build (world.entrypoint-identity, RUNTIME_PARTIAL).

RecoverPendingAsync#

AspectoDetalle
FirmaTask<RecoveryStatus> RecoverPendingAsync(InstallationSpec installation, bool restore, CancellationToken cancellationToken = default)
FinalidadNotificar o completar una transacción duradera obsoleta (<root>\.omsilaunch\journal.json) que ha dejado un propietario que se bloqueó. Toma el lease de la instalación mientras dura la llamada, de modo que nunca restaura por debajo de una sesión que se está iniciando. Capacidad pública session.recover; CLI /recovery-status y /recover.
Parámetrosinstallation.RootPath: la raíz de la instalación (normalizada con Path.GetFullPath). restore: false = solo notificar; true = restaurar, verificar y eliminar el diario y las copias de seguridad.
DevuelveRecoveryStatus(Pending, Recovered, Diagnostics): Pending = existía un diario cuando empezó la llamada; Recovered = se solicitó una restauración, se ejecutó y no queda ningún diario; Diagnostics = notas de la restauración (restore.session-artifact-removed con Data["sha256"], OL_W_RESTORE_FOREIGN_FILE_RETAINED), vacío cuando no se ha restaurado nada.
LanzaInvalidOperationException("OL_E_INSTALLATION_BUSY: another OmsiLaunch owner holds this installation.") cuando el lease está ocupado; IOException("OL_E_INSTALLATION_BUSY: a journaled OMSI process is still alive.") cuando el PID + la hora de creación + la ruta del ejecutable del diario siguen coincidiendo con un proceso vivo o (con el diario más allá de HandoffCreated y sin PID) hay en ejecución cualquier Omsi.exe de esa raíz; IOException con OL_E_RECOVERY_BACKUP_CORRUPT, OL_E_RECOVERY_ABSENT_OWNERSHIP_MISMATCH, OL_E_RECOVERY_ABSENT_OWNERSHIP_UNVERIFIED, OL_E_RECOVERY_JOURNAL_REMOVE_FAILED o un mensaje de verificación («Restore hash mismatch: ...», «Restore presence mismatch: ...»); InvalidDataException("Invalid OmsiLaunch journal.") / JsonException para un diario corrupto; ArgumentException para una raíz vacía; OperationCanceledException. Siempre que lanza una excepción después de iniciada una restauración, el diario se conserva y la siguiente llamada la repite de forma idempotente.
CancelaciónSe pasa a las escrituras del diario y de las copias de seguridad; cancelar a mitad de la restauración deja el diario pendiente.
Requiere una sesión en ejecuciónNo (se niega mientras haya un propietario activo).
Modifica el estado de OMSINo.
Modifica el sistema de archivosSolo con restore == true: reescribe los originales a partir de copias de seguridad verificadas (bytes, hora de última escritura, hora de creación, atributos; se gestionan los originales de solo lectura; escritura directa + vaciado; no queda ningún *.omsilaunch.tmp), elimina los artefactos de la sesión y elimina journal.json y backup\<sessionId>.
Transacción / restauraciónCompleta la transacción pendiente (Restoring → Restored → diario eliminado).
EstabilidadSTABLE_BETA: ruta de notificación y recuperación tras una salida prematura (matriz RV-008), recuperación tras una restauración fallida (cierre de validación en runtime F01), rechazo con un propietario activo y en la ventana previa al PID (S04) y recuperación diferida previa a la huella durante el inicio (S05); consulta estado de la validación en runtime.
Ejemplovar r = await launch.RecoverPendingAsync(new InstallationSpec(root), restore: true); Console.WriteLine($"pending={r.Pending} recovered={r.Recovered}");

Tipos del contrato#

Valores opcionales y primitivas semánticas#

TipoDefiniciónNotas
OptionalValue<T>readonly record struct OptionalValue<T>(Presence Presence, T? Value); IsSet, Unset estático, Set(T) estáticoDistingue «no solicitado» de «solicitado con un valor». La forma JSON se documenta en la referencia de LaunchSpec.
PresenceUnset = 0, Set = 1Enum de tipo byte.
SemanticDate(int Year, int Month, int Day)Solo se valida con DateTimeMode.Explicit (mes 1..12, día 1..31).
SemanticTime(int Hour, int Minute, int Second)Solo se valida con DateTimeMode.Explicit (0..23, 0..59, 0..59).

Familia LaunchSpec#

Todos los records siguientes se documentan propiedad por propiedad en la referencia de LaunchSpec; esta tabla fija el inventario de tipos.

TipoFinalidadEstabilidad
LaunchSpecRecord raíz de la solicitud, con los descriptores de acceso EffectiveYear, EffectiveWeather, EffectiveInput, EffectiveDiagnostics, EffectivePresentation, EffectiveInternetTextures, que sustituyen los miembros opcionales null por los valores predeterminados.STABLE_BETA
InstallationSpecRootPath, ExpectedExecutableSha256 (se transporta, no se consume).STABLE_BETA / PARTIAL
WorldSpec, WorldMode, EntrypointSpec, EntrypointModeSelección del mundo. WorldMode: NewMap = 0, SavedSituation = 1, LastMapState = 2, LastSituation = 2 (alias obsoleto de LastMapState; nunca significa «el .osn más reciente»). EntrypointMode: Unset, PresentedIndex, Identity (se calcula a partir de WorldSpec.Entrypoint).NewMap, SavedSituation: STABLE_BETA; LastMapState: UNAVAILABLE; EntrypointMode.Identity: PARTIAL
DateSpec, TimeSpec, YearSpec, DateTimeModeDateTimeMode: Unset, Explicit, System. Cualquier modo distinto de Unset hace que el plan no sea ejecutable.PARTIAL (STATICALLY_PARTIAL)
WeatherSpec, WeatherModeWeatherMode: Unset, Preset, Icao, RealCurrent. Cualquier modo distinto de Unset hace que el plan no sea ejecutable.PARTIAL
PlayerVehicleSpecModel, Repaint, Hof, FleetNumber, Registration, Enabled. Cualquier campo establecido hace que el plan no sea ejecutable.PARTIAL
EnvironmentSpecOcho grupos IReadOnlyDictionary<string, OptionalValue<string>> de ajustes semánticos de options.cfg.STABLE_BETA
InputSpecKeyboardDocument, ControllerDocument; cualquier valor establecido hace que el plan no sea ejecutable.PARTIAL
DiagnosticsSpecSeis booleanos; se transportan, no se consumen.PARTIAL
SessionPresentationSpec, SplashModeSplashMode: Unset = 0, Native = 0 (alias), Managed = 1.STABLE_BETA
InternetTexturesSpec, InternetTexturesModeInternetTexturesMode: Native, Disabled, Override.STABLE_BETA (Native), EXPERIMENTAL (Disabled, Override)
SessionProfileMetadataProcedencia de un perfil de sesión compilado (Id, Name, Version, Author, PresetId, PresetIndex, PresetName, PackagePath).STABLE_BETA
LaunchBehaviorSpecRestoreConfiguration (se transporta; la restauración se hace siempre), SuppressStaleClosecheckWarning, StartupTimeoutSeconds (1..600, predeterminado 180), ShutdownTimeoutSeconds (se transporta, no se consume).STABLE_BETA / PARTIAL

Tipos de plan y de estado#

TipoCamposNotas
SessionPlanSessionId (un Guid nuevo por plan), BuildProfileId ("Omsi23004_692EBFBF"), Spec, Platform (RuntimePlatformInfo), ResolvedContent (lista de ContentIdentity: map, vehicle, repaint, hof, situation, situation-map), TouchedFiles (rutas relativas distintas de PlannedMutations), RuntimeArtifacts, RequiredCapabilities (Capability con STATICALLY_VALIDATED o UNAVAILABLE), UnsupportedRequestedFeatures (entradas Capability para funciones solicitadas pero no compatibles), PlannedMutations, Diagnostics, IsRunnable.Un record público: se puede editar o quedar obsoleto, por eso StartSessionAsync vuelve a planificar.
RuntimePlatformInfoOsFamily, OsVersion, OsArchitecture, HostArchitecture, OmsiArchitecture (X86), PluginArchitecture (X86), CurrentPlatformSupported (Windows 10+, SO x64 y host x64), LegacyPlatform (siempre false), Wow64Available, InstallationWritable, ProcessLaunchSupported, PluginRuntimeSupported, NativeInteropSupported, SharedMemorySupported, ExactRestoreSupported (todos iguales a CurrentPlatformSupported).
CapabilityName, Available, EvidenceState, Reason.Las cadenas de evidencia son texto libre (RUNTIME_VALIDATED, STATICALLY_VALIDATED, STATICALLY_PARTIAL, RUNTIME_PARTIAL, UNAVAILABLE, UNSUPPORTED_FOR_CURRENT_PROFILE, IMPLEMENTED_NOT_RUNTIME_VALIDATED, RELEASE_IF_CLOSED).
PlannedMutationRelativePath, SemanticKey, RequestedValue, Operation (token-patch, vector-component-patch, exact-file-overlay).Las mutaciones de presentación usan las claves session-presentation.splash, internet-textures.override, internet-textures.cache, internet-textures.target.
LaunchDiagnosticCode, Message, Data (mapa de cadenas opcional).Los códigos que empiezan por OL_E_ son errores, los OL_W_ son advertencias y todo lo demás es informativo.
SessionStatusSessionId, State (SessionState), Diagnostics, RuntimeEvents.Los diagnósticos de la sesión no incluyen los diagnósticos del plan.
RuntimeEventType, TimestampUtc (hora de recepción en el host), Sequence (base 1, por sesión), Data.Acotado a los 256 eventos más recientes (se descartan los más antiguos). El slot de telemetría es de último valor: los eventos emitidos más rápido que el sondeo del host de 100 ms pueden perderse. No es un registro sin pérdidas.
SessionHandleSessionId.Local al proceso.
RecoveryStatusPending, Recovered, Diagnostics.Consulta RecoverPendingAsync.
ContentIdentityIdentity, Kind, DisplayName.Consulta DiscoverAsync.
ContentQueryKindMaps, Situations, Vehicles, Repaints, Hofs, FleetNumbers, Registrations, Addons, Entrypoints.

SessionState#

Enum de tipo byte, en orden de declaración: Created, ValidatingPlatform, Planning, AcquiringInstallationLock, RecoveringPreviousTransaction, Snapshotting, ApplyingConfiguration, DeployingRuntime, CreatingStartupHandoff, StartingProcess, WaitingForPlugin, PluginBootstrap, StartingWorld, EnteringGameplay, Running, ProcessExited, Restoring, CleaningRuntime, Completed, Failed. El servicio actual nunca establece ValidatingPlatform, Planning ni EnteringGameplay; Snapshotting es transitorio y prácticamente inobservable. Estados terminales: Completed, Failed. Semántica completa: ciclo de vida de la sesión.

Tipos de control de runtime#

TipoDefiniciónEstabilidad
RuntimeCommand(Guid SessionId, ulong RequestId, string Operation, IReadOnlyDictionary<string, string>? Arguments)STABLE_BETA
RuntimeCommandResult(Guid SessionId, ulong RequestId, bool Succeeded, string? ErrorCode, IReadOnlyDictionary<string, string>? Values)STABLE_BETA
RuntimeCommandWireCódec estático que usan el host y el plugin para el envelope del buzón: número mágico 0x4F4C5243 («OLRC»), versión 1, cabecera little-endian de 72 bytes (número mágico, versión, tipo 1 = solicitud / 2 = respuesta, longitud total, Guid de la sesión, id de solicitud, longitud de la carga útil, SHA-256 de la carga útil) seguida de una carga útil JSON en UTF-8. SerializeRequest, SerializeResponse, TryDeserializeRequest, TryDeserializeResponse, TryReadRequestId.INTERNAL: público porque lo comparten ambos extremos del puente; no es una superficie de integración; el formato puede cambiar con la versión del protocolo.
StartupHandoff(Guid SessionId, string BuildProfileId, WorldMode WorldMode, string MapIdentity, int PresentedEntrypointIndex, bool HeadlessStart, bool PlayerVehicleEnabled, DateTimeMode DateMode, DateTimeMode TimeMode, string EntrypointIdentity, string SituationIdentity): lo que el host publica para el plugin en el archivo asignado en memoria OmsiLaunch.Handoff.<sessionId>.INTERNAL
StartupHandoffWireCódec: número mágico 0x4F4C5348, versión 4 (lee la 3 y la 4), cabecera de 64 bytes con integridad de la carga útil mediante SHA-256.INTERNAL

El plugin rechaza un handoff (plugin.request.unsupported → OL_E_CAPABILITY_UNAVAILABLE) salvo que WorldMode sea NewMap o SavedSituation, HeadlessStart sea true, PlayerVehicleEnabled sea false, ambos modos de fecha y hora sean Unset y una situación guardada tenga una identidad no vacía. El planificador aplica las mismas restricciones antes, así que un plan ejecutable nunca provoca este rechazo.

Tipos del registro de capacidades#

TipoFinalidad
PublicCapabilityRegistryProtocolVersion ("0.1"), All (36 entradas PublicCapabilityDescriptor), PublicRuntimeOperationIds (los 48 ids de operación concretos que un frontend puede reenviar), IsPublicRuntimeOperation, GetRuntimeArguments, ValidateRuntimeArguments (devuelve PublicRuntimeArgumentValidation), IsInternalResultKey. Lo aplican ExecuteRuntimeAsync, la CLI y el plano de control local.
PublicCapabilityDescriptorId, Family, Classification, Kind, RequiresSession, RequiresExactProfile, ApiRoute, CliRoute, RuntimeValidation, Description, HandleTypes.
PublicCapabilityClassificationPublicStableBeta, PublicExperimental, InternalOnly, Unsupported.
PublicCapabilityKindRead, Write, Action, Event.
PublicRuntimeArgumentDescriptorName, Required, Description.
PublicRuntimeArgumentValidationAccepted, ErrorCode, Message.

Catálogo completo: capacidades.

Métodos de extensión de D3DRuntimeApi#

Envoltorios tipados sobre ExecuteRuntimeAsync para las operaciones d3d.* (EXPERIMENTAL, capacidad PublicExperimental d3d.texture). Asignan los ids de solicitud a partir de un contador de ámbito de proceso que empieza en 30 000 y usan un timeout predeterminado de 5 s (salvo GetD3DStatusAsync, que exige uno).

MétodoOperaciónArgumentos y límites
GetD3DStatusAsync(IOmsiLaunch, SessionHandle, TimeSpan timeout, CancellationToken) → D3DDeviceStatusd3d.statusninguno
CreateD3DTextureAsync(..., uint width, uint height, D3DTextureFormat format, uint levels = 1, TimeSpan? timeout, ...) → D3DTextureDescriptiond3d.texture.createanchura/altura 1..4096, niveles 0..16
DescribeD3DTextureAsync(..., D3DTextureHandle handle, uint level = 0, ...)d3d.texture.describenivel 0..15
UpdateD3DTextureAsync(..., D3DTextureHandle handle, D3DTextureUpdate update, ...)d3d.texture.updateD3DTextureUpdate(Level, X, Y, Width, Height, Pixels): x/y 0..4095, anchura/altura 1..4096, píxeles ≤ 48 KiB (codificados en Base64 en la transmisión)
ReleaseD3DTextureAsync(..., D3DTextureHandle handle, ...)d3d.texture.releaseuna liberación repetida se rechaza con OL_E_D3D_RESOURCE_RELEASED

Tipos: D3DDeviceStatus(Available, State, Generation, LiveTextureCount, ResetHookInstalled, ExecutionThreadId, LastResetThreadId, QueryInterfaceHResult, CooperativeLevelHResult, OwnedDeviceReferences); D3DDeviceState: NotReady, Ready, Lost, Resetting, Stopping, Stopped; D3DTextureHandle(Value) con Value = "d3dtex-<session id N>-<16 hex>"; D3DTextureDescription(Handle, State, DeviceState, Generation, Width, Height, Format, Levels, Level, LevelWidth, LevelHeight, HResult, ExecutionThreadId); D3DTextureResourceState: Live, Released, Stale; D3DTextureFormat: A8R8G8B8, X8R8G8B8, R5G6B5, X1R5G5B5, A1R5G5B5, A4R4G4B4, A8, L8, A8L8.

Errores: un resultado fallido se vuelve a lanzar como OmsiRuntimeException(Code, detail), donde Code es el ErrorCode del resultado (u OL_E_RUNTIME_OPERATION_FAILED si no lo hay) y el mensaje es "<code>: <Values["detail"]>"; un resultado correcto sin valores, o una cadena de estado del dispositivo desconocida, lanza OmsiRuntimeException("OL_E_RUNTIME_PROTOCOL_MISMATCH", ...). Todo lo que lanza ExecuteRuntimeAsync se propaga sin cambios. La gestión del reset del dispositivo está validada en runtime: un reset hace pasar el dispositivo por Resetting de vuelta a Ready e invalida todas las texturas activas (OL_E_D3D_STALE_RESOURCE_HANDLE, cierre de validación en runtime D01); GetCapabilitiesAsync sigue notificando runtime.d3d.lifecycle.reset como IMPLEMENTED_NOT_RUNTIME_VALIDATED (retraso del autoinforme). La transición Lost no se puede producir desde fuera del producto y solo está cubierta offline.

var status = await launch.GetD3DStatusAsync(session, TimeSpan.FromSeconds(5));
if (status.State == D3DDeviceState.Ready)
{
    var texture = await launch.CreateD3DTextureAsync(session, 8, 8, D3DTextureFormat.A8R8G8B8);
    var pixels = new byte[8 * 8 * 4];
    await launch.UpdateD3DTextureAsync(session, texture.Handle, new D3DTextureUpdate(0, 0, 0, 8, 8, pixels));
    await launch.ReleaseD3DTextureAsync(session, texture.Handle);
}

Tipos del contrato de proceso#

TipoContenido
PublicExitCodeSuccess = 0, SessionFailed = 1, InvalidArguments = 2, UnsupportedProfile = 3, NoActiveSession = 4, RuntimeUnavailable = 5, NotFound = 6, OperationRejected = 7, TransactionRecoveryFailed = 8, InternalError = 10. Lo usa solo la CLI (códigos de salida); la API nunca termina el proceso.
PublicErrorCategoryConstantes de cadena usadas en los envelopes de error de la CLI y del control: invalid_argument, unsupported_profile, session, runtime, not_found, transaction, internal.
PublicErrorCodesUna const string por código (143: 142 errores OL_E_ y 1 advertencia OL_W_) y All, el catálogo de PublicErrorDescriptor(Code, Category). Categorías: Cli, Compatibility, Content, Installation, InvalidArgument, LaunchSpec, LocalControl, Other, Presentation, Process, Runtime, RuntimeD3D, Session, SessionProfile, Transaction, Warning. Referencia: códigos de error.
PublicErrorDescriptor(string Code, string Category).
OmsiRuntimeExceptionPropiedad Code más el mensaje; solo la lanza D3DRuntimeApi.

InstallationPaths (identidad de la instalación y contención de rutas)#

Estabilidad: STABLE_BETA (funciones puras, sin E/S, sin estado de OMSI, sin modificaciones del sistema de archivos, sin participación en transacciones, sin necesidad de una sesión en ejecución). Es la definición única que usan el lease de la instalación, el nombre de la canalización de control local, el confinamiento de los recursos de los perfiles de sesión, la validación de destinos de las texturas de Internet y la ruta del modelo para la generación en runtime.

MiembroComportamiento
string NormalizeRoot(string root)Path.GetFullPath(root) sin separadores finales, excepto en una raíz de unidad (C:\), que se conserva. Resuelve los segmentos . y .., trata / y \ por igual y colapsa los separadores repetidos. No resuelve uniones ni vínculos simbólicos. Lanza ArgumentException para una raíz null o en blanco.
string IdentityKey(string root)NormalizeRoot(root) en mayúsculas. Las grafías léxicamente equivalentes de una misma raíz (C:\OMSI, C:\OMSI\, C:\OMSI\., C:\foo\..\OMSI, c:\omsi) comparten una clave; las raíces distintas (C:\OMSI-A, C:\OMSI-B) nunca.
bool TryGetContainedRelativePath(string root, string candidate, out string relativePath)Resuelve candidate (relativo a root, o absoluto) y devuelve true solo cuando se encuentra estrictamente por debajo de root; relativePath es la grafía canónica con \. Usa los segmentos de Path.GetRelativePath, así que un directorio hermano como C:\OMSI-A\x nunca está dentro de C:\OMSI; la propia raíz, otros volúmenes y los escapes con .. devuelven false.
IReadOnlyList<string> Segments(string relativePath)Divide por / y \, descartando los segmentos vacíos.
var same = InstallationPaths.IdentityKey(@"C:\OMSI") == InstallationPaths.IdentityKey(@"c:\foo\..\OMSI\"); // true
InstallationPaths.TryGetContainedRelativePath(@"C:\OMSI", @"Sceneryobjects\\x\texture\a.tga", out var relative); // true, "Sceneryobjects\x\texture\a.tga"

Perfiles de sesión (OmsiLaunch.Core)#

Estabilidad: EXPERIMENTAL. Estos tipos compilan un perfil de sesión YAML (<root>\.omsilaunch\session-profiles\<id>\profile.yaml, esquema omsilaunch.session-profile/v1) en un LaunchSpec. La CLI /predefined-profile:<id> /predefined-profile-index:<n> usa exactamente estas llamadas; un integrador puede usarlas para iniciar un perfil a través de la API.

MiembroComportamiento
SessionProfileCompiler.Load(string installationRoot, string id, int presetIndex) → SessionProfilePackageLee y valida el paquete. id debe ser un nombre de directorio simple (en caso contrario, OL_E_SESSION_PROFILE_PATH_ESCAPE); presetIndex es 1..5 (OL_E_SESSION_PROFILE_PRESET_NOT_FOUND). Archivo ausente: OL_E_SESSION_PROFILE_NOT_FOUND; mayor que MaxBytes (256 KiB), YAML no válido, anclas, claves desconocidas o un id distinto del nombre del directorio: OL_E_SESSION_PROFILE_INVALID; otro schema: OL_E_SESSION_PROFILE_SCHEMA_UNSUPPORTED. Las rutas de los recursos quedan confinadas al paquete (OL_E_SESSION_PROFILE_PATH_ESCAPE, OL_E_SESSION_PROFILE_ASSET_MISSING). Todo fallo es una SessionProfileException.
SessionProfileCompiler.Apply(SessionProfilePackage profile, LaunchSpec baseline, WorldMode selectedWorldMode) → LaunchSpecDevuelve baseline con el perfil aplicado: para NewMap, el bloque new del perfil (mapa, punto de entrada y cualquier fecha/hora/año/meteorología, que hacen que el plan no sea ejecutable en esta build); los settings del preset combinados sobre Environment.General; los Presentation, InternetTextures y Behavior del preset, cuando existen; y SessionProfile = profile.Metadata. Para NewMap también comprueba el mapa contra la lista compatibility del perfil (OL_E_SESSION_PROFILE_MAP_MISMATCH).
SessionProfileCompiler.ValidateCompatibility(SessionProfilePackage, string installationRoot, WorldSpec world, WorldMode mode)La comprobación de compatibilidad para los demás modos (para SavedSituation, el mapa se lee del .osn). La CLI la llama después de construir el WorldSpec definitivo.
SessionProfileCompiler.Schema, MaxBytes, SchemaKeys"omsilaunch.session-profile/v1", 262144 y las claves aceptadas en cada mapeo YAML.
SessionProfilePackage(RootPath, Metadata, CompatibleMaps, New, Preset), ProfileNew, ProfilePresetEl paquete cargado; Preset es solo el preset seleccionado.
SessionProfileException(string code, string message)IOException con Code (uno de los códigos OL_E_SESSION_PROFILE_*); el mensaje es "<code>: <message>".

Además, la CLI rechaza los flags de línea de comandos que entran en conflicto con el perfil (OL_E_SESSION_PROFILE_OVERRIDE_CONFLICT); esa comprobación no forma parte del compilador. Consulta perfiles de sesión para ver el orden completo de combinación.

static async Task<SessionPlan> PlanProfileAsync(IOmsiLaunch launch, LaunchSpec baseline, string installationRoot, string profileId)
{
    // baseline: a LaunchSpec for installationRoot with World.Mode = WorldMode.NewMap (see the complete example).
    var profile = SessionProfileCompiler.Load(installationRoot, profileId, presetIndex: 1);
    var spec = SessionProfileCompiler.Apply(profile, baseline, WorldMode.NewMap);
    return await launch.PlanSessionAsync(spec); // plan.Spec.SessionProfile carries the provenance
}

Tipos de plataforma (OmsiLaunch.Process)#

TipoEstabilidadUso
IRuntimePlatformSTABLE_BETA como tipo del parámetro del constructor de OmsiLaunchServiceDetecta la plataforma, comprueba si se puede escribir, inicia, observa, termina y espera a Omsi.exe. Pasa new CurrentWindowsX64Platform(); implementarlo por tu cuenta no está soportado.
CurrentWindowsX64PlatformSTABLE_BETALa única implementación: host Windows x64, CreateProcessW para Omsi.exe, TerminateProcess para la detención canónica. Sus métodos los llama el servicio; los integradores solo lo construyen. Miembros (compartidos con IRuntimePlatform): Detect(root) devuelve el RuntimePlatformInfo del plan; ValidateCurrent(info) lanza OL_E_UNSUPPORTED_OPERATING_SYSTEM / OL_E_UNSUPPORTED_OS_ARCHITECTURE / OL_E_PLATFORM_CAPABILITY_MISSING cuando el host no puede ejecutar una sesión; IsInstallationWritable(root) respalda OL_E_INSTALLATION_NOT_WRITABLE; StartAsync(request, sha256) crea Omsi.exe y registra la identidad del proceso (PID, hora de creación, ruta y el hash calculado por el servicio; OL_E_PROCESS_START_FAILED, OL_E_PROCESS_CREATION_TIME_FAILED); HasExited, WaitForExitAsync y Terminate lo observan y lo finalizan.
InstallationLease, LaunchedProcess, ProcessIdentity, ReleaseManifest, RuntimeArtifact, RuntimeArtifactSet, StartupProcessRequest, CurrentRuntimeCommandStore, IOmsiProcessController, OmsiProcessStateINTERNALPúblicos en el ensamblado porque el servicio y las pruebas los comparten. No son una superficie de integración; LaunchedProcess envuelve internamente los handles del proceso y del thread de OMSI (no son miembros públicos) y IOmsiLaunch nunca lo devuelve.

Seguridad para subprocesos#

  • OmsiLaunchService admite llamadas concurrentes sobre sesiones distintas: las sesiones residen en un ConcurrentDictionary y toda modificación de una sesión se hace bajo el bloqueo privado de esa sesión.
  • Las llamadas concurrentes sobre la misma sesión son seguras, pero se serializan donde importa: ExecuteRuntimeAsync toma una barrera por sesión, de modo que un segundo comando espera al primero (su timeout empieza cuando se deposita).
  • El supervisor se ejecuta en una tarea del grupo de threads (Task.Run) desde el momento en que StartSessionAsync retorna hasta que la sesión es terminal; sondea la telemetría y el proceso cada 100 ms. Los llamadores nunca ejecutan código del supervisor.
  • StopAsync y GetStatusAsync se completan de forma síncrona y se pueden llamar desde cualquier thread, incluso dentro de un controlador ProcessExit (la CLI lo hace con un margen de 4 s).
  • Ninguna llamada de la API está ligada a un thread; ninguna requiere un contexto de sincronización.

Lo que no está en la API#

  • Ningún IntPtr, nint, handle de Win32, dirección nativa, puntero de VMT ni objeto de proceso. Los valores del resultado cuyas claves empiezan por internal_ o terminan en _address, _pointer, _vmt se eliminan antes de que un resultado salga de ExecuteRuntimeAsync.
  • Ninguna operación de runtime internal.*: internal.road-vehicles.make-basic es InternalOnly en el registro y devuelve OL_E_RUNTIME_OPERATION_UNKNOWN desde la API y desde la CLI.
  • Ninguna lectura ni escritura directa de la memoria de OMSI, ningún acceso a nivel de archivo a la instalación más allá de lo que declara un LaunchSpec.
  • Ningún handle entre procesos: el plano de control local es la única vía entre procesos y solo acepta session.status, session.events, session.stop y runtime.execute.
  • Ningún cierre cooperativo de OMSI, ningún LAST_MAP_STATE, ninguna aplicación de fecha/hora/meteorología/vehículo del jugador y ningún overlay de documentos de teclado/mando en esta build.

Resumen de estabilidad#

SuperficieEstabilidad
Constructor de OmsiLaunchService, OmsiLaunchRuntimePathsSTABLE_BETA
PlanSessionAsync, StartSessionAsync (NEW_MAP, SAVED_SITUATION), GetStatusAsync, WaitForAsync, StopAsync, CloseAsyncSTABLE_BETA
Transporte de ExecuteRuntimeAsync; operaciones PublicStableBetaSTABLE_BETA
Operaciones PublicExperimental, D3DRuntimeApi, camera.lockEXPERIMENTAL
Contenido de la lista de GetCapabilitiesAsync, miembros de la especificación de fecha/hora/meteorología/vehículo del jugador/entrada, DiagnosticsSpec, ExpectedExecutableSha256, RestoreConfiguration, ShutdownTimeoutSecondsPARTIAL
RuntimeCommandWire, StartupHandoff, StartupHandoffWire, implementaciones de IRuntimePlatform, todos los ensamblados de implementaciónINTERNAL
WorldMode.LastMapState / LastSituation, weather.set, operaciones internal.*UNAVAILABLE