Neopets system includes

From Computers Wiki
Jump to navigationJump to search

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 Include 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/np8_include_v29.swf 180965 2018-06-21

There seems to be only one accessible Flash dictionary version.

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
  • 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:

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(). You will always get a username at the minimum. game_id can be omitted; it doesn't seem to do anything. It seems like double-equals instead of triple-equals is being used for equality because having values like "3a" and "3.0" still count as if you typed "3".

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. Additionally, for users that have more than four pets, pet1_name, pet1_color, and pet1_species will always be the active pet, but the other pets will be in alphabetical order; this means that pets named later in the alphabet will never appear unless one of them is currently the active pet.

Also, if you specify at least one of these three 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

If you are not logged in, no matter what the parameters are, you get username=Not Logged In.&user_full_name=Not Logged In.&user_email=Not Logged In.

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.

References