Referência da API pública (OmsiLaunch.Api)

Documentação da versão v0.1.0-beta.3Ver fonte no GitHub

Tradução da página original em inglês do OmsiLaunch 0.1.0-beta3. A página em inglês é a referência normativa: se as duas divergirem, valem a página em inglês e o código.

Esta página é a referência normativa da API pública gerenciada do OmsiLaunch 0.1.0-beta3: o assembly OmsiLaunch.Api (contratos) e o ponto de entrada para integradores OmsiLaunchService em OmsiLaunch.Core. Ela documenta somente o que o código atual faz. Tudo o que um integrador pode chamar, receber ou observar está listado aqui com seu nível de estabilidade; o que não está listado não é uma superfície de integração.

O inventário da API pública gerado lista todo tipo e membro público de OmsiLaunch.Api, OmsiLaunch.Core e OmsiLaunch.Process com sua assinatura e estabilidade; um gate de documentação falha quando o inventário e os assemblies divergem. Esta página explica a semântica.

Páginas relacionadas: referência do LaunchSpec, códigos de erro, ciclo de vida da sessão, transações e recuperação, controle de runtime, capacidades, plano de controle local, códigos de saída, status da validação em runtime.

Vocabulário de estabilidade#

NívelSignificado nesta página
STABLE_BETAO contrato está congelado para a linha de protocolo 0.1 e o caminho está validado em runtime em research/reports/OMSILAUNCH-RUNTIME-VALIDATION-MATRIX.md.
EXPERIMENTALPode ser chamado e é testado, mas o contrato ou a evidência de runtime podem mudar antes de se tornar estável.
PARTIALPresente no contrato; somente parte do comportamento está implementada ou validada (o texto diz qual parte).
INTERNALPúblico no assembly por razões técnicas (a ponte compartilha o tipo), mas não é uma superfície de integração; pode mudar sem aviso.
UNAVAILABLEPresente no contrato, mas rejeitado pela build atual.

Visão geral dos assemblies#

AssemblyPapel para integradores
OmsiLaunch.ApiContratos puros: records, enums, IOmsiLaunch, registro de capacidades, catálogo de erros, formatos de transmissão (wire formats), helpers de D3D. Não contém IntPtr, nint, handle Win32, endereço nativo nem objeto de processo.
OmsiLaunch.CoreOmsiLaunchService (a implementação de IOmsiLaunch), OmsiLaunchRuntimePaths, SessionPlanner, LaunchValidation, SessionProfileCompiler.
OmsiLaunch.ProcessIRuntimePlatform e CurrentWindowsX64Platform (o único adaptador de plataforma), InstallationLease. Necessários para construir o serviço.
OmsiLaunch.Configuration, OmsiLaunch.Content, OmsiLaunch.Interop, OmsiLaunch.Plugin, OmsiLaunch.Builds.Omsi23004Assemblies de implementação. Seus tipos públicos são INTERNAL para integradores.

Ponto de entrada: OmsiLaunchService e 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álidoInválido / padrão
platformnew CurrentWindowsX64Platform() (namespace OmsiLaunch.Process). Detecta a plataforma, cria o processo do OMSI com CreateProcessW, aguarda seu término e o encerra.Nenhuma outra implementação é distribuída. Um IRuntimePlatform personalizado é INTERNAL.
PluginBuildDirectoryDiretório que contém os arquivos de referência do conjunto de arquivos do plugin permanente (closure do plugin): OmsiLaunch.Plugin.opl, OmsiLaunch.PluginNE.dll, OmsiLaunch.Plugin.deps.json, OmsiLaunch.Plugin.runtimeconfig.json e todo OmsiLaunch.*.dll da closure gerenciada (deve incluir OmsiLaunch.Plugin.dll). Em um pacote instalado, é <package>\plugins.Diretório ou arquivo ausente: PlanSessionAsync retorna um plano não apto para execução com OL_E_RUNTIME_ARTIFACT_MISSING.
NativeBridgePathCaminho de OmsiLaunch.Native.x86.dll (no pacote: <package>\plugins\OmsiLaunch.Native.x86.dll).Igual ao anterior.
ReleaseManifestPathrelease-manifest.json ao lado de OmsiLaunch.exe, quando presente. Fornece o SHA-256 esperado de cada arquivo de plugins/ (plugin.integrity.reference = manifest).null (layout de desenvolvimento): os arquivos instalados são verificados apenas quanto à presença e à autoconsistência em relação à closure de referência (plugin.integrity.reference = self). Manifesto malformado: OL_E_RELEASE_MANIFEST_INVALID.

O serviço lê esses caminhos em toda chamada de PlanSessionAsync e StartSessionAsync; ele nunca copia, prepara nem remove arquivos do plugin (consulte plugin permanente). A CLI constrói o serviço exatamente assim (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));

Crie um serviço por processo e compartilhe-o. Estabilidade: STABLE_BETA.

Regras de propriedade da sessão#

RegraDetalhe
Um proprietário por instalaçãoStartSessionAsync adquire o lease da instalação, um semáforo nomeado Local\OmsiLaunch.Installation.<SHA-256 of the upper-cased full root path>, e o mantém até que o supervisor tenha restaurado a instalação. Um segundo início na mesma raiz, a partir de qualquer processo da mesma sessão de logon, falha com OL_E_INSTALLATION_BUSY (relatado como uma sessão Failed, consulte StartSessionAsync). O lease vale por sessão de logon, não entre sessões de logon diferentes, e não é liberado enquanto outro processo mantiver um handle para ele (risco aceito).
Handles são locais ao processoSessionHandle encapsula o Guid da sessão. Ele só tem significado para a instância de OmsiLaunchService que o retornou. Um handle construído a partir de um Guid conhecido em outro processo (ou em outra instância do serviço) resulta em KeyNotFoundException. O controle entre processos passa pelo plano de controle local, não por handles.
Sempre chame CloseAsyncA partir de StartSessionAsync, o processo possui uma transação durável. CloseAsync solicita a parada canônica quando necessário, aguarda o supervisor (saída do processo, restauração exata, liberação do lease) e esquece a sessão. Deve ser chamado em todo caminho de saída, inclusive após um estado Failed. Sem ele, a entrada da sessão permanece na memória; a restauração em si é executada pelo supervisor de qualquer forma.
Sessões com falha continuam sendo sessõesUm início que falha depois que StartSessionAsync retornou relata SessionState.Failed; o handle continua válido para GetStatusAsync/WaitForAsync até CloseAsync.
Planos são verificados novamenteStartSessionAsync recalcula o hash de Omsi.exe e replaneja a especificação; um plano que não está mais apto para execução é rejeitado com 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);
}

Fatos comuns a todos os métodos:

  • Handles desconhecidos ou já fechados lançam KeyNotFoundException ("Unknown OmsiLaunch session.").
  • Nenhum método exige uma sessão em execução (Running), exceto ExecuteRuntimeAsync.
  • Exceções que carregam um código do OmsiLaunch colocam o código no início de Exception.Message ("OL_E_PLAN_NOT_RUNNABLE: ..."). A CLI extrai códigos das mensagens da mesma forma (CliProgram.Classify).
  • Build suportada: somente Omsi23004_692EBFBF (além do hash da lista de permissões Steam LAA, aceito; gameplay não validado, pois exige uma instalação Steam genuína). Consulte compatibilidade.

Exemplo 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#

AspectoDetalhe
AssinaturaTask<SessionPlan> PlanSessionAsync(LaunchSpec spec, CancellationToken cancellationToken = default)
FinalidadeCompilar um LaunchSpec em um SessionPlan sem iniciar o OMSI: validar a especificação, detectar a plataforma, calcular a impressão digital de Omsi.exe, resolver as identidades de conteúdo, calcular as mutações de arquivo planejadas, listar as capacidades exigidas e as não suportadas e decidir IsRunnable. Capacidade pública session.plan.
Parâmetrosspec: um LaunchSpec totalmente preenchido (consulte a referência do LaunchSpec). Installation, World, Date, Time, Environment (todos os oito dicionários) e Behavior não podem ser nulos; os membros opcionais podem ser null. RootPath deve ser um diretório absoluto; uma raiz vazia é registrada como OL_E_INSTALLATION_NOT_FOUND, mas a sondagem da plataforma em um caminho vazio lança ArgumentException antes que o plano seja retornado, portanto nunca passe uma raiz vazia.
RetornoSessionPlan com um SessionId novo, BuildProfileId = "Omsi23004_692EBFBF" (sempre essa constante, mesmo quando o executável não corresponde), o Spec de entrada, Platform, ResolvedContent, TouchedFiles, RuntimeArtifacts (caminhos de destino plugins\OmsiLaunch.* mais "OmsiLaunch startup handoff v4"), RequiredCapabilities, UnsupportedRequestedFeatures, PlannedMutations, Diagnostics, IsRunnable. IsRunnable é true exatamente quando nenhum código de diagnóstico começa com OL_E_. Diagnósticos informativos (plugin.integrity.reference com mensagem self ou manifest, session_profile.selected) nunca tornam um plano não apto para execução.
Erros no resultadoTodo erro de planejamento é um diagnóstico, não uma exceção: 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 (a mensagem carrega o código de splash/ITX), OL_E_PERMANENT_PLUGIN_MISSING, OL_E_PERMANENT_PLUGIN_HASH_MISMATCH, OL_E_PERMANENT_PLUGIN_MANIFEST_INCOMPLETE (a closure do plugin instalada em plugins\ é verificada em relação ao manifesto de release no momento do planejamento), OL_E_RUNTIME_ARTIFACT_MISSING (a mensagem pode carregar OL_E_RELEASE_MANIFEST_INVALID). Condições completas: regras de validação do LaunchSpec.
Exceções lançadasOperationCanceledException se o token já estiver cancelado na entrada (o único ponto de verificação); ArgumentException/NotSupportedException para caminhos de raiz sintaticamente inválidos; NullReferenceException/ArgumentNullException para membros obrigatórios nulos; System.Text.Json.JsonException para um manifesto de release sintaticamente inválido.
CancelamentoVerificado uma vez na entrada. Depois disso, o planejamento é trabalho síncrono no sistema de arquivos.
Exige sessão em execuçãoNão.
Altera o estado do OMSINão.
Altera o sistema de arquivosNão (lê Omsi.exe, arquivos de conteúdo, a closure do plugin, o manifesto). Os valores de configuração não são validados aqui (somente a existência da chave e a possibilidade de escrita); um valor inválido falha no início com OL_E_INVALID_SETTING_VALUE.
Transação / restauraçãoNenhuma.
LimitaçõesSolicitar qualquer modo de Date/Time/Year diferente de Unset, qualquer modo de Weather diferente de Unset, qualquer campo de PlayerVehicle, documentos de Input, EntrypointIdentity ou WorldMode.LastMapState produz OL_E_CAPABILITY_UNAVAILABLE e um plano não apto para execução nesta build (entradas STATICALLY_PARTIAL / UNSUPPORTED_FOR_CURRENT_PROFILE em UnsupportedRequestedFeatures).
EstabilidadeSTABLE_BETA.
Exemplovar 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#

AspectoDetalhe
AssinaturaTask<SessionHandle> StartSessionAsync(SessionPlan plan, CancellationToken cancellationToken = default)
FinalidadeIniciar uma sessão gerenciada e transacional do OMSI a partir de um plano apto para execução: adquirir o lease da instalação, recuperar um journal obsoleto, validar a closure do plugin permanente, fazer snapshot dos arquivos da sessão e aplicar overlays sobre eles, criar o handoff de inicialização, o slot de telemetria e o mailbox de runtime, iniciar Omsi.exe, registrar o processo no journal e entregar a sessão a um supervisor em segundo plano. Capacidade pública session.start.
Parâmetrosplan: um SessionPlan com IsRunnable == true. A especificação dentro do plano é replanejada; do plano do chamador, somente plan.SessionId é mantido. plan.Spec.Behavior.StartupTimeoutSeconds deve estar em 1..600.
RetornoSessionHandle(plan.SessionId) assim que Omsi.exe for criado e registrado (estado WaitingForPlugin), ou assim que o caminho de início falhar (estado Failed). Não aguarda o gameplay; use WaitForAsync(session, SessionState.Running, ...).
Exceções lançadasInvalidOperationException("OL_E_PLAN_NOT_RUNNABLE") quando plan.IsRunnable é false; InvalidOperationException("OL_E_PLAN_NOT_RUNNABLE: <codes>") quando o replanejamento não está apto para execução (por exemplo, Omsi.exe foi alterado, conteúdo foi removido, closure do plugin ausente); InvalidOperationException("Duplicate session id.") quando uma sessão com o mesmo id ainda está registrada (chame CloseAsync antes); ArgumentOutOfRangeException quando StartupTimeoutSeconds está fora de 1..600; OperationCanceledException quando cancelado antes ou durante o replanejamento; e tudo o que PlanSessionAsync lança. Em todos os casos com exceção, nenhuma sessão é registrada.
Erros no resultadoQualquer falha após o replanejamento é capturada dentro do caminho de início: a sessão é registrada, seu estado é Failed e seus diagnósticos contêm OL_E_START_SESSION, cuja mensagem é a mensagem interna (começando pelo código interno, quando houver): OL_E_INSTALLATION_BUSY (lease ocupado ou um processo do OMSI registrado no journal ainda 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 (somente quando a nova tentativa adiada com os overlays desta sessão ainda não consegue provar a propriedade), OL_E_RECOVERY_JOURNAL_REMOVE_FAILED, OL_E_PROCESS_START_FAILED, OL_E_PROCESS_CREATION_TIME_FAILED. A limpeza pode acrescentar OL_E_PROCESS_CLEANUP_FAILED, OL_E_RESTORE_DEFERRED (saída do OMSI não confirmada; journal mantido) ou OL_E_RESTORE_FAILED. Falhas posteriores são relatadas pelo supervisor (consulte ciclo de vida da sessão).
CancelamentoAntes/durante o replanejamento: lança exceção. Depois disso, o token é repassado à transação e à criação do processo; um cancelamento nesse ponto é tratado como qualquer falha de início (Failed + OL_E_START_SESSION: The operation was canceled.), o processo (se criado) é encerrado e a instalação é restaurada.
Exige sessão em execuçãoNão.
Altera o estado do OMSISim: cria o processo do OMSI com as variáveis de ambiente OMSILAUNCH_SESSION_ID, OMSILAUNCH_HANDOFF_NAME, OMSILAUNCH_TELEMETRY_NAME, OMSILAUNCH_RUNTIME_CHANNEL, OMSILAUNCH_INTERNET_TEXTURES_MODE.
Altera o sistema de arquivosSim, dentro da raiz da instalação: .omsilaunch\diagnostics\<sessionId>-host.log (retenção: as 50 sessões mais recentes), .omsilaunch\journal.json, .omsilaunch\backup\<sessionId>\*.bin, .omsilaunch\assets\splash\*.bmp (copiado uma vez para o splash gerenciado), overlays da sessão (patches de options.cfg, GUI\NewSplashscreen_*.bmp, Texture\standard.itx), exclusões da sessão (destinos ITX, Texture\standard.ipr, closecheck) e remoção permanente de um closecheck obsoleto preexistente quando SuppressStaleClosecheckWarning é true (diagnóstico closecheck.stale-removed).
Transação / restauraçãoAbre a transação (Prepared → Applied → RuntimeDeployed → HandoffCreated → ProcessStarted). Todo caminho de saída da sessão termina em restauração. Consulte transações e recuperação.
LimitaçõesSomente WorldMode.NewMap com PresentedEntrypointIndex e WorldMode.SavedSituation chegam ao gameplay. WorldMode.LastMapState é UNAVAILABLE. Solicitações de data/hora/clima/veículo do jogador/entrada nunca chegam a este método, porque já são não aptas para execução no momento do planejamento.
EstabilidadeSTABLE_BETA (os ciclos de vida NEW_MAP e SAVED_SITUATION são validados em runtime).
Exemplovar 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#

AspectoDetalhe
AssinaturaTask<SessionStatus> GetStatusAsync(SessionHandle session, CancellationToken cancellationToken = default)
FinalidadeLer o estado semântico do ciclo de vida, os diagnósticos coletados até o momento e a lista limitada de eventos de runtime. Capacidade pública session.status.
Parâmetrossession: um handle retornado por StartSessionAsync e ainda não fechado.
RetornoSessionStatus(SessionId, State, Diagnostics, RuntimeEvents): um snapshot imutável (os arrays são copiados sob o lock da sessão). RuntimeEvents nunca é null para uma sessão ativa.
Exceções lançadasKeyNotFoundException para handles desconhecidos/fechados. Fora isso, nunca lança exceção.
CancelamentoO token é ignorado (a chamada é concluída de forma síncrona).
Exige sessão em execuçãoNão.
Altera OMSI / sistema de arquivos / transaçãoNão / Não / Nenhuma.
EstabilidadeSTABLE_BETA.
Exemplovar status = await launch.GetStatusAsync(session); Console.WriteLine($"{status.State} events={status.RuntimeEvents!.Count}");

WaitForAsync#

AspectoDetalhe
AssinaturaTask<SessionStatus> WaitForAsync(SessionHandle session, SessionState state, TimeSpan timeout, CancellationToken cancellationToken = default)
FinalidadeConsultar periodicamente (a cada 100 ms) até que a sessão esteja em state, ou em um estado terminal (Completed, Failed), ou até que o timeout expire; então retornar o status atual.
Parâmetrosstate: qualquer SessionState. Aguardar um estado transitório que já passou (ou que nunca é definido, consulte ciclo de vida da sessão) aguarda até um estado terminal ou até o timeout. timeout: qualquer TimeSpan não negativo ou Timeout.InfiniteTimeSpan.
RetornoO status no momento em que a espera terminou. No timeout, o status é retornado, não uma exceção: verifique State você mesmo. Uma espera por Running que termina em Failed retorna imediatamente com os diagnósticos da falha.
Exceções lançadasKeyNotFoundException; OperationCanceledException quando o token do chamador é cancelado (somente o cancelamento do chamador se propaga; o timeout interno não).
CancelamentoO token do chamador é respeitado a cada tick de 100 ms.
Exige sessão em execuçãoNão.
Altera OMSI / sistema de arquivos / transaçãoNão / Não / Nenhuma.
EstabilidadeSTABLE_BETA.
Exemplovar running = await launch.WaitForAsync(session, SessionState.Running, TimeSpan.FromSeconds(185)); if (running.State != SessionState.Running) { /* timed out or Failed */ }

StopAsync#

AspectoDetalhe
AssinaturaTask StopAsync(SessionHandle session, CancellationToken cancellationToken = default)
FinalidadeSolicitar a parada canônica. Define a flag de parada e retorna imediatamente; o supervisor observa a flag dentro do seu loop de 100 ms, chama TerminateProcess em Omsi.exe, aguarda a saída, marca o journal como ProcessExited, restaura todo arquivo de propriedade da sessão e libera o lease. Trata-se de um encerramento forçado: a rotina de desligamento do próprio OMSI não é executada e o OMSI não reescreve options.cfg ao sair (deliberado, protege a transação). O desligamento cooperativo via WM_CLOSE não está implementado (decisão de produto; no fechamento de runtime, o OMSI não fechou em até 30 s após WM_CLOSE, L05b). Capacidade pública session.stop.
Parâmetrossession.
RetornoTarefa concluída; não aguarda o encerramento nem a restauração. Use WaitForAsync(session, SessionState.Completed, ...) para observar a conclusão.
Exceções lançadasKeyNotFoundException.
CancelamentoToken ignorado.
Exige sessão em execuçãoNão. Idempotente; uma parada solicitada antes que o supervisor inicie é atendida assim que ele iniciar; uma parada em uma sessão terminal não tem efeito.
Altera o estado do OMSISim: encerra o processo do OMSI (código de saída 1).
Altera o sistema de arquivosIndiretamente: dispara a restauração, a exclusão do journal e a remoção dos backups pelo supervisor.
Transação / restauraçãoDispara ProcessExited → Restoring → Restored. Alterações do lado do runtime feitas por meio de ExecuteRuntimeAsync (escritas no relógio, veículos gerados, variáveis de script, texturas D3D) não são restauradas; elas desaparecem com o processo.
EstabilidadeSTABLE_BETA.
Exemploawait launch.StopAsync(session); var done = await launch.WaitForAsync(session, SessionState.Completed, TimeSpan.FromMinutes(1));

CloseAsync#

AspectoDetalhe
AssinaturaTask CloseAsync(SessionHandle session, CancellationToken cancellationToken = default)
FinalidadeLiberar o handle do consumidor sem deixar a transação abandonada: se a sessão não for terminal, solicitar a parada canônica; em seguida, aguardar a tarefa de ciclo de vida do supervisor (saída do processo, restauração, liberação do lease); depois, esquecer a sessão.
Parâmetrossession.
RetornoÉ concluído quando a sessão está terminal e foi removida. Depois que retorna, o handle é desconhecido (KeyNotFoundException em qualquer chamada posterior, inclusive um segundo CloseAsync).
Exceções lançadasKeyNotFoundException; OperationCanceledException se o chamador cancelar enquanto aguarda o supervisor. Nesse caso, a sessão não é removida e o supervisor continua em execução; chame CloseAsync novamente.
CancelamentoAplica-se somente à espera; nunca cancela a restauração.
Exige sessão em execuçãoNão.
Altera o estado do OMSISim, quando a sessão ainda está ativa (igual a StopAsync).
Altera o sistema de arquivosIndiretamente (restauração pelo supervisor).
Transação / restauraçãoGarante que a transação seja levada até a conclusão antes que o handle seja liberado (quando o supervisor foi iniciado). Para uma sessão que falhou antes do início do supervisor, o caminho de início já restaurou ou relatou OL_E_RESTORE_DEFERRED.
EstabilidadeSTABLE_BETA.
Exemplotry { ... } finally { await launch.CloseAsync(session); }

ExecuteRuntimeAsync#

AspectoDetalhe
AssinaturaTask<RuntimeCommandResult> ExecuteRuntimeAsync(SessionHandle session, RuntimeCommand command, TimeSpan timeout, CancellationToken cancellationToken = default)
FinalidadeExecutar uma operação de runtime pública dentro do processo do OMSI em execução por meio do mailbox da sessão, de voo único (mapeado em memória, 64 KiB, requisição vinculada ao id da sessão e ao id da requisição). O plugin executa a operação no thread de UI do OMSI. Catálogo de operações: controle de runtime e capacidades.
Parâmetroscommand.SessionId deve ser igual a session.SessionId. command.RequestId: ulong escolhido pelo chamador; use um contador estritamente crescente por processo (os helpers de D3D começam em 30 000, o proprietário da CLI em 10 001/50 000). command.Operation: um id de operação pública de PublicCapabilityRegistry.PublicRuntimeOperationIds (por exemplo, time.read, road-vehicle.read, d3d.texture.create). command.Arguments: valores string indexados por nomes ordinais; os nomes obrigatórios por operação vêm de PublicCapabilityRegistry.GetRuntimeArguments. timeout: medido a partir do momento em que a requisição é colocada no mailbox (a espera na fila atrás de outro comando em andamento não é contada). A CLI usa 5 s (15 s para road-vehicles.spawn) como proprietário e 8 s / 30 s como cliente.
Ordem das verificações1. Validação no registro (antes da busca da sessão): operação desconhecida ou internal.* → resultado Succeeded=false, ErrorCode=OL_E_RUNTIME_OPERATION_UNKNOWN; argumento obrigatório ausente (inexistente ou só com espaços em branco) → OL_E_RUNTIME_ARGUMENT_REQUIRED. 2. Busca da sessão → KeyNotFoundException. 3. command.SessionId != session.SessionId → InvalidOperationException("OL_E_RUNTIME_SESSION_MISMATCH"). 4. Estado diferente de Running → InvalidOperationException("OL_E_SESSION_NOT_RUNNING"). 5. Requisição ao mailbox. 6. Valores do resultado cuja chave começa com internal_ ou termina com _address, _pointer, _vmt são removidos.
RetornoRuntimeCommandResult(SessionId, RequestId, Succeeded, ErrorCode, Values). Em caso de sucesso, Values contém as strings semânticas da operação (documentadas por operação em controle de runtime).
Erros no resultadoOL_E_RUNTIME_OPERATION_UNKNOWN, OL_E_RUNTIME_ARGUMENT_REQUIRED (registro); OL_E_RUNTIME_RESPONSE_TOO_LARGE (o resultado do plugin excedeu o mailbox; resultados de listas limitadas são, em vez disso, encurtados com truncated=true); OL_E_RUNTIME_SETTING_NOT_PERSISTENT (weather.set, sempre); todo código OL_E_D3D_* (com Values["detail"] e Values["native_status"]); e OL_E_RUNTIME_OPERATION_FAILED para qualquer outra falha do lado do plugin. Neste último caso, o código específico não está em ErrorCode: ele é o primeiro token de Values["detail"] (por exemplo, detail = "OL_E_RUNTIME_OBJECT_HANDLE_STALE", exception = "InvalidOperationException"). Códigos que chegam dessa forma: OL_E_RUNTIME_OPERATION_UNAVAILABLE, OL_E_RUNTIME_ARGUMENT_REQUIRED (verificações do lado do 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 erro.
Exceções lançadasKeyNotFoundException; InvalidOperationException com OL_E_RUNTIME_SESSION_MISMATCH, OL_E_SESSION_NOT_RUNNING, OL_E_RUNTIME_CHANNEL_CLOSED (mailbox já descartado pelo supervisor), OL_E_RUNTIME_CHANNEL_BUSY (o slot ainda contém uma requisição abandonada), OL_E_RUNTIME_REQUEST_ID_REUSED (uma resposta obsoleta para o mesmo id de requisição ainda está no slot); TimeoutException("OL_E_RUNTIME_REQUEST_TIMEOUT"); InvalidDataException("OL_E_RUNTIME_RESPONSE_INVALID") (resposta corrompida, estranha ou não correspondente); ArgumentOutOfRangeException quando a requisição serializada excede o mailbox; OperationCanceledException.
CancelamentoRespeitado durante a espera pelo gate por sessão e a cada 20 ms durante a consulta periódica da resposta. Cancelar com a requisição em andamento não reinicia o slot: a próxima chamada nessa sessão pode falhar com OL_E_RUNTIME_CHANNEL_BUSY até que o plugin publique sua resposta (que é então descartada como obsoleta). Prefira o timeout; um timeout reinicia o slot, e uma resposta tardia é detectada e descartada.
Exige sessão em execuçãoSim (SessionState.Running); caso contrário, OL_E_SESSION_NOT_RUNNING é lançado. O mailbox existe até que o supervisor o descarte durante a restauração.
Altera o estado do OMSIDepende da operação: operações Read não alteram; operações Write/Action (time.set, camera.set, camera.lock, camera.unlock, road-vehicles.spawn, road-vehicles.place-random, vehicle.variable.set, d3d.texture.*) alteram estado dentro do processo que não é restaurado.
Altera o sistema de arquivosNenhuma escrita pelo host. O OMSI pode escrever seus próprios arquivos como consequência (não rastreado).
Transação / restauraçãoNenhuma.
LimitaçõesUm comando em andamento por sessão (chamadas na mesma sessão são serializadas). Requisição e resposta são limitadas, cada uma, a 64 KiB menos 8 bytes; payloads de pixels D3D, a 48 KiB. internal.road-vehicles.make-basic é INTERNAL e inacessível. weather.set é UNAVAILABLE. timetable.logs.read não é limitada e pode retornar OL_E_RUNTIME_RESPONSE_TOO_LARGE em tabelas de horários grandes. camera.lock é EXPERIMENTAL; exige um veículo do jogador e está validada em runtime (CAM01), embora a string RuntimeValidation do registro ainda indique STATICALLY_VALIDATED. Os handles (rv-NNNNNN, hb-NNNNNN, d3dtex-<session>-<hex>) têm escopo de sessão.
EstabilidadeTransporte e contrato STABLE_BETA; a estabilidade por operação segue PublicCapabilityRegistry (PublicStableBeta → STABLE_BETA, PublicExperimental → EXPERIMENTAL), com as exceções acima.
Exemplovar 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#

AspectoDetalhe
AssinaturaTask<IReadOnlyList<Capability>> GetCapabilitiesAsync(InstallationSpec installation, CancellationToken cancellationToken = default)
FinalidadeRetornar o inventário de evidências do produto para uma instalação: uma lista fixa de entradas Capability(Name, Available, EvidenceState, Reason) mantida em OmsiLaunchService. Somente runtime.current-windows-x64 é calculada (a partir da detecção da plataforma); todas as demais entradas são constantes.
Parâmetrosinstallation.RootPath: diretório usado para a sondagem da plataforma (a possibilidade de escrita exige que o diretório exista, não seja somente leitura e contenha plugins\). ExpectedExecutableSha256 é ignorado.
Retorno51 entradas, por exemplo 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).
Diferença em relação a PublicCapabilityRegistryPublicCapabilityRegistry.All é o catálogo da superfície de controle em tempo de compilação (36 descritores com classificação, tipo, rotas de API e CLI, argumentos obrigatórios) que a API e a CLI aplicam; ele não depende da instalação. GetCapabilitiesAsync é um relatório de evidências de runtime (estado de validação e motivos). Use o registro para decidir o que você pode chamar; use esta lista para decidir o que foi comprovado. Nenhuma das listas é derivada da outra.
Exceções lançadasOperationCanceledException na entrada; ArgumentException para um caminho de raiz vazio.
CancelamentoVerificado uma vez na entrada.
Exige sessão em execuçãoNão.
Altera OMSI / sistema de arquivos / transaçãoNão / Não / Nenhuma.
EstabilidadeContrato da chamada STABLE_BETA; o conteúdo da lista é um inventário mantido manualmente: PARTIAL.
Exemploforeach (var c in await launch.GetCapabilitiesAsync(new InstallationSpec(root))) Console.WriteLine($"{c.Name} {c.Available} {c.EvidenceState} {c.Reason}");

DiscoverAsync#

AspectoDetalhe
AssinaturaTask<IReadOnlyList<ContentIdentity>> DiscoverAsync(InstallationSpec installation, ContentQueryKind query, OptionalValue<string> scope = default, CancellationToken cancellationToken = default)
FinalidadeEnumerar o conteúdo instalado e retornar identidades canônicas utilizáveis em um LaunchSpec. A descoberta ignora pontos de nova análise (reparse points) (ciclos de junções não conseguem travá-la), lê os arquivos do OMSI como Windows-1252 (UTF-8/UTF-16 marcados com BOM são respeitados) e nunca segue links simbólicos.
Parâmetrosquery e scope conforme a tabela abaixo. scope é obrigatório para Entrypoints (identidade do mapa), Repaints, FleetNumbers, Registrations (identidade do veículo).
RetornoLista ordenada de ContentIdentity(Identity, Kind, DisplayName). As identidades são caminhos relativos à instalação com barras invertidas; as comparações não diferenciam maiúsculas de minúsculas.
Exceções lançadasOperationCanceledException na entrada; ArgumentException quando Entrypoints é consultado sem escopo ou a raiz está vazia; FileNotFoundException (sem código OL_E_; a CLI o mapeia para OL_E_NOT_FOUND) quando o mapa ou veículo do escopo não está instalado. Uma raiz ou diretório de conteúdo ausente produz uma lista vazia, não um erro.
CancelamentoVerificado uma vez na entrada.
Exige sessão em execuçãoNão.
Altera OMSI / sistema de arquivos / transaçãoNão / Não / Nenhuma.
EstabilidadeMaps, Situations, Vehicles: STABLE_BETA (todo plano validado em runtime é resolvido por meio deles). Entrypoints, Repaints, Hofs, FleetNumbers, Registrations, Addons: EXPERIMENTAL (somente evidência estática).
Exemplovar 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 e resultados de ContentQueryKind:

ValorEscopoIdentityKindDisplayName
Mapsnenhummaps\<dir>\global.cfgmapnome do diretório do mapa
Situationsnenhumsituations\...\<file>.osnsituationidentidade do mapa referenciado pelo .osn (pode ser null)
VehiclesnenhumVehicles\...\<file>.busvehicle[friendlyname] ou nome do arquivo
Repaintsidentidade do veículo (obrigatória; sem ela: lista vazia)<cti path>#item:<ordinal>repaintnome de [item]
HofsnenhumVehicles\...\<file>.hofhofnull
FleetNumbersidentidade do veículo (obrigatória; sem ela: lista vazia)caminho de origem de [number] relativo ao veículofleet-numbernull
Registrationsidentidade do veículo (obrigatória; sem ela: lista vazia)registration_automatic / registration_list / registration_freeregistrationprimeira linha de valor (null para free)
AddonsnenhumAddons\<dir>addondirectory-only
Entrypointsidentidade do mapa (obrigatória; sem ela: ArgumentException)<map identity>#entrypoint:<SHA-256 of the 12-line record>entrypointrótulo do ponto de entrada

As identidades de ponto de entrada servem somente para descoberta: o caminho de inicialização usa PresentedEntrypointIndex; passar um EntrypointIdentity torna o plano não apto para execução nesta build (world.entrypoint-identity, RUNTIME_PARTIAL).

RecoverPendingAsync#

AspectoDetalhe
AssinaturaTask<RecoveryStatus> RecoverPendingAsync(InstallationSpec installation, bool restore, CancellationToken cancellationToken = default)
FinalidadeRelatar ou concluir uma transação durável obsoleta (<root>\.omsilaunch\journal.json) deixada por um proprietário que travou. Adquire o lease da instalação durante a chamada, para nunca restaurar por baixo de uma sessão que está iniciando. Capacidade pública session.recover; CLI /recovery-status e /recover.
Parâmetrosinstallation.RootPath: a raiz da instalação (normalizada com Path.GetFullPath). restore: false = somente relatar; true = restaurar, verificar, excluir o journal e os backups.
RetornoRecoveryStatus(Pending, Recovered, Diagnostics): Pending = havia um journal quando a chamada começou; Recovered = uma restauração foi solicitada, foi executada e nenhum journal permanece; Diagnostics = notas da restauração (restore.session-artifact-removed com Data["sha256"], OL_W_RESTORE_FOREIGN_FILE_RETAINED), vazio quando nada foi restaurado.
Exceções lançadasInvalidOperationException("OL_E_INSTALLATION_BUSY: another OmsiLaunch owner holds this installation.") quando o lease está ocupado; IOException("OL_E_INSTALLATION_BUSY: a journaled OMSI process is still alive.") quando o PID + horário de criação + caminho do executável registrados no journal ainda correspondem a um processo ativo ou (journal além de HandoffCreated sem PID) quando qualquer Omsi.exe dessa raiz está em execução; IOException com OL_E_RECOVERY_BACKUP_CORRUPT, OL_E_RECOVERY_ABSENT_OWNERSHIP_MISMATCH, OL_E_RECOVERY_ABSENT_OWNERSHIP_UNVERIFIED, OL_E_RECOVERY_JOURNAL_REMOVE_FAILED ou uma mensagem de verificação ("Restore hash mismatch: ...", "Restore presence mismatch: ..."); InvalidDataException("Invalid OmsiLaunch journal.") / JsonException para um journal corrompido; ArgumentException para uma raiz vazia; OperationCanceledException. Sempre que lança exceção depois que uma restauração começou, o journal é mantido e a próxima chamada reexecuta de forma idempotente.
CancelamentoRepassado às escritas do journal/backup; cancelar no meio da restauração deixa o journal pendente.
Exige sessão em execuçãoNão (recusa enquanto um proprietário está ativo).
Altera o estado do OMSINão.
Altera o sistema de arquivosSomente com restore == true: reescreve os originais a partir de backups verificados (bytes, horário da última escrita, horário de criação, atributos; originais somente leitura são tratados; write-through + flush; nenhum *.omsilaunch.tmp é deixado), remove os artefatos da sessão, exclui journal.json e backup\<sessionId>.
Transação / restauraçãoConclui a transação pendente (Restoring → Restored → journal removido).
EstabilidadeSTABLE_BETA: caminho de relatório e recuperação após saída antecipada (matriz RV-008), recuperação após uma restauração com falha (fechamento de runtime F01), recusa sob um proprietário ativo e na janela anterior ao PID (S04) e recuperação adiada anterior à impressão digital no início (S05); consulte status da validação em runtime.
Exemplovar r = await launch.RecoverPendingAsync(new InstallationSpec(root), restore: true); Console.WriteLine($"pending={r.Pending} recovered={r.Recovered}");

Tipos de contrato#

Valores opcionais e primitivos semânticos#

TipoDefiniçãoObservações
OptionalValue<T>readonly record struct OptionalValue<T>(Presence Presence, T? Value); IsSet, Unset estático, Set(T) estáticoDistingue "não solicitado" de "solicitado com um valor". O formato JSON está documentado na referência do LaunchSpec.
PresenceUnset = 0, Set = 1Enum de byte.
SemanticDate(int Year, int Month, int Day)Validado somente quando DateTimeMode.Explicit (mês 1..12, dia 1..31).
SemanticTime(int Hour, int Minute, int Second)Validado somente quando DateTimeMode.Explicit (0..23, 0..59, 0..59).

Família LaunchSpec#

Todos os records abaixo estão documentados propriedade por propriedade na referência do LaunchSpec; esta tabela fixa o inventário de tipos.

TipoFinalidadeEstabilidade
LaunchSpecRecord raiz da requisição com os acessores EffectiveYear, EffectiveWeather, EffectiveInput, EffectiveDiagnostics, EffectivePresentation, EffectiveInternetTextures, que substituem membros opcionais null por valores padrão.STABLE_BETA
InstallationSpecRootPath, ExpectedExecutableSha256 (transportado, não consumido).STABLE_BETA / PARTIAL
WorldSpec, WorldMode, EntrypointSpec, EntrypointModeSeleção do mundo. WorldMode: NewMap = 0, SavedSituation = 1, LastMapState = 2, LastSituation = 2 (alias obsoleto de LastMapState; nunca "o .osn mais recente"). EntrypointMode: Unset, PresentedIndex, Identity (calculado a partir de WorldSpec.Entrypoint).NewMap, SavedSituation: STABLE_BETA; LastMapState: UNAVAILABLE; EntrypointMode.Identity: PARTIAL
DateSpec, TimeSpec, YearSpec, DateTimeModeDateTimeMode: Unset, Explicit, System. Qualquer modo diferente de Unset torna o plano não apto para execução.PARTIAL (STATICALLY_PARTIAL)
WeatherSpec, WeatherModeWeatherMode: Unset, Preset, Icao, RealCurrent. Qualquer modo diferente de Unset torna o plano não apto para execução.PARTIAL
PlayerVehicleSpecModel, Repaint, Hof, FleetNumber, Registration, Enabled. Qualquer campo definido torna o plano não apto para execução.PARTIAL
EnvironmentSpecOito grupos IReadOnlyDictionary<string, OptionalValue<string>> de configurações semânticas de options.cfg.STABLE_BETA
InputSpecKeyboardDocument, ControllerDocument; qualquer valor definido torna o plano não apto para execução.PARTIAL
DiagnosticsSpecSeis booleanos; transportados, não consumidos.PARTIAL
SessionPresentationSpec, SplashModeSplashMode: Unset = 0, Native = 0 (alias), Managed = 1.STABLE_BETA
InternetTexturesSpec, InternetTexturesModeInternetTexturesMode: Native, Disabled, Override.STABLE_BETA (Native), EXPERIMENTAL (Disabled, Override)
SessionProfileMetadataProcedência de um perfil de sessão compilado (Id, Name, Version, Author, PresetId, PresetIndex, PresetName, PackagePath).STABLE_BETA
LaunchBehaviorSpecRestoreConfiguration (transportado; a restauração sempre acontece), SuppressStaleClosecheckWarning, StartupTimeoutSeconds (1..600, padrão 180), ShutdownTimeoutSeconds (transportado, não consumido).STABLE_BETA / PARTIAL

Tipos de plano e de status#

TipoCamposObservações
SessionPlanSessionId (novo Guid por plano), BuildProfileId ("Omsi23004_692EBFBF"), Spec, Platform (RuntimePlatformInfo), ResolvedContent (lista de ContentIdentity: map, vehicle, repaint, hof, situation, situation-map), TouchedFiles (caminhos relativos distintos de PlannedMutations), RuntimeArtifacts, RequiredCapabilities (Capability com STATICALLY_VALIDATED ou UNAVAILABLE), UnsupportedRequestedFeatures (entradas Capability para recursos solicitados, mas não suportados), PlannedMutations, Diagnostics, IsRunnable.Um record público: pode ser editado ou ficar desatualizado, e é por isso que StartSessionAsync replaneja.
RuntimePlatformInfoOsFamily, OsVersion, OsArchitecture, HostArchitecture, OmsiArchitecture (X86), PluginArchitecture (X86), CurrentPlatformSupported (Windows 10+, SO x64 e host x64), LegacyPlatform (sempre false), Wow64Available, InstallationWritable, ProcessLaunchSupported, PluginRuntimeSupported, NativeInteropSupported, SharedMemorySupported, ExactRestoreSupported (todos iguais a CurrentPlatformSupported).
CapabilityName, Available, EvidenceState, Reason.As strings de evidência são texto livre (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).Mutações de apresentação usam estas chaves: session-presentation.splash, internet-textures.override, internet-textures.cache, internet-textures.target.
LaunchDiagnosticCode, Message, Data (mapa de strings opcional).Códigos que começam com OL_E_ são erros, OL_W_ são avisos e qualquer outro é informativo.
SessionStatusSessionId, State (SessionState), Diagnostics, RuntimeEvents.Os diagnósticos da sessão não incluem os diagnósticos do plano.
RuntimeEventType, TimestampUtc (horário de recebimento pelo host), Sequence (começando em 1, por sessão), Data.Limitado aos 256 eventos mais recentes (os mais antigos são descartados). O slot de telemetria guarda apenas o valor mais recente: eventos emitidos mais rápido do que a consulta periódica de 100 ms do host podem ser perdidos. Não é um log sem perdas.
SessionHandleSessionId.Local ao processo.
RecoveryStatusPending, Recovered, Diagnostics.Consulte RecoverPendingAsync.
ContentIdentityIdentity, Kind, DisplayName.Consulte DiscoverAsync.
ContentQueryKindMaps, Situations, Vehicles, Repaints, Hofs, FleetNumbers, Registrations, Addons, Entrypoints.

SessionState#

Enum de byte na ordem de declaração: Created, ValidatingPlatform, Planning, AcquiringInstallationLock, RecoveringPreviousTransaction, Snapshotting, ApplyingConfiguration, DeployingRuntime, CreatingStartupHandoff, StartingProcess, WaitingForPlugin, PluginBootstrap, StartingWorld, EnteringGameplay, Running, ProcessExited, Restoring, CleaningRuntime, Completed, Failed. ValidatingPlatform, Planning e EnteringGameplay nunca são definidos pelo serviço atual; Snapshotting é transitório e, na prática, não observável. Estados terminais: Completed, Failed. Semântica completa: ciclo de vida da sessão.

Tipos de controle de runtime#

TipoDefiniçãoEstabilidade
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
RuntimeCommandWireCodec estático usado pelo host e pelo plugin para o envelope do mailbox: magic 0x4F4C5243 ("OLRC"), versão 1, cabeçalho little-endian de 72 bytes (magic, versão, tipo 1 = requisição / 2 = resposta, comprimento total, Guid da sessão, id da requisição, comprimento do payload, SHA-256 do payload) seguido de um payload JSON em UTF-8. SerializeRequest, SerializeResponse, TryDeserializeRequest, TryDeserializeResponse, TryReadRequestId.INTERNAL: público porque as duas extremidades da ponte o compartilham; não é uma superfície de integração; o formato pode mudar com a versão do protocolo.
StartupHandoff(Guid SessionId, string BuildProfileId, WorldMode WorldMode, string MapIdentity, int PresentedEntrypointIndex, bool HeadlessStart, bool PlayerVehicleEnabled, DateTimeMode DateMode, DateTimeMode TimeMode, string EntrypointIdentity, string SituationIdentity) — o que o host publica para o plugin no arquivo mapeado em memória OmsiLaunch.Handoff.<sessionId>.INTERNAL
StartupHandoffWireCodec: magic 0x4F4C5348, versão 4 (lê 3 e 4), cabeçalho de 64 bytes com integridade do payload por SHA-256.INTERNAL

O plugin rejeita um handoff (plugin.request.unsupported → OL_E_CAPABILITY_UNAVAILABLE), a menos que WorldMode seja NewMap ou SavedSituation, HeadlessStart seja true, PlayerVehicleEnabled seja false, ambos os modos de data/hora sejam Unset e uma situação salva tenha uma identidade não vazia. O planejador aplica as mesmas restrições antes, portanto um plano apto para execução nunca provoca essa rejeição.

Tipos do registro de capacidades#

TipoFinalidade
PublicCapabilityRegistryProtocolVersion ("0.1"), All (36 entradas PublicCapabilityDescriptor), PublicRuntimeOperationIds (os 48 ids de operação concretos que um frontend pode encaminhar), IsPublicRuntimeOperation, GetRuntimeArguments, ValidateRuntimeArguments (retorna PublicRuntimeArgumentValidation), IsInternalResultKey. Aplicado por ExecuteRuntimeAsync, pela CLI e pelo plano de controle 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 extensão D3DRuntimeApi#

Wrappers tipados sobre ExecuteRuntimeAsync para as operações d3d.* (EXPERIMENTAL, capacidade PublicExperimental d3d.texture). Eles alocam ids de requisição a partir de um contador global do processo que começa em 30 000 e usam 5 s como timeout padrão (exceto GetD3DStatusAsync, que exige um timeout).

MétodoOperaçãoArgumentos e limites
GetD3DStatusAsync(IOmsiLaunch, SessionHandle, TimeSpan timeout, CancellationToken) → D3DDeviceStatusd3d.statusnenhum
CreateD3DTextureAsync(..., uint width, uint height, D3DTextureFormat format, uint levels = 1, TimeSpan? timeout, ...) → D3DTextureDescriptiond3d.texture.createlargura/altura 1..4096, níveis 0..16
DescribeD3DTextureAsync(..., D3DTextureHandle handle, uint level = 0, ...)d3d.texture.describenível 0..15
UpdateD3DTextureAsync(..., D3DTextureHandle handle, D3DTextureUpdate update, ...)d3d.texture.updateD3DTextureUpdate(Level, X, Y, Width, Height, Pixels): x/y 0..4095, largura/altura 1..4096, pixels ≤ 48 KiB (codificados em Base64 na transmissão)
ReleaseD3DTextureAsync(..., D3DTextureHandle handle, ...)d3d.texture.releaseuma liberação repetida é rejeitada com 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) com 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.

Erros: um resultado com falha é relançado como OmsiRuntimeException(Code, detail), em que Code é o ErrorCode do resultado (ou OL_E_RUNTIME_OPERATION_FAILED quando ausente) e a mensagem é "<code>: <Values["detail"]>"; um resultado bem-sucedido sem valores, ou uma string de estado do dispositivo desconhecida, lança OmsiRuntimeException("OL_E_RUNTIME_PROTOCOL_MISMATCH", ...). Tudo o que ExecuteRuntimeAsync lança se propaga sem alteração. O tratamento do reset do dispositivo está validado em runtime: um reset leva o dispositivo por Resetting de volta a Ready e invalida toda textura ativa (OL_E_D3D_STALE_RESOURCE_HANDLE, fechamento de runtime D01); GetCapabilitiesAsync ainda relata runtime.d3d.lifecycle.reset como IMPLEMENTED_NOT_RUNTIME_VALIDATED (defasagem do autorrelato). A transição para Lost não pode ser produzida de fora do produto e é coberta somente 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 de contrato de processo#

TipoConteúdo
PublicExitCodeSuccess = 0, SessionFailed = 1, InvalidArguments = 2, UnsupportedProfile = 3, NoActiveSession = 4, RuntimeUnavailable = 5, NotFound = 6, OperationRejected = 7, TransactionRecoveryFailed = 8, InternalError = 10. Usado somente pela CLI (códigos de saída); a API nunca encerra o processo.
PublicErrorCategoryConstantes string usadas nos envelopes de erro da CLI/controle: invalid_argument, unsupported_profile, session, runtime, not_found, transaction, internal.
PublicErrorCodesUma const string por código (143: 142 erros OL_E_ e 1 aviso OL_W_) e All, o catálogo PublicErrorDescriptor(Code, Category). Categorias: Cli, Compatibility, Content, Installation, InvalidArgument, LaunchSpec, LocalControl, Other, Presentation, Process, Runtime, RuntimeD3D, Session, SessionProfile, Transaction, Warning. Referência: códigos de erro.
PublicErrorDescriptor(string Code, string Category).
OmsiRuntimeExceptionPropriedade Code mais a mensagem; lançada somente por D3DRuntimeApi.

InstallationPaths (identidade da instalação e contenção de caminhos)#

Estabilidade: STABLE_BETA (funções puras, sem E/S, sem estado do OMSI, sem alteração do sistema de arquivos, sem participação em transação, sem exigir sessão em execução). É a definição única usada pelo lease da instalação, pelo nome do pipe de controle local, pelo confinamento de assets do perfil de sessão, pela validação de destinos das texturas da internet e pelo caminho do modelo para spawn em runtime.

MembroComportamento
string NormalizeRoot(string root)Path.GetFullPath(root) sem separadores finais, exceto na raiz de uma unidade (C:\), que é mantida. Resolve segmentos . e .., trata / e \ da mesma forma e reduz separadores repetidos. Não resolve junções nem links simbólicos. Lança ArgumentException para uma raiz nula ou em branco.
string IdentityKey(string root)NormalizeRoot(root) em maiúsculas. Grafias lexicalmente equivalentes de uma mesma raiz (C:\OMSI, C:\OMSI\, C:\OMSI\., C:\foo\..\OMSI, c:\omsi) compartilham uma chave; raízes diferentes (C:\OMSI-A, C:\OMSI-B) nunca compartilham.
bool TryGetContainedRelativePath(string root, string candidate, out string relativePath)Resolve candidate (relativo a root, ou absoluto) e retorna true somente quando ele está estritamente abaixo de root; relativePath é a grafia canônica com \. Usa os segmentos de Path.GetRelativePath, de modo que um irmão como C:\OMSI-A\x nunca está dentro de C:\OMSI; a própria raiz, outros volumes e escapes com .. retornam false.
IReadOnlyList<string> Segments(string relativePath)Divide em / e \, descartando segmentos vazios.
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"

Perfis de sessão (OmsiLaunch.Core)#

Estabilidade: EXPERIMENTAL. Estes tipos compilam um perfil de sessão YAML (<root>\.omsilaunch\session-profiles\<id>\profile.yaml, schema omsilaunch.session-profile/v1) em um LaunchSpec. O comando da CLI /predefined-profile:<id> /predefined-profile-index:<n> usa exatamente estas chamadas; um integrador pode usá-las para iniciar um perfil pela API.

MembroComportamento
SessionProfileCompiler.Load(string installationRoot, string id, int presetIndex) → SessionProfilePackageLê e valida o pacote. id deve ser um nome de diretório simples (caso contrário, OL_E_SESSION_PROFILE_PATH_ESCAPE); presetIndex é 1..5 (OL_E_SESSION_PROFILE_PRESET_NOT_FOUND). Arquivo ausente: OL_E_SESSION_PROFILE_NOT_FOUND; maior que MaxBytes (256 KiB), YAML inválido, âncoras, chaves desconhecidas ou um id diferente do nome do diretório: OL_E_SESSION_PROFILE_INVALID; outro schema: OL_E_SESSION_PROFILE_SCHEMA_UNSUPPORTED. Os caminhos de assets ficam confinados ao pacote (OL_E_SESSION_PROFILE_PATH_ESCAPE, OL_E_SESSION_PROFILE_ASSET_MISSING). Toda falha é uma SessionProfileException.
SessionProfileCompiler.Apply(SessionProfilePackage profile, LaunchSpec baseline, WorldMode selectedWorldMode) → LaunchSpecRetorna baseline com o perfil aplicado: para NewMap, o bloco new do perfil (mapa, ponto de entrada e qualquer data/hora/ano/clima, que tornam o plano não apto para execução nesta build); as settings da predefinição mescladas sobre Environment.General; Presentation, InternetTextures e Behavior da predefinição, quando presentes; e SessionProfile = profile.Metadata. Para NewMap, também verifica o mapa em relação à lista compatibility do perfil (OL_E_SESSION_PROFILE_MAP_MISMATCH).
SessionProfileCompiler.ValidateCompatibility(SessionProfilePackage, string installationRoot, WorldSpec world, WorldMode mode)A verificação de compatibilidade para os demais modos (para SavedSituation, o mapa é lido do .osn). A CLI a chama depois que o WorldSpec final é construído.
SessionProfileCompiler.Schema, MaxBytes, SchemaKeys"omsilaunch.session-profile/v1", 262144 e as chaves aceitas por mapeamento YAML.
SessionProfilePackage(RootPath, Metadata, CompatibleMaps, New, Preset), ProfileNew, ProfilePresetO pacote carregado; Preset é somente a predefinição selecionada.
SessionProfileException(string code, string message)IOException com Code (um dos códigos OL_E_SESSION_PROFILE_*); a mensagem é "<code>: <message>".

A CLI também rejeita flags de linha de comando que conflitam com o perfil (OL_E_SESSION_PROFILE_OVERRIDE_CONFLICT); essa verificação não faz parte do compilador. Consulte perfis de sessão para a ordem completa de mesclagem.

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

TipoEstabilidadeUso
IRuntimePlatformSTABLE_BETA como tipo do parâmetro do construtor de OmsiLaunchServiceDetecta a plataforma, verifica a possibilidade de escrita, inicia, observa, encerra e aguarda Omsi.exe. Passe new CurrentWindowsX64Platform(); implementá-lo por conta própria não é suportado.
CurrentWindowsX64PlatformSTABLE_BETAA única implementação: host Windows x64, CreateProcessW para Omsi.exe, TerminateProcess para a parada canônica. Seus métodos são chamados pelo serviço; integradores apenas o constroem. Membros (compartilhados com IRuntimePlatform): Detect(root) retorna o RuntimePlatformInfo do plano; ValidateCurrent(info) lança OL_E_UNSUPPORTED_OPERATING_SYSTEM / OL_E_UNSUPPORTED_OS_ARCHITECTURE / OL_E_PLATFORM_CAPABILITY_MISSING quando o host não consegue executar uma sessão; IsInstallationWritable(root) fundamenta OL_E_INSTALLATION_NOT_WRITABLE; StartAsync(request, sha256) cria Omsi.exe e registra a identidade do processo (PID, horário de criação, caminho e o hash calculado pelo serviço; OL_E_PROCESS_START_FAILED, OL_E_PROCESS_CREATION_TIME_FAILED); HasExited, WaitForExitAsync e Terminate o observam e o encerram.
InstallationLease, LaunchedProcess, ProcessIdentity, ReleaseManifest, RuntimeArtifact, RuntimeArtifactSet, StartupProcessRequest, CurrentRuntimeCommandStore, IOmsiProcessController, OmsiProcessStateINTERNALPúblicos no assembly porque o serviço e os testes os compartilham. Não são uma superfície de integração; LaunchedProcess encapsula internamente os handles de processo e de thread do OMSI (eles não são membros públicos) e nunca é retornado por IOmsiLaunch.

Segurança de threads#

  • OmsiLaunchService é seguro para chamadas simultâneas em sessões diferentes: as sessões ficam em um ConcurrentDictionary e toda alteração por sessão acontece sob o lock privado da sessão.
  • Chamadas simultâneas na mesma sessão são seguras, mas serializadas onde isso importa: ExecuteRuntimeAsync adquire um gate por sessão, de modo que um segundo comando aguarda o primeiro (seu timeout começa quando ele é colocado no mailbox).
  • O supervisor é executado em uma tarefa do pool de threads (Task.Run) desde o momento em que StartSessionAsync retorna até que a sessão seja terminal; ele consulta a telemetria e o processo a cada 100 ms. Os chamadores nunca executam código do supervisor.
  • StopAsync e GetStatusAsync são concluídos de forma síncrona e podem ser chamados de qualquer thread, inclusive dentro de um handler de ProcessExit (a CLI faz isso com um orçamento de 4 s).
  • Nenhuma chamada da API tem afinidade de thread; nenhuma exige um contexto de sincronização.

O que não está na API#

  • Nenhum IntPtr, nint, handle Win32, endereço nativo, ponteiro de VMT ou objeto de processo. Valores de resultado cujas chaves começam com internal_ ou terminam com _address, _pointer, _vmt são removidos antes que um resultado saia de ExecuteRuntimeAsync.
  • Nenhuma operação de runtime internal.*: internal.road-vehicles.make-basic é InternalOnly no registro e retorna OL_E_RUNTIME_OPERATION_UNKNOWN pela API e pela CLI.
  • Nenhuma leitura ou escrita bruta da memória do OMSI, nenhum acesso em nível de arquivo à instalação além do que um LaunchSpec declara.
  • Nenhum handle entre processos: o plano de controle local é a única rota entre processos, e ele aceita somente session.status, session.events, session.stop e runtime.execute.
  • Nenhum desligamento cooperativo do OMSI, nenhum LAST_MAP_STATE, nenhuma aplicação de data/hora/clima/veículo do jogador, nenhum overlay de documento de teclado/controle nesta build.

Resumo de estabilidade#

SuperfícieEstabilidade
Construtor de OmsiLaunchService, OmsiLaunchRuntimePathsSTABLE_BETA
PlanSessionAsync, StartSessionAsync (NEW_MAP, SAVED_SITUATION), GetStatusAsync, WaitForAsync, StopAsync, CloseAsyncSTABLE_BETA
Transporte de ExecuteRuntimeAsync; operações PublicStableBetaSTABLE_BETA
Operações PublicExperimental, D3DRuntimeApi, camera.lockEXPERIMENTAL
Conteúdo da lista de GetCapabilitiesAsync, membros de especificação de data/hora/clima/veículo do jogador/entrada, DiagnosticsSpec, ExpectedExecutableSha256, RestoreConfiguration, ShutdownTimeoutSecondsPARTIAL
RuntimeCommandWire, StartupHandoff, StartupHandoffWire, implementações de IRuntimePlatform, todos os assemblies de implementaçãoINTERNAL
WorldMode.LastMapState / LastSituation, weather.set, operações internal.*UNAVAILABLE