Super-Nostalgia-Zone/Bevels/Program.cs

373 lines
11 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Windows.Forms;
using RobloxFiles;
using RobloxFiles.Enums;
using RobloxFiles.DataTypes;
using Microsoft.Win32;
using System.Text.RegularExpressions;
namespace BevelGenerator
{
class Program
{
private const string uploadNewMesh = "https://data.roblox.com/ide/publish/UploadNewMesh";
private static string bevelCacheDir;
private static string studioCookies = "";
private static string xCsrfToken = "FETCH";
private static Random rng = new Random();
static void addNoise(ref float value)
{
float noise = (float)(-1e-6 + (rng.NextDouble() * 2e-6));
value += noise;
}
static Mesh CreateBevelMesh(Vector3 size, bool reupload = false)
{
byte[] baseMesh = Properties.Resources.Bevels;
Mesh bevels = Mesh.FromBuffer(baseMesh);
float sx = (size.X - 1f) / 2f,
sy = (size.Y - 1f) / 2f,
sz = (size.Z - 1f) / 2f;
if (reupload)
{
addNoise(ref sx);
addNoise(ref sy);
addNoise(ref sz);
}
foreach (Vertex vert in bevels.Verts)
{
Vector3 pos = vert.Position;
float x = pos.X;
x += (x >= 0 ? sx : -sx);
float y = pos.Y;
y += (y >= 0 ? sy : -sy);
float z = pos.Z;
z += (z >= 0 ? sz : -sz);
vert.Position = new Vector3(x, y, z);
}
return bevels;
}
static long UploadMesh(Mesh mesh, string name, string desc)
{
string uploadName = WebUtility.UrlEncode(name);
string uploadDesc = WebUtility.UrlEncode(desc);
string address = $"{uploadNewMesh}?name={uploadName}&description={uploadDesc}";
var request = WebRequest.Create(address) as HttpWebRequest;
request.Method = "POST";
request.ContentType = "*/*";
request.UserAgent = "RobloxStudio/WinInet";
request.Headers.Set("Cookie", studioCookies);
request.Headers.Set("X-CSRF-TOKEN", xCsrfToken);
using (Stream writeStream = request.GetRequestStream())
{
mesh.Save(writeStream);
writeStream.Close();
}
HttpWebResponse response = null;
try
{
var result = request.GetResponse();
response = result as HttpWebResponse;
}
catch (WebException e)
{
response = e.Response as HttpWebResponse;
if (response.StatusDescription.Contains("XSRF"))
{
// Update the X-CSRF-TOKEN.
xCsrfToken = response.Headers.Get("X-CSRF-TOKEN");
// Retry the upload.
return UploadMesh(mesh, name, desc);
}
else
{
throw e;
}
}
long assetId = -1;
using (Stream stream = response.GetResponseStream())
{
byte[] data;
using (MemoryStream buffer = new MemoryStream())
{
stream.CopyTo(buffer);
data = buffer.ToArray();
}
string strAssetId = Encoding.ASCII.GetString(data);
assetId = long.Parse(strAssetId);
}
return assetId;
}
static string ProcessInput(string input)
{
float[] xyz;
try
{
var inputData = input.Split('~', ',', ' ')
.Select(str => str.Trim())
.Where(str => str.Length > 0)
.Select(str => Format.ParseFloat(str));
if (inputData.Count() != 3)
throw new Exception();
xyz = inputData.ToArray();
input = Format.FormatFloats(xyz);
}
catch
{
Console.WriteLine("\tInvalid input: {0}", input);
return null;
}
string name = $"Bevel{input}";
const string desc = "[AUTO-GENERATED BEVEL MESH]";
string cached = Path.Combine(bevelCacheDir, $"{name}.txt");
bool exists = File.Exists(cached);
bool moderated = false;
if (exists)
{
string contents = File.ReadAllText(cached);
moderated = (contents.ToLower() == "moderated");
}
if (moderated || !exists)
{
Vector3 size = new Vector3(xyz);
Mesh mesh = CreateBevelMesh(size, moderated);
Console.WriteLine("\tUploading {0}...", name);
long assetId = UploadMesh(mesh, name, desc);
File.WriteAllText(cached, assetId.ToString());
}
string result = "rbxassetid://" + File.ReadAllText(cached);
Console.WriteLine(" Result -> {0} (copied to clipboard)", result);
return result;
}
static void ProcessModelFile(string filePath)
{
RobloxFile file = RobloxFile.Open(filePath);
var exportBin = file.FindFirstChild<Folder>("ExportBin");
if (exportBin != null)
exportBin.Name = "BevelCache";
else
return;
var unions = exportBin.GetChildrenOfType<UnionOperation>();
for (int i = 0; i < unions.Length; i++)
{
UnionOperation union = unions[i];
string name = union.Name;
Console.WriteLine("Working on {0}... ({1}/{2})", union.Name, i, unions.Length);
string meshId = ProcessInput(name);
if (meshId == null)
continue;
MeshPart bevelMesh = new MeshPart()
{
Name = name,
MeshId = meshId,
Size = union.Size,
InitialSize = union.InitialSize,
RenderFidelity = RenderFidelity.Automatic,
PhysicsData = union.PhysicsData,
PhysicalConfigData = union.PhysicalConfigData,
};
foreach (string tag in union.Tags)
bevelMesh.Tags.Add(tag);
bevelMesh.Parent = exportBin;
union.Parent = null;
}
using (FileStream export = File.OpenWrite(filePath))
file.Save(export);
if (file is XmlRobloxFile)
Process.Start(filePath);
Console.WriteLine("Finished processing order!");
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
Console.Clear();
}
static Mesh PortObjFile(FileInfo info)
{
Console.WriteLine("Reading obj file...");
string objPath = info.FullName;
Mesh mesh = Mesh.FromObjFile(objPath);
Console.WriteLine("Writing mesh file...");
string extension = info.Extension;
string filePath = objPath.Replace(extension, ".mesh");
using (FileStream file = File.OpenWrite(filePath))
{
file.SetLength(0);
mesh.SaveV1(file);
}
return mesh;
}
static void ProcessFileArg(string filePath)
{
FileInfo info = new FileInfo(filePath);
switch (info.Extension)
{
case ".obj":
Mesh export = PortObjFile(info);
Console.Write("Would you like to upload this mesh? (y/n): ");
string answer = Console.ReadLine();
if (answer.ToLower()[0] == 'y')
{
Console.Write("Enter a name for this mesh: ");
string name = Console.ReadLine();
Console.Write("Enter a description for this mesh: ");
string desc = Console.ReadLine();
Console.WriteLine("Uploading mesh...");
long result = 0;
try
{
result = UploadMesh(export, name, desc);
}
catch (Exception e)
{
Console.WriteLine($"An error occurred while uploading: {e.Message}");
}
if (result > 0)
{
Clipboard.SetText($"rbxassetid://{result}");
Console.WriteLine($"Result -> rbxassetid://{result} (copied to clipboard)");
}
Debugger.Break();
}
break;
case ".bin":
case ".mesh":
Mesh import = Mesh.FromFile(filePath);
Debugger.Break();
break;
default:
ProcessModelFile(filePath);
break;
}
}
[STAThread]
static void Main(string[] args)
{
string localAppData = Environment.GetEnvironmentVariable("LocalAppData");
bevelCacheDir = Path.Combine(localAppData, "BevelCache");
if (!Directory.Exists(bevelCacheDir))
Directory.CreateDirectory(bevelCacheDir);
RegistryKey robloxCookies = Registry.CurrentUser.GetSubKey
(
"SOFTWARE", "Roblox",
"RobloxStudioBrowser",
"roblox.com"
);
foreach (string name in robloxCookies.GetValueNames())
{
string cookie = robloxCookies.GetString(name);
Match match = Regex.Match(cookie, "COOK::<([^>]*)>");
if (match.Groups.Count > 1)
{
cookie = match.Groups[1].Value;
if (studioCookies.Length > 0)
studioCookies += "; ";
studioCookies += $"{name}={cookie}";
}
}
if (args.Length > 0)
{
string filePath = args[0];
ProcessFileArg(filePath);
}
Console.WriteLine("Enter bevel sizes in format: 'X ~ Y ~ Z' to generate them as .mesh files!");
Console.WriteLine(" (Also accepts format: 'X, Y, Z' and 'X Y Z')");
while (true)
{
Console.Write("> ");
string inputLine = Console.ReadLine();
if (inputLine == "exit")
break;
string assetId = ProcessInput(inputLine);
Clipboard.SetText(assetId);
}
}
}
}