open-toontown/otpgo/lua/ToontownClient.lua

212 lines
7.1 KiB
Lua

package.path = package.path .. ";lua/?.lua"
local date = require('date')
function readAccountBridge()
local json = require("json")
local io = require("io")
-- TODO: Custom path.
f, err = io.open("databases/accounts.json", "r")
if err then
print("ToontownClient: Returning empty table for account bridge")
return {}
end
decoder = json.new_decoder(f)
result, err = decoder:decode()
f:close()
assert(not err, err)
print("ToontownClient: Account bridge successfully loaded.")
return result
end
ACCOUNT_BRIDGE = readAccountBridge()
function saveAccountBridge()
local json = require("json")
local io = require("io")
-- TODO: Custom path.
f, err = io.open("databases/accounts.json", "w")
assert(not err, err)
encoder = json.new_encoder(f)
err = encoder:encode(ACCOUNT_BRIDGE)
assert(not err, err)
end
-- Load message types
dofile("lua/MsgTypes.lua")
function receiveDatagram(client, dgi)
-- Client received datagrams
msgType = dgi:readUint16()
if msgType == CLIENT_HEARTBEAT then
client:handleHeartbeat()
elseif msgType == CLIENT_DISCONNECT then
client:handleDisconnect()
elseif msgType == CLIENT_LOGIN_TOONTOWN then
handleLoginToontown(client, dgi)
-- We have reached the only message types unauthenticated clients can use.
elseif not client:authenticated() then
client:sendDisconnect(CLIENT_DISCONNECT_GENERIC, "First datagram is not CLIENT_LOGIN_TOONTOWN", true)
else
client:sendDisconnect(CLIENT_DISCONNECT_GENERIC, string.format("Unknown message type: %d", msgType), true)
end
if dgi:getRemainingSize() ~= 0 then
client:sendDisconnect(CLIENT_DISCONNECT_OVERSIZED_DATAGRAM, string.format("Datagram contains excess data.\n%s", tostring(dgi)), true)
end
end
function handleLoginToontown(client, dgi)
local playToken = dgi:readString()
local version = dgi:readString()
local hash = dgi:readUint32()
local tokenType = dgi:readInt32()
local wantMagicWords = dgi:readString()
if client:authenticated() then
client:sendDisconnect(CLIENT_DISCONNECT_RELOGIN, "Authenticated client tried to login twice!", true)
return
end
-- Check if version and hash matches
if version ~= SERVER_VERSION then
client:sendDisconnect(CLIENT_DISCONNECT_BAD_VERSION, string.format("Client version mismatch: client=%s, server=%s", version, SERVER_VERSION), true)
return
end
if hash ~= DC_HASH then
client:sendDisconnect(CLIENT_DISCONNECT_BAD_VERSION, string.format("Client DC hash mismatch: client=%d, server=%d", hash, DC_HASH), true)
return
end
-- TODO: Make these configurable.
local speedChatPlus = true
local openChat = true
local isPaid = true
local dislId = 1
local linkedToParent = false
local accountId = ACCOUNT_BRIDGE[playToken]
if accountId ~= nil then
-- Query the account object
client:getDatabaseValues(accountId, "Account", {"ACCOUNT_AV_SET", "CREATED", "LAST_LOGIN"}, function (doId, success, fields)
if not success then
client:sendDisconnect(CLIENT_DISCONNECT_ACCOUNT_ERROR, "The Account object was unable to be queried.", true)
return
end
-- Update LAST_LOGIN
fields.LAST_LOGIN = os.date("%a %b %d %H:%M:%S %Y")
client:setDatabaseValues(accountId, "Account", {
LAST_LOGIN = fields.LAST_LOGIN,
})
loginAccount(client, fields, accountId, playToken, openChat, isPaid, dislId, linkedToParent, speedChatPlus)
end)
else
-- Create a new account object
local account = {
-- The rest of the values are defined in the dc file.
CREATED = os.date("%a %b %d %H:%M:%S %Y"),
LAST_LOGIN = os.date("%a %b %d %H:%M:%S %Y"),
}
client:createDatabaseObject("Account", account, DATABASE_OBJECT_TYPE_ACCOUNT, function (accountId)
if accountId == 0 then
client:sendDisconnect(CLIENT_DISCONNECT_ACCOUNT_ERROR, "The Account object was unable to be created.", false)
return
end
-- Store the account into the bridge
ACCOUNT_BRIDGE[playToken] = accountId
saveAccountBridge()
account.ACCOUNT_AV_SET = {0, 0, 0, 0, 0, 0}
client:writeServerEvent("account-created", "ToontownClient", string.format("%d", accountId))
loginAccount(client, account, accountId, playToken, openChat, isPaid, dislId, linkedToParent, speedChatPlus)
end)
end
end
function loginAccount(client, account, accountId, playToken, openChat, isPaid, dislId, linkedToParent, speedChatPlus)
-- Eject other client if already logged in.
local ejectDg = datagram:new()
client:addServerHeaderWithAccountId(ejectDg, accountId, CLIENTAGENT_EJECT)
ejectDg:addUint16(100)
ejectDg:addString("You have been disconnected because someone else just logged in using your account on another computer.")
client:routeDatagram(ejectDg)
-- Subscribe to our puppet channel.
client:subscribePuppetChannel(accountId, 3)
-- Set our channel containing our account id
client:setChannel(accountId, 0)
client:authenticated(true)
-- Store the account id and avatar list into our client's user table:
local userTable = client:userTable()
userTable.accountId = accountId
userTable.avatars = account.ACCOUNT_AV_SET
userTable.playToken = playToken
userTable.isPaid = isPaid
userTable.speedChatPlus = speedChatPlus
userTable.openChat = openChat
client:userTable(userTable)
-- Log the event
client:writeServerEvent("account-login", "ToontownClient", string.format("%d", accountId))
-- Prepare the login response.
local resp = datagram:new()
resp:addUint16(CLIENT_LOGIN_TOONTOWN_RESP)
resp:addUint8(0) -- Return code
resp:addString("All Ok")
resp:addUint32(dislId) -- accountNumber
resp:addString(playToken) -- accountName
resp:addUint8(1) -- accountNameApproved
if openChat then
resp:addString('YES') -- openChatEnabled, does not seem to be used
else
resp:addString('NO') -- openChatEnabled, does not seem to be used
end
resp:addString('YES') -- createFriendsWithChat
resp:addString('YES') -- chatCodeCreationRule
resp:addUint32(os.time()) -- sec
resp:addUint32(os.clock()) -- usec
if isPaid then
resp:addString("FULL") -- access
else
resp:addString("VELVET") -- access
end
if speedChatPlus then
resp:addString("YES") -- WhiteListResponse
else
resp:addString("NO") -- WhiteListResponse
end
resp:addString(os.date("%Y-%m-%d %H:%M:%S")) -- lastLoggedInStr
resp:addInt32(math.floor(date.diff(account.LAST_LOGIN, account.CREATED):spandays())) -- accountDays
if linkedToParent then
resp:addString("WITH_PARENT_ACCOUNT") -- toonAccountType
else
resp:addString("NO_PARENT_ACCOUNT") -- toonAccountType
end
resp:addString(playToken) -- userName
-- Dispatch the response to the client.
client:sendDatagram(resp)
end