webdevRefinery Forum: Connecting to a server with Node.js - webdevRefinery Forum

Jump to content

  • 2 Pages +
  • 1
  • 2
  • You cannot start a new topic
  • You cannot reply to this topic

User is offline Cyril 

  • Group: Members
  • Posts: 2545
  • Joined: 03-August 10
  • Expertise:HTML,CSS,PHP,Javascript,Graphics

Posted 31 March 2012 - 04:56 AM (#21)

GOTCHA.

Posted Image

Posted Image

N°9 is one byte shorter.
However, if I add it manually, it gives me a protocol error with bad packet ID 29 (which is retarded, since the protocol didn't change, and still is 29, like the official client).

HOLY GUACAMOLE IT WORKS. WOOOOOOOOHOOOHOOOO I LOVE YOU PEOPLE (Well, Kyek) :D
/me goes to run around the house a few times, celebrating victory.

I added an extra parameter which appends a byte to the buffer, fixed it :D

Probably won't hear of my for some time now, I've got 68 "events" to implement (well, I'll obviously create a "void" function and throw some of them in there...).


Second Edit: Less important (right now -- I still have to handle things like the user ID, position, etc) but how should I "store" the chunks? (The chunks are bits of the map. Corresponding docs: http://wiki.vg/Map_Format)

website :: github :: twitter :: dribbble :: forrst
html, css, php, javascript, graphics
0


User is offline Kyek 

  • Founder of wdR
  • Group: Administrators
  • Posts: 5081
  • Joined: 20-February 10
  • LocationPhiladelphia, PA, USA
  • Expertise:HTML,CSS,PHP,Java,Javascript,Node.js,SQL

Posted 31 March 2012 - 01:56 PM (#22)

Ok, before I get into what you asked, let me draw your attention to the Data Types page :). Look at the "string" type. It's UCS-2 -- and *THAT* is why there's a null byte between each character :) If I may draw your attention to the Buffer.write() function: http://nodemanual.or...ml#Buffer.write The very last param will accept "ucs2" as the text formatting. Bam, no more for-loop for writing strings! UCS-2 is traditionally little-endian (meaning, for sake of simplicity, bytes are written in reverse order) but Minecraft treats them as big-endian. So you'll just have to shift a NULL byte from one end to the other. Easy peasey :).

With that said, I'm gonna respond to what you sent me on here, because I don't have iMessage on my laptop yet xD

Quote

I have a buffer like this:
04 00 00 00 00 00 00 97 da


How do I split that into "04" and "151" (which is the sum of the other bytes, in this case, to update the time).

Looking at the docs for the 0x04 command, you have a nine-byte packet. The first byte is the command (obviously) and the last 8 bytes are a Long. So you're not looking to sum up the bytes, you're looking to read them all together as one single number.

Node supports longs, but unfortunately, the Buffer object does not for some strange reason. So instead of calling something like Buffer.readInt64BitBE() or Buffer.readLongBE(), which would be convenient, you're going to have to roll your own. Something like this might do it:
function readLong(buffer, offset) {
    var longNum = 0;
    for (var i = 0; i < 8; i++) {
        // Here, we shift any already-read bits to the left 8 places.  It's like taking the number 1 and making it 10,
        // only we're taking something like 0xe4 and making it 0xe400
        longNum *= 0x100;
        // Now that the final byte of our long is 0x00, we overwrite it with the next digit.
        longNum += buffer[offset + i];
    }
    return longNum;
}

Then you can just call
var time = readLong(buffer, 1);
And you should have your time! This assumes that times can never be negative; according to the docs, "long" values are signed... so if the time can be negative in Minecraft, that function will leave you witha HUGE number. But twos compliment is the subject of another post :). As long as it can't be negative, you're good with that.

I haven't looked at the docs for map parsing yet, but I already told you my approach of saving it all in a 3d array :) If I get a chance to dig that far into the docs, I'll let you know if that changes my mind.
1


User is offline Cyril 

  • Group: Members
  • Posts: 2545
  • Joined: 03-August 10
  • Expertise:HTML,CSS,PHP,Javascript,Graphics

Posted 31 March 2012 - 02:36 PM (#23)

Thanks a lot, especially for the data values link -- didn't see that at first :)
I'll try to figure this out on my own for a bit, you've already helped heaps. Thanks a lot, again :)
(I feel kinda guilty, like I'm leaching off you :/)

I'm thinking the 3D array would be a good idea as well, now,
chunk[x][y][z] = 0x00;
or something similar. I still have to finish off some other bits before even thinking of trying to move my player around, let alone read through chunks to see where there are obstructions.

Rewritting most of my code; thanks for the tip on the UCS-2 encoding :D I just ditched that whole function - just made my life a lot easier, and could have avoided the problem I told you about on IM.


EDIT: OOOOH, suddenly it makes sense. Instead of putting random 0x00 bytes, I should actually be writing an int -> buf.writeUInt32 :D

website :: github :: twitter :: dribbble :: forrst
html, css, php, javascript, graphics
0


User is offline Cyril 

  • Group: Members
  • Posts: 2545
  • Joined: 03-August 10
  • Expertise:HTML,CSS,PHP,Javascript,Graphics

Posted 31 March 2012 - 03:47 PM (#24)

This was too good to be true :(

The errors are somewhat random - it sometimes manages to connect. Sometimes it manages to connect but with a buggy string (some of the chars get changed), and the rest of the time it errors out with:

java.io.IOException: Received string length longer than maximum allowed (38 > 16)


The server code isn't responsible for anything anymore (except events, but that works fine), so here's the rest:
(yeah, I have a thing with alignment)

var Server = require("./server").Server;

function Player(server, name) {

	this.info = {
		username: name,
		host: server.host,
		port: server.port,
		version: 1.2,
		protocol: 0x1D,
		online: [],
		time: 0,
		gamestate: false
	};

	this.events = {
		0x02: this.login.bind(this),
		0x01: this.accepted.bind(this),
		0xFF: this.kicked.bind(this)
	};
	
	this.server = new Server(server, this.events);
	
}

Player.prototype.start = function() {
	var string = "\0" + this.info.username + ";" + this.info.host + ":" + this.info.port;
	
	var buffer = new Buffer(string.length * 2 + 3);
	    buffer.writeUInt8(0x02, 0);
	    buffer.writeUInt16BE(string.length, 1);
	    buffer.write(string, 3, string.length, "ucs2");
	
	this.server.socket.write(buffer);
};

Player.prototype.login = function(data) {
	var buffer = new Buffer(this.info.username.length * 2 + 20);
	    buffer.writeUInt8(0x01, 0);
	    buffer.writeUInt32BE(this.info.protocol, 1);
	    buffer.writeUInt16BE(this.info.username.length, 5);
	    buffer.write(this.info.username, 8, this.info.username.length * 2, "ucs2");
	
	var bufferIndex = 8 + this.info.username.length * 2;
	
	buffer.write("\0", bufferIndex++, 1, "ucs2");
	buffer.writeUInt32BE(0x00, bufferIndex++);
	buffer.writeUInt32BE(0x00, bufferIndex++);
	buffer.writeUInt8(0x00, bufferIndex++);
	buffer.writeUInt8(0x00, bufferIndex++);
	buffer.writeUInt8(0x00, bufferIndex++);
	
	console.log(buffer);
	
	this.server.socket.write(buffer);
};

Player.prototype.accepted = function(data) {
	var buffer = new Buffer(2);
	    buffer.writeUInt8(0x0A, 0);
	    buffer.writeUInt8(0x01, 1);
	
	this.server.socket.write(buffer);
};


The code started to bug around when I added the Player.login method. (which corresponds to the sending of http://wiki.vg/Proto...uest_.280x01.29)

Edit: there it goes again. Managed to get it to run once (but the first character was changed to something weird), and now nothing.

Edit: Yeah, and that 38 in the error? Well, it changes. 36, 35, 38, etc.

Edit: Okay, once again. Managed to get it to login to the game -- but the first character of the username (in my case, Robot) was changed to something funky. Didn't even touch the code.

Edit: Dafuq :( Worked fine. Connected to the server fine, username is fine. I stopped the thing, tried again, same error as above, now with 49 as the number

website :: github :: twitter :: dribbble :: forrst
html, css, php, javascript, graphics
0


User is offline Kyek 

  • Founder of wdR
  • Group: Administrators
  • Posts: 5081
  • Joined: 20-February 10
  • LocationPhiladelphia, PA, USA
  • Expertise:HTML,CSS,PHP,Java,Javascript,Node.js,SQL

Posted 31 March 2012 - 04:24 PM (#25)

You have a handful of things that aren't adding up, here. For instance, here:

        var string = "\0" + this.info.username + ";" + this.info.host + ":" + this.info.port;
        
        var buffer = new Buffer(string.length * 2 + 3);
            buffer.writeUInt8(0x02, 0);
            buffer.writeUInt16BE(string.length, 1);
            buffer.write(string, 3, string.length, "ucs2");


You're adding a null byte to the beginning of the string, and then writing that string in UCS2 -- which means it won't come out as
00 f 00 i 00 r 00 e
and so on, it'll come out as
00 00 f 00 i 00 r 00 e
. Node.js's UCS2 is little-endian, meaning that your 'f' will come BEFORE the null byte, instead of after. I'm guessing that's why you preceded it with a null byte, but using UCS2 will mean that the \0 also gets another \0 added to it. It might be a good idea to write your own UCS2 Big-endian converter for this -- similarly to your old loop, but supporting multibyte characters as needed (if you run into those at all).

Moving on:
var buffer = new Buffer(this.info.username.length * 2 + 20);
            buffer.writeUInt8(0x01, 0);
            buffer.writeUInt32BE(this.info.protocol, 1);
            buffer.writeUInt16BE(this.info.username.length, 5);
            buffer.write(this.info.username, 8, this.info.username.length * 2, "ucs2");

Ok, so you write a UInt8 at byte 0 -- that 8 is 8 bits, which is one byte. Now we're up to byte #1.
Then we write a UInt32 starting at byte 1. 32 bits / 8 bits = 4 bytes. So we're now up to byte #5.
Then we write a UInt16 starting at byte 5. 16 bits / 8 bits = 2 bytes. So now we're at byte #7.
Then you start writing your string at byte ... 8! I'm imagining you're doing this because you need the UCS2 string to appear big-endian, but the problem with that is that you have no guarantee that the byte you're skipping is null. In a perfect world it would be, but that's where you're getting your randomness of the errors. You just mentioned to me that you're writing in the null bit and it sounds like that would fix it, so props there :). I just wanted to explain why that was happening. You could also call the buffer.fill() function to fill the buffer with zeros, but just writing each byte manually is better practice.

I'm headed out, but that's as far as I got -- good luck :)
0


User is offline Cyril 

  • Group: Members
  • Posts: 2545
  • Joined: 03-August 10
  • Expertise:HTML,CSS,PHP,Javascript,Graphics

Posted 31 March 2012 - 04:32 PM (#26)

Thanks :) Yep, writing in the null directly fixed the problem. I'm off (to bed :P) as well, so I'll see what other problem arises tomorrow...

Spent two full days on this - might take a break as well, maybe PHP a bit. Going to get me some sweet exceptions (guess who I got that idea from :P)

website :: github :: twitter :: dribbble :: forrst
html, css, php, javascript, graphics
0


User is offline Cyril 

  • Group: Members
  • Posts: 2545
  • Joined: 03-August 10
  • Expertise:HTML,CSS,PHP,Javascript,Graphics

Posted 02 April 2012 - 12:17 PM (#27)

Quick update to this :)
I've decided to Github this, so anyone can help me out, check out the code. Doesn't do much right now except listen for chat messages, but hey, more to come soon :)

website :: github :: twitter :: dribbble :: forrst
html, css, php, javascript, graphics
0


User is offline Cyril 

  • Group: Members
  • Posts: 2545
  • Joined: 03-August 10
  • Expertise:HTML,CSS,PHP,Javascript,Graphics

Posted 03 April 2012 - 02:23 PM (#28)

Ooookay, so it's done with the questions about the packets, got all that figured, my code works. (Well, it should).

Which introduces only one other problem (that I can think of right now): organisation!
Let's imagine I have every single method that can be called, with every possible corresponding packet ID:

0x00 -> keepAlive -> Some keepAlive code
etc...


So I know every event, every command that can happen, and I have the code to handle it.
How can I put it all together, in an efficient way? (This includes file structure / the way the functions (/objects) would work)

How do I go from receiving data from a socket to calling the corresponding method?
A thing that should be noted as well, is that I'm planning on having a second layer of events on top;

robot.on("chat", function(message) {
    console.log("Received Chat Message: " + message);
});


My current method I find ugly; my code doesn't even do anything (well, it can talk now, but still) and it's already at 250 lines.

website :: github :: twitter :: dribbble :: forrst
html, css, php, javascript, graphics
0


User is offline Kyek 

  • Founder of wdR
  • Group: Administrators
  • Posts: 5081
  • Joined: 20-February 10
  • LocationPhiladelphia, PA, USA
  • Expertise:HTML,CSS,PHP,Java,Javascript,Node.js,SQL

Posted 03 April 2012 - 03:08 PM (#29)

Hokay. So, organization, now that I've had time to think about it a bit more:

I would, first, have an instantiatable class called something like MCBufferReader. This class has methods to read Minecraft's data type, and after each call, saves the byte offset where it stopped reading. So it has readLong, readString, readInt, etc etc etc, abstracting out alll of the byte-level work.

Then you have a map of every single event type to the data associated with it. Something like (and this packet is TOTALLY made up):
{
    0x02: {
        event: "login",
        handler: Player,
        fields: [
            { username: "string" },
            { loginTime: "long" }
        ]
    }
}

Now, when a 0x02 packet comes in, you pull it out of your map, and you feed it to MCBufferReader. MCBufferReader iterates through the array of names/types, and reads each one from the buffer in order. At the end of it, it returns a javascript object like this:
{
    username: "Firecat",
    loginTime: 56782194823
}

The input processor tacks on the event name (and any other metadata you might need) so it becomes this:
{
    event: "login",
    fields: {
        username: "Firecat",
        loginTime: 56782194823
    }
}

And now it calls Player.handle(input). Player is the function stored in 'handler' above, input is the object in that last code block.

Bam. Instant sexiness. Now each of your main objects has a handle() method that looks at the event name and dispatches accordingly. OR, you could put a function in each main object , with the same name as any event it can handle. Then you just call
this[handler][event](obj);
and everything is even more automated.

Of course, that assumes that each event only needs to be consumed by one object. If an event occurs that should be observed by the Player, and everything in an NPCs array, and maybe the World object, then you'll want to use the Event Listener pattern and call something like
this.fire(event, data);
. Then you can use Node.js's EventEmitter object to make your life smooth and easy. Actually, now that I'm thinking about it more, that might even be the better way to do it regardless. Your call :)
0


User is offline Cyril 

  • Group: Members
  • Posts: 2545
  • Joined: 03-August 10
  • Expertise:HTML,CSS,PHP,Javascript,Graphics

Posted 15 April 2012 - 10:13 AM (#30)

Quick question -- how would a "buffer.writeLong" method work?
I need it to implement the protocol, but it doesn't exist by default (readLong works great, thanks Kyek) :)

website :: github :: twitter :: dribbble :: forrst
html, css, php, javascript, graphics
0


User is offline Kyek 

  • Founder of wdR
  • Group: Administrators
  • Posts: 5081
  • Joined: 20-February 10
  • LocationPhiladelphia, PA, USA
  • Expertise:HTML,CSS,PHP,Java,Javascript,Node.js,SQL

Posted 16 April 2012 - 02:41 PM (#31)

Here's my go at it -- this SO is not tested xD

function writeLong(long) {
	var BYTE_LENGTH = 8,
		bytes = [];
	while (long > 0) {
		bytes.unshift(long % 0x100);
		long = Math.floor(long / 0x100);
	}
	if (bytes.length > BYTE_LENGTH)
		throw new Error('Long is too long!');
	while (bytes.length < BYTE_LENGTH)
		bytes.unshift(0);
	// Bam, now the 'bytes' array contains an array of bytes in Big Endian.
	// Loop from 0-7 and write them sequentially as unsigned 8-bit integers.
}


Let me know if there's anything you don't understand about how that works, and I'd be happy to go into detail :)
1


User is offline Cyril 

  • Group: Members
  • Posts: 2545
  • Joined: 03-August 10
  • Expertise:HTML,CSS,PHP,Javascript,Graphics

Posted 17 April 2012 - 11:28 AM (#32)

Thanks, that works great :) And yeah, I understand how it works (thanks to your IM explanation xD)

Okay, *yet another thing* :/
I'm writing a BufferWrite function, and it works fine. Well, nearly; the server reads UCS2-BE, but Node.js only supports encoding it as UCS2-LE.
The result is, at the moment, I get exactly the right number of characters, but they appear as chinese (like, chinese characters).

From what I've read, that means I have to "swap" the bytes - what does that mean though? I get that you would have to reorder the bytes from LE to BE, but how does that work?
In my case, I'm writing to a buffer that already has some previous (non-string) data, so I can't just create a new buffer and reorder the whole thing in a loop.

The other problem I'm having with UCS2 is that when I get a string from the server, while I can read it fine, it stays UCS2, which means having a NULL char between each character. (
\u0000
)

website :: github :: twitter :: dribbble :: forrst
html, css, php, javascript, graphics
0


User is offline Kyek 

  • Founder of wdR
  • Group: Administrators
  • Posts: 5081
  • Joined: 20-February 10
  • LocationPhiladelphia, PA, USA
  • Expertise:HTML,CSS,PHP,Java,Javascript,Node.js,SQL

Posted 17 April 2012 - 12:00 PM (#33)

Here, just like with the Long types, you're going to want to write your own Read and Write functions. Your bread and butter, here, are unsigned 8-bit integers. Read one, and all you get is a single byte, unmodified, in INT form -- so you can do whatever math with it that you like. By writing them, you can construct whatever packet you want.

For things like reading UCS2 or things like that, you can use buffers as a go-between. Reading a 10-character string? Create a 20-byte buffer. Read two bytes from the input stream, write the second to myBuffer[0] and the first to myBuffer[1]. Read the next two bytes. Write the second to myBuffer[2] and the first to myBuffer[3]. Keep going, and now you can read UCS2 from that buffer and return it. You could write a UCS2 string the same way -- write it to a new buffer using the built-in node function, and just loop through every two bytes and reverse them when you write them to the output buffer.

Easy peasy :)
0


User is offline ianonavy 

  • Group: Members
  • Posts: 689
  • Joined: 14-April 10
  • Expertise:HTML,CSS,Java,Javascript,Python

Posted 10 July 2012 - 06:58 PM (#34)

I know that I'm bumping an old thread, but I found this project while I was looking for a Node.js to map subdomains to different ports on the same IP address (mc1.domain.com -> localhost:25566 and mc2.domain.com localhost:25567).

This is MUCH more interesting. Just out of curiosity, how did you handle the fact that servers check for an existing login session at minecraft.net unless they're on offline mode?

Also, it's not on your GitHub anymore. :(
reputation += 1 if post.is_helpful else 0
0


Share this topic:


  • 2 Pages +
  • 1
  • 2
  • You cannot start a new topic
  • You cannot reply to this topic

3 User(s) are reading this topic
0 members, 3 guests, 0 anonymous users


Enter your sign in name and password


Sign in options
  Or sign in with these services