Neopets system includes
The Neopets system includes are a set of Flash SWFs that are included and used by the Flash games.
❗ This page is going to suck until I better understand Flash development and reverse engineering. ❗
Old versions
The promotional disc[1] has the following old versions of the system includes:
- /system/bios.swf, 6441 bytes, modified 2007-06-04
- /system/np6_include_v7.swf, 49408 bytes, modified 2007-03-06
- /system/np8_include_v7.swf, 54949 bytes, modified 2007-03-06
The following loaders seem to download the following versions:
Game | Game ID | Loader | Loader Bytes | Loader Modified | Include | Include Bytes | Loader Modified |
---|---|---|---|---|---|---|---|
Kass Basher | 381 | https://images.neopets.com/games/gaming_system/np6_loader_v2_24.swf | 3200 | 2010-01-20 | https://images.neopets.com/games/gaming_system/np6_include_v16.swf | 50030 | 2010-08-18 |
Meerca Chase II | 500 | https://images.neopets.com/games/gaming_system/np8_loader_v3_24.swf | 3924 | 2010-01-21 | https://images.neopets.com/games/gaming_system/np6_include_v29.swf | 180965 | 2018-06-21 |
There seems to be only one accessible Flash dictionary version.
- https://images.neopets.com/games/utilities/flash_dictionary/flash_dictionary_en_v62.swf, 211325 bytes, modified 2008-09-29
Page Variables
These are injected into the SWFObject for the loader via JavaScript.
Key | Example Value | Default in np6_loader_v2_24 | Meaning |
---|---|---|---|
p | ml_haunted_woods | preloader_v1_en | Determines the preloader to load |
internetexplorer | 0 | Assuming earlier versions of IE had issues with SWF <---> Webpage interaction that needed workarounds? | |
g | g381_v58_67047 | g53_v22 | Basename of the actual game SWF; append this to https://images.neopets.com/games/ and add ".swf" |
id | 381 | 53 | Numeric ID of the game |
f | 24 | 24 | Does not appear to be the game framerate, since Kass Basher's SWF is 24 FPS but has this set to 18. However, the loader SWF itself does have a framerate of 24 in its header. Maybe this refers to that? |
v | 58 | 22 | Version of the game SWF |
n | 0.42 | 0 | Score-to-Neopoint ratio |
c | 1000 | 1000 | Cap on Neopoints per score submission |
username | whatever your username is | guest_user_account | It's whatever your username is |
lang | en | en | Language of the game, used for loading translation XML |
typeID | 4 | 4 | ? |
itemID | 381 | 53 | ? (seems to duplicate "id") |
destination | games%2Fplay.phtml%3Fgame_id%3D381 | games/ | Percent-encoded version of "games/play.phtml?game_id=381", which is the page the quality and resolution are set on before playing |
q | high | HIGH | Initial quality level |
nsid | -1 | -1 | ? |
nsm | 0 | 0 | ? |
member | 1 | doesn't get set | ? |
sh | 85db20345c9442b66091 | 35ba379a5d920acb6f18 | Used for submitting scores |
sk | 45c100747ccb99ffca70 | 35ba379a5d920acb6f18 | Used for submitting scores |
baseurl | www.neopets.com | www.neopets.com | Overrides the possible out-of-date default script URL from the NP6 and NP8 system |
image_host | %2F%2Fimages.neopets.com | http://images.neopets.com | Overrides the definitely out-of-date default image URL from the NP6 and NP8 system |
name | your first name | doesn't get set | Your first name from the User Information page |
useCustomMsg | 0 | doesn't get set | ? |
sp | 0 | 0 | ? (called "scorepoints" by loader) |
va | 1 | true | Verified account |
hiscore | 1 | 0 | ? |
chall | 0 | ? | |
dc | 0 | 0 | ? (called "dailyChallenge" by loader) |
dict_ver | 62 | 6 | Version of the dictionary SWF to load |
world | 9 | doesn't get set | The ID for the land associated with this game |
ddNcChallenge | 0 | doesn't get set | ? |
age | 1 | doesn't get set | ? |
ms | 3 | 3 | Maximum score submissions for this game per day |
ccard | 0 | doesn't get set | ? |
gamew | 700 | doesn't get set | Width of the movie |
gameh | 700 | doesn't get set | Height of the movie |
include_movie | games%2Fgaming_system%2Fnp6_include_v16.swf | /games/gaming_system/np6_include_v1.swf | Overrides the definitely out-of-date default include path for NP6 or NP8 |
calibration | doesn't get set | 256 | ? |
psurl | doesn't get set | ? | |
t | doesn't get set | 0 | ? (called "tracking" by loader) |
multiple | doesn't get set | 0 | ? |
isAdmin | doesn't get set | 0 | Account is an admin |
isSponsor | doesn't get set | 0 | Account is a sponsor |
forceScore | doesn't get set | 0 | ? (maybe used for Splat-A-Sloth?) |
Worlds
This is for the value of the "world" variable.
- 2: Neopia Central
- 9: Meridell
Loader
This seems to:
- Fetch a random preloader
- Run the preloader while the actual game downloads
- Pass the parameters from the game launch page to the game itself
- Run the game
Note that these parameters override defaults in the NP6 and NP8 includes that no longer work. For example, by default, they try to load SWFs from http://images50.neopets.com/, even though they aren't accessible from the outside world on images50 and need normal images (and HTTPS). Another one is that there are references to versions of includes that are no longer on the images server, such as np8_include_v9.
It seems like the directly loaded movie is _level0. If everything is OK, it looks like the game is then loaded into _level10 and the include SWF into _level100. The preloader gets _level12.
BIOS
This file is set to SWF version 6 (Flash 6 minimum). Based on my not-good understanding of ActionScript, it appears to do the following things:
On frame 1
- Check how the BIOS is loaded
- Check if
String(this) == "_level0"
; if so, trace "Bios: Error, load this externally!" and stop the movie - Check if
String(this).split(".")[0] == "_level0"
; if not, set a local flag to false, which appears to change some sort of progress meter display slightly and set a translation string path to use non-local paths - Else, set the local flag to true and set a separate offline flag to 1
- Check if
- Set up the loading fade-in effect for the "NEO*BIOS 330-MEGA" chip
- Wait for itself to finish loading
- If the local flag is true, open
system/np6_include_v7.swf
onto the target "_level100" and wait for it to finish loading - Play this movie's parent's parent
On the "bios" sprite
- If the parent is _level0, print "BIOS must be loaded by another file!" and stop the movie
- copyInProps, which seems to copy the properties of the BIOS child of either _level10 (if loaded) (not 100) or _level0 (if _level10 not loaded) onto this object
- addProtoCode, which seems to change Object.toString to be a serialization system like
{key1:value1,key2:{k3:v3,k4:v4}}
, but with a new line for each level, and adjustable indentation for each level - If fully loaded, make the parent invisible, and finish
- If not fully loaded, load "http://images.neopets.com/games/high_scores/include_movie.swf" onto the target _level100 and wait for it to finish loading
Other notes
The circuit background appears to actually be stored in the game resources instead of in the BIOS movie itself.
NP6 and NP8
This file is set to SWF version 6 (Flash 6) for NP6, and 8 (8) for NP8. This analysis was done with the v7 versions. NP8 seems really similar to the NP6 loader, at least when comparing both v7 versions. The difference is that the NP8 loader appears to decompile to more object-oriented code.
Reporting cheaters
This is in DefineSprite (51) -> frame 1 (wait) -> DoAction.
There are two mechanisms, called gameMsg and msg. They take two parameters: index and append. They create a "body" string of the format "game_id - game_filename - game_username", then append "append" if it isn't undefined. This is then POSTed to "http://www.neopets.com/games/dgs/dgs_protocol.phtml?id=index&subject=game_id&body=body" in obfuscated fashion, and the variables from the response loaded to _level0.
gameMsg is similar, except that " - old call -" is appended to "append" before anything happens, and the game then opens http://www.neopets.com/games/cheatmonster.phtml in a new window.
Dictionary support
This is in the same object and frame, but DoAction [7].
It loads "http://images50.neopets.com/games/utilities/flash_dictionary/flash_dictionary_en_vVERSION.swf", where VERSION is the value of _level0.game_dict_ver. Live site games use dict_ver 62. The images50 URL doesn't work; instead use https://images.neopets.com/games/utilities/flash_dictionary/flash_dictionary_en_v62.swf. The dictionary can check if something is a word, a bad word, dictionary word, and/or Neopian word. Words can also be scrambled and selected randomly.
NeoStatus
DoAction [9]. These URLs are hit:
- NEOSTATUS_URL = http://www.neopets.com/neostatus.phtml
- PROCESS_URL = http://www.neopets.com/process_click.phtml
- tracking URL base: http://www.neopets.com/track_plays.phtml?game_id=id where id is _level0.game_id
- nc_track URL base: http://www.neopets.com/nc_track.phtml
Events information
There is an idea of events, which have a tag name, status code, and offset. When creating an event, a base_multiple is also needed. This sets "multiple" to base_multiple * 2
and "active" to random(offset) + 1 == 1
.
Tag Name | Status Code | Offset |
---|---|---|
Game Started | 900 | 1 |
Multiplayer Game Started 1 | 901 | 1 |
Multiplayer Game Started 2 | 902 | 1 |
Multiplayer Game Started 3 | 903 | 1 |
Multiplayer Game Started 4 | 904 | 1 |
Game Finished | 1000 | 1 |
Sent Score | 1001 | 1 |
Reached Level n (where 1 <= n <= 100) | 7000 + n | 10 |
Sponsor Item Shown | 8010 | 30 |
Sponsor Logo Shown | 8020 | 10 |
Sponsor Item Collected | 8030 | 30 |
Sponsor Banner Shown | 8040 | 30 |
If tracking is enabled (Number(_level0.game_tracking) == 1
), Game Started and Game Finished events hit the tracking URL base with the following parameters added to the query string:
- dowhat=game_starts if Game Started, or game_ends if Game Finished
- multiple= followed by
_level0.game_multiple
- r= followed by
random(999999999)
The variables from this call to the tracking URL are then loaded. When I try URLs like https://www.neopets.com/track_plays.phtml?game_id=987&dowhat=game_ends&multiple=0&r=8309101 using the values from 200m Peanut Dash, nothing is in the response, so I'm not sure what the NP6 loader was trying to find.
Then, for the main NeoStatus URL, this gets hit once for each event:
- item_id= followed by
item_id
, which seems to be -1 if _level0.nsid is undefined and debug not -1, 1 if undefined and 1, or _level0.nsid - multiple= value of multiple for this event, which is that base_multiplier * 2 from earlier
- status= value of statusCode for this event
- If URLSuffix is undefined for the call: nc_value= followed by
_level0.nc_referer
. Note that nc_referer doesn't seem to be set to anything other than a blank string on the live website, though that's with NP8 games and not NP6. - r= value of random(999999999)
There is client-side logic to prevent sending an event if item_id is set to -1, which happens when _level0.nsid is undefined. Sending is also prevented if the "active" flag for the event is not 1. I am going to assume the random() call is exclusive, so events will be sent with probability 1 / offset
. In other words, if offset is 1, then it will always be sent; otherwise, only sometimes.
Process click
Then, for process click, which seems to be unrelated to the main NeoStatus system, these are the query parameters:
- item_id= passed item_id
- random= value of Math.random() * 999999999
If the requested method is POST (the default), open it in the passed windowName window if called via processClickGetURL. Else, if called by processClickLoadVariables, a "type_id" and "nc_value" need to be present too, and the variables from the body are loaded into this object.
Membership stuff
If a signup or login is gotten from the game, hit the nc_track URL base with the following parameters:
- type_id=12
- item_id=3187 if signup, else 3188
- nc_value= if undefined nc_referer, treat it as 0, then concatenate game_id, a hyphen, and nc_referer
- r= value of Math.random(999999)
User Profile
DoAction [11]. You pass an array with any of the following items:
- 1: scores_sent
- 2: high_score
- 3: user_age
- 4: user_gender
- 5: pet1_name
- 6: pet1_color
- 7: pet1_species
- 8: user_full_name (doesn't work)
- 9: user_email
- 10: user_country
- 11: user_dob
- 12: pet2_name
- 13: pet3_name
- 14: pet4_name
Slap a semicolon at the end of the numbers of each one you want, concatenate them together, call it typestring, then hit http://www.neopets.com/high_scores/fg_get_info.phtml?game_id=game_id&type=typestring. The response is then read using loadVariables()
.
Note that the pet names appear to be buggy. If you specify 12, 13, or 14, you only get pet1_name and pet2_name, regardless of which number you enter. If you specify two of them, you also get pet3_name. If you specify all three, you also get pet4_name. Yes, you can get the four pet names without actually specifying 5. Also, if you specify enough other numbers, you get access to the following numbers too:
- 15: active_pet_mood=hardcoded_as_happy
- 17: user_full_name (note that the one under number 8 doesn't seem to work anymore sometime after NP6 v7, but even much later in NP8 v29, the above list is still exactly the same)
- 18: is_admin
- 19: is_sponsor
- 20: neopoints
- 21: lang
Scoring
DoAction [13]. TODO: document this better
http://www.neopets.com/high_scores/process_flash_score.phtml with params:
- cn= value of 300 * _level0.game_id
- gd= value of getTimer() - _level0.game_isLoaded
- asp_cName=val for every pair of cName, val passed to addScoreParameter(cName, val) ahead of time
- r= value of Math.random(999999999)
- gmd_g= value of _level0.game_hash
- mltpl_g= value of _level0.game_multiple
- gmdt_g= value of sSlashed, see below
- sh_g= value of _level0.game_hash
- sk_g= value of _level0.game_sk
- usrnm_g= value of _level0.game_username
- dc_g= value of _level0.game_dailyChallenge
Where sSlashed is the following stuff in query string (minus the question mark) format, encoded in some crazy algorithm defined in np.projects.include.Strings:
- ssnhsh= value of _level0.game_hash
- ssnky= value of _level0.game_sk
- gmd= value of _level0.game_id
- scr= value of _SCORE.show()
- frmrt= value of _level0.average_real_framerate
- chllng= value of _level0.game_challenge
- gmdrtn= value of getTimer() - _level0.game_isLoaded
Flash Challenge Email
DoAction [15]. Hit http://www.neopets.com/games/email_flash_challenge.phtml with the following query params:
- flash=1
- game_id= value of _level0.game_id
- score= value of _SCORE.show()
- sender_name= value of fromName
- sender_email= value of fromEmail
- recipient_name= value of toName
- recipient_email= value of toEmail
This gets loaded into a special LoadVars() object, and it looks like "error_str" and "msg" are checked for to get an error message and success message.
Example success message
After a long delay:
Content-type: text/plain &success_str=1&msg=Your+message+was+sent%21
Example failure message
Content-type: text/plain &success_str=0&error_str=Test2+is+not+a+valid+email+address.+Please+correct+it.
Content-type: text/plain &success_str=0&error_str=Not+finding+that+game+id%2C+please+try+again.
Dictionary
This analysis is done with the v62 version. I couldn't find an old dictionary on the promotional disc.
SWF version is 6 (Flash 6). There is one script, DoAction for frame 1, and this script is 55218 lines long when disassembled. The data structure, in Python-esque terms, is list[dict[str, dict[str, int]]]
. The list is indexed using word length. The key of the outer mapping is the first letter of the word. The value of the outer mapping is another mapping. The key of the inner mapping is the word itself. The value is the category of the word:
- 1: Dictionary word
- 2: Neopian word, such as "edna", but this also includes others like "dont" and "mega"
An actual word is greater than 0. Bad words are not included, but are treated as -1 by consuming code using the NP6/8 dictionary methods.