-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create Cemu online files generation script
The patch to restore downloading online files from the website will be removed, as Pretendo will never add this as an optional website feature.
- Loading branch information
1 parent
019e15c
commit 5e11ba3
Showing
3 changed files
with
147 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
#!/usr/bin/env bash | ||
|
||
# shellcheck source=./internal/framework.sh | ||
source "$(dirname "$(realpath "$0")")/internal/framework.sh" | ||
set_description "This creates the necessary files for Cemu online play with Pretendo as an alternative to using Dumpling.\ | ||
It can also create fake OTP and SEEPROM dumps, which will only work on servers with console verification disabled." | ||
add_option "-f --force" "force" "Always overwrite the existing online files in the output directory without asking" | ||
add_option "-d --fake-dumps" "fake_dumps" "Create fake OTP and SEEPROM dumps (with all null bytes) in addition to the account.dat file" | ||
add_option_with_value "-i --persistent-id" "persistent_id" "id" "The persistent ID to use for the account.dat file" false "80000001" | ||
add_option_with_value "-o --output" "output_dir" "directory" "The output directory for the online files" false "./online-files" | ||
add_option_with_value "-p --password" "password" "password" "The password to use for the account.dat file (if not provided, you will be prompted to enter a password)" false | ||
add_positional_argument "pnid" "pnid" "The PNID to create an account.dat file" true | ||
parse_arguments "$@" | ||
|
||
if [[ -z "$password" ]]; then | ||
printf "Enter the password for PNID $pnid: " | ||
read -rs password | ||
echo | ||
fi | ||
|
||
account_dat_path="$output_dir/mlc01/usr/save/system/act/$persistent_id/account.dat" | ||
otp_path="$output_dir/otp.bin" | ||
seeprom_path="$output_dir/seeprom.bin" | ||
if [[ -n "$fake_dumps" ]]; then | ||
paths=("$account_dat_path" "$otp_path" "$seeprom_path") | ||
else | ||
paths=("$account_dat_path") | ||
fi | ||
|
||
needs_confirmation=false | ||
for path in "${paths[@]}"; do | ||
mkdir -p "$(dirname "$path")" | ||
if [[ -f "$path" ]]; then | ||
print_warning "Output file $path already exists. Continuing will overwrite it!" | ||
needs_confirmation=true | ||
fi | ||
done | ||
if [[ "$needs_confirmation" == true && -z "$force" ]]; then | ||
printf "Continue? [y/N] " | ||
read -r continue | ||
if [[ "$continue" != "Y" && "$continue" != "y" ]]; then | ||
echo "Aborting." | ||
exit 1 | ||
fi | ||
fi | ||
|
||
print_info "Generating online files for PNID $pnid..." | ||
|
||
compose_no_progress up -d account | ||
|
||
create_account_dat_script=$(cat "$git_base_dir/scripts/run-in-container/create-account-dat.js") | ||
|
||
docker compose exec -u root account sh -c "touch /tmp/account.dat && chmod 777 /tmp/account.dat" | ||
run_verbose docker compose exec account node -e "$create_account_dat_script" "$pnid" "$password" "$persistent_id" | ||
|
||
compose_no_progress cp account:/tmp/account.dat "$account_dat_path" | ||
docker compose exec -u root account rm -f /tmp/account.dat | ||
|
||
if [[ -n "$fake_dumps" ]]; then | ||
print_info "Creating fake OTP and SEEPROM dumps..." | ||
head -c 1024 /dev/zero >"$otp_path" | ||
head -c 512 /dev/zero >"$seeprom_path" | ||
fi | ||
|
||
print_success "Successfully generated online files for PNID $pnid." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// This should be evaled in the account container | ||
const fs = require("fs").promises; | ||
const { v4: uuidv4 } = require("uuid"); | ||
const { compare } = require("bcrypt"); | ||
const { config } = require("./dist/config-manager"); | ||
const { connect } = require("./dist/database"); | ||
const { PNID } = require("./dist/models/pnid"); | ||
const { nintendoPasswordHash } = require("./dist/util"); | ||
|
||
// See https://github.com/GabIsAwesome/accountfile-generator and | ||
// https://github.com/PretendoNetwork/website/blob/99ee7ebe0aa1c2b632526f1de42f9f8b0d15940d/src/routes/account.js#L245-L284 | ||
|
||
async function runAsync() { | ||
if (process.argv.length < 4) { | ||
console.log("Usage: <pnid-username> <password> <persistent-id>"); | ||
process.exit(1); | ||
} | ||
|
||
await connect(); | ||
|
||
const pnid = await PNID.findOne({ | ||
usernameLower: process.argv[1].toLowerCase(), | ||
}); | ||
if (pnid) { | ||
const accountDat = await generateAccountDat(pnid, process.argv[2], process.argv[3]); | ||
await fs.writeFile(`/tmp/account.dat`, accountDat); | ||
} else { | ||
throw new Error(`No PNID found for username ${process.argv[1]}.`); | ||
} | ||
} | ||
|
||
runAsync().then(() => { | ||
process.exit(0); | ||
}); | ||
|
||
async function generateAccountDat(pnid, password, persistentId) { | ||
const hashedPassword = nintendoPasswordHash(password, pnid.pid); | ||
if (!(await compare(hashedPassword, pnid.password))) { | ||
throw new Error("Incorrect password specified."); | ||
} | ||
|
||
const [year, month, day] = pnid.birthdate.split("-"); | ||
const birthYear = parseInt(year, 10); | ||
const birthMonth = parseInt(month, 10); | ||
const birthDay = parseInt(day, 10); | ||
|
||
let accountDat = "AccountInstance_00000000\n"; | ||
accountDat += `PersistentId=${persistentId}\n`; | ||
accountDat += "TransferableIdBase=0\n"; | ||
accountDat += `Uuid=${uuidv4().replace(/-/g, "")}\n`; | ||
accountDat += `MiiData=${Buffer.from(pnid.mii.data, "base64").toString("hex")}\n`; | ||
accountDat += `MiiName=${Buffer.from(pnid.mii.name, "utf16le").swap16().toString("hex")}\n`; | ||
accountDat += `AccountId=${pnid.username}\n`; | ||
accountDat += `BirthYear=${birthYear.toString(16)}\n`; | ||
accountDat += `BirthMonth=${birthMonth.toString(16)}\n`; | ||
accountDat += `BirthDay=${birthDay.toString(16)}\n`; | ||
accountDat += `Gender=${pnid.gender === "M" ? 1 : 0}\n`; | ||
accountDat += "IsMailAddressValidated=1\n"; | ||
accountDat += `EmailAddress=${pnid.email.address}\n`; | ||
// No convenient way to turn the country code into the necessary numerical ID format | ||
accountDat += "Country=0\n"; | ||
accountDat += "SimpleAddressId=0\n"; | ||
accountDat += `PrincipalId=${pnid.pid.toString(16)}\n`; | ||
accountDat += "NeedsToDownloadMiiImage=1\n"; | ||
accountDat += `MiiImageUrl=${config.cdn.base_url}/mii/${pnid.pid}/standard.tga\n`; | ||
accountDat += "IsPasswordCacheEnabled=1\n"; | ||
accountDat += `AccountPasswordCache=${hashedPassword}`; | ||
|
||
return accountDat; | ||
} |