From d5cb25fdd9ebd584e8b2cd495a53ebf698c26ef6 Mon Sep 17 00:00:00 2001 From: hero_124 Date: Thu, 8 Oct 2026 17:16:45 +0000 Subject: [PATCH 1/4] Login com Facebook (OAuth no servidor): entrar, ligar conta e criar conta --- README.md | 7 + RobloxServer.Web/Code/Config.cs | 12 ++ RobloxServer.Web/Code/Data/Models.cs | 3 + RobloxServer.Web/Landing.aspx | 1 + RobloxServer.Web/Login.aspx | 1 + RobloxServer.Web/Login/Facebook.ashx | 1 + RobloxServer.Web/Login/Facebook.ashx.cs | 239 +++++++++++++++++++++++ RobloxServer.Web/RobloxServer.Web.csproj | 4 + 8 files changed, 268 insertions(+) create mode 100644 RobloxServer.Web/Login/Facebook.ashx create mode 100644 RobloxServer.Web/Login/Facebook.ashx.cs diff --git a/README.md b/README.md index ef40b5a..216c08c 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,13 @@ Um **Raspberry Pi Zero 2W** faz o port forwarding, o UPnP e o DNS da rede. Veja Veja [docs/security-2015.md](docs/security-2015.md) para a lista completa das medidas de segurança (e das fraquezas mantidas de propósito). +### Login com Facebook + +1. Em developers.facebook.com crie um app, adicione o produto **Facebook Login** e em *Valid OAuth Redirect URIs* coloque `https://SEUSITE/Login/Facebook.ashx` (o mesmo endereço do `BaseUrl`). +2. No `Web.config` defina ``. +3. O **segredo do app não vai no Git**: defina a variável de ambiente `ROBLOXSERVER_FACEBOOK_SECRET` no servidor (e reinicie o site). +4. Os botões "Login with Facebook" só aparecem quando o `FacebookAppId` está definido. Quem entra pela primeira vez ganha uma conta nova (usuário gerado a partir do nome); quem já está logado e clica no botão liga o Facebook à conta atual. A primeira conta do site (admin) precisa ser criada com usuário e senha. + ### Modo bot (servidores 24/7) Todo jogo publicado (os que já existem e os novos) ganha um servidor mantido por um bot chamado **HOST**, sem ninguém diff --git a/RobloxServer.Web/Code/Config.cs b/RobloxServer.Web/Code/Config.cs index 741e93e..84d798a 100644 --- a/RobloxServer.Web/Code/Config.cs +++ b/RobloxServer.Web/Code/Config.cs @@ -113,6 +113,18 @@ static string RequestBaseUrl() /// RCC-style shared secret passed as ?apiKey= by game servers (2015 behaviour). public static string ApiKey { get { return Get("ApiKey", ""); } } + /// Facebook app id (public). The secret comes from the ROBLOXSERVER_FACEBOOK_SECRET environment variable. + public static string FacebookAppId { get { return Get("FacebookAppId", ""); } } + + public static string FacebookAppSecret + { + get + { + string fromEnvironment = System.Environment.GetEnvironmentVariable("ROBLOXSERVER_FACEBOOK_SECRET"); + return !string.IsNullOrEmpty(fromEnvironment) ? fromEnvironment : Get("FacebookAppSecret", ""); + } + } + public static bool RegistrationOpen { get { return GetBool("RegistrationOpen", true); } } /// User names that are site administrators ("Roblox" staff). diff --git a/RobloxServer.Web/Code/Data/Models.cs b/RobloxServer.Web/Code/Data/Models.cs index 40348b7..928cda2 100644 --- a/RobloxServer.Web/Code/Data/Models.cs +++ b/RobloxServer.Web/Code/Data/Models.cs @@ -27,6 +27,9 @@ public class User /// "Male", "Female" or null (2013 landing page signup). public string Gender { get; set; } + /// Facebook user id when the account signs in with Facebook (null otherwise). + public string FacebookId { get; set; } + /// Set only if the user consented, at signup, to link their real Roblox username. public bool RobloxVerified { get; set; } diff --git a/RobloxServer.Web/Landing.aspx b/RobloxServer.Web/Landing.aspx index 365ebb1..a09a9fe 100644 --- a/RobloxServer.Web/Landing.aspx +++ b/RobloxServer.Web/Landing.aspx @@ -54,6 +54,7 @@
diff --git a/RobloxServer.Web/Login.aspx b/RobloxServer.Web/Login.aspx index ceb2df7..ef45ac5 100644 --- a/RobloxServer.Web/Login.aspx +++ b/RobloxServer.Web/Login.aspx @@ -26,6 +26,7 @@
Forgot your password? Ask an administrator.
+ <% if (!string.IsNullOrEmpty(RobloxServer.Config.FacebookAppId)) { %>" class="btn-medium btn-neutral" style="margin-left:6px;background:#3b5998;color:#fff">Login with Facebook<% } %>
diff --git a/RobloxServer.Web/Login/Facebook.ashx b/RobloxServer.Web/Login/Facebook.ashx new file mode 100644 index 0000000..b864665 --- /dev/null +++ b/RobloxServer.Web/Login/Facebook.ashx @@ -0,0 +1 @@ +<%@ WebHandler Language="C#" CodeBehind="Facebook.ashx.cs" Class="RobloxServer.Handlers.Login.Facebook" %> diff --git a/RobloxServer.Web/Login/Facebook.ashx.cs b/RobloxServer.Web/Login/Facebook.ashx.cs new file mode 100644 index 0000000..e9e5efb --- /dev/null +++ b/RobloxServer.Web/Login/Facebook.ashx.cs @@ -0,0 +1,239 @@ +using System; +using System.Configuration; +using System.IO; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Web; +using RobloxServer.Data; +using RobloxServer.Security; +using RobloxServer.Web; + +namespace RobloxServer.Handlers.Login +{ + /// + /// "Login with Facebook" (OAuth 2, fluxo de codigo no servidor). + /// /Login/Facebook.ashx -> manda para o Facebook + /// /Login/Facebook.ashx?code=... -> volta do Facebook: entra, liga a conta logada, ou cria uma conta nova. + /// Precisa de FacebookAppId (Web.config) e do segredo na variavel de ambiente ROBLOXSERVER_FACEBOOK_SECRET. + /// + public class Facebook : HandlerBase + { + const string StateCookie = "RBXFbState"; + const string Api = "https://graph.facebook.com/v19.0/"; + + static Facebook() + { + ServicePointManager.SecurityProtocol |= (SecurityProtocolType)3072; // TLS 1.2 + } + + protected override void Handle() + { + string appId = Config.FacebookAppId; + string secret = Config.FacebookAppSecret; + if (string.IsNullOrEmpty(appId) || string.IsNullOrEmpty(secret)) + { + WriteStatus(503, "Facebook login is not configured on this server."); + return; + } + string code = Request.QueryString["code"]; + if (string.IsNullOrEmpty(code)) + { + if (!string.IsNullOrEmpty(Request.QueryString["error"])) + { + Fail("Facebook login was cancelled."); + return; + } + Start(appId); + return; + } + Finish(appId, secret, code); + } + + string RedirectUri() + { + string root = ConfigurationManager.AppSettings["BaseUrl"]; + if (string.IsNullOrEmpty(root)) + { + root = Request.Url.GetLeftPart(UriPartial.Authority); + } + return root.TrimEnd('/') + "/Login/Facebook.ashx"; + } + + static string Hex(byte[] bytes) + { + return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant(); + } + + static string RandomHex(int bytes) + { + var data = new byte[bytes]; + using (var rng = new RNGCryptoServiceProvider()) + { + rng.GetBytes(data); + } + return Hex(data); + } + + void Start(string appId) + { + string state = RandomHex(16); + Response.Cookies.Add(new HttpCookie(StateCookie, state) + { + HttpOnly = true, + Secure = Request.IsSecureConnection, + Expires = DateTime.UtcNow.AddMinutes(10) + }); + Response.Redirect("https://www.facebook.com/v19.0/dialog/oauth?client_id=" + Uri.EscapeDataString(appId) + + "&redirect_uri=" + Uri.EscapeDataString(RedirectUri()) + "&state=" + state + "&scope=public_profile", false); + } + + void Fail(string text) + { + Response.Cookies.Add(new HttpCookie("RBXLoginError", HttpUtility.UrlEncode(text)) + { + HttpOnly = true, + Expires = DateTime.UtcNow.AddMinutes(2) + }); + Response.Redirect("~/Login.aspx", false); + } + + static string Get(string url) + { + var request = (HttpWebRequest)WebRequest.Create(url); + request.Timeout = 15000; + request.ReadWriteTimeout = 15000; + using (var response = request.GetResponse()) + using (var reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8)) + { + return reader.ReadToEnd(); + } + } + + void Finish(string appId, string secret, string code) + { + HttpCookie cookie = Request.Cookies[StateCookie]; + string state = Request.QueryString["state"] ?? ""; + if (cookie == null || string.IsNullOrEmpty(cookie.Value) || !PasswordHasher.ConstantTimeEquals(cookie.Value, state)) + { + Fail("Facebook login expired. Please try again."); + return; + } + Response.Cookies.Add(new HttpCookie(StateCookie, "") { Expires = DateTime.UtcNow.AddDays(-1) }); + + string fbId; + string fbName; + try + { + string tokenJson = Get(Api + "oauth/access_token?client_id=" + Uri.EscapeDataString(appId) + + "&redirect_uri=" + Uri.EscapeDataString(RedirectUri()) + + "&client_secret=" + Uri.EscapeDataString(secret) + "&code=" + Uri.EscapeDataString(code)); + string token = Regex.Match(tokenJson, "\"access_token\"\s*:\s*\"([^\"]+)\"").Groups[1].Value; + if (token.Length == 0) + { + Fail("Facebook did not accept the login. Please try again."); + return; + } + string proof; + using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret))) + { + proof = Hex(hmac.ComputeHash(Encoding.UTF8.GetBytes(token))); + } + string me = Get(Api + "me?fields=id,name&access_token=" + Uri.EscapeDataString(token) + "&appsecret_proof=" + proof); + fbId = Regex.Match(me, "\"id\"\s*:\s*\"(\d+)\"").Groups[1].Value; + fbName = Regex.Match(me, "\"name\"\s*:\s*\"((?:[^\"\\\\]|\\\\.)*)\"").Groups[1].Value; + try { fbName = Regex.Unescape(fbName); } catch (ArgumentException) { } + } + catch (Exception ex) + { + Logging.Log(LogType.Security, "Facebook login failed: " + ex.Message); + Fail("Could not reach Facebook. Please try again."); + return; + } + if (fbId.Length == 0) + { + Fail("Facebook did not return your profile."); + return; + } + + User user = Db.Users.Find(u => u.FacebookId == fbId); + if (user == null) + { + User current = Auth.CurrentUser; + if (current != null) + { + // Ja esta logado: so liga o Facebook a esta conta. + Db.Users.Update(u => u.Id == current.Id, u => u.FacebookId = fbId); + Response.Redirect("~/My/Home.aspx", false); + return; + } + user = CreateAccount(fbId, fbName); + if (user == null) + { + return; + } + } + Auth.SignIn(user, Context); + Response.Redirect(Accounts.AfterLogin(user, null), false); + } + + User CreateAccount(string fbId, string fbName) + { + if (!Config.RegistrationOpen) + { + Fail("Registration is closed on this server."); + return null; + } + if (Db.Users.All().Count == 0) + { + Fail("Create the first account with a username and password before using Facebook."); + return null; + } + string ip = ClientIp.Get(Context); + if (FloodChecker.Hit("register:" + ip, 3, TimeSpan.FromHours(1))) + { + Fail("Too many accounts have been created from your IP. Try again later."); + return null; + } + + string baseName = Regex.Replace(fbName ?? "", "[^A-Za-z0-9]", ""); + if (baseName.Length < 3) + { + baseName = "Player"; + } + if (baseName.Length > 12) + { + baseName = baseName.Substring(0, 12); + } + string chosen = baseName; + var random = new Random(); + int tries = 0; + while (WordFilter.ValidateUserName(chosen) != null || Db.FindUser(chosen) != null) + { + if (++tries > 50) + { + Fail("Could not pick a username for you. Sign up with a username instead."); + return null; + } + chosen = baseName + random.Next(100, 99999); + } + + string name = chosen; + DateTime now = DateTime.UtcNow; + string hash = PasswordHasher.Hash(RandomHex(32)); // senha aleatoria: a conta entra pelo Facebook + User created = Db.Users.InsertWithId(u => u.Id, 1, id => new User + { + Id = id, + Name = name, + PasswordHash = hash, + Created = now, + LastOnline = now, + LastIp = ip, + FacebookId = fbId + }); + Logging.Log(LogType.Success, "New account " + created.Name + " (" + created.Id + ") via Facebook from " + ip); + return created; + } + } +} diff --git a/RobloxServer.Web/RobloxServer.Web.csproj b/RobloxServer.Web/RobloxServer.Web.csproj index 8695413..b180eec 100644 --- a/RobloxServer.Web/RobloxServer.Web.csproj +++ b/RobloxServer.Web/RobloxServer.Web.csproj @@ -158,6 +158,7 @@ + @@ -424,6 +425,9 @@ HeaderLogin.ashx + + Facebook.ashx + Forum.aspx ASPXCodeBehind From 746ba9a0ae6a62682733b8aa2f6acf1f45df5853 Mon Sep 17 00:00:00 2001 From: V0rtexLinux Date: Thu, 8 Oct 2026 15:48:52 -0300 Subject: [PATCH 2/4] Update README.md --- README.md | 126 ------------------------------------------------------ 1 file changed, 126 deletions(-) diff --git a/README.md b/README.md index 216c08c..8b13789 100644 --- a/README.md +++ b/README.md @@ -1,127 +1 @@ -
- RS Logo - ### RobloxServer é um servidor privado open-source do Roblox 2013! - -
-
- -## Procurando a documentação? Clique [aqui](docs/welcome.md). - -### Rodando no seu PC (Windows) em 1 minuto - -> O `127.0.0.1:8081` que aparece nos logs do GitHub Actions é uma máquina temporária do GitHub, usada só -> para testar e apagada logo depois. O site precisa rodar no **seu** computador. - -1. Baixe o site já compilado: aba **Actions** → última execução verde → **Artifacts** → `RobloxServer-site` - (ou clone o repositório e tenha o Visual Studio / Build Tools instalado, o script compila sozinho). -2. Instale o [IIS Express](https://www.microsoft.com/download/details.aspx?id=48264) se ainda não tiver. -3. Dê dois cliques em **`IniciarServidor.exe`** (ou no antigo `iniciar-servidor.bat`). O navegador abre `http://localhost:8080/`. -4. Crie a primeira conta em *Sign Up* (ela vira administradora) e publique um jogo em *Build*. -5. Coloque os clientes em `RobloxServer.Web/App_Data/Clients/2012M.zip` e `2013M.zip`, clique em **Download ROBLOX**, - rode o launcher uma vez e depois é só clicar em **Play** na página do jogo. Os servidores são criados pelo bot HOST (veja [Modo bot](#modo-bot-servidores-247)). - -O **`IniciarServidor.exe`** abre uma janela com o estado do site e: - -* inicia o site no IIS Express (compila antes, se precisar) e o reinicia sozinho se ele fechar; -* **mantém o computador ligado**: enquanto estiver aberto, o Windows não entra em suspensão (a tela ainda pode apagar); -* pode **iniciar junto com o Windows** (já minimizado, perto do relógio) e fica na área de notificação quando minimizado. - Uso: `IniciarServidor.exe [--port 8080] [--minimized]`. - -* com **Aceitar outros PCs (Radmin VPN / rede local)** marcado, amigos entram pelo seu IP do Radmin (`http://26.x.x.x:8080/`) - ou da rede. O Windows pede permissão de administrador uma vez para liberar a porta do site e a UDP 53640 no firewall. - -Sem essa opção, o IIS Express só atende este PC (quem vem de fora vê *Bad Request - Invalid Hostname*). -Para a Internet e o Raspberry Pi, use o IIS completo: [docs/setting-up.md](docs/setting-up.md). - -RobloxServer agora é um site **ASP.NET clássico (Web Forms, .NET Framework 4.6, C# 6 / Visual Studio 2015)**, -com handlers `.ashx` e páginas `.aspx` como o roblox.com de 2015. Todo o JavaScript (Node/Express) foi removido. - -Os jogos publicados aqui são jogados pelo **[RobloxPlayerLauncher](https://github.com/V0rtexLinux/RobloxServerLauncher)**, -o launcher oficial do site no estilo de 2013 (botão **Play** → o jogo abre), só com os clientes **2012M** e **2013M**. -Um **Raspberry Pi Zero 2W** faz o port forwarding, o UPnP e o DNS da rede. Veja [docs/playing.md](docs/playing.md). - -### Estrutura - -| Pasta | O que tem | -| --- | --- | -| `RobloxServer.Web/` | O site ASP.NET (abra `RobloxServer.sln` no Visual Studio 2015 ou mais novo) | -| `RobloxServer.Web/Game`, `Asset`, `Login`, ... | Handlers `.ashx` que os clientes 2015 chamam (`/Game/Visit.ashx`, `/asset/?id=`, ...) | -| `RobloxServer.Web/Api` | JSON usado pelo launcher (jogos, servidores) | -| `RobloxServer.Web/Install` | `/install/version.ashx` e `/install/download.ashx`: clientes 2012M/2013M e o launcher | -| `RobloxServer.Starter/` | `IniciarServidor.exe`: inicia o site no IIS Express e mantém o PC ligado | -| `RobloxServer.Web/App_Data` | Templates dos scripts; em execução guarda usuários, places, chaves e logs | -| `pi/` | Instalação do Raspberry Pi Zero 2W (port forwarding, UPnP, DNS, nginx, Mono opcional) | -| `tests/smoke_test.py` | Teste de ponta a ponta usado no CI | - -### Recursos - -* Visual do roblox.com de 2013: barra azul com o logo vermelho, submenu preto, página de jogos com filtro por gênero e os ícones originais (gênero, "no gear", 13+), botões verdes/azuis do StyleGuide e rodapé azul. As imagens ficam em `RobloxServer.Web/Images`. -* Contas com cookie `.ROBLOSECURITY` (Forms Authentication, igual ao de 2015), cadastro com "sou menor de 13" (SuperSafeChat). -* Publicação de jogos pelo site (`Develop.aspx`) ou pelo Studio 2015 (`/Data/Upload.ashx`). -* `Visit.ashx`, `Join.ashx` e `GameServer.ashx` assinados com `--rbxsig` (RSA 1024 + SHA-1); os scripts de jogo 2012M/2013M - (`App_Data/Templates/Join.lua` e `GameServer.lua`) seguem a conexão clássica do RBXPri/Novetus (licença MIT). -* Botão **Play** que abre o RobloxPlayerLauncher (`robloxserver-player:`), com a janela "Starting ROBLOX...". Não há mais botão **Host Server**: quem hospeda é o [bot HOST](#modo-bot-servidores-247). -* Clientes e launcher distribuídos pelo próprio site (`/install/`), com versão pelo SHA-256; a página Admin mostra o que está instalado. -* `PlaceLauncher.ashx`, tickets de autenticação de uso único, `Login/Negotiate.ashx`, `ValidateTicket.ashx`. -* `/GetAllowedMD5Hashes/` e `/GetAllowedSecurityVersions/` com `apiKey`. -* Master server compatível com Novetus (`list.php`, `delist.php`, `serverlist.txt`). -* Moderação estilo 2015: ban por tempo / "Account Deleted", página `NotApproved.aspx`, ban de IP, filtro de palavras. -* Limite de requisições por IP (45 a cada 30 s, o mesmo do servidor antigo) e flood check no login. -* Assets que não estão no servidor são redirecionados para o `assetdelivery.roblox.com`, como antes. - -Veja [docs/security-2015.md](docs/security-2015.md) para a lista completa das medidas de segurança (e das fraquezas mantidas de propósito). - -### Login com Facebook - -1. Em developers.facebook.com crie um app, adicione o produto **Facebook Login** e em *Valid OAuth Redirect URIs* coloque `https://SEUSITE/Login/Facebook.ashx` (o mesmo endereço do `BaseUrl`). -2. No `Web.config` defina ``. -3. O **segredo do app não vai no Git**: defina a variável de ambiente `ROBLOXSERVER_FACEBOOK_SECRET` no servidor (e reinicie o site). -4. Os botões "Login with Facebook" só aparecem quando o `FacebookAppId` está definido. Quem entra pela primeira vez ganha uma conta nova (usuário gerado a partir do nome); quem já está logado e clica no botão liga o Facebook à conta atual. A primeira conta do site (admin) precisa ser criada com usuário e senha. - -### Modo bot (servidores 24/7) - -Todo jogo publicado (os que já existem e os novos) ganha um servidor mantido por um bot chamado **HOST**, sem ninguém -clicar em nada. O bot usa a mesma função do antigo botão *Host Server* (`GameStarter.Host` do launcher), só que sem entrar no jogo. - -* **Conta HOST:** criada sozinha na primeira vez, sem senha utilizável. Só ela pode hospedar (`PlaceService.CanHost`), e isso vale - também no servidor, não só na tela. Crie a primeira conta (administradora) antes de iniciar os bots. -* **Lista de jogos:** o launcher pergunta ao site (`Game/Servers.ashx?action=botlist`) a cada 60 s. Jogo novo ganha servidor em cerca de - um minuto, jogo apagado perde o dele. Se o servidor cair, ou o site reiniciar e esquecer o job, o bot sobe de novo (com espera crescente). -* **Portas UDP:** uma por jogo, a partir da `53641`, fixas em `App_Data/BotPorts.txt`. Libere-as no firewall e no roteador - (o Cloudflare Tunnel leva só HTTP/HTTPS, não UDP). Para quem está fora da rede, defina `PublicGameAddress` no `Web.config`. -* **Ajustes opcionais** em `App_Data/BotServers.txt` (uma linha cada): - `placeId;porta;endereco` define porta e endereço de um jogo, `!placeId` desliga o bot desse jogo, `#` é comentário. -* **Peso:** cada jogo é um cliente Roblox rodando 24/7. Em máquina fraca use `!placeId` nos jogos que não precisam ficar abertos. - -**Chave dos bots.** Nada secreto vai para o Git. Três formas, da mais simples à mais flexível: - -| Situação | Como | -| --- | --- | -| Launcher na **mesma máquina** do site | `RobloxPlayerLauncher.exe --bot-host --site-dir C:\caminho\RobloxServer.Web` (usa `http://localhost:8080/` se não passar `--site`). A chave aleatória fica em `App_Data/bot.key`, criada pelo site ou pelo launcher; apague o arquivo para trocar. | -| Launcher em **outra máquina** | Pareamento único: logado como admin, abra `https://seusite/Game/Servers.ashx?action=botpaircode` (código de 10 min, uso único). Na outra máquina: `RobloxPlayerLauncher.exe --pair --site https://seusite/ --code CODIGO`. Depois: `RobloxPlayerLauncher.exe --bot-host --site https://seusite/`. | -| Chave que você já tem | `RobloxPlayerLauncher.exe --set-key CHAVE` guarda a chave criptografada com o DPAPI do Windows (`%LocalAppData%\RobloxServer\bot.key`, só aquele usuário daquela máquina decifra). `--api-key CHAVE` também funciona, mas deixa a chave no atalho. | - -Cada máquina pareada recebe uma chave própria; o site guarda só o hash em `App_Data/bot-keys.txt`. Para revogar uma máquina, apague a linha dela. -O pareamento exige `https://` (ou rede local), porque a chave viaja na resposta. - -**Deixar rodando 24/7:** o `IniciarServidor.exe` e o launcher são apps de janela e precisam de uma sessão do Windows aberta -(não rodam bem como serviço). Use login automático e uma Tarefa Agendada "ao fazer logon" para cada um, e desligue a suspensão. O script `hospedagem/instalar-hospedagem.ps1` (PowerShell como Administrador) faz isso tudo: cria as duas tarefas, libera as portas UDP dos bots, desliga a suspensão (inclusive ao fechar a tampa) e, se você passar `-TunnelToken`, instala o Cloudflare Tunnel como serviço. `-Remove` desfaz. - -Para uma máquina **sem tela e sem teclado**, `hospedagem/pendrive/` monta um pendrive que instala o Windows 10 sozinho e já liga tudo (veja `montar-pendrive.ps1`). A senha do Windows nunca vai para o Git: ela é passada no script. - -### Rodando - -* **Windows / IIS:** [docs/setting-up.md](docs/setting-up.md) -* **Raspberry Pi Zero 2W:** [docs/raspberry-pi.md](docs/raspberry-pi.md) - -### Downloads - -* Roblox Studio 2015 Version (January 14): - + [~~Download Point 1 - Archive~~](https://archive.frickinfire.com/ROBLOX/Executables/Windows/RobloxStudio/ROBLOXArchive/2015/January%2014/) - + [~~Download Point 2 - Ufile~~](https://ufile.io/2n226a2u) - + [~~Download Point 3 - Mediafire~~](https://www.mediafire.com/file/sipe7u37cn3f3l8/January_14.tar/file) - + [Download Point 4 - CatBox](https://files.catbox.moe/xdux95.7z) - -* Fiddler Classic (opcional, só para os executáveis oficiais de 2015; o DNS do Raspberry Pi cuida do resto): - + [Download Point 1 - Telerik](https://www.telerik.com/download/fiddler) From 5f706c52ec3fe666b17722438eee35a99bc80788 Mon Sep 17 00:00:00 2001 From: V0rtexLinux Date: Thu, 8 Oct 2026 15:50:23 -0300 Subject: [PATCH 3/4] Delete README.md --- README.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 8b13789..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ - From e194f5523e62e74312316509c52d65d6d5b5eb06 Mon Sep 17 00:00:00 2001 From: V0rtexLinux Date: Thu, 8 Oct 2026 15:55:07 -0300 Subject: [PATCH 4/4] Create README.md --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..f711146 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +## test