diff --git a/game/engine/ButtonList.lua b/game/engine/ButtonList.lua
index 944ed5490256a1184223d8187e5f4b93de800d02..0ceeb9d7d152bce2db43ae8225653f65e849a074 100644
--- a/game/engine/ButtonList.lua
+++ b/game/engine/ButtonList.lua
@@ -106,6 +106,7 @@ function _M:select(i, offset)
 	end
 	if self.selected > #self.list then self.selected = 1 end
 	if self.selected < 1 then self.selected = #self.list end
+	print(self.list,self.selected)
 	if old ~= self.selected and self.list[self.selected].onSelect then self.list[self.selected].onSelect() end
 	self.changed = true
 end
diff --git a/game/engine/Module.lua b/game/engine/Module.lua
index 2180fc4dd7274126fe8cc91719f1c849ce0011b6..2aa87b442857160afd61d805feda5869d5bab16d 100644
--- a/game/engine/Module.lua
+++ b/game/engine/Module.lua
@@ -93,6 +93,7 @@ function _M:loadDefinition(dir, team)
 
 				fs.mount(src, "/", false)
 			end
+			profile:loadModuleProfile(mod.short_name)
 			local m = require(mod.starter)
 			print("[MODULE LOADER] loading module", mod.long_name, "["..mod.starter.."]", "::", m[1] and m[1].__CLASSNAME, m[2] and m[2].__CLASSNAME)
 			return m[1], m[2]
@@ -192,7 +193,14 @@ function _M:loadRemoteList(src)
 			end
 		end
 		setfenv(f, dmods)
-		f()
+		local ok, err = pcall(f)
+		if not ok and err then
+			print("Could not read modules list from ", src, ":", err)
+			l:send("moduleslist", {})
+			return
+		end
+
+		for k, e in ipairs(list) do print("[INSTALLABLE MODULE] ", e.name) end
 
 		l:send("moduleslist", list)
 	end
diff --git a/game/engine/PlayerProfile.lua b/game/engine/PlayerProfile.lua
new file mode 100644
index 0000000000000000000000000000000000000000..ea934b7780882591d664d15c89aa4871a89c0bf8
--- /dev/null
+++ b/game/engine/PlayerProfile.lua
@@ -0,0 +1,177 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009, 2010 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local http = require "socket.http"
+local url = require "socket.url"
+local ltn12 = require "ltn12"
+require "Json2"
+
+--- Handles the player profile, possible online
+module(..., package.seeall, class.make)
+
+function _M:init(name)
+	self.generic = {}
+	self.modules = {}
+	self.name = name or "default"
+	self:loadGenericProfile()
+
+	self.auth = false
+
+	if self.generic.online then
+		self.login = self.generic.online.login
+		self.pass = self.generic.online.pass
+		self:tryAuth()
+		self:getConfigs("generic")
+	end
+end
+
+function _M:loadData(f, where)
+	setfenv(f, where)
+	local ok, err = pcall(f)
+	if not ok and err then error(err) end
+end
+
+--- Loads profile generic profile from disk
+function _M:loadGenericProfile()
+	local d = "/profiles/"..self.name.."/generic/"
+	fs.mount(engine.homepath, "/")
+
+	for i, file in ipairs(fs.list(d)) do
+		if file:find(".profile$") then
+			local f, err = loadfile(d..file)
+			if not f and err then error(err) end
+			local field = file:gsub(".profile$", "")
+			self.generic[field] = self.generic[field] or {}
+			self:loadData(f, self.generic[field])
+		end
+	end
+
+	fs.umount(engine.homepath)
+end
+
+--- Loads profile module profile from disk
+function _M:loadModuleProfile(short_name)
+	local d = "/profiles/"..self.name.."/modules/"..short_name.."/"
+	fs.mount(engine.homepath, "/")
+
+	self.modules[short_name] = self.modules[short_name] or {}
+
+	for i, file in ipairs(fs.list(d)) do
+		if file:find(".profile$") then
+			local f, err = loadfile(d..file)
+			if not f and err then error(err) end
+			local field = file:gsub(".profile$", "")
+			self.modules[short_name][field] = self.modules[short_name][field] or {}
+			self:loadData(f, self.modules[short_name][field])
+		end
+	end
+
+	fs.umount(engine.homepath)
+
+	self:getConfigs(short_name)
+
+	self.mod = self.modules[short_name]
+	self.mod_name = short_name
+end
+
+--- Saves a profile data
+function _M:saveGenericProfile(name, data)
+	-- Check for readability
+	local f, err = loadstring(data)
+	if not f then print("[PROFILES] cannot save generic data ", name, data, "it does not parse:") error(err) end
+	local ok, err = pcall(f)
+	if not ok and err then print("[PROFILES] cannot save generic data", name, data, "it does not parse") error(err) end
+
+	local restore = fs.getWritePath()
+	fs.setWritePath(engine.homepath)
+	fs.mkdir("/profiles/"..self.name.."/generic/")
+	local f = fs.open("/profiles/"..self.name.."/generic/"..name..".profile", "w")
+	f:write(data)
+	f:close()
+	if restore then fs.setWritePath(restore) end
+
+	self:setConfigs("generic", name, data)
+end
+
+--- Saves a module profile data
+function _M:saveModuleProfile(name, data)
+	-- Check for readability
+	local f, err = loadstring(data)
+	if not f then print("[PROFILES] cannot save module data ", name, data, "it does not parse:") error(err) end
+	local ok, err = pcall(f)
+	if not ok and err then print("[PROFILES] cannot save module data", name, data, "it does not parse") error(err) end
+
+	local restore = fs.getWritePath()
+	fs.setWritePath(engine.homepath)
+	fs.mkdir("/profiles/"..self.name.."/modules/")
+	fs.mkdir("/profiles/"..self.name.."/modules/"..self.mod_name.."/")
+	local f = fs.open("/profiles/"..self.name.."/modules/"..self.mod_name.."/"..name..".profile", "w")
+	f:write(data)
+	f:close()
+	if restore then fs.setWritePath(restore) end
+
+	self:setConfigs(self.mod_name, name, data)
+end
+
+
+-----------------------------------------------------------------------
+-- Online stuff
+-----------------------------------------------------------------------
+
+function _M:rpc(data)
+	local body, status = http.request("http://te4.org/lua/profilesrpc.ws/"..data.action, "json="..url.escape(json.encode(data)))
+	if not body then return end
+	return json.decode(body)
+end
+
+function _M:tryAuth()
+	local data = self:rpc{action="TryAuth", login=self.login, pass=self.pass}
+	if not data then print("[ONLINE PROFILE] not auth") return end
+	print("[ONLINE PROFILE] auth as ", data.name)
+	self.auth = data
+end
+
+function _M:getConfigs(module)
+	if not self.auth then return end
+	local data = self:rpc{action="GetConfigs", login=self.login, pass=self.pass, module=module}
+	if not data then print("[ONLINE PROFILE] get configs") return end
+	for name, val in pairs(data) do
+		print("[ONLINE PROFILE] config ", name)
+
+		local field = name
+		local f, err = loadstring(val)
+		if not f and err then error(err) end
+		if module == "generic" then
+			self.generic[field] = self.generic[field] or {}
+			self:loadData(f, self.generic[field])
+		else
+			self.modules[module] = self.modules[module] or {}
+			self.modules[module][field] = self.modules[module][field] or {}
+			self:loadData(f, self.modules[module][field])
+		end
+	end
+end
+
+function _M:setConfigs(module, name, val)
+	if not self.auth then return end
+	local data = self:rpc{action="SetConfigs", login=self.login, pass=self.pass, module=module, name=name, data=val}
+	if not data then return end
+	print("[ONLINE PROFILE] saved ", module, name)
+end
diff --git a/game/engine/init.lua b/game/engine/init.lua
index ded3129a3ba65c36ace3637c14b843e5e9758dc8..40fdb8f4cae4bfb6a13ae95883e18fbf2c54ad46 100644
--- a/game/engine/init.lua
+++ b/game/engine/init.lua
@@ -30,6 +30,7 @@ require "engine.interface.GameMusic"
 require "engine.KeyBind"
 require "engine.Savefile"
 require "engine.Tiles"
+require "engine.PlayerProfile"
 engine.Tiles.prefix = "/data/gfx/"
 
 -- Engine Version
@@ -40,6 +41,7 @@ engine.homepath = fs.getUserPath()..fs.getPathSeparator()..fs.getHomePath()..fs.
 fs.setWritePath(fs.getUserPath())
 fs.mkdir(fs.getHomePath())
 fs.mkdir(fs.getHomePath().."/4.0/")
+fs.mkdir(fs.getHomePath().."/4.0/profiles/")
 fs.mkdir(fs.getHomePath().."/4.0/settings/")
 fs.setWritePath(fs.getHomePath())
 
@@ -75,4 +77,7 @@ game = false
 engine.Game:setResolution(config.settings.window.size)
 engine.interface.GameMusic:soundSystemStatus(config.settings.sound.enabled, true)
 
+-- Load profile configs
+profile = engine.PlayerProfile.new()
+
 util.showMainMenu(true)
diff --git a/game/engine/interface/WorldAchievements.lua b/game/engine/interface/WorldAchievements.lua
index c191a0076bd12579dab17e6c9712706600148219..d5c0186b253928fb9cf64525b8c16a6cbc39aa60 100644
--- a/game/engine/interface/WorldAchievements.lua
+++ b/game/engine/interface/WorldAchievements.lua
@@ -54,8 +54,17 @@ function _M:newAchievement(t)
 	print("[ACHIEVEMENT] defined", t.order, t.id)
 end
 
-function _M:init()
+function _M:loadAchievements()
 	self.achieved = {}
+
+	for k, e in pairs(profile.mod) do
+		if k:find('^achievement_') then
+			local id = k:gsub('^achievement_', '')
+			if self.achiev_defs[id] then
+				self.achieved[id] = e
+			end
+		end
+	end
 end
 
 function _M:getAchievementFromId(id)
@@ -88,6 +97,7 @@ function _M:gainAchievement(id, src, ...)
 		if not a.can_gain(data, src, ...) then return end
 	end
 
+	profile:saveModuleProfile("achievement_"..id, ("turn=%d\nwho=%q\nwhen=%q\n"):format(game.turn, self:achievementWho(src), os.date("%Y-%m-%d %H:%M:%S")))
 	self.achieved[id] = {turn=game.turn, who=self:achievementWho(src), when=os.date("%Y-%m-%d %H:%M:%S")}
 	game.log("#LIGHT_GREEN#New Achievement: %s!", a.name)
 	Dialog:simplePopup("New Achievement: #LIGHT_GREEN#"..a.name, a.desc)
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index 89417a78aab6cc186ad02ed9c0f1d5e460524c75..c31ac256f7d2230d816109ffc2c188999f2e59f0 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -503,7 +503,10 @@ function _M:setupCommands()
 
 	self.key:addCommands{
 		[{"_d","ctrl"}] = function()
-			if config.settings.tome.cheat then self:changeLevel(1, "test") end
+			if config.settings.tome.cheat then
+				self:changeLevel(1, "test")
+				self:setAllowedBuild("mage", true)
+			end
 		end,
 	}
 	self.key:addBinds
@@ -823,15 +826,15 @@ function _M:setAllowedBuild(what, notify)
 	-- Do not unlock things in easy mode
 	--if game.difficulty == game.DIFFICULTY_EASY then return end
 
-	config.settings.tome = config.settings.tome or {}
-	config.settings.tome.allow_build = config.settings.tome.allow_build or {}
-	if config.settings.tome.allow_build[what] then return false end
-	config.settings.tome.allow_build[what] = true
+	profile.mod.allow_build = profile.mod.allow_build or {}
+	if profile.mod.allow_build[what] then return end
+	profile.mod.allow_build[what] = true
+
 	local t = {}
-	for k, e in pairs(config.settings.tome.allow_build) do
-		t[#t+1] = ("tome.allow_build.%s = %s"):format(k, tostring(e))
+	for k, e in pairs(profile.mod.allow_build) do
+		t[#t+1] = ("%s = %s"):format(k, tostring(e))
 	end
-	game:saveSettings("tome.allow_build", table.concat(t, "\n"))
+	profile:saveModuleProfile("allow_build", table.concat(t, "\n"))
 
 	if notify then
 		self:registerDialog(require("mod.dialogs.UnlockDialog").new(what))
diff --git a/game/modules/tome/class/World.lua b/game/modules/tome/class/World.lua
index 2d109ce952f8762833fed674a8d549304d5aec53..bd90f904c1c422e0b7aff40f61316e579871bd7a 100644
--- a/game/modules/tome/class/World.lua
+++ b/game/modules/tome/class/World.lua
@@ -26,7 +26,10 @@ module(..., package.seeall, class.inherit(engine.World, engine.interface.WorldAc
 
 function _M:init()
 	engine.World.init(self)
-	engine.interface.WorldAchievements.init(self)
+end
+
+function _M:run()
+	self:loadAchievements()
 end
 
 --- Requests the world to save
diff --git a/game/modules/tome/data/birth/classes/wilder.lua b/game/modules/tome/data/birth/classes/wilder.lua
index e238b53a2eed5c70e9938bb8495d5c426f6a0bad..589d622cef039d2c5a9e6687ef91986ae7e67934 100644
--- a/game/modules/tome/data/birth/classes/wilder.lua
+++ b/game/modules/tome/data/birth/classes/wilder.lua
@@ -29,8 +29,8 @@ newBirthDescriptor{
 		subclass =
 		{
 			__ALL__ = "never",
-			Summoner = function() return config.settings.tome.allow_build.wilder_summoner and "allow" or "never" end,
-			Wyrmic = function() return config.settings.tome.allow_build.wilder_wyrmic and "allow" or "never" end,
+			Summoner = function() return profile.mod.allow_build.wilder_summoner and "allow" or "never" end,
+			Wyrmic = function() return profile.mod.allow_build.wilder_wyrmic and "allow" or "never" end,
 		},
 	},
 	copy = {
diff --git a/game/modules/tome/data/birth/descriptors.lua b/game/modules/tome/data/birth/descriptors.lua
index 7d7b88206ec9a9bfbd4df0d520e1ed1335ae65cf..83edfff5bf74892cfd216015b3c72eae079acd96 100644
--- a/game/modules/tome/data/birth/descriptors.lua
+++ b/game/modules/tome/data/birth/descriptors.lua
@@ -27,7 +27,7 @@ newBirthDescriptor{
 		world =
 		{
 			Arda = "allow",
-			["Spydrë"] = function() return config.settings.tome.allow_build.world_spydre and "allow" or "never" end,
+			["Spydrë"] = function() return profile.mod.allow_build.world_spydre and "allow" or "never" end,
 		},
 	},
 	talents = {},
diff --git a/game/modules/tome/data/birth/races/undead.lua b/game/modules/tome/data/birth/races/undead.lua
index 10030e20c25e71cae2cf5be92035e4434d3628bc..079196ec2fbb0242834dba2c23da3c6b93e53598 100644
--- a/game/modules/tome/data/birth/races/undead.lua
+++ b/game/modules/tome/data/birth/races/undead.lua
@@ -32,10 +32,10 @@ newBirthDescriptor{
 		subrace =
 		{
 			__ALL__ = "never",
-			Ghoul = function() return config.settings.tome.allow_build.undead_ghoul and "allow" or "never" end,
-			Skeleton = function() return config.settings.tome.allow_build.undead_skeleton and "allow" or "never" end,
-			Vampire = function() return config.settings.tome.allow_build.undead_vampire and "allow" or "never" end,
-			Wight = function() return config.settings.tome.allow_build.undead_wight and "allow" or "never" end,
+			Ghoul = function() return profile.mod.allow_build.undead_ghoul and "allow" or "never" end,
+			Skeleton = function() return profile.mod.allow_build.undead_skeleton and "allow" or "never" end,
+			Vampire = function() return profile.mod.allow_build.undead_vampire and "allow" or "never" end,
+			Wight = function() return profile.mod.allow_build.undead_wight and "allow" or "never" end,
 		},
 	},
 	copy = {
diff --git a/game/modules/tome/data/birth/worlds.lua b/game/modules/tome/data/birth/worlds.lua
index 400db81115864d836595669b4dd06e4133fafba1..b0d3a123eadbd226e816071ebb8d22998c2ee6bd 100644
--- a/game/modules/tome/data/birth/worlds.lua
+++ b/game/modules/tome/data/birth/worlds.lua
@@ -38,18 +38,18 @@ newBirthDescriptor{
 			Elf = "allow",
 			Dwarf = "allow",
 			Hobbit = "allow",
---			Orc = function() return config.settings.tome.allow_build.evil and "allow" or "never" end,
---			Troll = function() return config.settings.tome.allow_build.evil and "allow" or "never" end,
-			Undead = function() return config.settings.tome.allow_build.undead and "allow" or "never" end,
+--			Orc = function() return profile.mod.allow_build.evil and "allow" or "never" end,
+--			Troll = function() return profile.mod.allow_build.evil and "allow" or "never" end,
+			Undead = function() return profile.mod.allow_build.undead and "allow" or "never" end,
 		},
 
 		class =
 		{
 			__ALL__ = "allow",
-			Mage = function() return config.settings.tome.allow_build.mage and "allow" or "never" end,
+			Mage = function() return profile.mod.allow_build.mage and "allow" or "never" end,
 			Wilder = function() return (
-				config.settings.tome.allow_build.wilder_summoner or
-				config.settings.tome.allow_build.wilder_wyrmic
+				profile.mod.allow_build.wilder_summoner or
+				profile.mod.allow_build.wilder_wyrmic
 				) and "allow" or "never"
 			end,
 		},
@@ -71,7 +71,7 @@ newBirthDescriptor{
 		{
 			__ALL__ = "never",
 			Human = "allow",
---			Spider = function() return config.settings.tome.allow_build.spider and "allow" or "never" end,
+--			Spider = function() return profile.mod.allow_build.spider and "allow" or "never" end,
 		},
 	},
 }
diff --git a/game/modules/tome/load.lua b/game/modules/tome/load.lua
index 0299be7b4b3b5ae9e90c5cb3be1c3a27c85d957e..2b6091b5d8129ed42e5f512dd790e547417b1586 100644
--- a/game/modules/tome/load.lua
+++ b/game/modules/tome/load.lua
@@ -33,7 +33,7 @@ local Store = require "mod.class.Store"
 local WorldAchievements = require "engine.interface.WorldAchievements"
 
 config.settings.tome = config.settings.tome or {}
-config.settings.tome.allow_build = config.settings.tome.allow_build or {}
+profile.mod.allow_build = profile.mod.allow_build or {}
 
 -- Achievements
 WorldAchievements:loadDefinition("/data/achievements/")
diff --git a/game/special/mainmenu/class/Game.lua b/game/special/mainmenu/class/Game.lua
index 5785cf9bb9ed000d3da077495a6b6e6fac3943e3..0f0ee71200ab3afc25b541af230c541498b15314 100644
--- a/game/special/mainmenu/class/Game.lua
+++ b/game/special/mainmenu/class/Game.lua
@@ -29,6 +29,7 @@ local DownloadDialog = require "engine.dialogs.DownloadDialog"
 module(..., package.seeall, class.inherit(engine.Game))
 
 function _M:init()
+	self.profile_font = core.display.newFont("/data/font/VeraIt.ttf", 14)
 	engine.Game.init(self, engine.KeyBind.new())
 
 	self.background = core.display.loadImage("/data/gfx/mainmenu/background.jpg")
@@ -41,10 +42,20 @@ function _M:run()
 	-- Setup display
 	self:selectStepMain()
 
+	self:checkLogged()
+
 	-- Ok everything is good to go, activate the game in the engine!
 	self:setCurrent()
 end
 
+function _M:checkLogged()
+	if profile.auth then
+		self.s_log = core.display.drawStringNewSurface(self.profile_font, "Online Profile: "..profile.auth.name.."[http://te4.org/players/"..profile.auth.page.."]", 255, 255, 0)
+	else
+		self.s_log = nil
+	end
+end
+
 function _M:tick()
 	return true
 end
@@ -56,6 +67,12 @@ function _M:display()
 	end
 	self.step:display()
 	self.step:toScreen(self.step.display_x, self.step.display_y)
+
+	if self.s_log then
+		local w, h = self.s_log:getSize()
+		self.s_log:toScreen(self.w - w, self.h - h)
+	end
+
 	engine.Game.display(self)
 end
 
@@ -133,7 +150,7 @@ function _M:selectStepMain()
 				self:selectStepLoad()
 			end,
 		},
---[[
+-- [[
 		{
 			name = "Install a game module",
 			fct = function()
@@ -264,6 +281,11 @@ function _M:selectStepInstall()
 		end
 	end
 
+	if #dllist == 0 then
+		Dialog:simplePopup("No modules available", "There are no modules to install or upgrade.")
+		return
+	end
+
 	local display_module = Dialog.new("", self.w * 0.73, self.h, self.w * 0.26, 0, 255)
 
 	for i, mod in ipairs(dllist) do