UserInputService changes Enum values when crossing client-server boundary

If I try to use remote events to pass UserInputService Enum values to the server when they are collected on the client, the server maps keys to different locations. This happens in “offline” testing, e.g. a local server test. Each key maps to a single different Enum value when it crosses the boundary.

This appears to be a problem with enums in general, not just KeyCode. Here’s an example that sends an enum from the client to the server and back.

Server:

local Remote = Instance.new('RemoteFunction',Workspace)
print('client-to-server | original')
function Remote.OnServerInvoke(player,enum,name,value)
	print(enum.Name,enum.Value,'|',name,value)
	return enum,enum.Name,enum.Value,name,value
end

Client:

local enum = Enum.NormalId

local Remote = Workspace:WaitForChild('RemoteFunction')
print('server-to-client | client-to-server | original')
for i,v in pairs(enum:GetEnumItems()) do
	local enum,name,value,oname,ovalue = Remote:InvokeServer(v,v.Name,v.Value)
	print(enum.Name,enum.Value,'|',name,value,'|',oname,ovalue)
end

I made a test that sends every enum from the client to the server, and checks whether each of the enum’s items are correct. Some very nasty errors occurred for a few enums. Here are the results:

The good: any enums not mentioned.

The bad: AASamples, AnimationPriority, Antialiasing, Button, CameraType, ControlMode, FilterResult, GraphicsMode, HumanoidStateType, InputType, JointType, KeyCode, LevelOfDetailSetting, Material, MeshType, NameOcclusion, NormalId, PrismSides, PrivilegeType, PyramidSides, SaveFilter, Shadow, TextXAlignment, ThreadPoolConfig, and UserInputType.

The ugly:
[ul]
[li]CenterDialogType
Produces the following error on the server:

Enum value overflow on CenterDialogType, size 4, index 0. Set to 0. (Are you using an outdated client?)

[/li]
[li]D3DDEVTYPE
Produces the following error on the server:

Enum value overflow on D3DDEVTYPE, size 4, index 0. Set to 0. (Are you using an outdated client?)

[/li]
[li]D3DFORMAT
The worst of all. Disconnects the client. Produces the following error on the client:

SendData: rbx::bad_placement_any_cast: failed conversion using rbx::placement_cast

And if the enum is sent early enough, we get the following messages on the server:

Error while processing packet.
Error while processing packet: deserializeEnum failed (packet id: 131, packet length: 50)

[/li]
[/ul]

I haven’t completely tested server-to-client, but I do know that it’s pretty much the same problem.

After the developer console was released, I noticed that this pops up in the server output in every game.

This is what happens if you miss a single type in a unit test…

local sres = workspace.FunctionSerialize:InvokeClient(player1,
	true,
	2,
	1.5,
	"test",
	BrickColor.new("Black"),
	{ foo="bar" },
	Vector3.new(1, 2, 3),
	Vector2.new(1, 2),
	Vector3int16.new(1, 2, 3),
	Vector2int16.new(1, 2),
	Color3.new(1, 0.5, 0.25),
	CFrame.new(1, 0.5, 0.25),
	Ray.new(Vector3.new(1, 2, 3), Vector3.new(0, -1, 0)),
	UDim.new(1, 2),
	UDim2.new(1, 2, 3, 4),
	Axes.new(Enum.Axis.X, Enum.Axis.Y, Enum.Axis.Z),
	Faces.new(Enum.NormalId.Right),
	Region3.new(Vector3.new(1, 2, 3), Vector3.new(4, 5, 6)),
	Region3int16.new(Vector3int16.new(1, 2, 3), Vector3int16.new(4, 5, 6)),
	workspace.BasePlate,
	Enum.Material.Fabric, -- This line was not here before :(
	{},
	function () end,
	newproxy(true), -- userdata
	""
	)

A fix for regular enum replication should ship in two weeks.
The enums that the server does not know about (see Merely’s list) could still be broken.

You should also consider the containers (i.e. [tt]Enum.Material[/tt]) as well, since those are values that can be passed around as much as anything else.

Containers “just work” - container of primitive types uses the same serialization code as the primitive types.
In fact, this test gets a tuple from the function, so containment is tested as a part of tuple serialization.

Glad I’m not the only one seeing this.