Giter Club home page Giter Club logo

skin-spec's Introduction

skin-spec

A technical specification for Minecraft skins, for developers.

Image Format

Skins are served in a PNG format from Mojang's servers. There is no enforced colour space of images; you should convert explicitly if you need to ensure that a skin is in some standard format. For example:

skinImg, format, err := image.Decode(imageBuffer)

if format != "NRGBA" {
    bounds := skinImg.Bounds()
    skinImg = image.NewNRGBA(bounds)
    draw.Draw(out.(draw.Image), bounds, skinImg, image.Pt(0, 0), draw.Src)
}

Image Dimension

The image dimensions may either be 64 by 32 pixels or 64 by 64 pixels. The former was used by Minecraft versions prior to 1.8, while the latter is accepted by Minecraft versions 1.8 and onwards. Positions of elements remain the same in 1.8, however there have been new "overlays" added to the skins file.

The following image, from the Minecraft Wiki, is a useful reference. Portions included in the pre-1.8 skin version are solid coloured, new elements are checkered.

Transparency

Transparency must be based off of the upper left-hand pixel of the image. Though many skins use standard the standard alpha-channel, others use a solid matte.

Note that mattes should only be removed for components marked "transparent" below, otherwise you may remove actual skin data.

We use the following code in Minotar to "fix" the matte after converting the image to NRGBA, where skin.AlphaSig is simply a four-element slice of the image's first pixel's data:

// Removes the skin's alpha matte from the given image.
func (skin *mcSkin) removeAlpha(img *image.NRGBA) {
    // If it's already a transparent image, do nothing
    if skin.AlphaSig[3] == 0 {
        return
    }

    // Otherwise loop through all the pixels and fix em
    for i := 0; i < len(img.Pix); i += 4 {
        if img.Pix[i+0] == skin.AlphaSig[0] &&
            img.Pix[i+1] == skin.AlphaSig[1] &&
            img.Pix[i+2] == skin.AlphaSig[2] &&
            img.Pix[i+3] == skin.AlphaSig[3] {
            img.Pix[i+3] = 0
        }
    }
}

Skin Serving

Prior to 1.8 Release

Originally Mojang used Amazon S3 to host all their skins: http://s3.amazonaws.com/MinecraftSkins/%Username%.png

Valid users (case sensitve) will still respond with an archived skin PNG which has Content-Type: application/octet-stream and are sent with accurate Last-Modified timestamps that can assist with caching.

Invalid users will result in a 403 Forbidden and a Content-Type: application/xml error code of AccessDenied. This will likely also happen for users which were created after the switch away from S3 and therfore don't have a skin hosted there (untested).

Since 1.8 Release

Along with the switch to UUID, Mojang has made efforts to reduce the cost and strain on their infrastructure by making the multiplayer Minecraft server deliver the skin/textures to the client (after being verified by Mojang). CloudFront is more heavily used as well as EC2 app servers for returning JSON data. S3 is (likely) still used for backend storage of textures.

Initial Client side lookups are made to EC2 app servers behind a CloudFront endpoint (not caching, but reducing the impact of malicoius traffic) https://sessionserver.mojang.com/session/minecraft/profile/%UUID% with the response Content-Type: application/json:

{  
    "id":"d9135e082f2244c89cb0bee234155292",
    "name":"clone1018",
    "properties":[  
        {  
            "name":"textures",
            "value":"eyJ0aW1lc3RhbXAiOjE0MjAzMDE3MzYwMjIsInByb2ZpbGVJZCI6ImQ5MTM1ZTA4MmYyMjQ0Yzg5Y2IwYmVlMjM0MTU1MjkyIiwicHJvZmlsZU5hbWUiOiJjbG9uZTEwMTgiLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q5Y2E1NWU5ODYyZjAwM2ViZmExODcyYTkyNDRhZDVmNzIxZDZiOWU2ODgzZGQxZDQyZjg3ZGFlMTI3NjQ5In19fQ=="
        }
    ]
}

The sessionserver.mojang.com domain uses very aggressive rate-limiting and will return a 429 429 [sic] if the same UUID is requested from the same source IP within 30 seconds.

The textures property is Base64 encoded string which decodes into JSON (%TimeStamp% is milliseconds since epoch):

{  
    "timestamp":%TimeStamp%,
    "profileId":"d9135e082f2244c89cb0bee234155292",
    "profileName":"clone1018",
    "textures":{  
        "SKIN":{  
            "url":"http://textures.minecraft.net/texture/cd9ca55e9862f003ebfa1872a9244ad5f721d6b9e6883dd1d42f87dae127649"
        }
    }
}

The textures object can have a SKIN and/or CAPE object, each with a url inside.

Mojang have also implemented a similar style lookup address as they had with S3 which allows usernames lookups to be made to: http://skins.minecraft.net/MinecraftSkins/%Username%.png

The response for a valid user (case insensitive) is a 301 Moved Permanently redirect to http://textures.minecraft.net/texture/%TextureHash% which then serves the skin as Content-Type: image/png.

The response for invalid users is a 404 Not Found and all requests (valid or not) are cached by CloudFront.

The %TextureHash% appears to be a hash of the skin which is then linked to the account.

It is worth noting that there appears to be no optimization of the skins being served from the textures.minecraft.net address. In some instances where someone has used certain programs which add large amounts of meta data to the PNGs, you can easily end up with skins 10x the size. This is worth considering if you plan to cache them.

Components

The following is a list of components of the Minecraft skin.

  • Version number indicates the version the component is present in. "0" indicates all versions.
  • Coordinates is in the format (x1, y1, x2, y1) indicating the coordinate bounds the component can be found in the skin.
  • Transparent indicates
Version Part Side Coordinates Transparent
0 Head Top (8, 0, 16, 8)
0 Head Bottom (16, 0, 24, 8)
0 Head Right (0, 8, 8, 16)
0 Head Front (8, 8, 16, 16)
0 Head Left (16, 8, 24, 16)
0 Head Back (24, 8, 32, 16)
0 Helm Top (40, 0, 48, 8)
0 Helm Bottom (48, 0, 56, 8)
0 Helm Right (32, 8, 40, 16)
0 Helm Front (40, 8, 48, 16)
0 Helm Left (48, 8, 56, 16)
0 Helm Back (56, 8, 64, 16)
0 Right Leg Top (4, 16, 8, 20)
0 Right Leg Bottom (8, 16, 12, 20)
0 Right Leg Right (0, 20, 4, 32)
0 Right Leg Front (4, 20, 8, 32)
0 Right Leg Left (8, 20, 12, 32)
0 Right Leg Back (12, 20, 16, 32)
0 Torso Top (20, 16, 28, 20)
0 Torso Bottom (28, 16, 36, 20)
0 Torso Right (16, 20, 20, 32)
0 Torso Front (20, 20, 28, 32)
0 Torso Left (28, 20, 32, 32)
0 Torso Back (32, 20, 40, 32)
0 Right Arm Top (44, 16, 48, 20)
0 Right Arm Bottom (48, 16, 52, 20)
0 Right Arm Right (40, 20, 44, 32)
0 Right Arm Front (44, 20, 48, 32)
0 Right Arm Left (48, 20, 52, 32)
0 Right Arm Back (52, 20, 56, 32)
1.8 Left Leg Top (20, 48, 24, 52)
1.8 Left Leg Bottom (24, 48, 28, 52)
1.8 Left Leg Right (16, 52, 20, 64)
1.8 Left Leg Front (20, 52, 24, 64)
1.8 Left Leg Left (24, 52, 28, 64)
1.8 Left Leg Back (28, 52, 32, 64)
1.8 Left Arm Top (36, 48, 40, 52)
1.8 Left Arm Bottom (40, 48, 44, 52)
1.8 Left Arm Right (32, 52, 36, 64)
1.8 Left Arm Front (36, 52, 40, 64)
1.8 Left Arm Left (40, 52, 44, 64)
1.8 Left Arm Back (44, 52, 48, 64)
1.8 Right Leg Layer 2 Top (4, 48, 8, 36)
1.8 Right Leg Layer 2 Bottom (8, 48, 12, 36)
1.8 Right Leg Layer 2 Right (0, 36, 4, 48)
1.8 Right Leg Layer 2 Front (4, 36, 8, 48)
1.8 Right Leg Layer 2 Left (8, 36, 12, 48)
1.8 Right Leg Layer 2 Back (12, 36, 16, 48)
1.8 Torso Layer 2 Top (20, 48, 28, 36)
1.8 Torso Layer 2 Bottom (28, 48, 36, 36)
1.8 Torso Layer 2 Right (16, 36, 20, 48)
1.8 Torso Layer 2 Front (20, 36, 28, 48)
1.8 Torso Layer 2 Left (28, 36, 32, 48)
1.8 Torso Layer 2 Back (32, 36, 40, 48)
1.8 Right Arm Layer 2 Top (44, 48, 48, 36)
1.8 Right Arm Layer 2 Bottom (48, 48, 52, 36)
1.8 Right Arm Layer 2 Right (40, 36, 44, 48)
1.8 Right Arm Layer 2 Front (44, 36, 48, 48)
1.8 Right Arm Layer 2 Left (48, 36, 52, 48)
1.8 Right Arm Layer 2 Back (52, 36, 64, 48)
1.8 Left Leg Layer 2 Top (4, 48, 8, 52)
1.8 Left Leg Layer 2 Bottom (8, 48, 12, 52)
1.8 Left Leg Layer 2 Right (0, 52, 4, 64)
1.8 Left Leg Layer 2 Front (4, 52, 8, 64)
1.8 Left Leg Layer 2 Left (8, 52, 12, 64)
1.8 Left Leg Layer 2 Back (12, 52, 16, 64)
1.8 Left Arm Layer 2 Top (52, 48, 56, 52)
1.8 Left Arm Layer 2 Bottom (56, 48, 60, 52)
1.8 Left Arm Layer 2 Right (48, 52, 52, 64)
1.8 Left Arm Layer 2 Front (52, 52, 56, 64)
1.8 Left Arm Layer 2 Left (56, 52, 60, 64)
1.8 Left Arm Layer 2 Back (60, 52, 64, 64)

skin-spec's People

Contributors

connor4312 avatar jomo avatar lukehandle avatar xtrafrancyz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

skin-spec's Issues

Client lookups

Are you sure it uses the username and follows the redirect?


AFAIK it makes a request to https://sessionserver.mojang.com/session/minecraft/profile/<uuid>.
e.g:
https://sessionserver.mojang.com/session/minecraft/profile/8ce569f21658431f96d4fd4db501505d

{
  "id": "8ce569f21658431f96d4fd4db501505d",
  "name": "connor4312",
  "properties": [
    {
      "name": "textures",
      "value": "eyJ0aW1lc3RhbXAiOjE0MjAyMzk1NjUxNTQsInByb2ZpbGVJZCI6IjhjZTU2OWYyMTY1ODQzMWY5NmQ0ZmQ0ZGI1MDE1MDVkIiwicHJvZmlsZU5hbWUiOiJjb25ub3I0MzEyIiwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2U3NzQ1NWI5ZjEzNzc4ZjI1NGNjOGYzYTUyYTNjZWQ0NGI4ZTk0OTFkMzEzYjEzOGRlOWVkNWE4NzgwNzIifX19"
    }
  ]
}

The textures property is Base64 encoded JSON (I guess that's what they store in their DB). When decoded, it looks like this:

{
  "timestamp": 1420239565154,
  "profileId": "8ce569f21658431f96d4fd4db501505d",
  "profileName": "connor4312",
  "textures": {
    "SKIN": {
      "url": "http://textures.minecraft.net/texture/e77455b9f13778f254cc8f3a52a3ced44b8e9491d313b138de9ed5a878072"
    }
  }
}

the textures object can have a SKIN and/or CAPE object. I haven't seen any other keys than url here.

rate limited = Steve?

the response for being rate limited is the default Steve skin.

this doesn't seem to be right.
I just tried

while true
    curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" "http://skins.minecraft.net/MinecraftSkins/Notch.png" &
end

and never ran into any sort of rate limits nor did it redirect to a steve skin.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.