diff --git a/modules/skins.js b/modules/skins.js index f08c345..ad026ad 100644 --- a/modules/skins.js +++ b/modules/skins.js @@ -84,12 +84,23 @@ exp.resize_img = function(inname, size, callback) { }); }; -// returns "alex" or "steve" calculated by the +userId+ -exp.default_skin = function(userId) { - if (Number("0x" + userId[31]) % 2 === 0) { - return "alex"; - } else { +// returns "alex" or "steve" calculated by the +uuid+ +exp.default_skin = function(uuid) { + if (uuid.length <= 16) { + // we can't get the return "steve"; + } else { + // great thanks to Minecrell for research into Minecraft and Java's UUID hashing! + // https://git.io/xJpV + // MC uses `uuid.hashCode() & 1` for alex + // that can be compacted to counting the LSBs of every 4th byte in the UUID + // an odd sum means alex, an even sum means steve + // XOR-ing all the LSBs gives us 1 for alex and 0 for steve + var lsbs_even = parseInt(uuid[07], 16) ^ + parseInt(uuid[15], 16) ^ + parseInt(uuid[23], 16) ^ + parseInt(uuid[31], 16); + return lsbs_even ? "alex" : "steve"; } }; diff --git a/test/test.js b/test/test.js index 78b6b14..0af58e9 100644 --- a/test/test.js +++ b/test/test.js @@ -26,6 +26,30 @@ var names = fs.readFileSync("test/usernames.txt").toString().split(/\r?\n/); var uuid = uuids[Math.round(Math.random() * (uuids.length - 1))]; var name = names[Math.round(Math.random() * (names.length - 1))]; + +// Let's hope these will never be assigned +var steve_ids = [ + "fffffff0"+"fffffff0"+"fffffff0"+"fffffff0", + "fffffff0"+"fffffff0"+"fffffff1"+"fffffff1", + "fffffff0"+"fffffff1"+"fffffff0"+"fffffff1", + "fffffff0"+"fffffff1"+"fffffff1"+"fffffff0", + "fffffff1"+"fffffff0"+"fffffff0"+"fffffff1", + "fffffff1"+"fffffff0"+"fffffff1"+"fffffff0", + "fffffff1"+"fffffff1"+"fffffff0"+"fffffff0", + "fffffff1"+"fffffff1"+"fffffff1"+"fffffff1", +]; +// Let's hope these will never be assigned +var alex_ids = [ + "fffffff0"+"fffffff0"+"fffffff0"+"fffffff1", + "fffffff0"+"fffffff0"+"fffffff1"+"fffffff0", + "fffffff0"+"fffffff1"+"fffffff0"+"fffffff0", + "fffffff0"+"fffffff1"+"fffffff1"+"fffffff1", + "fffffff1"+"fffffff0"+"fffffff0"+"fffffff0", + "fffffff1"+"fffffff0"+"fffffff1"+"fffffff1", + "fffffff1"+"fffffff1"+"fffffff0"+"fffffff1", + "fffffff1"+"fffffff1"+"fffffff1"+"fffffff0", +]; + var rid = "TestReqID: "; function getRandomInt(min, max) { @@ -107,13 +131,9 @@ describe("Crafatar", function() { }); }); describe("Avatar", function() { - // profile "Alex" - hoping it'll never have a skin - var alex_uuid = "ec561538f3fd461daff5086b22154bce"; - // profile "Steven" (Steve doesn't exist) - hoping it'll never have a skin - var steven_uuid = "b8ffc3d37dbf48278f69475f6690aabd"; - it("uuid's account should exist, but skin should not", function(done) { - networking.get_profile(rid, alex_uuid, function(err, profile) { + // profile "Alex" - hoping it'll never have a skin + networking.get_profile(rid, "ec561538f3fd461daff5086b22154bce", function(err, profile) { assert.notStrictEqual(profile, null); networking.get_uuid_url(profile, 1, function(url) { assert.strictEqual(url, null); @@ -121,14 +141,24 @@ describe("Crafatar", function() { }); }); }); - it("odd UUID should default to Alex", function(done) { - assert.strictEqual(skins.default_skin(alex_uuid), "alex"); - done(); - }); - it("even UUID should default to Steve", function(done) { - assert.strictEqual(skins.default_skin(steven_uuid), "steve"); - done(); - }); + for (var a in alex_ids) { + var alex_id = alex_ids[a]; + (function(alex_id) { + it("UUID " + alex_id + " should default to Alex", function(done) { + assert.strictEqual(skins.default_skin(alex_id), "alex"); + done(); + }); + })(alex_id); + } + for (var s in steve_ids) { + var steve_id = steve_ids[s]; + (function(steve_id) { + it("UUID " + steve_id + " should default to Steve", function(done) { + assert.strictEqual(skins.default_skin(steve_id), "steve"); + done(); + }); + })(steve_id); + } }); describe("Errors", function() { it("should time out on uuid info download", function(done) {