Any ideas for a custom level loading system?

Hello everyone,

I need help when it comes to loading custom user made levels.

Currently, I have a plugin which helps with the creation of the custom levels, however when it comes to transferring that level from studio and into the game, I have a couple problems.

My first idea was to make the user upload the level (which is a model, btw) to ROBLOX and for me to use InsertService to retrieve that model. Unfortunately there are restrictions when it comes to InsertService:LoadAsset, which restrict me from inserting the level in-game.

My second, and most probable idea is to use a serializer, I would give the serialized data to the user, and load the level in-game by the user passing that data to the server.
I don’t like this idea because of unknown issues that could appear, also, an issue is inputting the data, if I were to use TextBoxes to capture the data, their .Text property has a 200K+ character limit, which could be a disaster for large levels.

So, does anybody have any ideas on how I could make a custom level loading system? Does anybody know any alternatives to my first idea?

2 Likes

Is your goal to allow the player to upload their level while in game? if not, then one thing you can do is to add the level to the game while in studio and put it inside ServerStorage.

I’m not sure I entirely understood your question, if you meant if my goal was to allow players to create their levels while in my game, then no.


I have a Studio plugin, which assists in making the level in Studio.

For the user to create a level for my game, they would need to edit/create that level in Studio.

My goal is to transfer that level from Studio into the actual game, note that the place that the user is editing the level in is their own place, which is not in any way related to my account, thus I couldn’t use InsertService.

Sorry, I should’ve probably clarified what I meant in my first comment. In my first comment, I was asking if the user would make the level in studio first, not create their level in your game.

Due to a Roblox update, I don’t think it’s possible anymore to insert an asset you don’t own into your game using InsertService while the game is running. The only way that would be possible would be for you to own the model and have it in your inventory. Then, it would be possible for a user to use InsertService to view their model while in a live game.

Also, I would be careful because a user’s model could contain malicious scripts. If your plan involved allowing the player’s level to have custom made scripts, then I’d recommend checking the models.

1 Like

Uhh… So probably the only way I could do it this way is by making some sort of bot it seems…

I was actually thinking about that at some point, I even have an idea of sending the model to a different “quarantine” place, in which my system would serialize the model (and leave out any unwanted Instances, such as scripts) and send a “clean” version back to the main server.

However, before I worry about that, I would need a way to transfer the level first.


Anyways, thanks for the help!

Until Roblox fixes the TextBox limit, the best option would be for the user to upload the serialized data to a paste service like Pastebin and give the server the URL.

1 Like

Unfortunately, Roblox also has a system in place that puts a cooldown on the number of assets you can buy at a single given time. This was to prevent people like kids from spam buying items from the catalog. Just letting you know about that if you did make a bot.

1 Like

Hmm, that’s seems like a great idea, I’ll do some research on some paste services.

My only concern with that could be Roblox’s TOS, I don’t know if linking users off-site would be permissible in this case, it’s most likely fine, or maybe I could automatically upload the serialized data using HttpService, I’ll take a look at it a bit later.

Well, I didn’t know that, thanks for the info! I’ll probably look into paste services rather than creating a bot. I might also create a Feature Request tomorrow regarding InsertService.

You could do something like what FE2:CM and TRIA.os do. A user can join the game, insert an id, and if the user created the model and it is public then it automatically sends a request to an external server, which will then download the model on a bot account. This also has the bonus that they can immediately make it private, preventing others from stealing the map once released. Unfortunately, this seems to require storing the .ROBLOSECURITY token, which would allow someone to steal your account if stolen. This is why both of these games are run on a second account.

OAuth 2.0 has promise, but it seems to not support downloading models, only uploading/updating them. You could theoretically download the contents of the free model using an external service then reupload/update them using OAuth or Open Cloud, however this would require users to upload it using your game every time they want to update the map, while with a model owned by the user (such as in FE2:CM and TRIA.os), they just have to update the model to update the map. You could make a feature request to add taking models to OAuth so that you do not need to store the security token of the account.

A third idea would be to have the users upload their maps as a MainModule that then returns the model, as MainModules don’t need to be owned by the creator of the game in order to require them. This has the downsides that it will be permanently public, so anyone could steal the map, and that it requires running untrusted code to retrieve the map (although if you are inserting untrusted models, then that probably isn’t a concern).

2 Likes

I’ve did some research on this method, however I unfortunately don’t have time and the resources to host an external server/bot, I also wouldn’t want to rely on any free hosting services.

I’ll do some research tomorrow and look into making a feature request for that.

This is actually the idea I am currently experimenting with. Funny story, but at the time of this reply I’ve already implemented 50% of this idea and it seems as this could be the way to go without any 3rd-party services, however when it comes to this, doesn’t the privacy setting work the same for the first method and the third method you mentioned, or am I missing something here…?

Couldn’t the user do the same, regardless if it was a model or a module script, couldn’t they immediately make the asset private, preventing others from stealing the source once released?

In my implementation of this, the user would need to give the server the serialized data only once, since I’ll be saving that data in data stores for later use.

I was thinking of any ways a malicious actor could exploit with this method, and inserting malicious code came into my mind, right now, my idea is to require the module script on the client (which I am not entirely sure is possible at the time of writing this).

If requiring module scripts is possible on the client using LocalScripts (which I don’t see why it wouldn’t be possible), then locally I would fetch the module, extract the passed serialized data, along with some extra data, and then pass that onto the server, which would form the model and regulate any special rules that I’ve set in place.

I’ll do all this in an separate server in order to minimize any damage that could be done.

Ah, if that is the case then neither OAuth nor Open Cloud will work for you, as it would require you to store the private keys in the plugin which would allow anyone to access them.

I misunderstood how your game worked, and assumed you wanted users to be able to update their map without having to enter the game. This does make it easier (it makes using Open Cloud possible, however you already said you don’t want to use an external server), and doesn’t have the concern about maps being stolen as the model doesn’t need to be permanently public.

require(assetId) cannot be used on the client, most likely because it would allow exploiters to get access to otherwise private MainModules if the owner of the game owns the model.

Unfortunately, I can’t think of any way to obtain the serialized info, other than using a third party service or external server. I will try to think of something, but I don’t think I will come up with anything. Perhaps if the serialized string is >200k bytes then split it into multiple strings?

1 Like

Oh… That just crushed my current idea, well time for a change of plans. It seems as a 3rd party service needs to be used in this case.

That could be possible, however that would result in a huge amount of different strings for huge huge levels, the current serializer I am using outputs 4.5K+ characters for a single R6 humanoid model (with 3 hats equipped, which is approx. 40 instances serialized)

I’ll do some more research on this whole topic, my current plan is using a 3rd-party paste service to temporary host the data and if that doesn’t work out I’ll directly give the user the serialized data in order to transfer the level to the game itself.

Currently, the biggest pain in all this is keeping API keys a secret, however since I am working with a plugin here, that is unfortunately impossible or very hard to achieve… I never expected this much pain from just transferring the level from studio to the actual game, atm I am thinking of just passing the user the raw serialized data since I can’t stand this anymore…

I have thought of a roundabout way to transfer data from a MainModule to your game securely using teleport data. The idea is to require the MainModule in a separate universe to prevent any manipulation of datastores or the like.

First, have the plugin save the data in a MainModule somehow and have the player upload this as a model.

Next, instruct the player to join a separate game in a different universe (it MUST be in a different universe, otherwise you could inject code and modify datastores or things like that). This game would have solo servers so that any injected code would not affect others.

In this game, tell them to insert the id of the model and call require(assetId) on the server and get the serialized data. Send this data to the client via something like a RemoteEvent (note: IIRC RemoteEvents have data limits, so you may need to separate large data into several calls and recombine it on the client, but that shouldn’t be too hard to do). When the client has this data, call TeleportService:Teleport and teleport them to the starting place of the actual game, putting the serialized data inside of the teleport data. It is important that you use TeleportService:Teleport on the client, as if you do it on the server then it tries to send the teleport data to the client, but if it is too large then the teleport fails. Doing the teleport on the client removes this need, allowing you to send seemingly unlimited data.

Finally, inside of the starting place of the actual game, when a player joins have the client check TeleportService:GetLocalPlayerTeleportData and see if it is a serialized map. If it is, then you could either directly send the data directly to the server they are in or teleport them to a sub-place if needed (such as to prevent DDoS attacks by sending massive maps to the server to crash it), passing the teleport data with the second teleport as well. When sending the data with a RemoteEvent, again be aware of data size limits per request.

Note that you MUST use TeleportService:GetLocalPlayerTeleportData and not Player:GetJoinData, as Player:GetJoinData will not include the teleport data if the teleport originated from outside of the universe or if it was set on the client.

Since this data is being passed through the client, it could be spoofed by exploiters (or even just teleporting from a place the person owns), so do any sanity checks after receiving the data on the server in the real game.

This method seems to work. I tested it with the following code in two LocalScripts in two separate universes.
Place 1 (substitute PLACE_ID with the place id of Place 2):

local TeleportService = game:GetService("TeleportService")

-- testString is approaching the maximum size a string can be, so I didn't test any larger
local testString = string.rep("abcdefghijklmnopqrstuvwxyz", 40000000) -- Length of 1040000000

-- Several variants are stored to test possible data size limits
local testTeleportData = {
	Test1 = testString,
	Test2 = string.upper(testString),
	Test3 = string.reverse(testString),
	Test4 = testString,
}

TeleportService:Teleport(PLACE_ID, nil, testTeleportData)

Place 2:

local TeleportService = game:GetService("TeleportService")

local data = TeleportService:GetLocalPlayerTeleportData()

if data then
	print("data exists")
	
	print("Test1 Length:", #data.Test1)
	print("Test2 Length:", #data.Test2)
	print("Test3 Length:", #data.Test3)
	print("Test4 Length:", #data.Test4)
else
	print("no data found")
end

Although working with strings these large caused massive lag, when it finally teleported to the second place, the output was, as expected:

data exists
Test1 Length: 1040000000
Test2 Length: 1040000000
Test3 Length: 1040000000
Test4 Length: 1040000000

This method, while inconvenient, is probably the best way to do what you want without some sort of proxy to download the model.

3 Likes

Oh. My. God… You’re a genius!

I did not even once think about using teleports with the data passed thru them, what can I say… this is actually such a genius way of doing this!

For a moment, after my last message, I have given up entirely on the MainModule method, I originally had the idea of doing all this in the same universe, because if I didn’t do it in the same universe, how could I communicate between servers? However, you have just broadened my horizons with this reply.

I am currently about 70% done with the plugin, I’ve managed to compile the level into a module script, and once I am done with the plugin, I’ll start implementing this idea in-game.


Thank you so much for all the help!

I'll mark your reply as a solution, and I'll probably post a reply with some additional information once I finish all this! (If I discover anything new)

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.