mirror of
https://github.com/TribesNext/t2-scripts.git
synced 2026-01-19 18:14:43 +00:00
Initial release of the TribesNext scripts for Tribes 2. This contains incomplete in-game GUI code for player and clan profile management.
This commit is contained in:
commit
62c22f43f8
7
README.md
Normal file
7
README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
TribesNext
|
||||
==========
|
||||
|
||||
Scripts for system integration with Tribes 2
|
||||
--------------------------------------------
|
||||
|
||||
For more details, see the TribesNext project at: www.tribesnext.com
|
||||
1139
loginScreens.cs
Normal file
1139
loginScreens.cs
Normal file
File diff suppressed because it is too large
Load diff
185
scripts/autoexec/t2csri_IRCfix.cs
Normal file
185
scripts/autoexec/t2csri_IRCfix.cs
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
$IRCClient::NickName = getField(wonGetAuthInfo(),0);
|
||||
$IRCClient::NickName = strReplace($IRCClient::NickName," ","_");
|
||||
$IRCClient::NickName = stripChars($IRCClient::NickName,"~@#$!+%/|^{&*()<>");
|
||||
|
||||
package t2csri_ircfix {
|
||||
function GetIRCServerList(%arg1) {
|
||||
return "IP:irc.arloria.net:6667";
|
||||
}
|
||||
function IRCClient::notify(%event)
|
||||
{
|
||||
if (isObject(ServerConnection) && getSubStr(%event,0,9) $= "IDIRC_ERR") return;
|
||||
Parent::notify(%event);
|
||||
}
|
||||
function IRCClient::away(%params)
|
||||
{
|
||||
%me = $IRCClient::people.getObject(0);
|
||||
%me.flags = %me.flags & ~$PERSON_AWAY;
|
||||
if (strlen(%params))
|
||||
{
|
||||
if ($IRCClient::awaytimeout)
|
||||
{
|
||||
cancel($IRCClient::awaytimeout);
|
||||
$IRCClient::awaytimeout = 0;
|
||||
}
|
||||
IRCClient::send("AWAY :" @ %params);
|
||||
} else IRCClient::send("AWAY");
|
||||
}
|
||||
function IRCTCP::onDisconnect(%this)
|
||||
{
|
||||
$IRCClient::state = IDIRC_DISCONNECTED;
|
||||
IRCClient::reset();
|
||||
//IRCClient::notify(IDIRC_ERR_DROPPED);
|
||||
parent::onDisconnect(%this);
|
||||
}
|
||||
function IRCTCP::onConnected(%this)
|
||||
{
|
||||
IRCClient::newMessage("","IRCClient: Established TCP/IP connection");
|
||||
%me = $IRCClient::people.getObject(0);
|
||||
%me.displayName = $IRCClient::NickName;
|
||||
%me.setName(%me.displayName);
|
||||
$IRCClient::tcp.schedule(500, "send", "NICK " @ $IRCClient::NickName @ "\r\n");
|
||||
$IRCClient::tcp.schedule(500, "send", "USER " @ $IRCClient::NickName @ " x x :" @ $IRCClient::NickName @ "\r\n");
|
||||
$IRCClient::tcp.schedule(2000, "send", "WHOIS " @ $IRCClient::NickName @ "\r\n");
|
||||
$IRCClient::state = IDIRC_CONNECTING_WAITING;
|
||||
}
|
||||
function IRCClient::relogin()
|
||||
{
|
||||
if($IRCClient::state !$= IDIRC_CONNECTED)
|
||||
IRCClient::connect();
|
||||
%me = $IRCClient::people.getObject(0);
|
||||
%me.displayName = $IRCClient::NickName;
|
||||
%me.setName(%me.displayName);
|
||||
%me.tagged = %me.displayName;
|
||||
IRCClient::correctNick(%me);
|
||||
IRCClient::newMessage("","IRCClient: Reauthentication starting");
|
||||
$IRCClient::tcp.schedule(500, "send", "NICK " @ $IRCClient::NickName @ "\r\n");
|
||||
$IRCClient::tcp.schedule(500, "send", "USER " @ $IRCClient::NickName @ " x x :" @ $IRCClient::NickName @ "\r\n");
|
||||
$IRCClient::tcp.schedule(2000, "send", "WHOIS " @ $IRCClient::NickName @ "\r\n");
|
||||
$IRCClient::state = IDIRC_CONNECTING_WAITING;
|
||||
}
|
||||
function IRCClient::dispatch(%prefix,%command,%params)
|
||||
{
|
||||
if (%command == 378) {IRCClient::onConFrom(%prefix,%params); return true;}
|
||||
else parent::dispatch(%prefix,%command,%params);
|
||||
}
|
||||
function chatMemberPopup::add(%this,%name,%index) {
|
||||
if (%index == 10 || %index == 11) return;
|
||||
parent::add(%this,%name,%index);
|
||||
}
|
||||
function JoinChatDlg::onWake(%this)
|
||||
{
|
||||
if ($IRCClient::state $= IDIRC_CONNECTING_WAITING)
|
||||
MessageBoxOK("CONNECTING...","Waiting for IRC server to respond, please wait.");
|
||||
else
|
||||
parent::onWake(%this);
|
||||
}
|
||||
function ChatTabView::onSelect(%this,%obj,%name)
|
||||
{
|
||||
parent::onSelect(%this,%obj,%name);
|
||||
if (%name $= "welcome" && $IRCClient::channels.getObject(0) != %obj)
|
||||
{
|
||||
ChatPanel.setVisible(true);
|
||||
WelcomePanel.setVisible(false);
|
||||
ChatEditOptionsBtn.setVisible(false);
|
||||
}
|
||||
}
|
||||
function IRCClient::onConFrom(%prefix,%params)
|
||||
{
|
||||
//IP acquisition test... may remove
|
||||
//Krash-T2 Krash-T2 :is connecting from *@24.108.153.184 24.108.153.184
|
||||
if ($IPv4::InetAddress $= "" && getWord(%params,0) $= $IRCClient::people.getObject(0).displayName) $IPv4::InetAddress = getWord(%params,getWordCount(%params)-1);
|
||||
}
|
||||
function IRCClient::onBadNick(%prefix,%params)
|
||||
{
|
||||
$IRCClient::NickName = getField(wonGetAuthInfo(),0) @ "-"@getRandom(0,99);
|
||||
$IRCClient::NickName = strReplace($IRCClient::NickName," ","_");
|
||||
IRCClient::relogin();
|
||||
}
|
||||
function IRCClient::onNick(%prefix,%params)
|
||||
{
|
||||
%person = IRCClient::findPerson2(%prefix,false);
|
||||
if (%person) {
|
||||
%person.displayName = %params;
|
||||
%person.tagged = %params;
|
||||
IRCClient::correctNick(%person);
|
||||
ChatRoomMemberList_rebuild();
|
||||
}
|
||||
parent::onNick(%prefix,%params);
|
||||
|
||||
}
|
||||
function IRCClient::newMessage(%channel,%message)
|
||||
{
|
||||
//quick UE fix, rewrite later
|
||||
for (%i = 0;%i < getWordCount(%message);%i++) {
|
||||
%word = getWord(%message,%i);
|
||||
%first = strstr(%word,"<");
|
||||
if (%first != -1) {
|
||||
%word1 = getSubstr(%word,%first,strlen(%word));
|
||||
%second = strstr(%word1,">");
|
||||
if (%second == -1)
|
||||
%message = stripChars(%message,"<>");
|
||||
}
|
||||
}
|
||||
parent::newMessage(%channel,%message);
|
||||
}
|
||||
function IRCClient::setIdentity(%p,%ident)
|
||||
{
|
||||
parent::setIdentity(%p,%ident);
|
||||
if(%p.getName() !$= %p.displayName) %p.setName(%p.displayName);
|
||||
if(%p.untagged $= "")%p.untagged = %p.displayName;
|
||||
}
|
||||
function IRCClient::onMode(%prefix,%params)
|
||||
{
|
||||
parent::onMode(%prefix,%params);
|
||||
ChatRoomMemberList_rebuild();
|
||||
}
|
||||
function IRCClient::onJoinServer(%mission,%server,%address,%mayprequire,%prequire)
|
||||
{
|
||||
if(strstr(strlwr($IRCClient::currentChannel.getName(),"tribes")) != -1) return;
|
||||
parent::onJoinServer(%mission,%server,%address,%mayprequire,%prequire);
|
||||
}
|
||||
function IRCClient::onNameReply(%prefix,%params)
|
||||
{
|
||||
|
||||
%params = strreplace(%params,"~","@");
|
||||
%params = strreplace(%params,"&","@");
|
||||
%params = strreplace(%params,"*","@");
|
||||
%params = strreplace(%params,"%","@");
|
||||
%params = strreplace(%params,"^","@");
|
||||
parent::onNameReply(%prefix,%params);
|
||||
}
|
||||
function IRCClient::onPing(%prefix,%params)
|
||||
{
|
||||
//echo(%prefix SPC %params);
|
||||
if (!$PingStarted) {
|
||||
$IRCClient::tcp.schedule(1000, "send", "PONG " @ %params @ "\r\n");
|
||||
$PingStarted = true;
|
||||
} else $IRCClient::tcp.send("PONG " @ %params @ "\r\n");
|
||||
|
||||
}
|
||||
function IRCClient::onPart(%prefix,%params)
|
||||
{
|
||||
%params = firstWord(%params);
|
||||
parent::onPart(%prefix,%params);
|
||||
ChatRoomMemberList_rebuild();
|
||||
}
|
||||
function IRCClient::notify(%event)
|
||||
{
|
||||
if (%event $= IDIRC_CHANNEL_LIST) {
|
||||
JoinChatList.clear();
|
||||
for (%i = 0; %i < $IRCClient::numChannels; %i++)
|
||||
{
|
||||
switch$ ( $IRCClient::channelNames[%i] ) {
|
||||
case "#the_construct" or "#help" or "#welcome": %temp = 1;
|
||||
default: %temp = 0;
|
||||
}
|
||||
if (strStr(strlwr($IRCClient::channelNames[%i]),"tribes") != -1) %temp = 1;
|
||||
JoinChatList.addRow(%i, IRCClient::displayChannel( $IRCClient::channelNames[%i]) TAB $IRCClient::channelUsers[%i] TAB %temp );
|
||||
JoinChatList.setRowStyle( %i, %temp > 0 );
|
||||
}
|
||||
JoinChatList.sort();
|
||||
JoinChatName.onCharInput();
|
||||
} else parent::notify(%event);
|
||||
}
|
||||
}; activatePackage(t2csri_ircfix);
|
||||
459
scripts/autoexec/t2csri_list.cs
Normal file
459
scripts/autoexec/t2csri_list.cs
Normal file
|
|
@ -0,0 +1,459 @@
|
|||
// Tribes 2 Unofficial Authentication System
|
||||
// http://www.tribesnext.com/
|
||||
// Written by Krash
|
||||
// Copyright 2008 by Krash and the Tribes 2 Community System Reengineering Intitiative
|
||||
|
||||
// Master listing / Queries.
|
||||
|
||||
if ($Host::TN::beat $= "") $Host::TN::beat = 3; //Time between beats in minutes.
|
||||
if ($Host::TN::echo $= "") $Host::TN::echo = 1; //Enable the MS echoes.
|
||||
|
||||
|
||||
function NewsGui::onWake( %this )
|
||||
{
|
||||
Canvas.pushDialog( LaunchToolbarDlg );
|
||||
%this.pane = "News";
|
||||
NM_TabView.setSelected( 1 );
|
||||
}
|
||||
function NM_TabView::onAdd( %this )
|
||||
{
|
||||
%this.addSet( 1, "gui/shll_horztabbuttonB", "5 5 5", "50 50 0", "5 5 5" );
|
||||
%this.addTab(1,"NEWS",1);
|
||||
%this.addTab(2,"FORUMS");
|
||||
%this.setTabActive(2,0);
|
||||
%this.addTab(3,"DOWNLOADS");
|
||||
%this.setTabActive(3,0);
|
||||
}
|
||||
function NM_TabView::onSelect( %this, %id, %text )
|
||||
{
|
||||
NM_NewsPane.setVisible( %id == 1 );
|
||||
//NM_ForumPane.setVisible( %id == 2 );
|
||||
//NM_FilePane.setVisible( %id == 3 );
|
||||
NM_TabFrame.setAltColor( %id == 1 );
|
||||
|
||||
%ctrl = "NM_" @ NewsGui.pane @ "Pane";
|
||||
if ( isObject( %ctrl ) )
|
||||
%ctrl.onDeactivate();
|
||||
|
||||
switch ( %id )
|
||||
{
|
||||
case 1: // News
|
||||
NM_NewsPane.onActivate();
|
||||
}
|
||||
}
|
||||
function NM_NewsPane::onActivate(%this) {
|
||||
NewsGui.pane = "News";
|
||||
|
||||
}
|
||||
function NM_NewsPane::onDeactivate(%this) {}
|
||||
function NewsGui::setKey(%this) {}
|
||||
function LaunchNews() {
|
||||
if (!isObject(NewsGui)){
|
||||
new GuiChunkedBitmapCtrl(NewsGui) {
|
||||
profile = "GuiContentProfile";
|
||||
horizSizing = "width";
|
||||
vertSizing = "height";
|
||||
position = "0 0";
|
||||
extent = "640 480";
|
||||
minExtent = "8 8";
|
||||
visible = "1";
|
||||
hideCursor = "0";
|
||||
bypassHideCursor = "0";
|
||||
variable = "$ShellBackground";
|
||||
helpTag = "0";
|
||||
useVariable = "1";
|
||||
|
||||
new ShellPaneCtrl() {
|
||||
profile = "ShellPaneProfile";
|
||||
horizSizing = "width";
|
||||
vertSizing = "height";
|
||||
position = "12 13";
|
||||
extent = "620 423";
|
||||
minExtent = "48 92";
|
||||
visible = "1";
|
||||
hideCursor = "0";
|
||||
bypassHideCursor = "0";
|
||||
helpTag = "0";
|
||||
text = "TRIBESNEXT";
|
||||
maxLength = "255";
|
||||
noTitleBar = "0";
|
||||
|
||||
|
||||
new ShellTabFrame(NM_TabFrame) {
|
||||
profile = "ShellHorzTabFrameProfile";
|
||||
horizSizing = "width";
|
||||
vertSizing = "height";
|
||||
position = "22 54";
|
||||
extent = "576 351";
|
||||
minExtent = "26 254";
|
||||
visible = "1";
|
||||
hideCursor = "0";
|
||||
bypassHideCursor = "0";
|
||||
helpTag = "0";
|
||||
isVertical = "0";
|
||||
useCloseButton = "0";
|
||||
edgeInset = "0";
|
||||
};
|
||||
new ShellTabGroupCtrl(NM_TabView) {
|
||||
profile = "TabGroupProfile";
|
||||
horizSizing = "width";
|
||||
vertSizing = "bottom";
|
||||
position = "30 25";
|
||||
extent = "560 29";
|
||||
minExtent = "38 29";
|
||||
visible = "1";
|
||||
hideCursor = "0";
|
||||
bypassHideCursor = "0";
|
||||
helpTag = "0";
|
||||
glowOffset = "7";
|
||||
tabSpacing = "2";
|
||||
maxTabWidth = "150";
|
||||
stretchToFit = "0";
|
||||
};
|
||||
new GuiControl(NM_NewsPane) {
|
||||
profile = "GuiDefaultProfile";
|
||||
horizSizing = "width";
|
||||
vertSizing = "height";
|
||||
position = "0 0";
|
||||
extent = "586 423";
|
||||
minExtent = "8 8";
|
||||
visible = "0";
|
||||
hideCursor = "0";
|
||||
bypassHideCursor = "0";
|
||||
helpTag = "0";
|
||||
|
||||
new ShellFieldCtrl(NewsPanel) {
|
||||
profile = "ShellFieldProfile";
|
||||
horizSizing = "width";
|
||||
vertSizing = "height";
|
||||
position = "31 92";
|
||||
extent = "559 315";
|
||||
minExtent = "16 18";
|
||||
visible = "1";
|
||||
hideCursor = "0";
|
||||
bypassHideCursor = "0";
|
||||
helpTag = "0";
|
||||
|
||||
new ShellScrollCtrl() {
|
||||
profile = "NewScrollCtrlProfile";
|
||||
horizSizing = "width";
|
||||
vertSizing = "height";
|
||||
position = "195 5";
|
||||
extent = "360 303";
|
||||
minExtent = "24 52";
|
||||
visible = "1";
|
||||
hideCursor = "0";
|
||||
bypassHideCursor = "0";
|
||||
helpTag = "0";
|
||||
willFirstRespond = "1";
|
||||
hScrollBar = "alwaysOff";
|
||||
vScrollBar = "alwaysOn";
|
||||
constantThumbHeight = "0";
|
||||
defaultLineHeight = "15";
|
||||
childMargin = "0 2";
|
||||
fieldBase = "gui/shll_field";
|
||||
|
||||
new GuiScrollContentCtrl() {
|
||||
profile = "GuiDefaultProfile";
|
||||
horizSizing = "width";
|
||||
vertSizing = "height";
|
||||
position = "4 6";
|
||||
extent = "336 291";
|
||||
minExtent = "8 8";
|
||||
visible = "1";
|
||||
hideCursor = "0";
|
||||
bypassHideCursor = "0";
|
||||
helpTag = "0";
|
||||
|
||||
new GuiMLTextCtrl(NewsText) {
|
||||
profile = "NewTextEditProfile";
|
||||
horizSizing = "width";
|
||||
vertSizing = "bottom";
|
||||
position = "0 0";
|
||||
extent = "362 2376";
|
||||
minExtent = "8 8";
|
||||
visible = "1";
|
||||
hideCursor = "0";
|
||||
bypassHideCursor = "0";
|
||||
helpTag = "0";
|
||||
lineSpacing = "2";
|
||||
allowColorChars = "0";
|
||||
maxChars = "-1";
|
||||
deniedSound = "InputDeniedSound";
|
||||
};
|
||||
};
|
||||
};
|
||||
new ShellScrollCtrl() {
|
||||
profile = "NewScrollCtrlProfile";
|
||||
horizSizing = "right";
|
||||
vertSizing = "height";
|
||||
position = "2 21";
|
||||
extent = "195 287";
|
||||
minExtent = "24 52";
|
||||
visible = "1";
|
||||
hideCursor = "0";
|
||||
bypassHideCursor = "0";
|
||||
helpTag = "0";
|
||||
willFirstRespond = "1";
|
||||
hScrollBar = "alwaysOff";
|
||||
vScrollBar = "dynamic";
|
||||
constantThumbHeight = "0";
|
||||
defaultLineHeight = "15";
|
||||
childMargin = "0 3";
|
||||
fieldBase = "gui/shll_field";
|
||||
|
||||
new GuiScrollContentCtrl() {
|
||||
profile = "GuiDefaultProfile";
|
||||
horizSizing = "right";
|
||||
vertSizing = "bottom";
|
||||
position = "4 7";
|
||||
extent = "187 273";
|
||||
minExtent = "8 8";
|
||||
visible = "1";
|
||||
hideCursor = "0";
|
||||
bypassHideCursor = "0";
|
||||
helpTag = "0";
|
||||
|
||||
new ShellTextList(NewsHeadlines) {
|
||||
profile = "ShellTextArrayProfile";
|
||||
horizSizing = "right";
|
||||
vertSizing = "bottom";
|
||||
position = "0 0";
|
||||
extent = "187 180";
|
||||
minExtent = "8 8";
|
||||
visible = "1";
|
||||
hideCursor = "0";
|
||||
bypassHideCursor = "0";
|
||||
helpTag = "0";
|
||||
enumerate = "0";
|
||||
resizeCell = "1";
|
||||
columns = "0";
|
||||
fitParentWidth = "1";
|
||||
clipColumnText = "0";
|
||||
};
|
||||
};
|
||||
};
|
||||
new GuiTextCtrl() {
|
||||
profile = "ShellAltTextProfile";
|
||||
horizSizing = "right";
|
||||
vertSizing = "bottom";
|
||||
position = "12 6";
|
||||
extent = "72 20";
|
||||
minExtent = "8 8";
|
||||
visible = "1";
|
||||
hideCursor = "0";
|
||||
bypassHideCursor = "0";
|
||||
helpTag = "0";
|
||||
text = "HEADLINES:";
|
||||
longTextBuffer = "0";
|
||||
maxLength = "255";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
};
|
||||
} else LaunchTabView.viewTab( "TRIBESNEXT", NewsGui, 0 );
|
||||
}
|
||||
//================================================================
|
||||
|
||||
function queryTNServers(%filter,%mod,%maptype,%minplayers,%maxplayers,%maxBots,%flags) {
|
||||
|
||||
%server = "master.tribesnext.com:80";
|
||||
if (!isObject(TNbite))
|
||||
%bite = new TCPObject(TNbite){};
|
||||
else %bite = TNbite;
|
||||
%bite.mode = 0;
|
||||
%filename = "/list";
|
||||
if (%filter)
|
||||
%filename = "/list/"@%mod@"/"@%maptype@"/"@%minplayers@"/"@%maxplayers@"/"@%maxBots@"/"@%flags;
|
||||
if (%filter $= "types") {
|
||||
%filename = "/listtypes";
|
||||
%bite.mode = 2;
|
||||
} else queryFavoriteServers(); // Filtering fix, since the old master query isn't used.
|
||||
|
||||
%bite.get(%server, %filename);
|
||||
}
|
||||
|
||||
function queryMasterGameTypes(){
|
||||
clearGameTypes();
|
||||
clearMissionTypes();
|
||||
queryTNServers("types");
|
||||
}
|
||||
|
||||
function queryMasterServer(%port, %flags, %rulesSet, %missionType, %minPlayers, %maxPlayers, %maxBots, %regionMask, %maxPing, %minCpu, %filtFlags, %buddy )
|
||||
{
|
||||
if (%flags !$= "") queryTNServers(1,%rulesSet,%missionType,%minplayers,%maxplayers,%maxBots,%filtFlags SPC %buddy);
|
||||
else queryTNServers();
|
||||
}
|
||||
|
||||
function TNbite::onLine(%this, %line) {
|
||||
if (trim(%line) $= "") {
|
||||
if (!%this.primed) %this.primed = true;
|
||||
if (%this.mode != 5) return;
|
||||
}
|
||||
if (!%this.primed) return;
|
||||
|
||||
if (%this.mode == 1)
|
||||
switch (%line) { // heartbeats
|
||||
case 0: if ($Host::TN::echo) echo(" - Server added to list.");
|
||||
case 1: if ($Host::TN::echo) { echo(" - Your server could not be contacted.");
|
||||
echo(" - Check your IP / port configuration."); }
|
||||
case 2: if ($Host::TN::echo) echo(" - Heartbeat confirmed.");
|
||||
}
|
||||
else if (%this.mode == 2) //filter retrieval
|
||||
switch (firstWord(%line)) {
|
||||
case 0: addGameType( restWords(%line) );
|
||||
case 1: addMissionType( restWords(%line) );
|
||||
}
|
||||
else if (%this.mode == 5) // news retrieval
|
||||
NewsGui.addLine(%line);
|
||||
else // and finally, the server list...
|
||||
if ( strpos(%line,":") != -1 && strstr(%line,".") != -1) {
|
||||
querySingleServer( %line );
|
||||
if (!%this.fnd) %this.fnd = true;
|
||||
}
|
||||
}
|
||||
|
||||
function TNbite::onConnectFailed(%this) {
|
||||
if ($Host::TN::echo) echo("-- Could not connect to master server.");
|
||||
}
|
||||
|
||||
function TNbite::onDNSFailed(%this) {
|
||||
if ($Host::TN::echo) echo("-- Could not connect to DNS server.");
|
||||
}
|
||||
|
||||
function TNbite::onDisconnect(%this) {
|
||||
if (!%this.fnd && %this.mode == 0)
|
||||
if (!GMJ_Browser.rowCount())
|
||||
updateServerBrowserStatus( "No servers found.", 0 );
|
||||
%this.delete();
|
||||
}
|
||||
|
||||
function NewsGui::addLine( %this, %line ) {
|
||||
%this = NewsText;
|
||||
if (firstWord(%line) $= "<tag>") {
|
||||
%line = setWord(%line,0,"<tag:"@%this.index++@">");
|
||||
NewsHeadlines.addRow(%this.index,stripMLControlChars(%line));
|
||||
}
|
||||
if (%line $= "#EOF") {NewsText.upToDate = true; NewsHeadlines.setSelectedRow(0); return;}
|
||||
%text = %this.getText();
|
||||
%line = detag( %line );
|
||||
%text = (%text $= "") ? %line : %text NL %line;
|
||||
%this.setText( %text );
|
||||
}
|
||||
|
||||
function NewsText::update( %this, %online ) {
|
||||
%this.setText("");
|
||||
NewsHeadlines.clear();
|
||||
%this.index = -1;
|
||||
if (%online) {
|
||||
%server = "www.tribesnext.com:80";
|
||||
if (!isObject(TNbite))
|
||||
%bite = new TCPObject(TNbite){};
|
||||
else %bite = TNbite;
|
||||
%bite.mode = 5;
|
||||
%filename = "/news";
|
||||
%bite.get(%server, %filename);
|
||||
}
|
||||
}
|
||||
function NewsHeadlines::onSelect( %this, %id, %text )
|
||||
{
|
||||
NewsText.scrollToTag( %id );
|
||||
}
|
||||
//================================================================
|
||||
package t2csri_webs {
|
||||
|
||||
function TNbite::get(%this, %server, %query)
|
||||
{
|
||||
if ($t2csri::isOfflineMode)
|
||||
{
|
||||
warn("TribesNext: Running in offline mode. Aborting query to the Master List Server.");
|
||||
return;
|
||||
}
|
||||
%this.server = %server;
|
||||
%this.query = %query;
|
||||
%this.connect(%server);
|
||||
}
|
||||
|
||||
function TNbite::onConnected(%this)
|
||||
{
|
||||
if (%this.query !$= "") {
|
||||
%query = "GET " @ %this.query @ " HTTP/1.1\r\nHost: " @ %this.server @ "\r\nUser-Agent: Tribes 2\r\nConnection: close\r\n\r\n";
|
||||
%this.send(%query);
|
||||
}
|
||||
}
|
||||
|
||||
function LaunchTabView::addLaunchTab( %this, %text, %gui, %makeInactive ) {
|
||||
// disable currently unused tabs
|
||||
//if (%text $= "EMAIL" || %text $= "BROWSER") parent::addLaunchTab( %this, %text, %gui, 1 );
|
||||
if (%text $= "BROWSER") parent::addLaunchTab( %this, %text, %gui, 1 );
|
||||
else parent::addLaunchTab( %this, %text, %gui, %makeInactive );
|
||||
}
|
||||
function LaunchToolbarMenu::add(%this,%id,%text) {
|
||||
parent::add(%this,%id,%text);
|
||||
if ($PlayingOnline && %text $= "BROWSER") {
|
||||
LaunchToolbarMenu.add( 1, "TRAINING" );
|
||||
LaunchToolbarMenu.add( 2, "TRIBESNEXT" );
|
||||
}
|
||||
}
|
||||
|
||||
function OpenLaunchTabs( %gotoWarriorSetup ) {
|
||||
parent::OpenLaunchTabs( %gotoWarriorSetup );
|
||||
if ($PlayingOnline && !TrainingGui.added) {
|
||||
LaunchTabView.addLaunchTab( "TRAINING", TrainingGui );
|
||||
LaunchTabView.addLaunchTab( "TRIBESNEXT", NewsGui );
|
||||
LaunchNews();
|
||||
NewsText.update(1);
|
||||
TrainingGui.added = true;
|
||||
}
|
||||
}
|
||||
|
||||
function JoinSelectedGame() {
|
||||
if (($IPv4::InetAddress $= "" || strstr($IPv4::InetAddress,".") == -1) && $PlayingOnline) {
|
||||
messageBoxOK("IP ERROR","Your external address has not been set or is set incorrectly. \n\nAttempting to reset...");
|
||||
ipv4_getInetAddress();
|
||||
return;
|
||||
} else parent::JoinSelectedGame();
|
||||
}
|
||||
function ClientReceivedDataBlock(%index, %total)
|
||||
{
|
||||
DB_LoadingProgress.setValue( %index / %total );
|
||||
parent::ClientReceivedDataBlock(%index, %total);
|
||||
}
|
||||
|
||||
function CreateServer(%mission, %missionType) {
|
||||
parent::CreateServer(%mission, %missionType);
|
||||
if (!isActivePackage(t2csri_server)) schedule(2000,0,"exec","t2csri/serverGlue.cs");
|
||||
}
|
||||
|
||||
function StartHeartbeat() {
|
||||
if ($playingOnline) {
|
||||
if(isEventPending($TNBeat)) cancel($TNBeat);
|
||||
%server = "master.tribesnext.com:80";
|
||||
if ($Host::BindAddress !$= "")
|
||||
%path = "/add/" @ $Host::Port @"/"@ $Host::BindAddress;
|
||||
else %path = "/add/" @ $Host::Port;
|
||||
if (!isObject(TNbite))
|
||||
%bite = new TCPObject(TNbite){};
|
||||
else %bite = TNbite;
|
||||
%bite.mode = 1;
|
||||
%bite.get(%server, %path);
|
||||
if ($Host::TN::echo)
|
||||
echo("-- Sent heartbeat to TN Master. ("@%server@")");
|
||||
$TNBeat = schedule($Host::TN::beat*60000,0,"StartHeartBeat");
|
||||
} else parent::StartHeartbeat();
|
||||
}
|
||||
|
||||
function StopHeartbeat() {
|
||||
if ($playingOnline) {
|
||||
if(isEventPending($TNBeat)) cancel($TNBeat);
|
||||
} else parent::StartHeartbeat();
|
||||
}
|
||||
//================================================================
|
||||
};
|
||||
if (!isActivePackage(t2csri_webs)) activatepackage (t2csri_webs);
|
||||
|
||||
exec("t2csri/postLogin.cs");
|
||||
8
scripts/autoexec/t2csri_serv.cs
Normal file
8
scripts/autoexec/t2csri_serv.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Tribes 2 Unofficial Authentication System
|
||||
// http://www.tribesnext.com/
|
||||
// Written by Electricutioner/Thyth
|
||||
// Copyright 2008 by Electricutioner/Thyth and the Tribes 2 Community System Reengineering Intitiative
|
||||
|
||||
// Version 1.0 initialization and glue file (server side)
|
||||
|
||||
schedule(0, 0, exec, "t2csri/serverglue.cs");
|
||||
87
t2csri/authconnect.cs
Normal file
87
t2csri/authconnect.cs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
// Tribes 2 Unofficial Authentication System
|
||||
// http://www.tribesnext.com/
|
||||
// Written by Electricutioner/Thyth
|
||||
// Copyright 2008 by Electricutioner/Thyth and the Tribes 2 Community System Reengineering Intitiative
|
||||
|
||||
// Authentication Server Connector Version 1.0: 11/06/2008
|
||||
|
||||
function authConnect_findAuthServer()
|
||||
{
|
||||
if ($AuthServer::Address !$= "")
|
||||
return;
|
||||
echo("Looking up Authentication Server...");
|
||||
if (isObject(AuthConnection))
|
||||
{
|
||||
AuthConnection.disconnect();
|
||||
AuthConnection.delete();
|
||||
}
|
||||
new TCPObject(AuthConnection);
|
||||
|
||||
%data = "GET /auth HTTP/1.1\r\nHost: www.tribesnext.com\r\nUser-Agent: Tribes 2\r\nConnection: close\r\n\r\n";
|
||||
AuthConnection.data = %data;
|
||||
AuthConnection.connect("www.tribesnext.com:80");
|
||||
$AuthServer::Primed = 0;
|
||||
}
|
||||
|
||||
function AuthConnection::onLine(%this, %line)
|
||||
{
|
||||
if (%line == 411)
|
||||
return;
|
||||
if (trim(%line) $= "")
|
||||
{
|
||||
$AuthServer::Primed = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($AuthServer::Primed)
|
||||
{
|
||||
$AuthServer::Address = %line;
|
||||
%this.disconnect();
|
||||
authConnect_verifyLookup();
|
||||
}
|
||||
}
|
||||
|
||||
function AuthConnection::onConnected(%this)
|
||||
{
|
||||
%this.send(%this.data);
|
||||
}
|
||||
|
||||
function authConnect_verifyLookup()
|
||||
{
|
||||
|
||||
if (getFieldCount($AuthServer::Address) != 2)
|
||||
{
|
||||
$AuthServer::Address = "";
|
||||
error("Authentication server lookup failed.");
|
||||
return;
|
||||
}
|
||||
%address = getField($AuthServer::Address, 0);
|
||||
%signature = getField($AuthServer::Address, 1);
|
||||
|
||||
%sha1sum = sha1sum(%address);
|
||||
%verifSum = t2csri_verify_auth_signature(%signature);
|
||||
while (strlen(%verifSum) < 40)
|
||||
%verifSum = "0" @ %verifSum;
|
||||
if (%sha1sum !$= %verifSum)
|
||||
{
|
||||
// signature verification failed... someone has subverted the auth server lookup
|
||||
error("Authentication server lookup returned an address with an invalid signature.");
|
||||
error("Unable to contact the authentication server.");
|
||||
$AuthServer::Address = "";
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
echo("Authentication server found at " @ %address @ ". Ready to authenticate.");
|
||||
$AuthServer::Address = %address;
|
||||
$AuthServer::Primed = "";
|
||||
}
|
||||
}
|
||||
|
||||
// perform signature verification to prove that the auth server has designated the
|
||||
// provided address
|
||||
function t2csri_verify_auth_signature(%sig)
|
||||
{
|
||||
rubyEval("tsEval '$temp=\"' + t2csri_verify_auth_signature('" @ %sig @ "').to_s(16) + '\";'");
|
||||
return $temp;
|
||||
}
|
||||
238
t2csri/authinterface.cs
Normal file
238
t2csri/authinterface.cs
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
// Tribes 2 Unofficial Authentication System
|
||||
// http://www.tribesnext.com/
|
||||
// Written by Electricutioner/Thyth
|
||||
// Copyright 2008 by Electricutioner/Thyth and the Tribes 2 Community System Reengineering Intitiative
|
||||
|
||||
// Authentication Server Interface Version 1.0: 12/29/2008
|
||||
|
||||
$Authentication::Mode::Available = 1;
|
||||
$Authentication::Mode::Name = 2;
|
||||
$Authentication::Mode::Recover = 3;
|
||||
$Authentication::Mode::Sign = 4;
|
||||
|
||||
$Authentication::Settings::Timeout = 30000;
|
||||
|
||||
function AuthenticationInterface::onLine(%this, %line)
|
||||
{
|
||||
//warn(%line);
|
||||
if (isEventPending($Authentication::TransactionCompletionSchedule))
|
||||
cancel($Authentication::TransactionCompletionSchedule);
|
||||
$Authentication::TransactionCompletionSchedule = schedule(700, 0, Authentication_transactionComplete);
|
||||
|
||||
if ($Authentication::Status::ActiveMode != 0)
|
||||
{
|
||||
$Authentication::Buffer[$Authentication::Status::ActiveMode] = $Authentication::Buffer[$Authentication::Status::ActiveMode] @ "\n" @ %line;
|
||||
}
|
||||
}
|
||||
|
||||
// connection complete... send the buffer
|
||||
function AuthenticationInterface::onConnected(%this)
|
||||
{
|
||||
%this.send(%this.data);
|
||||
}
|
||||
|
||||
function Authentication_transactionComplete()
|
||||
{
|
||||
// terminate the connection
|
||||
AuthenticationInterface.disconnect();
|
||||
|
||||
%buffer = trim($Authentication::Buffer[$Authentication::Status::ActiveMode]);
|
||||
if ($Authentication::Status::ActiveMode == $Authentication::Mode::Available)
|
||||
{
|
||||
if (strlen(%buffer) > 0 && %buffer $= "AVAIL")
|
||||
{
|
||||
echo("Authentication: Server is available.");
|
||||
$Authentication::Status::Available = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
error("Authentication: Server is not available.");
|
||||
$Authentication::Status::Available = 0;
|
||||
}
|
||||
}
|
||||
else if ($Authentication::Status::ActiveMode == $Authentication::Mode::Name)
|
||||
{
|
||||
if (%buffer $= "TOOSHORT")
|
||||
{
|
||||
$Authentication::Status::Name = "Requested name is too short.";
|
||||
error("Authentication: " @ $Authentication::Status::Name);
|
||||
|
||||
}
|
||||
else if (%buffer $= "TOOLONG")
|
||||
{
|
||||
$Authentication::Status::Name = "Requested name is too long.";
|
||||
error("Authentication: " @ $Authentication::Status::Name);
|
||||
}
|
||||
else if (%buffer $= "INVALID")
|
||||
{
|
||||
$Authentication::Status::Name = "Requested name is rejected.";
|
||||
error("Authentication: " @ $Authentication::Status::Name);
|
||||
}
|
||||
else if (%buffer $= "TAKEN")
|
||||
{
|
||||
$Authentication::Status::Name = "Requested name is taken.";
|
||||
error("Authentication: " @ $Authentication::Status::Name);
|
||||
}
|
||||
else if (%buffer $= "SUCCESS")
|
||||
{
|
||||
$Authentication::Status::Name = "Name is available and acceptable.";
|
||||
echo("Authentication: " @ $Authentication::Status::Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
// this shouldn't happen
|
||||
$Authentication::Status::Name = "Unknown name status code returned from server.";
|
||||
error("Authentication: " @ $Authentication::Status::Name);
|
||||
}
|
||||
}
|
||||
else if ($Authentication::Status::ActiveMode == $Authentication::Mode::Recover)
|
||||
{
|
||||
if (%buffer $= "RECOVERERROR")
|
||||
{
|
||||
// this generic error happens if a malformed request is sent to the server
|
||||
error("Authentication: Unknown credential recovery status code returned from server.");
|
||||
}
|
||||
else if (%buffer $= "NOTFOUND")
|
||||
{
|
||||
error("Authentication: No user with that name exists.");
|
||||
}
|
||||
else if (%buffer $= "INVALIDPASSWORD")
|
||||
{
|
||||
error("Authentication: Invalid password provided for that user.");
|
||||
}
|
||||
else if (getWord(%buffer, 0) $= "CERT:")
|
||||
{
|
||||
%cert = getSubStr(%buffer, 0, strstr(%buffer, "\n"));
|
||||
%buffer = getSubStr(%buffer, strstr(%buffer, "\n") + 1, strlen(%buffer));
|
||||
%exp = getSubStr(%buffer, 0, (strstr(%buffer, "\n") == -1 ? strlen(%buffer) : strstr(%buffer, "\n")));
|
||||
|
||||
$Authentication::Status::LastCert = %cert;
|
||||
$Authentication::Status::LastExp = %exp;
|
||||
echo("Authentication: Successfully downloaded certificate and encrypted key.");
|
||||
}
|
||||
else
|
||||
{
|
||||
error("Authentication: Unknown recovery status code returned from server.");
|
||||
}
|
||||
}
|
||||
else if ($Authentication::Status::ActiveMode == $Authentication::Mode::Sign)
|
||||
{
|
||||
if (%buffer $= "REJECTED")
|
||||
{
|
||||
// this is returned if the user created an account from this IP in the last week, or 5 accounts total
|
||||
$Authentication::Status::Signature = "Server chose to reject account generation request.";
|
||||
error("Authentication: " @ $Authentication::Status::Signature);
|
||||
}
|
||||
else if (%buffer $= "INVALIDNAME")
|
||||
{
|
||||
// name taken, or otherwise not allowed
|
||||
$Authentication::Status::Signature = "Server rejected account name.";
|
||||
error("Authentication: " @ $Authentication::Status::Signature);
|
||||
}
|
||||
else if (%buffer $= "SIGNERROR")
|
||||
{
|
||||
$Authentication::Status::Signature = "Corrupt signature request rejected.";
|
||||
error("Authentication: " @ $Authentication::Status::Signature);
|
||||
}
|
||||
else if (strlen(%buffer) > 0 && getFieldCount(%buffer) > 4)
|
||||
{
|
||||
%cert = %buffer;
|
||||
$Authentication::Status::LastCert = %cert;
|
||||
$Authentication::Status::Signature = "Account generation successful.";
|
||||
echo("Authentication: " @ $Authentication::Status::Signature);
|
||||
}
|
||||
else
|
||||
{
|
||||
$Authentication::Status::Signature = "Unknown signature status code returned from server.";
|
||||
error("Authentication: " @ $Authentication::Status::Signature);
|
||||
}
|
||||
}
|
||||
|
||||
// clear out the buffer
|
||||
$Authentication::Buffer[$Authentication::Status::ActiveMode] = "";
|
||||
$Authentication::Status::ActiveMode = 0;
|
||||
}
|
||||
|
||||
// determine if the server is available
|
||||
function Authentication_checkAvail()
|
||||
{
|
||||
if ($Authentication::Status::ActiveMode != 0)
|
||||
{
|
||||
// already a request active, retry this one in 10 seconds
|
||||
schedule(10000, 0, Authentication_checkAvail);
|
||||
return;
|
||||
}
|
||||
|
||||
$Authentication::Status::ActiveMode = $Authentication::Mode::Available;
|
||||
|
||||
if (isObject(AuthenticationInterface))
|
||||
AuthenticationInterface.delete();
|
||||
new TCPObject(AuthenticationInterface);
|
||||
|
||||
AuthenticationInterface.data = "AVAIL\n";
|
||||
AuthenticationInterface.connect($AuthServer::Address);
|
||||
$Authentication::TransactionCompletionSchedule = schedule($Authentication::Settings::Timeout, 0, Authentication_transactionComplete);
|
||||
}
|
||||
|
||||
// determine if the given name is acceptable/available
|
||||
function Authentication_checkName(%name)
|
||||
{
|
||||
if ($Authentication::Status::ActiveMode != 0)
|
||||
{
|
||||
// already a request active, retry this one in 10 seconds
|
||||
schedule(10000, 0, Authentication_checkName, %name);
|
||||
return;
|
||||
}
|
||||
|
||||
$Authentication::Status::ActiveMode = $Authentication::Mode::Name;
|
||||
|
||||
if (isObject(AuthenticationInterface))
|
||||
AuthenticationInterface.delete();
|
||||
new TCPObject(AuthenticationInterface);
|
||||
|
||||
AuthenticationInterface.data = "NAME\t" @ %name @ "\n";
|
||||
AuthenticationInterface.connect($AuthServer::Address);
|
||||
$Authentication::TransactionCompletionSchedule = schedule($Authentication::Settings::Timeout, 0, Authentication_transactionComplete);
|
||||
}
|
||||
|
||||
// request a certificate and encrypted exponent from the authentication server
|
||||
function Authentication_recoverAccount(%payload)
|
||||
{
|
||||
if ($Authentication::Status::ActiveMode != 0)
|
||||
{
|
||||
// already a request active, retry this one in 10 seconds
|
||||
schedule(10000, 0, Authentication_recoverAccount, %payload);
|
||||
return;
|
||||
}
|
||||
|
||||
$Authentication::Status::ActiveMode = $Authentication::Mode::Recover;
|
||||
|
||||
if (isObject(AuthenticationInterface))
|
||||
AuthenticationInterface.delete();
|
||||
new TCPObject(AuthenticationInterface);
|
||||
|
||||
AuthenticationInterface.data = "RECOVER\t" @ %payload @ "\n";
|
||||
AuthenticationInterface.connect($AuthServer::Address);
|
||||
$Authentication::TransactionCompletionSchedule = schedule($Authentication::Settings::Timeout, 0, Authentication_transactionComplete);
|
||||
}
|
||||
|
||||
// request a new account certificate
|
||||
function Authentication_registerAccount(%payload)
|
||||
{
|
||||
if ($Authentication::Status::ActiveMode != 0)
|
||||
{
|
||||
// already a request active, retry this one in 10 seconds
|
||||
schedule(10000, 0, Authentication_registerAccount, %payload);
|
||||
return;
|
||||
}
|
||||
|
||||
$Authentication::Status::ActiveMode = $Authentication::Mode::Sign;
|
||||
|
||||
if (isObject(AuthenticationInterface))
|
||||
AuthenticationInterface.delete();
|
||||
new TCPObject(AuthenticationInterface);
|
||||
|
||||
AuthenticationInterface.data = "SIGN\t" @ %payload @ "\n";
|
||||
AuthenticationInterface.connect($AuthServer::Address);
|
||||
$Authentication::TransactionCompletionSchedule = schedule($Authentication::Settings::Timeout, 0, Authentication_transactionComplete);
|
||||
}
|
||||
111
t2csri/autoupdate.cs
Normal file
111
t2csri/autoupdate.cs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// Tribes 2 Unofficial Authentication System
|
||||
// http://www.tribesnext.com/
|
||||
// Written by Electricutioner/Thyth
|
||||
// Copyright 2008 by Electricutioner/Thyth and the Tribes 2 Community System Reengineering Intitiative
|
||||
|
||||
// Bare Bones Auto Update System Version 1.0: 11/06/2008
|
||||
|
||||
function authConnect_findAutoUpdater()
|
||||
{
|
||||
if ($AutoUpdater::Address !$= "")
|
||||
return;
|
||||
|
||||
if (isObject(AutoUpdateConnection))
|
||||
{
|
||||
AutoUpdateConnection.disconnect();
|
||||
AutoUpdateConnection.delete();
|
||||
}
|
||||
new TCPObject(AutoUpdateConnection);
|
||||
|
||||
%data = "GET /update HTTP/1.1\r\nHost: www.tribesnext.com\r\nUser-Agent: Tribes 2\r\nConnection: close\r\n\r\n";
|
||||
AutoUpdateConnection.connect("www.tribesnext.com:80");
|
||||
AutoUpdateConnection.schedule(1000, send, %data);
|
||||
}
|
||||
|
||||
function AutoUpdateConnection::onLine(%this, %line)
|
||||
{
|
||||
if (!$AutoUpdater::UpdateFound)
|
||||
{
|
||||
$AutoUpdater::Address = %line;
|
||||
%this.disconnect();
|
||||
autoUpdate_verifyLookup();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isEventPending($AutoUpdate::LastLineSch))
|
||||
cancel($AutoUpdate::LastLineSch);
|
||||
$AutoUpdate::LastLineSch = autoUpdate_applyUpdate();
|
||||
if ($AutoUpdate::UpdateStarted)
|
||||
$AutoUpdate::Buffer = $AutoUpdate::Buffer @ "\n" @ %line;
|
||||
else if (strlen(%line) == 0)
|
||||
$AutoUpdate::UpdateStarted = 1;
|
||||
}
|
||||
}
|
||||
|
||||
function autoUpdate_verifyLookup()
|
||||
{
|
||||
if (getFieldCount($AutoUpdate::Address) != 2)
|
||||
{
|
||||
$AutoUpdater::Address = "";
|
||||
error("No valid update address found.");
|
||||
return;
|
||||
}
|
||||
%address = getField($AutoUpdater::Address, 0);
|
||||
%signature = getField($AutoUpdater::Address, 1);
|
||||
|
||||
%sha1sum = sha1sum(%address);
|
||||
if (%sha1sum !$= t2csri_verify_update_signature(%signature))
|
||||
{
|
||||
// signature verification failed... someone has subverted the auth server lookup
|
||||
error("Auto update lookup returned an address with an invalid signature.");
|
||||
error("Unable to download update without a correct signature.");
|
||||
$AutoUpdater::Address = "";
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
echo("New update found at " @ %address @ ". Ready to download.");
|
||||
$AutoUpdater::Address = %address;
|
||||
$AutoUpdater::UpdateFound = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// perform signature verification to prove that the update server has designated the
|
||||
// provided URL for a download, we don't want people injecting arbitrary code into
|
||||
// user installations
|
||||
function t2csri_verify_update_signature(%sig)
|
||||
{
|
||||
rubyEval("tsEval '$temp=\"' + t2csri_verify_update_signature('" @ %sig @ "') + '\";'");
|
||||
return $temp;
|
||||
}
|
||||
|
||||
function autoUpdate_performUpdate()
|
||||
{
|
||||
if ($AutoUpdater::Address $= "")
|
||||
return;
|
||||
|
||||
if (isObject(AutoUpdateConnection))
|
||||
{
|
||||
AutoUpdateConnection.disconnect();
|
||||
AutoUpdateConnection.delete();
|
||||
}
|
||||
new TCPObject(AutoUpdateConnection);
|
||||
|
||||
%host = getSubStr($AutoUpdater::Address, 0, strstr("/"));
|
||||
%uri = getSubStr($AutoUpdater::Address, strlen(%host), strlen($AutoUpdater::Address));
|
||||
|
||||
%data = "GET " @ %uri @ " HTTP/1.1\nHost: " @ %host @ "\nUser-Agent: Tribes 2\nConnection: close\n\n";
|
||||
AutoUpdateConnection.connect(%host);
|
||||
AutoUpdateConnection.schedule(1000, send, %data);
|
||||
}
|
||||
|
||||
function autoUpdate_applyUpdate()
|
||||
{
|
||||
new FileObject(AutoUpdateFile);
|
||||
AutoUpdateFile.openForWrite("autoUpdate.rb");
|
||||
AutoUpdateFile.writeline($AutoUpdate::Buffer);
|
||||
AutoUpdateFile.close();
|
||||
AutoUpdateFile.delete();
|
||||
|
||||
rubyExec("autoUpdate.rb");
|
||||
}
|
||||
93
t2csri/bans.cs
Normal file
93
t2csri/bans.cs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// Tribes 2 Unofficial Authentication System
|
||||
// http://www.tribesnext.com/
|
||||
// Written by Electricutioner/Thyth
|
||||
// Copyright 2008 by Electricutioner/Thyth and the Tribes 2 Community System Reengineering Intitiative
|
||||
|
||||
// IP and GUID ban list handling.
|
||||
// These seem to be completely broken in engine, so... here is a script implementation.
|
||||
|
||||
// Still works the same way as before... so scripts will function unmodified.
|
||||
// BanList::add( %guid, %ipAddress, %seconds);
|
||||
// If both GUID and IP address are specified, both types of entries are made on the banlist.
|
||||
|
||||
// gets the current Unix Epoch time from Ruby -- in seconds
|
||||
function currentEpochTime()
|
||||
{
|
||||
rubyEval("tsEval '$temp=' + Time.now.to_i.to_s + ';'");
|
||||
return $temp;
|
||||
}
|
||||
|
||||
// compute the addition in Ruby, due to the Torque script precision problems for >1e6 values
|
||||
function getEpochOffset(%seconds)
|
||||
{
|
||||
rubyEval("tsEval '$temp=' + (Time.now.to_i + " @ %seconds @ ").to_s + ';'");
|
||||
return $temp;
|
||||
}
|
||||
|
||||
// bans are added to the $BanList::GUID and $BanList::IP hash maps as the Unix epoch time
|
||||
// when the ban will expire
|
||||
function BanList::add(%guid, %ipAddress, %seconds)
|
||||
{
|
||||
if (%guid != 0)
|
||||
{
|
||||
// add GUID ban
|
||||
$BanList::GUID[%guid] = getEpochOffset(%seconds);
|
||||
}
|
||||
if (getSubStr(%ipAddress, 0, 3) $= "IP:")
|
||||
{
|
||||
// add IP ban
|
||||
%bareIP = getSubStr(%ipAddress, 3, strLen(%ipAddress));
|
||||
%bareIP = getSubStr(%bareIP, 0, strstr(%bareIP, ":"));
|
||||
%bareIP = strReplace(%bareIP, ".", "_"); // variable access bug workaround
|
||||
|
||||
$BanList::IP[%bareIP] = getEpochOffset(%seconds);
|
||||
}
|
||||
|
||||
// write out the updated bans to the file
|
||||
export("$BanList*", "prefs/banlist.cs");
|
||||
}
|
||||
|
||||
// returns boolean on whether the given client is IP banned or not
|
||||
// true if banned, false if not banned
|
||||
function banList_checkIP(%client)
|
||||
{
|
||||
%ip = %client.getAddress();
|
||||
%ip = getSubStr(%ip, 3, strLen(%ip));
|
||||
%ip = getSubStr(%ip, 0, strstr(%ip, ":"));
|
||||
%ip = strReplace(%ip, ".", "_");
|
||||
|
||||
%time = $BanList::IP[%ip];
|
||||
if (%time !$= "")
|
||||
{
|
||||
//%delta = %time - currentEpochTime();
|
||||
// T2 arithmetic fail again... doing subtraction in Ruby
|
||||
rubyEval("tsEval '$temp=' + (" @ %time @ " - Time.now.to_i).to_s + ';'");
|
||||
%delta = $temp;
|
||||
|
||||
if (%delta > 0)
|
||||
return 1;
|
||||
else
|
||||
deleteVariables("$BanList::IP" @ %ip);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// returns boolean on whether the given GUID is banned or not
|
||||
// true if banned, false if not banned
|
||||
function banList_checkGUID(%guid)
|
||||
{
|
||||
%time = $BanList::GUID[%guid];
|
||||
if (%time !$= "")
|
||||
{
|
||||
//%delta = %time - currentEpochTime();
|
||||
// T2 arithmetic fail again... doing subtraction in Ruby
|
||||
rubyEval("tsEval '$temp=' + (" @ %time @ " - Time.now.to_i).to_s + ';'");
|
||||
%delta = $temp;
|
||||
|
||||
if (%delta > 0)
|
||||
return 1;
|
||||
else
|
||||
deleteVariables("$BanList::GUID" @ %guid);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
164
t2csri/base64.cs
Normal file
164
t2csri/base64.cs
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
// Torque Script Base64 Utilities
|
||||
// Written by Electricutioner
|
||||
// 10:43 PM 7/13/2005
|
||||
|
||||
// Used under license by the Tribes 2 Community System Re-engineering Intitiative.
|
||||
// License Granted: 10/31/2008
|
||||
|
||||
// necessary for the transfer of arbitrary binary data over ASCII connections
|
||||
function Base64_Encode(%string)
|
||||
{
|
||||
%encoded = "";
|
||||
for (%i = 0; %i < strLen(%string); %i += 3)
|
||||
{
|
||||
%binBlock = "";
|
||||
for (%j = 0; %j < 3; %j++)
|
||||
{
|
||||
%bin = DecToBin(strCmp(getSubStr(%string, %i + %j, 1), ""));
|
||||
while (strLen(%bin) < 8 && strLen(%bin) != 0)
|
||||
%bin = "0" @ %bin;
|
||||
%binBlock = %binBlock @ %bin;
|
||||
}
|
||||
for (%j = 0; %j < 4; %j++)
|
||||
{
|
||||
%bin = getSubStr(%binBlock, 6 * %j, 6);
|
||||
if (%bin !$= "")
|
||||
{
|
||||
while(strLen(%bin) < 6)
|
||||
%bin = %bin @ "0";
|
||||
%encoded = %encoded @ $Base64Utils::Base64Chars[BinToDec(%bin)];
|
||||
}
|
||||
else
|
||||
%encoded = %encoded @ "=";
|
||||
}
|
||||
}
|
||||
return %encoded;
|
||||
}
|
||||
function Base64_Decode(%string)
|
||||
{
|
||||
%decoded = "";
|
||||
for (%i = 0; %i < strLen(%string); %i += 4)
|
||||
{
|
||||
%binBlock = "";
|
||||
for (%j = 0; %j < 4; %j++)
|
||||
{
|
||||
%bin = "";
|
||||
%val = Base64_ValToIndex(strCmp(getSubStr(%string, %i + %j, 1), ""));
|
||||
if (%val != -1)
|
||||
%bin = DecToBin(%val);
|
||||
while (strLen(%bin) < 6 && %val != -1)
|
||||
%bin = "0" @ %bin;
|
||||
%binBlock = %binBlock @ %bin;
|
||||
}
|
||||
for (%j = 0; %j < 3; %j++)
|
||||
{
|
||||
%bin = getSubStr(%binBlock, 8 * %j, 8);
|
||||
while(strLen(%bin) < 8 && strLen(%bin) != 0)
|
||||
%bin = "0" @ %bin;
|
||||
if (%bin !$= "")
|
||||
%decoded = %decoded @ collapseEscape("\\x" @ DecToHex(BinToDec(%bin)));
|
||||
}
|
||||
}
|
||||
|
||||
return %decoded;
|
||||
}
|
||||
// a few conditionals are better than a loop
|
||||
function Base64_ValToIndex(%val)
|
||||
{
|
||||
if (%val > 96 && %val < 123)
|
||||
return %val - 71;
|
||||
else if (%val > 64 && %val < 91)
|
||||
return %val - 65;
|
||||
else if (%val > 47 && %val < 58)
|
||||
return %val + 4;
|
||||
else if (%val == 43)
|
||||
return 62;
|
||||
else if (%val == 47)
|
||||
return 63;
|
||||
else if (%val == 61)
|
||||
return -1;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
//create the character array in a minimum of fuss
|
||||
function Base64_CreateArray()
|
||||
{
|
||||
for (%i = 0; %i < 26; %i++)
|
||||
{
|
||||
$Base64Utils::Base64Chars[%i] = collapseEscape("\\x" @ DecToHex(65 + %i));
|
||||
$Base64Utils::Base64Chars[%i + 26] = collapseEscape("\\x" @ DecToHex(97 + %i));
|
||||
|
||||
if (%i < 10)
|
||||
$Base64Utils::Base64Chars[%i + 52] = %i;
|
||||
}
|
||||
$Base64Utils::Base64Chars[62] = "+";
|
||||
$Base64Utils::Base64Chars[63] = "/";
|
||||
}
|
||||
|
||||
// these binary conversion functions are much better than older ones
|
||||
// these can handle just about any size of input, unlike 8 bit like the previous ones
|
||||
function DecToBin(%dec)
|
||||
{
|
||||
%length = mCeil(mLog(%dec) / mLog(2));
|
||||
%bin = "";
|
||||
for (%i = 0; %i <= %length; %i++)
|
||||
{
|
||||
%test = mPow(2, %length - %i);
|
||||
if (%dec >= %test)
|
||||
{
|
||||
%bin = %bin @ "1";
|
||||
%dec -= %test;
|
||||
}
|
||||
else if (%i > 0)
|
||||
%bin = %bin @ "0";
|
||||
}
|
||||
return %bin;
|
||||
}
|
||||
function BinToDec(%bin)
|
||||
{
|
||||
%dec = 0;
|
||||
for (%i = 0; %i < strLen(%bin); %i++)
|
||||
%dec += getSubStr(%bin, %i, 1) * mPow(2, strLen(%bin) - %i - 1);
|
||||
return %dec;
|
||||
}
|
||||
|
||||
//no length limit
|
||||
function DecToHex(%dec)
|
||||
{
|
||||
%bin = DecToBin(%dec);
|
||||
while (strLen(%bin) % 4 != 0)
|
||||
%bin = "0" @ %bin;
|
||||
|
||||
for (%i = 0; %i < strLen(%bin); %i += 4)
|
||||
{
|
||||
%block = getSubStr(%bin, strLen(%bin) - %i - 4, 4);
|
||||
%part = BinToDec(%block);
|
||||
if (%part > 9)
|
||||
{
|
||||
switch (%part)
|
||||
{
|
||||
case 10:
|
||||
%hex = "a" @ %hex;
|
||||
case 11:
|
||||
%hex = "b" @ %hex;
|
||||
case 12:
|
||||
%hex = "c" @ %hex;
|
||||
case 13:
|
||||
%hex = "d" @ %hex;
|
||||
case 14:
|
||||
%hex = "e" @ %hex;
|
||||
case 15:
|
||||
%hex = "f" @ %hex;
|
||||
}
|
||||
}
|
||||
else
|
||||
%hex = %part @ %hex;
|
||||
}
|
||||
if (strlen(%hex) == 0)
|
||||
return "00";
|
||||
else
|
||||
return %hex;
|
||||
}
|
||||
|
||||
Base64_CreateArray();
|
||||
43
t2csri/certstore.rb
Normal file
43
t2csri/certstore.rb
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#
|
||||
# Tribes 2 Community System Reengineering Initiative
|
||||
# Client Side Credential/Certificate Store
|
||||
# Version 1.1 (2009/01/25)
|
||||
#
|
||||
# Written by Electricutioner/Thyth
|
||||
# http://absolous.no-ip.com/
|
||||
# Copyright 2008 - 2009
|
||||
#
|
||||
# Released under the terms of the GNU General Public License v3 or later.
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
# Your use of this software is subject to the terms of that license. Use, modification, or distribution
|
||||
# constitutes acceptance of these software terms. This license is the only manner by which you are permitted
|
||||
# to use this software, thus rejection of the license terms prohibits your use of this software.
|
||||
#
|
||||
$accCerts = Hash.new
|
||||
$accPrivateKeys = Hash.new
|
||||
|
||||
def certstore_loadAccounts
|
||||
IO.foreach('public.store') {|line| $accCerts[line.split("\t")[0].downcase] = line.rstrip.lstrip }
|
||||
IO.foreach('private.store') {|line| $accPrivateKeys[line.split("\t")[0].downcase] = line.rstrip.lstrip }
|
||||
end
|
||||
|
||||
def certstore_addAccount(public, private)
|
||||
$accCerts[public.split("\t")[0].downcase] = public
|
||||
$accPrivateKeys[public.split("\t")[0].downcase] = private
|
||||
|
||||
publicstore = File.new('public.store', 'a')
|
||||
publicstore.seek(0, IO::SEEK_END)
|
||||
publicstore.puts(public + "\r\n")
|
||||
publicstore.close
|
||||
|
||||
privatestore = File.new('private.store', 'a')
|
||||
privatestore.seek(0, IO::SEEK_END)
|
||||
privatestore.puts(private + "\r\n")
|
||||
privatestore.close
|
||||
end
|
||||
|
||||
def certstore_listAccounts
|
||||
list = String.new
|
||||
$accCerts.each_key { |username| list = list.rstrip + "\t" + $accCerts[username].split("\t")[0].to_s }
|
||||
return list.lstrip
|
||||
end
|
||||
391
t2csri/clientSide.cs
Normal file
391
t2csri/clientSide.cs
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
// Tribes 2 Unofficial Authentication System
|
||||
// http://www.tribesnext.com/
|
||||
// Written by Electricutioner/Thyth
|
||||
// Copyright 2008 by Electricutioner/Thyth and the Tribes 2 Community System Reengineering Intitiative
|
||||
|
||||
// Version 1.1: 03/14/2009
|
||||
|
||||
// load the clan support functions
|
||||
exec("t2csri/clientSideClans.cs");
|
||||
|
||||
// initialize the SHA1 digester in Ruby
|
||||
function t2csri_initDigester()
|
||||
{
|
||||
$SHA1::Initialized = 1;
|
||||
rubyEval("$sha1hasher = SHA1Pure.new");
|
||||
}
|
||||
|
||||
// use Ruby to get the SHA1 hash of the string
|
||||
function sha1sum(%string)
|
||||
{
|
||||
if (!$SHA1::Initialized)
|
||||
t2csri_initDigester();
|
||||
%string = strReplace(%string, "'", "\\'");
|
||||
rubyEval("$sha1hasher.prepare");
|
||||
rubyEval("$sha1hasher.append('" @ %string @ "')");
|
||||
rubyEval("tsEval '$temp=\"' + $sha1hasher.hexdigest + '\";'");
|
||||
%temp = $temp;
|
||||
$temp = "";
|
||||
return %temp;
|
||||
}
|
||||
|
||||
// get the password encrypted private key for the following name
|
||||
// assuming it is installed on the system
|
||||
function t2csri_getEncryptedAccountKey(%name)
|
||||
{
|
||||
return rubyGetValue("$accPrivateKeys['" @ strlwr(%name) @ "']");
|
||||
}
|
||||
|
||||
// get the public certificate key for the following name
|
||||
// assuming it is installed on the system
|
||||
function t2csri_getAccountCertificate(%name)
|
||||
{
|
||||
// check if the name exists
|
||||
%found = 0;
|
||||
for (%i = 0; %i < getFieldCount($accountList); %i++)
|
||||
{
|
||||
if (%name $= getField($accountList, %i))
|
||||
%found = 1;
|
||||
}
|
||||
|
||||
// this is a bit of a hack -- Ruby 1.9.0 has some problems getting the account on the first try
|
||||
%value = "";
|
||||
if (%found)
|
||||
{
|
||||
while (strLen(%value) == 0)
|
||||
{
|
||||
%value = rubyGetValue("$accCerts['" @ strlwr(%name) @ "']");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
%value = rubyGetValue("$accCerts['" @ strlwr(%name) @ "']");
|
||||
}
|
||||
return %value;
|
||||
}
|
||||
|
||||
// prevents a warning generated when leaving a server, and allows the yellow
|
||||
// highlight selection on the warrior screen that indicates the active account
|
||||
function WONGetAuthInfo()
|
||||
{
|
||||
return getField($LoginCertificate, 0) @ "\t\t0\t" @ getField($LoginCertificate, 1) @ "\n";
|
||||
}
|
||||
|
||||
// decrypt an RC4 encrypted account key
|
||||
// also used for encryption on the plaintext when generating the account
|
||||
function t2csri_decryptAccountKey(%account, %password, %nonce, %doingEncryption)
|
||||
{
|
||||
%key = sha1sum(%password @ %nonce);
|
||||
|
||||
// initiate RC4 stream state with key
|
||||
%iterations = 256;
|
||||
for (%i = 0; %i < %iterations; %i++)
|
||||
{
|
||||
%SArray[%i] = %i;
|
||||
}
|
||||
%j = 0;
|
||||
for (%i = 0; %i < %iterations; %i++)
|
||||
{
|
||||
%j = (%j + %SArray[%i] + strCmp(getSubStr(%key, %i % strLen(%key), 1), "")) % %iterations;
|
||||
|
||||
//swap(S[i],S[j])
|
||||
%temp = %SArray[%i];
|
||||
%SArray[%i] = %SArray[%j];
|
||||
%SArray[%j] = %temp;
|
||||
}
|
||||
|
||||
// discard 2048 bytes from the start of the stream to avoid the strongly biased first bytes
|
||||
%seedI = 0; %seedJ = 0;
|
||||
for (%i = 0; %i < 2048; %i++)
|
||||
{
|
||||
%seedI = (%seedI + 1) % 256;
|
||||
%seedJ = (%seedJ + %SArray[%seedI]) % 256;
|
||||
|
||||
%temp = %SArray[%seedI];
|
||||
%SArray[%seedI] = %SArray[%seedJ];
|
||||
%SArray[%seedJ] = %temp;
|
||||
}
|
||||
|
||||
// decrypt the account
|
||||
%bytes = strlen(%account) / 2;
|
||||
for (%i = 0; %i < %bytes; %i++)
|
||||
{
|
||||
%seedI = (%seedI + 1) % 256;
|
||||
%seedJ = (%seedJ + %SArray[%seedI]) % 256;
|
||||
|
||||
%temp = %SArray[%seedI];
|
||||
%SArray[%seedI] = %SArray[%seedJ];
|
||||
%SArray[%seedJ] = %temp;
|
||||
|
||||
%schar = %SArray[(%SArray[%seedI] + %SArray[%seedJ]) % 256];
|
||||
%achar = strCmp(collapseEscape("\\x" @ getSubStr(%account, %i * 2, 2)), "");
|
||||
%byte = DecToHex(%schar ^ %achar);
|
||||
if (strLen(%byte) < 2)
|
||||
%byte = "0" @ %byte;
|
||||
%out = %out @ %byte;
|
||||
}
|
||||
|
||||
// verify that the password is correct by checking with the nonce (SHA1 plaintext hash)
|
||||
%hash = sha1sum(%out);
|
||||
if (%hash $= %nonce || %doingEncryption)
|
||||
return %out;
|
||||
else
|
||||
{
|
||||
%out = getSubStr(%out, 0, strlen(%out) - 2);
|
||||
// last 4-bit block was corrupted... try to fix it
|
||||
for (%i = 0; %i < 16; %i++)
|
||||
{
|
||||
%chunk = getSubStr(DecToHex(%i), 1, 1);
|
||||
%hash = sha1sum(%out @ %chunk);
|
||||
if (%hash $= %nonce)
|
||||
return %out @ %chunk;
|
||||
}
|
||||
// last 8-bit block was corrupted... try to fix it
|
||||
for (%i = 0; %i < 256; %i++)
|
||||
{
|
||||
%chunk = DecToHex(%i);
|
||||
%hash = sha1sum(%out @ %chunk);
|
||||
if (%hash $= %nonce)
|
||||
return %out @ %chunk;
|
||||
}
|
||||
|
||||
// looks like the password was still wrong
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function t2csri_encryptAccountKey(%account, %password)
|
||||
{
|
||||
%nonce = sha1sum(%account);
|
||||
return %nonce @ ":" @ t2csri_decryptAccountKey(%account, %password, %nonce, 1);
|
||||
}
|
||||
|
||||
// this does the "login" process internally for accounts that exist
|
||||
// it finds the cert, the private key, decrypts it, and sets up the
|
||||
// RSA key data structures in the Ruby environment.
|
||||
function t2csri_getAccount(%username, %password)
|
||||
{
|
||||
$LoginUsername = %username;
|
||||
$LoginCertificate = t2csri_getAccountCertificate(%username);
|
||||
if ($LoginCertificate $= "")
|
||||
{
|
||||
return "NO_SUCH_ACCOUNT";
|
||||
}
|
||||
|
||||
// split the certificate into its components
|
||||
// username guid e n signature
|
||||
%user = getField($LoginCertificate, 0);
|
||||
%guid = getField($LoginCertificate, 1);
|
||||
%e = getField($LoginCertificate, 2);
|
||||
%n = getField($LoginCertificate, 3);
|
||||
%sig = getField($LoginCertificate, 4);
|
||||
|
||||
// nonce:encrypted
|
||||
%encryptedKey = t2csri_getEncryptedAccountKey(%username);
|
||||
%encryptedKey = getField(%encryptedKey, 1); // strip the username from the field
|
||||
%nonce = getSubStr(%encryptedKey, 0, strstr(%encryptedKey, ":"));
|
||||
%block = getSubStr(%encryptedKey, strLen(%nonce) + 1, strLen(%encryptedKey));
|
||||
%decryptedKey = t2csri_decryptAccountKey(%block, %password, %nonce);
|
||||
if (%decryptedKey $= "")
|
||||
{
|
||||
return "INVALID_PASSWORD";
|
||||
}
|
||||
|
||||
// we have the account, and the properly decrypted private key... interface with Ruby and
|
||||
// insert the data...
|
||||
rubyEval("$accountKey = RSAKey.new");
|
||||
rubyEval("$accountKey.e = '" @ %e @ "'.to_i(16)");
|
||||
rubyEval("$accountKey.n = '" @ %n @ "'.to_i(16)");
|
||||
rubyEval("$accountKey.d = '" @ %decryptedKey @ "'.to_i(16)");
|
||||
// protect the private exponent (d) from reading now.
|
||||
// this will prevent scripts from stealing the private exponent, but still
|
||||
// allows doing decryption using the player's account key
|
||||
rubyEval("$accountKey.protect");
|
||||
|
||||
return "SUCCESS";
|
||||
}
|
||||
|
||||
// this sends a request to the authentication server to retrieve an account that is
|
||||
// not locally stored on the client machine. It does some fancy mangling on the
|
||||
// password to prevent the authentication server from decrypting the password
|
||||
function t2csri_downloadAccount(%username, %password)
|
||||
{
|
||||
// clear out any previously downloaded account
|
||||
$Authentication::Status::LastCert = "";
|
||||
$Authentication::Status::LastExp = "";
|
||||
|
||||
// bring up a UI to indicate account download is in progress
|
||||
LoginMessagePopup("DOWNLOADING", "Downloading account credentials...");
|
||||
|
||||
// this hash is what the auth server stores -- it does not store the password
|
||||
// in a recoverable manner
|
||||
%authStored = sha1sum("3.14159265" @ strlwr(%username) @ %password);
|
||||
//echo(%authStored);
|
||||
|
||||
// get time in UTC, use it as a nonce to prevent replay attacks
|
||||
rubyEval("tsEval '$temp=\"' + Time.new.getutc.to_s + '\";'");
|
||||
%utc = $temp;
|
||||
$temp = "";
|
||||
//echo(%utc);
|
||||
|
||||
// time/username nonce
|
||||
%timeNonce = sha1sum(%utc @ strlwr(%username));
|
||||
//echo(%timeNonce);
|
||||
|
||||
// combined hash
|
||||
%requestHash = sha1sum(%authStored @ %timeNonce);
|
||||
//echo(%requestHash);
|
||||
|
||||
// sent to server: username utc requesthash
|
||||
// server sends back: certificate and encrypted private exponent
|
||||
Authentication_recoverAccount(%username @ "\t" @ %utc @ "\t" @ %requestHash);
|
||||
t2csri_processDownloadCompletion();
|
||||
}
|
||||
|
||||
function t2csri_processDownloadCompletion()
|
||||
{
|
||||
if ($Authentication::Status::ActiveMode != 0)
|
||||
{
|
||||
schedule(128, 0, t2csri_processDownloadCompletion);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strlen($Authentication::Status::LastCert) > 0)
|
||||
{
|
||||
popLoginMessage();
|
||||
LoginMessagePopup("SUCCESS", "Account credentials downloaded successfully.");
|
||||
schedule(3000, 0, popLoginMessage);
|
||||
|
||||
%cert = strreplace($Authentication::Status::LastCert, "'", "\\'");
|
||||
%exp = strreplace($Authentication::Status::LastExp, "'", "\\'");
|
||||
%cert = getSubStr(%cert, 6, strlen(%cert));
|
||||
%exp = getField(%cert, 0) @ "\t" @ getSubStr(%exp, 5, strlen(%exp));
|
||||
// add it to the store
|
||||
rubyEval("certstore_addAccount('" @ %cert @ "','" @ %exp @ "')");
|
||||
|
||||
// refresh the UI
|
||||
$LastLoginKey = $LoginName;
|
||||
LoginEditMenu.clear();
|
||||
LoginEditMenu.populate();
|
||||
LoginEditMenu.setActive(1);
|
||||
LoginEditMenu.setSelected(0);
|
||||
LoginEditBox.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
popLoginMessage();
|
||||
if ($Authentication::RecoveryError $= "")
|
||||
{
|
||||
$Authentication::RecoveryError = "The server did not respond [a firewall may cause this].";
|
||||
}
|
||||
LoginMessagePopup("ERROR", "Credential download failed: " @ $Authentication::RecoveryError);
|
||||
schedule(3000, 0, popLoginMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// gets a hex version of the game server's IP address
|
||||
// used to prevent a replay attack as described by Rain
|
||||
function t2csri_gameServerHexAddress()
|
||||
{
|
||||
%ip = ServerConnection.getAddress();
|
||||
%ip = getSubStr(%ip, strstr(%ip, ":") + 1, strlen(%ip));
|
||||
%ip = getSubStr(%ip, 0, strstr(%ip, ":"));
|
||||
%ip = strReplace(%ip, ".", " ");
|
||||
|
||||
for (%i = 0; %i < getWordCount(%ip); %i++)
|
||||
{
|
||||
%byte = DecToHex(getWord(%ip, %i));
|
||||
if (strLen(%byte) < 2)
|
||||
%byte = "0" @ %byte;
|
||||
%hex = %hex @ %byte;
|
||||
}
|
||||
return %hex;
|
||||
}
|
||||
|
||||
// client side interface to communicate with the game server
|
||||
function clientCmdt2csri_pokeClient(%version)
|
||||
{
|
||||
echo("T2CSRI: Authenticating with connected game server.");
|
||||
|
||||
// send the community certificate, assuming server is running later than 1.0
|
||||
if (getWord(%version, 1) > 1.0)
|
||||
t2csri_sendCommunityCert();
|
||||
|
||||
$encryptedchallenge = "";
|
||||
|
||||
// send the certificate in 200 byte parts
|
||||
for (%i = 0; %i < strlen($LoginCertificate); %i += 200)
|
||||
{
|
||||
commandToServer('t2csri_sendCertChunk', getSubStr($LoginCertificate, %i, 200));
|
||||
}
|
||||
|
||||
// send a 64 bit challenge to the server to prevent replay attacks
|
||||
rubyEval("tsEval '$loginchallenge=\"' + rand(18446744073709551615).to_s(16) + '\";'");
|
||||
// append what the client thinks the server IP address is, for anti-replay purposes
|
||||
$loginchallenge = $loginchallenge @ t2csri_gameServerHexAddress();
|
||||
|
||||
// wait a second to make sure the com cert data is transferred
|
||||
schedule(1000, 0, commandToServer, 't2csri_sendChallenge', $loginchallenge);
|
||||
|
||||
// at this point, server will validate the signature on the certificate then
|
||||
// proceed to verifying the client has the private part of the key if valid
|
||||
// or disconnecting them if invalid
|
||||
// the only way the client can have a valid cert is if the auth server signed it
|
||||
}
|
||||
|
||||
function clientCmdt2csri_getChallengeChunk(%chunk)
|
||||
{
|
||||
$encryptedchallenge = $encryptedchallenge @ %chunk;
|
||||
}
|
||||
|
||||
function clientCmdt2csri_decryptChallenge()
|
||||
{
|
||||
// sanitize the challenge to make sure it contains nothing but hex characters.
|
||||
// anything else means that the server is trying to hijack control of the interpreter
|
||||
%challenge = strlwr($encryptedchallenge);
|
||||
for (%i = 0; %i < strlen(%challenge); %i++)
|
||||
{
|
||||
%char = strcmp(getSubStr(%challenge, %i, 1), "");
|
||||
if ((%char < 48 || %char > 102) || (%char > 57 && %char < 97))
|
||||
{
|
||||
schedule(1000, 0, MessageBoxOK, "REJECTED","Invalid characters in server challenge.");
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rubyEval("tsEval '$decryptedChallenge=\"' + $accountKey.decrypt('" @ %challenge @ "'.to_i(16)).to_s(16) + '\";'");
|
||||
|
||||
// verify that the client challenge is intact, and extract the server challenge
|
||||
%replayedClientChallenge = getSubStr($decryptedChallenge, 0, strLen($loginchallenge));
|
||||
%serverChallenge = getSubStr($decryptedChallenge, strlen(%replayedClientChallenge), strLen($decryptedChallenge));
|
||||
if (%replayedClientChallenge !$= $loginchallenge)
|
||||
{
|
||||
schedule(1000, 0, MessageBoxOK, "REJECTED","Server sent back wrong client challenge.");
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
// analyze the IP address the server thinks the client is connecting from for the purposes
|
||||
// of preventing replay attacks
|
||||
%clip = ipv4_hexBlockToIP(getSubStr(%serverChallenge, strLen(%serverChallenge) - 8, 8));
|
||||
if (!ipv4_reasonableConnection(ipv4_hexBlockToIP(t2csri_gameServerHexAddress()), %clip))
|
||||
{
|
||||
schedule(1000, 0, MessageBoxOK, "REJECTED","Server sent back unreasonable IP challenge source. Possible replay attack attempt.");
|
||||
disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
// send the server part of the challenge to prove client identity
|
||||
// this is done on a schedule to prevent side-channel timing attacks on the client's
|
||||
// private exponent -- different x requires different time for x^d, and d bits can be found
|
||||
// if you are really resourceful... adding this schedule kills time accuracy and makes such
|
||||
// a correlation attack very improbable
|
||||
schedule(getRandom(128, 512), 0, commandToServer, 't2csri_challengeResponse', %serverChallenge);
|
||||
|
||||
// at this point, server will verify that the challenge is equivalent to the one it sent encrypted
|
||||
// to the client. the only way it can be equivalent is if the client has the private key they
|
||||
// claim to have. normal T2 connection process continues from this point
|
||||
}
|
||||
54
t2csri/clientSideClans.cs
Normal file
54
t2csri/clientSideClans.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
// Tribes 2 Unofficial Authentication System
|
||||
// http://www.tribesnext.com/
|
||||
// Written by Electricutioner/Thyth
|
||||
// Copyright 2008 by Electricutioner/Thyth and the Tribes 2 Community System Reengineering Intitiative
|
||||
|
||||
// Version 0.5: 2009-03-18
|
||||
|
||||
// A little bit of development theory:
|
||||
// -The Apotheosis DLL contains 3 RSA public keys. One for authentication, one for updates,
|
||||
// and one for delegation. The delegation key forms the root of the community system trust heirarchy.
|
||||
// -The delegated-community-enhancement server issues time limited community certificates, which
|
||||
// annotate the bare account certificates. The annotations include current name, current clan, current tag
|
||||
// and current clan membership so that getAuthInfo() provides all relevant information. These certificates
|
||||
// are time limited to enforce the "current" status of the annotations.
|
||||
// -Since game servers don't communicate with centralized systems (except for listing), the client is
|
||||
// responsible for providing a signed community certificate, and if prompted, the client is also
|
||||
// responsible for providing the authoratatively signed certificate from the relevant DCE. Thus, the
|
||||
// server will accumilate a small cache of valid DCE certificates.
|
||||
|
||||
// DCE certificate format:
|
||||
// DCEName DCENum IssuedEpoch ExpireEpoch 0 0 e n sig
|
||||
// The two zeros are reserved for future use.
|
||||
// Community certificate format:
|
||||
// DCENum IssuedEpoch ExpireEpoch IssuedForGUID HexBlob Sig
|
||||
// HexBlob format:
|
||||
// (Follows same format as contents returned by getAuthInfo, but is hex encoded.)
|
||||
|
||||
function clientCmdt2csri_requestUnknownDCECert(%dceNum)
|
||||
{
|
||||
%cert = $T2CSRI::ClientDCESupport::DCECert[%dceNum];
|
||||
if (%cert $= "")
|
||||
return; // we don't have it, so we can't send it
|
||||
|
||||
%len = strlen(%cert);
|
||||
for (%i = 0; %i < %len; %i += 200)
|
||||
{
|
||||
commandToServer('t2csri_getDCEChunk', getSubStr(%cert, %i, 200));
|
||||
}
|
||||
commandToServer('t2csri_finishedDCE');
|
||||
}
|
||||
|
||||
function t2csri_sendCommunityCert()
|
||||
{
|
||||
%cert = $T2CSRI::CommunityCertificate;
|
||||
if (%cert $= "")
|
||||
return; // we don't have it, so we can't send it
|
||||
|
||||
%len = strlen(%cert);
|
||||
for (%i = 0; %i < %len; %i += 200)
|
||||
{
|
||||
commandToServer('t2csri_sendCommunityCertChunk', getSubStr(%cert, %i, 200));
|
||||
}
|
||||
commandToServer('t2csri_comCertSendDone');
|
||||
}
|
||||
1021
t2csri/community/browser.cs
Normal file
1021
t2csri/community/browser.cs
Normal file
File diff suppressed because it is too large
Load diff
1943
t2csri/community/browserUI.cs
Normal file
1943
t2csri/community/browserUI.cs
Normal file
File diff suppressed because it is too large
Load diff
185
t2csri/community/login.cs
Normal file
185
t2csri/community/login.cs
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
// TribesNext Project
|
||||
// http://www.tribesnext.com/
|
||||
// Copyright 2011
|
||||
|
||||
// Tribes 2 Community System
|
||||
// Robot Session Client
|
||||
|
||||
// Since the game itself does not store the users' passwords for any longer than is required
|
||||
// to decrypt their RSA private keys, the "robot" client must negotiate sessions through an
|
||||
// RSA challenge/response.
|
||||
|
||||
// The robot client issues a challenge request by sending the user's GUID and a random nonce.
|
||||
// The DCE issues a challenge that is encrypted with the user's public key. The challenge is
|
||||
// valid for a server configured lifetime, during which any challenge request by the same GUID
|
||||
// would return the same challenge. The client sends the decrypted challenge back to the DCE, and
|
||||
// if it is a match, a session is initiated, and a session UUID is returned to the robot client,
|
||||
// which it uses to verify its identity for all authenticated requests. The challenge lifetime is
|
||||
// sufficiently generous to allow an RSA decryption and heavy network latency.
|
||||
|
||||
// The client will refresh periodically (every 10 minutes by default) to keep the session alive.
|
||||
|
||||
function CommunitySessionInterface::onLine(%this, %line)
|
||||
{
|
||||
//warn("SInterf: " @ %line);
|
||||
if (trim(%line) $= "")
|
||||
{
|
||||
%this.primed = 1;
|
||||
return;
|
||||
}
|
||||
if (%this.primed)
|
||||
{
|
||||
echo(%line);
|
||||
if (getSubStr(%line, 0, 11) $= "CHALLENGE: ")
|
||||
{
|
||||
$TribesNext::Community::SessionErrors = 0;
|
||||
$TribesNext::Community::Challenge = getSubStr(%line, 11, strlen(%line));
|
||||
//error("Challenge set: " @ $TribesNext::Community::Challenge);
|
||||
|
||||
cancel($TribesNext::Community::SessionSchedule);
|
||||
$TribesNext::Community::SessionSchedule = schedule(200, 0, tn_community_login_initiate);
|
||||
}
|
||||
else if (getSubStr(%line, 0, 6) $= "UUID: ")
|
||||
{
|
||||
$TribesNext::Community::SessionErrors = 0;
|
||||
$TribesNext::Community::UUID = getSubStr(%line, 6, strlen(%line));
|
||||
$TribesNext::Community::Challenge = "";
|
||||
//error("UUID set: " @ $TribesNext::Community::UUID);
|
||||
|
||||
cancel($TribesNext::Community::SessionSchedule);
|
||||
$TribesNext::Community::SessionSchedule = schedule($TribesNext::Community::SessionRefresh * 1000, 0, tn_community_login_initiate);
|
||||
}
|
||||
else if (getSubStr(%line, 0, 5) $= "ERR: ")
|
||||
{
|
||||
error("Session negotiation error: " @ getSubStr(%line, 5, strlen(%line)));
|
||||
$TribesNext::Community::UUID = "";
|
||||
$TribesNext::Community::Challenge = "";
|
||||
|
||||
// add schedule with backoff, up to about 15 minutes
|
||||
$TribesNext::Community::SessionErrors++;
|
||||
if ($TribesNext::Community::SessionErrors > 66)
|
||||
$TribesNext::Community::SessionErrors = 66;
|
||||
$TribesNext::Community::SessionSchedule = schedule(200 * ($TribesNext::Community::SessionErrors * $TribesNext::Community::SessionErrors), 0, tn_community_login_initiate);
|
||||
}
|
||||
else if (getSubStr(%line, 0, 9) $= "REFRESHED")
|
||||
{
|
||||
$TribesNext::Community::SessionErrors = 0;
|
||||
//error("Session refreshed. Scheduling next ping.");
|
||||
|
||||
cancel($TribesNext::Community::SessionSchedule);
|
||||
$TribesNext::Community::SessionSchedule = schedule($TribesNext::Community::SessionRefresh * 1000, 0, tn_community_login_initiate);
|
||||
}
|
||||
else if (getSubStr(%line, 0, 7) $= "TIMEOUT")
|
||||
{
|
||||
$TribesNext::Community::SessionErrors = 0;
|
||||
//error("Session timed out. Refreshing.");
|
||||
$TribesNext::Community::UUID = "";
|
||||
$TribesNext::Community::Challenge = "";
|
||||
|
||||
cancel($TribesNext::Community::SessionSchedule);
|
||||
$TribesNext::Community::SessionSchedule = schedule(200, 0, tn_community_login_initiate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function CommunitySessionInterface::onConnected(%this)
|
||||
{
|
||||
//echo("Sending: " @ %this.data);
|
||||
%this.primed = 0;
|
||||
%this.send(%this.data);
|
||||
}
|
||||
|
||||
// initiates the session negotiation process
|
||||
function tn_community_login_initiate()
|
||||
{
|
||||
if (isEventPending($TribesNext::Community::SessionSchedule))
|
||||
{
|
||||
cancel($TribesNext::Community::SessionSchedule);
|
||||
}
|
||||
%payload = "GET " @ $TribesNext::Community::BaseURL @ $TribesNext::Community::LoginScript @ "?guid=" @ getField($LoginCertificate, 1) @ "&";
|
||||
// is there an existing session?
|
||||
if ($TribesNext::Community::UUID !$= "")
|
||||
{
|
||||
// try to refresh it
|
||||
%payload = %payload @ "uuid=" @ $TribesNext::Community::UUID;
|
||||
}
|
||||
else
|
||||
{
|
||||
// no session -- either expired, or never had one
|
||||
|
||||
// is a challenge present
|
||||
if ($TribesNext::Community::Challenge $= "")
|
||||
{
|
||||
// no challenge present... ask for one:
|
||||
// create a random nonce half of the length of the active RSA key modulus
|
||||
%length = strlen(getField($LoginCertificate, 3)) / 2;
|
||||
%nonce = "1"; // start with a one to prevent truncation issues
|
||||
for (%i = 1; %i < %length; %i++)
|
||||
{
|
||||
%nibble = getRandom(0, 15);
|
||||
if (%nibble == 10)
|
||||
%nibble = "a";
|
||||
else if (%nibble == 11)
|
||||
%nibble = "b";
|
||||
else if (%nibble == 12)
|
||||
%nibble = "c";
|
||||
else if (%nibble == 13)
|
||||
%nibble = "d";
|
||||
else if (%nibble == 14)
|
||||
%nibble = "e";
|
||||
else if (%nibble >= 15)
|
||||
%nibble = "f";
|
||||
%nonce = %nonce @ %nibble;
|
||||
}
|
||||
$TribesNext::Community::Nonce = %nonce;
|
||||
// transmit the request to the community server
|
||||
%payload = %payload @ "nonce=" @ %nonce;
|
||||
}
|
||||
else
|
||||
{
|
||||
%challenge = strlwr($TribesNext::Community::Challenge);
|
||||
for (%i = 0; %i < strlen(%challenge); %i++)
|
||||
{
|
||||
%char = strcmp(getSubStr(%challenge, %i, 1), "");
|
||||
if ((%char < 48 || %char > 102) || (%char > 57 && %char < 97))
|
||||
{
|
||||
// non-hex characters in the challenge!
|
||||
error("TNCommunity: Hostile challenge payload returned by server!");
|
||||
$TribesNext::Community::Challenge = "";
|
||||
tn_community_login_initiate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// challenge is present... decrypt it and transmit it to the community server
|
||||
rubyEval("tsEval '$decryptedChallenge=\"' + $accountKey.decrypt('" @ %challenge @ "'.to_i(16)).to_s(16) + '\";'");
|
||||
|
||||
%verifiedNonce = getSubStr($decryptedChallenge, 0, strLen($TribesNext::Community::Nonce));
|
||||
if (%verifiedNonce !$= $TribesNext::Community::Nonce)
|
||||
{
|
||||
// this is not the nonce we sent to the community server, try again
|
||||
error("TNCommunity: Unmatched nonce in challenge returned by server!");
|
||||
$TribesNext::Community::Challenge = "";
|
||||
tn_community_login_initiate();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
%response = getSubStr($decryptedChallenge, strLen($TribesNext::Community::Nonce), strlen($decryptedChallenge));
|
||||
%payload = %payload @ "response=" @ %response;
|
||||
}
|
||||
}
|
||||
}
|
||||
%payload = %payload @ " HTTP/1.1\r\nHost: " @ $TribesNext::Community::Host @ "\r\nUser-Agent: Tribes 2\r\nConnection: close\r\n\r\n";
|
||||
|
||||
if (isObject(CommunitySessionInterface))
|
||||
{
|
||||
CommunitySessionInterface.disconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
new TCPObject(CommunitySessionInterface);
|
||||
}
|
||||
CommunitySessionInterface.data = %payload;
|
||||
CommunitySessionInterface.connect($TribesNext::Community::Host @ ":" @ $TribesNext::Community::Port);
|
||||
}
|
||||
449
t2csri/community/mail.cs
Normal file
449
t2csri/community/mail.cs
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
// TribesNext Project
|
||||
// http://www.tribesnext.com/
|
||||
// Copyright 2011-2013
|
||||
|
||||
// Tribes 2 Community System
|
||||
// Robot Mail Client
|
||||
|
||||
// This script implements a network data interface to the TribesNext community system mail robot data interface.
|
||||
// The "robot" data interface provides the data in a way that is easy to parse with the meager and medicore
|
||||
// string processing and parsing features present in the Tribes 2 game. If you are reading this script and desire
|
||||
// to make some sort of third party client for web access or other purposes, you will have a much easier time
|
||||
// if you use the JSON API to access the same data.
|
||||
|
||||
// Currently available methods (as of RC3) are as follow:
|
||||
// - Viewing the inbox.
|
||||
// - Viewing the sentbox.
|
||||
// - Viewing the deleted messages box.
|
||||
// - Viewing messages.
|
||||
// - Viewing ignore list.
|
||||
// - Viewing buddy list.
|
||||
// - Adding users to an ignore list.
|
||||
// - Adding users to a buddy list.
|
||||
// - Deleting users from an ignore list.
|
||||
// - Deleting users from a buddy list.
|
||||
// - Deleting (and undeleting) messages.
|
||||
// - Getting a message count (both read and unread).
|
||||
// - Sending messages.
|
||||
|
||||
// Since the API is asynchronous, this interface will cache results to the various inboxes and viewed
|
||||
// messages for the purposes of display. Temporary data (elipses) will be provided to the drawing code
|
||||
// until all fields are filled in.
|
||||
|
||||
$TribesNext::Community::Mail::Active = 0;
|
||||
$TribesNext::Community::Mail::ChunkSize = 25;
|
||||
|
||||
function CommunityMailInterface::onConnected(%this)
|
||||
{
|
||||
echo("Sending: " @ %this.data);
|
||||
%this.primed = 0;
|
||||
%this.send(%this.data);
|
||||
}
|
||||
|
||||
function CommunityMailInterface::onDisconnect(%this)
|
||||
{
|
||||
$TribesNext::Community::Mail::Active = 0;
|
||||
tn_community_mail_executeNextRequest();
|
||||
}
|
||||
|
||||
function CommunityMailInterface::onLine(%this, %line)
|
||||
{
|
||||
if (trim(%line) $= "")
|
||||
{
|
||||
%this.primed = 1;
|
||||
return;
|
||||
}
|
||||
if (!%this.primed)
|
||||
return;
|
||||
|
||||
warn("mail: " @ %line);
|
||||
%message = getField(%line, 0);
|
||||
switch$ (%message)
|
||||
{
|
||||
// display errors to the user -- some of these should never actually happen
|
||||
case "ERR":
|
||||
if (getField(%line, 1) $= "MAIL")
|
||||
{
|
||||
%type = getField(%line, 2);
|
||||
switch$ (%type)
|
||||
{
|
||||
case "INVALID_RECIP":
|
||||
%message = "Invalid recipient in mail send request.";
|
||||
case "INVALID_SBJ":
|
||||
%message = "Blank or invalid subject in mail send request.";
|
||||
case "INVALID_BODY":
|
||||
%message = "Blank or invalid body in message send request.";
|
||||
case "UNAUTHENTICATED":
|
||||
%message = "Session authentication error in mail request.";
|
||||
case "NO_METHOD":
|
||||
%message = "Internal error: no mail method specified in request.";
|
||||
case "UNKNOWN_METHOD":
|
||||
%message = "Internal error: unknown mail method specified in request.";
|
||||
case "READ":
|
||||
%message = "Access denied on message ID #" @ getField(%line, 3) @ ".";
|
||||
default:
|
||||
%message = "Unknown error in mail system: " @ %line;
|
||||
}
|
||||
schedule(500, 0, MessageBoxOK, "ERROR", %message);
|
||||
}
|
||||
// success is sent when a message is sent out
|
||||
case "SUCCESS":
|
||||
schedule(500, 0, MessageBoxOK, "SENT", "Your message has been sent.");
|
||||
|
||||
// the rest of these should be handled and accepted quietly to populate the various data objects
|
||||
|
||||
// message format sent as part of a box search
|
||||
case "MSG":
|
||||
%msg = tn_community_mail_getMessageObject(getField(%line, 1));
|
||||
%msg.box = getField(%line, 2);
|
||||
%msg.read = getField(%line, 3);
|
||||
%msg.type = getField(%line, 4);
|
||||
%msg.time = getField(%line, 5);
|
||||
|
||||
%box = tn_community_mail_getMailboxObject(%msg.box);
|
||||
if (!%box.isMember(%msg))
|
||||
{
|
||||
if (%box.newest < %msg.id)
|
||||
%box.newest = %msg.id;
|
||||
%box.add(%msg);
|
||||
}
|
||||
|
||||
// check if we're getting new messages
|
||||
if (%box.gettingNew)
|
||||
{
|
||||
%since = %box.since;
|
||||
if (%msg.id <= %since)
|
||||
{
|
||||
// found the desired message
|
||||
%box.gettingNew = 0;
|
||||
%box.since = %box.newest;
|
||||
}
|
||||
else
|
||||
{
|
||||
// not yet found desired message, try the next chunk
|
||||
|
||||
// first make sure that the chunk exists and we're not at the end of the mailbox
|
||||
%box.chunk = %box.chunk + 1;
|
||||
if ($TMail::MessageBoxCount[%box.name] > (%box.chunk * $TribesNext::Community::Mail::ChunkSize))
|
||||
tn_community_mail_request_boxList(%box.chunk * $TribesNext::Community::Mail::ChunkSize, (%box.chunk + 1) * $TribesNext::Community::Mail::ChunkSize, %box.name, %since);
|
||||
else
|
||||
{
|
||||
%box.since = %box.newest;
|
||||
}
|
||||
}
|
||||
}
|
||||
// message format sent as part of a message view
|
||||
case "MSG2":
|
||||
%msg = tn_community_mail_getMessageObject(getField(%line, 1));
|
||||
%msg.deleted = getField(%line, 2);
|
||||
%msg.type = getField(%line, 3);
|
||||
%msg.time = getField(%line, 4);
|
||||
%msg.read = "true";
|
||||
// message subject
|
||||
case "SBJ":
|
||||
tn_community_mail_getMessageObject(getField(%line, 1)).subject = getField(%line, 2);
|
||||
// sender of a message
|
||||
case "SNDR":
|
||||
tn_community_mail_getMessageObject(getField(%line, 1)).sender = tn_community_util_extractPlayer(%line, 2);
|
||||
// body of a message
|
||||
case "BDY":
|
||||
tn_community_mail_getMessageObject(getField(%line, 1)).body = collapseEscape(getField(%line, 2));
|
||||
// "to" recipient of a message
|
||||
case "TO":
|
||||
%msg = tn_community_mail_getMessageObject(getField(%line, 1));
|
||||
%index = getField(%line, 2);
|
||||
%msg.to[%index] = tn_community_util_extractPlayer(%line, 3);
|
||||
if (%msg.toMax < %index)
|
||||
%msg.toMax = %index;
|
||||
// "cc" recipient of a message
|
||||
case "CC":
|
||||
%msg = tn_community_mail_getMessageObject(getField(%line, 1));
|
||||
%index = getField(%line, 2);
|
||||
%msg.cc[%index] = tn_community_util_extractPlayer(%line, 3);
|
||||
if (%msg.ccMax < %index)
|
||||
%msg.ccMax = %index;
|
||||
// entries of a buddy or ignore list
|
||||
case "LIST":
|
||||
$TMail::ListVals[getField(%line, 1), getField(%line, 2)] = tn_community_util_extractPlayer(%line, 3);
|
||||
if ($TMail::ListMax[getField(%line, 1)] < getField(%line, 2))
|
||||
$TMail::ListMax[getField(%line, 1)] = getField(%line, 2);
|
||||
// search results for player name queries
|
||||
case "SEARCH":
|
||||
$TMail::SearchVals[getField(%line, 2)] = tn_community_util_extractPlayer(%line, 3);
|
||||
if ($TMail::SearchMax < getField(%line, 2))
|
||||
$TMail::SearchMax = getField(%line, 2);
|
||||
// unread message count for a box
|
||||
case "COUNT_U":
|
||||
$TMail::MessageBoxUnread[getField(%line, 1)] = getField(%line, 2);
|
||||
// message count for a box
|
||||
case "COUNT_A":
|
||||
$TMail::MessageBoxCount[getField(%line, 1)] = getField(%line, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// extract four fields from a string that correspond to a player
|
||||
function tn_community_util_extractPlayer(%string, %fInit)
|
||||
{
|
||||
return getField(%string, %fInit) @ "\t" @ getField(%string, %fInit + 1) @ "\t" @ getField(%string, %fInit + 2) @ "\t" @ getField(%string, %fInit + 3);
|
||||
}
|
||||
|
||||
function tn_community_mail_getMessageObject(%id)
|
||||
{
|
||||
if (isObject($TMail::MessageTable[%id]))
|
||||
return $TMail::MessageTable[%id];
|
||||
|
||||
%obj = new SimObject()
|
||||
{
|
||||
class = TMailMessage;
|
||||
id = %id;
|
||||
};
|
||||
$TMail::MessageTable[%id] = %obj;
|
||||
|
||||
$TMailMessageSet.add(%obj);
|
||||
return %obj;
|
||||
}
|
||||
|
||||
function tn_community_mail_getMailboxObject(%name)
|
||||
{
|
||||
if (isObject($TMail::MailboxTable[%name]))
|
||||
return $TMail::MailboxTable[%name];
|
||||
|
||||
%obj = new SimSet()
|
||||
{
|
||||
class = TMailBox;
|
||||
name = %name;
|
||||
since = 0;
|
||||
};
|
||||
$TMail::MailboxTable[%name] = %obj;
|
||||
return %obj;
|
||||
}
|
||||
|
||||
function tn_community_mail_initMessageSet()
|
||||
{
|
||||
if (isObject($TMailMessageSet))
|
||||
{
|
||||
while ($TMailMessageSet.getCount() > 0)
|
||||
$TMailMessageSet.getObject(0).delete();
|
||||
$TMailMessageSet.delete();
|
||||
}
|
||||
$TMailMessageSet = new SimSet("TMailMessageSet");
|
||||
}
|
||||
tn_community_mail_initMessageSet();
|
||||
|
||||
function tn_community_mail_initQueue()
|
||||
{
|
||||
if (isObject($TMailRequestQueue))
|
||||
$TMailRequestQueue.delete();
|
||||
$TMailRequestQueue = new MessageVector();
|
||||
}
|
||||
tn_community_mail_initQueue();
|
||||
|
||||
function tn_community_mail_processRequest(%request, %payload)
|
||||
{
|
||||
if (%request !$= "")
|
||||
{
|
||||
%request = "?guid=" @ getField($LoginCertificate, 1) @ "&uuid=" @ $TribesNext::Community::UUID @ "&" @ %request;
|
||||
}
|
||||
if (%payload $= "")
|
||||
{
|
||||
%data = "GET " @ $TribesNext::Community::BaseURL @ $TribesNext::Community::MailScript @ %request;
|
||||
%data = %data @ " HTTP/1.1\r\nHost: " @ $TribesNext::Community::Host @ "\r\nUser-Agent: Tribes 2\r\nConnection: close\r\n\r\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
%data = "POST " @ $TribesNext::Community::BaseURL @ $TribesNext::Community::MailScript @ " HTTP/1.1\r\n";
|
||||
%data = %data @ "Host: " @ $TribesNext::Community::Host @ "\r\nUser-Agent: Tribes 2\r\nConnection: close\r\n";
|
||||
%data = %data @ %payload;
|
||||
}
|
||||
|
||||
$TMailRequestQueue.pushBackLine(%data);
|
||||
|
||||
if (!$TribesNext::Community::Mail::Active)
|
||||
tn_community_mail_executeNextRequest();
|
||||
}
|
||||
|
||||
function tn_community_mail_executeNextRequest()
|
||||
{
|
||||
if ($TMailRequestQueue.getNumLines() <= 0)
|
||||
return;
|
||||
|
||||
%data = $TMailRequestQueue.getLineText(0);
|
||||
$TMailRequestQueue.popFrontLine();
|
||||
|
||||
$TribesNext::Community::Mail::Active = 1;
|
||||
|
||||
if (isObject(CommunityMailInterface))
|
||||
{
|
||||
CommunityMailInterface.disconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
new TCPObject(CommunityMailInterface);
|
||||
}
|
||||
CommunityMailInterface.data = %data;
|
||||
CommunityMailInterface.connect($TribesNext::Community::Host @ ":" @ $TribesNext::Community::Port);
|
||||
}
|
||||
|
||||
// implementation of API requests
|
||||
|
||||
// this isn't strictly an API request -- this gets the latest messages since the last check
|
||||
function tn_community_mail_request_getNew(%box)
|
||||
{
|
||||
%obj = tn_community_mail_getMailboxObject(%box);
|
||||
tn_community_mail_request_count(%box, "all");
|
||||
%since = %obj.since;
|
||||
%obj.gettingNew = 1;
|
||||
%obj.chunk = 0;
|
||||
tn_community_mail_request_boxList(0, $TribesNext::Community::Mail::ChunkSize, %box, %since);
|
||||
}
|
||||
|
||||
function tn_community_mail_request_boxList(%first, %last, %box, %since)
|
||||
{
|
||||
tn_community_mail_processRequest("method=box&first=" @ %first @ "&last=" @ %last @ "&box=" @ %box @ "&since=" @ %since);
|
||||
}
|
||||
|
||||
function tn_community_mail_request_read(%messageId)
|
||||
{
|
||||
tn_community_mail_processRequest("method=read&id=" @ %messageId);
|
||||
}
|
||||
|
||||
function tn_community_mail_request_viewList(%list)
|
||||
{
|
||||
$TMail::ListMax[%list] = 0;
|
||||
deleteVariables("$TMail::ListVals" @ %list @ "*");
|
||||
tn_community_mail_processRequest("method=viewlist&list=" @ %list);
|
||||
}
|
||||
|
||||
function tn_community_mail_request_addListEntry(%list, %target)
|
||||
{
|
||||
tn_community_mail_processRequest("method=addlist&list=" @ %list @ "&target=" @ %target);
|
||||
tn_community_mail_request_viewList(%list); // refresh the list
|
||||
}
|
||||
|
||||
function tn_community_mail_request_delListEntry(%list, %target)
|
||||
{
|
||||
tn_community_mail_processRequest("method=dellist&list=" @ %list @ "&target=" @ %target);
|
||||
tn_community_mail_request_viewList(%list); // refresh the list
|
||||
}
|
||||
|
||||
function tn_community_mail_request_deleteMessage(%messageId, %set)
|
||||
{
|
||||
%msg = tn_community_mail_getMessageObject(%messageId);
|
||||
if (%set $= "0")
|
||||
{
|
||||
%add = "&set=0";
|
||||
%msg.deleted = "false";
|
||||
}
|
||||
else
|
||||
{
|
||||
%add = "&set=1";
|
||||
%msg.deleted = "true";
|
||||
}
|
||||
tn_community_mail_processRequest("method=delete&id=" @ %messageId @ %add);
|
||||
tn_community_mail_request_read(%messageId); // refresh the message status
|
||||
|
||||
// move the message to the right box
|
||||
if (%set !$= "0")
|
||||
{
|
||||
// been deleted, make sure it's in the deleted set
|
||||
%box = tn_community_mail_getMailboxObject(%msg.box);
|
||||
%box.remove(%msg);
|
||||
tn_community_mail_getMailboxObject("deleted").add(%msg);
|
||||
%msg.box = "deleted";
|
||||
}
|
||||
else
|
||||
{
|
||||
// been undeleted? make sure it's not in the deleted set
|
||||
tn_community_mail_getMailboxObject("deleted").remove(%msg);
|
||||
if (getField(%msg.sender, 3) !$= getField($LoginCertificate, 1))
|
||||
%box = tn_community_mail_getMailboxObject("inbox");
|
||||
else
|
||||
%box = tn_community_mail_getMailboxObject("sentbox");
|
||||
%box.add(%msg);
|
||||
%msg.box = %box.name;
|
||||
}
|
||||
}
|
||||
|
||||
function tn_community_mail_request_count(%box, %mode)
|
||||
{
|
||||
tn_community_mail_processRequest("method=count&box=" @ %box @ "&mode=" @ %mode);
|
||||
}
|
||||
|
||||
function tn_community_mail_request_search(%query)
|
||||
{
|
||||
$TMail::SearchMax = 0;
|
||||
deleteVariables("$TMail::SearchVals*");
|
||||
tn_community_mail_processRequest("method=search&query=" @ %query);
|
||||
}
|
||||
|
||||
function tn_community_mail_request_send(%subject, %contents, %to, %cc)
|
||||
{
|
||||
// sending messages themselves is done with a POST,
|
||||
// since the contents can be longer than URI length limits
|
||||
%guid = getField($LoginCertificate, 1);
|
||||
%uuid = $TribesNext::Community::UUID;
|
||||
|
||||
%boundary = "-------------------------";
|
||||
%rand = getRandom(10000, 99999) @ getRandom(10000, 99999) @ getRandom(10, 9999);
|
||||
%formelem = "Content-Disposition: form-data; name=\"";
|
||||
|
||||
%payload = "--" @ %boundary @ %rand @ "\r\n";
|
||||
|
||||
// GUID element
|
||||
%payload = %payload @ %formelem @ "guid\"\r\n\r\n" @ %guid @ "\r\n";
|
||||
%payload = %payload @ "--" @ %boundary @ %rand @ "\r\n";
|
||||
|
||||
// UUID
|
||||
%payload = %payload @ %formelem @ "uuid\"\r\n\r\n" @ %uuid @ "\r\n";
|
||||
%payload = %payload @ "--" @ %boundary @ %rand @ "\r\n";
|
||||
|
||||
// method
|
||||
%payload = %payload @ %formelem @ "method\"\r\n\r\nsend\r\n";
|
||||
%payload = %payload @ "--" @ %boundary @ %rand @ "\r\n";
|
||||
|
||||
// subject
|
||||
%payload = %payload @ %formelem @ "subject\"\r\n\r\n" @ %subject @ "\r\n";
|
||||
%payload = %payload @ "--" @ %boundary @ %rand @ "\r\n";
|
||||
|
||||
// contents
|
||||
%payload = %payload @ %formelem @ "contents\"\r\n\r\n" @ %contents @ "\r\n";
|
||||
%payload = %payload @ "--" @ %boundary @ %rand @ "\r\n";
|
||||
|
||||
// to
|
||||
%payload = %payload @ %formelem @ "to\"\r\n\r\n" @ %to @ "\r\n";
|
||||
%payload = %payload @ "--" @ %boundary @ %rand @ "\r\n";
|
||||
|
||||
// cc
|
||||
%payload = %payload @ %formelem @ "cc\"\r\n\r\n" @ %cc @ "\r\n";
|
||||
%payload = %payload @ "--" @ %boundary @ %rand @ "\r\n";
|
||||
|
||||
%header = "Content-Type: multipart/form-data; boundary=" @ %boundary @ %rand @ "\r\n";
|
||||
%header = %header @ "Content-Length: " @ strlen(%payload) @ "\r\n\r\n";
|
||||
|
||||
tn_community_mail_processRequest("", %header @ %payload);
|
||||
}
|
||||
|
||||
function tn_community_isOnList(%searchguid, %list)
|
||||
{
|
||||
if ($TMail::ListMax[%list] $= "")
|
||||
return "";
|
||||
%count = $TMail::ListMax[%list];
|
||||
for (%i = 0; %i <= %count; %i++)
|
||||
{
|
||||
%player = $TMail::ListVals[%list, %i];
|
||||
%guid = getField(%player, 3);
|
||||
if (%guid == %searchguid)
|
||||
return %player;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function tn_community_isUserBuddy(%searchguid)
|
||||
{
|
||||
return tn_community_isOnList(%searchguid, "buddy");
|
||||
}
|
||||
|
||||
function tn_community_isUserBlocked(%searchguid)
|
||||
{
|
||||
return tn_community_isOnList(%searchguid, "ignore");
|
||||
}
|
||||
971
t2csri/community/mailUI.cs
Normal file
971
t2csri/community/mailUI.cs
Normal file
|
|
@ -0,0 +1,971 @@
|
|||
// TribesNext Project
|
||||
// http://www.tribesnext.com/
|
||||
// Copyright 2011-2013
|
||||
|
||||
// Tribes 2 Community System
|
||||
// Mail UI Coercion
|
||||
|
||||
// This script implements connectivity between the Dynamix mail UI shipped with Tribes 2 and the community
|
||||
// systems developed for TribesNext. The communication to the TribesNext systems via network is implemented
|
||||
// in the robot client data interface script for mail. This script merely connects (modified) Dynamix UI
|
||||
// elements to query/invoke methods on this new data interface, instead of the IRC server command used
|
||||
// initially.
|
||||
|
||||
// Several functional changes were made as part of this process. Firstly, all players are now keyed by
|
||||
// GUID instead of player name. Since player names are not immutable, it is foolish to use them in ways
|
||||
// that assume they are. As a result of this change, the To/CC fields in the mail composition system do
|
||||
// not accept input directly from a typing user. If there was a reasonable capability to implement auto
|
||||
// completion, it may still have been possible to use this UI element. Instead, users will need to press
|
||||
// the associated To/CC buttons to invoke the address book. From here, they can perform search by name,
|
||||
// see their buddy list, see fellow members of their clans, and add players to the message (or buddy list).
|
||||
|
||||
// Second, deleted messages can now be undeleted for a duration. Messages marked as deleted are swept by the
|
||||
// remote community system only at an interval. Users can undelete messages until this deletion process is
|
||||
// run on the server. Users should not rely on the continued availability of deleted messages, since this
|
||||
// sweep process can be run at any time.
|
||||
|
||||
// Thirdly, there were some hidden user interface elements that were intended for additional functionality,
|
||||
// but apparently were never started by Dynamix. This includes a "Sent Item" view, which consists of messages
|
||||
// sent by this user to other players. This has now been implemented, and connected to the previously hidden
|
||||
// user interface widgets that were present before.
|
||||
|
||||
// Finally, the original Dynamix code would store mail messages in a local "webcache" file. Since the data
|
||||
// used to populate these UIs now comes from another script data source, whose format completely differs from
|
||||
// the original, and since the data format parsing facilities of the game are incredibly primitive, there is
|
||||
// no longer any local file cache of mail messages.
|
||||
|
||||
// TODO add scroll handler to load more messages
|
||||
|
||||
$TribesNext::Community::MailUI::ActiveMailbox = "inbox";
|
||||
$TribesNext::Community::MailUI::ActiveRow = -1;
|
||||
$TribesNext::Community::MailUI::Awake = 0;
|
||||
|
||||
// avoid sending garbage to the IRC server, just in case code is missed in the UI
|
||||
function DatabaseQuery(%a0, %a1, %a2, %a3)
|
||||
{
|
||||
error("Uncaught DatabaseQuery(" @ %a0 @ ", " @ %a1 @ ", " @ %a2 @ ", " @ %a3 @ ")");
|
||||
}
|
||||
|
||||
function DatabaseQueryArray(%a0, %a1, %a2, %a3)
|
||||
{
|
||||
error("Uncaught DatabaseQueryArray(" @ %a0 @ ", " @ %a1 @ ", " @ %a2 @ ", " @ %a3 @ ")");
|
||||
}
|
||||
|
||||
function DatabaseQueryCancel(%a0, %a1, %a2, %a3)
|
||||
{
|
||||
error("Uncaught DatabaseQueryCancel(" @ %a0 @ ", " @ %a1 @ ", " @ %a2 @ ", " @ %a3 @ ")");
|
||||
}
|
||||
|
||||
function DatabaseQueryi(%a0, %a1, %a2, %a3)
|
||||
{
|
||||
error("Uncaught DatabaseQueryi(" @ %a0 @ ", " @ %a1 @ ", " @ %a2 @ ", " @ %a3 @ ")");
|
||||
}
|
||||
|
||||
// this function makes some (minor) changes to the Dynamix UI structure
|
||||
function tn_community_mailui_modifyGUIData()
|
||||
{
|
||||
// this UI element is present in the original UI, but it looks like it was never used
|
||||
rbSendItems.command = "EMailGui.ButtonClick(2);";
|
||||
rbSendItems.setVisible(1);
|
||||
|
||||
// expand the delete button so that the text "UNDELETE" can be set comfortably
|
||||
EM_DeleteBtn.extent = "78 35";
|
||||
%shift = 13;
|
||||
EM_BlockEditBtn.position = (getWord(EM_DeleteBtn.position, 0) + 49 + %shift) SPC 42;
|
||||
EM_BlockBtn.position = (getWord(EM_DeleteBtn.position, 0) + 123 + %shift) SPC 42;
|
||||
|
||||
// compose window settings changes
|
||||
EMail_Subject.maxLength = 250;
|
||||
|
||||
// compose window -- making these effectively read only, the backing store must change
|
||||
EMail_ToEdit.maxLength = 0;
|
||||
EMail_ToEdit.validate = "tn_community_mailui_recipientValidate();";
|
||||
EMail_CcEdit.maxLength = 0;
|
||||
EMail_CcEdit.validate = "tn_community_mailui_recipientValidate();";
|
||||
|
||||
// block list window settings, remove the unused UI element indicating number of blocks
|
||||
%panel = EmailBlockDlg.getObject(0);
|
||||
if (%panel.getCount() == 5)
|
||||
%panel.getObject(4).setText("");
|
||||
|
||||
}
|
||||
tn_community_mailui_modifyGUIData();
|
||||
|
||||
// convert from data interface mail structure to the string expected by the Dynamix UI
|
||||
function tn_community_mailui_convertMessage(%msg)
|
||||
{
|
||||
%out = %msg.id @ "\n"; // id
|
||||
%out = %out @ %msg.sender @ "\n"; // from
|
||||
%out = %out @ (%msg.read $= "true") @ "\n"; // read flag
|
||||
%out = %out @ tn_community_mailui_epochToDate(%msg.time) @ "\n"; // send date
|
||||
|
||||
if (%msg.body !$= "") // mail is loaded
|
||||
{
|
||||
// to, always at least one
|
||||
%count = %msg.toMax;
|
||||
for (%i = 0; %i <= %count; %i++)
|
||||
%to = %to @ "\t" @ %msg.to[%i];
|
||||
%to = getSubStr(%to, 1, strlen(%to));
|
||||
if (%msg.ccMax !$= "") // CC, if exists
|
||||
{
|
||||
%count = %msg.ccMax;
|
||||
for (%i = 0; %i <= %count; %i++)
|
||||
%cc = %cc @ "\t" @ %msg.cc[%i];
|
||||
%cc = getSubStr(%cc, 1, strlen(%cc));
|
||||
}
|
||||
%out = %out @ %to @ "\n" @ %cc @ "\n";
|
||||
%out = %out @ %msg.subject @ "\n";
|
||||
%out = %out @ %msg.body;
|
||||
}
|
||||
else
|
||||
{
|
||||
// don't have this message actually downloaded
|
||||
%out = %out @ "\n\n";
|
||||
%out = %out @ %msg.subject @ "\n";
|
||||
%out = %out @ "<font:Sui Generis:14><color:00cc00>Loading message. Please wait...";
|
||||
}
|
||||
}
|
||||
|
||||
// produces a list of recipients suitable for UI display
|
||||
function tn_community_mailui_recipientShowList(%list, %color)
|
||||
{
|
||||
%entries = getFieldCount(%list);
|
||||
%showList = "";
|
||||
for (%i = 0; %i < %entries; %i += 4)
|
||||
{
|
||||
%name = getField(%list, %i);
|
||||
%tag = getField(%list, %i + 1);
|
||||
%append = getField(%list, %i + 2);
|
||||
%guid = getField(%list, %i + 3);
|
||||
|
||||
if (%color)
|
||||
{
|
||||
%name = "\c0" @ %name @ "\c3";
|
||||
%tag = "\c2" @ %tag @ "\c3";
|
||||
}
|
||||
|
||||
%showName = (%append ? (%name @ %tag) : (%tag @ %name));
|
||||
%showList = %showList @ ", " @ %showName;
|
||||
}
|
||||
return getSubStr(%showList, 2, strlen(%showList));
|
||||
}
|
||||
|
||||
function tn_community_mailui_recipientValidate()
|
||||
{
|
||||
Email_ToEdit.setText(tn_community_mailui_recipientShowList(Email_ToEdit.backing, 1));
|
||||
Email_CCEdit.setText(tn_community_mailui_recipientShowList(Email_CCEdit.backing, 1));
|
||||
}
|
||||
|
||||
function tn_community_mailui_epochToDate(%epoch)
|
||||
{
|
||||
// uses ruby, since T2 does not expose local system timezone, nor can T2 handle epoch times
|
||||
|
||||
// verify %epoch uses only numbers for security reasons
|
||||
%len = strlen(%epoch);
|
||||
for (%i = 0; %i < %len; %i++)
|
||||
{
|
||||
%char = strcmp(getSubStr(%epoch, %i, 1), "");
|
||||
if (%char > 0x39 || %char < 0x30)
|
||||
{
|
||||
%epoch = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check memoization table, since ruby computations can be on the expensive side
|
||||
%mem = $EpochMem[%epoch];
|
||||
if (%mem !$= "")
|
||||
return %mem;
|
||||
|
||||
// pass it to ruby
|
||||
$temp = "PROCESSING ERROR";
|
||||
rubyEval("tsEval '$temp=\"' + Time.at(" @ %epoch @ ").strftime('%Y-%m-%d %H:%M:%S') + '\";'");
|
||||
|
||||
// memoize then return
|
||||
$EpochMem[%epoch] = $temp;
|
||||
return $temp;
|
||||
}
|
||||
|
||||
function tn_community_mailui_clearCheckStatus()
|
||||
{
|
||||
if (isEventPending($TribesNext::Community::MailUi::StatusSchedule))
|
||||
cancel($TribesNext::Community::MailUi::StatusSchedule);
|
||||
|
||||
if ($TribesNext::Community::Mail::Active)
|
||||
{
|
||||
$TribesNext::Community::MailUi::StatusSchedule = schedule(32, 0, tn_community_mailui_clearCheckStatus);
|
||||
return;
|
||||
}
|
||||
EmailGui.checkingEmail = 0;
|
||||
|
||||
$TribesNext::Community::MailUI::ActiveRow = EM_Browser.getSelectedId();
|
||||
tn_community_mailui_displayBox($TribesNext::Community::MailUI::ActiveMailbox);
|
||||
|
||||
if (EmailBlockDlg.gettingList)
|
||||
{
|
||||
tn_community_mailui_displayBlockList();
|
||||
EmailBlockDlg.gettingList = 0;
|
||||
}
|
||||
if (AddressDlg.searchActive)
|
||||
{
|
||||
tn_community_mailui_displaySearchResults();
|
||||
AddressDlg.searchActive = 0;
|
||||
}
|
||||
error("Mail UI update.");
|
||||
}
|
||||
|
||||
function tn_community_mailui_displayBox(%mailbox)
|
||||
{
|
||||
EmailMessageVector.clear();
|
||||
EM_Browser.clearList();
|
||||
$EmailNextSeq = 0;
|
||||
EMailInboxBodyText.setText("");
|
||||
|
||||
if (%mailbox $= "deleted") // set delete button to undelete mode in deleted items
|
||||
EM_DeleteBtn.text = "UNDELETE";
|
||||
else
|
||||
EM_DeleteBtn.text = "DELETE";
|
||||
|
||||
%box = tn_community_mail_getMailboxObject(%mailbox);
|
||||
%count = %box.getCount();
|
||||
for (%i = 0; %i < %count; %i++)
|
||||
{
|
||||
%msg = %box.getObject(%i);
|
||||
%id = %msg.id;
|
||||
EmailNewMessageArrived(tn_community_mailui_convertMessage(%msg), %id);
|
||||
}
|
||||
EM_Browser.selectRowByID($TribesNext::Community::MailUI::ActiveRow);
|
||||
|
||||
EM_Browser.sort();
|
||||
}
|
||||
|
||||
function tn_community_mailui_loadSelected()
|
||||
{
|
||||
if (isEventPending($TribesNext::Community::MailUi::SelectLoadSchedule))
|
||||
cancel($TribesNext::Community::MailUi::SelectLoadSchedule);
|
||||
|
||||
if ($TribesNext::Community::MailUI::Awake)
|
||||
{
|
||||
%id = EM_Browser.getSelectedId();
|
||||
if (%id != -1)
|
||||
{
|
||||
%msg = tn_community_mail_getMessageObject(%id);
|
||||
if (%msg.body $= "" && !%msg.uiRequested)
|
||||
{
|
||||
%msg.uiRequested = 1;
|
||||
tn_community_mail_request_read(%id);
|
||||
tn_community_mailui_clearCheckStatus();
|
||||
}
|
||||
}
|
||||
$TribesNext::Community::MailUi::SelectLoadSchedule = schedule(250, 0, tn_community_mailui_loadSelected);
|
||||
}
|
||||
}
|
||||
|
||||
// replacing function in webbrowser.cs, 571
|
||||
function LinkEMail(%MailTo)
|
||||
{
|
||||
%count = getRecordCount(%MailTo);
|
||||
%recipients = "";
|
||||
for (%i = 0; %i < %count; %i++)
|
||||
{
|
||||
%record = getRecord(%MailTo, %i);
|
||||
%guid = getField(%record, 0);
|
||||
%name = getField(%record, 1);
|
||||
%tag = getField(%record, 2);
|
||||
%append = getField(%record, 3);
|
||||
|
||||
%player = tn_community_browser_getPlayerProfile(%guid);
|
||||
if (%name $= "")
|
||||
%name = %player.name;
|
||||
if (%tag $= "")
|
||||
{
|
||||
%tag = getField(%player.tag, 0);
|
||||
%append = getField(%player.tag, 1);
|
||||
}
|
||||
if (%append $= "")
|
||||
%append = 0;
|
||||
%recipient = %name TAB %tag TAB %append TAB %guid @ "\t";
|
||||
%recipients = %recipients @ %recipient;
|
||||
}
|
||||
%recipients = trim(%recipients);
|
||||
Email_ToEdit.backing = %recipients;
|
||||
Email_ToEdit.setText(tn_community_mailui_recipientShowList(Email_ToEdit.backing, 1));
|
||||
|
||||
//Email_ToEdit.setText(%MailTo);
|
||||
Email_CCEdit.setText("");
|
||||
$EmailSubject = "";
|
||||
Canvas.pushDialog(EmailComposeDlg);
|
||||
EmailBodyText.setValue("");
|
||||
Email_Subject.makeFirstResponder(1);
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 369
|
||||
function CheckEmail(%schedule)
|
||||
{
|
||||
if ($TribesNext::Community::UUID $= "") // session not established
|
||||
return;
|
||||
if (EmailGui.checkingEmail)
|
||||
return;
|
||||
|
||||
if (isEventPending(EmailGui.checkSchedule) && !%scheduled)
|
||||
cancel(EmailGui.checkSchedule);
|
||||
|
||||
EmailGui.checkSchedule = "";
|
||||
EMailGui.key = LaunchGui.key++;
|
||||
EmailGui.state = "getMail";
|
||||
EmailGui.checkingEmail = true;
|
||||
|
||||
// new code
|
||||
if (EMailGui.initialDownloaded)
|
||||
{
|
||||
// initial messages have already been downloaded -- get updates
|
||||
tn_community_mail_request_getNew("inbox");
|
||||
tn_community_mail_request_getNew("sentbox");
|
||||
tn_community_mail_request_getNew("deleted");
|
||||
}
|
||||
else
|
||||
{
|
||||
// no initial messages downloaded yet -- get first chunk
|
||||
// this will limit messages to the first chunk in each mailbox
|
||||
// and further messages can be downloaded as the user scrolls to the bottom
|
||||
EMailGui.initialDownloaded = 1;
|
||||
tn_community_mail_request_boxList(0, $TribesNext::Community::Mail::ChunkSize, "inbox", 0);
|
||||
tn_community_mail_request_boxList(0, $TribesNext::Community::Mail::ChunkSize, "sentbox", 0);
|
||||
tn_community_mail_request_boxList(0, $TribesNext::Community::Mail::ChunkSize, "deleted", 0);
|
||||
}
|
||||
tn_community_mailui_clearCheckStatus();
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 1319
|
||||
function EM_Browser::onSelect(%this, %id)
|
||||
{
|
||||
tn_community_mailui_loadSelected();
|
||||
|
||||
%text = EmailMessageVector.getLineTextByTag(%id);
|
||||
if (rbinbox.getValue())
|
||||
{
|
||||
if(!getRecord(%text, 2)) // read flag
|
||||
{
|
||||
%line = EmailMessageVector.getLineIndexByTag(%id);
|
||||
%text = setRecord(%text, 2, 1);
|
||||
|
||||
// Update the GUI:
|
||||
%this.setRowFlags( %id, 1 );
|
||||
EmailMessageVector.deleteLine(%line);
|
||||
EmailMessageVector.insertLine(%line, %text, %id);
|
||||
}
|
||||
}
|
||||
EmailInboxBodyText.setValue(EmailGetTextDisplay(%text));
|
||||
EM_ReplyBtn.setActive( true );
|
||||
EM_ReplyToAllBtn.setActive( true );
|
||||
EM_ForwardBtn.setActive( true );
|
||||
EM_DeleteBtn.setActive( true );
|
||||
EM_BlockBtn.setActive( true );
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 297
|
||||
function EmailGetTextDisplay(%text)
|
||||
{
|
||||
// get ID to check some additional properties
|
||||
%id = getRecord(%text, 0);
|
||||
%msg = tn_community_mail_getMessageObject(%id);
|
||||
if (%msg.deleted $= "true")
|
||||
%prepend = "<spush><font:Sui Generis:14><color:cc0000>Message has been deleted and will be removed from the mail system soon.<spop>\n";
|
||||
|
||||
%toList = getRecord(%text, 4);
|
||||
%to = getLinkNameList(%toList);
|
||||
%ccList = getRecord(%text, 5);
|
||||
%ccLine = getLinkNameList(%ccList);
|
||||
|
||||
%from = getLinkName(getRecord(%text, 1), 0);
|
||||
%msgtext = "From: " @ %from NL
|
||||
"To: " @ %to NL
|
||||
"CC: " @ %ccLine NL
|
||||
"Subject: " @ getRecord(%text, 6) NL
|
||||
"Date Sent: " @ getRecord(%text, 3) @ "\n\n" @
|
||||
EmailGetBody(%text);
|
||||
|
||||
return %prepend @ %msgtext;
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 401
|
||||
function EmailEditBlocks()
|
||||
{
|
||||
// this function is called when bringing up the block list editor
|
||||
// -- initially this initiated a database query that would populate the UI
|
||||
// as it was recieved. instead, this will be dealt with during the rest of the UI update
|
||||
Canvas.pushDialog(EmailBlockDlg);
|
||||
EmailBlockList.clear();
|
||||
EMailBlockDlg.key = LaunchGui.key++;
|
||||
EmailBlockDlg.state = "getBlocklist";
|
||||
|
||||
EmailBlockDlg.gettingList = 1;
|
||||
|
||||
tn_community_mail_request_viewList("ignore");
|
||||
tn_community_mailui_clearCheckStatus();
|
||||
}
|
||||
|
||||
function tn_community_mailui_displayBlockList()
|
||||
{
|
||||
// update the block list UI from the data interface cache
|
||||
if ($TMail::ListMax["ignore"] $= "")
|
||||
return;
|
||||
%count = $TMail::ListMax["ignore"];
|
||||
for (%i = 0; %i <= %count; %i++)
|
||||
{
|
||||
%player = $TMail::ListVals["ignore", %i];
|
||||
|
||||
%name = getField(%player, 0);
|
||||
%tag = getField(%player, 1);
|
||||
%append = getField(%player, 2);
|
||||
%guid = getField(%player, 3);
|
||||
|
||||
%showName = (%append ? (%name @ %tag) : (%tag @ %name));
|
||||
EmailBlockList.addRow(%guid, %showName);
|
||||
}
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 410
|
||||
function EmailBlockSender()
|
||||
{
|
||||
%id = EM_Browser.getSelectedId();
|
||||
if ( %id == -1 )
|
||||
{
|
||||
MessageBoxOK("WARNING","You cannot block a non-existent sender.");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
%text = EmailMessageVector.getLineTextByTag(EM_Browser.getSelectedId());
|
||||
%blockUser = getRecord(%text, 1);
|
||||
|
||||
%name = getField(%blockUser, 0);
|
||||
%tag = getField(%blockUser, 1);
|
||||
%append = getField(%blockUser, 2);
|
||||
%guid = getField(%blockUser, 3);
|
||||
|
||||
%showName = (%append ? (%name @ %tag) : (%tag @ %name));
|
||||
MessageBoxYesNo("CONFIRM BLOCK","Are you sure you want to block " @ %showName @ "?","tn_community_mail_request_addListEntry(\"ignore\", " @ %guid @ ");");
|
||||
}
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 431
|
||||
function EmailBlockRemove()
|
||||
{
|
||||
%rowId = EmailBlockList.getSelectedId();
|
||||
if(%rowId == -1)
|
||||
{
|
||||
MessageBoxOK("WARNING","You cannot remove a non-existent block.");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
%line = EmailBlockList.getRowTextById(%rowId);
|
||||
%name = getField(%line, 2);
|
||||
EMailBlockDlg.state = "removeBlock";
|
||||
EMailBlockDlg.key = LaunchGui.key++;
|
||||
EmailBlockList.removeRowById(%rowId);
|
||||
|
||||
tn_community_mail_request_delListEntry("ignore", %rowId);
|
||||
}
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 43
|
||||
function EmailMessageNew()
|
||||
{
|
||||
Email_ToEdit.backing = "";
|
||||
Email_ToEdit.setText("");
|
||||
Email_CCEdit.backing = "";
|
||||
Email_CCEdit.setText("");
|
||||
$EmailSubject = "";
|
||||
EmailBodyText.setValue("");
|
||||
|
||||
EMailComposeDlg.state = "sendMail";
|
||||
Canvas.pushDialog(EmailComposeDlg);
|
||||
Email_ToEdit.makeFirstResponder(1);
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 55
|
||||
function EmailMessageReply()
|
||||
{
|
||||
EMailComposeDlg.state = "replyMail";
|
||||
%text = EmailMessageVector.getLineTextByTag( EM_Browser.getSelectedId() );
|
||||
Email_ToEdit.backing = getRecord(%text, 1);
|
||||
Email_ToEdit.setText(tn_community_mailui_recipientShowList(Email_ToEdit.backing, 1));
|
||||
Email_CCEdit.backing = "";
|
||||
Email_CCEdit.setText("");
|
||||
$EmailSubject = "RE: " @ getRecord(%text, 6);
|
||||
%date = getRecord(%text, 3);
|
||||
Canvas.pushDialog(EmailComposeDlg);
|
||||
|
||||
%player = Email_ToEdit.getValue();
|
||||
%name = getField(%player, 0);
|
||||
%tag = getField(%player, 1);
|
||||
%append = getField(%player, 2);
|
||||
%guid = getField(%player, 3);
|
||||
|
||||
%showName = (%append ? (%name @ %tag) : (%tag @ %name));
|
||||
|
||||
EmailBodyText.setValue("\n\n----------------------------------\n On " @ %date SPC %showName @ " wrote:\n\n" @ EmailGetBody(%text) );
|
||||
EmailBodyText.SetCursorPosition(0);
|
||||
EmailBodyText.makeFirstResponder(1);
|
||||
}
|
||||
|
||||
function EmailMessageForward()
|
||||
{
|
||||
%text = EmailMessageVector.getLineTextByTag( EM_Browser.getSelectedId() );
|
||||
Email_ToEdit.backing = "";
|
||||
Email_ToEdit.setText("");
|
||||
Email_CCEdit.backing = "";
|
||||
Email_CCEdit.setText("");
|
||||
$EmailSubject = "FW: " @ getRecord(%text, 6);
|
||||
Canvas.pushDialog(EmailComposeDlg);
|
||||
EmailBodyText.setValue("\n\n\n--- Begin Forwarded Message ---\n\n" @ EmailGetTextDisplay(%text));
|
||||
Email_toEdit.makeFirstResponder(1);
|
||||
EmailBodyText.SetCursorPosition(0);
|
||||
EMailComposeDlg.state = "forwardMail";
|
||||
}
|
||||
// replacing function in webemail.cs, 82
|
||||
function EmailMessageReplyAll()
|
||||
{
|
||||
EMailComposeDlg.state = "replyAll";
|
||||
%text = EmailMessageVector.getLineTextByTag( EM_Browser.getSelectedId() );
|
||||
Email_ToEdit.backing = getRecord(%text, 1);
|
||||
Email_ToEdit.setText(tn_community_mailui_recipientShowList(Email_ToEdit.backing, 1));
|
||||
Email_CCEdit.backing = getRecord(%text, 4) @ "\t" @ getRecord(%text,5);
|
||||
Email_CCEdit.setText(tn_community_mailui_recipientShowList(Email_CCEdit.backing, 1));
|
||||
$EmailSubject = "RE: " @ getRecord(%text, 6);
|
||||
%date = getRecord(%text, 3);
|
||||
Canvas.pushDialog(EmailComposeDlg);
|
||||
|
||||
%player = Email_ToEdit.getValue();
|
||||
%name = getField(%player, 0);
|
||||
%tag = getField(%player, 1);
|
||||
%append = getField(%player, 2);
|
||||
%guid = getField(%player, 3);
|
||||
|
||||
%showName = (%append ? (%name @ %tag) : (%tag @ %name));
|
||||
|
||||
EmailBodyText.setValue("\n\n===========================\n On " @ %date SPC %showName @ " wrote:\n\n" @ EmailGetBody(%text) );
|
||||
EmailBodyText.makeFirstResponder(1);
|
||||
EmailBodyText.SetCursorPosition(0);
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 145
|
||||
function EmailSend()
|
||||
{
|
||||
EMailComposeDlg.key = LaunchGui.key++;
|
||||
EMailComposeDlg.state = "sendMail";
|
||||
|
||||
%to = tn_community_mailui_extractRecipientIds(Email_ToEdit.backing);
|
||||
%cc = tn_community_mailui_extractRecipientIds(Email_CCEdit.backing);
|
||||
%subj = $EmailSubject;
|
||||
%text = EMailBodyText.getValue();
|
||||
|
||||
tn_community_mail_request_send(%subj, %text, %to, %cc);
|
||||
Canvas.popDialog(EmailComposeDlg);
|
||||
|
||||
// run checkemail to update the list of messages in the mailboxes
|
||||
CheckEmail();
|
||||
}
|
||||
|
||||
function tn_community_mailui_extractRecipientIds(%recipients)
|
||||
{
|
||||
%entries = getFieldCount(%recipients);
|
||||
%guidList = "";
|
||||
for (%i = 0; %i < %entries; %i += 4)
|
||||
{
|
||||
%name = getField(%recipients, %i);
|
||||
%tag = getField(%recipients, %i + 1);
|
||||
%append = getField(%recipients, %i + 2);
|
||||
%guid = getField(%recipients, %i + 3);
|
||||
|
||||
%guidList = %guidList @ "," @ %guid;
|
||||
}
|
||||
return getSubStr(%guidList, 1, strlen(%guidList));
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 96
|
||||
function EmailMessageDelete()
|
||||
{
|
||||
%id = EM_Browser.getSelectedId();
|
||||
if ( %id == -1 )
|
||||
return;
|
||||
|
||||
EMailComposeDlg.key = LaunchGui.key++;
|
||||
|
||||
// Make these buttons inactive until another message is selected:
|
||||
%state = 1;
|
||||
if ($TribesNext::Community::MailUI::ActiveMailbox $= "deleted")
|
||||
%state = 0;
|
||||
DoEmailDelete(%id, %state);
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 121
|
||||
function DoEmailDelete(%mid, %state)
|
||||
{
|
||||
%row = EM_Browser.findById(%mid);
|
||||
|
||||
EM_ReplyBtn.setActive( false );
|
||||
EM_ReplyToAllBtn.setActive( false );
|
||||
EM_ForwardBtn.setActive( false );
|
||||
EM_DeleteBtn.setActive( false );
|
||||
EM_BlockBtn.setActive( false );
|
||||
|
||||
EM_Browser.removeRowByIndex(%row);
|
||||
EmailMessageVector.deleteLine(EmailMessageVector.getLineIndexByTag(%mid));
|
||||
|
||||
if ( EM_Browser.rowCount() == 0 )
|
||||
EMailInboxBodyText.setText("");
|
||||
else
|
||||
EM_Browser.setSelectedRow(%row);
|
||||
|
||||
tn_community_mail_request_deleteMessage(%mid, %state);
|
||||
tn_community_mailui_clearCheckStatus();
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 1017
|
||||
function EmailGui::ButtonClick(%this,%ord)
|
||||
{
|
||||
switch(%ord)
|
||||
{
|
||||
case 0: // wired to inbox button
|
||||
$TribesNext::Community::MailUI::ActiveMailbox = "inbox";
|
||||
tn_community_mailui_clearCheckStatus();
|
||||
case 1: // wired to deleted items button
|
||||
$TribesNext::Community::MailUI::ActiveMailbox = "deleted";
|
||||
tn_community_mailui_clearCheckStatus();
|
||||
case 2: // newly wired to sent items button which was present, but hidden
|
||||
$TribesNext::Community::MailUI::ActiveMailbox = "sentbox";
|
||||
tn_community_mailui_clearCheckStatus();
|
||||
}
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 1229
|
||||
function EmailGui::loadCache(%this) { }
|
||||
// replacing function in webemail.cs, 1274
|
||||
function EmailGui::dumpCache(%this) { }
|
||||
// replacing function in webemail.cs, 1174
|
||||
function EmailGui::getCache(%this) { %this.cacheLoaded = true; }
|
||||
|
||||
// addressdlg related functions in webemail
|
||||
|
||||
// replacing function in webemail.cs, 823
|
||||
function AddressDlg::GoSearch(%this)
|
||||
{
|
||||
if(trim(LC_Search.getValue()) !$="")
|
||||
{
|
||||
%this.key = LaunchGui.key++;
|
||||
%this.state = "goSearch";
|
||||
%this.lbstate = "errorcheck";
|
||||
LC_BigList.mode = "select";
|
||||
|
||||
// searching via mail API
|
||||
LC_BigList.clear();
|
||||
AddressDlg.searchActive = 1;
|
||||
tn_community_mail_request_search(LC_Search.getValue());
|
||||
tn_community_mailui_clearCheckStatus();
|
||||
|
||||
LC_BuddyListBtn.direction = 0;
|
||||
LC_BuddyListBtn.text = "ADD TO BUDDYLIST";
|
||||
LC_ListBox.setSelected(0);
|
||||
}
|
||||
else
|
||||
MessageBoxOK("DENIED", "Blank searches for player names are not permitted. Please enter the start of the player name you are seeking.");
|
||||
}
|
||||
|
||||
function tn_community_mailui_displaySearchResults()
|
||||
{
|
||||
if (LC_BigList.mode $= "select" || LC_BigList.mode $= "")
|
||||
{
|
||||
// update the search UI from the data interface cache
|
||||
if ($TMail::SearchMax $= "")
|
||||
return;
|
||||
%count = $TMail::SearchMax;
|
||||
for (%i = 0; %i <= %count; %i++)
|
||||
{
|
||||
%player = $TMail::SearchVals[%i];
|
||||
%guid = getField(%player, 3);
|
||||
|
||||
if (%guid !$= "")
|
||||
LC_BigList.addRow(%guid, %player);
|
||||
}
|
||||
}
|
||||
else if (LC_BigList.mode $= "buddy")
|
||||
{
|
||||
// update the block list UI from the data interface cache
|
||||
if ($TMail::ListMax["buddy"] $= "")
|
||||
return;
|
||||
%count = $TMail::ListMax["buddy"];
|
||||
for (%i = 0; %i <= %count; %i++)
|
||||
{
|
||||
%player = $TMail::ListVals["buddy", %i];
|
||||
%guid = getField(%player, 3);
|
||||
if (%guid !$= "")
|
||||
LC_BigList.addRow(%guid, %player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 742
|
||||
function AddressDlg::AddBuddylist(%this)
|
||||
{
|
||||
%this.key = LaunchGui.key++;
|
||||
%this.lbstate = "buddylist";
|
||||
|
||||
switch (%this.SrcList)
|
||||
{
|
||||
case 0:
|
||||
%addremove = LC_BuddyListBtn.direction;
|
||||
%player = LC_BigList.getValue();
|
||||
%selRow = LC_BigList.getRownumByID(LC_BigList.GetSelectedID());
|
||||
case 1:
|
||||
%addremove = 0;
|
||||
%player = LC_ToList.getValue();
|
||||
case 2:
|
||||
%addremove = 0;
|
||||
%player = LC_CCList.getValue();
|
||||
}
|
||||
%guid = getField(%player, 3);
|
||||
|
||||
if (%guid !$= "")
|
||||
{
|
||||
if (%addremove==0)
|
||||
{
|
||||
%this.doRefresh = 1;
|
||||
%this.state = "addBuddy";
|
||||
tn_community_mail_request_addListEntry("buddy", %guid);
|
||||
//DatabaseQuery(10,%player,%this,%this.key);
|
||||
}
|
||||
else
|
||||
{
|
||||
%this.state = "dropBuddy";
|
||||
tn_community_mail_request_delListEntry("buddy", %guid);
|
||||
//DatabaseQuery(11,%player,%this,%this.key);
|
||||
LC_BigList.removeRowbyId(LC_BigList.getSelectedID());
|
||||
if(%selRow>=LC_BigList.RowCount())
|
||||
%selRow = LC_BigList.RowCount()-1;
|
||||
LC_BigList.setSelectedRow(%selRow);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
error("Trying to modify buddy list on player data missing GUID: " @ %player);
|
||||
}
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 839
|
||||
function AddressDlg::GoList(%this)
|
||||
{
|
||||
%this.key = LaunchGui.key++;
|
||||
%this.lbstate = "errorcheck";
|
||||
if(LC_ListBox.getValue() $= "Select List")
|
||||
{
|
||||
LC_BigList.mode = "select";
|
||||
LC_BigList.clear();
|
||||
}
|
||||
else if(LC_ListBox.getValue() $= "Buddy List")
|
||||
{
|
||||
LC_BigList.mode = "buddy";
|
||||
LC_BigList.clear();
|
||||
%this.state = "getBuddyList";
|
||||
AddressDlg.searchActive = 1;
|
||||
tn_community_mail_request_viewList("buddy");
|
||||
LC_BuddyListBtn.direction = 1;
|
||||
LC_BuddyListBtn.text = "REMOVE FROM BUDDYLIST";
|
||||
}
|
||||
else
|
||||
{
|
||||
LC_BigList.mode = "clan";
|
||||
LC_BigList.clear();
|
||||
|
||||
LC_BuddyListBtn.direction = 0;
|
||||
LC_BuddyListBtn.text = "ADD TO BUDDYLIST";
|
||||
|
||||
%clanid = LC_ListBox.getSelected();
|
||||
%clan = tn_community_browser_getClanProfile(%clanid);
|
||||
|
||||
%pcount = %clan.pcount;
|
||||
if (%pcount !$= "")
|
||||
{
|
||||
for (%i = 0; %i <= %pcount; %i++)
|
||||
{
|
||||
%member = %clan.player[%i];
|
||||
%mguid = getField(%member, 3);
|
||||
if (%mguid != getField(WONGetAuthInfo(), 3))
|
||||
LC_BigList.addRow(%mguid, getFields(%member, 0, 3));
|
||||
}
|
||||
}
|
||||
}
|
||||
tn_community_mailui_clearCheckStatus();
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 777
|
||||
function AddressDlg::AddCC(%this)
|
||||
{
|
||||
if(LC_CCListBtn.direction == 0)
|
||||
{
|
||||
%addName = LC_BigList.getRowTextById(LC_BigList.getSelectedID());
|
||||
%hasDupes = CheckAllDuplicates(%addName);
|
||||
if(%hasDupes == 0)
|
||||
LC_CCList.addRow(LC_CCList.RowCount()+1, %addName);
|
||||
}
|
||||
else
|
||||
{
|
||||
%selRow = LC_CCList.getRownumByID(LC_CCList.GetSelectedID());
|
||||
LC_CCList.removeRowbyID(LC_CCList.getSelectedID());
|
||||
if(%selRow>=LC_CCList.RowCount())
|
||||
%selRow = LC_CCList.RowCount()-1;
|
||||
LC_CCList.setSelectedRow(%selRow);
|
||||
}
|
||||
%this.DestList = 1;
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 797
|
||||
function AddressDlg::AddTo(%this)
|
||||
{
|
||||
if(LC_ToListBtn.direction == 0)
|
||||
{
|
||||
%addName = LC_BigList.getRowTextById(LC_BigList.getSelectedID());
|
||||
%hasDupes = CheckAllDuplicates(%addName);
|
||||
if(%hasDupes == 0 )
|
||||
LC_ToList.addRow(LC_ToList.RowCount()+1, %addName);
|
||||
}
|
||||
else
|
||||
{
|
||||
%selRow = LC_ToList.getRownumByID(LC_ToList.GetSelectedID());
|
||||
LC_ToList.removeRowbyID(LC_ToList.getSelectedID());
|
||||
if(%selRow>=LC_ToList.RowCount())
|
||||
%selRow = LC_ToList.RowCount()-1;
|
||||
LC_ToList.SetSelectedRow(%selRow);
|
||||
}
|
||||
%this.DestList = 0;
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 863
|
||||
function AddressDlg::OK(%this)
|
||||
{
|
||||
EMail_ToEdit.backing = ListToStr(LC_ToList,"\t");
|
||||
EMail_CCEdit.backing = ListToStr(LC_CCList,"\t");
|
||||
|
||||
tn_community_mailui_recipientValidate();
|
||||
|
||||
LC_BigList.Clear();
|
||||
Canvas.PopDialog("AddressDlg");
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 602
|
||||
function ListToStr(%listName,%delim)
|
||||
{
|
||||
%str = "";
|
||||
%rCount = %listName.rowCount();
|
||||
if (%rCount > 0)
|
||||
{
|
||||
for(%r=0;%r<%rCount;%r++)
|
||||
{
|
||||
%str = %str @ %listName.getRowText(%r);
|
||||
if(%r < %rCount-1)
|
||||
{
|
||||
%str = %str @ %delim;
|
||||
}
|
||||
}
|
||||
}
|
||||
return %str;
|
||||
}
|
||||
|
||||
function tn_community_mailui_populateRList(%list, %backing)
|
||||
{
|
||||
%entries = getFieldCount(%backing);
|
||||
%showList = "";
|
||||
for (%i = 0; %i < %entries; %i += 4)
|
||||
{
|
||||
%name = getField(%backing, %i);
|
||||
%tag = getField(%backing, %i + 1);
|
||||
%append = getField(%backing, %i + 2);
|
||||
%guid = getField(%backing, %i + 3);
|
||||
|
||||
%list.addRow(%guid, %name TAB %tag TAB %append TAB %guid);
|
||||
}
|
||||
}
|
||||
|
||||
// replacing function in webemail.cs, 953
|
||||
function AddressDlg::onWake(%this)
|
||||
{
|
||||
%this.doRefresh = 0;
|
||||
%this.key = LaunchGui.key++;
|
||||
%this.state = "loadlistbox";
|
||||
%this.lbstate = "errorcheck";
|
||||
%this.DestList = 0;
|
||||
%this.SrcList = 0;
|
||||
LC_BuddyListBtn.setVisible(0);
|
||||
LC_ListBox.Clear();
|
||||
LC_ListBox.Add("Select List",0);
|
||||
LC_ListBox.Add("Buddy List",1);
|
||||
LC_ListBox.setSelected(0);
|
||||
LC_Search.clear();
|
||||
|
||||
//StrToList(LC_ToList,Email_ToEdit.getValue(),",");
|
||||
LC_ToList.clear();
|
||||
tn_community_mailui_populateRList(LC_ToList, EMail_ToEdit.backing);
|
||||
//StrToList(LC_CCList,Email_CCEdit.getValue(),",");
|
||||
LC_CCList.clear();
|
||||
tn_community_mailui_populateRList(LC_CcList, EMail_CCEdit.backing);
|
||||
|
||||
// retrieve a list of clans the user is in
|
||||
// and make the list of members available for easy emailing.
|
||||
%myguid = getField(WONGetAuthInfo(), 3);
|
||||
%player = tn_community_browser_getPlayerProfile(%myguid);
|
||||
|
||||
%mcount = %player.mcount;
|
||||
if (%mcount !$= "")
|
||||
{
|
||||
%ptag = getField(%player.tag, 0);
|
||||
for (%i = 0; %i <= %mcount; %i++)
|
||||
{
|
||||
%membership = %player.membership[%i];
|
||||
%mid = getField(%membership, 0);
|
||||
%mname = getField(%membership, 1);
|
||||
|
||||
LC_ListBox.add(%mname, %mid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
package tn_tmail
|
||||
{
|
||||
function EmailGui::onWake(%this)
|
||||
{
|
||||
Parent::onWake(%this);
|
||||
|
||||
// Make these buttons inactive until a message is selected:
|
||||
EM_ReplyBtn.setActive( false );
|
||||
EM_ReplyToAllBtn.setActive( false );
|
||||
EM_ForwardBtn.setActive( false );
|
||||
EM_DeleteBtn.setActive( false );
|
||||
EM_BlockBtn.setActive( false );
|
||||
%selId = EM_Browser.getSelectedId();
|
||||
Canvas.pushDialog(LaunchToolbarDlg);
|
||||
|
||||
if ( EM_Browser.rowCount() > 0 )
|
||||
{
|
||||
%row = EM_Browser.findById( %selId );
|
||||
if ( %row == -1 )
|
||||
EM_Browser.setSelectedRow( 0 );
|
||||
else
|
||||
EM_Browser.setSelectedRow( %row );
|
||||
}
|
||||
|
||||
//error("EmailGui::onWake: " @ %this);
|
||||
$TribesNext::Community::MailUI::Awake = 1;
|
||||
tn_community_mailui_loadSelected();
|
||||
}
|
||||
|
||||
function EmailGui::onSleep(%this)
|
||||
{
|
||||
Parent::onSleep(%this);
|
||||
$TribesNext::Community::MailUI::Awake = 0;
|
||||
//error("EmailGui::onSleep: " @ %this);
|
||||
}
|
||||
};
|
||||
if (!isActivePackage(tn_tmail))
|
||||
activatePackage(tn_tmail);
|
||||
18
t2csri/community/settings.cs
Normal file
18
t2csri/community/settings.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// TribesNext Project
|
||||
// http://www.tribesnext.com/
|
||||
// Copyright 2011
|
||||
|
||||
// Tribes 2 Community System
|
||||
// Robot Client Settings
|
||||
|
||||
// This file contains the URL and server settings for the community system.
|
||||
|
||||
$TribesNext::Community::Host = "localhost";
|
||||
$TribesNext::Community::Port = 80;
|
||||
$TribesNext::Community::BaseURL = "/tn/robot/";
|
||||
|
||||
$TribesNext::Community::LoginScript = "robot_login.php";
|
||||
$TribesNext::Community::MailScript = "robot_mail.php";
|
||||
$TribesNext::Community::BrowserScript = "robot_browser.php";
|
||||
|
||||
$TribesNext::Community::SessionRefresh = 10*60;
|
||||
492
t2csri/crypto.rb
Normal file
492
t2csri/crypto.rb
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
#
|
||||
# Tribes 2 Community System Reengineering Initiative
|
||||
# Assymetric Cryptography Identity Provisioning
|
||||
# Version 1.0
|
||||
#
|
||||
# Written by Electricutioner/Thyth
|
||||
# http://absolous.no-ip.com/
|
||||
# Copyright 2008
|
||||
#
|
||||
# Released under the terms of the GNU General Public License v3 or later.
|
||||
# http://www.gnu.org/licenses/gpl.html
|
||||
# Your use of this software is subject to the terms of that license. Use, modification, or distribution
|
||||
# constitutes acceptance of these software terms. This license is the only manner by which you are permitted
|
||||
# to use this software, thus rejection of the license terms prohibits your use of this software.
|
||||
#
|
||||
|
||||
# fast modular exponentiation -- the key to the RSA algorithm
|
||||
# result = (b ^ e) % m
|
||||
def rsa_mod_exp(b, e, m)
|
||||
result = 1
|
||||
while (e > 0)
|
||||
if ((e & 1) == 1)
|
||||
result = (result * b) % m
|
||||
end
|
||||
e = e >> 1
|
||||
b = (b * b) % m
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
# RSA key class to keep things nice and organized
|
||||
class RSAKey
|
||||
# allow reading and writing the key values
|
||||
attr_reader :e, :n, :twister, :strength
|
||||
attr_writer :e, :d, :n, :twister
|
||||
|
||||
# allow protecting the d value so it isn't stolen by evil scripts
|
||||
# once a key is protected, it cannot be deprotected, but it can be used to decrypt
|
||||
def protect
|
||||
@protected = 1
|
||||
end
|
||||
# attribute reader for d that returns nil if key protection is active
|
||||
def d
|
||||
if (@protected == 1)
|
||||
return nil
|
||||
else
|
||||
return @d
|
||||
end
|
||||
end
|
||||
|
||||
# encrypt a message with the public exponent (e)
|
||||
# this could be construed as a misnomer, since this is used to verify authentication
|
||||
# images from the authentication server, and to verify a client has both parts of the key they
|
||||
# claim to have
|
||||
def encrypt(message)
|
||||
rsa_mod_exp(message, @e, @n)
|
||||
end
|
||||
|
||||
# decrypt a message with the private exponent (d), also usable for signing
|
||||
# obviously, this will fail if the instance is only the public part of the key
|
||||
def decrypt(message)
|
||||
rsa_mod_exp(message, @d, @n)
|
||||
end
|
||||
|
||||
# generate a new random RSA key of the specified bitsize
|
||||
# this generates keys that should be resistant to quick factorization techniques
|
||||
def generate(bitsize)
|
||||
p = 0
|
||||
q = 0
|
||||
@n = 100
|
||||
@strength = bitsize
|
||||
|
||||
# test for some conditions that could produce insecure RSA keys
|
||||
# p, q difference to see if Fermat factorization could be successful
|
||||
# p - q must be greater than 2*(n ^ (1/4))
|
||||
while ((p - q).abs < (2 * Math.sqrt(Math.sqrt(@n))))
|
||||
p = createPrime(bitsize / 2, 150)
|
||||
q = createPrime(bitsize / 2, 150)
|
||||
@n = p * q
|
||||
end
|
||||
|
||||
totient = (p - 1) * (q - 1)
|
||||
|
||||
# e must be coprime to the totient. we start at 3 and add 2 whenever coprime test fails
|
||||
@e = 3
|
||||
coprimee = 0
|
||||
while (coprimee)
|
||||
if (@e > 7)
|
||||
# e over 7 has a large chance of not being coprime to the totient
|
||||
generate(bitsize)
|
||||
return
|
||||
end
|
||||
block = extendedEuclid(@e, totient, 0, 1, 1, 0)
|
||||
if (block[0] > 1)
|
||||
@e = @e + 2
|
||||
else
|
||||
coprimee = nil
|
||||
end
|
||||
end
|
||||
|
||||
# calculate the d value such that d * e = 1 mod totient
|
||||
# this calculation is done in the coprime of e verification
|
||||
@d = block[1]
|
||||
while (@d < 0)
|
||||
@d = @d + totient
|
||||
end
|
||||
|
||||
# verify that the generated key is a valid RSA key
|
||||
1.upto(10) do |i|
|
||||
testVal = @twister.randomnumber(bitsize) % @n
|
||||
if (decrypt(encrypt(testVal)) != testVal)
|
||||
# key failed... generate a new one
|
||||
generate(bitsize)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# private methods that people shouldn't be poking without a good reason
|
||||
private
|
||||
# obtain gcd and return the "d" value that we want
|
||||
def extendedEuclid(a, b, c, d, e, f)
|
||||
if (b == 0)
|
||||
block = Array.new(3, 0)
|
||||
block[0] = a; # gcd(a, b)
|
||||
block[1] = e; # coefficient of 'a' and the 'd' value we want
|
||||
block[2] = f; # coefficient of 'b'
|
||||
return block
|
||||
else
|
||||
return extendedEuclid(b, a % b, e - ((a / b) * c), f - ((a / b) * d), c, d);
|
||||
end
|
||||
end
|
||||
|
||||
# create a prime number of the specified bitlength
|
||||
# the number of tests specified will control how many miller-rabin primality tests are run
|
||||
# this function will return a prime number with a high degree of confidence if sufficient
|
||||
# tests are run
|
||||
def createPrime(bitlen, tests)
|
||||
# generate a random number of the specific bitlen
|
||||
p = @twister.randomnumber(bitlen)
|
||||
|
||||
# run the primality tests
|
||||
testrun = 0
|
||||
while (testrun < tests)
|
||||
if (prime?(p))
|
||||
testrun = testrun + 1
|
||||
else # not prime -- generate a new one
|
||||
return createPrime(bitlen, tests)
|
||||
end
|
||||
end
|
||||
return p
|
||||
end
|
||||
|
||||
# run a miller-rabin primality test on the given number
|
||||
# returns true if the number is "probably" prime
|
||||
def prime?(potential)
|
||||
qandm = getqm(potential)
|
||||
if (qandm[0] == -1)
|
||||
return nil
|
||||
end
|
||||
|
||||
bval = @twister.randomnumber(@strength / 2)
|
||||
mval = qandm[1]
|
||||
|
||||
if (rsa_mod_exp(bval, mval, potential) == 1)
|
||||
return 1
|
||||
end
|
||||
j = 0
|
||||
while (j < qandm[0])
|
||||
if ((potential - 1) == rsa_mod_exp(bval, mval, potential))
|
||||
return 1
|
||||
end
|
||||
mval = mval * 2
|
||||
j = j + 1
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
def getqm(p)
|
||||
p = p - 1
|
||||
rt = Array.new(2, 0)
|
||||
if (p & 1 != 0)
|
||||
rt[0] = -1
|
||||
rt[1] = -1
|
||||
return rt
|
||||
end
|
||||
div = p / 2
|
||||
counter = 1
|
||||
while (div & 1 == 0)
|
||||
counter = counter + 1
|
||||
div = div / 2
|
||||
end
|
||||
rt[0] = counter
|
||||
rt[1] = div
|
||||
return rt
|
||||
end
|
||||
end
|
||||
|
||||
# Mersenne Twister pseudo random number generator, modified for cryptographic security
|
||||
# period length should be 20 * (2 ^ 19937 - 1)
|
||||
class MersenneTwister
|
||||
@index = 0
|
||||
|
||||
# build the internal storage array
|
||||
def initialize
|
||||
@mt = Array.new(624, 0)
|
||||
end
|
||||
|
||||
# initialize the generator from a seed, can be done repeatedly
|
||||
def seedgen(seed)
|
||||
@mt[0] = seed
|
||||
1.upto(623) do |i|
|
||||
@mt[i] = 0xffffffff & (1812433243 * (@mt[i - 1] ^ (@mt[i - 1] >> 30)) + i)
|
||||
end
|
||||
generateNumbers
|
||||
end
|
||||
|
||||
# extract a number that does not give away the state of the generator, takes 37 elements from generator
|
||||
# and applies SHA1 on it to get a 20 element number. this is repeated until the required length
|
||||
# is reached, and truncated as necessary to bring it down to the requested bitlen
|
||||
def randomnumber(bits)
|
||||
bytes = bits / 8
|
||||
if (bits % 8 != 0)
|
||||
bytes = bytes + 1
|
||||
end
|
||||
|
||||
produced = 0
|
||||
output = 0
|
||||
stages = 0
|
||||
mask = 0
|
||||
|
||||
sha1hash = SHA1Pure.new
|
||||
while (produced < bytes)
|
||||
sha1hash.prepare
|
||||
1.upto(37) do |i|
|
||||
sha1hash.append(extractNumber().to_s);
|
||||
end
|
||||
digest = sha1hash.hexdigest.to_i(16)
|
||||
output = output | (digest << (160 * stages))
|
||||
produced = produced + 20
|
||||
stages = stages + 1
|
||||
end
|
||||
|
||||
0.upto(bits.to_i) do |i|
|
||||
mask = (mask.to_i << 1) | 1
|
||||
end
|
||||
return (output & mask)
|
||||
end
|
||||
|
||||
private
|
||||
# extract a tempered pseudorandom number
|
||||
def extractNumber()
|
||||
if (@index == 0)
|
||||
generateNumbers()
|
||||
end
|
||||
|
||||
y = @mt[@index.to_i]
|
||||
y = y ^ (y >> 11)
|
||||
y = y ^ ((y << 7) & 2636928640)
|
||||
y = y ^ ((y << 15) & 4022730752)
|
||||
y = y ^ (y >> 18)
|
||||
y = y & 0xffffffff
|
||||
|
||||
@index = (@index.to_i + 1) % 624
|
||||
return y
|
||||
end
|
||||
|
||||
# generate 624 untempered numbers for this generator's array
|
||||
def generateNumbers()
|
||||
0.upto(623) do |i|
|
||||
y = (@mt[i] & 0x80000000) + (@mt[(i + 1) % 624] & 0x7FFFFFFF)
|
||||
@mt[i] = @mt[(i + 397) % 624] ^ (y >> 1)
|
||||
if (y & 1 == 1)
|
||||
@mt[i] = @mt[i] ^ 2567483615
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# SHA1 in Pure Ruby
|
||||
class SHA1Pure
|
||||
|
||||
def initialize
|
||||
prepare
|
||||
end
|
||||
|
||||
# prepare the hash digester for a new hash
|
||||
def prepare
|
||||
@state = Array.new(5, 0)
|
||||
@block = Array.new(16, 0)
|
||||
@blockIndex = 0
|
||||
@count = 0
|
||||
|
||||
@state[0] = 0x67452301
|
||||
@state[1] = 0xefcdab89
|
||||
@state[2] = 0x98badcfe
|
||||
@state[3] = 0x10325476
|
||||
@state[4] = 0xc3d2e1f0
|
||||
end
|
||||
|
||||
# append a string to the string being digested
|
||||
def append(str)
|
||||
str = str.to_s
|
||||
str.each_byte {|c| update(c.to_i & 0xff)}
|
||||
end
|
||||
|
||||
# produce a hexidecimal digest string
|
||||
def hexdigest
|
||||
bits = Array.new(8, 0)
|
||||
0.upto(7) do |i|
|
||||
bits[i] = (@count >> (((7 - i) * 8) & 0xff)) & 0xff
|
||||
end
|
||||
update(128)
|
||||
while (@blockIndex != 56)
|
||||
update(0)
|
||||
end
|
||||
0.upto(7) do |i|
|
||||
update(bits[i])
|
||||
end # this will accomplish a transform
|
||||
|
||||
# output the digest
|
||||
digest = ""
|
||||
0.upto(4) do |i|
|
||||
chunk = @state[i].to_s(16)
|
||||
while(chunk.length < 8)
|
||||
chunk = "0" + chunk
|
||||
end
|
||||
digest = digest + chunk
|
||||
end
|
||||
prepare
|
||||
return digest
|
||||
end
|
||||
|
||||
private
|
||||
def rol(val, bits)
|
||||
val = val.to_i
|
||||
bits = bits.to_i
|
||||
return (val << bits) | (val >> (32 - bits))
|
||||
end
|
||||
|
||||
def blk0(i)
|
||||
i = i.to_i
|
||||
@block[i] = (rol(@block[i], 24) & 0xff00ff00) | (rol(@block[i], 8) & 0xff00ff)
|
||||
@block[i] = @block[i] & 0xffffffff
|
||||
return @block[i]
|
||||
end
|
||||
|
||||
def blk(i)
|
||||
i = i.to_i
|
||||
@block[i & 15] = rol(@block[(i + 13) & 15] ^ @block[(i + 8) & 15] ^ @block[(i + 2) & 15] ^ @block[i & 15], 1)
|
||||
@block[i & 15] = @block[i & 15] & 0xffffffff
|
||||
return @block[i & 15]
|
||||
end
|
||||
|
||||
def r0(data, v, w, x, y, z, i)
|
||||
data[z] += ((data[w] & (data[x] ^ data[y])) ^ data[y]) + blk0(i) + 0x5a827999 + rol(data[v], 5)
|
||||
data[z] = data[z] & 0xffffffff
|
||||
data[w] = rol(data[w], 30) & 0xffffffff
|
||||
end
|
||||
|
||||
def r1(data, v, w, x, y, z, i)
|
||||
data[z] += ((data[w] & (data[x] ^ data[y])) ^ data[y]) + blk(i) + 0x5a827999 + rol(data[v], 5)
|
||||
data[z] = data[z] & 0xffffffff
|
||||
data[w] = rol(data[w], 30) & 0xffffffff
|
||||
end
|
||||
|
||||
def r2(data, v, w, x, y, z, i)
|
||||
data[z] += (data[w] ^ data[x] ^ data[y]) + blk(i) + 0x6ed9eba1 + rol(data[v], 5)
|
||||
data[z] = data[z] & 0xffffffff
|
||||
data[w] = rol(data[w], 30) & 0xffffffff
|
||||
end
|
||||
|
||||
def r3(data, v, w, x, y, z, i)
|
||||
data[z] += (((data[w] | data[x]) & data[y]) | (data[w] & data[x])) + blk(i) + 0x8f1bbcdc + rol(data[v], 5)
|
||||
data[z] = data[z] & 0xffffffff
|
||||
data[w] = rol(data[w], 30) & 0xffffffff
|
||||
end
|
||||
|
||||
def r4(data, v, w, x, y, z, i)
|
||||
data[z] += (data[w] ^ data[x] ^ data[y]) + blk(i) + 0xca62c1d6 + rol(data[v], 5)
|
||||
data[z] = data[z] & 0xffffffff
|
||||
data[w] = rol(data[w], 30) & 0xffffffff
|
||||
end
|
||||
|
||||
def transform
|
||||
dd = Array.new(5, 0)
|
||||
dd[0] = @state[0]
|
||||
dd[1] = @state[1]
|
||||
dd[2] = @state[2]
|
||||
dd[3] = @state[3]
|
||||
dd[4] = @state[4]
|
||||
|
||||
r0(dd,0,1,2,3,4, 0)
|
||||
r0(dd,4,0,1,2,3, 1)
|
||||
r0(dd,3,4,0,1,2, 2)
|
||||
r0(dd,2,3,4,0,1, 3)
|
||||
r0(dd,1,2,3,4,0, 4)
|
||||
r0(dd,0,1,2,3,4, 5)
|
||||
r0(dd,4,0,1,2,3, 6)
|
||||
r0(dd,3,4,0,1,2, 7)
|
||||
r0(dd,2,3,4,0,1, 8)
|
||||
r0(dd,1,2,3,4,0, 9)
|
||||
r0(dd,0,1,2,3,4,10)
|
||||
r0(dd,4,0,1,2,3,11)
|
||||
r0(dd,3,4,0,1,2,12)
|
||||
r0(dd,2,3,4,0,1,13)
|
||||
r0(dd,1,2,3,4,0,14)
|
||||
r0(dd,0,1,2,3,4,15)
|
||||
r1(dd,4,0,1,2,3,16)
|
||||
r1(dd,3,4,0,1,2,17)
|
||||
r1(dd,2,3,4,0,1,18)
|
||||
r1(dd,1,2,3,4,0,19)
|
||||
r2(dd,0,1,2,3,4,20)
|
||||
r2(dd,4,0,1,2,3,21)
|
||||
r2(dd,3,4,0,1,2,22)
|
||||
r2(dd,2,3,4,0,1,23)
|
||||
r2(dd,1,2,3,4,0,24)
|
||||
r2(dd,0,1,2,3,4,25)
|
||||
r2(dd,4,0,1,2,3,26)
|
||||
r2(dd,3,4,0,1,2,27)
|
||||
r2(dd,2,3,4,0,1,28)
|
||||
r2(dd,1,2,3,4,0,29)
|
||||
r2(dd,0,1,2,3,4,30)
|
||||
r2(dd,4,0,1,2,3,31)
|
||||
r2(dd,3,4,0,1,2,32)
|
||||
r2(dd,2,3,4,0,1,33)
|
||||
r2(dd,1,2,3,4,0,34)
|
||||
r2(dd,0,1,2,3,4,35)
|
||||
r2(dd,4,0,1,2,3,36)
|
||||
r2(dd,3,4,0,1,2,37)
|
||||
r2(dd,2,3,4,0,1,38)
|
||||
r2(dd,1,2,3,4,0,39)
|
||||
r3(dd,0,1,2,3,4,40)
|
||||
r3(dd,4,0,1,2,3,41)
|
||||
r3(dd,3,4,0,1,2,42)
|
||||
r3(dd,2,3,4,0,1,43)
|
||||
r3(dd,1,2,3,4,0,44)
|
||||
r3(dd,0,1,2,3,4,45)
|
||||
r3(dd,4,0,1,2,3,46)
|
||||
r3(dd,3,4,0,1,2,47)
|
||||
r3(dd,2,3,4,0,1,48)
|
||||
r3(dd,1,2,3,4,0,49)
|
||||
r3(dd,0,1,2,3,4,50)
|
||||
r3(dd,4,0,1,2,3,51)
|
||||
r3(dd,3,4,0,1,2,52)
|
||||
r3(dd,2,3,4,0,1,53)
|
||||
r3(dd,1,2,3,4,0,54)
|
||||
r3(dd,0,1,2,3,4,55)
|
||||
r3(dd,4,0,1,2,3,56)
|
||||
r3(dd,3,4,0,1,2,57)
|
||||
r3(dd,2,3,4,0,1,58)
|
||||
r3(dd,1,2,3,4,0,59)
|
||||
r4(dd,0,1,2,3,4,60)
|
||||
r4(dd,4,0,1,2,3,61)
|
||||
r4(dd,3,4,0,1,2,62)
|
||||
r4(dd,2,3,4,0,1,63)
|
||||
r4(dd,1,2,3,4,0,64)
|
||||
r4(dd,0,1,2,3,4,65)
|
||||
r4(dd,4,0,1,2,3,66)
|
||||
r4(dd,3,4,0,1,2,67)
|
||||
r4(dd,2,3,4,0,1,68)
|
||||
r4(dd,1,2,3,4,0,69)
|
||||
r4(dd,0,1,2,3,4,70)
|
||||
r4(dd,4,0,1,2,3,71)
|
||||
r4(dd,3,4,0,1,2,72)
|
||||
r4(dd,2,3,4,0,1,73)
|
||||
r4(dd,1,2,3,4,0,74)
|
||||
r4(dd,0,1,2,3,4,75)
|
||||
r4(dd,4,0,1,2,3,76)
|
||||
r4(dd,3,4,0,1,2,77)
|
||||
r4(dd,2,3,4,0,1,78)
|
||||
r4(dd,1,2,3,4,0,79)
|
||||
|
||||
@state[0] = (@state[0] + dd[0]) & 0xffffffff
|
||||
@state[1] = (@state[1] + dd[1]) & 0xffffffff
|
||||
@state[2] = (@state[2] + dd[2]) & 0xffffffff
|
||||
@state[3] = (@state[3] + dd[3]) & 0xffffffff
|
||||
@state[4] = (@state[4] + dd[4]) & 0xffffffff
|
||||
end
|
||||
|
||||
def update(b)
|
||||
mask = (8 * (@blockIndex & 3))
|
||||
@count = @count + 8
|
||||
@block[@blockIndex >> 2] = @block[@blockIndex >> 2] & ~(0xff << mask)
|
||||
@block[@blockIndex >> 2] = @block[@blockIndex >> 2] | ((b & 0xff) << mask)
|
||||
@blockIndex = @blockIndex + 1
|
||||
if (@blockIndex == 64)
|
||||
transform
|
||||
@blockIndex = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
47
t2csri/glue.cs
Normal file
47
t2csri/glue.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Tribes 2 Unofficial Authentication System
|
||||
// http://www.tribesnext.com/
|
||||
// Written by Thyth
|
||||
// Copyright 2008-2011 by Thyth and the Tribes 2 Community System Reengineering Intitiative
|
||||
|
||||
// Version 1.2 initialization and glue file
|
||||
|
||||
// enable debugging console
|
||||
//enableWinConsole(1);
|
||||
|
||||
// check to see if the game has been launched in offline mode
|
||||
function t2csri_glue_initChecks()
|
||||
{
|
||||
$t2csri::isOfflineMode = 0;
|
||||
for (%i = 0; %i < $Game::argc; %i++)
|
||||
{
|
||||
%arg = $Game::argv[%i];
|
||||
if (%arg $= "-nologin")
|
||||
$t2csri::isOfflineMode = 1;
|
||||
}
|
||||
if ($t2csri::isOfflineMode)
|
||||
{
|
||||
echo("Running TribesNext in offline mode. Not making connections to the Internet.");
|
||||
}
|
||||
}
|
||||
t2csri_glue_initChecks();
|
||||
|
||||
// load the torque script components
|
||||
exec("t2csri/authconnect.cs");
|
||||
exec("t2csri/authinterface.cs");
|
||||
exec("t2csri/base64.cs");
|
||||
exec("t2csri/clientSide.cs");
|
||||
exec("t2csri/ipv4.cs");
|
||||
exec("t2csri/rubyUtils.cs");
|
||||
|
||||
// load the Ruby components
|
||||
rubyExec("t2csri/crypto.rb");
|
||||
rubyExec("t2csri/certstore.rb");
|
||||
|
||||
rubyEval("certstore_loadAccounts");
|
||||
rubyEval("tsEval '$RubyEnabled=1;'");
|
||||
|
||||
// connect to the auth server via signed lookup
|
||||
schedule(32, 0, authConnect_findAuthServer);
|
||||
|
||||
// get the global IP for sanity testing purposes
|
||||
schedule(32, 0, ipv4_getInetAddress);
|
||||
106
t2csri/ipv4.cs
Normal file
106
t2csri/ipv4.cs
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// Tribes 2 Unofficial Authentication System
|
||||
// http://www.tribesnext.com/
|
||||
// Written by Electricutioner/Thyth
|
||||
// Copyright 2008 by Electricutioner/Thyth and the Tribes 2 Community System Reengineering Intitiative
|
||||
|
||||
// IPv4 Utils Version 1.1 (03/26/2008)
|
||||
|
||||
// Whatismyip spat this out for automation purposes:
|
||||
// http://www.whatismyip.com/automation/n09230945.asp
|
||||
// Hopefully it won't change. We only check for extern-ip once
|
||||
// when the game launches, so there shouldn't be more than a
|
||||
// couple of hundred hits per day from the entire T2 community.
|
||||
|
||||
$IPv4::AutomationURL = "/whatismyip.php";
|
||||
|
||||
function ipv4_getInetAddress()
|
||||
{
|
||||
if ($IPv4::InetAddress !$= "")
|
||||
return;
|
||||
|
||||
if (isObject(IPv4Connection))
|
||||
{
|
||||
IPv4Connection.disconnect();
|
||||
IPv4Connection.delete();
|
||||
}
|
||||
new TCPObject(IPv4Connection);
|
||||
IPV4Connection.data = "GET " @ $IPv4::AutomationURL @ " HTTP/1.1\r\nHost: www.tribesnext.com\r\nUser-Agent: Tribes 2\r\nConnection: close\r\n\r\n";
|
||||
IPv4Connection.connect("www.tribesnext.com:80");
|
||||
}
|
||||
|
||||
function IPv4Connection::onConnected(%this)
|
||||
{
|
||||
%this.send(%this.data);
|
||||
}
|
||||
|
||||
function IPv4Connection::onLine(%this, %line)
|
||||
{
|
||||
if (%line $= "" || %line == 0)
|
||||
return;
|
||||
$IPv4::InetAddress = %line;
|
||||
%this.disconnect();
|
||||
}
|
||||
|
||||
// added for 1.1, schedule a new attempt if we're blank, until we have an address
|
||||
function IPv4Connection::onDisconnect(%this)
|
||||
{
|
||||
schedule(5000, 0, ipv4_getInetAddress);
|
||||
}
|
||||
|
||||
// used for the IP-nonce sanity check...
|
||||
// source will claim that this computer is the destination.
|
||||
// check to make sure the destination is reasonable
|
||||
function ipv4_reasonableConnection(%source, %destination)
|
||||
{
|
||||
if (%destination $= $IPv4::InetAddress)
|
||||
{
|
||||
// the destination claims to be us from the Internet. This is reasonable.
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// destination is different from the IPv4 Internet Address. We could be on a LAN.
|
||||
if (getSubStr(%destination, 0, 2) $= "10")
|
||||
{
|
||||
// Class A LAN, check if the client is also on the same network
|
||||
return (getSubStr(%source, 0, 2) $= "10");
|
||||
}
|
||||
else if (getSubStr(%destination, 0, 3) $= "172" && getSubStr(%destination, 4, 2) > 15 && getSubStr(%destination, 4, 2) < 33)
|
||||
{
|
||||
// Class B LAN, check if the client is also on the same network
|
||||
return (getSubStr(%source, 0, 3) $= "172" && getSubStr(%source, 4, 2) > 15 && getSubStr(%source, 4, 2) < 33);
|
||||
}
|
||||
else if (getSubStr(%destination, 0, 7) $= "192.168")
|
||||
{
|
||||
// Class C LAN, check if the client is also on the same network
|
||||
return (getSubStr(%source, 0, 7) $= "192.168");
|
||||
}
|
||||
else if (getSubStr(%destination, 0, 7) $= "169.254")
|
||||
{
|
||||
// Link-local addresses/Zeroconf network, check if client is from the same place
|
||||
return (getSubStr(%source, 0, 7) $= "169.254");
|
||||
}
|
||||
else if (%destination $= $Host::BindAddress)
|
||||
{
|
||||
// Or it could be the pref-based bind address.
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// looks like the destination address provided by the source is not reasonable
|
||||
// this is likely an attempt at a client token replay attack
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// convert a (big endian) hex block into a numeric IP
|
||||
function ipv4_hexBlockToIP(%hex)
|
||||
{
|
||||
for (%i = 0; %i < 4; %i++)
|
||||
{
|
||||
%ip = %ip @ "." @ strcmp(collapseEscape("\\x" @ getSubStr(%hex, %i * 2, 2)), "");
|
||||
}
|
||||
return getSubStr(%ip, 1, strlen(%ip) - 1);
|
||||
}
|
||||
19
t2csri/postLogin.cs
Normal file
19
t2csri/postLogin.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// TribesNext Project
|
||||
// http://www.tribesnext.com/
|
||||
// Copyright 2011
|
||||
|
||||
// Tasks to be run after the login process is completed.
|
||||
|
||||
// load the community script components
|
||||
if (WONGetAuthInfo() !$= "")
|
||||
{
|
||||
exec("t2csri/community/settings.cs");
|
||||
exec("t2csri/community/login.cs");
|
||||
exec("t2csri/community/mail.cs");
|
||||
exec("t2csri/community/browser.cs");
|
||||
schedule(32, 0, exec, "t2csri/community/mailUI.cs");
|
||||
schedule(64, 0, exec, "t2csri/community/browserUI.cs");
|
||||
|
||||
// log into the community server
|
||||
tn_community_login_initiate();
|
||||
}
|
||||
31
t2csri/rubyUtils.cs
Normal file
31
t2csri/rubyUtils.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Tribes 2 Unofficial Authentication System
|
||||
// http://www.tribesnext.com/
|
||||
// Written by Electricutioner/Thyth
|
||||
// Copyright 2008-2009 by Electricutioner/Thyth and the Tribes 2 Community System Reengineering Intitiative
|
||||
|
||||
// Ruby Interface Utilities Version 1.3 (01/27/2009)
|
||||
|
||||
// loads a ruby script
|
||||
function rubyExec(%script)
|
||||
{
|
||||
echo("Loading Ruby script " @ %script @ ".");
|
||||
new FileObject("RubyExecutor");
|
||||
RubyExecutor.openForRead(%script);
|
||||
|
||||
while (!RubyExecutor.isEOF())
|
||||
{
|
||||
%line = RubyExecutor.readLine();
|
||||
%buffer = %buffer @ "\n" @ %line;
|
||||
}
|
||||
rubyEval(%buffer);
|
||||
RubyExecutor.close();
|
||||
RubyExecutor.delete();
|
||||
}
|
||||
|
||||
// extracts a value from the Ruby interpreter environment
|
||||
function rubyGetValue(%value)
|
||||
{
|
||||
$temp = "";
|
||||
rubyEval("tsEval '$temp=\"' + " @ %value @ " + '\";'");
|
||||
return $temp;
|
||||
}
|
||||
322
t2csri/serverSide.cs
Normal file
322
t2csri/serverSide.cs
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
// Tribes 2 Unofficial Authentication System
|
||||
// http://www.tribesnext.com/
|
||||
// Written by Electricutioner/Thyth
|
||||
// Copyright 2008 by Electricutioner/Thyth and the Tribes 2 Community System Reengineering Intitiative
|
||||
|
||||
// Version 1.3: 2009-04-23
|
||||
// Clan/Rename Certificate support is included in this version.
|
||||
|
||||
// initialize the SHA1 digester in Ruby
|
||||
function t2csri_initDigester()
|
||||
{
|
||||
$SHA1::Initialized = 1;
|
||||
rubyEval("$sha1hasher = SHA1Pure.new");
|
||||
}
|
||||
|
||||
// use Ruby to get the SHA1 hash of the string
|
||||
function sha1sum(%string)
|
||||
{
|
||||
if (!$SHA1::Initialized)
|
||||
t2csri_initDigester();
|
||||
%string = strReplace(%string, "'", "\\'");
|
||||
rubyEval("$sha1hasher.prepare");
|
||||
rubyEval("$sha1hasher.append('" @ %string @ "')");
|
||||
rubyEval("tsEval '$temp=\"' + $sha1hasher.hexdigest + '\";'");
|
||||
%temp = $temp;
|
||||
$temp = "";
|
||||
return %temp;
|
||||
}
|
||||
|
||||
// verify with the auth server's RSA public key... hard coded in the executable
|
||||
function t2csri_verify_auth_signature(%sig)
|
||||
{
|
||||
rubyEval("tsEval '$temp=\"' + t2csri_verify_auth_signature('" @ %sig @ "').to_s(16) + '\";'");
|
||||
while (strLen($temp) < 40)
|
||||
$temp = "0" @ $temp;
|
||||
return $temp;
|
||||
}
|
||||
|
||||
// server sends the client a certificate in chunks, since they can be rather large
|
||||
function serverCmdt2csri_sendCertChunk(%client, %chunk)
|
||||
{
|
||||
if (%client.doneAuthenticating)
|
||||
return;
|
||||
|
||||
//echo("Client sent certificate chunk.");
|
||||
%client.t2csri_cert = %client.t2csri_cert @ %chunk;
|
||||
if (strlen(%client.t2csri_cert) > 20000)
|
||||
{
|
||||
%client.setDisconnectReason("Account certificate too long. Check your account key for corruption.");
|
||||
%client.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// gets a hex version of the client's IP address
|
||||
// used to prevent a replay attack as described by Rain
|
||||
function t2csri_gameClientHexAddress(%client)
|
||||
{
|
||||
%ip = %client.getAddress();
|
||||
%ip = getSubStr(%ip, strstr(%ip, ":") + 1, strlen(%ip));
|
||||
%ip = getSubStr(%ip, 0, strstr(%ip, ":"));
|
||||
%ip = strReplace(%ip, ".", " ");
|
||||
|
||||
for (%i = 0; %i < getWordCount(%ip); %i++)
|
||||
{
|
||||
%byte = DecToHex(getWord(%ip, %i));
|
||||
if (strLen(%byte) < 2)
|
||||
%byte = "0" @ %byte;
|
||||
%hex = %hex @ %byte;
|
||||
}
|
||||
return %hex;
|
||||
}
|
||||
|
||||
// client is done sending their cert... verify it, and encrypt a challenge for the client
|
||||
// challenge sent to client is %clientChallenge @ %serverChallenge.
|
||||
function serverCmdt2csri_sendChallenge(%client, %clientChallenge)
|
||||
{
|
||||
if (%client.doneAuthenticating)
|
||||
return;
|
||||
|
||||
//echo("Client requesting challenge. CC: " @ %clientChallenge);
|
||||
//echo("Client's certificate: " @ %client.t2csri_cert);
|
||||
// verify that the certificate the client sent is signed by the authentication server
|
||||
%user = strReplace(getField(%client.t2csri_cert, 0), "\x27", "\\\x27");
|
||||
|
||||
%guid = getField(%client.t2csri_cert, 1);
|
||||
// sanitize GUID
|
||||
for (%i = 0; %i < strlen(%guid); %i++)
|
||||
{
|
||||
%char = strcmp(getSubStr(%guid, %i, 1), "");
|
||||
if (%char > 57 || %char < 48)
|
||||
{
|
||||
%client.setDisconnectReason("Invalid characters in client GUID.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
%e = getField(%client.t2csri_cert, 2);
|
||||
%n = getField(%client.t2csri_cert, 3);
|
||||
%sig = getField(%client.t2csri_cert, 4);
|
||||
|
||||
// sanitize e, n, sig... all of which are just hex
|
||||
%rsa_chunk = strlwr(%e @ %n @ %sig);
|
||||
for (%i = 0; %i < strlen(%rsa_chunk); %i++)
|
||||
{
|
||||
%char = strcmp(getSubStr(%rsa_chunk, %i, 1), "");
|
||||
if ((%char < 48 || %char > 102) || (%char > 57 && %char < 97))
|
||||
{
|
||||
%client.setDisconnectReason("Invalid characters in certificate RSA fields.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// get a SHA1 sum
|
||||
%sumStr = %user @ "\t" @ %guid @ "\t" @ %e @ "\t" @ %n;
|
||||
%certSum = sha1sum(%sumStr);
|
||||
%verifSum = t2csri_verify_auth_signature(%sig);
|
||||
while (strLen(%verifSum) < 40)
|
||||
%verifSum = "0" @ %verifSum;
|
||||
//echo("Calc'd SHA1: " @ %certSum);
|
||||
//echo("Signed SHA1: " @ %verifSum);
|
||||
|
||||
// verify signature
|
||||
if (%verifSum !$= %certSum)
|
||||
{
|
||||
// client supplied a bogus certificate that was never signed by the auth server
|
||||
// abort their connection
|
||||
%client.setDisconnectReason("Invalid account certificate.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
// process client challenge half
|
||||
%client.t2csri_clientChallenge = %clientChallenge;
|
||||
|
||||
// sanitize the challenge to make sure it contains nothing but hex characters.
|
||||
// anything else means that the client is trying to hijack control of the interpreter
|
||||
%clientChallenge = strlwr(%clientChallenge);
|
||||
for (%i = 0; %i < strlen(%clientChallenge); %i++)
|
||||
{
|
||||
%char = strcmp(getSubStr(%clientChallenge, %i, 1), "");
|
||||
if ((%char < 48 || %char > 102) || (%char > 57 && %char < 97))
|
||||
{
|
||||
%client.setDisconnectReason("Invalid characters in client challenge.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// verify that the IP address the client thinks it is connecting to is the address this server
|
||||
// is reasonable... take into account connections from the same private IP subnet (192.168.*.*, 10.*.*.*, etc)
|
||||
%sanityIP = ipv4_hexBlockToIP(getSubStr(%clientChallenge, strLen(%clientChallenge) - 8, 8));
|
||||
%sourceIP = ipv4_hexBlockToIP(t2csri_gameClientHexAddress(%client));
|
||||
if (!ipv4_reasonableConnection(%sourceIP, %sanityIP))
|
||||
{
|
||||
%client.setDisconnectReason("Potential man in the middle attack detected. Your client claims it connected to: " @ %sanityIP @ ", but the server does not consider this reasonable.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate a random 64-bit server side challenge
|
||||
rubyEval("tsEval '$temp=\"' + rand(18446744073709551615).to_s(16) + '\";'");
|
||||
%client.t2csri_serverChallenge = $temp @ t2csri_gameClientHexAddress(%client);
|
||||
|
||||
%fullChallenge = %client.t2csri_clientChallenge @ %client.t2csri_serverChallenge;
|
||||
rubyEval("tsEval '$temp=\"' + rsa_mod_exp('" @ %fullChallenge @ "'.to_i(16), '" @ %e @ "'.to_i(16), '" @ %n @ "'.to_i(16)).to_s(16) + '\";'");
|
||||
|
||||
// send the challenge in 200 byte chunks
|
||||
for (%i = 0; %i < strlen($temp); %i += 200)
|
||||
{
|
||||
commandToClient(%client, 't2csri_getChallengeChunk', getSubStr($temp, %i, 200));
|
||||
}
|
||||
// tell the client we're done sending
|
||||
commandToClient(%client, 't2csri_decryptChallenge');
|
||||
|
||||
// set up the "auth" info retrieved by cid.getAuthInfo()
|
||||
%client.t2csri_authinfo = %user @ "\t\t0\t" @ %guid @ "\n0\n";
|
||||
|
||||
// clan support: check supplemental time limited certificate, if it was sent
|
||||
%comCert = %client.t2csri_comCert;
|
||||
if (strLen(%comCert) > 0)
|
||||
{
|
||||
// assuming there is a comCert, and we aren't running in bare mode
|
||||
if (getField(%comCert, 3) $= %guid)
|
||||
{
|
||||
// GUID in the community cert matches that of the account cert
|
||||
%client.t2csri_authinfo = %client.t2csri_comInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
// uh oh... someone's being naughty.. valid cert, but for a different player. kill them!
|
||||
%client.setDisconnectReason("Community supplemental certificate doesn't match account certificate.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// verify the client's server challenge matches the one stored, if so, continue
|
||||
// loading sequence
|
||||
function serverCmdt2csri_challengeResponse(%client, %serverChallenge)
|
||||
{
|
||||
if (%client.doneAuthenticating)
|
||||
return;
|
||||
|
||||
if (%client.t2csri_serverChallenge $= %serverChallenge)
|
||||
{
|
||||
// check to see if the client is GUID banned, now that we verified their certificate
|
||||
if (banList_checkGUID(getField(%client.t2csri_authInfo, 3)))
|
||||
{
|
||||
%client.setDisconnectReason("You are not allowed to play on this server.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
// client checks out... continue loading sequence
|
||||
%client.onConnect(%client.tname, %client.trgen, %client.tskin, %client.tvoic, %client.tvopi);
|
||||
}
|
||||
else
|
||||
{
|
||||
%client.setDisconnectReason("Invalid server challenge. Check your account key for corruption.");
|
||||
%client.delete();
|
||||
}
|
||||
}
|
||||
|
||||
// delete a client if they spend more than 15 seconds authenticating
|
||||
function t2csri_expireClient(%client)
|
||||
{
|
||||
if (!isObject(%client))
|
||||
return;
|
||||
%client.setDisconnectReason("This is a TribesNext server. You must install the TribesNext client to play. See www.tribesnext.com for info.");
|
||||
%client.delete();
|
||||
}
|
||||
|
||||
package t2csri_server
|
||||
{
|
||||
// packaged to create the "pre-connection" authentication phase
|
||||
function GameConnection::onConnect(%client, %name, %raceGender, %skin, %voice, %voicePitch)
|
||||
{
|
||||
if (%client.t2csri_serverChallenge $= "")
|
||||
{
|
||||
// check to see if the client is IP banned
|
||||
if (banList_checkIP(%client))
|
||||
{
|
||||
%client.setDisconnectReason("You are not allowed to play on this server.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
//echo("Client connected. Initializing pre-connection authentication phase...");
|
||||
// save these for later
|
||||
%client.tname = %name;
|
||||
%client.trgen = %raceGender;
|
||||
%client.tskin = %skin;
|
||||
%client.tvoic = %voice;
|
||||
%client.tvopi = %voicePitch;
|
||||
|
||||
// start the 15 second count down
|
||||
%client.tterm = schedule(15000, 0, t2csri_expireClient, %client);
|
||||
|
||||
commandToClient(%client, 't2csri_pokeClient', "T2CSRI 1.1 - 03/18/2009");
|
||||
return;
|
||||
}
|
||||
//echo("Client completed pre-authentication phase.");
|
||||
|
||||
// continue connection process
|
||||
if (isEventPending(%client.tterm))
|
||||
cancel(%client.tterm);
|
||||
|
||||
Parent::onConnect(%client, %name, %raceGender, %skin, %voice, %voicePitch);
|
||||
%client.doneAuthenticating = 1;
|
||||
}
|
||||
|
||||
// packaged to prevent game leaving messages for clients that are in the authentication phase
|
||||
function GameConnection::onDrop(%client, %reason)
|
||||
{
|
||||
if (!isObject(%client) || !%client.doneAuthenticating)
|
||||
return;
|
||||
Parent::onDrop(%client, %reason);
|
||||
}
|
||||
|
||||
// packaged to pull info from the certificate, rather than some internal data structures
|
||||
// format is kept consistent though:
|
||||
// >Name ActiveClanTag Prepend(0)/Postpend(1)Tag guid
|
||||
// >NumberOfClans
|
||||
// >ClanName TagForClan Prepend(0)/Postpend(1)Tag clanid rank title
|
||||
|
||||
// in this version, there is no clan support, so those fields are empty
|
||||
// clan support will be implemented via delegation to a community server
|
||||
function GameConnection::getAuthInfo(%client)
|
||||
{
|
||||
if (%client.getAddress() $= "Local" && %client.t2csri_authInfo $= "")
|
||||
%client.t2csri_authInfo = WONGetAuthInfo();
|
||||
return %client.t2csri_authInfo;
|
||||
}
|
||||
|
||||
// deactivating old master list server protocol handlers in script
|
||||
// sending a game type list to a dedicated server would result in a massive number
|
||||
// of nuiscance calls to the following functions, and spam the console with pages of errors
|
||||
// the errors were the main source of CPU utilization, so just setting stubs is adequate protection
|
||||
function addGameType()
|
||||
{
|
||||
return;
|
||||
}
|
||||
function clearGameTypes()
|
||||
{
|
||||
return;
|
||||
}
|
||||
function clearMissionTypes()
|
||||
{
|
||||
return;
|
||||
}
|
||||
function sortGameAndMissionTypeLists()
|
||||
{
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if ($PlayingOnline)
|
||||
activatePackage(t2csri_server);
|
||||
204
t2csri/serverSideClans.cs
Normal file
204
t2csri/serverSideClans.cs
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
// Tribes 2 Unofficial Authentication System
|
||||
// http://www.tribesnext.com/
|
||||
// Written by Electricutioner/Thyth
|
||||
// Copyright 2008 by Electricutioner/Thyth and the Tribes 2 Community System Reengineering Intitiative
|
||||
|
||||
// Version 1.0: 2009-02-13
|
||||
|
||||
// A little bit of development theory:
|
||||
// -The Apotheosis DLL contains 3 RSA public keys. One for authentication, one for updates,
|
||||
// and one for delegation. The delegation key forms the root of the community system trust heirarchy.
|
||||
// -The delegated-community-enhancement server issues time limited community certificates, which
|
||||
// annotate the bare account certificates. The annotations include current name, current clan, current tag
|
||||
// and current clan membership so that getAuthInfo() provides all relevant information. These certificates
|
||||
// are time limited to enforce the "current" status of the annotations.
|
||||
// -Since game servers don't communicate with centralized systems (except for listing), the client is
|
||||
// responsible for providing a signed community certificate, and if prompted, the client is also
|
||||
// responsible for providing the authoratatively signed certificate from the relevant DCE. Thus, the
|
||||
// server will accumilate a small cache of valid DCE certificates.
|
||||
|
||||
// DCE certificate format:
|
||||
// DCEName DCENum IssuedEpoch ExpireEpoch 0 0 e n sig
|
||||
// The two zeros are reserved for future use.
|
||||
// Community certificate format:
|
||||
// DCENum IssuedEpoch ExpireEpoch IssuedForGUID HexBlob Sig
|
||||
// HexBlob format:
|
||||
// (Follows same format as contents returned by getAuthInfo, but is hex encoded.)
|
||||
|
||||
// verify with the delegation RSA public key... hard coded in the executable
|
||||
function t2csri_verify_deleg_signature(%sig)
|
||||
{
|
||||
%sig = strReplace(%sig, "\x27", "\\\x27");
|
||||
rubyEval("tsEval '$temp=\"' + t2csri_verify_deleg_signature('" @ %sig @ "').to_s(16) + '\";'");
|
||||
while (strLen($temp) < 40)
|
||||
$temp = "0" @ $temp;
|
||||
return $temp;
|
||||
}
|
||||
|
||||
// allow the client to send in an unknown DCE certificate
|
||||
function serverCmdt2csri_getDCEChunk(%client, %chunk)
|
||||
{
|
||||
// client can only send in one DCE
|
||||
if (%client.t2csri_sentDCEDone)
|
||||
return;
|
||||
|
||||
%client.t2csri_activeDCE = %client.t2csri_activeDCE @ %chunk;
|
||||
if (strlen(%client.t2csri_activeDCE) > 20000)
|
||||
{
|
||||
%client.setDisconnectReason("DCE certificate is too long.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// client finished sending their DCE. validate it
|
||||
function serverCmdt2csri_finishedDCE(%client)
|
||||
{
|
||||
if (%client.t2csri_sentDCEDone)
|
||||
return;
|
||||
|
||||
%dce = %client.t2csri_activeDCE;
|
||||
if (getFieldCount(%dce) != 9)
|
||||
{
|
||||
%client.setDisconnectReason("DCE certificate format is invalid.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
%dceName = getField(%dce, 0);
|
||||
%dceNum = getField(%dce, 1);
|
||||
%dceIssued = getField(%dce, 2);
|
||||
%dceExpire = getField(%dce, 3);
|
||||
%dceE = getField(%dce, 6);
|
||||
%dceN = getField(%dce, 7);
|
||||
|
||||
// check to see if we already have this certificate
|
||||
if ($T2CSRI::DCEE[%dceNum] !$= "")
|
||||
{
|
||||
// we already have the cert... set the client as done
|
||||
%client.t2csri_sentDCEDone = 1;
|
||||
%client.t2csri_activeDCE = "";
|
||||
return;
|
||||
}
|
||||
|
||||
%dceSig = getField(%dce, 8);
|
||||
%sigSha = t2csri_verify_deleg_signature(%dceSig);
|
||||
%sumStr = %dceName @ "\t" @ %dceNum @ "\t" @ %dceIssued @ "\t" @ %dceExpire @ "\t";
|
||||
%sumStr = %sumStr @ getField(%dce, 4) @ "\t" @ getField(%dce, 5) @ "\t" @ %dceE @ "\t" @ %dceN;
|
||||
%calcSha = sha1sum(%sumStr);
|
||||
|
||||
if (%sigSha !$= %calcSha)
|
||||
{
|
||||
%client.setDisconnectReason("DCE is not signed by authoritative root.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
// passed signature check... now check to see if it has expired/issued time has arrived
|
||||
%currentTime = currentEpochTime();
|
||||
if (%currentTime < %dceIssued || %currentTime > %dceExpire)
|
||||
{
|
||||
%client.setDisconnectReason("DCE is not valid for the current time period.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
// passed time check... enter it into global data structure
|
||||
$T2CSRI::DCEName[%dceNum] = %dceName;
|
||||
$T2CSRI::DCEE[%dceNum] = %dceE;
|
||||
$T2CSRI::DCEN[%dceNum] = %dceN;
|
||||
|
||||
// client has successfully sent a DCE
|
||||
%client.t2csri_sentDCEDone = 1;
|
||||
%client.t2csri_activeDCE = "";
|
||||
|
||||
// client was pending on a certificate signature check, do that now that we have the DCE cert
|
||||
if (%client.t2csri_pendingDCE)
|
||||
{
|
||||
%client.t2csri_pendingDCE = 0;
|
||||
serverCmdt2csri_comCertSendDone(%client);
|
||||
}
|
||||
}
|
||||
|
||||
// client sending community cert chunk
|
||||
function serverCmdt2csri_sendCommunityCertChunk(%client, %chunk)
|
||||
{
|
||||
// client can only send in one community cert
|
||||
if (%client.t2csri_sentComCertDone)
|
||||
return;
|
||||
|
||||
%client.t2csri_comCert = %client.t2csri_comCert @ %chunk;
|
||||
if (strlen(%client.t2csri_comCert) > 20000)
|
||||
{
|
||||
%client.setDisconnectReason("Community certificate is too long.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// client has sent in a full community certificate... validate and parse it
|
||||
function serverCmdt2csri_comCertSendDone(%client)
|
||||
{
|
||||
if (%client.t2csri_sentComCertDone)
|
||||
return;
|
||||
|
||||
%comCert = %client.t2csri_comCert;
|
||||
if (getFieldCount(%comCert) != 6)
|
||||
{
|
||||
%client.setDisconnectReason("Community certificate format is invalid.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
// parse
|
||||
%dceNum = getField(%comCert, 0);
|
||||
%issued = getField(%comCert, 1);
|
||||
%expire = getField(%comCert, 2);
|
||||
%guid = getField(%comCert, 3);
|
||||
%blob = getField(%comCert, 4);
|
||||
%sig = getField(%comCert, 5);
|
||||
%sumStr = getFieldS(%comCert, 0, 4);
|
||||
%calcSha = sha1Sum(%sumStr);
|
||||
|
||||
// find the correct DCE
|
||||
%e = $T2CSRI::DCEE[%dceNum];
|
||||
%n = $T2CSRI::DCEN[%dceNum];
|
||||
|
||||
// what if we don't have it? ask the client for a copy
|
||||
if (%e $= "")
|
||||
{
|
||||
%client.t2csri_pendingDCE = 1;
|
||||
commandToClient(%client, 't2csri_requestUnknownDCECert', %dceNum);
|
||||
return;
|
||||
}
|
||||
|
||||
// get the signature SHA1
|
||||
rubyEval("tsEval '$temp = \"' + rsa_mod_exp('" @ %sig @ "'.to_i(16), '" @ %e @ "'.to_i(16), '" @ %n @ "'.to_i(16)).to_s(16) + '\";'");
|
||||
while (strlen($temp) < 40)
|
||||
$temp = "0" @ $temp;
|
||||
%sigSha = $temp;
|
||||
|
||||
if (%sigSha !$= %calcSha)
|
||||
{
|
||||
%client.setDisconnectReason("Community cert is not signed by a known/valid DCE.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
// check expiration
|
||||
%currentTime = currentEpochTime();
|
||||
if (%currentTime > %expire)
|
||||
{
|
||||
%client.setDisconnectReason("Community cert has expired. Get a fresh one from the DCE.");
|
||||
%client.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
// valid cert... set the field for processing in the auth-phase code
|
||||
%len = strlen(%blob);
|
||||
for (%i = 0; %i < %len; %i += 2)
|
||||
{
|
||||
%decoded = %decoded @ collapseEscape("\\x" @ getSubStr(%blob, %i, 2));
|
||||
}
|
||||
%client.t2csri_comInfo = %decoded @ "\n";
|
||||
%client.t2csri_sentComCertDone = 1;
|
||||
}
|
||||
23
t2csri/serverglue.cs
Normal file
23
t2csri/serverglue.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Tribes 2 Unofficial Authentication System
|
||||
// http://www.tribesnext.com/
|
||||
// Written by Electricutioner/Thyth
|
||||
// Copyright 2008 by Electricutioner/Thyth and the Tribes 2 Community System Reengineering Intitiative
|
||||
|
||||
// Version 1.0 initialization and glue file (server side)
|
||||
|
||||
if (isObject(ServerGroup))
|
||||
{
|
||||
// load the Ruby utils and cryptography module
|
||||
exec("t2csri/rubyUtils.cs");
|
||||
rubyExec("t2csri/crypto.rb");
|
||||
|
||||
// load the torque script components
|
||||
exec("t2csri/serverSide.cs");
|
||||
exec("t2csri/serverSideClans.cs");
|
||||
exec("t2csri/bans.cs");
|
||||
exec("t2csri/ipv4.cs");
|
||||
exec("t2csri/base64.cs");
|
||||
|
||||
// get the global IP for sanity testing purposes
|
||||
schedule(32, 0, ipv4_getInetAddress);
|
||||
}
|
||||
Loading…
Reference in a new issue