Esta é uma implementação ENet independente com um protocolo modificado para C, C++, C# e outras linguagens.
Características:
Por favor, leia os erros comuns para ter uma ideia do que pode dar errado.
Para construir a biblioteca nativa é necessário software apropriado:
Para plataformas desktop CMake com GNU Make ou Visual Studio.
Para plataformas móveis NDK para Android e Xcode para iOS. Certifique-se de que todas as bibliotecas compiladas sejam atribuídas a plataformas e arquiteturas de CPU apropriadas.
Para construir a biblioteca para Nintendo Switch, siga este guia.
Um assembly gerenciado pode ser criado usando qualquer plataforma de compilação disponível que suporte C# 3.0 ou superior.
Você pode obter bibliotecas compiladas na seção de lançamento ou no NuGet:
ENet-CSharp
contém assembly compilado com bibliotecas nativas para o ambiente .NET (.NET Standard 2.1).
ENet-Unity
contém script com bibliotecas nativas para o Unity.
É altamente recomendável excluir uma pasta com binários em vez de substituí-la para atualizar.
Esses pacotes são fornecidos apenas para plataformas tradicionais: Windows, Linux e macOS (x64).
Versões de sistema operacional suportadas:
Antes de começar a trabalhar, a biblioteca deve ser inicializada usando ENet.Library.Initialize();
função.
Após a conclusão do trabalho, desinicialize a biblioteca usando ENet.Library.Deinitialize();
função.
using ( Host server = new Host ( ) ) {
Address address = new Address ( ) ;
address . Port = port ;
server . Create ( address , maxClients ) ;
Event netEvent ;
while ( ! Console . KeyAvailable ) {
bool polled = false ;
while ( ! polled ) {
if ( server . CheckEvents ( out netEvent ) <= 0 ) {
if ( server . Service ( 15 , out netEvent ) <= 0 )
break ;
polled = true ;
}
switch ( netEvent . Type ) {
case EventType . None :
break ;
case EventType . Connect :
Console . WriteLine ( "Client connected - ID: " + netEvent . Peer . ID + ", IP: " + netEvent . Peer . IP ) ;
break ;
case EventType . Disconnect :
Console . WriteLine ( "Client disconnected - ID: " + netEvent . Peer . ID + ", IP: " + netEvent . Peer . IP ) ;
break ;
case EventType . Timeout :
Console . WriteLine ( "Client timeout - ID: " + netEvent . Peer . ID + ", IP: " + netEvent . Peer . IP ) ;
break ;
case EventType . Receive :
Console . WriteLine ( "Packet received from - ID: " + netEvent . Peer . ID + ", IP: " + netEvent . Peer . IP + ", Channel ID: " + netEvent . ChannelID + ", Data length: " + netEvent . Packet . Length ) ;
netEvent . Packet . Dispose ( ) ;
break ;
}
}
}
server . Flush ( ) ;
}
using ( Host client = new Host ( ) ) {
Address address = new Address ( ) ;
address . SetHost ( ip ) ;
address . Port = port ;
client . Create ( ) ;
Peer peer = client . Connect ( address ) ;
Event netEvent ;
while ( ! Console . KeyAvailable ) {
bool polled = false ;
while ( ! polled ) {
if ( client . CheckEvents ( out netEvent ) <= 0 ) {
if ( client . Service ( 15 , out netEvent ) <= 0 )
break ;
polled = true ;
}
switch ( netEvent . Type ) {
case EventType . None :
break ;
case EventType . Connect :
Console . WriteLine ( "Client connected to server" ) ;
break ;
case EventType . Disconnect :
Console . WriteLine ( "Client disconnected from server" ) ;
break ;
case EventType . Timeout :
Console . WriteLine ( "Client connection timeout" ) ;
break ;
case EventType . Receive :
Console . WriteLine ( "Packet received from server - Channel ID: " + netEvent . ChannelID + ", Data length: " + netEvent . Packet . Length ) ;
netEvent . Packet . Dispose ( ) ;
break ;
}
}
}
client . Flush ( ) ;
}
Packet packet = default ( Packet ) ;
byte [ ] data = new byte [ 64 ] ;
packet . Create ( data ) ;
peer . Send ( channelID , ref packet ) ;
byte [ ] buffer = new byte [ 1024 ] ;
netEvent . Packet . CopyTo ( buffer ) ;
AllocCallback OnMemoryAllocate = ( size ) => {
return Marshal . AllocHGlobal ( size ) ;
} ;
FreeCallback OnMemoryFree = ( memory ) => {
Marshal . FreeHGlobal ( memory ) ;
} ;
NoMemoryCallback OnNoMemory = ( ) => {
throw new OutOfMemoryException ( ) ;
} ;
Callbacks callbacks = new Callbacks ( OnMemoryAllocate , OnMemoryFree , OnNoMemory ) ;
if ( ENet . Library . Initialize ( callbacks ) )
Console . WriteLine ( "ENet successfully initialized using a custom memory allocator" ) ;
O uso é quase o mesmo do ambiente .NET, exceto que as funções do console devem ser substituídas por funções fornecidas pelo Unity. Se Host.Service()
for chamado em um loop de jogo, certifique-se de que o parâmetro timeout esteja definido como 0, o que significa não bloqueio. Além disso, mantenha o Unity rodando em segundo plano ativando a opção apropriada nas configurações do player.
A estratégia mais conhecida é usar ENet em um thread de E/S independente e utilizar técnicas de mensagens entre threads para transferir dados entre threads/tarefas sem quaisquer bloqueios/mutexes. Filas sem bloqueio, como Ring Buffer, foram projetadas para tais fins. Abstrações e lógica de alto nível podem ser paralelizadas usando trabalhadores e, em seguida, comunicar-se com thread de E/S e enfileirar/desenfileirar mensagens para enviar/receber dados pela rede.
Em geral, ENet não é thread-safe, mas algumas de suas funções podem ser usadas com segurança se o usuário for cuidadoso o suficiente:
A estrutura Packet
e suas funções são seguras até que um pacote se mova apenas entre threads por valor e um alocador de memória personalizado não seja usado.
Peer.ID
assim que um ponteiro para um peer for obtido do lado nativo, o ID será armazenado em cache na estrutura Peer
para ações adicionais com objetos atribuídos a esse ID. A estrutura Peer
pode ser movida entre threads por valor, mas suas funções não são thread-safe porque os dados na memória podem ser alterados pelo serviço em outro thread.
Library.Time
utiliza primitivas atômicas internamente para gerenciar o tempo monotônico local.
Definições de sinalizadores para a função Peer.Send()
:
PacketFlags.None
sequenciado não confiável, a entrega do pacote não é garantida.
PacketFlags.Reliable
confiável sequenciado, um pacote deve ser recebido pelo peer de destino e tentativas de reenvio devem ser feitas até que o pacote seja entregue.
PacketFlags.Unsequenced
um pacote não será sequenciado com outros pacotes e pode ser entregue fora de ordem. Este sinalizador torna a entrega não confiável.
PacketFlags.NoAllocate
um pacote não alocará dados e o usuário deverá fornecê-los. A vida útil do pacote deve ser rastreada usando o retorno de chamada PacketFreeCallback
.
PacketFlags.UnreliableFragmented
um pacote será fragmentado de forma não confiável se exceder o MTU. Por padrão, os pacotes não confiáveis que excedem o MTU são fragmentados e transmitidos de forma confiável. Este sinalizador deve ser usado para indicar explicitamente pacotes que não devem permanecer confiáveis.
PacketFlags.Instant
um pacote não será empacotado com outros pacotes em uma próxima iteração de serviço e, em vez disso, será enviado instantaneamente. Esse tipo de entrega troca a eficiência da multiplexação em favor da latência. O mesmo pacote não pode ser usado para múltiplas chamadas Peer.Send()
.
PacketFlags.Unthrottled
um pacote que foi enfileirado para envio de forma não confiável não deve ser descartado devido à limitação e enviado, se possível.
PacketFlags.Sent
um pacote foi enviado de todas as filas em que entrou.
Definições de tipos de eventos para a propriedade Event.Type
:
EventType.None
nenhum evento ocorreu dentro do limite de tempo especificado.
EventType.Connect
uma solicitação de conexão iniciada pela função Peer.Connect()
foi concluída. Event.Peer
retorna um par que se conectou com sucesso. Event.Data
retorna os dados fornecidos pelo usuário que descrevem a conexão ou 0 se nenhum estiver disponível.
EventType.Disconnect
um par foi desconectado. Este evento é gerado após a conclusão bem-sucedida de uma desconexão iniciada pela função Peer.Disconnect()
. Event.Peer
retorna um peer que foi desconectado. Event.Data
retorna os dados fornecidos pelo usuário que descrevem a desconexão ou 0 se nenhum estiver disponível.
EventType.Receive
um pacote foi recebido de um peer. Event.Peer
retorna um par que enviou o pacote. Event.ChannelID
especifica o número do canal no qual o pacote foi recebido. Event.Packet
retorna um pacote que foi recebido, e este pacote deve ser destruído usando a função Event.Packet.Dispose()
após o uso.
EventType.Timeout
um peer expirou. Este evento ocorre se o tempo limite de um peer expirou ou se uma solicitação de conexão inicializada por Peer.Connect()
expirou. Event.Peer
retorna um par que expirou.
Definições de estados pares para a propriedade Peer.State
:
PeerState.Uninitialized
um peer não inicializado.
PeerState.Disconnected
um par desconectado ou com tempo limite esgotado.
PeerState.Connecting
uma conexão peer em andamento.
PeerState.Connected
um peer conectado com sucesso.
PeerState.Disconnecting
uma desconexão de peer em andamento.
PeerState.Zombie
um par não desconectado corretamente.
Fornece eventos por aplicativo.
AllocCallback(IntPtr size)
notifica quando uma memória é solicitada para alocação. Espera um ponteiro para a memória recém-alocada. Uma referência ao delegado deve ser preservada de ser coletada como lixo.
FreeCallback(IntPtr memory)
notifica quando a memória pode ser liberada. Uma referência ao delegado deve ser preservada de ser coletada como lixo.
NoMemoryCallback()
notifica quando a memória não é suficiente. Uma referência ao delegado deve ser preservada de ser coletada como lixo.
Fornece eventos por pacote.
PacketFreeCallback(Packet packet)
notifica quando um pacote está sendo destruído. Indica se um pacote confiável foi reconhecido. Uma referência ao delegado deve ser preservada de ser coletada como lixo.
Fornece eventos por host.
InterceptCallback(ref Event @event, ref Address address, IntPtr receivedData, int receivedDataLength)
notifica quando um pacote UDP bruto é interceptado. O código de status retornado deste retorno de chamada instrui o ENet como o evento definido deve ser tratado. Retornar 1 indica o despacho do evento definido pelo serviço. Retornar 0 indica que os subsistemas ENet devem tratar os dados recebidos. Retornar -1 indica um erro. Uma referência ao delegado deve ser preservada de ser coletada como lixo.
ChecksumCallback(IntPtr buffers, int bufferCount)
notifica quando uma soma de verificação deve ser calculada para buffers no envio e recebimento. Um valor retornado desse retorno de chamada é uma soma de verificação de 64 bits. O ENet lida automaticamente com a verificação de integridade dos pacotes se um mecanismo de soma de verificação estiver habilitado em ambas as extremidades. Pode ser usado com a função ENet.Library.CRC64()
. Uma referência ao delegado deve ser preservada de ser coletada como lixo.
Contém estrutura com dados de host anônimos e número de porta.
Address.Port
obtém ou define um número de porta.
Address.GetIP()
obtém um endereço IP.
Address.SetIP(string ip)
define um endereço IP. Para usar a transmissão IPv4 na rede local, o endereço pode ser definido como 255.255.255.255 para um cliente. A ENet responderá automaticamente à transmissão e atualizará o endereço para o IP real do servidor.
Address.GetHost()
tenta fazer uma pesquisa inversa a partir do endereço. Retorna uma string com um nome resolvido ou um endereço IP.
Address.SetHost(string hostName)
define o nome do host ou um endereço IP. Deve ser usado para ligação a uma interface de rede ou para conexão a um host externo. Retorna verdadeiro em caso de sucesso ou falso em caso de falha.
Contém estrutura com o tipo de evento, ponteiro gerenciado para o peer, ID do canal, os dados fornecidos pelo usuário e ponteiro gerenciado para o pacote.
Event.Type
retorna um tipo de evento.
Event.Peer
retorna um par que gerou um evento de conexão, desconexão, recebimento ou tempo limite.
Event.ChannelID
retorna um ID de canal no par que gerou o evento, se apropriado.
Event.Data
retorna os dados fornecidos pelo usuário, se apropriado.
Event.Packet
retorna um pacote associado ao evento, se apropriado.
Contém um ponteiro gerenciado para o pacote.
Packet.Dispose()
destrói o pacote. Deve ser chamado somente quando o pacote foi obtido do evento EventType.Receive
.
Packet.IsSet
retorna um estado do ponteiro gerenciado.
Packet.Data
retorna um ponteiro gerenciado para os dados do pacote.
Packet.UserData
obtém ou define os dados fornecidos pelo usuário.
Packet.Length
retorna o comprimento da carga útil no pacote.
Packet.HasReferences
verifica referências ao pacote.
Packet.SetFreeCallback(PacketFreeCallback callback)
define o retorno de chamada para notificar quando um pacote apropriado está sendo destruído. Um ponteiro IntPtr
para um retorno de chamada pode ser usado em vez de uma referência a um delegado.
Packet.Create(byte[] data, int offset, int length, PacketFlags flags)
cria um pacote que pode ser enviado a um peer. O parâmetro offset indica o ponto inicial dos dados em uma matriz, o comprimento é o ponto final dos dados em uma matriz. Todos os parâmetros são opcionais. Vários sinalizadores de pacote podem ser especificados de uma só vez. Um ponteiro IntPtr
para um buffer nativo pode ser usado em vez de uma referência a uma matriz de bytes.
Packet.CopyTo(byte[] destination)
copia a carga útil do pacote para a matriz de destino.
Contém um ponteiro gerenciado para o peer e o ID armazenado em cache.
Peer.IsSet
retorna um estado do ponteiro gerenciado.
Peer.ID
retorna um ID de par. É sempre zero no lado do cliente.
Peer.IP
retorna um endereço IP em formato imprimível.
Peer.Port
retorna um número de porta.
Peer.MTU
retorna um MTU.
Peer.State
retorna um estado de mesmo nível descrito na enumeração PeerState
.
Peer.RoundTripTime
retorna um tempo de ida e volta em milissegundos.
Peer.LastRoundTripTime
retorna um tempo de ida e volta desde a última confirmação em milissegundos.
Peer.LastSendTime
retorna o tempo de envio do último pacote em milissegundos.
Peer.LastReceiveTime
retorna o tempo de recebimento do último pacote em milissegundos.
Peer.PacketsSent
retorna um número total de pacotes enviados durante a conexão.
Peer.PacketsLost
retorna um número total de pacotes considerados perdidos durante a conexão com base na lógica de retransmissão.
Peer.PacketsThrottle
retorna uma proporção de aceleração de pacotes dependendo das condições da conexão com o peer.
Peer.BytesSent
retorna um número total de bytes enviados durante a conexão.
Peer.BytesReceived
retorna um número total de bytes recebidos durante a conexão.
Peer.Data
obtém ou define os dados fornecidos pelo usuário. Deve ser usado com uma conversão explícita para o tipo de dados apropriado.
Peer.ConfigureThrottle(uint interval, uint acceleration, uint deceleration, uint threshold)
configura o parâmetro de aceleração para um par. Pacotes não confiáveis são descartados pela ENet em resposta às condições variáveis da conexão com o peer. O acelerador representa a probabilidade de que um pacote não confiável não seja descartado e, portanto, enviado pela ENet ao peer. O menor tempo médio de ida e volta desde o envio de um pacote confiável até o recebimento de sua confirmação é medido durante um período de tempo especificado pelo parâmetro de intervalo em milissegundos. Se um tempo de ida e volta medido for significativamente menor que o tempo médio de ida e volta medido durante o intervalo, a probabilidade de aceleração será aumentada para permitir mais tráfego em uma quantidade especificada no parâmetro de aceleração, que é uma proporção da Library.throttleScale
constante. Se um tempo de ida e volta medido for significativamente maior que o tempo médio de ida e volta medido durante o intervalo, a probabilidade de aceleração será diminuída para limitar o tráfego em um valor especificado no parâmetro de desaceleração, que é uma proporção para a Library.throttleScale
constante Library.throttleScale
. Quando o acelerador tem um valor Library.throttleScale
, nenhum pacote não confiável será descartado pelo ENet e, portanto, 100% de todos os pacotes não confiáveis serão enviados. Quando o acelerador tem valor 0, todos os pacotes não confiáveis são descartados pelo ENet e, portanto, 0% de todos os pacotes não confiáveis serão enviados. Valores intermediários para o acelerador representam probabilidades intermediárias entre 0% e 100% de pacotes não confiáveis sendo enviados. Os limites de largura de banda dos hosts locais e estrangeiros são levados em consideração para determinar um limite sensato para a probabilidade de aceleração acima do qual ela não deve aumentar mesmo nas melhores condições. Para desativar o estrangulamento, o parâmetro de desaceleração deve ser definido como zero. O parâmetro limite pode ser usado para reduzir a aceleração de pacotes em relação ao tempo de ida e volta medido em ambientes de rede instáveis com alto jitter e baixa latência média, que é uma condição comum para redes Wi-Fi em locais lotados. Por padrão, o parâmetro limite é definido como Library.throttleThreshold
em milissegundos.
Peer.Send(byte channelID, ref Packet packet)
enfileira um pacote para ser enviado. Retorna verdadeiro em caso de sucesso ou falso em caso de falha.
Peer.Receive(out byte channelID, out Packet packet)
tenta desenfileirar qualquer pacote enfileirado de entrada. Retorna verdadeiro se um pacote foi retirado da fila ou falso se nenhum pacote estiver disponível.
Peer.Ping()
envia uma solicitação de ping para um par. O ENet faz ping automaticamente em todos os peers conectados em intervalos regulares; no entanto, esta função pode ser chamada para garantir solicitações de ping mais frequentes.
Peer.PingInterval(uint interval)
define um intervalo no qual os pings serão enviados para um par. Os pings são usados para monitorar a atividade da conexão e também para ajustar dinamicamente o acelerador durante períodos de baixo tráfego, para que o acelerador tenha capacidade de resposta razoável durante picos de tráfego.
Peer.Timeout(uint timeoutLimit, uint timeoutMinimum, uint timeoutMaximum)
define parâmetros de tempo limite para um par. Os parâmetros de tempo limite controlam como e quando um par atingirá o tempo limite devido a uma falha no reconhecimento de tráfego confiável. Valores de tempo limite usados no mecanismo semilinear, onde se um pacote confiável não for reconhecido dentro de um tempo médio de ida e volta mais uma tolerância de variação até que o tempo limite atinja um limite definido. Se o tempo limite estiver neste limite e pacotes confiáveis tiverem sido enviados, mas não reconhecidos dentro de um determinado período de tempo mínimo, o peer será desconectado. Alternativamente, se pacotes confiáveis tiverem sido enviados, mas não reconhecidos por um determinado período de tempo máximo, o par será desconectado independentemente do valor limite de tempo limite atual.
Peer.Disconnect(uint data)
solicita uma desconexão de um par.
Peer.DisconnectNow(uint data)
força uma desconexão imediata de um peer.
Peer.DisconnectLater(uint data)
solicita uma desconexão de um par, mas somente depois que todos os pacotes de saída na fila forem enviados.
Peer.Reset()
desconecta à força um par. O host estrangeiro representado pelo par não é notificado da desconexão e atingirá o tempo limite de sua conexão com o host local.
Contém um ponteiro gerenciado para o host.
Host.Dispose()
destrói o host.
Host.IsSet
retorna um estado do ponteiro gerenciado.
Host.PeersCount
retorna um número de pares conectados.
Host.PacketsSent
retorna um número total de pacotes enviados durante a sessão.
Host.PacketsReceived
retorna um número total de pacotes recebidos durante a sessão.
Host.BytesSent
retorna um número total de bytes enviados durante a sessão.
Host.BytesReceived
retorna um número total de bytes recebidos durante a sessão.
Host.Create(Address? address, int peerLimit, int channelLimit, uint incomingBandwidth, uint outgoingBandwidth, int bufferSize)
cria um host para comunicação com pares. Os parâmetros de largura de banda determinam o tamanho da janela de uma conexão que limita o número de pacotes confiáveis que podem estar em trânsito a qualquer momento. A ENet descartará pacotes estrategicamente em lados específicos de uma conexão entre hosts para garantir que a largura de banda do host não seja sobrecarregada. O parâmetro buffer size é usado para definir o tamanho do buffer do soquete para envio e recebimento de datagramas. Todos os parâmetros são opcionais, exceto o endereço e o limite de pares nos casos em que a função é usada para criar um host que escutará as conexões de entrada.
Host.PreventConnections(bool state)
impede o acesso ao host para novas conexões de entrada. Esta função torna o host completamente invisível na rede, qualquer peer que tentar se conectar a ele atingirá o tempo limite.
Host.Broadcast(byte channelID, ref Packet packet, Peer[] peers)
enfileira um pacote para ser enviado a um intervalo de peers ou a todos os peers associados ao host se o parâmetro opcional peers não for usado. Qualquer estrutura Peer
zerada em uma matriz será excluída da transmissão. Em vez de um array, um único Peer
pode ser passado para a função que será excluída da transmissão.
Host.CheckEvents(out Event @event)
verifica se há eventos na fila no host e despacha um, se disponível. Retorna > 0 se um evento foi despachado, 0 se nenhum evento estiver disponível, < 0 em caso de falha.
Host.Connect(Address address, int channelLimit, uint data)
inicia uma conexão com um host externo. Retorna um peer que representa o host estrangeiro em caso de sucesso ou lança uma exceção em caso de falha. O peer retornado não terá concluído a conexão até que Host.Service()
notifique sobre um evento EventType.Connect
. O limite do canal e os parâmetros de dados fornecidos pelo usuário são opcionais.
Host.Service(int timeout, out Event @event)
aguarda eventos no host especificado e transporta pacotes entre o host e seus pares. ENet usa um modelo de evento pesquisado para notificar o usuário sobre eventos significativos. Os hosts ENet são pesquisados para eventos com esta função, onde um valor de tempo limite opcional em milissegundos pode ser especificado para controlar por quanto tempo o ENet fará a pesquisa. Se um tempo limite 0 for especificado, esta função retornará imediatamente se não houver eventos para despachar. Caso contrário, retornará 1 se um evento foi despachado dentro do tempo limite especificado. Esta função deve ser chamada regularmente para garantir que os pacotes sejam enviados e recebidos, caso contrário, ocorrerão picos de tráfego, levando ao aumento da latência. O parâmetro timeout definido como 0 significa não bloqueio, necessário para casos em que a função é chamada em um loop de jogo.
Host.SetBandwidthLimit(uint incomingBandwidth, uint outgoingBandwidth)
ajusta os limites de largura de banda de um host em bytes por segundo.
Host.SetChannelLimit(int channelLimit)
limita o máximo de canais permitidos de futuras conexões de entrada.
Host.SetMaxDuplicatePeers(ushort number)
limita o máximo permitido de peers duplicados do mesmo host e evita a conexão se excedido. Por padrão definido como Library.maxPeers
, não pode ser menor que um.
Host.SetInterceptCallback(InterceptCallback callback)
define o retorno de chamada para notificar quando um pacote UDP bruto é interceptado. Um ponteiro IntPtr
para um retorno de chamada pode ser usado em vez de uma referência a um delegado.
Host.SetChecksumCallback(ChecksumCallback callback)
define o retorno de chamada para notificar quando uma soma de verificação deve ser calculada. Um ponteiro IntPtr
para um retorno de chamada pode ser usado em vez de uma referência a um delegado.
Host.Flush()
envia quaisquer pacotes enfileirados no host especificado para seus pares designados.
Contém campos constantes.
Library.maxChannelCount
o número máximo possível de canais.
Library.maxPeers
o número máximo possível de peers.
Library.maxPacketSize
o tamanho máximo de um pacote.
Library.version
a versão de compatibilidade atual relativa à biblioteca nativa.
Library.Time
retorna um tempo monotônico local atual em milissegundos. Ele nunca é redefinido enquanto o aplicativo permanece ativo.
Library.Initialize(Callbacks callbacks)
inicializa a biblioteca nativa. O parâmetro callbacks é opcional e deve ser usado apenas com um alocador de memória personalizado. Deve ser chamado antes de iniciar o trabalho. Retorna verdadeiro em caso de sucesso ou falso em caso de falha.
Library.Deinitialize()
desinicializa a biblioteca nativa. Deve ser chamado após a conclusão do trabalho.
Library.CRC64(IntPtr buffers, int bufferCount)
calcula uma soma de verificação para buffers não gerenciados.
Este projeto é patrocinado por:
Entretenimento esquilo voador
Estúdios Raiz Quadrada
Jogos de Loop Estranhos