Eu tenho o costume de procurar no GitHub mods, patches e outras formas de mexer nos programas que uso. Principalmente quando tem propaganda envolvida. Eu odeio abrir um aplicativo e encontrar metade da interface ocupada por anúncio, então, quando dá, procuro alguma forma de remover aquilo.
Foi assim que, alguns anos atrás, encontrei o OverwolfPatcher.
Se você nunca usou Overwolf, vale começar por aí. O Overwolf é uma plataforma sobre a qual outros aplicativos relacionados a jogos rodam. Ele fornece a infraestrutura comum: integração com o jogo, overlays, eventos da partida, captura de vídeo e outras APIs. O aplicativo que eu usava, o Outplayed, roda em cima dessa plataforma.
O Outplayed faz uma coisa que eu gosto bastante: fica aberto junto com o jogo e grava a partida automaticamente. Depois eu consigo voltar a uma kill, uma teamfight ou qualquer outro momento sem ter lembrado de iniciar uma gravação antes.
Eu gostava bastante dele. Só não gostava muito de tudo que vinha em volta.
Parte dos recursos ficava atrás do premium e a interface tinha anúncios. O OverwolfPatcher alterava verificações locais do Overwolf e, com isso, mudava o que aplicativos como o Outplayed enxergavam. Na época eu não precisava entender além disso. Baixava uma versão, rodava e seguia a vida.
Inclusive, eu já tinha aprendido uma coisa meio específica sobre o projeto: releases mais novas às vezes me davam problema, então eu costumava voltar para uma versão antiga que sabia que funcionava.
Depois entrei na faculdade, passei a jogar bem menos e parei de usar o Outplayed. O patcher foi junto e ficou esquecido por alguns anos.
Recentemente comecei a jogar um pouco mais de novo e senti falta de rever algumas partidas. Instalei o Outplayed, abri o programa e encontrei a opção de remover a marca d’água bloqueada pelo premium, junto com três anúncios enormes na tela principal.

Figura 1. Tela principal do Outplayed antes da intervenção, com a opção “GO PREMIUM” e anúncios visíveis.
Minha reação foi procurar o patcher de novo.
Baixei a versão mais recente e rodei.
Não funcionou.
Como isso já tinha acontecido antes, fui direto para a versão antiga que eu costumava usar. Eu sabia que aquela tinha funcionado comigo, então imaginei que seria só mais uma regressão do próprio patcher.
Só que a antiga também não funcionou.
Foi isso que me fez abrir o código.
Até esse ponto eu só queria recuperar o efeito que o patcher me dava anos antes. Ao abrir o código, ficou claro que ele modificava o Overwolf, mais especificamente partes do Core consultadas pelos aplicativos da plataforma.
A versão antiga do patcher era a mesma. O Overwolf é que tinha continuado evoluindo.
Minha hipótese inicial era bem pouco dramática. Talvez algum método tivesse mudado de nome, uma classe tivesse sido movida, a assinatura de uma função tivesse mudado ou uma DLL não estivesse mais no caminho que o patcher esperava.
Eu esperava encontrar uma peça velha apontando para o lugar errado.
O que tinha envelhecido era a arquitetura inteira.
Onde o patch acontecia
Para mudar o comportamento do Outplayed, eu precisava seguir a decisão até a camada que produzia a informação usada por ele.
Um programa no Windows normalmente é dividido entre executáveis e bibliotecas. No Overwolf, uma dessas bibliotecas era OverWolf.Client.Core.dll, parte do Core consultado pelos aplicativos instalados na plataforma.
Se o Outplayed pergunta ao Core algo como “quais assinaturas esta extensão possui?”, existem pelo menos dois lugares conceitualmente possíveis para alterar o resultado: eu poderia modificar o código do Outplayed que interpreta a resposta ou modificar a própria resposta que o Core fornece.
O patcher antigo fazia a segunda coisa.
Outplayed | | pergunta sobre a assinatura da extensão vOverWolf.Client.Core.dll | | devolve a informação vOutplayed decide o que fazerAlterar o Core era interessante porque ele ficava entre a pergunta e a decisão. Em vez de procurar cada botão ou checagem dentro do Outplayed, eu podia mudar a informação que essas decisões consumiam.
Mas como ele mudava uma DLL?
Aqui entra a forma clássica de patch em disco: modificar o código que será carregado na próxima execução. Em um binário nativo, isso pode significar trocar instruções x86-64. Aqui o alvo era uma assembly .NET, então o patch podia acontecer em outro nível.
Uma assembly .NET guarda metadados e normalmente armazena os corpos dos métodos em IL, de Intermediate Language, uma representação intermediária entendida pelo runtime do .NET.
O OverwolfPatcher usava o Mono.Cecil para ler e reescrever essa estrutura.
Um método C# como:
int GetNumber(){ return 7;}pode virar algo próximo disso em IL:
ldc.i4.7retldc.i4.7 coloca o valor 7 na pilha usada pela IL. ret devolve esse valor.
Com Cecil, o patcher conseguia localizar um método pelo nome, apagar as instruções que estavam ali e montar outras no lugar. Um patch antigo mostra isso quase sem ruído. O método que exibia um bloqueio relacionado ao Windows Insider era localizado e substituído por um corpo equivalente a return false:
MethodDefinition showInsiderBlockMessageMethod = overwolfCoreWManager.Methods .SingleOrDefault(x => x.Name == "ShowInsiderBlockMessage");
showInsiderBlockMessageMethod.Body.Instructions.Clear();showInsiderBlockMessageMethod.Body.Instructions.Add( Instruction.Create(OpCodes.Ldc_I4_0));showInsiderBlockMessageMethod.Body.Instructions.Add( Instruction.Create(OpCodes.Ret));ldc.i4.0 produz false; ret o retorna. O método simplesmente deixava de ter o corpo antigo e passava a ter outro.
Os patches relacionados a assinatura eram maiores, mas o mecanismo era o mesmo. O patcher montava novos corpos de IL e, no final, fazia isto:
fullPath.Backup(true);overwolfCore.Write(fullPath.FullName);Console.WriteLine(Utils.Pad("Patched successfully"));Primeiro guardava um backup. Depois escrevia a assembly modificada por cima da original.
A lógica era simples: modificar o arquivo para que, na próxima execução, o Overwolf carregasse outro comportamento.
Era um patch persistente em disco. A diferença para um patch nativo estava na representação: em vez de editar código de máquina, o projeto reescrevia a estrutura e o IL da assembly .NET.
Essa diferença logo deixou de ser apenas técnica.
Por que comecei pelo Core e por apenas dois métodos
O patcher mexia em várias partes do Overwolf. Para isolar a falha, procurei o menor caminho que ainda pudesse afetar o Outplayed.
Ao inspecionar o aplicativo, encontrei um caminho legado de assinatura que ainda consultava dois métodos do Core:
GetExtensionSubscriptionsGetExtensionSubscriptionsIdsNo Overwolf, uma extensão é basicamente um dos aplicativos instalados na plataforma. O primeiro método devolvia informações mais detalhadas sobre os planos associados à extensão. O segundo devolvia apenas os IDs desses planos.
O Outplayed ainda registrava um provedor legado que consultava essas informações e reconhecia o plano 61. Também havia um sistema mais novo, baseado no Tebex e com consulta ao servidor, mas o caminho legado ainda participava da decisão.
Eu precisava responder uma pergunta menor: se esses dois métodos voltassem a devolver o que o patcher esperava, o Outplayed ainda reagiria?
A cadeia que eu queria testar era esta:
Outplayed pergunta ao Core pela assinatura | vGetExtensionSubscriptions / GetExtensionSubscriptionsIds | vCore devolve plano legado 61 | vOutplayed inclui esse resultado na decisão localSe não funcionasse, não valeria a pena reconstruir o resto do patcher. Foi nesse caminho mínimo que encontrei o primeiro bug real.
A primeira correção funcionou, e foi exatamente por isso que ela me enganou
O patcher construía um dos campos de preço usando Ldc_R4, que coloca na IL um float de 32 bits. A propriedade usada pela versão atual esperava um System.Double, de 64 bits.
A diferença cabia em uma instrução:
Instruction.Create(OpCodes.Ldc_R4, 1.0f)Instruction.Create(OpCodes.Ldc_R8, 0d)Em IL, eu estava montando diretamente os valores consumidos pelas chamadas seguintes. O tipo precisava bater com o que o método esperava.
Era exatamente o tipo de incompatibilidade que eu esperava encontrar: o Overwolf mudou e o patcher continuou emitindo a forma antiga.
Corrigi isso e reduzi o patch para alterar somente os dois métodos que eu queria estudar.
Antes de testar no programa inteiro, montei uma fixture, uma assembly pequena com a estrutura relevante daqueles métodos. Assim eu podia verificar isoladamente se o IL gerado era válido e devolvia o resultado esperado.
Os testes passaram.
O método detalhado devolvia o plano esperado. O método de IDs também. O preço usava o tipo correto. Para uma extensão diferente, o caminho original continuava funcionando.
Até ali, a história parecia encerrada.
Eu tinha encontrado e corrigido uma incompatibilidade concreta, e o código corrigido executava.
Então rodei o patch no Overwolf real.
O Overwolf nem chegou a abrir.
Refusing to start - deployed assembly failed verification:C:\Program Files (x86)\Overwolf\0.309.0.14\OverWolf.Client.Core.dllIsso acontecia antes de o Outplayed iniciar.
Eu estava testando se o código modificado funcionava. O launcher recusava o arquivo antes que aquele código tivesse qualquer oportunidade de rodar.
O bug de float para double era real. Só não era a causa principal.
O arquivo modificado era recusado
Primeiro precisei verificar se eu simplesmente tinha produzido uma assembly inválida. Uma tentativa anterior já tinha mostrado uma falha de strong name em outro componente, então era importante separar duas ideias diferentes.
O strong name faz parte da identidade de uma assembly .NET. O Authenticode é uma assinatura digital aplicada ao arquivo executável no Windows. Simplificando bastante, o fabricante assina criptograficamente um hash calculado sobre as partes relevantes do arquivo. Mais tarde, alguém pode recalcular esse hash e verificar se o conteúdo protegido continua sendo o mesmo que foi assinado.
O OverWolf.Client.Core.dll que eu estava modificando não tinha strong name. Ele tinha uma assinatura Authenticode válida da Overwolf.
Isso não significa que uma DLL assinada seja imutável. Significa que a alteração pode ser detectada. Um patch em disco funciona enquanto o caminho de carregamento aceita o arquivo modificado.
No Overwolf atual havia uma verificação explícita.
Ao abrir o launcher, encontrei uma chamada a VerifyDeployedAssembliesOrFail(). Antes de seguir com a inicialização normal, esse caminho usava WinTrust para verificar se as DLLs implantadas continuavam com uma assinatura válida da Overwolf.
DLL no disco | vlauncher verifica a assinatura | +-- válida --> continua a inicialização | +-- inválida --> recusa o arquivoO patcher precisava alterar o arquivo; o launcher queria provar que ele não tinha sido alterado desde a assinatura.
O crack dependia de mudar o arquivo. A verificação dependia de provar que o arquivo não tinha mudado.
Ainda havia uma explicação alternativa: talvez meu IL estivesse corrompendo a assembly. Então removi o patch da equação.
Peguei uma cópia limpa do Core, abri com Mono.Cecil e mandei salvar novamente sem modificar deliberadamente nenhum método.
Depois comparei os métodos da cópia original com os da regravada e rodei a mesma verificação de assinatura usada pelo launcher.
O resultado foi este:
| Arquivo | WinTrust aceitou? | Diferenças nos métodos comparados |
|---|---|---|
| Core original | True |
referência |
| Core com os dois métodos modificados | False |
2 |
| Core apenas aberto e regravado | False |
0 |
O comparador percorreu 16.992 métodos e conferiu IL, propriedades, variáveis locais e regiões de exceção. 0 diferenças não significa identidade byte a byte, mas mostrava que a DLL podia deixar de passar no WinTrust sem eu alterar nenhum dos métodos que estava tentando patchar.
O problema não era mais o IL que eu queria colocar ali. Era o fato de eu estar regravando o arquivo.
O simples ciclo “abrir e gravar novamente” já bastava.
O patcher dependia desta premissa:
posso substituir a DLL instalada por outra e o programa vai carregá-la.
Na versão atual, essa premissa não era mais verdadeira. Eu precisava parar de modificar o arquivo.

Figura 2. O patch em disco falha antes de o código modificado ter chance de executar.
Se o disco não servia mais, onde intervir?
A fronteira agora era clara: antes do carregamento, trocar a DLL quebrava a validação. Depois, o runtime ainda precisava transformar o IL em algo executável.
Para encontrar esse intervalo, bastava seguir um método .NET do disco até a CPU.
A DLL contém o corpo do método em IL. O CLR, o runtime do .NET, carrega a assembly. Quando um método precisa executar, o JIT, de just-in-time compiler, normalmente transforma aquele IL em código nativo para a arquitetura da máquina. É esse código nativo que a CPU executa.
DLL no disco | | contém IL vCLR carrega a assembly | vJIT compila o método | | produz código nativo vCPU executaA assinatura protegia o arquivo verificado pelo launcher, não tornava o método imutável durante toda a vida do processo.
A solução precisava satisfazer três requisitos:
- a DLL no disco precisava continuar intacta e validamente assinada;
- eu precisava intervir antes que o comportamento do método estivesse consolidado no código nativo que seria executado;
- como o Core é compartilhado, somente o Outplayed deveria receber o resultado alterado. As outras extensões precisavam continuar vendo o comportamento original.
Em que ponto o arquivo já foi aceito, mas o comportamento ainda pode ser alterado?
A primeira tentativa em memória chegava tarde demais
A primeira abordagem usou um AppDomainManager para entrar cedo na inicialização do runtime e redirecionar os dois métodos para implementações que eu gerava em memória.
Se eu não podia substituir o corpo no arquivo, talvez pudesse deixar a DLL carregar e depois redirecionar a entrada do método.
No teste sintético, funcionou pela metade.
GetExtensionSubscriptions foi redirecionado. GetExtensionSubscriptionsIds, não.
O motivo era inlining.
Uma chamada normal seria:
caller -> GetExtensionSubscriptionsIds -> resultadoHá uma entrada clara para interceptar.
Só que o JIT pode otimizar métodos pequenos copiando o corpo deles diretamente para dentro do código que chama. Isso é inlining.
sem inlining
caller -> GetExtensionSubscriptionsIds -> resultado ^ dá para redirecionar aqui
com inlining
caller -> [código do método copiado para cá] -> resultadoDepois do inlining, aquela execução não passa mais pela entrada redirecionada. Marcar a fixture com NoInlining fez o teste passar, mas confirmou a fragilidade da estratégia: eu dependia de chegar antes de otimizações que não controlava.
Havia outro problema: só o UID do Outplayed deveria receber a resposta alterada; qualquer outra extensão precisava executar o método original.
Antes de redirecionar a entrada, guardei um delegate para o método original. Achei que ele preservaria a implementação antiga.
Não preservava. Depois do redirecionamento, o delegate ainda entrava pelo mesmo ponto substituído.
substituição -> não é a extensão alvo -> chamar "original" -> entrada já foi redirecionada -> substituição -> chamar "original" -> ...O fallback que deveria escapar do patch voltava para dentro do próprio patch.
O resultado foi um StackOverflowException.

Figura 3. Guardar um delegate não preservou uma rota independente para a implementação antiga.
Ainda tentei guardar um ponteiro de função antes da troca. Isso empurrou o problema para detalhes cada vez mais baixos: Marshal.GetDelegateForFunctionPointer, marshaling de arrays gerenciados, calling conventions e uma tentativa com calli que terminou em InvalidProgramException.
Os erros eram diferentes, mas apontavam para a mesma coisa: eu tentava intervir depois que o runtime já havia começado a preparar, compilar e otimizar o método.
A intervenção estava acontecendo tarde demais.
O ponto certo estava antes do JIT
Foi aí que a API de profiling do CLR passou a fazer sentido.
Apesar do nome, a API de profiling do CLR não serve só para medir performance. Ela permite que uma DLL nativa receba callbacks de eventos internos do runtime.
Um desses callbacks é JITCompilationStarted. O CLR chama o profiler quando está prestes a compilar uma função.
A segunda peça era SetILFunctionBody. Ela permite fornecer ao CLR um novo corpo de IL para um método antes daquela compilação.
O ponto que eu precisava existia imediatamente antes do JIT.
Eu podia deixar a DLL original passar pela verificação e intervir antes de o JIT produzir código nativo:
DLL original e assinada | | launcher verifica: continua válida vCLR carrega a assembly original | vmétodo está prestes a ser compilado | +--> profiler fornece outro corpo de IL | vJIT compila o corpo fornecido | vCPU executaO launcher verificava uma representação. O runtime executava outra.
O arquivo assinado permanecia intacto; dentro do processo, o CLR recebia outro corpo para o método antes de compilá-lo.

Figura 4. O arquivo verificado permanece intacto; a substituição acontece apenas no caminho de execução.
O patch deixava de existir no artefato persistente e passava a existir apenas dentro do processo.
Como o profiler encontra o método sem tocar na DLL
O profiler é uma DLL nativa x64 em C++, carregada pelo CLR junto do processo esperado.
Quando uma função chega ao JIT, o callback recebe um identificador. A partir dele, o profiler descobre o módulo e o metadata token do método e verifica se é um dos dois alvos.
A transferência do novo corpo acontece com memória fornecida pelo próprio CLR:
info->GetILFunctionBodyAllocator(module, &allocator);BYTE *destination = reinterpret_cast<BYTE *>( allocator->Alloc(static_cast<ULONG>(body.size())));CopyMemory(destination, body.data(), body.size());hr = info->SetILFunctionBody(module, method, destination);allocator->Release();O ponto importante é o que não existe mais: não há Write(), nem uma nova OverWolf.Client.Core.dll que precise sobreviver ao WinTrust. O corpo alternativo só existe no processo.
Faltava preservar o comportamento das outras extensões.
O método precisava preservar o comportamento original
Como o método faz parte do Core compartilhado, outras extensões também podem consultá-lo. Um retorno global alteraria a semântica da plataforma inteira.
Então o corpo novo precisava ter dois comportamentos:
se esta chamada é para o Outplayed: devolver o resultado experimentalsenão: executar exatamente o corpo originalEra o mesmo fallback que tinha quebrado com o delegate. Agora, porém, eu podia preservar as próprias instruções.
O fallback deixou de ser uma chamada e virou parte do método
O profiler monta um corpo composto: um prefixo compara o UID da extensão com o alvo; depois vêm as instruções originais.
ldarg.0call get_UIDldstr "identificador da extensão alvo"call string::op_Equalitybrfalse ORIGINAL
// Construir e devolver o resultado local do experimento.ret
ORIGINAL:// Instruções originais, copiadas para cá.Se o UID corresponde ao Outplayed, o método segue pelo caminho novo. Caso contrário, brfalse salta para as instruções originais no mesmo corpo, sem delegate ou ponteiro externo.

Figura 5. O caminho modificado e o caminho original passam a coexistir dentro do mesmo corpo de IL.
Isso eliminava a recursão, mas copiar o código original alguns bytes adiante não era só concatenar bytes.
Mover IL também significa mover as coordenadas internas do método
Um corpo .NET também descreve variáveis locais e regiões de exceção (try, catch, finally, filtros), que referenciam posições dentro do método.
Quando eu coloco um prefixo de tamanho P antes do código antigo, todas as instruções originais começam P bytes mais tarde.
Desvios relativos internos continuam válidos porque origem e destino se deslocam juntos. Já offsets medidos a partir do começo do método precisam ser ajustados.
Um campo particularmente fácil de errar pode representar, conforme as flags, um token de tipo para catch ou um offset de filtro.
const ULONG classOrFilter = (clause.flags & 0x1u) != 0 ? clause.classOrFilter + static_cast<ULONG>(originalEntry) : clause.classOrFilter;Se o campo contém um offset de filtro, ele precisa andar junto com o código. Se contém o token de uma classe de exceção, somar bytes destruiria a referência.
O construtor também preserva a assinatura das variáveis locais, a opção de inicializá-las e o restante do cabeçalho necessário para o JIT interpretar o método.
Preservar as instruções não basta; é preciso preservar um método executável. Ainda assim, o prefixo introduz uma chamada ao getter de UID antes do corpo original, então os testes não provam equivalência semântica para todo caso possível.
O número de um método só faz sentido dentro de uma build
O profiler identifica métodos por metadata tokens. Um valor como 0x060030B7 pode parecer permanente, mas não é.
Ele significa, grosso modo, “esta entrada na tabela de métodos desta assembly”. Outra build pode usar o mesmo número para outro método.
Por isso o profiler valida SHA-256, arquitetura, MVID e metadados antes de substituir qualquer corpo. O MVID, Module Version ID, ajuda a distinguir uma build de outra.
A regra passou a ser conservadora: se a DLL não for exatamente a versão que eu investiguei, não presumo que tokens, assinaturas e formatos continuam significando a mesma coisa.
Isso não torna o patch universal; impede que um experimento específico finja ser universal.
O que isso muda no Outplayed
Era importante separar “o crack funcionou” de “o programa inteiro virou premium”. O modo altera um caminho local específico, não todo o estado da conta.
Na linha de comando, premium é apenas o nome de um modo de instrumentação:
.\OverwolfPatcher.exe instrument --mode premium ` --app cghphpbjeabdkomiphingnegihoigeggcfphdofo ` --plans 61--app informa qual UID deve seguir pelo caminho alterado. --plans informa quais IDs os dois métodos locais devem devolver nesse caminho. Para o experimento com Outplayed, o valor observado era o plano legado 61.
O Outplayed ainda possuía um provedor legado que consultava esses dois métodos e combinava o resultado com o caminho mais novo baseado no Tebex.
O profiler não cria uma assinatura Tebex nem muda estado no servidor. Ele altera duas respostas locais para o UID alvo.
servidor / Tebex ----------------------> continua igual
Core localGetExtensionSubscriptions ------------> resposta alterada para o UID alvoGetExtensionSubscriptionsIds ---------> resposta alterada para o UID alvo | v decisões locais que usam esse caminhoNa sessão observada, o botão “Go Premium” desapareceu e alguns comportamentos locais mudaram, mas a conta continuou mostrando Outplayed Core - Free. Isso é compatível com caminhos distintos usando fontes diferentes: o experimento alterava um ramo da decisão, não toda a noção de conta premium.
O profiler só funciona se o método realmente chegar até o JIT
A nova arquitetura dependia de o método realmente passar pelo JIT.
Inlining era uma forma de perder essa oportunidade. Outra eram imagens nativas de NGEN, que permitem ao .NET Framework usar código previamente compilado.
Por isso o processo instrumentado desativa inlining e o uso dessas imagens nativas.
É uma intervenção mais ampla, mas garantia que os métodos passassem pelo ponto em que o profiler podia substituir o IL.
Só então todas as peças ficam alinhadas:
1. arquivo original permanece no disco2. launcher valida a assinatura original3. CLR carrega a assembly4. método alvo chega ao JIT5. profiler fornece o corpo composto6. JIT compila esse corpo7. somente o UID alvo segue pelo caminho alterado8. os demais caem nas instruções originaisPela primeira vez, a solução não estava brigando com alguma etapa anterior do sistema.
Cada requisito tinha um lugar específico onde era resolvido.
Fazer a mecânica funcionar não prova que o aplicativo mudou por causa dela
A arquitetura fazia sentido. Isso ainda não demonstrava o efeito no Outplayed real.
Havia quatro perguntas diferentes:
- o CLR consegue carregar o profiler?
SetILFunctionBodyaceita o corpo que eu montei?- o JIT consegue compilar e executar esse corpo?
- o Outplayed real toma uma decisão diferente por causa desse corpo?
A fixture x64 respondia principalmente às três primeiras. Ela exercitava a mecânica da instrumentação, não o conjunto de estados e chamadores do aplicativo real.
Por isso o launcher ganhou modos intermediários: baseline sem instrumentação; bootstrap para carregar a infraestrutura; observe para acompanhar callbacks; neutral para substituir IL por corpos equivalentes aos originais; só então o modo que muda o retorno.
Se bootstrap falha, o IL novo nem foi aplicado. Se neutral falha, o problema está na instrumentação, não no plano 61.
O ambiente também interferiu: numa conta restrita de automação, o Overwolf falhava antes dos métodos alvo com Failed to initialize CEF runtime. O teste real precisou da conta interativa usada pelo Overwolf.
Depois disso, os logs registraram a passagem pelo método real:
2026-09-08T21:09:08.275Z JIT target started function=0x7FFEC3916450 token=0x60030B72026-09-08T21:09:08.284Z premium SetILFunctionBody detailed=ok ids=ok rollback=ok2026-09-08T21:09:08.285Z Core JIT finished function=0x7FFEC3916450 token=0x60030B7 status=0x0Esse log prova algo delimitado: o Core real chegou ao callback, a API aceitou os corpos e o JIT concluiu a compilação do método detalhado. Não prova sozinho os efeitos visuais nem, nesse trecho, uma execução independente do método de IDs.
Na sessão testada, o botão “Go Premium” desapareceu, os anúncios não estavam visíveis e alguns recursos locais aparentavam estar disponíveis. A tela de plano continuou mostrando Outplayed Core - Free.
Isso é compatível com a arquitetura reconstruída, mas não prova que cada mudança visual veio desse caminho. A ausência de anúncios pode depender de outras condições, uma execução não demonstra persistência e benefícios dependentes do servidor estavam fora do escopo.
A execução real usou este comando:
PS C:\Users\bruno\Downloads\Compressed\OverwolfPatcher> .\OverwolfPatcher.exe instrument --mode premium --app cghphpbjeabdkomiphingnegihoigeggcfphdofo --plans 61Launched native OverwolfLauncher PID 8636; profiler targets managed Overwolf.exe only.Mode: premium | profiler log: C:\Users\bruno\Downloads\Compressed\OverwolfPatcher\artifacts\profiler\0.309.0.14-20260909-211034.logInstalled files were not modified. Remove the profiling environment by launching Overwolf normally.PS C:\Users\bruno\Downloads\Compressed\OverwolfPatcher>Na sessão resultante, o Outplayed mostrou as alterações locais descritas acima. Embora a conta não tivesse uma assinatura premium, os anúncios desapareceram e a opção de ocultar o layout do jogo ficou disponível gratuitamente:

Figura 6. Depois da execução, o Outplayed mostra recursos locais bloqueados pelo premium sem uma assinatura: os anúncios desapareceram e o layout do jogo pode ser ocultado gratuitamente.
No fim, o problema era quando intervir
Quando abri o OverwolfPatcher, minha pergunta era:
qual parte deste patch ficou incompatível com a versão atual?
Eu esperava encontrar um método velho, uma assinatura diferente ou uma instrução incorreta. O float onde agora era necessário um double era um bug real, mas a DLL corrigida continuava sem chance de executar.
O experimento decisivo foi aquele em que eu não alterei método nenhum: só abrir o Core com Cecil e gravá-lo novamente já fazia o launcher rejeitar a cópia.
O conteúdo lógico do patch podia ser corrigido.
O mecanismo de entrega desse conteúdo tinha deixado de ser compatível com o sistema.
O patch antigo pressupunha que podia substituir o arquivo em disco. A versão investigada verificava antes se a DLL ainda correspondia ao artefato assinado. Insistir no patch em disco era insistir na pergunta errada.
A pergunta útil passou a ser:
Em que ponto o arquivo já foi aceito, mas o comportamento ainda não virou código nativo?
A resposta estava imediatamente antes do JIT.
O arquivo permanece original. Quando o método alvo está prestes a ser compilado, o profiler entrega outro corpo de IL; o UID do Outplayed segue pelo caminho experimental e os demais caem nas instruções originais.
Eu comecei tentando mudar o que estava no arquivo. Terminei mudando quando a intervenção acontecia: depois da verificação, antes do JIT.