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: em caso de divergência, prevalecem a página em inglês e o código.
Esta página é a referência normativa da API pública gerida do OmsiLaunch 0.1.0-beta3: o assembly OmsiLaunch.Api (contratos) e o ponto de entrada para integradores OmsiLaunchService em OmsiLaunch.Core. Documenta apenas o que o código atual faz. Tudo o que um integrador pode chamar, receber ou observar está aqui listado com o respetivo nível de estabilidade; o que não estiver listado não é uma superfície de integração.
O inventário da API pública gerado lista todos os tipos e membros públicos de OmsiLaunch.Api, OmsiLaunch.Core e OmsiLaunch.Process com a respetiva assinatura e estabilidade; um gate de documentação (verificação obrigatória) falha quando o inventário e os assemblies divergem. Esta página explica a semântica.
O 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.
EXPERIMENTAL
Pode ser chamado e está testado, mas o contrato ou a evidência de runtime podem mudar antes de se tornar estável.
PARTIAL
Presente no contrato; apenas parte do comportamento está implementada ou validada (o texto indica qual parte).
INTERNAL
Público no assembly por razões técnicas (a ponte partilha o tipo), mas não é uma superfície de integração; pode mudar sem aviso.
UNAVAILABLE
Presente no contrato, mas rejeitado pela build atual.
Contratos puros: records, enums, IOmsiLaunch, registo de capacidades, catálogo de erros, formatos de transmissão, auxiliares D3D. Não contém nenhum IntPtr, nint, handle Win32, endereço nativo nem objeto de processo.
OmsiLaunch.Core
OmsiLaunchService (a implementação de IOmsiLaunch), OmsiLaunchRuntimePaths, SessionPlanner, LaunchValidation, SessionProfileCompiler.
OmsiLaunch.Process
IRuntimePlatform e CurrentWindowsX64Platform (o único adaptador de plataforma), InstallationLease. Necessários para construir o serviço.
Assemblies de implementação. Os seus tipos públicos são INTERNAL para os 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âmetro
Valor válido
Inválido / predefinição
platform
new CurrentWindowsX64Platform() (namespace OmsiLaunch.Process). Deteta a plataforma, cria o processo do OMSI com CreateProcessW, aguarda-o e termina-o.
Não é distribuída nenhuma outra implementação. Um IRuntimePlatform personalizado é INTERNAL.
PluginBuildDirectory
Diretório que contém os ficheiros de referência do fecho (closure) do plugin permanente: OmsiLaunch.Plugin.opl, OmsiLaunch.PluginNE.dll, OmsiLaunch.Plugin.deps.json, OmsiLaunch.Plugin.runtimeconfig.json e todos os OmsiLaunch.*.dll do fecho gerido (deve incluir OmsiLaunch.Plugin.dll). Num pacote instalado, é <package>\plugins.
Diretório ou ficheiro em falta: PlanSessionAsync devolve um plano não executável com OL_E_RUNTIME_ARTIFACT_MISSING.
NativeBridgePath
Caminho de OmsiLaunch.Native.x86.dll (no pacote: <package>\plugins\OmsiLaunch.Native.x86.dll).
Igual ao anterior.
ReleaseManifestPath
release-manifest.json ao lado de OmsiLaunch.exe, quando existe. Fornece o SHA-256 esperado de cada ficheiro de plugins/ (plugin.integrity.reference = manifest).
null (estrutura de desenvolvimento): os ficheiros instalados são verificados apenas quanto à presença e à coerência interna face ao fecho de referência (plugin.integrity.reference = self). Manifesto malformado: OL_E_RELEASE_MANIFEST_INVALID.
O serviço lê estes caminhos em cada PlanSessionAsync e StartSessionAsync; nunca copia, prepara nem remove ficheiros do plugin (ver 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));
Deve criar-se um serviço por processo e partilhá-lo. Estabilidade: STABLE_BETA.
StartSessionAsync adquire a concessão (lease) da instalação, um semáforo nomeado Local\OmsiLaunch.Installation.<SHA-256 of the upper-cased full root path>, e mantém-na até o supervisor ter restaurado a instalação. Um segundo arranque na mesma raiz, a partir de qualquer processo da mesma sessão de início de sessão (logon), falha com OL_E_INSTALLATION_BUSY (comunicado como uma sessão Failed, ver StartSessionAsync). A concessão é por sessão de logon, não abrange várias sessões de logon, e não é libertada enquanto outro processo mantiver um handle para ela (risco aceite).
Os handles são locais ao processo
SessionHandle encapsula o Guid da sessão. Só tem significado para a instância de OmsiLaunchService que o devolveu. Um handle construído a partir de um Guid conhecido noutro processo (ou noutra instância do serviço) produz KeyNotFoundException. O controlo entre processos passa pelo plano de controlo local, não pelos handles.
Chamar sempre CloseAsync
A partir de StartSessionAsync, o processo é proprietário de uma transação durável. CloseAsync pede a paragem canónica quando necessário, aguarda o supervisor (saída do processo, restauro exato, libertação da concessão) e esquece a sessão. Deve ser chamado em todos os caminhos de saída, incluindo após um estado Failed. Sem ele, a entrada da sessão permanece em memória; o restauro em si é efetuado pelo supervisor de qualquer forma.
As sessões falhadas continuam a ser sessões
Um arranque que falha depois de StartSessionAsync ter retornado comunica SessionState.Failed; o handle continua válido para GetStatusAsync/WaitForAsync até CloseAsync.
Os planos são verificados novamente
StartSessionAsync volta a calcular o hash de Omsi.exe e volta a planear a especificação; um plano que já não seja executável é rejeitado com OL_E_PLAN_NOT_RUNNABLE.
Handles desconhecidos ou já fechados lançam KeyNotFoundException ("Unknown OmsiLaunch session.").
Nenhum método exige uma sessão em execução (Running), exceto ExecuteRuntimeAsync.
As exceções que transportam um código OmsiLaunch colocam o código no início de Exception.Message ("OL_E_PLAN_NOT_RUNNABLE: ..."). A CLI extrai os códigos das mensagens da mesma forma (CliProgram.Classify).
Build suportada: apenas Omsi23004_692EBFBF (mais o hash da lista de permissões Steam LAA, aceite; jogabilidade não validada, pois requer uma instalação Steam genuína). Ver compatibilidade.
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
}
Compilar um LaunchSpec num SessionPlan sem iniciar o OMSI: validar a especificação, detetar a plataforma, obter a impressão digital de Omsi.exe, resolver as identidades de conteúdo, calcular as mutações de ficheiros planeadas, listar as capacidades necessárias e não suportadas e decidir IsRunnable. Capacidade pública session.plan.
Parâmetros
spec: um LaunchSpec totalmente preenchido (ver referência do LaunchSpec). Installation, World, Date, Time, Environment (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 é registada como OL_E_INSTALLATION_NOT_FOUND, mas a sondagem da plataforma num caminho vazio lança ArgumentException antes de o plano ser devolvido, pelo que nunca se deve passar uma raiz vazia.
Devolve
SessionPlan com um SessionId novo, BuildProfileId = "Omsi23004_692EBFBF" (sempre esta 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 por OL_E_. Os diagnósticos informativos (plugin.integrity.reference com a mensagem self ou manifest, session_profile.selected) nunca tornam um plano não executável.
Erros transportados no resultado
Todos os erros de planeamento são diagnósticos, não exceções: 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 transporta o código de splash/ITX), OL_E_PERMANENT_PLUGIN_MISSING, OL_E_PERMANENT_PLUGIN_HASH_MISMATCH, OL_E_PERMANENT_PLUGIN_MANIFEST_INCOMPLETE (o fecho do plugin instalado em plugins\ é verificado face ao manifesto de lançamento no momento do planeamento), OL_E_RUNTIME_ARTIFACT_MISSING (a mensagem pode transportar OL_E_RELEASE_MANIFEST_INVALID). Condições completas: regras de validação do LaunchSpec.
Lançadas
OperationCanceledException se o token já estiver cancelado à 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 lançamento sintaticamente inválido.
Cancelamento
Verificado uma vez à entrada. Depois disso, o planeamento é trabalho síncrono no sistema de ficheiros.
Requer sessão em execução
Não.
Altera o estado do OMSI
Não.
Altera o sistema de ficheiros
Não (lê Omsi.exe, ficheiros de conteúdo, o fecho do plugin e o manifesto). Os valores das definições não são validados aqui (apenas a existência da chave e a possibilidade de escrita); um valor inválido falha no arranque com OL_E_INVALID_SETTING_VALUE.
Transação / restauro
Nenhum.
Limitações
Pedir 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 executável nesta build (entradas STATICALLY_PARTIAL / UNSUPPORTED_FOR_CURRENT_PROFILE em UnsupportedRequestedFeatures).
Estabilidade
STABLE_BETA.
Exemplo
var plan = await launch.PlanSessionAsync(spec); Console.WriteLine(plan.IsRunnable ? "READY" : string.Join(", ", plan.Diagnostics.Where(d => d.Code.StartsWith("OL_E_")).Select(d => d.Code)));
Iniciar uma sessão gerida e transacional do OMSI a partir de um plano executável: adquirir a concessão da instalação, recuperar um journal obsoleto, validar o fecho do plugin permanente, fazer snapshot e overlay dos ficheiros da sessão, criar o handoff de arranque, o slot de telemetria e a mailbox de runtime (caixa de correio), iniciar Omsi.exe, registar o processo no journal e entregar a sessão a um supervisor em segundo plano. Capacidade pública session.start.
Parâmetros
plan: um SessionPlan com IsRunnable == true. A especificação dentro do plano é planeada novamente; do plano do chamador só se mantém plan.SessionId. plan.Spec.Behavior.StartupTimeoutSeconds tem de estar entre 1..600.
Devolve
SessionHandle(plan.SessionId) logo que Omsi.exe tenha sido criado e registado (estado WaitingForPlugin), ou logo que o caminho de arranque tenha falhado (estado Failed). Não aguarda pela jogabilidade; deve usar-se WaitForAsync(session, SessionState.Running, ...).
Lançadas
InvalidOperationException("OL_E_PLAN_NOT_RUNNABLE") quando plan.IsRunnable é falso; InvalidOperationException("OL_E_PLAN_NOT_RUNNABLE: <codes>") quando o novo planeamento não é executável (por exemplo, Omsi.exe foi alterado, conteúdo removido, fecho do plugin em falta); InvalidOperationException("Duplicate session id.") quando uma sessão com o mesmo id ainda está registada (chamar primeiro CloseAsync); ArgumentOutOfRangeException quando StartupTimeoutSeconds está fora de 1..600; OperationCanceledException quando cancelado antes ou durante o novo planeamento; e tudo o que PlanSessionAsync lança. Em todos os casos com exceção, nenhuma sessão é registada.
Erros transportados no resultado
Qualquer falha após o novo planeamento é capturada dentro do caminho de arranque: a sessão é registada, o seu estado é Failed e os seus diagnósticos contêm OL_E_START_SESSION, cuja mensagem é a mensagem interna (a começar pelo código interno, quando existe): OL_E_INSTALLATION_BUSY (concessão detida ou um processo OMSI registado 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 (apenas quando a nova tentativa diferida 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. As falhas posteriores são comunicadas pelo supervisor (ver ciclo de vida da sessão).
Cancelamento
Antes/durante o novo planeamento: lança exceção. Depois disso, o token é passado à transação e à criação do processo; um cancelamento nessa fase é tratado como qualquer falha de arranque (Failed + OL_E_START_SESSION: The operation was canceled.), o processo (se tiver sido criado) é terminado e a instalação restaurada.
Requer sessão em execução
Não.
Altera o estado do OMSI
Sim: 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 ficheiros
Sim, 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 gerido), overlays da sessão (patches de options.cfg, GUI\NewSplashscreen_*.bmp, Texture\standard.itx), eliminações da sessão (alvos ITX, Texture\standard.ipr, closecheck) e remoção permanente de um closecheck obsoleto preexistente quando SuppressStaleClosecheckWarning é verdadeiro (diagnóstico closecheck.stale-removed).
Transação / restauro
Abre a transação (Prepared → Applied → RuntimeDeployed → HandoffCreated → ProcessStarted). Todos os caminhos de saída da sessão terminam em restauro. Ver transações e recuperação.
Limitações
Apenas WorldMode.NewMap com PresentedEntrypointIndex e WorldMode.SavedSituation chegam à jogabilidade. WorldMode.LastMapState é UNAVAILABLE. Os pedidos de data/hora/meteorologia/veículo do jogador/entrada nunca chegam a este método, porque são não executáveis no momento do planeamento.
Estabilidade
STABLE_BETA (os ciclos de vida NEW_MAP e SAVED_SITUATION estão validados em runtime).
Exemplo
var session = await launch.StartSessionAsync(plan); var s = await launch.GetStatusAsync(session); if (s.State == SessionState.Failed) Console.WriteLine(s.Diagnostics.Last(d => d.Code.StartsWith("OL_E_")).Message);
Ler o estado semântico do ciclo de vida, os diagnósticos recolhidos até ao momento e a lista limitada de eventos de runtime. Capacidade pública session.status.
Parâmetros
session: um handle devolvido por StartSessionAsync e ainda não fechado.
Devolve
SessionStatus(SessionId, State, Diagnostics, RuntimeEvents): um snapshot imutável (as matrizes são copiadas sob o lock da sessão). RuntimeEvents nunca é null para uma sessão ativa.
Lançadas
KeyNotFoundException para handles desconhecidos/fechados. Caso contrário, nunca lança exceções.
Cancelamento
O token é ignorado (a chamada conclui-se de forma síncrona).
Requer sessão em execução
Não.
Altera OMSI / sistema de ficheiros / transação
Não / Não / Nenhuma.
Estabilidade
STABLE_BETA.
Exemplo
var status = await launch.GetStatusAsync(session); Console.WriteLine($"{status.State} events={status.RuntimeEvents!.Count}");
Consultar periodicamente (a cada 100 ms) até a sessão estar em state, ou num estado terminal (Completed, Failed), ou até expirar o timeout; em seguida, devolver o estado atual.
Parâmetros
state: qualquer SessionState. Aguardar um estado transitório que já foi ultrapassado (ou que nunca é definido, ver ciclo de vida da sessão) aguarda até um estado terminal ou até ao timeout. timeout: qualquer TimeSpan não negativo ou Timeout.InfiniteTimeSpan.
Devolve
O estado no momento em que a espera terminou. Em caso de timeout, é devolvido o estado e não uma exceção: o próprio chamador deve verificar State. Uma espera por Running que termine em Failed retorna imediatamente com os diagnósticos da falha.
Lançadas
KeyNotFoundException; OperationCanceledException quando o token do chamador é cancelado (apenas o cancelamento pelo chamador se propaga; o timeout interno não).
Cancelamento
O token do chamador é respeitado a cada intervalo de 100 ms.
Requer sessão em execução
Não.
Altera OMSI / sistema de ficheiros / transação
Não / Não / Nenhuma.
Estabilidade
STABLE_BETA.
Exemplo
var running = await launch.WaitForAsync(session, SessionState.Running, TimeSpan.FromSeconds(185)); if (running.State != SessionState.Running) { /* timed out or Failed */ }
Pedir a paragem canónica. Define a flag de paragem e retorna imediatamente; o supervisor observa a flag no seu ciclo de 100 ms, chama TerminateProcess sobre Omsi.exe, aguarda a saída, marca o journal como ProcessExited, restaura todos os ficheiros pertencentes à sessão e liberta a concessão. Trata-se de uma terminação forçada: a rotina de encerramento do próprio OMSI não é executada e o OMSI não reescreve options.cfg à saída (deliberado, protege a transação). O encerramento cooperativo por WM_CLOSE não está implementado (decisão de produto; no fecho de runtime, o OMSI não fechou no prazo de 30 s após WM_CLOSE, L05b). Capacidade pública session.stop.
Parâmetros
session.
Devolve
Tarefa concluída; não aguarda pela terminação nem pelo restauro. Deve usar-se WaitForAsync(session, SessionState.Completed, ...) para observar a conclusão.
Lançadas
KeyNotFoundException.
Cancelamento
Token ignorado.
Requer sessão em execução
Não. Idempotente; uma paragem pedida antes de o supervisor arrancar é respeitada logo que ele arranca; uma paragem numa sessão terminal não tem efeito.
Altera o estado do OMSI
Sim: termina o processo do OMSI (código de saída 1).
Altera o sistema de ficheiros
Indiretamente: desencadeia o restauro, a eliminação do journal e a remoção das cópias de segurança pelo supervisor.
Transação / restauro
Desencadeia ProcessExited → Restoring → Restored. As alterações do lado do runtime efetuadas através de ExecuteRuntimeAsync (escritas no relógio, veículos criados, variáveis de script, texturas D3D) não são restauradas; desaparecem com o processo.
Estabilidade
STABLE_BETA.
Exemplo
await launch.StopAsync(session); var done = await launch.WaitForAsync(session, SessionState.Completed, TimeSpan.FromMinutes(1));
Libertar o handle do consumidor sem deixar a transação abandonada: se a sessão não for terminal, pede a paragem canónica; em seguida, aguarda a tarefa de ciclo de vida do supervisor (saída do processo, restauro, libertação da concessão); por fim, esquece a sessão.
Parâmetros
session.
Devolve
Conclui-se quando a sessão é terminal e removida. Depois de retornar, o handle é desconhecido (KeyNotFoundException em qualquer chamada posterior, incluindo um segundo CloseAsync).
Lançadas
KeyNotFoundException; OperationCanceledException se o chamador cancelar enquanto aguarda o supervisor. Nesse caso, a sessão não é removida e o supervisor continua em execução; deve chamar-se CloseAsync novamente.
Cancelamento
Aplica-se apenas à espera; nunca cancela o restauro.
Requer sessão em execução
Não.
Altera o estado do OMSI
Sim, quando a sessão ainda está ativa (igual a StopAsync).
Altera o sistema de ficheiros
Indiretamente (restauro pelo supervisor).
Transação / restauro
Garante que a transação é levada até à conclusão antes de o handle ser libertado (quando o supervisor foi iniciado). Para uma sessão que falhou antes de o supervisor arrancar, o caminho de arranque já restaurou ou comunicou OL_E_RESTORE_DEFERRED.
Executar uma operação de runtime pública dentro do processo OMSI em execução, através da mailbox da sessão de pedido único em curso (single-flight) (mapeada em memória, 64 KiB, pedido associado ao id de sessão e ao id de pedido). O plugin executa a operação na thread de UI do OMSI. Catálogo de operações: controlo de runtime e capacidades.
Parâmetros
command.SessionId tem de ser igual a session.SessionId. command.RequestId: ulong escolhido pelo chamador; deve usar-se um contador estritamente crescente por processo (os auxiliares 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 de cadeia indexados por nomes ordinais; os nomes obrigatórios por operação vêm de PublicCapabilityRegistry.GetRuntimeArguments. timeout: medido a partir do momento em que o pedido é colocado na mailbox (a espera em fila atrás de outro comando em curso não é contabilizada). A CLI usa 5 s (15 s para road-vehicles.spawn) como proprietário e 8 s / 30 s como cliente.
Ordem das verificações
1. Validação no registo (antes da procura da sessão): operação desconhecida ou internal.* → resultado Succeeded=false, ErrorCode=OL_E_RUNTIME_OPERATION_UNKNOWN; argumento obrigatório em falta (ausente ou apenas espaços em branco) → OL_E_RUNTIME_ARGUMENT_REQUIRED. 2. Procura 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. Pedido à mailbox. 6. Os valores do resultado cuja chave começa por internal_ ou termina em _address, _pointer, _vmt são removidos.
Devolve
RuntimeCommandResult(SessionId, RequestId, Succeeded, ErrorCode, Values). Em caso de sucesso, Values contém as cadeias semânticas da operação (documentadas por operação em controlo de runtime).
Erros transportados no resultado
OL_E_RUNTIME_OPERATION_UNKNOWN, OL_E_RUNTIME_ARGUMENT_REQUIRED (registo); OL_E_RUNTIME_RESPONSE_TOO_LARGE (o resultado do plugin excedeu a mailbox; os resultados de listas limitadas são, em vez disso, encurtados com truncated=true); OL_E_RUNTIME_SETTING_NOT_PERSISTENT (weather.set, sempre); todos os códigos 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: é o primeiro token de Values["detail"] (por exemplo detail = "OL_E_RUNTIME_OBJECT_HANDLE_STALE", exception = "InvalidOperationException"). Códigos que chegam desta 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. Ver códigos de erro.
Lançadas
KeyNotFoundException; InvalidOperationException com OL_E_RUNTIME_SESSION_MISMATCH, OL_E_SESSION_NOT_RUNNING, OL_E_RUNTIME_CHANNEL_CLOSED (mailbox já descartada pelo supervisor), OL_E_RUNTIME_CHANNEL_BUSY (o slot ainda contém um pedido abandonado), OL_E_RUNTIME_REQUEST_ID_REUSED (uma resposta obsoleta para o mesmo id de pedido ainda está no slot); TimeoutException("OL_E_RUNTIME_REQUEST_TIMEOUT"); InvalidDataException("OL_E_RUNTIME_RESPONSE_INVALID") (resposta corrompida, alheia ou não correspondente); ArgumentOutOfRangeException quando o pedido serializado excede a mailbox; OperationCanceledException.
Cancelamento
Respeitado durante a espera pelo gate por sessão e a cada 20 ms durante a consulta periódica da resposta. Cancelar a meio de um pedido não repõe o slot: a chamada seguinte nessa sessão pode falhar com OL_E_RUNTIME_CHANNEL_BUSY até o plugin publicar a sua resposta (que é então descartada como obsoleta). É preferível usar o timeout; um timeout repõe o slot e uma resposta tardia é detetada e descartada.
Requer sessão em execução
Sim (SessionState.Running); caso contrário, é lançado OL_E_SESSION_NOT_RUNNING. A mailbox existe até o supervisor a descartar durante o restauro.
Altera o estado do OMSI
Depende da operação: as operações Read não alteram; as 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 ficheiros
Nenhuma escrita do anfitrião. O OMSI pode escrever os seus próprios ficheiros em consequência (não monitorizado).
Transação / restauro
Nenhum.
Limitações
Um comando em curso por sessão (as chamadas na mesma sessão são serializadas). O pedido e a resposta estão cada um limitados a 64 KiB menos 8 bytes; os payloads de píxeis D3D a 48 KiB. internal.road-vehicles.make-basic é INTERNAL e inacessível. weather.set é UNAVAILABLE. timetable.logs.read não é limitada e pode devolver OL_E_RUNTIME_RESPONSE_TOO_LARGE em horários grandes. camera.lock é EXPERIMENTAL; requer um veículo do jogador e está validada em runtime (CAM01), embora a cadeia RuntimeValidation do registo ainda indique STATICALLY_VALIDATED. Os handles (rv-NNNNNN, hb-NNNNNN, d3dtex-<session>-<hex>) têm âmbito de sessão.
Estabilidade
Transporte e contrato STABLE_BETA; a estabilidade por operação segue PublicCapabilityRegistry (PublicStableBeta → STABLE_BETA, PublicExperimental → EXPERIMENTAL), com as exceções acima.
Exemplo
var 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"]}");
Devolver 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. Apenas runtime.current-windows-x64 é calculada (a partir da deteção da plataforma); todas as outras entradas são constantes.
Parâmetros
installation.RootPath: diretório usado para a sondagem da plataforma (a possibilidade de escrita exige que o diretório exista, não seja só de leitura e contenha plugins\). ExpectedExecutableSha256 é ignorado.
PublicCapabilityRegistry.All é o catálogo da superfície de controlo definido 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 impõem; não depende da instalação. GetCapabilitiesAsync é um relatório de evidências de runtime (estado de validação e razões). Deve usar-se o registo para decidir o que se pode chamar; esta lista, para decidir o que foi comprovado. Nenhuma das listas é derivada da outra.
Lançadas
OperationCanceledException à entrada; ArgumentException para um caminho de raiz vazio.
Cancelamento
Verificado uma vez à entrada.
Requer sessão em execução
Não.
Altera OMSI / sistema de ficheiros / transação
Não / Não / Nenhuma.
Estabilidade
Contrato de chamada STABLE_BETA; o conteúdo da lista é um inventário mantido manualmente: PARTIAL.
Exemplo
foreach (var c in await launch.GetCapabilitiesAsync(new InstallationSpec(root))) Console.WriteLine($"{c.Name} {c.Available} {c.EvidenceState} {c.Reason}");
Enumerar o conteúdo instalado e devolver identidades canónicas utilizáveis num LaunchSpec. A descoberta ignora pontos de reanálise (reparse points) (ciclos de junções não a podem bloquear), lê os ficheiros do OMSI como Windows-1252 (UTF-8/UTF-16 marcados com BOM são respeitados) e nunca segue ligações simbólicas.
Parâmetros
query e scope conforme a tabela abaixo. scope é obrigatório para Entrypoints (identidade do mapa), Repaints, FleetNumbers, Registrations (identidade do veículo).
Devolve
Lista ordenada de ContentIdentity(Identity, Kind, DisplayName). As identidades são caminhos relativos à instalação com barras invertidas; as comparações não distinguem maiúsculas de minúsculas.
Lançadas
OperationCanceledException à entrada; ArgumentException quando Entrypoints é consultado sem âmbito ou a raiz está vazia; FileNotFoundException (sem código OL_E_; a CLI mapeia-a para OL_E_NOT_FOUND) quando o mapa ou veículo do âmbito não está instalado. Uma raiz ou um diretório de conteúdo em falta produz uma lista vazia, não um erro.
Cancelamento
Verificado uma vez à entrada.
Requer sessão em execução
Não.
Altera OMSI / sistema de ficheiros / transação
Não / Não / Nenhuma.
Estabilidade
Maps, Situations, Vehicles: STABLE_BETA (todos os planos validados em runtime são resolvidos através deles). Entrypoints, Repaints, Hofs, FleetNumbers, Registrations, Addons: EXPERIMENTAL (apenas evidência estática).
Exemplo
var 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:
Valor
Âmbito
Identity
Kind
DisplayName
Maps
nenhum
maps\<dir>\global.cfg
map
nome do diretório do mapa
Situations
nenhum
situations\...\<file>.osn
situation
identidade do mapa referenciada pelo .osn (pode ser null)
Vehicles
nenhum
Vehicles\...\<file>.bus
vehicle
[friendlyname] ou nome do ficheiro
Repaints
identidade do veículo (obrigatória; sem ela: lista vazia)
<cti path>#item:<ordinal>
repaint
nome de [item]
Hofs
nenhum
Vehicles\...\<file>.hof
hof
null
FleetNumbers
identidade do veículo (obrigatória; sem ela: lista vazia)
caminho de origem de [number] relativo ao veículo
fleet-number
null
Registrations
identidade do veículo (obrigatória; sem ela: lista vazia)
identidade do mapa (obrigatória; sem ela: ArgumentException)
<map identity>#entrypoint:<SHA-256 of the 12-line record>
entrypoint
etiqueta do ponto de entrada
As identidades de ponto de entrada servem apenas para descoberta: o caminho de lançamento usa PresentedEntrypointIndex; passar um EntrypointIdentity torna o plano não executável nesta build (world.entrypoint-identity, RUNTIME_PARTIAL).
Comunicar ou concluir uma transação durável obsoleta (<root>\.omsilaunch\journal.json) deixada por um proprietário que falhou abruptamente. Obtém a concessão da instalação durante a chamada, para nunca restaurar por baixo de uma sessão em arranque. Capacidade pública session.recover; CLI /recovery-status e /recover.
Parâmetros
installation.RootPath: a raiz da instalação (normalizada com Path.GetFullPath). restore: false = apenas comunicar; true = restaurar, verificar, eliminar o journal e as cópias de segurança.
Devolve
RecoveryStatus(Pending, Recovered, Diagnostics): Pending = existia um journal quando a chamada começou; Recovered = foi pedido um restauro, este foi executado e não resta nenhum journal; Diagnostics = notas de restauro (restore.session-artifact-removed com Data["sha256"], OL_W_RESTORE_FOREIGN_FILE_RETAINED), vazio quando nada foi restaurado.
Lançadas
InvalidOperationException("OL_E_INSTALLATION_BUSY: another OmsiLaunch owner holds this installation.") quando a concessão está detida; IOException("OL_E_INSTALLATION_BUSY: a journaled OMSI process is still alive.") quando o PID + hora de criação + caminho do executável do journal ainda correspondem a um processo vivo, ou (journal para além de HandoffCreated sem PID) 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 uma exceção depois de um restauro ter começado, o journal é mantido e a chamada seguinte repete-o de forma idempotente.
Cancelamento
Passado às escritas do journal/das cópias de segurança; cancelar a meio do restauro deixa o journal pendente.
Requer sessão em execução
Não (recusa enquanto houver um proprietário ativo).
Altera o estado do OMSI
Não.
Altera o sistema de ficheiros
Apenas com restore == true: reescreve os originais a partir de cópias de segurança verificadas (bytes, hora da última escrita, hora de criação, atributos; originais só de leitura tratados; write-through + flush; nenhum *.omsilaunch.tmp deixado), remove os artefactos da sessão, elimina journal.json e backup\<sessionId>.
Transação / restauro
Conclui a transação pendente (Restoring → Restored → journal removido).
Estabilidade
STABLE_BETA: caminho de comunicação e recuperação após saída antecipada (matriz RV-008), recuperação após um restauro falhado (fecho de runtime F01), recusa com um proprietário ativo e na janela anterior ao PID (S04) e recuperação diferida anterior à impressão digital no arranque (S05); ver estado da validação em runtime.
Exemplo
var r = await launch.RecoverPendingAsync(new InstallationSpec(root), restore: true); Console.WriteLine($"pending={r.Pending} recovered={r.Recovered}");
Todos os records abaixo estão documentados propriedade a propriedade na referência do LaunchSpec; esta tabela fixa o inventário de tipos.
Tipo
Finalidade
Estabilidade
LaunchSpec
Record de pedido raiz com os acessores EffectiveYear, EffectiveWeather, EffectiveInput, EffectiveDiagnostics, EffectivePresentation, EffectiveInternetTextures, que substituem os membros opcionais null por predefinições.
STABLE_BETA
InstallationSpec
RootPath, ExpectedExecutableSha256 (transportado, não consumido).
Seleçã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).
SessionId (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 funcionalidades pedidas mas não suportadas), PlannedMutations, Diagnostics, IsRunnable.
Um record público: pode ser editado ou ficar desatualizado, razão pela qual StartSessionAsync volta a planear.
RuntimePlatformInfo
OsFamily, OsVersion, OsArchitecture, HostArchitecture, OmsiArchitecture (X86), PluginArchitecture (X86), CurrentPlatformSupported (Windows 10+, SO x64 e anfitrião x64), LegacyPlatform (sempre false), Wow64Available, InstallationWritable, ProcessLaunchSupported, PluginRuntimeSupported, NativeInteropSupported, SharedMemorySupported, ExactRestoreSupported (todos iguais a CurrentPlatformSupported).
Capability
Name, Available, EvidenceState, Reason.
As cadeias 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).
Chaves usadas pelas mutações de apresentação: session-presentation.splash, internet-textures.override, internet-textures.cache, internet-textures.target.
LaunchDiagnostic
Code, Message, Data (mapa de cadeias opcional).
Os códigos que começam por OL_E_ são erros, OL_W_ avisos, e tudo o resto é informativo.
SessionStatus
SessionId, State (SessionState), Diagnostics, RuntimeEvents.
Os diagnósticos da sessão não incluem os diagnósticos do plano.
RuntimeEvent
Type, TimestampUtc (hora de receção no anfitrião), Sequence (a partir de 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 depressa do que a consulta de 100 ms do anfitrião podem perder-se. Não é um registo sem perdas.
Enum de byte, pela 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 praticamente inobservável. Estados terminais: Completed, Failed. Semântica completa: ciclo de vida da sessão.
Codec estático usado pelo anfitrião e pelo plugin para o envelope da mailbox: número mágico 0x4F4C5243 ("OLRC"), versão 1, cabeçalho little-endian de 72 bytes (número mágico, versão, tipo 1 = pedido / 2 = resposta, comprimento total, Guid da sessão, id de pedido, comprimento do payload, SHA-256 do payload) seguido de um payload JSON em UTF-8. SerializeRequest, SerializeResponse, TryDeserializeRequest, TryDeserializeResponse, TryReadRequestId.
INTERNAL: público porque ambas as extremidades da ponte o partilham; 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 anfitrião publica para o plugin no ficheiro mapeado em memória OmsiLaunch.Handoff.<sessionId>.
INTERNAL
StartupHandoffWire
Codec: número mágico 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 verdadeiro, PlayerVehicleEnabled seja falso, ambos os modos de data/hora sejam Unset e uma situação guardada tenha uma identidade não vazia. O planeador impõe as mesmas restrições mais cedo, pelo que um plano executável nunca desencadeia esta rejeição.
ProtocolVersion ("0.1"), All (36 entradas PublicCapabilityDescriptor), PublicRuntimeOperationIds (os 48 ids de operação concretos que um frontend pode reencaminhar), IsPublicRuntimeOperation, GetRuntimeArguments, ValidateRuntimeArguments (devolve PublicRuntimeArgumentValidation), IsInternalResultKey. Imposto por ExecuteRuntimeAsync, pela CLI e pelo plano de controlo local.
Wrappers tipados sobre ExecuteRuntimeAsync para as operações d3d.* (EXPERIMENTAL, capacidade PublicExperimentald3d.texture). Atribuem ids de pedido a partir de um contador global do processo que começa em 30 000 e usam por predefinição um timeout de 5 s (exceto GetD3DStatusAsync, que exige um).
Erros: um resultado falhado é 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 cadeia de estado do dispositivo desconhecida, lança OmsiRuntimeException("OL_E_RUNTIME_PROTOCOL_MISMATCH", ...). Tudo o que ExecuteRuntimeAsync lança propaga-se sem alterações. O tratamento da reposição do dispositivo (reset) está validado em runtime: uma reposição faz o dispositivo passar por Resetting de volta a Ready e invalida todas as texturas ativas (OL_E_D3D_STALE_RESOURCE_HANDLE, fecho de runtime D01); GetCapabilitiesAsync ainda comunica runtime.d3d.lifecycle.reset como IMPLEMENTED_NOT_RUNTIME_VALIDATED (atraso do autorrelato). A transição Lost não pode ser produzida a partir do exterior do produto e está coberta apenas 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);
}
Success = 0, SessionFailed = 1, InvalidArguments = 2, UnsupportedProfile = 3, NoActiveSession = 4, RuntimeUnavailable = 5, NotFound = 6, OperationRejected = 7, TransactionRecoveryFailed = 8, InternalError = 10. Usado apenas pela CLI (códigos de saída); a API nunca termina o processo.
PublicErrorCategory
Constantes de cadeia usadas nos envelopes de erro da CLI/do controlo: invalid_argument, unsupported_profile, session, runtime, not_found, transaction, internal.
PublicErrorCodes
Uma 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).
OmsiRuntimeException
Propriedade Code mais mensagem; lançada apenas por D3DRuntimeApi.
InstallationPaths (identidade da instalação e contenção de caminhos)#
Estabilidade: STABLE_BETA (funções puras, sem I/O, sem estado do OMSI, sem alterações ao sistema de ficheiros, sem participação em transações, sem necessidade de sessão em execução). É a definição única usada pela concessão da instalação, pelo nome do pipe de controlo local, pelo confinamento de recursos dos perfis de sessão, pela validação dos alvos de Internet Textures e pelo caminho do modelo de criação (spawn) em runtime.
Membro
Comportamento
string NormalizeRoot(string root)
Path.GetFullPath(root) sem separadores finais, exceto numa raiz de unidade (C:\), que é mantida. Resolve os segmentos . e .., trata / e \ da mesma forma e reduz separadores repetidos. Não resolve junções nem ligações simbólicas. Lança ArgumentException para uma raiz nula ou em branco.
string IdentityKey(string root)
NormalizeRoot(root) convertido para maiúsculas. Grafias lexicalmente equivalentes de uma mesma raiz (C:\OMSI, C:\OMSI\, C:\OMSI\., C:\foo\..\OMSI, c:\omsi) partilham uma chave; raízes diferentes (C:\OMSI-A, C:\OMSI-B) nunca a partilham.
bool TryGetContainedRelativePath(string root, string candidate, out string relativePath)
Resolve candidate (relativo a root, ou absoluto) e devolve true apenas quando se encontra estritamente abaixo de root; relativePath é a grafia canónica com \. Usa os segmentos de Path.GetRelativePath, pelo que um irmão como C:\OMSI-A\x nunca está dentro de C:\OMSI; a própria raiz, outros volumes e fugas com .. devolvem false.
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"
Estabilidade: EXPERIMENTAL. Estes tipos compilam um perfil de sessão YAML (<root>\.omsilaunch\session-profiles\<id>\profile.yaml, esquema omsilaunch.session-profile/v1) num LaunchSpec. A CLI /predefined-profile:<id> /predefined-profile-index:<n> usa exatamente estas chamadas; um integrador pode usá-las para iniciar um perfil através da API.
Membro
Comportamento
SessionProfileCompiler.Load(string installationRoot, string id, int presetIndex) → SessionProfilePackage
Lê e valida o pacote. id tem de ser um nome de diretório simples (caso contrário, OL_E_SESSION_PROFILE_PATH_ESCAPE); presetIndex está entre 1..5 (OL_E_SESSION_PROFILE_PRESET_NOT_FOUND). Ficheiro em falta: OL_E_SESSION_PROFILE_NOT_FOUND; maior do 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 recursos estão confinados ao pacote (OL_E_SESSION_PROFILE_PATH_ESCAPE, OL_E_SESSION_PROFILE_ASSET_MISSING). Todas as falhas são uma SessionProfileException.
Devolve baseline com o perfil aplicado: para NewMap, o bloco new do perfil (mapa, ponto de entrada e qualquer data/hora/ano/meteorologia, que tornam o plano não executável nesta build); as settings da predefinição (preset) fundidas sobre Environment.General; Presentation, InternetTextures e Behavior da predefinição, quando presentes; e SessionProfile = profile.Metadata. Para NewMap, verifica também o mapa face à lista compatibility do perfil (OL_E_SESSION_PROFILE_MAP_MISMATCH).
A verificação de compatibilidade para os outros modos (para SavedSituation, o mapa é lido do .osn). A CLI chama-a depois de o WorldSpec final ser construído.
IOException com Code (um dos códigos OL_E_SESSION_PROFILE_*); a mensagem é "<code>: <message>".
A CLI rejeita adicionalmente flags da linha de comandos que entrem em conflito com o perfil (OL_E_SESSION_PROFILE_OVERRIDE_CONFLICT); essa verificação não faz parte do compilador. Ver perfis de sessão para a ordem de fusão completa.
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
}
STABLE_BETA como tipo do parâmetro do construtor de OmsiLaunchService
Deteta a plataforma, verifica a possibilidade de escrita, inicia, observa, termina e aguarda Omsi.exe. Deve passar-se new CurrentWindowsX64Platform(); implementá-lo por conta própria não é suportado.
CurrentWindowsX64Platform
STABLE_BETA
A única implementação: anfitrião Windows x64, CreateProcessW para Omsi.exe, TerminateProcess para a paragem canónica. Os seus métodos são chamados pelo serviço; os integradores apenas o constroem. Membros (partilhados com IRuntimePlatform): Detect(root) devolve 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 anfitrião não consegue executar uma sessão; IsInstallationWritable(root) suporta OL_E_INSTALLATION_NOT_WRITABLE; StartAsync(request, sha256) cria Omsi.exe e regista a identidade do processo (PID, hora 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 observam-no e terminam-no.
Públicos no assembly porque o serviço e os testes os partilham. Não são uma superfície de integração; LaunchedProcess encapsula internamente os handles do processo e da thread do OMSI (não são membros públicos) e nunca é devolvido por IOmsiLaunch.
OmsiLaunchService é seguro para chamadas concorrentes em sessões diferentes: as sessões vivem num ConcurrentDictionary e cada alteração por sessão ocorre sob o lock privado da sessão.
As chamadas concorrentes na mesma sessão são seguras, mas serializadas onde é relevante: ExecuteRuntimeAsync obtém um gate por sessão, pelo que um segundo comando aguarda pelo primeiro (o seu timeout começa quando é colocado na mailbox).
O supervisor é executado numa tarefa do thread pool (Task.Run) desde o momento em que StartSessionAsync retorna até a sessão ser terminal; consulta a telemetria e o processo a cada 100 ms. Os chamadores nunca executam código do supervisor.
StopAsync e GetStatusAsync concluem-se de forma síncrona e podem ser chamados a partir de qualquer thread, incluindo dentro de um handler ProcessExit (a CLI faz isto com um orçamento de 4 s).
Nenhuma chamada da API está vinculada a uma thread; nenhuma requer um contexto de sincronização.
Nenhum IntPtr, nint, handles Win32, endereços nativos, ponteiros de VMT ou objetos de processo. Os valores do resultado cujas chaves começam por internal_ ou terminam em _address, _pointer, _vmt são removidos antes de um resultado sair de ExecuteRuntimeAsync.
Nenhuma operação de runtime internal.*: internal.road-vehicles.make-basic é InternalOnly no registo e devolve OL_E_RUNTIME_OPERATION_UNKNOWN a partir da API e da CLI.
Nenhuma leitura ou escrita direta na memória do OMSI, nenhum acesso ao nível de ficheiros à instalação para além do que um LaunchSpec declara.
Nenhum handle entre processos: o plano de controlo local é a única via entre processos e aceita apenas session.status, session.events, session.stop e runtime.execute.
Nenhum encerramento cooperativo do OMSI, nenhum LAST_MAP_STATE, nenhuma aplicação de data/hora/meteorologia/veículo do jogador, nenhum overlay de documentos de teclado/controlador nesta build.
Conteúdo da lista de GetCapabilitiesAsync, membros de especificação de data/hora/meteorologia/veículo do jogador/entrada, DiagnosticsSpec, ExpectedExecutableSha256, RestoreConfiguration, ShutdownTimeoutSeconds
PARTIAL
RuntimeCommandWire, StartupHandoff, StartupHandoffWire, implementações de IRuntimePlatform, todos os assemblies de implementação