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

Documentación de la versión v0.1.0-beta.3Ver código 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 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 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, 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).
INTERNALEs 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.
UNAVAILABLEPresente en el contrato, pero el build actual lo rechaza.

Descripció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, helpers de 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 finaliza.No se distribuye ninguna otra implementación. Un IRuntimePlatform personalizado es INTERNAL.
PluginBuildDirectoryDirectorio 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 <package>\plugins.Directorio o archivo faltante: 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 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). 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));

Cree un único servicio por proceso y compártalo. Estabilidad: STABLE_BETA.

Reglas de propiedad de la sesión#

ReglaDetalle
Un propietario por instalaciónStartSessionAsync adquiere el lease de la instalación (bloqueo exclusivo), un semáforo con nombre Local\OmsiLaunch.Installation.<SHA-256 of the upper-cased full root path> (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 procesoSessionHandle 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, no por handles.
Llame siempre a CloseAsyncA 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 sesionesUn 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 verificarStartSessionAsync 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 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.

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)
PropósitoCompilar 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ámetrosspec: un LaunchSpec completamente poblado (consulte 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 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.
DevuelveSessionPlan 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 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 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.
ExcepcionesOperationCanceledException 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ónSe 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ónNo.
Modifica el estado de OMSINo.
Modifica el sistema de archivosNo (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ó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 este 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)
PropósitoIniciar 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á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 en el rango 1..600.
DevuelveSessionHandle(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, ...).
ExcepcionesInvalidOperationException("OL_E_PLAN_NOT_RUNNABLE") cuando plan.IsRunnable es false; InvalidOperationException("OL_E_PLAN_NOT_RUNNABLE: <codes>") 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 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 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).
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 creó) se finaliza 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 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ónAbre 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.
LimitacionesSolo 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.
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)
PropósitoLeer 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ámetrossession: un handle devuelto por StartSessionAsync que todavía no se cerró.
DevuelveSessionStatus(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.
ExcepcionesKeyNotFoundException para handles desconocidos o cerrados. En cualquier otro caso nunca lanza excepciones.
CancelaciónEl token se ignora (la llamada se completa de forma sincrónica).
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)
PropósitoConsultar 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ámetrosstate: cualquier SessionState. Esperar un estado transitorio que ya pasó (o que nunca se establece, consulte 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. 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.
ExcepcionesKeyNotFoundException; 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)
PropósitoSolicitar 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ámetrossession.
DevuelveUna tarea completada; no espera la finalización ni la restauración. Use WaitForAsync(session, SessionState.Completed, ...) para observar la finalización.
ExcepcionesKeyNotFoundException.
CancelaciónEl token se ignora.
Requiere una sesión en ejecuciónNo. 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 OMSISí: finaliza el proceso de OMSI (código de salida 1).
Modifica el sistema de archivosIndirectamente: 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ó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)
PropósitoLiberar 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ámetrossession.
DevuelveSe 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).
ExcepcionesKeyNotFoundException; 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ó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 viva (igual que StopAsync).
Modifica el sistema de archivosIndirectamente (restauración por parte del supervisor).
Transacción / restauraciónGarantiza 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.
EstabilidadSTABLE_BETA.
Ejemplotry { ... } finally { await launch.CloseAsync(session); }

ExecuteRuntimeAsync#

AspectoDetalle
FirmaTask<RuntimeCommandResult> ExecuteRuntimeAsync(SessionHandle session, RuntimeCommand command, TimeSpan timeout, CancellationToken cancellationToken = default)
PropósitoEjecutar 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 y capacidades.
Parámetroscommand.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 verificaciones1. 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.
DevuelveRuntimeCommandResult(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).
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 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.
ExcepcionesKeyNotFoundException; 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ónSe 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ó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 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 archivosEl host no escribe nada. OMSI puede escribir sus propios archivos como consecuencia (sin seguimiento).
Transacción / restauraciónNinguna.
LimitacionesUn 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-<session>-<hex>) tienen alcance de sesión.
EstabilidadTransporte y contrato: STABLE_BETA; la estabilidad de cada operación sigue a 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)
PropósitoDevolver 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 considere escribible, 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 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.
ExcepcionesOperationCanceledException al entrar; ArgumentException para una ruta raíz vacía.
CancelaciónSe verifica una vez al entrar.
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)
PropósitoEnumerar 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á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.
ExcepcionesOperationCanceledException 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ónSe verifica una vez al entrar.
Requiere una sesión en ejecuciónNo.
Modifica OMSI / sistema de archivos / transacciónNo / No / Ninguna.
EstabilidadMaps, 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).
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:

ValorAlcanceIdentityKindDisplayName
Mapsningunomaps\<dir>\global.cfgmapnombre del directorio del mapa
Situationsningunosituations\...\<file>.osnsituationidentidad del mapa referenciada por 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 son solo para el descubrimiento: la ruta de inicio usa PresentedEntrypointIndex; pasar un EntrypointIdentity vuelve no ejecutable el plan en este build (world.entrypoint-identity, RUNTIME_PARTIAL).

RecoverPendingAsync#

AspectoDetalle
FirmaTask<RecoveryStatus> RecoverPendingAsync(InstallationSpec installation, bool restore, CancellationToken cancellationToken = default)
PropósitoInformar o completar una transacción duradera obsoleta (<root>\.omsilaunch\journal.json) que dejó un propietario que se bloqueó. Toma el lease de la instalación durante toda la llamada, para no restaurar nunca por debajo de una sesión que se está iniciando. Capacidad pública session.recover; en la CLI, /recovery-status y /recover.
Parámetrosinstallation.RootPath: la raíz de la instalación (normalizada con Path.GetFullPath). restore: false = solo informar; true = restaurar, verificar y eliminar el journal y las copias de seguridad.
DevuelveRecoveryStatus(Pending, Recovered, Diagnostics): Pending = existía un journal cuando comenzó la llamada; Recovered = se solicitó una restauración, se ejecutó y no queda ningún journal; Diagnostics = notas de la restauración (restore.session-artifact-removed con Data["sha256"], OL_W_RESTORE_FOREIGN_FILE_RETAINED), vacío cuando no se restauró nada.
ExcepcionesInvalidOperationException("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 archivo ejecutable del journal siguen coincidiendo con un proceso vivo o, si el journal está más allá de HandoffCreated sin PID, cuando hay algún Omsi.exe de esa raíz en ejecución; 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 journal dañado; ArgumentException para una raíz vacía; OperationCanceledException. Siempre que lanza una excepción después de iniciada una restauración, el journal se conserva y la siguiente llamada la vuelve a ejecutar de forma idempotente.
CancelaciónSe pasa a las escrituras del journal y de las copias de seguridad; cancelar a mitad de la restauración deja el journal pendiente.
Requiere una sesión en ejecuciónNo (se niega a actuar 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 manejan los originales de solo lectura; escritura directa + vaciado del búfer; no quedan archivos *.omsilaunch.tmp), elimina los artefactos de la sesión y borra journal.json y backup\<sessionId>.
Transacción / restauraciónCompleta la transacción pendiente (Restoring → Restored → journal eliminado).
EstabilidadSTABLE_BETA: ruta de informe y recuperación tras una salida anticipada (matriz RV-008), recuperación después de una restauración fallida (cierre de validación en runtime F01), rechazo con un propietario vivo y en la ventana previa al PID (S04) y recuperación diferida previa a la huella al iniciar (S05); consulte 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 está documentada en la referencia de LaunchSpec.
PresenceUnset = 0, Set = 1Enum de tipo byte.
SemanticDate(int Year, int Month, int Day)Solo se valida cuando DateTimeMode.Explicit (mes 1..12, día 1..31).
SemanticTime(int Hour, int Minute, int Second)Solo se valida cuando DateTimeMode.Explicit (0..23, 0..59, 0..59).

Familia de LaunchSpec#

Todos los records siguientes están documentados propiedad por propiedad en la referencia de LaunchSpec; esta tabla fija el inventario de tipos.

TipoPropósitoEstabilidad
LaunchSpecRecord raíz de la solicitud, con los descriptores de acceso EffectiveYear, EffectiveWeather, EffectiveInput, EffectiveDiagnostics, EffectivePresentation, EffectiveInternetTextures, que sustituyen por valores predeterminados los miembros opcionales null.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 vuelve no ejecutable el plan.PARTIAL (STATICALLY_PARTIAL)
WeatherSpec, WeatherModeWeatherMode: Unset, Preset, Icao, RealCurrent. Cualquier modo distinto de Unset vuelve no ejecutable el plan.PARTIAL
PlayerVehicleSpecModel, Repaint, Hof, FleetNumber, Registration, Enabled. Cualquier campo establecido vuelve no ejecutable el plan.PARTIAL
EnvironmentSpecOcho grupos IReadOnlyDictionary<string, OptionalValue<string>> de ajustes semánticos de options.cfg.STABLE_BETA
InputSpecKeyboardDocument, ControllerDocument; cualquier valor establecido vuelve no ejecutable el plan.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 ocurre 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.Es un record público: puede editarse o quedar obsoleto, y por eso StartSessionAsync vuelve a planificar.
RuntimePlatformInfoOsFamily, OsVersion, OsArchitecture, HostArchitecture, OmsiArchitecture (X86), PluginArchitecture (X86), CurrentPlatformSupported (Windows 10 o posterior, sistema operativo 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 comienzan con OL_E_ son errores; los que comienzan con OL_W_, advertencias; cualquier otro 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 (basado en 1, por sesión), Data.Limitado a los 256 eventos más recientes (se descartan los más antiguos). El slot de telemetría es un slot de último valor: los eventos emitidos más rápido que el sondeo de 100 ms del host pueden perderse. No es un registro sin pérdidas.
SessionHandleSessionId.Local al proceso.
RecoveryStatusPending, Recovered, Diagnostics.Consulte RecoverPendingAsync.
ContentIdentityIdentity, Kind, DisplayName.Consulte 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 en la práctica no se puede observar. Estados terminales: Completed, Failed. Semántica completa: ciclo de vida de la sesión.

Tipos del 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 envoltorio del buzón: valor mágico 0x4F4C5243 ("OLRC"), versión 1, encabezado little-endian de 72 bytes (valor 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) seguido de una carga útil JSON en UTF-8. SerializeRequest, SerializeResponse, TryDeserializeRequest, TryDeserializeResponse, TryReadRequestId.INTERNAL: es público porque ambos extremos del puente lo comparten; 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 mapeado en memoria OmsiLaunch.Handoff.<sessionId>.INTERNAL
StartupHandoffWireCódec: valor mágico 0x4F4C5348, versión 4 (lee 3 y 4), encabezado 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/hora sean Unset y, en el caso de una situación guardada, esta tenga una identidad no vacía. El planificador aplica las mismas restricciones antes, por lo que un plan ejecutable nunca provoca este rechazo.

Tipos del registro de capacidades#

TipoPropósito
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 hacen cumplir 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#

Wrappers tipados sobre ExecuteRuntimeAsync para las operaciones d3d.* (EXPERIMENTAL, capacidad PublicExperimental d3d.texture). Asignan los ids de solicitud a partir de un contador global del proceso que comienza en 30 000 y usan un timeout predeterminado de 5 s (excepto GetD3DStatusAsync, que requiere uno explícito).

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.createancho/alto 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, ancho/alto 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 (o OL_E_RUNTIME_OPERATION_FAILED cuando falta) y el mensaje es "<code>: <Values["detail"]>"; un resultado exitoso 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. El manejo del reinicio del dispositivo (D3D) está validado en runtime: un reinicio hace pasar el dispositivo por Resetting y lo devuelve a Ready, e invalida todas las texturas vivas (OL_E_D3D_STALE_RESOURCE_HANDLE, cierre de validación en runtime D01); GetCapabilitiesAsync todavía informa runtime.d3d.lifecycle.reset como IMPLEMENTED_NOT_RUNTIME_VALIDATED (desfase del autoinforme). La transición a Lost no puede producirse 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 del proceso#

TipoContenido
PublicExitCodeSuccess = 0, SessionFailed = 1, InvalidArguments = 2, UnsupportedProfile = 3, NoActiveSession = 4, RuntimeUnavailable = 5, NotFound = 6, OperationRejected = 7, TransactionRecoveryFailed = 8, InternalError = 10. Solo lo usa la CLI (códigos de salida); la API nunca finaliza el proceso.
PublicErrorCategoryConstantes de cadena usadas en los envoltorios de error de la CLI y del control local: 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 requerir una sesión en estado Running). Es la única definición que usan el lease de la instalación, el nombre del pipe de control local, el confinamiento de los assets de los perfiles de sesión, la validación de los destinos de Internet Textures y la ruta del modelo de generación (spawn) en runtime.

MiembroComportamiento
string NormalizeRoot(string root)Path.GetFullPath(root) sin separadores finales, excepto en la raíz de una unidad (C:\), que se conserva. Resuelve los segmentos . y .., trata / y \ de la misma forma y colapsa los separadores repetidos. No resuelve junctions 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 misma clave; raíces distintas (C:\OMSI-A, C:\OMSI-B) nunca la comparten.
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, de modo 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 mediante .. 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 en 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 mediante la API.

MiembroComportamiento
SessionProfileCompiler.Load(string installationRoot, string id, int presetIndex) → SessionProfilePackageLee y valida el paquete. id debe ser un nombre de directorio simple (de lo contrario, OL_E_SESSION_PROFILE_PATH_ESCAPE); presetIndex está en el rango 1..5 (OL_E_SESSION_PROFILE_PRESET_NOT_FOUND). Archivo faltante: OL_E_SESSION_PROFILE_NOT_FOUND; tamaño 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 assets 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/clima, lo que vuelve no ejecutable el plan en este build); los settings del preset combinados sobre Environment.General; Presentation, InternetTextures y Behavior del preset cuando están presentes; y SessionProfile = profile.Metadata. Para NewMap también verifica el mapa contra la lista compatibility del perfil (OL_E_SESSION_PROFILE_MAP_MISMATCH).
SessionProfileCompiler.ValidateCompatibility(SessionProfilePackage, string installationRoot, WorldSpec world, WorldMode mode)La verificació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 final.
SessionProfileCompiler.Schema, MaxBytes, SchemaKeys"omsilaunch.session-profile/v1", 262144 y las claves aceptadas para 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>".

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

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, verifica si se puede escribir, inicia, observa, finaliza y espera a Omsi.exe. Pase new CurrentWindowsX64Platform(); implementarlo por cuenta propia no está soportado.
CurrentWindowsX64PlatformSTABLE_BETALa única implementación: host Windows x64, CreateProcessW para Omsi.exe, TerminateProcess para la detención canónica. El servicio llama a sus métodos; 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 encapsula internamente los handles del proceso y del thread de OMSI (no son miembros públicos) y IOmsiLaunch nunca lo devuelve.

Seguridad de threads#

  • OmsiLaunchService admite llamadas concurrentes sobre sesiones distintas: las sesiones residen en un ConcurrentDictionary y cada modificación por sesión ocurre 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 un gate por sesión, por lo que un segundo comando espera al primero (su timeout comienza cuando se deposita en el buzón).
  • 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; consulta la telemetría y el proceso cada 100 ms. Los llamadores nunca ejecutan código del supervisor.
  • StopAsync y GetStatusAsync se completan de forma sincrónica y se pueden llamar desde cualquier thread, incluso dentro de un manejador de ProcessExit (la CLI lo hace con un margen de 4 s).
  • Ninguna llamada de la API tiene afinidad de thread; ninguna requiere un contexto de sincronización.

Lo que no está en la API#

  • No hay IntPtr, nint, handles de Win32, direcciones nativas, punteros a VMT ni objetos de proceso. Los valores del resultado cuyas claves comienzan con internal_ o terminan en _address, _pointer, _vmt se eliminan antes de que un resultado salga de ExecuteRuntimeAsync.
  • No hay operaciones 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.
  • No hay lecturas ni escrituras directas de la memoria de OMSI, ni acceso a nivel de archivo a la instalación más allá de lo que declara un LaunchSpec.
  • No hay handles entre procesos: el plano de control local es la única ruta entre procesos, y solo acepta session.status, session.events, session.stop y runtime.execute.
  • En este build no hay cierre cooperativo de OMSI, ni LAST_MAP_STATE, ni aplicación de fecha/hora/clima/vehículo del jugador, ni overlays de documentos de teclado/controlador.

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/clima/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