Twenty-eight — Revisiting "Shaun the Sheep Go Home 2"
Recalling the memories of chasing those three little sheep from my childhood—it's been ten years already.
Don't ask me if I remembered this game because "Sheep a Sheep" went viral.

Three-Star Clear + Full Item Collection
If you want to collect all the items (socks, cakes, game consoles) in this game, it requires a bit of strategic thinking. And usually, you can't aim for a three-star clear while grabbing all the items at the same time. (Speedrunners and game developers, please ignore this comment—after all, no one can beat the records set by the developers themselves)
However, if you've collected the items in a playthrough, you don't need to collect them again when replaying. So the most efficient approach is to take it slow the first time, figure out the path to clear the level while gathering all the items, then go for a speedrun on the second attempt (personally, it took me about three tries on average to get three stars).
What, you can't clear the level? Well, maybe it's time to call it a night. After all, the later stages require some seriously hardcore skills.
Unlocking All Achievements
If simply clearing the game has a difficulty level of 1, then achieving a three-star clear with full item collection would be around 3. Unlocking all the achievements? That difficulty might just shoot up to 10.
60% Low-Difficulty Achievements
If you have achieved three-star clears and collected all items, you should have already unlocked most of the achievements in this category. Below, I'll explain some of the more tricky ones.
30% Medium Difficulty Achievements
These primarily involve clever maneuvers or unconventional strategies within levels. Here are a few representative examples:
Where Are the Parachutes (Using Only Two Parachutes)

The sheep, from largest to smallest, are Shirley, Shaun, and Timmy.
Shaun jumps onto the rock on the right to retrieve a parachute. After Shirley grabs the parachute above, she leaps downward. Since Shirley ascends slowly, Timmy has enough time to jump onto Shirley's back and ride up with her.
High-Speed Close-Up (Get the Speed Camera to Capture All Three of Them)

Shaun can simply hurdle directly on top of the truck.
Timmy steps on Shirley's back and jumps a bit farther forward (imagine the speed camera shooting from below).
Shirley steps on Timmy's back and just needs to jump once.
This achievement does not require completing the level. It is recommended to exit the level immediately after obtaining the achievement.
Aren't you worried about Shirley being used as a shield?
Roast Mutton (Make Shaun Jump to the Tail of the Rocket)

(Theoretically the method to make the three of them jump the highest)
Shirley steps on Timmy, and at the moment Shirley jumps, switch the character to Shaun and leap onto the rocket.
This requires a bit of hand speed, but it's not too difficult. However, capturing the moment when both sheep are in mid-air after Shaun jumps is a bit tricky.
When I was a kid, I probably couldn't get past this step to complete the other achievements.
The 10% High-Difficulty Achievements
I finished the main game in three days, but these last four achievements have been tormenting me for two whole days.
Waste Not, Want Not (Use Teleport Only Once in Space Walk Level 14)

At first, I thought, isn't this achievement straightforward? Just have Shirley and Shaun jump to the end together, right? In fact, it only requires one teleport—swap Timmy with the small block on the right. However, after completing the level, the achievement didn't unlock. Even after repeating it twice, it still didn't work.
So I began to suspect that my understanding of the achievement might be incorrect. I opened this website and found the following description for the achievement:

It means "only switch the selected sheep once," not "use teleport only once." The translation is so irresponsible! @-}(|&;;-%!$+:.{~:
So the correct approach is to first have Shaun jump with Shirley toward the end (it's a bit tricky for Shaun to push Shirley, but it's doable), then swap with Timmy. Next, jump with Shirley to the end and push the small block into the teleporter. Finally, switch to Shirley and swap with the small block.
Extreme Sports (Breaking the Developer's Time Once)
This developer's time is truly outrageous. I feel like my hand speed is fast enough, but my clear times for most levels are still about 30% slower than the developer's.
After multiple attempts with no success, I opened Bilibili. However, due to the game's extreme obscurity, there were hardly any guides available. The few existing guides only covered three-star clears and full item collection.
I turned my attention to YouTube and finally found something: https://www.youtube.com/watch?v=hSL0vngdDBc
The developer's time in the video is 10s, but here it's 9s for me. Seriously, does it keep getting harder over time?
I started mimicking the strategies shown in the video. Since my execution was too clumsy, I used a speed modifier to increase my reaction time. After more than twenty attempts, I finally broke the developer's record with a time of 8.4s.

Submitting Scores (Submitting Scores to the Official Leaderboard)
This is a pirated single-player game, so this achievement is theoretically impossible to complete (the Steam version removed this achievement, but another achievement in it also has a bug that prevents completion).
Therefore, we need to use some unconventional methods to achieve this.
Since it's a single-player game, the game data should be saved somewhere on the computer (specifically, in the Roaming folder under the user directory's AppData). A quick search with Everything will reveal it.
We only need to focus on two files: savedata.txt
and savedata.old
. Based on my experience, the game likely reads savedata.old
on startup, writes modifications to savedata.txt
, and finally copies savedata.txt
to savedata.old
when the game closes.
These two files are essentially JSON files. The part related to achievements is as follows:
"achievements": [
{"got": true, "id": "miscRoastLamb"},
{"got": true, "id": "miscBringLuggage"},
{"got": true, "id": "miscCameraDestroy"},
{"got": true, "id": "miscCameraJump"},
{"got": true, "id": "miscUndamagedChimneys"},
{"got": true, "id": "miscTwoParachutes"},
{"got": true, "id": "progressAllEpisodes"},
{"got": true, "id": "miscNoExplosives"},
{"got": true, "id": "miscSaveExplosives"},
{"got": true, "id": "miscTwoTeleports"},
{"got": true, "id": "miscSingleSwap"},
{"got": true, "id": "miscSurfTheVan"},
{"got": true, "id": "miscUnchangedTime"},
{"got": true, "id": "miscSheepJuggling"},
{"got": true, "id": "miscNoFootball"},
{"got": false, "id": "miscCrossPromo"},
{"got": false, "id": "miscSubmitScore"},
{"got": true, "id": "stars1"},
{"got": true, "id": "collect12Socks"},
{"got": true, "id": "stars5"},
{"got": true, "id": "collect24Socks"},
{"got": true, "id": "stars30"},
{"got": true, "id": "starsAll"},
{"got": true, "id": "starsDev"},
{"got": true, "id": "collect2Socks"},
{"got": false, "id": "collect12SocksWeb"},
{"got": true, "id": "progressUnderground_1"},
{"got": true, "id": "collect50Socks"},
{"got": true, "id": "progressUnderground_5"},
{"got": true, "id": "collect75Socks"},
{"got": true, "id": "progressUnderground_10"},
{"got": true, "id": "collectAllSocks"},
{"got": true, "id": "progressUnderground_15"},
{"got": true, "id": "collect1Cake"},
{"got": true, "id": "progressLondon_1"},
{"got": false, "id": "collect1Secret"},
{"got": true, "id": "progressLondon_5"},
{"got": true, "id": "collect1Joystick"},
{"got": true, "id": "progressLondon_10"},
{"got": true, "id": "collectAllCakes"},
{"got": true, "id": "progressLondon_15"},
{"got": false, "id": "collectAllSecrets"},
{"got": true, "id": "progressSpace_1"},
{"got": true, "id": "collectAllJoysticks"},
{"got": true, "id": "progressSpace_5"},
{"got": true, "id": "collectAllCollectables"},
{"got": true, "id": "progressSpace_10"},
{"got": false, "id": "miscOneHundredPercent"},
{"got": true, "id": "progressSpace_15"},
{"got": true, "id": "progressTraining1"},
{"got": true, "id": "progressTraining2"},
{"got": true, "id": "progressTraining3"},
{"got": true, "id": "progressBonus1"},
{"got": true, "id": "progressBonus5"},
{"got": true, "id": "progressBonus10"},
{"got": true, "id": "progressBonusAll"},
{"got": true, "id": "miscCredits"}
],
I tried changing miscSubmitScore
from false
to true
. After restarting the game, oh no—all my previous saves were gone. Fortunately, Notepad++ prompts for user confirmation when a file is modified, otherwise, two days of hard work would have been wasted.
Why did this happen? After consulting with mxy, I learned that there is a val
object in the JSON:
"val": "76b44f37329e3de81ce86f2874a7ea1d",
It is 32 characters long and only contains characters from 0-9 and a-f. I guessed that the program might have stored an MD5 hash of some user information in val
. On startup, it compares this value, and if it doesn't match, it deletes all saves and starts over.
How to verify this? Naturally, by reverse engineering the program.
The main body of the program is an SWF file, so this is a Flash game. FFDEC can be used to disassemble Flash. After opening the software, search for MD5, select the Levels script, and locate the function that generates val
:

public static function getValidationCode() : String
{
var _loc1_:String = "";
var _loc2_:int = 0;
while(_loc2_ < defs.length)
{
if(defs[_loc2_].available)
{
_loc1_ = _loc1_ + defs[_loc2_].dev;
_loc1_ = _loc1_ + defs[_loc2_].complete;
_loc1_ = _loc1_ + defs[_loc2_].autoReduce;
_loc1_ = _loc1_ + defs[_loc2_].best;
_loc1_ = _loc1_ + defs[_loc2_].stars;
_loc1_ = _loc1_ + defs[_loc2_].episode;
_loc1_ = _loc1_ + defs[_loc2_].levelNum;
_loc1_ = _loc1_ + defs[_loc2_].attempts;
_loc1_ = _loc1_ + defs[_loc2_].title;
_loc1_ = _loc1_ + defs[_loc2_].par;
_loc1_ = _loc1_ + defs[_loc2_].levelTotal;
_loc1_ = _loc1_ + defs[_loc2_].locked;
_loc1_ = _loc1_ + defs[_loc2_].brisk;
_loc1_ = _loc1_ + defs[_loc2_].isBonus;
}
_loc2_++;
}
_loc1_ = _loc1_ + Achievements.inst.totalGot();
_loc1_ = _loc1_ + Achievements.inst.totalAvailable();
_loc1_ = _loc1_ + Collectables.inst.totalGot();
_loc1_ = _loc1_ + Collectables.inst.totalAvailable();
return MD5.hash(salt + _loc1_ + salt);
}
The program logic is clear: it concatenates all levels in defs
in the order given by the code, then appends the number of achievements obtained, total achievements, number of items collected, and total items available. It then adds salt to the beginning and end of the resulting string and computes its MD5 hash.
The salt is directly provided in the code, and the level data is available in the JSON. Therefore, I wrote the following Python script:
import ijson
import hashlib
file_name = 'D:/tmp1/savedata.txt'
with open(file_name, 'r') as f:
obj = list(ijson.items(f, 'defs'))
_loc1_ = ''
flag = 0
salt = 'q2vakri78abq23i8rcynaql2irnvolqy8ito'
for item in obj:
for level in item:
if level['available'] != True:
continue
_loc1_ = _loc1_ + str(level['dev'])
_loc1_ = _loc1_ + str(level['complete']).lower()
_loc1_ = _loc1_ + str(level['autoReduce']).lower()
_loc1_ = _loc1_ + str(level['best'])
_loc1_ = _loc1_ + str(level['stars'])
_loc1_ = _loc1_ + str(level['episode'])
_loc1_ = _loc1_ + str(level['levelNum'])
_loc1_ = _loc1_ + str(level['attempts'])
_loc1_ = _loc1_ + str(level['title'])
_loc1_ = _loc1_ + str(level['par'])
_loc1_ = _loc1_ + str(level['levelTotal'])
_loc1_ = _loc1_ + str(level['locked']).lower()
_loc1_ = _loc1_ + str(level['brisk'])
_loc1_ = _loc1_ + str(level['isBonus']).lower()
# 355truefalse5453Underground111Level01185415false927false
if flag != 2:
print(level)
print(_loc1_)
flag += 1
with open(file_name, 'r') as f:
obj = list(ijson.items(f, 'achievements'))
# print(obj)
for item in obj:
achieve_cnt = 0
achieve_total = 0
for achieve in item:
if achieve['got'] == True:
achieve_cnt += 1
achieve_total += 1
print('achieve_cnt: ' + str(achieve_cnt))
print('achieve_total: ' + str(achieve_total))
with open(file_name, 'r') as f:
obj = list(ijson.items(f, 'collectables'))
for item in obj:
collection_cnt = 0
collection_total = 0
for collection in item:
if collection['got'] == True:
collection_cnt += 1
collection_total += 1
print('collection_cnt: ' + str(collection_cnt))
print('collection_total: ' + str(collection_total))
_loc1_ += str(achieve_cnt)
_loc1_ += str(achieve_total)
_loc1_ += str(collection_cnt)
_loc1_ += str(collection_total)
# print(_loc1_)
val = hashlib.md5((salt+_loc1_+salt).encode('utf-8')).hexdigest()
print(val)
Unfortunately, the MD5 value calculated by this code does not match val
for unknown reasons.
Updated on 13/6/2025:
The reason the hash value did not match is that the values of achieve_total
and collection_total
are not the lengths of the achievements
and collectables
fields. This is because some achievements and collectible items were ultimately not included in the final game. To obtain the correct values for these two, we can use a correct save file and perform a hash collision with combinations of these two values, for example:
# Count the total obtained achievements and available achievements
achievements_got = sum(1 for ach in json_data["achievements"] if ach["got"])
achievements_available = len(json_data["achievements"])
# Count the total obtained collectibles and available collectibles
collectables_got = sum(1 for col in json_data["collectables"] if col["got"])
collectables_available = len(json_data["collectables"])
# Append achievement and collectible counts
for i in range(200):
for j in range(200):
tmp = result
tmp += str(achievements_got)
tmp += str(i)
tmp += str(collectables_got)
tmp += str(j)
# Calculate MD5 hash using salt
final_string = salt + tmp + salt
if hashlib.md5(final_string.encode('utf-8')).hexdigest() == json_data["val"]:
print(i, j)
return
Here, it was found that the value of achieve_total
(i.e., achievements_got
in the collision code) is 53, and the value of collection_total
(i.e., collectables_available
in the modified code) is 121. Therefore, we replace the last four lines of the source code:
_loc1_ += str(achieve_cnt)
_loc1_ += str(achieve_total)
_loc1_ += str(collection_cnt)
_loc1_ += str(collection_total)
with
_loc1_ += 53
_loc1_ += 53
_loc1_ += str(collection_cnt)
_loc1_ += 121
So I opted for an alternative approach: directly modifying the code to bypass the check.
public static function fromString(param1:String) : void
{
var param1:String = param1;
var obj:Object = null;
var str:String = param1;
try
{
obj = JSONPretty.decode(str);
if(obj.ver != ver)
{
initialiseSO();
return;
}
defs = obj.defs.slice();
Achievements.inst.loadFromArray(obj.achievements);
Collectables.inst.loadFromArray(obj.collectables);
Hacks.tried = obj.hacksTried.slice();
DevComments.seenComments = obj.seenComments.slice();
}
catch(e:Error)
{
DevPanel.log("Error decoding saved data: " + e.message);
initialiseSO();
return;
}
checkValid(obj.val);
}
Find a version 30 playerglobal.swc
on GitHub to enable source code editing, and comment out the second-to-last line.
Note: Be sure to save in the top-left corner and confirm that the SWF's modification time has changed before closing FFDEC.
Then modify the previous savedata.txt
and savedata.old
(I made them identical).
Unexpectedly—it worked!
Rest Time (Complete All Achievements)
Unfortunately, forcibly modifying the score submission earlier did not automatically unlock this achievement.
Therefore, following the same method, change the value of miscOneHundredPercent
in the JSON to true
.