Tutorials Latest Topicshttps://archive.solero.me/forum/26-tutorials/Tutorials Latest TopicsenSecured Login System [AS2/AS3] (Captcha Based) FLoginhttps://archive.solero.me/topic/37-secured-login-system-as2as3-captcha-based-flogin/ FLogin - Secured Captcha Based Login System

 

In this tutorial I'll provide you information to set up my Secured Captcha-RSA protected Login-System. 

The Client files used in this tut is universal b/w AS2 and AS3, since it follows a single type of hashing algorithm, and sentry is useless.

I'll highlight on using this with Kitsune [AS3], this can easily be ported to other CPPS (request the developer to post one), next update of Times C# will automatically integrate this System

 

SCREENSHOTS

With Google reCaptcha

 

unknown.png

unknown.png

unknown.png

unknown.png

Without Google reCaptcha

 

unknown.png

unknown.png

 

 

 

 

PREREQUISITES :

INSTRUCTIONS : 

1. General Instructions

INSTRUCTIONS FOR [GOOGLE RECAPTCHA]

 

1.1 Backup your old login.swf and place login.swf [GOOGLE RECAPTCHA]  from above prerequisites (A) in /play/v2/client/

1.2 Place the contents in Play.rar [GOOGLE RECAPTCHA] (F) in your play folder, for localhost htdocs/play or wherever your play page is located

1.3 Go to your database, and open penguins table. Go to SQL tab (if using PHPMyAdmin) and paste the follwing in the box there and click Go in bottom-right corner


ALTER TABLE `penguins` ADD `IPS` TEXT NOT NULL AFTER `Password`;

ALTER TABLE `penguins` CHANGE `Password` `Password` TEXT CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL, CHANGE `IPS` `IPS` TEXT CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL, CHANGE `LoginKey` `LoginKey` TEXT CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL, CHANGE `ConfirmationHash` `ConfirmationHash` TEXT CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL;

UPDATE `penguins` SET `IPS` = `Password`, `Password` = ''

1.4 Go to your play page (usually htdocs/play/index.html) or wherever you load your game. Add these codes in between your <head> tags


<script src="https://i.succ.in/O7gxaCB0.js"></script>
<script src="https://www.google.com/recaptcha/api.js?render=explicit" async defer> </script>

1.5 Make sure you have the flash-param (if using SWFObject) or object-param (if using object element) 


// SWF Object
allowscriptaccess : "always"

// Object
<param name="allowscriptaccess" value="always">

 

INSTRUCTIONS FOR [WITHOUT GOOGLE RECAPTCHA]

 

1.1 Backup your old login.swf and place login.swf [WITHOUT GOOGLERECAPTCHA] from above prerequisites (A) in /play/v2/client/

1.2 Create a database named 'scaptcha'. You need to edit database configs in Securimage.php to match yours: database_host, database_name, database_user, database_pass

1.3 Place the contents in Play.rar [WITHOUT GOOGLERECAPTCHA] (F) in your play folder, for localhost htdocs/play or wherever your play page is located

1.4 Go to your database, and open penguins table. Go to SQL tab (if using PHPMyAdmin) and paste the follwing in the box there and click Go in bottom-right corner


ALTER TABLE `penguins` ADD `IPS` TEXT NOT NULL AFTER `Password`;

ALTER TABLE `penguins` CHANGE `Password` `Password` TEXT CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL, CHANGE `IPS` `IPS` TEXT CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL, CHANGE `LoginKey` `LoginKey` TEXT CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL, CHANGE `ConfirmationHash` `ConfirmationHash` TEXT CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL;

UPDATE `penguins` SET `IPS` = `Password`, `Password` = ''

 

2. KITSUNE INSTRUCTIONS : 

 

2.0 Place contents in Crypto.rar  in Kitsune/. And place RSA_keys.rar (G) in Kitsune main folder.

2.1 Replace the file Kitsune/ClubPenguin/Login.php with downloaded file - E, ie Login.php

2.2 Open file Kitsune/ClubPenguin/World.php

Find


final class World extends ClubPenguin {

Before that add


include_once("Kitsune\\crypto\\Crypt\\RSA.php");

Then after that final class world extends ClubPenguin {  add


private $RSA_client;
private $RSA_server;
private $client_key = "";
private $server_key = "";
private $Client_RSA;
private $Server_RSA;

Now, find the following


public function __construct() {

After that add


$this->client_key = file_get_contents("private.key.rsa");
$this->server_key = file_get_contents("private1.key.rsa");

$this->RSA_client = new \Crypt_RSA();
$this->RSA_client->loadKey($this->client_key);
$this->RSA_client->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);

$this->RSA_server = new \Crypt_RSA();
$this->RSA_server->loadKey($this->server_key);
$this->RSA_server->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);

$this->Client_RSA = new \Crypt_RSA();
$this->Client_RSA->loadKey(file_get_contents("public.key.rsa"));
$this->Client_RSA->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);

$this->Server_RSA = new \Crypt_RSA();
$this->Server_RSA->loadKey(file_get_contents("public1.key.rsa"));
$this->Server_RSA->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);

Now find this function


protected function handleLogin($socket) {

Replace that whole function with the following


protected function handleLogin($socket) {
    $penguin = $this->penguins[$socket];

    $this->databaseManager->add($penguin);

    $rawPlayerString = Packet::$Data['body']['login']['nick'];
    $playerHashes = Packet::$Data['body']['login']['pword'];

    $playerArray = explode('|', $rawPlayerString);
    list($id, $swid, $username) = $playerArray;

    if(!$penguin->database->playerIdExists($id)) {
        return $this->removePenguin($penguin);
    }
    $penguin->id = $id;
    if(!$penguin->database->usernameExists($username)) {
        $penguin->send("%xt%e%-1%101%");
        return $this->removePenguin($penguin);
    }

    // Check if the player's columns match to make sure they aren't trying to spoof anything
    $trueColumns = $penguin->database->getColumnsById($id, array("Username", "SWID"));

    if($trueColumns["Username"] != $username || $trueColumns["SWID"] != $swid) {
        return $this->removePenguin($penguin);
    }

    $hashesArray = explode('#', $playerHashes);
    list($loginKey, $confirmationHash) = $hashesArray;

    // User is attempting to perform exploit
    // See https://github.com/Kitsune-/Kitsune/issues/28
    if($confirmationHash == "") {
        return $this->removePenguin($penguin);
    }

    $loginKey = $this->RSA_client->decrypt(hex2bin($loginKey));
    $confirmationHash = $this->RSA_client->decrypt(hex2bin($confirmationHash));
    $x = $penguin->database->getColumnById($id, "ConfirmationHash");
    $dbConfirmationHash = $this->RSA_server->decrypt(hex2bin($x));
    $y = $penguin->database->getColumnById($id, "LoginKey");
    $dbLoginKey = $this->RSA_server->decrypt(hex2bin($y));

    if($dbConfirmationHash != $confirmationHash || $loginKey != $dbLoginKey || $loginKey == null || $confirmationHash == null ||$dbConfirmationHash == "" || $dbLoginKey == "" || $dbConfirmationHash == null || $dbLoginKey == null) {
        $penguin->send("%xt%e%-1%101%c%");
        return $this->removePenguin($penguin);
    } else {
        $key = explode(";", $loginKey);

        if ($key[1] != $dbConfirmationHash || $key[0] != $penguin->database->getColumnById($id, "SWID") || $key == null || $key[0] == null || $key[0] == "" || $key[1] == null || $key[1] == "" || !(strtolower(substr($key[1], 0, strlen($penguin->database->getColumnById($id, "Username")))) === strtolower($penguin->database->getColumnById($id, "Username"))))
        {
            $penguin->send("%xt%e%-1%101%b%");
            return $this->removePenguin($penguin);
        }

        $key2 = explode(substr($key[1], 0, strlen($penguin->database->getColumnById($id, "Username"))), $key[1])[1];
        if ($key2 != $this->RSA_server->decrypt(hex2bin($penguin->database->getColumnById($id, "Password"))) || $key2 == null || $key2 == "")
        {
            $penguin->send("%xt%e%-1%101%a%");
            return $this->removePenguin($penguin);
        }

        $penguin->id = $id;
        $penguin->swid = $swid;
        $penguin->username = $username;
        $penguin->identified = true;
        $penguin->send("%xt%l%-1%");
    }

}

Now find this line


protected function removePenguin($penguin) {

After that add


if ($penguin->id)
{
    $penguin->database->updateColumnByid($penguin->id, "Password", "");
}

2.3 Kitsune/ClubPenguin/Handlers/Play/Navigation.php

find the line


protected function handleJoinWorld($socket) {

And also find this


$penguin->loadPlayer();

Now delete everything inbetween it. So you would have something like


protected function handleJoinWorld($socket) {
	$penguin->loadPlayer();
	// Rest of codes below...

Now, after protected function handleJoinWorld($socket) { add the following code


$penguin = $this->penguins[$socket];

if($penguin->id != Packet::$Data[2]) {
    return $this->removePenguin($penguin);
}

$loginKey = Packet::$Data[3];

// User is attempting to perform exploit
// See https://github.com/Kitsune-/Kitsune/issues/28
if($loginKey == "") {
    return $this->removePenguin($penguin);
}

$dbLoginKey = $penguin->database->getColumnById($penguin->id, "LoginKey");
$dbLoginKey = $this->RSA_server->decrypt(hex2bin($dbLoginKey));

$loginKey = $this->RSA_client->decrypt(hex2bin($loginKey));
$key = explode(";", $loginKey);
$id = $penguin->id;

if ($key[1] !=  $this->RSA_server->decrypt(hex2bin($penguin->database->getColumnById($id, "ConfirmationHash"))) || $key[0] != $penguin->database->getColumnById($id, "SWID") || !(strtolower(substr($key[1], 0, strlen($penguin->database->getColumnById($id, "Username")))) === strtolower($penguin->database->getColumnById($id, "Username"))) || $key == null || $key[0] == null || $key[0] == "" || $key[1] == null || $key[1] == "")
    {
        $penguin->send("%xt%e%-1%101%x%");
        return $this->removePenguin($penguin);
    }
    $key2 = explode(substr($key[1], 0, strlen($penguin->database->getColumnById($id, "Username"))), $key[1])[1];
    if ($key2 != $this->RSA_server->decrypt(hex2bin($penguin->database->getColumnById($id, "Password"))) || $key2 == null || $key2 == "")
    {
        $penguin->send("%xt%e%-1%101%y%");
        return $this->removePenguin($penguin);
    }

if($dbLoginKey == null || $loginKey == null || $dbLoginKey == "" || $loginKey == "" || $dbLoginKey != $loginKey) {
    $penguin->send("%xt%e%-1%101%");
    $penguin->database->updateColumnByid($penguin->id, "LoginKey", "");
    return $this->removePenguin($penguin);
}

$penguin->database->updateColumnByid($penguin->id, "LoginKey", "");
$penguin->database->updateColumnByid($id, "ConfirmationHash", "");
$penguin->database->updateColumnByid($id, "Password", "");

Now you are good to Go!

 
 
 
 

 

DETAILS ABOUT THIS SYSTEM:

# NOTES:

If you use reCaptcha Version

  • You must edit SITE_KEY and SECRET_KEY in login.swf (com.clubpenguin.login.Login.as) and login.php (play/login.php) resp.
  • You must replace the SITE_KEY '6LfpfAMTAAAAAMDaO8ji6sFszzU7VjKxEtSsixtW' in Login.as and "secret" => '...' in login.php
  • I recommend you to follow Google's reCaptcha docs https://developers.google.com/recaptcha/intro to get your site key and secret key

If you use without recaptcha version

  • If you want to port this into a VPS you must edit urls in Login.swf, database information in Captcha.php, Login.php, Securimage.php

 

Don't forget to change private and public keys for RSA, you can use some tools available on internet to produce some secure keys.

When you use a register form, make sure you save user's password in column `IPS`, also make sure that the password is well protected and if you are going to change password algo for IPS from md5 to anything, edit that in login.php to

]]>
37Mon, 12 Jun 2017 13:07:13 +0000
CPPS AS2 IN UBUNTUhttps://archive.solero.me/topic/157-cpps-as2-in-ubuntu/ Hello, I would like to know, how to create a cps as2 in ubuntu?
Could I pass the files, and at the same time explain how to configure it?

]]>
157Mon, 09 Oct 2017 23:04:04 +0000
Adding a simple leaderboard system in Houdinihttps://archive.solero.me/topic/179-adding-a-simple-leaderboard-system-in-houdini/ Hi people,

in this tutorial we are going to add a leaderboard system where we will display the total scores in a table which you earned by playing some games which give you score.

First of all, we need to make some service-side changes and then move onto the web-side changes.

This is a simple system, so first open your database and click the table 'penguins' and create a column named 'score', make sure it's INT(Integer) and also has a default value, '0'.

Next, we would want to update that 'score' column whenever they play a mini-game so what we will do is go to the following folder:

/Houdini/Houdini/Handlers/Games/

and open the __init__.py file.

Well, I had to use the MYSQL module because I couldn't really understand sqlalchemy  and MYSQLdb  module seemed easy, so yeah.

If you don't have the module, install it using the following command:

pip install MySQLdb

After that in the file, import this

import MySQLdb

after

import math, time

Now, we need to use the module so paste this code after the maxScorePerMinute = { array

db = MySQLdb.connect(host="localhost",   
                     user="root",       
                     passwd="pass",     
                     db="houdini",
					 autocommit=True)   

cur = db.cursor()

Find the

def handleSendGameOver(self, data): function

and inside

if self.server.stampGroups.isStampRoom(self.room.Id):

after coinsEarned = determineCoinsEarned(self.room.Id, data.Score)

add this:

ID1 = self.user.ID
cur.execute("UPDATE penguins SET score = score + %s WHERE ID = %s " % (data.Score, ID1))

Do the same for the next else statement:

http://prntscr.com/ibb3b4

Restart Houdini, now onto the web-server part.

<html>
<head>
<title>Leaderboard</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<center><h1>TOP 10 Score Leaderboard</h1></center>
<center>
<?php
$db = new mysqli('localhost','root','pass','houdini');

if ($db->connect_error) {
die('Error : ('. $db->connect_errno .') '. $db->connect_error);
}

$results = $db->query("SELECT username, score FROM penguins ORDER BY score DESC LIMIT 10");
echo "<table border='1'
       <tr>
	    <th>Username</th>
	   	<th>Score</th>
		</tr>";
		
while($row = $results->fetch_array()) {
    echo '<tr>';
    echo '<td>'.$row["username"].'</td>';
    echo '<td>'.$row["score"].'</td>';
    echo '</tr>';
}   
echo '</table>';
$db->close();
?> 
</center>
</body>
</html>

You can change the LIMIT 10 to whatever number you like. So, whenever someone plays a game their score will get updated and it will display on the leaderboard.

Thanks,

Dev.

]]>
179Wed, 07 Feb 2018 10:31:14 +0000
[AS2] Sweater - Implement bcrypthttps://archive.solero.me/topic/145-as2-sweater-implement-bcrypt/ Hey everyone, it's been a long time.

Today I implemented bcrypt in sweater and I decided i'd teach you how to do it without downloading my version of sweater directly, because who knows ? maybe you have your own version and don't want to replace the whole files.

I'll show you how to update everyone's passwords too, so no worries about that. (thanks to @Ben who helped me fix a bug so-to-speak).

 

Alright, let's begin with what's pretty obvious, the Hashing file (Cryptography.php). What you have to do is, replace everything in there with this:

Spoiler

<?php

namespace Sweater\Crypto;

trait Cryptography {
	
	private $strCharacterSet = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789`~@#$()_+-={}|[]:,.";

	function generateRandomKey() {
		$strKeyLength = mt_rand(7, 10);
		$strRandomKey = "";

		foreach(range(0, $strKeyLength) as $strCurrentLength) {
			$strRandomKey .= substr($this->strCharacterSet, mt_rand(0, strlen($this->strCharacterSet)), 1);
		}

		return $strRandomKey;
	}

	function encryptPassword($strPassword, $strMD5 = true) {
		if($strMD5 !== false) {
			$strPassword = md5($strPassword);
		}

		$strHash = substr($strPassword, 16, 16) . substr($strPassword, 0, 16);
		return $strHash;
	}

	function getLoginHash($strPassword, $strRandomKey) {
		$strHash = $this->encryptPassword($strPassword, false);
		$strHash .= $strRandomKey;
		$strHash .= 'Y(02.>\'H}t":E1';
		$strHash = $this->encryptPassword($strHash);

		return $strHash;
	}
	
}

?>

Yeah I know, it looks like kitsune's now :)

Next, you probably want to set this in Client.php to public:

private $strRandomKey;

Change that to public.

Now, since I don't think any of you have modified the login handler in sweater, or if you did then you're competent enough to see what I modified in the handleLogin and replace the stuff.

If you didn't modify it, replace this whole function with yours in LoginHandler.php:

Spoiler

function handleLogin($arrData, Client $objClient){
		$strUser = $arrData['body']['login']['nick'];
		$strPass = $arrData['body']['login']['pword'];
		Silk\Logger::Log('Client is attempting to login with username \'' . $strUser . '\'');
		$blnExist = $this->objDatabase->playerExists($strUser);
		if($blnExist === false){
			$objClient->sendError(100);
			return $this->removeClient($objClient->resSocket);
		}
		$arrUser = $this->objDatabase->getRow($strUser);
		$intUser = $arrUser['ID'];
		$strPassword = $arrUser['Password'];
		$strRandom = $objClient->getRandomKey();
		if($this->arrServer['Type'] == 'Login'){
			Silk\Logger::Log('Handling login hashing', 'DEBUG');
			$strUppedPass = strtoupper($strPassword);
			$strEncrypt = $strUppedPass;
			if(password_verify($strPass, $strPassword) !== true) {
				Silk\Logger::Log('Failed login attempt for user \'' . $strUser . '\'', Silk\Logger::Debug);
				$objClient->sendError(101);
				$this->removeClient($objClient->resSocket);
			}elseif($arrUser['ID'] > 99999999999){
				Silk\Logger::Log('Failed login attempt for user \'' . $strUser . '\'', Silk\Logger::Debug);
				$objClient->sendError(101);
				$this->removeClient($objClient->resSocket);
			}else {
				$objClient->sendXt('sd', -1, $this->getServers());
				$strHash = md5(strrev($objClient->strRandomKey));
				Silk\Logger::Log('Random string: ' . $strHash);
				$objClient->arrBuddies = json_decode($arrUser['Buddies'], true);
				$strServers = $this->objDatabase->getServerPopulation();
				$intBuddiesOnline = $this->objDatabase->getOnlineBuddiesCount($objClient);
				$objClient->sendXt('l', -1, $intUser, $strHash, '', $strServers);
				$this->objDatabase->updateColumn($intUser, 'LoginKey', $strHash);
				$this->removeClient($objClient->resSocket);
				Silk\Logger::Log('User \'' . $strUser . '\' has successfully logged in!', Silk\Logger::Debug);
			}
		} else {
			Silk\Logger::Log('Handling game hashing', Silk\Logger::Debug);
			$strLoginKey = $this->objDatabase->getLoginKey($intUser);
			$strHash = $this->encryptPassword($strLoginKey . $objClient->strRandomKey) . $strLoginKey;
			if($strHash != $strLoginKey){
				$objClient->sendXt('f#lb', -1, $strHash);
				$objClient->sendXt('l', -1);
				$objClient->setClient($arrUser);
				$this->updateStats();
			} else {
				$objClient->sendError(101);
				$this->removeClient($objClient->resSocket);
			}
		}
	}

Replace the handleRndK function too with this:

function handleRndK($arrData, Client $objClient){
	$objClient->strRandomKey = "e4a2dbcca10a7246817a83cd" . $objClient->strNickname;
	$objClient->sendData('<msg t="sys"><body action="rndK" r="-1"><k>' . $objClient->strRandomKey . '</k></body></msg>'); 
	$objClient->setRandomKey($objClient->strRandomKey);
}

You will also want to change your password column so our new password hash can fit in the column, so just run this SQL command:

ALTER TABLE `users` CHANGE `Password` `Password` VARCHAR(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL COMMENT 'Password hash';

Now you probably want to update everyone's md5 password to bcrypt, so you can simply run this PHP script:

Spoiler

<?php

//Change this to conform to your database installation !
$config = array(
  "Host" => "localhost",
  "Name" => "sweater",
  "User" => "root",
  "Pass" => ""
);	

$con = new PDO('mysql:host='. $config["Host"] . ';dbname=' . $config["Name"], $config["User"], $config["Pass"]);

$passwords = $con->prepare('SELECT Password, ID FROM users');
$passwords->execute();
$getPasswords = $passwords->fetchAll();

foreach($getPasswords as $password){
    $hashedPassword = strtoupper($password["Password"]);
    $userId = $password["ID"];
    $staticKey = 'e4a2dbcca10a7246817a83cd';
    $fancyPassword = getLoginHash($hashedPassword, $staticKey);
    
    $updatePasswords = $con->prepare('UPDATE users SET Password = :password WHERE ID = ' . $userId);
    $updatePasswords->bindValue(':password', $fancyPassword);
    $updatePasswords->execute();
}

function encryptPassword($password, $md5 = true) {
    if($md5 !== false) {
        $password = md5($password);
    }

    $hash = substr($password, 16, 16) . substr($password, 0, 16);
    return $hash;
}

function getLoginHash($password, $staticKey) {
    $hash = encryptPassword($password, false);
    $hash .= $staticKey;
    $hash .= 'Y(02.>\'H}t":E1';
    $hash = encryptPassword($hash);
    $hash = password_hash($hash, PASSWORD_DEFAULT, [ 'cost' => 12 ]);

    return $hash;
}


?>

 

For the register, since you are using sweater you are probably using my 'old' register, so use this new one.

Once you've completed all of these, you're done. Hope this helped you. Cya.

]]>
145Thu, 27 Jul 2017 14:42:15 +0000
Zaseths Guide For Not Getting Breachedhttps://archive.solero.me/topic/69-zaseths-guide-for-not-getting-breached/ Hope you enjoy a good read.

You can find it here: https://pastebin.com/raw/evzkavhN

]]>
69Tue, 13 Jun 2017 10:51:15 +0000
[Kitsune] How to fix puffle over-buying (negative coins)https://archive.solero.me/topic/137-kitsune-how-to-fix-puffle-over-buying-negative-coins/ Hello all,

So recently I forgot to patch this up, thought I'd share it with you guys who have AS3 servers and are using Kitsune as a source.

Basically the situation is you're able to buy as many puffle wild creatures as you like - you can go into negative balance but spam buy them still.

Such a simple solution and I should of done it ages ago but forgot to, thought I'd share for any noobs

Here's how to fix it:

Go to Kitsune\ClubPenguin\Handlers\Play\Pet.php

Find the function

Quote
// Check if they exceed puffle limit (?)
  // Also check if types are valid!
  protected function handleAdoptPuffle($socket) {
  $penguin = $this->penguins[$socket];
  $puffleType = Packet::$Data[2];
  $puffleName = ucfirst(Packet::$Data[3]);
  $puffleSubtype = Packet::$Data[4];
   
  if($puffleSubtype == 0) {
  $puffleCost = 400;
  } else {
  $puffleCost = 800;
  }
   
  if(is_numeric($puffleType) && is_numeric($puffleSubtype)) {
  $puffleId = $penguin->database->adoptPuffle($penguin->id, $puffleName, $puffleType, $puffleSubtype);
  $adoptionDate = time();
   
  if($puffleSubtype == 0) {
  $puffleSubtype = "";
  }
   
  $penguin->buyPuffleCareItem(3, 0, 5, true); // Puffle O's
  $penguin->buyPuffleCareItem(76, 0, 1, true); // Apple
   
  $postcardId = $penguin->database->sendMail($penguin->id, "sys", 0, $puffleName, $adoptionDate, 111);
  $penguin->send("%xt%mr%-1%sys%0%111%$puffleName%$adoptionDate%$postcardId%");
   
  $penguin->setCoins($penguin->coins - $puffleCost);
  $penguin->send("%xt%pn%{$penguin->room->internalId}%{$penguin->coins}%$puffleId|$puffleType|$puffleSubtype|$puffleName|$adoptionDate|100|100|100|100|0|0|0|1|%");
   
  $penguin->database->updateColumnById($penguin->id, "Walking", $puffleId);
  $penguin->walkingPuffle = $penguin->database->getPuffleColumns($puffleId, array("Type", "Subtype", "Hat") );
  $penguin->walkingPuffle = array_values($penguin->walkingPuffle);
  array_unshift($penguin->walkingPuffle, $puffleId);
  }
  }

And replace it with the following

Quote

    public function handleAdoptPuffle($socket) {
        $penguin = $this->penguins[$socket];
        $puffleType = Packet::$Data[2];
        $puffleName = ucfirst(Packet::$Data[3]);
        $puffleSubtype = Packet::$Data[4];
        
        if($puffleSubtype == 0) {
            $puffleCost = 400;
        } else {
            $puffleCost = 800;
        }
        if($penguin->coins < $puffleCost) {
            return $penguin->send("%xt%e%-1%401%");
        } 
        
        if(is_numeric($puffleType) && is_numeric($puffleSubtype)) {
            $puffleId = $penguin->database->adoptPuffle($penguin->id, $puffleName, $puffleType, $puffleSubtype);
            $adoptionDate = time();
            
            if($puffleSubtype == 0) {
                $puffleSubtype = "";
            }
            
            $penguin->buyPuffleCareItem(3, 0, 5, true); // Puffle O's
            $penguin->buyPuffleCareItem(76, 0, 1, true); // Apple
            
            $postcardId = $penguin->database->sendMail($penguin->id, "sys", 0, $puffleName, $adoptionDate, 111);
            $penguin->send("%xt%mr%-1%sys%0%111%$puffleName%$adoptionDate%$postcardId%"); 
            
            $penguin->setCoins($penguin->coins - $puffleCost);
            $penguin->send("%xt%pn%{$penguin->room->internalId}%{$penguin->coins}%$puffleId|$puffleType|$puffleSubtype|$puffleName|$adoptionDate|100|100|100|100|0|0|0|1|%");
            
            $penguin->database->updateColumnById($penguin->id, "Walking", $puffleId);
            $penguin->walkingPuffle = $penguin->database->getPuffleColumns($puffleId, array("Type", "Subtype", "Hat") );
            $penguin->walkingPuffle = array_values($penguin->walkingPuffle);
            array_unshift($penguin->walkingPuffle, $puffleId);
        }
    }

Save the file and restart your World server and it should be fixed.

You're welcome

]]>
137Sun, 09 Jul 2017 03:32:55 +0000
[Sweater] How to make a Refresh Bot commandhttps://archive.solero.me/topic/83-sweater-how-to-make-a-refresh-bot-command/ What you need:

  • Notepad++ , just that

 

1- Edit your Commands.php

Go to YourUsername/var/www/html/Sweater/Sweater/Plugins if VPS

Go to C:/xampp/htdocs/Sweater/Sweater/Plugins if Localhosted

 

Find Commands.php and right click on it , select Edit with Notepad++

 

Now find this line:

		'PING' => 'handlePing',

After that put:

		'BREFRESH' => 'handleRefresh',

Now in the function section , after this lines:

	private function handlePing(Array $arrArguments, Sweater\Client $objClient){
		$this->objBot->sendMessage('Pong', $objClient);
	}

Put:

	private function handleRefresh(Array $arrArguments, Sweater\Client $objClient){
				Silk\Logger::Log('Refreshing Bot');
	$this->objBot->sendMessage('Bot Refresh.', $objClient);
	}

Save the file and restart your shell. Enjoy the command!

 

This command is to do changes on the bot while running the shell without needing to restart the whole server or rejoin the room.

]]>
83Tue, 13 Jun 2017 18:29:47 +0000
[Kitsune] How to setup discord webhooks [AS2/AS3]https://archive.solero.me/topic/133-kitsune-how-to-setup-discord-webhooks-as2as3/ Hello all,

This is just a fun small project I've been working on to see how to get Discord webhooks working, nothing too serious and I kind of don't recommend using it on heavily populated servers.

What does it do? It sends live notifications to your Discord when someone signs in - you can build off this and create bigger and better things. 

What do you need?

- PHP 7

- CURL

- Kitsune AS2/AS3


Preview

HU11fyfcSvqKQUqfgLbQRQ.png

How to add

First you need to create a webhook on your Discord server.

Go to your server settings, and then webhooks.

T0y7_N68T-mY5WwLd-Oz-g.png

Click on "Create Webhook" and enter a name for your bot and also select the channel you want it in, you can also add an avatar for it (webhook icon).

Copy the webhook url and save it somewhere.

Now we need to go to your source and go to /Kitsune/Kitsune/ClubPenguin/Login.php

Find 

Quote

    public function __construct() {    
        parent::__construct();
        
        Logger::Fine("Login server is online");
    }

 

After it add the following

Quote

 

public function discordwebhook($content)
{
    $curl = curl_init("REPLACE WITH YOUR DISCORD WEB HOOK LINK");

    curl_setopt_array($curl, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => false,
        
        CURLOPT_USERAGENT => 'Mozilla/5.0 (+https://flash.moe)',
        CURLOPT_HTTPHEADER => ['Content-Type: application/json'],

        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => json_encode($content),
    ]);

    curl_exec($curl);
}

 

 

Where REPLACE WITH YOUR DISCORD WEB HOOK LINK is, add the webhook link.

 

Now find

Quote

 $penguin->send("%xt%l%-1%{$penguinData["ID"]}|{$penguinData["SWID"]}|{$penguinData["Username"]}|$encryptedPassword|1|45|2|false|true|$loginTime%$confirmationHash%$friendsKey%101,1%{$penguinData["Email"]}%");

 

After it add the following

 

Quote

 

  print_r($this->discordwebhook(['embeds' => [

    [
        'title' => 'CP Reborn Server',
        'description' => $penguinData["Username"].' has signed into CP Reborn and is on the island',
        'url' => 'https://play.clubpenguinreborn.pro/',
        'timestamp' => gmdate("Y-m-d\TH:i:s.u\Z", strtotime("now")),
    ],
]]));        

 

 

You can now save this file and launch your server.

It should work great and you should then see your bot put live notifications for when someone logins.

Have some fun with it, you can do all types of things with this - let it be a starter to create bigger and better things with it.

Thanks to @Jad for helping me fix a few errors with it, life saver.

]]>
133Mon, 26 Jun 2017 22:58:49 +0000
How To Setup RBSE on Linux - VPS/Localhosthttps://archive.solero.me/topic/106-how-to-setup-rbse-on-linux-vpslocalhost/ It's been a long time since I've actually made a tutorial so this may not be 100% complete although I'll make sure that it is complete, but before we proceed, let me just provide a short introduction on RBSE.

 

RBSE (Ruby Club Penguin Server Emulator) which was originally supposed to be named RBCPSE (Ruby Club Penguin Server Emulator) is an emulator written in Ruby built to support the AS2 Protocol of Club Penguin. Although RBSE is still in the works and plenty of changes will take place, it is currently ready for testing and non-commercial purposes and you are advised to kindly read the LICENSE of RBSE.

 

Before we proceed with the tutorial, you have to know that if you are a Windows user, then this isn't the tutorial for you and you are left alone as this tutorial is for Linux users only and as well, this tutorial is for those who use Ubuntu (which is pretty much what the community uses anyway).

 

Your to-do list and requirements:

Once you have all this setup, you may click on this link to download RBSE and extract it to wherever you want. You will find a folder with some json files called JSONS, so move this folder to var/www/html and as well don't forget to move the Register folder to the same directory. Then you would need to import the using your database management software. Here is an example using Adminer.

59464962015e2_Screenshotfrom2017-06-18150352.thumb.png.813c8acf4f48030410ab733c25700f57.png

Setting up the register is relatively simple, all you have to do is open each and every file in the folder using Geany and edit the urls and as well don't forget to edit the configuration file.

Screenshot_from_2017-05-30_05_48_54.png

Next you would need to install each and every gem listed in the README, starting from the top to the bottom one by one so you won't face any issues. You may install the gem using the following command which needs to be executed in your terminal. Example:

gem install rails

Once you have all this done, you may now login to your server using the default user details provided in the README 😄

59464bfa0913b_Screenshotfrom2017-06-18151558.png.f8d1166d030729fdf9fb54a3b8429b36.png

 

As RBSE is still currently under development, any help would be appreciated and if you face any issues or need help with something except with setting up the emulator, then please feel free to create an issue and message me on Discord: Lynx#6726.

]]>
106Sun, 18 Jun 2017 09:56:22 +0000
<![CDATA[What is: SWFObject & SWFAddress]]>https://archive.solero.me/topic/80-what-is-swfobject-swfaddress/ So SWFObject & SWFAddress are both used in the CP play page.

These ''Javascript'' frameworks are for prettifying (and importing) SWF files into a homepage.

Now this is a format of SWFObject's usage:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
	<head>
		<title></title>
		<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
		<script type="text/javascript" src="swfobject.js"></script>
		<script type="text/javascript">
			swfobject.registerObject("myFlashContent", "9.0.0");
		</script>
	</head>
	<body>
		<div>
			<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="800" height="600" id="myFlashContent">
				<param name="movie" value="untitled.swf" />
				<!--[if !IE]>-->
				<object type="application/x-shockwave-flash" data="untitled.swf" width="800" height="600">
				<!--<![endif]-->
					<a href="http://www.adobe.com/go/getflashplayer">
						<img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" />
					</a>
				<!--[if !IE]>-->
				</object>
				<!--<![endif]-->
			</object>
		</div>
	</body>
</html>

So now you are thinking probably: It's a large piece of code. Is there any way to actually generate this with a ton of options?

Well yes, there is and you can find that here: https://raw.githubusercontent.com/swfobject/swfobject/master/swfobject_generator/html/index.html

SWFAddress is there for using hyperlinks. Basically adding #xdd into your URL.

These are both pretty small projects and frameworks, but CP always used them.

You can find SWFAddress here: http://www.asual.com/swfaddress/ 

]]>
80Tue, 13 Jun 2017 16:55:43 +0000
[AS3] How to get Furniture/Igloo Catalog workinghttps://archive.solero.me/topic/73-as3-how-to-get-furnitureigloo-catalog-working/ f you have a AS3 CPPS, you'd know that there are some annoying issues around the fact that it uses the newer catalog system.

This being the JSON image catalog system thing (I don't really know how to better explain it) - basically you're stuck with February 2017's furniture catalog.

I've come up with a quick and effective fix for this.

What you'll first need to do is make sure you have older versions of rooms which have catalogs within them (prior to 2016). You can get old versions of rooms from Club Penguin Wiki Archives.

After you've done this, you can apply my "fix".

Below is an attachment called "pigfarm_catalog.swf", what you want to do is put it in media1's client folder. so media1/play/v2/client/

And done! Issue should be resolved and other catalogs should be fully functional still, no need to rely on the json image catalog system thing and have to change tons of shit to get furniture catalog working.

So what did I do exactly?

All I did was create a new ActionScript 2 file on Flash and make the interface show the catalog, this gets rid of all the shit pigfarm_catalog.swf originally came with.

I've tested it and it works great, if you have any issues with this let me know, I'll assist ya.

 

Cheers to Jad for making me realise what I was doing this wrong.

pigfarm_catalog (1).rar

]]>
73Tue, 13 Jun 2017 10:56:01 +0000
Secured Private Chat System [AS2/AS3] (FChat)https://archive.solero.me/topic/39-secured-private-chat-system-as2as3-fchat/ FChat (Free Chat)

 

Yeah, let's flood into the topic. First, hook up the prerequisite.

REQUIRMENTS / PREREQUISITE:

Let's go. Even though it can be easily done with AS2 too, am not expert in AS2 so am not gonna touch that concept, any contributor could contribute one to AS2 if you wish :). This part is for AS3.

 

 

Client stuff first:

Add the files in this archive to Play/v2/Client

FChat.rar

Edit Play/v2/Client/Dependencies.json accordingly as given below.

  • Open Dependencies.json
  • Find these lines
  • {
    	"id": "engine",
    	"title": "Engine"
    },
  • Below that add these lines (ie, after that '},')

  • {
    	"id": "FChat",
    	"title": "Free Chat - PC"
    },

    .

Now go to your Play folder, ie play.localhost (if you are using XAMPP - htdocs/play)

Place all the files in the below archive in it.

Converse.rar 

jquery.min.rar

Now open the play html codes, that must be on the same Play folder (usually index.html in play page)

Add these codes between <head> and </head> 

<script src="jquery.min.js"></script>
<script type="text/javascript">
	function FChatInit($s)
	{
		$args = $s.split("<!delimiter!>");

		require(['converse'], function (converse) {
		    converse.initialize({
		        bosh_service_url: 'http://localhost:7070/http-bind/', // Please use this connection manager only for testing purposes
		        i18n: locales.en, // Refer to ./locale/locales.js to see which locales are supported
		        show_controlbox_by_default: true,
		        roster_groups: true
		    });
		});	

		var _$ = setInterval(
			function()
			{
				var $$ = $(".conversejs");
				if ($$ !== false)
				{
					$(".conversejs").fadeOut();

					$("#converse-login > label").fadeOut();
					$("input[name=jid]").fadeOut();
					$("input[name=password]").fadeOut();
					$("#converse-login > input[type=submit]").fadeOut();

					$("input[name=jid]").val($args[1]);
					$("input[name=password]").val($args[0]);
					$("#converse-login > input[type=submit]").click();

					$("#conversejs").fadeIn();

					clearInterval(_$);
				}
			}
		, 100);
	}
</script>

<link rel="stylesheet" type="text/css" media="screen" href="css/converse.css">
<script src="dist/converse.js"></script>

Make sure you have this param in your ClubPenguin.swf object element

<param name="allowscriptaccess" value="always">

 

Here comes the server part:

KITSUNE : 

Place the content in the following archive in Kitsune/ClubPenguin/Plugins

FChat.rar

For other server emulators, just request me on comments I'll make one.

 

Here are some pictures of this chat

 

 

012400f898a74bc2a8e1bdbd6f416e92.png

Above is not the actual login UI, penguin is auto logged into the chat server.

8c3ae10e5a114d5ebf53e77506307a22.png

efcec00200f74a378ed6af1448242d75.png

 

 
 

FEATURES

  • Secured Login, the password used for each penguin on every login is one time usable and gets changed per every use
  • Auto logs you into PC
  • Very secured private Chat system
  • Supports encryption (encrypted message transfer)
  • 100% compatible with both AS2 and AS3
  • Supports emojis
  • Chats can be moderated, and also have mods with special privillages
  • Have it's own admin control panel
  • Highly stable, extensible, have various of useful plugins like Chat Filter we used in Flippr, but that didn't work out as it converted "hello" to "****o" lol.

 

If you have any doubt, issues, or concerns about this just comment.

While using this chat system or protocol you must give credits to me (dote) or flippr, to use this.

]]>
39Mon, 12 Jun 2017 13:14:20 +0000
[Sweater] How to change Bot namehttps://archive.solero.me/topic/57-sweater-how-to-change-bot-name/ Hey! Today ill show how to change bot name!

 

You will just need Notepad++

 

First go to your computer explorer, i will use Windows Explorer, and put this in the directory bar:

C:\xampp\htdocs\Sweater\Sweater\Plugins (if localhosted)

 

YourUsername/var/www/html/Sweater/Sweater/Plugins (if VPS)

 

Now right-click on the Bot.php and click "Edit with Notepad++"

 

Click "Find" and search for this line:    

const strPlayerName = 'Bot'; (line 11 if you can't search)

Change Bot to anything you want and save.

    

(Attention, don't edit commands.php, my bad.)

 

I hope it helped!

 

 

]]>
57Mon, 12 Jun 2017 23:07:06 +0000
Avoiding XSShttps://archive.solero.me/topic/53-avoiding-xss/ Really simple small 'tutorial'

Now as an example, I've seen people use this:

<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>"> <!-- form contents --> </form>

Please NEVER use this. This is vulnerable to XSS because there's no htmlentities. The xss code gets reflected and executed.

What you should do:

<form method="post" action="<?php echo htmlentities($_SERVER['PHP_SELF']); ?>">

What happens is that the htmlentities breaks the <script> part of a xss injection. The injection then will not work. 

You can also use strip_tags.

Example of mysql real escaping:

$xdd = $_POST["xdd"];

$xdd = mysqli_real_escape_string($xdd);

$xdd = htmlentities($xdd);

You can also add some javascript stuff to your register. I created some javascript register here: https://pastebin.com/EWs5RgSS

This includes:

Disallow special characters

Max length

Password generator

Email can only contain regex

These also make xss impossible

]]>
53Mon, 12 Jun 2017 21:23:08 +0000
Secure crossdomain.xmlhttps://archive.solero.me/topic/55-secure-crossdomainxml/ I've seen a lot of people also using insecure crossdomain.xml configs. This file is for domains that can connect etc. When it's insecure, an attacker can upload malicious SWF content.

 

Insecure crossdomain.xml:

<?xml version="1.0"?>
<cross-domain-policy>
<allow-access-from domain="*" />
</cross-domain-policy>

The following is a correct crossdomain.xml:

<?xml version="1.0"?>
<cross-domain-policy>
<allow-access-from domain="www.xdd.com" />
<allow-access-from domain="xdd.com" />
</cross-domain-policy>
]]>
55Mon, 12 Jun 2017 21:30:57 +0000
How to add custom music to your rooms without making it laghttps://archive.solero.me/topic/52-how-to-add-custom-music-to-your-rooms-without-making-it-lag/ This tutorial is only for AS2.

Things you need:

1. Flash cs6

2. Any decompiler will do

For this tutorial I am going to use SoThink decompiler.

Select any room and decompile it and open it in Flash CS6

http://prntscr.com/c23e5d

You'll see a layer named 'Script Layer' edit the white frame by right-clicking on it and then click Actions

http://prntscr.com/c23evs

You should see the actionscript after clicking Actions, after that scroll down until you dont find

triggers_mc._visible = false;

When found add this code before it:

var music = new Sound(this);
music.loadSound("3.mp3", true);
music.onSoundComplete = function ()
 {
	 music.start();
}

http://prntscr.com/c23gm5

When done export the file by executing  Ctrl + Alt + Shift + S on your keyboard.

Make sure the file is a mp3 file and  also make sure it is uploaded at your /play/ directory as it will only load songs from the play directory.

Enjoy!

]]>
52Mon, 12 Jun 2017 21:15:18 +0000
Secured Private Chat System (Server) [General]https://archive.solero.me/topic/38-secured-private-chat-system-server-general/ Let me just go directly ahead towards the topic. So here's it.

REQUIREMENTS / PREQUISITE:

  1. MySQL Server
  2. Apache / Nginx or any hostable server
  3. OpenFire
  4. ConverseJS or any XMPP-Chat Server

 

Making sure you meet all the prerequisite you may go ahead. 

  • First open MySQL and Apache (or server)
  • Second install openfire.
  • Third go to openfire web page.
    If you are running on localhost : http://localhost:9090/
    If you are running on any other VPS, then http://YourVPSIP:9090/
  • Now, install OpenFire. The installation if very user friendly if you have any difficulty in it comment below.
  • Now, go to OpenFire url (http://localhost-or-vps-ip:9090/)
  • Login to OF with username admin and password you entered during above installation
  • You would be welcomed with something like this
 


4508f0d6a8a84a03afbc1a57dcc436da.png

 
 


If so you are good to go :1_grinning: Now it's time to set up the chat community. Head up to Users/Groups Tab You would be presented with something like this. Click on Groups

 

 


18766aacc72a4b74b6e4758ec9bbd546.png

 
 

Now on the left hand side, below that group button you've pressed before, click on Create New Group Now here's where you actually set permissions and privillages for your chat clients.

For now we are only creating 1 group - Members. It's common to name it <Your CPPS Name>Members. Give it a Group Name, some description  and click Create Group

Let Contact List Sharing  be Disabled Don't care about those member's well deal with it to auto add those later. Now go to Plugins, Found on the tab list below openfire logo Make sure you have these plugins,
26ac30e21c9b4210af63049f93b4838e.png
You can find them on Available Plugins button to the left of listed plugins.If you have any trouble installing it comment below.

Now let's move on to Chatable Rooms Now Go to Group Chat tab on the tab pane below openfire logo Click Create New Room

Give the room a notable ID, NAME, Description, Topic, and it's upto to you for room options on the right side.
Anyway, here are the recommended settings, except for those name, id, desc etc. Then Click on Save Changes
1a6d99dad9c240bc8d27641440b8d9ec.png Once again go to Users/Groups tab, present below tab bar under openfire logo Click on Registration Properties button on the left side Make sure you have these 3 options checked
 

Enable welcome message.
Enable automatically adding of new users to a group.
Enable users to register via a web page at http://localhost:9090/plugins/registration/sign-up.jsp


Click Save settings

Scroll down until you reach Default Group

Now there's where you actually give your users the permission. 

Set that group to what you've created above, ie, <Your CPPS Name>Members.or anything according to what you've set

Click Save Group

Ok you are now done on the server part.

Let's register one test account. Got to http://localhost:9090/plugins/registration/sign-up.jsp or http://YourVPSIP:9090/plugins/registration/sign-up.jsp

Now you have to setup ConverseJS or any other XMPP Client

Once you download ConverseJS extract it into htdocs/chat. Now open http://localhost-or-vps-ip/chat/test/

You can login with username username@localhost eg, admin@localhost and password you set. Anyway you need to change the ip address in the index.php file in htdocs/chat. 

If you want to integrate that into CPPS do the following

Transfer or embed this ConverseJS into your play page

During penguin registration, make sure to send a get request to http://localhost:9090/plugins/registration/sign-up.jsp or http://YourVPSIP:9090/plugins/registration/sign-up.jsp

There will be a topic on integrating this into CPPS on a new Topic.

 

Thanks. Any doubt comment!

]]>
38Mon, 12 Jun 2017 13:10:42 +0000
[AS2/AS3] Friends Listhttps://archive.solero.me/topic/36-as2as3-friends-list/ Hey Solero! Let me start my journey with all time requested AS3-Friends List (Compatible with AS2 too)

CLIENT SIDED INSTRUCTIONS : 

Download this and paste it in media1/play/v2/client - https://i.succ.in/OIPCks8p.swf

Edit your dependencies accordingly, for example

Find 

{
	"id": "interface",
	"title": "Interface"
},

After that add

{
	"id" : "Friends",
	"title" : "Dote's Friends List"
},

And save it. Clear your cache.

 

SERVER SIDED INSTRUCTIONS:

1. KITSUNE:
Download this rar file - https://i.succ.in/OIqEBDxU.rar

Place the folder Fr into Kitsune/ClubPenguin/Plugins

That's it. You are good to go. Run your login and world server, others are all automated!

 

SCREENSHOT:

e5f98dac718a4085aa8b024edbe0cf4c.png

]]>
36Mon, 12 Jun 2017 13:05:23 +0000
Sub Party! (Vintage Penguin)https://archive.solero.me/topic/18-sub-party-vintage-penguin/ Hello!

Are you having a hard time finding all the items "hidden" under the Sub party on Vintage Penguin. Well if that is the case this is the post for you!

First off we have the sea belt, located at the book room, the coffee shop's second floor if you didn't know:

aa.PNG.62f9626d46470af72bbbed9282799581.PNG

Then we have the life vest located at the beach:

aaa.PNG.48ad0205bf08b3113ceb3af18a28c97f.PNG

Thirdly we have the yellow snorkel located in the forest:

a.PNG.d08e0f4de8d2ddf818354e5cea9f155e.PNG

As the last item we have the miners helmet, which already exsist in the mine, but it is now during the party also at the iceberg:

aaaa.PNG.d76d962f4075a9105accc2505f0d4aad.PNG

Last but not least we have the anchor pin, located at the cove, right besides me in the middle quite far down:

aaaaa.PNG.51db240b3cef39bd2bd3b39287396e25.PNG

Thanks for me! Hope this guide/tutorial help you!

]]>
18Sun, 11 Jun 2017 17:46:56 +0000