commit 66f9b4d8a9b1ca562a286d983171ceeee57ce90b Author: Robert MacGregor Date: Fri Feb 27 21:56:30 2026 -0500 * Initial commit. diff --git a/base/loginScreens.cs b/base/loginScreens.cs new file mode 100755 index 0000000..73b88b2 --- /dev/null +++ b/base/loginScreens.cs @@ -0,0 +1,1140 @@ +// Tribes 2 Unofficial Authentication System +// http://www.tribesnext.com/ +// Written by Krash & Electricutioner/Thyth +// Copyright 2008-2009 by Electricutioner/Thyth, and the Tribes 2 Community System Reengineering Intitiative + +// Login UIs and Account processing jumble. + +$LastLoginKey = $pref::LastLoginKey; +exec("scripts/commonDialogs.cs"); +exec("gui/MessageBoxDlg.gui"); +exec("t2csri/glue.cs"); + + // Begin UI replacements: + new GuiBitmapCtrl(TN_logo) + { + profile = "GuiDefaultProfile"; + horizSizing = "center"; + vertSizing = "top"; + bitmap = "TN_logo"; + position = "0 20"; + extent = "640 105"; + visible = true; + minExtent = "8 8"; + helpTag = "0"; + }; + new GuiControlProfile(noMoreModal) + { + modal = false; + }; + new GuiControlProfile (ShellTextCenterProfile) + { + fontType = "Univers Condensed"; + fontSize = 18; + fontColor = "66 229 244"; + justify = "center"; + autoSizeWidth = false; + autoSizeHeight = true; + Modal = false; + }; + new GuiControlProfile (ShellTextLeftProfile) + { + fontType = "Univers Condensed"; + fontSize = 18; + fontColor = "66 229 244"; + justify = "left"; + autoSizeWidth = false; + autoSizeHeight = true; + Modal = false; + }; + + new GuiControl(CreateAccountDlg) { + profile = "GuiDefaultProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "0 0"; + extent = "640 480"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + open = "0"; + + new ShellPaneCtrl(TitleBar) { + profile = "ShellDlgPaneProfile"; + horizSizing = "center"; + vertSizing = "center"; + position = "70 36"; + extent = "500 408"; + minExtent = "48 92"; + visible = "1"; + helpTag = "0"; + text = "Create Account - Step 1 of 3"; + noTitleBar = "0"; + + + new GuiControlProfile ("BrowserH1Profile") + { + fontType = "Univers Condensed Bold"; + fontSize = 28; + fontColor = "66 219 234"; + autoSizeWidth = false; + autoSizeHeight = true; + bitmapBase = "gui/shll"; + }; + new GuiBitmapCtrl(tn_EntropyBox) { + profile = "GuiDefaultProfile"; + horizSizing = "center"; + vertSizing = "bottom"; + position = "37 84"; + extent = "440 188"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + + new GuiMouseEventCtrl(tn_EntropyEvent) + { + profile = "DefaultProfile"; + position = "0 0"; + extent = "440 188"; + vertSizing = "top"; + horizSizing = "left"; + visible = "true"; + }; + }; + + new GuiMLTextCtrl(AccountInstructions) { + profile = "BrowserH1Profile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "26 34"; + extent = "390 14"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + lineSpacing = "2"; + }; + new GuiMLTextCtrl(AccountText) { + profile = "ShellMessageTextProfile"; + horizSizing = "width"; + vertSizing = "height"; + position = "26 74"; + extent = "445 16"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + lineSpacing = "-2"; + }; + new GuiTextCtrl(CN_keyName) { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "35 128"; + extent = "100 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Key Details:"; + }; + new GuiMLTextCtrl(CA_keyName) { + profile = "ShellTextLeftProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "141 128"; + extent = "200 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = ""; + }; + new GuiTextCtrl(CN_userName) { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "35 174"; + extent = "100 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Account Name:"; + }; + new GuiTextCtrl(CN_chooPass) { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "35 220"; + extent = "100 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Password:"; + }; + new GuiTextCtrl(CN_confPass) { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "35 250"; + extent = "100 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Confirm Password:"; + }; + new ShellTextEditCtrl(CA_userName) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "131 166"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + variable = "$CreateAccountLoginName"; + command = "CA_userName.validateWarriorName();"; + IRCName = true; + helpTag = "0"; + historySize = "0"; + maxLength = "16"; + password = "0"; + glowOffset = "9 9"; + }; + new ShellTextEditCtrl(CA_chooPass) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "131 212"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + variable = "$CreateAccountPassword"; + helpTag = "0"; + historySize = "0"; + maxLength = "255"; + password = "1"; + glowOffset = "9 9"; + }; + new ShellTextEditCtrl(CA_confPass) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "131 242"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + variable = "$CreateAccountConfirmPassword"; + helpTag = "0"; + historySize = "0"; + maxLength = "255"; + password = "1"; + glowOffset = "9 9"; + }; + new ShellBitmapButton(CreateAccountPrevBtn) { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "72 351"; + extent = "128 38"; + minExtent = "32 38"; + visible = "1"; + command = "CreateAccountDlg.nextBtn(1);"; + accelerator = "escape"; + helpTag = "0"; + text = "CANCEL"; + simpleStyle = "0"; + }; + new GuiTextCtrl(CN_strength) { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "37 288"; + extent = "85 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Strength:"; + maxLength = "255"; + }; + new ShellPopupMenu(CA_strength) { + profile = "ShellPopupProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "118 280"; + extent = "140 38"; + minExtent = "32 38"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + text = "RSA-512"; + helpTag = "0"; + glowOffset = "9 9"; + maxLength = "255"; + longTextBuffer = "0"; + maxPopupHeight = "200"; + buttonBitmap = "gui/shll_pulldown"; + rolloverBarBitmap = "gui/shll_pulldownbar_rol"; + selectedBarBitmap = "gui/shll_pulldownbar_act"; + noButtonStyle = "0"; + }; + new ShellBitmapButton(CreateAccountGenBtn) { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "250 280"; + extent = "189 38"; + minExtent = "32 38"; + visible = "1"; + command = "CreateAccountDlg.genBtn();"; + helpTag = "1"; + text = "GENERATE YOUR KEY"; + simpleStyle = "0"; + }; + new GuiMLTextCtrl(HintText) { + profile = "ShellTextCenterProfile"; + horizSizing = "width"; + vertSizing = "height"; + position = "125 255"; + extent = "445 16"; + minExtent = "8 8"; + visible = "1"; + helpTag = "1"; + lineSpacing = "-2"; + }; + new GuiTextCtrl(HintText2) { + profile = "ShellTextCenterProfile"; + horizSizing = "center"; + vertSizing = "bottom"; + position = "0 315"; + extent = "445 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Click the above button to proceed."; + maxLength = "255"; + }; + new ShellBitmapButton(CreateAccountNextBtn) { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "282 351"; + extent = "128 38"; + minExtent = "32 38"; + visible = "1"; + command = "CreateAccountDlg.nextBtn();"; + helpTag = "0"; + text = "NEXT STEP"; + simpleStyle = "0"; + }; + }; + }; + + // Modified Login dlg + new GuiControl(LoginDlg) { + profile = "GuiDefaultProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "0 0"; + extent = "640 480"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + + new ShellPaneCtrl() { + profile = "ShellDlgPaneProfile"; + horizSizing = "center"; + vertSizing = "top"; + position = "72 143"; + extent = "495 194"; + minExtent = "48 92"; + visible = "1"; + helpTag = "0"; + text = "LOGIN"; + maxLength = "255"; + noTitleBar = "0"; + + new GuiTextCtrl(accnTxt) { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "37 77"; + extent = "85 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Account:"; + maxLength = "255"; + }; + new ShellPopupMenu(LoginEditMenu) { + profile = "ShellPopupProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "118 69"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + text = "Select Account"; + helpTag = "0"; + glowOffset = "9 9"; + maxLength = "255"; + longTextBuffer = "0"; + maxPopupHeight = "200"; + buttonBitmap = "gui/shll_pulldown"; + rolloverBarBitmap = "gui/shll_pulldownbar_rol"; + selectedBarBitmap = "gui/shll_pulldownbar_act"; + noButtonStyle = "0"; + }; + new ShellTextEditCtrl(LoginEditBox) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "118 99"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + variable = "$LoginName"; + altCommand = "newLoginProcess();"; + helpTag = "0"; + maxLength = "16"; + historySize = "0"; + password = "0"; + glowOffset = "9 9"; + }; + new GuiTextCtrl() { + profile = "ShellTextLeftProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "37 29"; + extent = "420 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Use the form below to log in with an existing key, retrieve a login key, or create"; + maxLength = "255"; + }; + new GuiTextCtrl() { + profile = "ShellTextLeftProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "37 45"; + extent = "420 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "a new account on the server."; + maxLength = "255"; + }; + new GuiTextCtrl(passTxt) { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "37 107"; + extent = "85 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Password:"; + maxLength = "255"; + }; + new GuiLoginPasswordCtrl(LoginPasswordBox) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "118 99"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + variable = "$LoginPassword"; + altCommand = "newLoginProcess();"; + helpTag = "0"; + maxLength = "255"; + historySize = "0"; + password = "1"; + glowOffset = "9 9"; + }; + new ShellToggleButton(rmbrPass) { + profile = "ShellRadioProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "122 134"; + extent = "167 27"; + minExtent = "26 27"; + visible = "1"; + variable = "$pref::RememberPassword"; + helpTag = "0"; + text = "REMEMBER PASSWORD"; + maxLength = "255"; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "300 69"; + extent = "147 38"; + minExtent = "32 38"; + visible = "1"; + command = "newLoginProcess();"; + helpTag = "0"; + text = "LOG IN"; + simpleStyle = "0"; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "300 99"; + extent = "147 38"; + minExtent = "32 38"; + visible = "1"; + command = "newCreateAccount();"; + helpTag = "0"; + text = "CREATE NEW ACCOUNT"; + simpleStyle = "0"; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "300 129"; + extent = "147 38"; + minExtent = "32 38"; + visible = "1"; + command = "quit();"; + accelerator = "escape"; + helpTag = "0"; + text = "QUIT"; + simpleStyle = "0"; + }; + }; + }; + + // Add these to the StartupGui to make sure everything gets cleaned up. + StartupGui.add(TN_logo); + StartupGui.add(ShellTextCenterProfile); + StartupGui.add(ShellTextLeftProfile); + StartupGui.add(noMoreModal); + // End UI replacements + + + +// Fill the drop-down list +function CA_strength::populate(%this) { + %this.add( "RSA-512", 0 ); + %this.add( "RSA-768", 1 ); + %this.add( "RSA-1024", 2 ); + %this.setSelected( 0 ); + %this.onSelect( 0, "RSA-512" ); +} +function CA_strength::onSelect( %this, %id, %text ) { + $keyStrength = %text; + %this.setText( %text ); + if (CreateAccountDlg.page != 2) return; + switch (%id) { + case 0: + %time = 2; + case 1: + %time = 3; + case 2: + %time = 7; + } + HintText2.setText("Your key could take up to "@%time@" minutes to create."); +} +function LoginEditMenu::populate(%this) { + %this.add( "Retrieve Account", 0 ); + + // LoginEditMenu.add( %name, %id ); + // Make sure to index keys to the number in the menu. 0 is used for key download. + // Use LoginEditMenu.size() for current length. + // + // When a new key is downloaded through t2csri_downloadAccount, try to have it use + // the setSelected/onSelect functions after adding to make the new field current and default + + // pull the list of accounts from the Ruby certificate store + $accountList = rubyEval("certstore_listAccounts", 1); + + %count = 0; + %accounts = getFieldCount($accountList); + for (%i = 0; %i < %accounts; %i++) + { + %this.add(getField($accountList, %i), %count++); + } + + if (%count < 1) %this.setActive(0); + %id = %this.findText( $LastLoginKey ); + if ( %id == -1 ) + %id = 0; + %text = %this.getTextById(%id); + %this.setSelected( %id ); + %this.onSelect( %id, %text ); + + // populate the game's alias selections for post-login + for (%i = 0; %i < %accounts; %i++) + { + %present = 0; + for (%j = 0; %j < $pref::Player::Count; %j++) + { + if (getField($pref::Player[%j], 0) $= getField($accountList, %i)) + %present = 1; + } + if (!%present) + { + $pref::Player[$pref::Player::Count] = getField($accountList, %i) @ "\tHuman Male\tbeagle\tMale1"; + $pref::Player::Count++; + } + } +} + +// Make sure everything is in the right place when an option is selected +function LoginEditMenu::onSelect( %this, %id, %text ) { + if (%id == 0) { + LoginPasswordBox.setPosition(118, 129); + passTxt.setPosition(37, 137); + accnTxt.setPosition(37, 107); + rmbrPass.setVisible(0); + LoginEditBox.setVisible(1); + } else { + LoginPasswordBox.setPosition(118, 99); + passTxt.setPosition(37, 107); + accnTxt.setPosition(37, 77); + rmbrPass.setVisible(1); + LoginEditBox.setVisible(0); + } + $LastLoginKey = %text; + %this.setText( %text ); +} +LoginEditMenu.populate(); +// Track the open state, and disable the next button unless ready to go. +function CreateAccountDlg::onWake( %this ) +{ + %this.open = true; + tn_EntropyBox.setBitmap("TN_entropy"); + CreateAccountDlg.bringToFront(tn_EntropyEvent); + // Check online status. + Authentication_checkAvail(); + // If it's online, set %this.online to true. + t2csri_checkOnlineStatusLoop(%this); +} +// this interfaces to the authentication interface script +function t2csri_checkOnlineStatusLoop(%this) +{ + // if no transaction to the authentication server is active... + if ($Authentication::Status::ActiveMode == 0) + { + %this.online = $Authentication::Status::Available; + CreateAccountNextBtn.setActive( false ); + updateNextButton(%this); + } + else + { + // otherwise, check again, as the transaction may still be in progress + schedule(128, 0, t2csri_checkOnlineStatusLoop, %this); + } +} + +function CreateAccountDlg::onSleep( %this ) +{ + %this.open = false; +} + +// All the account creation page junk is sent through here. +function CreateAccountDlg::nextBtn(%this,%reverse) { + CreateAccountNextBtn.setActive( false ); + %this.showFields[1] = false; + %this.showFields[2] = false; + %this.showFields[3] = false; + if(%reverse) %this.page--; + else %this.page++; + + %this.showFields[%this.page] = true; + switch (%this.page) { + case 1: + TitleBar.setText("Create Account - Step 1 of 3"); + CreateAccountPrevBtn.setValue(" CANCEL"); + CreateAccountNextBtn.setValue("NEXT STEP"); + %hintText = "Please wait as the server status is checked."; + HintText2.setText(%hintText); + %headtext = "Step One: Account Server Status"; + %body = "In order to create your account, the account server must be connectable. If it's offline, you won't be able to pass this step. \n\nIn step two, you will generate your unique key to ensure your account cannot be stolen.\n\nIn step three, you will choose your login information."; + AccountInstructions.setText(%headtext); + AccountText.setText(%body); + HintText.setVisible(1); + HintText.setPosition(125, 255); + HintText.setText(""); + + case 2: + TitleBar.setText("Create Account - Step 2 of 3"); + %headtext = "Step Two: "; + %body = ""; + AccountInstructions.setText(%headtext); + AccountText.setText(%body); + HintText.setVisible(1); + HintText.setPosition(120, 30); + HintText.setEntropyText(); + %keyText = $keyCreated ? "KEY GENERATED" : "GENERATE YOUR KEY"; + %active = (tn_EntropyEvent.finished && !$keyCreated) ? true : false; + if (!%active) HintText2.setText("Click the NEXT STEP button to proceed."); + CreateAccountGenBtn.setValue(%keyText); + CA_strength.setActive(%active); + CreateAccountGenBtn.setActive(%active); + CreateAccountPrevBtn.setValue("BACK"); + CreateAccountNextBtn.setValue("NEXT STEP"); + if (!CA_strength.size()) CA_strength.populate(); + logEntropy(); + + case 3: + TitleBar.setText("Create Account - Step 3 of 3"); + %headtext = "Step Three: Choose Your Account Details"; + %body = "Pick out your account details and confirm they are correct before registering your account. Don't forget your password."; + CA_keyName.setText("Strength: "@$keyStrength); + AccountInstructions.setText(%headtext); + AccountText.setText(%body); + HintText.setVisible(0); + CreateAccountGenBtn.setVisible(0); + CreateAccountPrevBtn.setValue("BACK"); + CreateAccountNextBtn.setValue("FINISH"); + HintText.setVisible(1); + HintText.setPosition(100, 290); + HintText.setText(""); + HintText2.setText("Fill out the above form to proceed."); + + + case 4: + // TODO: + // Send information to registration process: + // $CreateAccountLoginName + // $CreateAccountPassword + + LoginMessagePopup("PLEASE WAIT", "Registering Account with the Authentication Server..."); + t2csri_requestAccountSignature(%this); + + + default: + Canvas.popDialog( CreateAccountDlg ); + Canvas.pushDialog( LoginDlg ); + + } + CreateAccountGenBtn.setVisible(%this.showFields[2]); + CN_strength.setVisible(%this.showFields[2]); + CA_strength.setVisible(%this.showFields[2]); + tn_EntropyBox.setVisible(%this.showFields[2]); + CN_keyName.setVisible(%this.showFields[3]); + CA_keyName.setVisible(%this.showFields[3]); + CN_userName.setVisible(%this.showFields[3]); + CA_userName.setVisible(%this.showFields[3]); + CN_chooPass.setVisible(%this.showFields[3]); + CA_chooPass.setVisible(%this.showFields[3]); + CN_confPass.setVisible(%this.showFields[3]); + CA_confPass.setVisible(%this.showFields[3]); +} + +// ready to send the account to the server for processing, prepare it... +function t2csri_requestAccountSignature(%this) +{ + // pull the keys from the Ruby interpreter + $e = rubyEval("$accountKey.e.to_s(16)", 1); + $n = rubyEval("$accountKey.n.to_s(16)", 1); + $d = rubyEval("$accountKey.d.to_s(16)", 1); + + $encryptedExponent = t2csri_encryptAccountKey($d, $CreateAccountPassword); + %authSHA = sha1sum("3.14159265" @ trim(strlwr($CreateAccountLoginName)) @ $CreateAccountPassword); + %reqsig = $CreateAccountLoginName @ "\t" @ $e @ "\t" @ $n @ "\t" @ $encryptedExponent @ "\t" @ %authSHA; + + // delete the variables + $e = ""; + $d = ""; + $n = ""; + + // (RC2) perform a signature operation on the fields from the name to the end + %requestSHA1 = sha1sum(%reqsig); + $requestRSA = rubyEval("$accountKey.decrypt(" @ %requestSHA1 @ ").to_i(16).to_s(16)", 1); + %reqsig = %reqsig @ "\t" @ $requestRSA; + + //echo("Request: " @ %reqsig); + $Authentication::Status::LastCert = ""; + Authentication_registerAccount(%reqsig); + schedule(512, 0, t2csri_completeAccountRequest, %this); +} + +function t2csri_completeAccountRequest(%this) +{ + // if no transaction to the authentication server is active... + if ($Authentication::Status::ActiveMode == 0) + { + popLoginMessage(); + if (strLen($Authentication::Status::LastCert) > 0) + { + // success + LoginMessagePopup("SUCCESS", "Account generated successfully. Storing account data to disk and logging in..."); + schedule(3000, 0, popLoginMessage); + schedule(3000, 0, LoginDone); + + // store the account data to file + %username = getField($Authentication::Status::LastCert, 0); + rubyEval("certstore_addAccount('" @ $Authentication::Status::LastCert @ "','" @ %username @ "\t" @ $encryptedExponent @ "')"); + // protect the key... now that we have succeeded + $LoginCertificate = $Authentication::Status::LastCert; + rubyEval("$accountKey.protect"); + } + else + { + // handle the error + if ($Authentication::Status::Signature $= "Server chose to reject account generation request.") + { + LoginMessagePopup("ERROR", "The Authentication Server understood your request, but chose not to fulfill it."); + } + else if ($Authentication::Status::Signature $= "Server rejected account name.") + { + LoginMessagePopup("ERROR", "The Authentication Server rejected your requested account name."); + } + else if ($Authentication::Status::Signature $= "Corrupt signature request rejected.") + { + LoginMessagePopup("ERROR", "The server detected a problem in your request and could not create an account."); + } + else if ($Authentication::Status::Signature $= "Unknown signature status code returned from server.") + { + LoginMessagePopup("ERROR", "The Authentication Server timed out while fulfilling your request."); + } + // go back to the account page + %this.nextBtn(1); + // schedule a "pop" of the error box we just put up + schedule(7000, 0, popLoginMessage); + } + } + else + { + // otherwise, check again, as the transaction may still be in progress + schedule(128, 0, t2csri_completeAccountRequest, %this); + } +} + +function HintText::setEntropyText( %this ) +{ + if (tn_EntropyEvent.finished) + { + %lines = "1. Select your key strength.\n2. Click the generate button."; + CreateAccountGenBtn.setActive(1); + CA_strength.setActive(1); + + } + else + { + if(tn_EntropyEvent.time $= "") + tn_EntropyEvent.time = 80; + %lines = (tn_EntropyEvent.hasMouse ? "":"") @ "1. Move your mouse inside the big box."; + %lines = %lines NL "2. Wiggle it around for "@mCeil(tn_EntropyEvent.time / 8)@" more seconds."; + } + HintText.setText(%lines); +} +function tn_EntropyEvent::onMouseEnter(%this, %mod, %pos, %count) +{ + if (tn_EntropyEvent.finished) + return; + tn_EntropyEvent.hasMouse = true; + HintText.setEntropyText(); +} +function tn_EntropyEvent::onMouseLeave(%this, %mod, %pos, %count) +{ + if (tn_EntropyEvent.finished) + return; + tn_EntropyEvent.hasMouse = false; + HintText.setEntropyText(); +} +function logEntropy() +{ + if (tn_EntropyEvent.finished) + return; + + // Ruby Invocation Happens Here... + // first call of this function... build the Mersenne Twister RNG in Ruby + if (!$rubyRNGCreated) + { + $rubyRNGCreated = 1; + rubyEval("$twister = MersenneTwister.new"); + rubyEval("$entropy = 0"); + } + + if ( CreateAccountDlg.page != 2 || !CreateAccountDlg.open ) + return; + if ( tn_EntropyEvent.lastPos $= canvas.getCursorPos() ) + { + schedule(128, 0, logEntropy); + return; + } + if ( tn_EntropyEvent.hasMouse ) + { + tn_EntropyEvent.lastPos = canvas.getCursorPos(); + tn_EntropyEvent.time--; + if (strstr( tn_EntropyEvent.time, 0) != -1) + { + %pos = canvas.getCursorPos(); + %bit = new GuiBitmapCtrl() { + profile = "noMoreModal"; + bitmap = "texticons/bullet_2"; + extent = "19 18"; + visible = true; + opacity = "0.25"; + minExtent = "19 18"; + helpTag = "0"; + wrap = true; + }; + tn_EntropyBox.add(%bit); + %bit.setPosition(getWord(%pos,0)-365,getWord(%pos,1)-320); + } + %entropy = strreplace(canvas.getCursorPos()," ",""); + // Ruby Invocation Happens Here... + // add the current screen coordinate to the entropy pool + rubyEval("$entropy = $entropy + " @ %entropy); + if ( tn_EntropyEvent.time == 0 ) + { + rubyEval("$entropy = $entropy + " @ getRealTime()); + //rubyEval("puts $entropy % 4294967296"); + rubyEval("$twister.seedgen($entropy % 4294967296)"); + tn_EntropyEvent.finished = true; + beginEntropyWait(); + } + else + schedule(128,0, logEntropy); + HintText.setEntropyText(); + } + else + schedule(128,0, logEntropy); +} + +// churn the RNG state for additional entropy +function beginEntropyWait() +{ + if (CreateAccountDlg.page != 2 || $keyCreated) + return; + if (isEventPending($entropyWait)) + { + cancel($entropyWait); + } + $entropyWait = schedule(256, 0, beginEntropyWait); + + rubyEval("$twister.randomnumber(160)"); +} + +// Warrior name check. Useful to keep entry valid. +function CA_userName::validateWarriorName( %this ) +{ + %name = %this.getValue(); + %test = strToPlayerName( %name ); + if ( %name !$= %test ) + %this.setText( %test ); +} + +// If the options aren't in, disable the button. +function updateNextButton() +{ + if ( !CreateAccountDlg.open ) + return; + + %done = true; + switch (CreateAccountDlg.page) + { + case 1: + if (!$RubyEnabled) + { + HintText.setText("Your game is not running the patched executable."); + HintText2.setText("Close the game and verify it is patched."); + %done = false; + } + else if ($AuthServer::Address $= "") + { + HintText.setText("The server address has not yet been retrieved."); + HintText2.setText("Close this page and try again in a moment."); + authConnect_findAuthServer(); + %done = false; + } + else if (!CreateAccountDlg.online) + { + if (CreateAccountDlg.online !$= "") + { + HintText.setText("The account server is OFFLINE or unreachable."); + HintText2.setText("Check your network connection and try again."); + } + %done = false; + } + else + { + HintText.setText("The account server is ONLINE and connectable."); + HintText2.setText("Click the NEXT STEP button to proceed."); + } + + case 2: + if (!$keyCreated) %done = false; + + case 3: + if (strlen($CreateAccountLoginName) < 4) + { + %done = false; + if (strlen($CreateAccountLoginName) > 0) + HintText.setText("Error: Your username must be at least 4 characters long."); + else + HintText.setText(""); + } + else if (strlen($CreateAccountPassword) < 6) + { + %done = false; + if (strlen($CreateAccountPassword) > 0) + HintText.setText("Error: Your password must be at least 6 characters long."); + else + HintText.setText(""); + } + else if (strcmp($CreateAccountPassword, $CreateAccountConfirmPassword)) + { + %done = false; + if (strlen($CreateAccountConfirmPassword) > 0) + HintText.setText("Error: Your password confirmation doesn't match."); + else + HintText.setText(""); + } + else + { + if ($CreateAccountLastEnteredUsername !$= $CreateAccountLoginName) + { + // client has typed in a new name... test suitability with the auth server + $CreateAccountLastEnteredUsername = $CreateAccountLoginName; + $Authentication::Status::Name = ""; + $NameSuitabilityMode = 1; + Authentication_checkName($CreateAccountLoginName); + t2csri_testNameSuitability(); + } + if ($NameSuitabilityMode) + { + HintText.setText(""); + %done = false; + } + if ($Authentication::Status::Name !$= "Name is available and acceptable.") + { + %status = ($Authentication::Status::Name $= "") ? "Checking name for availability..." : "Error:" SPC $Authentication::Status::Name; + HintText.setText(%status); + %done = false; + } + } + + } + CreateAccountNextBtn.setActive( %done ); + + schedule( 1000, 0, updateNextButton ); +} + +function t2csri_testNameSuitability() +{ + // if no transaction to the authentication server is active... + if ($Authentication::Status::ActiveMode == 0) + { + if ($Authentication::Status::Name !$= "Name is available and acceptable.") + %status = "Error: "; + else + %status = "Success: "; + HintText.setText(%status @ $Authentication::Status::Name); + $NameSuitabilityMode = 0; + } + else + { + // otherwise, check again, as the transaction may still be in progress + schedule(128, 0, t2csri_testNameSuitability); + } +} + +function CreateAccountDlg::genBtn(%this) +{ + LoginMessagePopup( "Creating your key...", "This can take a few minutes.\nDO NOT EXIT THE GAME\n" ); + schedule( 2000, 0, popLoginMessage ); + // Ruby Invocation Happens Here... + // Pass this through to the key generation function. + // $keyStrength + $keyStrength = getSubStr($keyStrength, 4, strlen($keyStrength)); + rubyEval("$accountKey = RSAKey.new"); + rubyEval("$accountKey.twister = $twister"); + cancel($entropyWait); + schedule(1024, 0, rubyEval, "$accountKey.generate(" @ $keyStrength @ ")"); + + // When done, have the following set: + $keyCreated = true; + CA_strength.setActive(!$keyCreated); + CreateAccountGenBtn.setActive(!$keyCreated); + CreateAccountGenBtn.setValue("KEY GENERATED"); + HintText2.setText("Click the NEXT STEP button to proceed."); + CreateAccountNextBtn.setActive(1); +} +function popLoginMessage() +{ + Canvas.popDialog( LoginMessagePopupDlg ); +} +function newCreateAccount() +{ + $CreateAccountLoginName = ""; + $CreateAccountPassword = ""; + $CreateAccountConfirmPassword = ""; + Canvas.pushDialog( CreateAccountDlg ); + Canvas.popDialog( LoginDlg ); + CreateAccountDlg.page = 0; + CreateAccountDlg.nextBtn(); +} +function newLoginProcess() +{ + if (!$RubyEnabled) + { + MessageBoxOK("LOGIN ERROR","Your game is not running the patched game executable.\n\nClose the game and verify the patch was run successfully."); + return; + } + if (LoginEditMenu.getSelected() == 0) + { + if ( strlen( $LoginName ) < 3 ) + return; + else + { + if ( LoginEditMenu.findText( $LoginName ) == -1 ) + MessageBoxYesNo("Connect Account","That account isn't stored locally, would you like to retrieve it from the account server?","t2csri_downloadAccount($LoginName, $LoginPassword);",""); + else + { + LoginMessagePopup( "PLEASE WAIT", "Logging in..." ); + schedule(128, 0, t2csri_doLogin, $LoginName, $LoginPassword); + } + } + } + else + { + if ( $pref::RememberPassword ) + LoginPasswordBox.savePassword(); + LoginMessagePopup( "PLEASE WAIT", "Logging in..." ); + schedule(128, 0, t2csri_doLogin, $LastLoginKey, $LoginPassword); + } +} + + +function t2csri_doLogin(%username, %password) +{ + //warn(%username SPC %password); + %status = t2csri_getAccount(%username, %password); + warn(%status); + if (%status $= "SUCCESS") + { + // continue login + $pref::LastLoginKey = $LastLoginKey; + export( "$pref::*", "prefs/ClientPrefs.cs", false); + Canvas.popDialog(LoginDlg); + schedule(128, 0, popLoginMessage); + schedule(128, 0, LoginDone); + + // set the active "alias" to the current username + for (%i = 0; %i < $pref::Player::Count; %i++) + { + if (getField($pref::Player[%i], 0) $= trim(%username)) + $pref::Player::Current = %i; + } + } + else if (%status $= "INVALID_PASSWORD") + { + // pop-up a dialog asking the player to try again + popLoginMessage(); + LoginMessagePopup( "INVALID PASSWORD", "The password you entered is not correct. Try again." ); + schedule(3000, 0, popLoginMessage); + } + else + { + popLoginMessage(); + LoginMessagePopup( "ERROR", "An unknown error occured. Status code: " @ %status); + schedule(3000, 0, popLoginMessage); + } +} diff --git a/base/scripts/TR2BonusHud.cs b/base/scripts/TR2BonusHud.cs new file mode 100755 index 0000000..fa5f4c3 --- /dev/null +++ b/base/scripts/TR2BonusHud.cs @@ -0,0 +1,168 @@ +new GuiControlProfile ("TR2BonusBigText") +{ + fontType = "Verdana"; + fontSize = 10; + fontColor = "255 255 51"; +}; + +new GuiControlProfile ("TR2BonusHUGEText") +{ + fontType = "Verdana Bold"; + fontSize = 36; + fontColor = "255 255 51"; +}; + +new GuiControlProfile ("TR2BonusPopupProfile") +{ + bitmapbase = "gui/hud_new_window"; +}; + +function createBonusHud() +{ + if( isObject(TR2BonusHud) ) + TR2BonusHud.delete(); + + new ShellFieldCtrl(TR2BonusHud) { + profile = "TR2BonusPopupProfile"; + horizSizing = "center"; + vertSizing = "bottom"; + position = "0 0"; + extent = "77 52"; + minExtent = "70 48"; + visible = "0"; + helpTag = "0"; + }; + + %profile[1] = "TR2BonusBigText"; + %profile[2] = "TR2BonusHUGEText"; + %sizing[1] = "bottom"; + %sizing[2] = "top"; + %pos[1] = 3; + %pos[2] = 17; + %size[1] = "77 22"; + %size[2] = "77 50"; + for( %i = 1; %i <= 2; %i++ ) + { + $TR2BonusText[%i] = new GuiMLTextCtrl() + { + profile = %profile[%i]; + horizSizing = "center"; + vertSizing = %sizing[%i]; + position = "0 " @ %pos[%i]; + extent = %size[%i]; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + lineSpacing = "2"; + allowColorChars = "0"; + maxChars = "256"; + }; + + TR2BonusHud.add($TR2BonusText[%i]); + } + $TR2BonusText[1].setText("JACKPOT"); +} + +function TR2BonusHud::dockToChat(%this) +{ + %x = getWord(outerChatHud.extent, 2); + %y = getWord(outerChatHud.position, 1); + %this.setPosition(%x, %y); +} + +function TR2BonusObject::flashText(%this, %count, %delay) +{ + for( %i = 0; %i < %count; %i++ ) + { + %isNewBonus = %i % 2; + %text = %isNewBonus ? $TR2Bonus::NewBonus : $TR2Bonus::OldBonus; + %extraDelay = %isNewBonus ? 0 : 250; + $TR2BonusText[2].schedule(%delay * %i + %extraDelay, "setText", %text); + } + $TR2BonusText[2].schedule(%delay * %count, "setText", $TR2Bonus::NewBonus); +} + +function TR2BonusObject::doBonus(%this, %text, %color) +{ + if( %color $= "" ) + %color = "ffffff"; + TR2BonusHud.setVisible(1); + $TR2Bonus::OldBonus = "" @ %this.currentBonus; + + $TR2Bonus::NewBonus = "" @ %text; + + // No flash if old score == new score + if( %text !$= %this.currentBonus || (%text $= %this.currentBonus && %color !$= %this.currentColor) ) + %this.flashText(5, 500); + + %this.currentBonus = %text; + %this.currentColor = %color; +} + +function handleNewBonus(%msgType, %msgString, %text, %color) +{ + if( $TR2Bonus::Gametype $= "TR2Game") + TR2BonusObject.doBonus(%text, %color); +} + +function updateBonusTitle(%msgType, %msgString, %newTitle) +{ + $TR2BonusText[1].setText(%newTitle); +} + +function captureGameType(%msgType, %msgString, %game) +{ + $TR2Bonus::Gametype = detag(%game); + if( detag(%game) $= "TR2Game" ) + TR2BonusHud.setVisible(1); + else + TR2BonusHud.setVisible(0); +} + +function setBonusHudState(%msgType, %msgString, %a, %b, %c) +{ + if( $TR2Bonus::Gametype $= "TR2Game" ) + TR2BonusHud.setVisible(1); + else + TR2BonusHud.setVisible(0); +} + +createBonusHud(); +PlayGui.add(TR2BonusHud); +$TR2BonusText[2].setText("0"); +if( !isObject(TR2BonusObject) ) +{ + new ScriptObject(TR2BonusObject) + { + class = TR2BonusObject; + currentBonus = 0; + }; +} + +//package TR2BonusHud +//{ +//function PlayGui::onWake(%this) +//{ +// if( $TRBonus::Gametype $= "TR2Game" ) +// TR2BonusHud.setVisible(1); +// else +// TR2BonusHud.setVisible(0); +// +// parent::onWake(%this); +//} +// +//function PlayGui::onSleep(%this) +//{ +// TR2BonusHud.setVisible(0); +// parent::onSleep(%this); +//} +//}; +//activatePackage(TR2BonusHud); + +addMessageCallback('MsgTR2Bonus', handleNewBonus); + + +addMessageCallback('MsgTR2BonusTitle', updateBonusTitle); +addMessageCallback('MsgClientReady', captureGameType); +addMessageCallback('MsgMissionStart', setBonusHudState); +addMessageCallback('MsgMissionEnd', setBonusHudState); diff --git a/base/scripts/autoexec/Strcmp.cs b/base/scripts/autoexec/Strcmp.cs new file mode 100755 index 0000000..6b4d3fd --- /dev/null +++ b/base/scripts/autoexec/Strcmp.cs @@ -0,0 +1,97 @@ +// For T2 Linux: Patch for "broken" strcmp function. +// In Windows, that function delivers a direction and magnitude. In Linux, that function delivers a direction only. + +// Patch by Electricutioner + +package strCmpPatch +{ + function strcmp(%a, %b) + { + %posA = 0; + if (%a !$= "") + %posA = strPos($Patch::Strcmp::ASCIIStream, %a) + 1; + %posB = 0; + if (%b !$= "") + %posB = strPos($Patch::Strcmp::ASCIIStream, %b) + 1; + return (%posA - %posB); + } + + function generateASCIIStream() + { + for (%i = 1; %i < 255; %i++) + { + $Patch::Strcmp::ASCIIStream = $Patch::Strcmp::ASCIIStream @ collapseEscape("\\x" @ DecToHex(%i)); + } + } +}; + +// Bone functions +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; +} +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; + } + + //special purpose modification: + //pad out the hex output to lengh that is divisible by 2 and is not zero + while (strLen(%hex) == 0 || strLen(%hex) % 2 != 0) + %hex = "0" @ %hex; + return %hex; +} + +if ($platform $= "Linux") +{ + activatePackage(strCmpPatch); + generateASCIIStream(); +} + diff --git a/base/scripts/autoexec/t2csri_IRCfix.cs b/base/scripts/autoexec/t2csri_IRCfix.cs new file mode 100755 index 0000000..7041553 --- /dev/null +++ b/base/scripts/autoexec/t2csri_IRCfix.cs @@ -0,0 +1,190 @@ +$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 IRCClient::onVersion(%prefix,%params) +{ + nextToken(%prefix,prefix,"!"); + parent::onVersion(%prefix,%params); +} +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); diff --git a/base/scripts/autoexec/t2csri_list.cs b/base/scripts/autoexec/t2csri_list.cs new file mode 100755 index 0000000..2745b54 --- /dev/null +++ b/base/scripts/autoexec/t2csri_list.cs @@ -0,0 +1,456 @@ +// 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 TNbite::get(%this, %server, %query) +{ + %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 NewsGui::addLine( %this, %line ) { + %this = NewsText; + if (firstWord(%line) $= "") { + %line = setWord(%line,0,""); + 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 CheckEmail( %bool ) { + if ($LaunchMode $= "Normal") return; // Do nothing for now + parent::CheckEmail( %bool ); +} + +function LaunchTabView::addLaunchTab( %this, %text, %gui, %makeInactive ) { + // disable currently unused tabs + if (%text $= "EMAIL" || %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)) 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); diff --git a/base/scripts/autoexec/t2csri_serv.cs b/base/scripts/autoexec/t2csri_serv.cs new file mode 100755 index 0000000..4caa3f4 --- /dev/null +++ b/base/scripts/autoexec/t2csri_serv.cs @@ -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"); \ No newline at end of file diff --git a/base/t2csri/authconnect.cs b/base/t2csri/authconnect.cs new file mode 100755 index 0000000..5525306 --- /dev/null +++ b/base/t2csri/authconnect.cs @@ -0,0 +1,90 @@ +// 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 (strcmp(%sha1sum, %verifSum) != 0) + { + // 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) +{ + $temp = rubyEval("$temp = t2csri_verify_auth_signature('" @ %sig @ "').to_s(16)"); + $temp = rubyGetValue("$temp", 40); + return $temp; +} diff --git a/base/t2csri/authinterface.cs b/base/t2csri/authinterface.cs new file mode 100755 index 0000000..8d456a0 --- /dev/null +++ b/base/t2csri/authinterface.cs @@ -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); +} \ No newline at end of file diff --git a/base/t2csri/autoupdate.cs b/base/t2csri/autoupdate.cs new file mode 100755 index 0000000..fcd2e1c --- /dev/null +++ b/base/t2csri/autoupdate.cs @@ -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) +{ + $temp = rubyEval("t2csri_verify_update_signature('" @ %sig @ "')", 1); + 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"); +} diff --git a/base/t2csri/bans.cs b/base/t2csri/bans.cs new file mode 100755 index 0000000..3852294 --- /dev/null +++ b/base/t2csri/bans.cs @@ -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() +{ + $temp = rubyEval("Time.now.to_i.to_s", 1); + return $temp; +} + +// compute the addition in Ruby, due to the Torque script precision problems for >1e6 values +function getEpochOffset(%seconds) +{ + $temp = rubyEval("Time.now.to_i + " @ %seconds @ ").to_s", 1); + 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 + $temp = rubyEval("(" @ %time @ " - Time.now.to_i).to_s", 1); + %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 + $temp = rubyEval("(" @ %time @ " - Time.now.to_i).to_s", 1); + %delta = $temp; + + if (%delta > 0) + return 1; + else + deleteVariables("$BanList::GUID" @ %guid); + } + return 0; +} diff --git a/base/t2csri/base64.cs b/base/t2csri/base64.cs new file mode 100755 index 0000000..d4f86b7 --- /dev/null +++ b/base/t2csri/base64.cs @@ -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(); \ No newline at end of file diff --git a/base/t2csri/clientSide.cs b/base/t2csri/clientSide.cs new file mode 100755 index 0000000..8a3ee90 --- /dev/null +++ b/base/t2csri/clientSide.cs @@ -0,0 +1,387 @@ +// 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 @ "')"); + $temp = rubyGetValue("$sha1hasher.hexdigest", 40); + %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) @ "']", 255); +} + +// 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) @ "']", 1240); + } + } + else + { + %value = rubyGetValue("$accCerts['" @ strlwr(%name) @ "']", 1240); + } + 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 (strcmp(%hash, %nonce) == 0 || %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 (strcmp(%hash, %nonce) == 0) + 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 (strCmp(%hash, %nonce) == 0) + 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 + $temp = rubyEval("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(); + LoginMessagePopup("ERROR", "Credential download failed. Check your username/password."); + 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 + $loginChallenge = rubyEval("rand(18446744073709551615).to_s(16)"); + // append what the client thinks the server IP address is, for anti-replay purposes + $loginchallenge = $loginchallenge @ t2csri_gameServerHexAddress(); + + 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; + } + } + + $decryptedChallenge = rubyEval("$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 +} diff --git a/base/t2csri/clientSideClans.cs b/base/t2csri/clientSideClans.cs new file mode 100755 index 0000000..26a90e9 --- /dev/null +++ b/base/t2csri/clientSideClans.cs @@ -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'); +} diff --git a/base/t2csri/console_start.cs b/base/t2csri/console_start.cs new file mode 100755 index 0000000..b991204 --- /dev/null +++ b/base/t2csri/console_start.cs @@ -0,0 +1,2535 @@ +//-------------------------------------------------------------------------- +// +// Tribes 2 startup +// +//-------------------------------------------------------------------------- + +enableWinConsole(true); + +// z0dd - ZOD - Founder (founder@mechina.com), 10/23/02. Fixes bug where by +// parent functions are lost when packages are deactivated. +package PackageFix +{ + function isActivePackage(%package) + { + for(%i = 0; %i < $TotalNumberOfPackages; %i++) + { + if($Package[%i] $= %package) + { + return true; + break; + } + } + return false; + } + + function ActivatePackage(%this) + { + Parent::ActivatePackage(%this); + if($TotalNumberOfPackages $= "") + $TotalNumberOfPackages = 0; + else + { + // This package name is allready active, so lets not activate it again. + if(isActivePackage(%this)) + { + error("ActivatePackage called for a currently active package!"); + return; + } + } + $Package[$TotalNumberOfPackages] = %this; + $TotalNumberOfPackages++; + } + + function DeactivatePackage(%this) + { + %count = 0; + %counter = 0; + //find the index number of the package to deactivate + for(%i = 0; %i < $TotalNumberOfPackages; %i++) + { + if($Package[%i] $= %this) + %breakpoint = %i; + } + for(%j = 0; %j < $TotalNumberOfPackages; %j++) + { + if(%j < %breakpoint) + { + //go ahead and assign temp array, save code + %tempPackage[%count] = $Package[%j]; + %count++; + } + else if(%j > %breakpoint) + { + %reactivate[%counter] = $Package[%j]; + $Package[%j] = ""; + %counter++; + } + } + //deactivate all the packagess from the last to the current one + for(%k = (%counter - 1); %k > -1; %k--) + Parent::DeactivatePackage(%reactivate[%k]); + + //deactivate the package that started all this + Parent::DeactivatePackage(%this); + + //don't forget this + $TotalNumberOfPackages = %breakpoint; + + //reactivate all those other packages + for(%l = 0; %l < %counter; %l++) + ActivatePackage(%reactivate[%l]); + } + + function listPackages() + { + echo("Activated Packages:"); + for(%i = 0; %i < $TotalNumberOfPackages; %i++) + echo($Package[%i]); + } +}; +activatePackage(PackageFix); +// End z0dd - ZOD - Founder +//-------------------------------------------------------------------------- + +//-------------------------------------------------------------------------- + +//-------------------------------------------------------------------------- +// parse arguments: + +$TestObjectFileName = ""; +$LaunchMode = "Normal"; +$Login = false; +$clientprefs = "prefs/clientPrefs.cs"; +$serverprefs = "prefs/serverPrefs.cs"; +$fromLauncher = false; +$PureServer = true; + +//------------------------------------------------------------------------------ +function prepBuild() +{ + // this compiles all the scripts and guis + for(%file = findFirstFile("*.cs"); %file !$= ""; %file = findNextFile("*.cs")) + compile(%file); + for(%file = findFirstFile("*.gui"); %file !$= ""; %file = findNextFile("*.gui")) + compile(%file); +} + +//------------------------------------------------------------------------------ +function cleanupAudio() +{ + alxStopAll(); + AudioGui.delete(); + sButtonDown.delete(); + sButtonOver.delete(); + InputDeniedSound.delete(); +} + +function startAudio() +{ + //-------------------------------------- + // temp Audio Profiles + new AudioDescription(AudioGui) + { + volume = 1.0; + isLooping= false; + is3D = false; + type = $GuiAudioType; + }; + + new AudioProfile(sButtonDown) + { + filename = "gui/buttonDown.wav"; + description = "audioGui"; + preload = true; + }; + + new AudioProfile(sButtonOver) + { + filename = "gui/buttonOver.wav"; + description = "audioGui"; + preload = true; + }; + + new AudioProfile(InputDeniedSound) + { + filename = "fx/misc/diagnostic_beep.wav"; + description = "audioGui"; + preload = true; + }; + + $Audio::defaultDriver = "miles"; + audioDetect(); + if(!$noloadAudio) + { + // make sure there is a driver list + if($pref::Audio::drivers $= "") + $pref::Audio::drivers = $Audio::defaultDriver; + + // make sure that there is an active driver + if($pref::Audio::activeDriver $= "") + $pref::Audio::activeDriver = $Audio::defaultDriver; + + // install the active driver + $Audio::initialized = audioSetDriver($pref::Audio::activeDriver); + if(!$Audio::initialized) + { + error("Audio: failed to initialize using [" @ $pref::Audio::activeDriver @ "] driver"); + + // if the driver was not the default then attempt to load the default + if($pref::Audio::activeDriver !$= $Audio::defaultDriver) + { + error("Audio: attempting to initialize [" @ $Audio::defaultDriver @ "]"); + $Audio::initialized = audioSetDriver($Audio::defaultDriver); + if($Audio::initialized) + $pref::Audio::activeDriver = $Audio::defaultDriver; + } + } + + // just set the volumes that are needed now + if($Audio::initialized) + { + alxListenerf( AL_GAIN_LINEAR, $pref::Audio::masterVolume ); + alxSetChannelVolume( $GuiAudioType, $pref::Audio::guiVolume ); + } + } +} + +//------------------------------------------------------------------------------ +function repaintCanvas() +{ + if ( isObject( Canvas ) ) + Canvas.repaint(); +} + +function resetCanvas() +{ + if ( isObject( Canvas ) ) + Canvas.reset(); +} + +//------------------------------------------------------------------------------ +if ( isDemo() ) +{ + $SkipLogin = true; + $LaunchMode = "Offline"; + + new GuiChunkedBitmapCtrl(DemoSplashGui) { + profile = "GuiContentProfile"; + bitmap = "gui/bg_DemoSplash.png"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "0 0"; + extent = "640 480"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + useVariable = "0"; + hideCursor = "1"; + qLineCount = "0"; + }; + + $DemoMasterAddress = "IP:64.94.105.141:27999"; + $ShellBackground = "gui/bg_Demo.png"; +} +else +{ + for($i = 1; $i < $Game::argc ; $i++) + { + $arg = $Game::argv[$i]; + $nextArg = $Game::argv[$i+1]; + $nextArg2 = $Game::argv[$i+2]; + $hasNextArg = $Game::argc - $i > 1; + $has2NextArgs = $Game::argc - $i > 2; + + if (!stricmp(fileExt($arg), ".dif")) + { + $LaunchMode = "InteriorView"; + //$SkipLogin = true; + $TestObjectFileName = $arg; + echo($TestObjectFileName); + } + else if(!stricmp(fileExt($arg), ".dif\"")) + { + $LaunchMode = "InteriorView"; + //$SkipLogin = true; + $TestObjectFileName = getSubStr($arg,1, strlen($arg) - 2); + } + else if ( $arg $= "-mod" && $hasNextArg ) + { + setModPaths( $nextArg ); + $i += 2; + $PureServer = false; + } + else if($arg $= "-dedicated") + { + $LaunchMode = "DedicatedServer"; + } + else if($arg $= "-nonpure") + { + $PureServer = false; + } + else if($arg $= "-clientprefs" && $hasNextArg) + { + $i++; + $clientprefs = $nextArg; + } + else if($arg $= "-serverprefs" && $hasNextArg) + { + $i++; + $serverprefs = $nextArg; + } + else if($arg $= "-host") + { + $LaunchMode = "HostGame"; + } + else if($arg $= "-mission" && $has2NextArgs) + { + $i += 2; + $mission = $nextArg; + $missionType = $nextArg2; + } + else if($arg $= "-telnetParams" && $has2NextArgs) + { + $i += 3; + $telnetPort = $nextArg; + $telnetPassword = $nextArg2; + $telnetListenPass = $nextArg3; + telnetSetParameters($telnetPort, $telnetPassword, $telnetListenPass); + } + else if($arg $= "-connect" && $hasNextArg) + { + $i++; + $LaunchMode = "Connect"; + $JoinGameAddress = $nextArg; + } + else if($arg $= "-password" && $hasNextArg) + { + $i++; + $JoinGamePassword = $nextArg; + } + else if($arg $= "-jload" && $hasNextArg) + { + $i++; + $JournalFile = $nextArg; + $JournalMode = "LoadJournal"; + $PureServer = false; + } + else if($arg $= "-jsave" && $hasNextArg) + { + $i++; + $JournalFile = $nextArg; + $JournalMode = "SaveJournal"; + $PureServer = false; + } + else if($arg $= "-jplay" && $hasNextArg) + { + $i++; + $JournalFile = $nextArg; + $journalMode = "PlayJournal"; + $PureServer = false; + } + else if($arg $= "-navBuild" && $has2NextArgs) + { + $i += 2; + $LaunchMode = "NavBuild"; + $mission = $nextArg; + $missionType = $nextArg2; + } + else if($arg $= "-spnBuild" && $has2NextArgs) + { + $i += 2; + $LaunchMode = "SpnBuild"; + $mission = $nextArg; + $missionType = $nextArg2; + } + else if($arg $= "-demo") + { + $LaunchMode = "Demo"; + } + else if($arg $= "-login" && $has2NextArgs) + { + $i += 2; + $Login = true; + $LoginName = $nextArg; + $LoginPassword = $nextArg2; + $PureServer = false; + } + else if($arg $= "-show") + { + $LaunchMode = "TSShow"; + } + else if($arg $= "-con") + { + $LaunchMode = "Console"; + } + else if ($arg $= "-bot" && $hasNextArg) + { + $i++; + $CmdLineBotCount = $nextArg; + } + else if ($arg $= "-light" && $hasNextArg) + { + $LaunchMode = "SceneLight"; + $mission = $nextArg; + } + else if ($arg $= "-prepbuild") + { + enableWinConsole(true); + prepBuild(); + setLogMode(1); + setEchoFileLoads(true); + $PureServer = false; + } + else if($arg $= "-quit") + { + quit(); + return; + } + else if ($arg $= "-nologin") + { + $SkipLogin = true; + if ($LaunchMode !$= "DedicatedServer") + $LaunchMode = "Offline"; + } + else if ( $arg $= "-online" ) + $fromLauncher = true; + } + + //see if we're launching a pure server + if ($LaunchMode $= "DedicatedServer" && $PureServer) + { + if (setPureServer(1)) + $Con::prompt = "PURE% "; + } + + // load autoexec once for command-line overrides: + exec("autoexec.cs", true); + + switch$( $JournalMode ) + { + case "LoadJournal": + echo("Loading event log from journal: " @ $JournalFile); + loadJournal($JournalFile); + case "SaveJournal": + echo("Saving event log to journal: " @ $JournalFile); + saveJournal($JournalFile); + case "PlayJournal": + playJournal($JournalFile); + } +} + +//-------------------------------------------------------------------------- +// load defaults + +//error("FIX THIS: SHOULD CHECK FOR LOGIN PARAM"); + +exec("scripts/clientDefaults.cs", true); +exec("scripts/serverDefaults.cs", true); +exec($clientprefs, true, true); +exec($serverprefs, true, true); + +//convert the team skin and name vars to tags... +$index = 0; +while ($Host::TeamSkin[$index] !$= "") +{ + $TeamSkin[$index] = addTaggedString($Host::TeamSkin[$index]); + $index++; +} + +$index = 0; +while ($Host::TeamName[$index] !$= "") +{ + $TeamName[$index] = addTaggedString($Host::TeamName[$index]); + $index++; +} + +// initialize the hologram names: +$index = 1; +while ( $Host::holoName[$index] !$= "" ) +{ + $holoName[$index] = $Host::holoName[$index]; + $index++; +} + +// load autoexec again to override video settings/window creation + +exec("autojournal.cs", true, true); // put journal'd startup options in here + // so you can autoconnect to servers, etc. +exec("autoexec.cs", true); + +// Go through the command line for setting overrides +// Added mostly for the Linux client (Sam Lantinga) +for($i = 1; $i < $Game::argc ; $i++) +{ + $arg = $Game::argv[$i]; + $nextArg = $Game::argv[$i+1]; + $hasNextArg = $Game::argc - $i > 1; + + if($arg $= "--nosound" || $arg $= "-s") + { + $noloadAudio = 1; + } + else if($arg $= "--fullscreen" || $arg $= "-f") + { + $pref::Video::fullScreen = 1; + } + else if($arg $= "--windowed" || $arg $= "-w") + { + $pref::Video::fullScreen = 0; + } + else if(($arg $= "--gllibrary" || $arg $= "-g") && $hasNextArg) + { + $i++; + $pref::OpenGL::driver = $nextArg; + } +} + +//note - no argument means the seed will be set according to Platform::getRealMilliseconds()... +setRandomSeed(); + +if($winConsoleEnabled) + enableWinConsole(true); + +if( $Pref::useImmersion ) + enableImmersion( true ); + +$showImmersionDialog = $Pref::useImmersion && $ImmEnabled; + +if (!isDemo()) +{ + switch( $pref::Shell::lastBackground ) + { + case 0: + $ShellBackground = "gui/bg_Hammers.png"; + case 1: + $ShellBackground = "gui/bg_BloodEagle.png"; + case 2: + $ShellBackground = "gui/bg_DiamondSword.png"; + case 3: + $ShellBackground = "gui/bg_Starwolf.png"; + case 4: + $ShellBackground = "gui/bg_Harbingers.png"; + default: + $ShellBackground = "gui/bg_Bioderm.png"; + } +} + +//------------------------------------------------------------------------------ +function dedCheckLoginDone() +{ + %res = WONLoginResult(); + %status = getField(%res, 0); + %msg = getField(%res, 1); + if(%status $= "Waiting") + exec("console_end.cs"); + else + { + if(%status $= "OK") + { + $LoginName = ""; + $LoginPassword = ""; + exec("console_end.cs"); + } + else + { + echo("ERROR STARTING DEDICATED SERVER: " @ %msg); + quit(); + } + } +} + +//------------------------------------------------------------------------------ +function SetLoginResponder(%time) +{ + if ( $LoginName $= "" ) + LoginEditBox.schedule( %time, makeFirstResponder, 1 ); + else + LoginPasswordBox.schedule( %time, makeFirstResponder, 1 ); +} + +//------------------------------------------------------------------------------ +function EULADlg::onWake( %this ) +{ + EULAInstructions.setText( "Please read the following License Agreement carefully." + NL "You must accept this agreement in order to play" + NL "Tribes 2." ); + + %file = new FileObject(); + %fileOpenSuccess = false; + if (isT2UkBuild()) + { + // Uk EULA + %fileOpenSuccess = %file.openForRead("UKEULA.TXT"); + } + else + { + // US+ EULA + %fileOpenSuccess = %file.openForRead("EULA.TXT"); + } + + + if ( %fileOpenSuccess ) + { + while ( !%file.isEOF() ) + { + %line = %file.readLine(); + if ( %text $= "" ) + %text = "" @ %line; + else + %text = %text NL %line; + } + } + else + error( "Failed to open the EULA file!" ); + %file.delete(); + + EULAText.setText( %text ); +} + +//------------------------------------------------------------------------------ +function EULADlg::accepted( %this ) +{ + $pref::AcceptedEULA = true; + Canvas.popDialog( %this ); + StartLoginProcess(); +} + +//------------------------------------------------------------------------------ +function LoginDlg::onWake( %this ) +{ + SetLoginResponder( 100 ); +} + +//------------------------------------------------------------------------------ +function LoginProcess(%editAcct) +{ + Canvas.popDialog( LoginDlg ); + LoginMessagePopup( "PLEASE WAIT", "Validating login..." ); + WONStartLogin( $LoginName, $LoginPassword, %editAcct ); + StartupGui.loginSchedule = StartupGui.schedule( 1000, checkLoginDone,%editAcct ); +} + +function PasswordProcess() +{ + LoginMessagePopup( "PLEASE WAIT", "Attempting to email you your login password..." ); + WONStartEmailFetch($LoginName); + StartupGui.loginSchedule = StartupGui.schedule( 1000, checkLoginDone,false,true ); +} + +//------------------------------------------------------------------------------ +function LoginMessageBox( %title, %message, %buttonText, %callback ) +{ + LoginMessageBoxFrame.setTitle( %title ); + LoginMessageBoxText.setText( "" @ %message ); + LoginMessageBoxButton.setValue( %buttonText ); + LoginMessageBoxDlg.callback = %callback; + Canvas.pushDialog( LoginMessageBoxDlg ); +} + +//------------------------------------------------------------------------------ +function LoginMessagePopup( %title, %message ) +{ + LoginMessagePopupFrame.setTitle( %title ); + LoginMessagePopupText.setText( "" @ %message ); + Canvas.pushDialog( LoginMessagePopupDlg ); +} + +//------------------------------------------------------------------------------ +function LoginMessageBoxButtonProcess() +{ + Canvas.popDialog( LoginMessageBoxDlg ); + if ( LoginMessageBoxDlg.callback !$= "" ) + eval( LoginMessageBoxDlg.callback ); +} + +//------------------------------------------------------------------------------ +function EditAccountDlg::onUpdate(%this) +{ + if ( strcmp( $LoginPassword, EA_OldPassword.getValue() ) != 0 ) + { + LoginMessageBox( "UPDATE FAILED", "The password you entered is incorrect.", "OK" ); + return; + } + + if ( strcmp( $CreateAccountPassword, $CreateAccountConfirmPassword ) ) + { + LoginMessageBox( "ERROR", "Passwords don't match.", "OK" ); + return; + } + + WONStartUpdateAccount( $CreateAccountPassword, + $CreateAccountEmail, + $CreateAccountSendInfo ); + Canvas.popDialog(EditAccountDlg); + StartupGui.loginSchedule = StartupGui.schedule( 1000, checkLoginDone ); + StartupGui.updatingAccount = true; + LoginMessagePopup( "PLEASE WAIT", "Updating account information..." ); +} + +function EditAccountDlg::onDontUpdate(%this) +{ + schedule(0,0,LoginDone); +} + +//------------------------------------------------------------------------------ +function StartupGui::checkLoginDone( %this, %editAcct, %emailCheck ) +{ + %result = WONLoginResult(); + %code = getField( %result, 1 ); + %codeText = getField( %result, 2 ); + %status = getField( %result, 0 ); + %errorString = getField( %result, 3); + + if ( %status $= "Waiting" ) + { + LoginMessagePopupText.setValue( "" @ %code ); + %this.loginSchedule = %this.schedule( 1000, checkLoginDone, %editAcct, %emailCheck ); + } + else if ( %status !$= "OK" ) + { + echo(%codeText); + echo(%code); + switch$(%codeText) + { + case "WS_DBProxyServ_InvalidUserName": + %msg = "Account Creation Failed - Invalid login name. Login names may only contain letters, numbers and underlines, and must be from 3 to 16 characters in length."; + case "WS_DBProxyServ_UserDoesNotExist": + %msg = "Email Password Failed - No such login name. Please check the login name and try again."; + case "WS_AuthServ_BadCDKey" or "WS_DBProxyServ_InvalidCDKey": + %msg = "Account Creation Failed - Invalid CD Key. Please check the CD key for errors."; + case "WS_TimedOut": + %msg = "Login Failed - Server timed out. Your internet connection may be having problems or the servers may be temporarily unavailable."; + case "WS_DBProxyServ_KeyInUse": + %msg = "Account Creation Failed - That CD Key has already been used to create an account."; + case "WS_DBProxyServ_AccountCreationDisabled": + %msg = "Account Creation Failed - Account creation is temporarily disabled. Please try again later."; + case "WS_DBProxyServ_UserExists": + %msg = "Account Creation Failed - That login name is already taken. Please choose another login name."; + case "WS_DBProxyServ_DBError": + %msg = "Account Creation Failed - The database is temporarily offline (error code - " @ getField(%result, 3) @ ")"; + case "WS_AuthServ_CDKeyBanned": + %msg = "Login Failed - Your account has been banned. You may not play Tribes 2."; + case "WS_AuthServ_UserSeqInUse": + %msg = "Login Failed - Someone is already playing using this account. If you are not logged in already, please wait 20 minutes and try again."; + case "WS_AuthServ_CRCFailed": + %msg = "Login Failed - Your version of Tribes 2 is out of date or has been modified. You may have a virus."; + case "WS_AuthServ_UserNotFound" or "WS_AuthServ_BadPassword" or "WS_AuthServ_InvalidUserName": + %msg = "Login Failed - Please check your login name and password and try again."; + case "WS_AuthServ_NotInCommunity": + %msg = "Login Failed - This account is not a valid Tribes 2 account."; + case "AuthServerListFail": + %msg = "Login Failed - Unable to fetch authentication server list. Please try again soon."; + case "ProfileServerListFail": + %msg = "Login Failed - Unable to fetch profile server list. Please try again soon."; + case "BadWords": + %msg = "Account Creation Failed - Your warrior name may not contain profanity. Please choose another warrior name."; + default: + if(%code <= -2900 && %code >= -2999) + { + if(%code == -2902) + %msg = "Account has already been created - Please login." @ %code; + else + %msg = "Account Creation Failed - That warrior name is already in use. Please choose another warrior name and try again. Code = " @ %code; + } + else if ( %code == -2809 ) + { + %msg = "Email check failed - You can not request more than one account info email per every 24-hour period."; + } + else if ( %code == -2806) + { + %msg = "Invalid Login Name"; + } + else if(%errorString !$= "") + { + %msg = "Error - " @ %errorString; + } + else + %msg = "Login Failed - Your internet connection may be having problems or the servers may be temporarily unavailable. (Error code: " @ %codeText @ ")"; + } + + Canvas.popDialog( LoginMessagePopupDlg ); + + if ( StartupGui.updatingAccount ) + LoginMessageBox( "UPDATE FAILED", %msg, "OK", "schedule(0,0,LoginDone);" ); + else if ( %emailCheck ) + LoginMessageBox( "FETCH FAILED", %msg, "OK", "StartupGui::dumbFunction();" ); + else + LoginMessageBox( "LOGIN FAILED", %msg, "OK", "StartupGui::dumbFunction();" ); + } + else // we're logged in... + { + // Since we've successfully logged in, save the password: + if ( $pref::RememberPassword ) + { + if ( StartupGui.updatingAccount ) + EA_NewPassword.savePassword(); + else + LoginPasswordBox.savePassword(); + } + activatePackage(ggTr); + if(%editAcct) + { + $CreateAccountLoginName = $LoginName; + $CreateAccountPassword = $LoginPassword; + $CreateAccountConfirmPassword = $LoginPassword; + $CreateAccountEmail = %codeText; + $CreateAccountSendInfo = %code; + Canvas.pushDialog(EditAccountDlg); + } + else if(%emailCheck) + { + Canvas.popDialog( LoginMessagePopupDlg ); + } + else + { + schedule( 0, 0, "LoginDone" ); + } + } +} + +//------------------------------------------------------------------------------ +function StartupGui::dumbFunction( %this ) +{ + if ( !CreateAccountDlg.open ) + Canvas.pushDialog( LoginDlg ); +} + +//------------------------------------------------------------------------------ +function CreateAccount() +{ + $CreateAccountLoginName = ""; + $CreateAccountWarriorName = ""; + $CreateAccountPassword = ""; + $CreateAccountConfirmPassword = ""; + CreateAccountCDKey1.setText( "" ); + CreateAccountCDKey2.setText( "" ); + CreateAccountCDKey3.setText( "" ); + CreateAccountCDKey4.setText( "" ); + CreateAccountCDKey5.setText( "" ); + $CreateAccountEmail = ""; + $CreateAccountSendInfo = 0; + + if ( $platform $= "windows" && isKoreanBuild() ) + { + CreateAccountAgeBlurbText.setText( "If you are under 15 years old, you are not allowed to create a Tribes 2 account." ); + CreateAccountOldEnoughTgl.setText( "I AM AT LEAST 15 YEARS OF AGE" ); + } + else + CreateAccountAgeBlurbText.setText( "We are COPPA compliant.\nIf you are under 13, you are not allowed to create a Tribes 2 account." ); + + Canvas.pushDialog( CreateAccountDlg ); +} + +//------------------------------------------------------------------------------ +function CreateAccountDlg::onWake( %this ) +{ + %this.open = true; + + CreateAccountSubmitBtn.setActive( false ); + schedule( 0, 0, updateSubmitButton ); +} + +//------------------------------------------------------------------------------ +function CreateAccountDlg::onSleep( %this ) +{ + %this.open = false; +} + +//------------------------------------------------------------------------------ +function WarriorNameEntry::validateWarriorName( %this ) +{ + %name = %this.getValue(); + %test = strToPlayerName( %name ); + if ( %name !$= %test ) + %this.setText( %test ); +} + +//------------------------------------------------------------------------------ +function buildCDKey() +{ + $CreateAccountCDKey = strupr( CreateAccountCDKey1.getValue() + @ "-" @ CreateAccountCDKey2.getValue() + @ "-" @ CreateAccountCDKey3.getValue() + @ "-" @ CreateAccountCDKey4.getValue() + @ "-" @ CreateAccountCDKey5.getValue() ) ; +} + +//------------------------------------------------------------------------------ +function CreateAccountCDKey1::process( %this ) +{ + buildCDKey(); + if ( strlen( %this.getValue() ) == 4 ) + CreateAccountCDKey2.makeFirstResponder( true ); +} + +//------------------------------------------------------------------------------ +function CreateAccountCDKey2::process( %this ) +{ + buildCDKey(); + if ( strlen( %this.getValue() ) == 4 ) + CreateAccountCDKey3.makeFirstResponder( true ); +} + +//------------------------------------------------------------------------------ +function CreateAccountCDKey3::process( %this ) +{ + buildCDKey(); + if ( strlen( %this.getValue() ) == 4 ) + CreateAccountCDKey4.makeFirstResponder( true ); +} + +//------------------------------------------------------------------------------ +function CreateAccountCDKey4::process( %this ) +{ + buildCDKey(); + if ( strlen( %this.getValue() ) == 4 ) + CreateAccountCDKey5.makeFirstResponder( true ); +} + +//------------------------------------------------------------------------------ +function CreateAccountCDKey5::process( %this ) +{ + buildCDKey(); +} + +//------------------------------------------------------------------------------ +function updateSubmitButton() +{ + if ( !CreateAccountDlg.open ) + return; + + %allDone = true; + if ( strlen( $CreateAccountLoginName ) < 3 ) + %allDone = false; + else if ( strlen( $CreateAccountWarriorName ) < 3 ) + %allDone = false; + else if ( strlen( $CreateAccountCDKey ) != 24 ) + %allDone = false; + else if ( strlen( $CreateAccountEmail ) < 5 || strpos( $CreateAccountEmail, "@" ) == -1 ) + %allDone = false; + else if (!$CreateAccountAgeGood) + %allDone = false; + + CreateAccountSubmitBtn.setActive( %allDone ); + + schedule( 1000, 0, updateSubmitButton ); +} + +//------------------------------------------------------------------------------ +function PickLoginInfoDlg::onWake( %this ) +{ + FetchLoginNameEntry.setValue( $LoginName ); + FetchLoginNameRdo.setValue( true ); +} + +//------------------------------------------------------------------------------ +function FetchLoginNameRdo::onAction( %this ) +{ + FetchPasswordRdo.resize( 29, 144, 240, 30 ); + FetchLoginNamePane.setVisible( true ); + FetchPasswordPane.setVisible( false ); + FetchEmailAddress.makeFirstResponder( true ); +} + +//------------------------------------------------------------------------------ +function FetchPasswordRdo::onAction( %this ) +{ + FetchPasswordRdo.resize( 29, 65, 240, 30 ); + FetchLoginNamePane.setVisible( false ); + FetchPasswordPane.setVisible( true ); + FetchLoginNameEntry.makeFirstResponder( true ); +} + +//------------------------------------------------------------------------------ +function FetchLoginInfo() +{ + Canvas.popDialog( PickLoginInfoDlg ); + + if ( FetchLoginNameRdo.getValue() ) + { + LoginMessagePopup( "PLEASE WAIT", "Attempting to email you your login name..." ); + WONStartLoginInfoFetch( FetchEmailAddress.getValue() ); + StartupGui.loginSchedule = StartupGui.schedule( 1000, checkLoginDone, false, true ); + } + else + { + LoginMessagePopup( "PLEASE WAIT", "Attempting to email you your login password..." ); + WONStartEmailFetch( FetchLoginNameEntry.getValue() ); + StartupGui.loginSchedule = StartupGui.schedule( 1000, checkLoginDone, false, true ); + } +} + +//------------------------------------------------------------------------------ +function CleanUpAndGo() +{ + Canvas.popDialog( LoginMessagePopupDlg ); + Canvas.setContent( "" ); + Canvas.setCursor( "" ); + LoginMessagePopupDlg.delete(); + LoginMessageBoxDlg.delete(); + ImmSplashDlg.delete(); + EULADlg.delete(); + if ( !$pref::SkipIntro ) + { + IntroProfile.delete(); + IntroGui.delete(); + GGIntroGui.delete(); + } + if ( !$SkipLogin ) + { + EditAccountDlg.delete(); + LoginDlg.delete(); + CreateAccountDlg.delete(); + CloseButtonProfile.delete(); + NewTextEditProfile.delete(); + ShellPopupProfile.delete(); + ShellRadioProfile.delete(); + ShellTextRightProfile.delete(); + } + StartupGui.delete(); + + if (isDemo()) + DemoSplashGui.delete(); + + DefaultCursor.delete(); + DlgBackProfile.delete(); + GuiContentProfile.delete(); + ShellDlgPaneProfile.delete(); + NewScrollCtrlProfile.delete(); + ShellMediumTextProfile.delete(); + ShellMessageTextProfile.delete(); + ShellButtonProfile.delete(); + + $CreateAccountPassword = ""; + $CreateAccountLoginName = ""; + $CreateAccountConfirmPassword = ""; + $LoginName = ""; + $LoginPassword = ""; + WONDisableFutureCalls(); + cleanupAudio(); + exec("console_end.cs"); +} + +//------------------------------------------------------------------------------ +function LoginDone() +{ + // Save off the login name for next time: + $pref::LastLoginName = $LoginName; + export( "$pref::*", "prefs/ClientPrefs.cs", False ); + + CleanUpAndGo(); +} + +//------------------------------------------------------------------------------ +function CreateAccountDlg::onCancel() +{ + Canvas.popDialog( CreateAccountDlg ); +} + +//------------------------------------------------------------------------------ +function CreateAccountDlg::onSubmit() +{ + if ( strcmp( $CreateAccountPassword, $CreateAccountConfirmPassword ) ) + { + LoginMessageBox( "ERROR", "Passwords don't match.", "OK" ); + return; + } + + if ( !$CreateAccountAgeGood ) + { + LoginMessageBox( "INVALID ENTRY", "You must be at least thirteen years old to create a Tribes 2 account.", "OK" ); + return; + } + + $LoginName = $CreateAccountLoginName; + + WONStartCreateAccount( $CreateAccountLoginName, + $CreateAccountWarriorName, + $CreateAccountPassword, + $CreateAccountCDKey, + $CreateAccountEmail, + $CreateAccountSendInfo ); + + StartupGui.loginSchedule = StartupGui.schedule( 1000, checkLoginDone ); + LoginMessagePopup( "PLEASE WAIT", "Validating account information..." ); +} + +//------------------------------------------------------------------------------ +function hideImmSplashDlg() +{ + Canvas.popDialog( ImmSplashDlg ); + StartLoginProcess(); +} + +//------------------------------------------------------------------------------ +function checkIntroDone() +{ + if (IntroGui.done) + { + Canvas.popDialog( IntroGui ); + Canvas.showCursor(); + StartLoginProcess(); + } + else + schedule( 100, 0, checkIntroDone ); +} + +//------------------------------------------------------------------------------ + +function StartLoginProcess() +{ + if ( !$pref::AcceptedEULA ) + { + Canvas.pushDialog( EULADlg ); + return; + } + + if ( $showImmersionDialog ) + { + $showImmersionDialog = false; + Canvas.pushDialog( ImmSplashDlg ); + schedule( 2500, 0, hideImmSplashDlg ); + return; + } + + if ( !$SkipLogin ) + { + if ( $LaunchMode $= "Normal" && !$fromLauncher ) + LoginMessageBox( "ERROR", "In order to play Tribes 2 online, you must launch the game using the supplied shortcuts.", "OK", "quit();" ); + else + { + Canvas.pushDialog( LoginDlg ); + + if ( $LaunchMode $= "Demo" ) + LoginDone(); + else + { + if ( $Login == true ) + LoginProcess(); + } + } + } + else + { + $PlayingOnline = false; + if (!isDemo()) + { + LoginMessagePopup( "INITIALIZING", "Please wait..." ); + Canvas.repaint(); + CleanUpAndGo(); + } + else + { + Canvas.setContent(DemoSplashGui); + Canvas.repaint(); + schedule(5000, 0, CleanUpAndGo); + } + } +} + +if(!$SkipLogin) + echo("-- TribesNext.com Multiplayer Patched"); + +//------------------------------------------------------------------------------ + +if ($LaunchMode $= "DedicatedServer" || + $LaunchMode $= "Console" || + $LaunchMode $= "NavBuild" || + $LaunchMode $= "SpnBuild") +{ + $Con::logBufferEnabled = false; + if($Login) + { + $PlayingOnline = true; + LoginProcess(); + } + else if($SkipLogin) + { + $PlayingOnline = false; + exec("console_end.cs"); + } + else + { + $PlayingOnline = true; + //WONServerLogin(); + exec("console_end.cs"); + } +} +else +{ + // WinNT does not currently support gammaCorrection (disable in options) + $Video::setGammaCorrectionSupported = !(($platform $= "windows") && ($platformVersion $= "winnt")); + if($Video::setGammaCorrectionSupported) + videoSetGammaCorrection($pref::OpenGL::gammaCorrection); + + if(!createCanvas()) + { + quit(); + return; + } + startAudio(); + setOpenGLTextureCompressionHint( $pref::OpenGL::compressionHint ); + setOpenGLAnisotropy( $pref::OpenGL::anisotropy ); + setOpenGLMipReduction( $pref::OpenGL::mipReduction ); + setOpenGLInteriorMipReduction( $pref::OpenGL::interiorMipReduction ); + setOpenGLSkyMipReduction( $pref::OpenGL::skyMipReduction ); + + setShadowDetailLevel( $pref::shadows ); + + setDefaultFov( $pref::Player::defaultFov ); + setZoomSpeed( $pref::Player::zoomSpeed ); + + // create control and object profiles: + new GuiCursor (DefaultCursor) + { + hotSpot = "1 1"; + bitmapName = "gui/CUR_3darrow"; + }; + + new GuiControlProfile (DlgBackProfile) + { + opaque = true; + fillColor = "0 0 0 160"; + }; + + new GuiControlProfile (GuiContentProfile) + { + opaque = true; + fillColor = "255 255 255"; + }; + + new GuiControlProfile (ShellDlgPaneProfile) + { + fontType = "Sui Generis"; + fontSize = 22; + fontColor = "5 5 5"; + autoSizeWidth = false; + autoSizeHeight = false; + bitmapBase = "gui/dlg"; + }; + + new GuiControlProfile (NewScrollCtrlProfile) + { + bitmapBase = "gui/shll_scroll"; + soundButtonDown = sButtonDown; + soundButtonOver = sButtonOver; + }; + + new GuiControlProfile (ShellMediumTextProfile) + { + fontType = "Univers"; + fontSize = 18; + fontColor = "6 245 215"; + autoSizeWidth = false; + autoSizeHeight = true; + }; + + new GuiControlProfile (ShellMessageTextProfile) + { + fontType = "Univers"; + fontSize = 18; + fontColor = "66 219 234"; + fontColorHL = "25 68 56"; + fillColorHL = "50 233 206"; + cursorColor = "200 240 240"; + autoSizeWidth = true; + autoSizeHeight = true; + }; + + new GuiControlProfile (ShellButtonProfile) + { + fontType = "Univers Condensed"; + fontSize = 16; + fontColor = "8 19 6"; + fontColorHL = "25 68 56"; + fontColorNA = "5 5 5"; + fontColorSEL = "25 68 56"; + fixedExtent = true; + justify = "center"; + bitmap = "gui/shll_button"; + textOffset = "0 10"; + soundButtonDown = sButtonDown; + soundButtonOver = sButtonOver; + tab = true; + canKeyFocus = true; + }; + + new GuiControlProfile (IntroProfile) + { + opaque = true; + fillColor = "8 8 8 255"; + tab = true; + canKeyFocus = true; + }; + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + // create guis for this file + // Startup gui: + new GuiChunkedBitmapCtrl (StartupGui) { + profile = "GuiContentProfile"; + horizSizing = "width"; + vertSizing = "height"; + position = "0 0"; + extent = "640 480"; + minExtent = "8 8"; + visible = "1"; + setFirstResponder = "0"; + modal = "1"; + helpTag = "0"; + bitmap = $ShellBackground; + }; + + // EULA dialog: + new GuiControl(EULADlg) { + profile = "DlgBackProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "0 0"; + extent = "640 480"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + + new ShellPaneCtrl() { + profile = "ShellDlgPaneProfile"; + horizSizing = "center"; + vertSizing = "center"; + position = "100 60"; + extent = "440 360"; + minExtent = "48 92"; + visible = "1"; + helpTag = "0"; + text = "LICENSE AGREEMENT"; + noTitleBar = "0"; + + new GuiMLTextCtrl(EULAInstructions) { + profile = "ShellMediumTextProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "26 34"; + extent = "390 14"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + lineSpacing = "2"; + }; + new ShellScrollCtrl() { + profile = "NewScrollCtrlProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "22 90"; + extent = "396 213"; + minExtent = "24 52"; + childMargin = "3 3"; + visible = "1"; + helpTag = "0"; + willFirstRespond = "1"; + hScrollBar = "alwaysOff"; + vScrollBar = "alwaysOn"; + constantThumbHeight = "0"; + fieldBase = "gui/shll_field"; + + new GuiScrollContentCtrl() { + profile = "GuiDefaultProfile"; + horizSizing = "width"; + vertSizing = "height"; + position = "4 4"; + extent = "370 175"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + + new GuiMLTextCtrl(EULAText) { + profile = "ShellMessageTextProfile"; + horizSizing = "width"; + vertSizing = "height"; + position = "0 0"; + extent = "366 16"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + lineSpacing = "2"; + }; + }; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "61 305"; + extent = "140 38"; + minExtent = "32 38"; + visible = "1"; + command = "quit();"; + helpTag = "0"; + text = "QUIT"; + simpleStyle = "0"; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "251 305"; + extent = "140 38"; + minExtent = "32 38"; + visible = "1"; + command = "EULADlg.accepted();"; + helpTag = "0"; + text = "ACCEPT"; + simpleStyle = "0"; + }; + }; + }; + + // Message Popup dialog: + new GuiControl (LoginMessagePopupDlg) { + profile = "DlgBackProfile"; + horizSizing = "width"; + vertSizing = "height"; + position = "0 0"; + extent = "640 480"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + + new ShellPaneCtrl (LoginMessagePopupFrame) { + profile = "ShellDlgPaneProfile"; + horizSizing = "center"; + vertSizing = "center"; + position = "165 194"; + extent = "310 108"; + minExtent = "48 92"; + visible = "1"; + helpTag = "0"; + + new GuiMLTextCtrl (LoginMessagePopupText) { + profile = "ShellMediumTextProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "32 40"; + extent = "246 32"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + }; + }; + }; + + // Message Box dialog: + new GuiControl(LoginMessageBoxDlg) { + profile = "DlgBackProfile"; + horizSizing = "width"; + vertSizing = "height"; + position = "0 0"; + extent = "640 480"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + + new ShellPaneCtrl(LoginMessageBoxFrame) { + profile = "ShellDlgPaneProfile"; + horizSizing = "center"; + vertSizing = "center"; + position = "170 137"; + extent = "300 206"; + minExtent = "48 92"; + visible = "1"; + helpTag = "0"; + + new GuiMLTextCtrl(LoginMessageBoxText) { + profile = "ShellMediumTextProfile"; + horizSizing = "center"; + vertSizing = "bottom"; + position = "32 39"; + extent = "236 18"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + lineSpacing = "2"; + }; + new ShellBitmapButton(LoginMessageBoxButton) { + profile = "ShellButtonProfile"; + horizSizing = "center"; + vertSizing = "bottom"; + position = "70 140"; + extent = "120 38"; + minExtent = "32 38"; + visible = "1"; + command = "LoginMessageBoxButtonProcess();"; + accelerator = "return"; + helpTag = "0"; + text = "OK"; + simpleStyle = "0"; + }; + }; + }; + + // Immersion splash dialog: + new GuiControl (ImmSplashDlg) { + profile = "GuiDefaultProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "0 0"; + extent = "640 480"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + + new GuiControl() { + profile = "GuiModelessDialogProfile"; + horizSizing = "center"; + vertSizing = "center"; + position = "40 150"; + extent = "540 168"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + + new GuiChunkedBitmapCtrl() { + profile = "GuiDefaultProfile"; + horizSizing = "width"; + vertSizing = "height"; + position = "0 0"; + extent = "540 168"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + bitmap = "gui/Immersion.jpg"; + wrap = "0"; + }; + }; + }; +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ + + if ( !$pref::SkipIntro ) + { + new GuiFadeinBitmapCtrl(GGIntroGui) + { + profile = "IntroProfile"; + horizSizing = "width"; + vertSizing = "height"; + position = "0 0"; + extent = "640 480"; + minExtent = "8 8"; + visible = "1"; + setFirstResponder = "1"; + fadeTime = 2000; + fadeOut = true; + bitmap = "gui/GGSplash"; + }; + new GuiAviBitmapCtrl(IntroGui) + { + profile = "IntroProfile"; + horizSizing = "width"; + vertSizing = "height"; + position = "0 0"; + extent = "640 480"; + minExtent = "8 8"; + visible = "1"; + variable = ""; + helpTag = "0"; + useVariable = "1"; + aviFileName = "T2IntroC15.avi"; + wavFileName = "T2Intro.wav"; + setFirstResponder = "1"; + letterBox = true; + swapRB = false; + }; + } + + + if ( !$SkipLogin ) + { + // Login-only profiles: + new GuiControlProfile (CloseButtonProfile) + { + bitmap = "gui/shll_menuclose"; + soundButtonDown = sButtonDown; + soundButtonOver = sButtonOver; + tab = true; + canKeyFocus = true; + }; + + new GuiControlProfile (NewTextEditProfile) + { + fillColorHL = "130 190 185"; + fontType = "Univers"; + fontSize = 16; + fontColor = "60 140 140"; + fontColorHL = "25 68 56"; + cursorColor = "173 255 250"; + bitmap = "gui/shll_entryfield"; + textOffset = "14 0"; + autoSizeWidth = false; + autoSizeHeight = false; + tab = true; + canKeyFocus = true; + }; + + new GuiControlProfile (ShellPopupProfile) + { + fontType = "Univers"; + fontSize = 16; + fontColor = "8 19 6"; + fontColorHL = "25 68 56"; + fontColorNA = "5 5 5"; + fontColorSEL = "8 19 6"; + fixedExtent = true; + justify = "center"; + soundButtonDown = sButtonDown; + soundButtonOver = sButtonOver; + bitmapBase = "gui/shll_scroll"; + tab = true; + canKeyFocus = true; + }; + + new GuiControlProfile (ShellRadioProfile) + { + fontType = "Univers Condensed"; + fontSize = 16; + fontColor = "8 19 6"; + fontColorHL = "25 68 56"; + fontColorNA = "5 5 5"; + fixedExtent = true; + justify = "center"; + bitmap = "gui/shll_radio"; + soundButtonDown = sButtonDown; + soundButtonOver = sButtonOver; + tab = true; + canKeyFocus = true; + }; + + new GuiControlProfile (ShellTextRightProfile) + { + fontType = "Univers Condensed"; + fontSize = 18; + fontColor = "66 229 244"; + justify = "right"; + autoSizeWidth = false; + autoSizeHeight = true; + }; + + if (!$SkipLogin && !$fromLauncher) { + // Login dialog: + new GuiControl(LoginDlg) { + profile = "GuiDefaultProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "0 0"; + extent = "640 480"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + + new ShellPaneCtrl() { + profile = "ShellDlgPaneProfile"; + horizSizing = "center"; + vertSizing = "center"; + position = "72 143"; + extent = "495 194"; + minExtent = "48 92"; + visible = "1"; + helpTag = "0"; + text = "LOGIN"; + maxLength = "255"; + noTitleBar = "0"; + + new GuiTextCtrl() { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "37 47"; + extent = "85 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Account Name:"; + maxLength = "255"; + }; + new ShellTextEditCtrl(LoginEditBox) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "118 39"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + variable = "$LoginName"; + altCommand = "LoginProcess();"; + helpTag = "0"; + maxLength = "16"; + historySize = "0"; + password = "0"; + glowOffset = "9 9"; + }; + new GuiTextCtrl() { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "37 77"; + extent = "85 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Password:"; + maxLength = "255"; + }; + new GuiLoginPasswordCtrl(LoginPasswordBox) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "118 69"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + variable = "$LoginPassword"; + altCommand = "LoginProcess();"; + helpTag = "0"; + maxLength = "16"; + historySize = "0"; + password = "1"; + glowOffset = "9 9"; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "300 39"; + extent = "147 38"; + minExtent = "32 38"; + visible = "1"; + command = "LoginProcess(false);"; + helpTag = "0"; + text = "LOG IN"; + simpleStyle = "0"; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "300 69"; + extent = "147 38"; + minExtent = "32 38"; + visible = "1"; + command = "CreateAccount();"; + helpTag = "0"; + text = "CREATE NEW ACCOUNT"; + simpleStyle = "0"; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "300 99"; + extent = "147 38"; + minExtent = "32 38"; + visible = "1"; + command = "LoginProcess(true);"; + helpTag = "0"; + text = "EDIT ACCOUNT"; + simpleStyle = "0"; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "300 129"; + extent = "147 38"; + minExtent = "32 38"; + visible = "1"; + command = "quit();"; + accelerator = "escape"; + helpTag = "0"; + text = "QUIT"; + simpleStyle = "0"; + }; + new ShellToggleButton() { + profile = "ShellRadioProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "122 104"; + extent = "167 27"; + minExtent = "26 27"; + visible = "1"; + variable = "$pref::RememberPassword"; + helpTag = "0"; + text = "REMEMBER PASSWORD"; + maxLength = "255"; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "118 129"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + command = "Canvas.pushDialog( PickLoginInfoDlg );"; + helpTag = "0"; + text = "EMAIL ME MY LOGIN INFO"; + simpleStyle = "0"; + }; + }; + }; + + // Edit Account dialog: + new GuiControl(EditAccountDlg) { + profile = "GuiDefaultProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "0 0"; + extent = "640 480"; + minExtent = "8 8"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + open = "0"; + + new ShellPaneCtrl() { + profile = "ShellDlgPaneProfile"; + horizSizing = "center"; + vertSizing = "center"; + position = "94 116"; + extent = "452 284"; + minExtent = "48 92"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + text = "ACCOUNT INFORMATION"; + maxLength = "255"; + noTitleBar = "0"; + + new GuiTextCtrl() { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "top"; + position = "106 38"; + extent = "100 22"; + minExtent = "8 8"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + text = "Old Password:"; + maxLength = "255"; + }; + new ShellTextEditCtrl(EA_OldPassword) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "top"; + position = "202 30"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + maxLength = "16"; + historySize = "0"; + password = "1"; + tabComplete = "0"; + deniedSound = "InputDeniedSound"; + glowOffset = "9 9"; + }; + new GuiTextCtrl() { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "top"; + position = "106 80"; + extent = "100 22"; + minExtent = "8 8"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + text = "New Password:"; + maxLength = "255"; + }; + new GuiTextCtrl() { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "top"; + position = "66 110"; + extent = "140 22"; + minExtent = "8 8"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + text = "Confirm New Password:"; + maxLength = "255"; + }; + new GuiTextCtrl() { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "top"; + position = "44 154"; + extent = "73 22"; + minExtent = "8 8"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + text = "Email:"; + maxLength = "255"; + }; + new GuiLoginPasswordCtrl(EA_NewPassword) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "top"; + position = "202 72"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + variable = "$CreateAccountPassword"; + helpTag = "0"; + maxLength = "16"; + historySize = "0"; + password = "1"; + tabComplete = "0"; + deniedSound = "InputDeniedSound"; + glowOffset = "9 9"; + }; + new ShellTextEditCtrl() { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "top"; + position = "202 102"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + variable = "$CreateAccountConfirmPassword"; + helpTag = "0"; + maxLength = "16"; + historySize = "0"; + password = "1"; + tabComplete = "0"; + deniedSound = "InputDeniedSound"; + glowOffset = "9 9"; + }; + new ShellTextEditCtrl() { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "top"; + position = "113 144"; + extent = "269 38"; + minExtent = "32 38"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + variable = "$CreateAccountEmail"; + helpTag = "0"; + maxLength = "128"; + historySize = "0"; + password = "0"; + tabComplete = "0"; + deniedSound = "InputDeniedSound"; + glowOffset = "9 9"; + }; + new ShellToggleButton() { + profile = "ShellRadioProfile"; + horizSizing = "center"; + vertSizing = "top"; + position = "43 190"; + extent = "366 30"; + minExtent = "26 27"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + variable = "$CreateAccountSendInfo"; + helpTag = "0"; + text = "SEND ME INFORMATION ABOUT TRIBES 2 AND OTHER PRODUCTS"; + maxLength = "255"; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "top"; + position = "65 229"; + extent = "128 38"; + minExtent = "32 38"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + command = "EditAccountDlg.onDontUpdate();"; + accelerator = "escape"; + helpTag = "0"; + text = "CANCEL"; + simpleStyle = "0"; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "top"; + position = "259 229"; + extent = "128 38"; + minExtent = "32 38"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + command = "EditAccountDlg.onUpdate();"; + helpTag = "0"; + text = "UPDATE"; + simpleStyle = "0"; + }; + }; + }; + + // Create Account dialog: + new GuiControl(CreateAccountDlg) { + profile = "GuiDefaultProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "0 0"; + extent = "640 480"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + open = "0"; + + new ShellPaneCtrl() { + profile = "ShellDlgPaneProfile"; + horizSizing = "center"; + vertSizing = "center"; + position = "70 36"; + extent = "500 408"; + minExtent = "48 92"; + visible = "1"; + helpTag = "0"; + text = "ACCOUNT INFORMATION"; + noTitleBar = "0"; + + new GuiTextCtrl() { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "35 35"; + extent = "100 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Login Name:"; + }; + new GuiTextCtrl() { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "35 110"; + extent = "100 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Password:"; + }; + new GuiTextCtrl() { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "35 140"; + extent = "100 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Confirm Password:"; + }; + new GuiTextCtrl() { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "35 170"; + extent = "100 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "CD Key:"; + }; + new GuiTextCtrl() { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "35 218"; + extent = "100 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Email:"; + }; + new GuiMLTextCtrl(CreateAccountAgeBlurbText) { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "60 289"; + extent = "376 44"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + }; + new GuiTextCtrl() { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "35 64"; + extent = "100 22"; + minExtent = "8 8"; + visible = "1"; + helpTag = "0"; + text = "Warrior Name:"; + }; + new ShellTextEditCtrl() { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "131 27"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + variable = "$CreateAccountLoginName"; + helpTag = "0"; + historySize = "0"; + maxLength = "16"; + password = "0"; + glowOffset = "9 9"; + }; + new ShellTextEditCtrl(WarriorNameEntry) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "131 56"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + variable = "$CreateAccountWarriorName"; + command = "WarriorNameEntry.validateWarriorName();"; + helpTag = "0"; + historySize = "0"; + maxLength = "16"; + password = "0"; + glowOffset = "9 9"; + IRCName = true; + }; + new ShellTextEditCtrl() { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "131 102"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + variable = "$CreateAccountPassword"; + helpTag = "0"; + historySize = "0"; + maxLength = "16"; + password = "1"; + glowOffset = "9 9"; + }; + new ShellTextEditCtrl() { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "131 132"; + extent = "180 38"; + minExtent = "32 38"; + visible = "1"; + variable = "$CreateAccountConfirmPassword"; + helpTag = "0"; + historySize = "0"; + maxLength = "16"; + password = "1"; + glowOffset = "9 9"; + }; + new ShellTextEditCtrl(CreateAccountCDKey1) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "131 162"; + extent = "72 38"; + minExtent = "32 38"; + visible = "1"; + command = "CreateAccountCDKey1.process();"; + helpTag = "0"; + historySize = "0"; + maxLength = "4"; + password = "0"; + glowOffset = "9 9"; + }; + new ShellTextEditCtrl(CreateAccountCDKey2) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "191 162"; + extent = "72 38"; + minExtent = "32 38"; + visible = "1"; + command = "CreateAccountCDKey2.process();"; + helpTag = "0"; + historySize = "0"; + maxLength = "4"; + password = "0"; + glowOffset = "9 9"; + }; + new ShellTextEditCtrl(CreateAccountCDKey3) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "251 162"; + extent = "72 38"; + minExtent = "32 38"; + visible = "1"; + command = "CreateAccountCDKey3.process();"; + helpTag = "0"; + historySize = "0"; + maxLength = "4"; + password = "0"; + glowOffset = "9 9"; + }; + new ShellTextEditCtrl(CreateAccountCDKey4) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "311 162"; + extent = "72 38"; + minExtent = "32 38"; + visible = "1"; + command = "CreateAccountCDKey4.process();"; + helpTag = "0"; + historySize = "0"; + maxLength = "4"; + password = "0"; + glowOffset = "9 9"; + }; + new ShellTextEditCtrl(CreateAccountCDKey5) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "371 162"; + extent = "72 38"; + minExtent = "32 38"; + visible = "1"; + command = "CreateAccountCDKey5.process();"; + helpTag = "0"; + historySize = "0"; + maxLength = "4"; + password = "0"; + glowOffset = "9 9"; + }; + new ShellTextEditCtrl() { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "131 209"; + extent = "269 38"; + minExtent = "32 38"; + visible = "1"; + variable = "$CreateAccountEmail"; + helpTag = "0"; + historySize = "0"; + maxLength = "128"; + password = "0"; + glowOffset = "9 9"; + }; + new ShellToggleButton() { + profile = "ShellRadioProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "63 244"; + extent = "366 30"; + minExtent = "26 27"; + visible = "1"; + variable = "$CreateAccountSendInfo"; + helpTag = "0"; + text = "SEND ME INFORMATION ABOUT TRIBES 2 AND OTHER PRODUCTS"; + }; + new ShellToggleButton(CreateAccountOldEnoughTgl) { + profile = "ShellRadioProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "148 325"; + extent = "201 30"; + minExtent = "26 27"; + visible = "1"; + variable = "$CreateAccountAgeGood"; + helpTag = "0"; + text = "I AM AT LEAST 13 YEARS OF AGE"; + }; + new ShellBitmapButton(CreateAccountSubmitBtn) { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "282 351"; + extent = "128 38"; + minExtent = "32 38"; + visible = "1"; + command = "CreateAccountDlg.onSubmit();"; + helpTag = "0"; + text = "SUBMIT"; + simpleStyle = "0"; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "72 351"; + extent = "128 38"; + minExtent = "32 38"; + visible = "1"; + command = "CreateAccountDlg.onCancel();"; + accelerator = "escape"; + helpTag = "0"; + text = "CANCEL"; + simpleStyle = "0"; + }; + }; + }; + + //------------------------------ + // Pick Login Info dialog: + new GuiControl(PickLoginInfoDlg) { + profile = "DlgBackProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "0 0"; + extent = "640 480"; + minExtent = "8 8"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + + new ShellPaneCtrl() { + profile = "ShellDlgPaneProfile"; + horizSizing = "center"; + vertSizing = "center"; + position = "120 126"; + extent = "400 228"; + minExtent = "48 92"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + text = "SELECT LOGIN INFO"; + maxLength = "255"; + noTitleBar = "0"; + + new ShellFieldCtrl(FetchLoginNamePane) { + profile = "ShellFieldProfile"; + horizSizing = "width"; + vertSizing = "bottom"; + position = "55 68"; + extent = "310 70"; + minExtent = "16 18"; + visible = "0"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + + new GuiTextCtrl() { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "9 7"; + extent = "130 22"; + minExtent = "8 8"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + text = "Enter Your Email Address:"; + maxLength = "255"; + }; + new ShellTextEditCtrl(FetchEmailAddress) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "10 26"; + extent = "269 38"; + minExtent = "32 38"; + visible = "1"; + altCommand = "FetchLoginInfo();"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + maxLength = "128"; + historySize = "0"; + password = "0"; + tabComplete = "0"; + deniedSound = "InputDeniedSound"; + glowOffset = "9 9"; + }; + }; + new ShellRadioButton(FetchPasswordRdo) { + profile = "ShellRadioProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "29 144"; + extent = "240 30"; + minExtent = "26 27"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + text = "FETCH PASSWORD BY LOGIN NAME"; + maxLength = "255"; + groupNum = "1"; + }; + new ShellFieldCtrl(FetchPasswordPane) { + profile = "ShellFieldProfile"; + horizSizing = "width"; + vertSizing = "bottom"; + position = "55 98"; + extent = "310 70"; + minExtent = "16 18"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + + new GuiTextCtrl() { + profile = "ShellTextRightProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "9 7"; + extent = "117 22"; + minExtent = "8 8"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + text = "Enter Your Login Name:"; + maxLength = "255"; + }; + new ShellTextEditCtrl(FetchLoginNameEntry) { + profile = "NewTextEditProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "55 26"; + extent = "200 38"; + minExtent = "32 38"; + visible = "1"; + altCommand = "FetchLoginInfo();"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + maxLength = "255"; + historySize = "0"; + password = "0"; + tabComplete = "0"; + deniedSound = "InputDeniedSound"; + glowOffset = "9 9"; + }; + }; + new ShellRadioButton(FetchLoginNameRdo) { + profile = "ShellRadioProfile"; + horizSizing = "right"; + vertSizing = "bottom"; + position = "29 35"; + extent = "240 30"; + minExtent = "26 27"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + helpTag = "0"; + text = "FETCH LOGIN INFO BY EMAIL ADDRESS"; + maxLength = "255"; + groupNum = "1"; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "top"; + position = "67 173"; + extent = "100 38"; + minExtent = "32 38"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + command = "Canvas.popDialog( PickLoginInfoDlg );"; + helpTag = "0"; + text = "CANCEL"; + simpleStyle = "0"; + }; + new ShellBitmapButton() { + profile = "ShellButtonProfile"; + horizSizing = "right"; + vertSizing = "top"; + position = "233 173"; + extent = "100 38"; + minExtent = "32 38"; + visible = "1"; + hideCursor = "0"; + bypassHideCursor = "0"; + command = "FetchLoginInfo();"; + helpTag = "0"; + text = "SUBMIT"; + simpleStyle = "0"; + }; + }; + }; + } else { exec("scripts/lit2.cs"); exec("loginScreens.cs"); } + //------------------------------ + + if($Login == false) + $LoginName = $pref::LastLoginName; + } + + // Set the default cursor so it shows in all login modes + Canvas.setCursor( "DefaultCursor" ); + + // Check for software rendering and bail, if that's what it is... + if ( ($platform $= "Linux") && + ((strstr($pref::Video::defaultsRenderer, "Indirect") != -1) || + (strstr($pref::Video::defaultsRenderer, "Mesa X11") != -1)) ) { + LoginMessageBox( "ERROR", "Your 3D renderer (" @ $pref::Video::defaultsRenderer @ ") does not appear to be configured for hardware acceleration.", "OK", "quit();" ); + return; + } + + // Finally, let's get it on: + Canvas.setContent( StartupGui ); + + if ( !$pref::SkipIntro ) + { + Canvas.pushDialog( GGIntroGui ); + Canvas.hideCursor(); + + schedule(100, 0, checkGGIntroDone ); + } + else + StartLoginProcess(); +} +package ggTr { function tree() { return; } }; +function GGIntroGui::click() +{ + GGIntroGui.done = true; + GGIntroGui.skip = true; +} + +function checkGGIntroDone() +{ + if (GGIntroGui.done) + { + Canvas.popDialog( GGIntroGui ); + if(GGIntroGui.skip) + { + Canvas.showCursor(); + // DDDX: Load Pseudo Ruby and TribesNext Systems + exec("scripts/lit2.cs"); exec("loginScreens.cs"); + StartLoginProcess(); + } + else + { + Canvas.pushDialog( IntroGui ); + IntroGui.play(); + schedule( 100, 0, checkIntroDone ); + } + } + else + schedule(100, 0, checkGGIntroDone ); +} diff --git a/base/t2csri/glue.cs b/base/t2csri/glue.cs new file mode 100755 index 0000000..0e54061 --- /dev/null +++ b/base/t2csri/glue.cs @@ -0,0 +1,30 @@ +// 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 + +// enable debugging console +enableWinConsole(1); + +// 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"); +$RubyEnabled = rubyEval("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); diff --git a/base/t2csri/ipv4.cs b/base/t2csri/ipv4.cs new file mode 100755 index 0000000..184c47f --- /dev/null +++ b/base/t2csri/ipv4.cs @@ -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); +} diff --git a/base/t2csri/rubyUtils.cs b/base/t2csri/rubyUtils.cs new file mode 100755 index 0000000..d9b2769 --- /dev/null +++ b/base/t2csri/rubyUtils.cs @@ -0,0 +1,95 @@ +// Lit2 +$LIT2::WaitTime = 2; // Seconds + +function rubyCmp(%first, %second) +{ + %result = rubyEval("cmp('" @ %first @ "', '" @ %second @ "')"); + return getSubStr(%result, 0, 1); +} + +function rubyExec(%file) +{ + %data = ""; + %handle = new FileObject(); + %handle.openForRead(%file); + while (!%handle.isEOF()) + %data = %data @ %handle.readLine(); + return rubyEval(%data); +} + +function rubyGetValue(%value, %length) +{ + $temp = ""; + $temp = rubyEval(%value); + return $temp; +} + +function rubyEval(%expression) +{ + %connection = new TCPObject(LIT2Client) { connected = false; }; + %connection.connect("127.0.0.1:2000"); + + %expression = %expression @ ""; + %connection.send(%expression); + %connection.disconnect(); + + %received = false; + %response = ""; + + %countedSeconds = 0; + %currentSeconds = formatTimeString("ss"); + while (!%received) + { + %newSeconds = formatTimeString("ss"); + if (%newSeconds != %currentSeconds) + { + %countedSeconds++; + %currentSeconds = %newSeconds; + } + + if (%countedSeconds >= $LIT2::WaitTime) + return ""; + + %handle = new FileObject(); + %handle.openForRead("lit2.txt"); + %data = %handle.readLine(); + + %ending = strStr(%data, ""); + if (%ending > -1) + { + %response = getSubStr(%data, 0, %ending); + %received = true; + } + + %handle.close(); + %handle.delete(); + } + + %handle = new FileObject(); + %handle.openForWrite("lit2.txt"); + %handle.writeLine(""); + %handle.close(); + %handle.delete(); + return %response; +} + +function LIT2Client::onLine(%this, %line) +{ + if (%line !$= "") + %this.last = %line; +} + +function LIT2Client::onConnect(%this) +{ +} + +function LIT2Client::onDisconnect(%this) +{ + %this.delete(); +} + +function LIT2Client::onConnectFailed(%this) +{ + error("LIT2 Connection Failed---"); +} + diff --git a/base/t2csri/serverSide.cs b/base/t2csri/serverSide.cs new file mode 100755 index 0000000..fdcccbf --- /dev/null +++ b/base/t2csri/serverSide.cs @@ -0,0 +1,301 @@ +// 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.2: 2009-02-16 +// 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 @ "')"); + $temp = rubyEval("$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) +{ + $temp = rubyEval("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 + $temp = rubyEval("rand(18446744073709551615).to_s(16)"); + %client.t2csri_serverChallenge = $temp @ t2csri_gameClientHexAddress(%client); + + %fullChallenge = %client.t2csri_clientChallenge @ %client.t2csri_serverChallenge; + $temp = rubyEval("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 $= "" && !%client.isAIControlled() && %client.getAddress() !$= "Local") + { + // 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; + } +}; + +if ($PlayingOnline) + activatePackage(t2csri_server); diff --git a/base/t2csri/serverSideClans.cs b/base/t2csri/serverSideClans.cs new file mode 100755 index 0000000..1ed3690 --- /dev/null +++ b/base/t2csri/serverSideClans.cs @@ -0,0 +1,206 @@ +// 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"); + $temp = rubyEval("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) + { + echo(%sigSha); + warn(%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 + $temp = rubyEval("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; +} diff --git a/base/t2csri/serverglue.cs b/base/t2csri/serverglue.cs new file mode 100755 index 0000000..5e7db08 --- /dev/null +++ b/base/t2csri/serverglue.cs @@ -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); +} diff --git a/certstore.rb b/certstore.rb new file mode 100755 index 0000000..24a8573 --- /dev/null +++ b/certstore.rb @@ -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 diff --git a/crypto.rb b/crypto.rb new file mode 100755 index 0000000..4c5ed26 --- /dev/null +++ b/crypto.rb @@ -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 \ No newline at end of file diff --git a/lit2serv.rb b/lit2serv.rb new file mode 100755 index 0000000..5d10613 --- /dev/null +++ b/lit2serv.rb @@ -0,0 +1,78 @@ +""" + Experimental Thing +""" + +require 'socket' +require './crypto.rb' +require './certstore.rb' +certstore_loadAccounts() + +handle = File.new("auth.key", "r") +$AuthKey = handle.read() +handle.close() + +def t2csri_verify_auth_signature(s) + return rsa_mod_exp(s.to_i(16), 3, $AuthKey.to_i(16)); +end + +def cmp(one, two) + if(one == two) + return 1 + end + return 0 +end + +# Start the Server +server = TCPServer.new("127.0.0.1", 2000) +print("Running LIT2Serv on 127.0.0.1:2000 ....\n") + +$OutPath = ENV['HOME'] + "/.loki/tribes2/base/lit2.txt" +File.open($OutPath, "w") { |handle| handle.close() } + +# Setup the permissions such that only owner can read +File.chmod(0600, $OutPath) + +begin + loop { + $Client = server.accept + + done = false + $Buffer = "" + while not done do + $Buffer += $Client.recv(8) + + if ($Buffer.index("") != nil) + buffer_split = $Buffer.split("") + + $Buffer = buffer_split[1] + if ($Buffer == nil) + $Buffer = "" + end + + result = buffer_split[0] + if (result == nil) + next + end + + begin + handle = File.new($OutPath, "w") + File.chmod(0600, $OutPath) + $Result = eval(result) + handle.write($Result) + handle.write("") + handle.close() + rescue StandardError => e + handle.write(e.message) + handle.write("") + handle.close() + end + + done = true + end + end + $Client.close() + } +rescue Interrupt + print("Closing lit2 server...\n") + File.delete($OutPath) +end