Skip to content

Commit 12c0700

Browse files
authored
Add files via upload
1 parent 3865eab commit 12c0700

File tree

1 file changed

+143
-3
lines changed

1 file changed

+143
-3
lines changed

godtool.html

Lines changed: 143 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8">
5-
<title>Godtool (Web) • v1.0</title>
5+
<title>Godtool (Web) • v1.1</title>
66
<style>
77
:root {
88
--bg-main: #101014;
@@ -146,6 +146,7 @@
146146
width: 70px;
147147
}
148148
.dwc-field,
149+
.dwc-salt-field,
149150
.fileinfo-field,
150151
.friends-field,
151152
.penalty-field,
@@ -796,7 +797,7 @@
796797
<body>
797798
<header>
798799
<div class="row">
799-
<span class="logo">Godtool (Web) • v1.0</span>
800+
<span class="logo">Godtool (Web) • v1.1</span>
800801
<input type="file" id="fileInput" accept=".dat,.ikw,.rkp">
801802
<button id="downloadBtn" disabled>Download DAT</button>
802803
<span id="status">No file loaded</span>
@@ -2433,7 +2434,13 @@
24332434
return false;
24342435
}
24352436
if (choice === "apply" && typeof currentDirtyTab.applyFn === "function") {
2436-
currentDirtyTab.applyFn();
2437+
const res = currentDirtyTab.applyFn();
2438+
if (res && typeof res.then === "function") {
2439+
await res;
2440+
}
2441+
if (currentDirtyTab && currentDirtyTab.dirty) {
2442+
return false;
2443+
}
24372444
} else if (choice === "discard") {
24382445
clearDirtyTab(true);
24392446
}
@@ -3838,11 +3845,95 @@
38383845
}
38393846

38403847

3848+
function formatGameIdSalt(bytes) {
3849+
if (!bytes || bytes.length < 4) return "????";
3850+
let allZero = true;
3851+
let printable = true;
3852+
let ascii = "";
3853+
for (let i = 0; i < 4; i++) {
3854+
const b = bytes[i];
3855+
if (b !== 0) allZero = false;
3856+
if (b < 0x20 || b > 0x7E) printable = false;
3857+
ascii += String.fromCharCode(b);
3858+
}
3859+
if (allZero) return "0000";
3860+
if (printable) return ascii;
3861+
let hex = "";
3862+
for (let i = 0; i < 4; i++) {
3863+
hex += bytes[i].toString(16).padStart(2, "0");
3864+
}
3865+
return hex.toUpperCase();
3866+
}
3867+
3868+
function parseGameIdSaltInput(raw) {
3869+
const s = String(raw || "").trim();
3870+
if (!s) return null;
3871+
if (s === "0000") return new Uint8Array([0, 0, 0, 0]);
3872+
if (/^0x[0-9a-fA-F]{8}$/.test(s)) {
3873+
const hex = s.slice(2);
3874+
const out = new Uint8Array(4);
3875+
for (let i = 0; i < 4; i++) {
3876+
out[i] = parseInt(hex.substr(i * 2, 2), 16) & 0xFF;
3877+
}
3878+
return out;
3879+
}
3880+
if (/^[0-9a-fA-F]{8}$/.test(s)) {
3881+
const out = new Uint8Array(4);
3882+
for (let i = 0; i < 4; i++) {
3883+
out[i] = parseInt(s.substr(i * 2, 2), 16) & 0xFF;
3884+
}
3885+
return out;
3886+
}
3887+
if (s.length === 4) {
3888+
const out = new Uint8Array(4);
3889+
for (let i = 0; i < 4; i++) {
3890+
const code = s.charCodeAt(i);
3891+
if (code < 0x20 || code > 0x7E) return null;
3892+
out[i] = code & 0xFF;
3893+
}
3894+
return out;
3895+
}
3896+
return null;
3897+
}
3898+
3899+
function gameIdSaltFormatLabel(raw) {
3900+
const s = String(raw || "").trim();
3901+
if (!s) return "";
3902+
if (/^0x[0-9a-fA-F]{8}$/.test(s) || /^[0-9a-fA-F]{8}$/.test(s) || s === "0000") {
3903+
return "(Hex)";
3904+
}
3905+
if (s.length === 4) {
3906+
let printable = true;
3907+
for (let i = 0; i < 4; i++) {
3908+
const code = s.charCodeAt(i);
3909+
if (code < 0x20 || code > 0x7E) {
3910+
printable = false;
3911+
break;
3912+
}
3913+
}
3914+
if (printable) return "(ASCII)";
3915+
}
3916+
const parsed = parseGameIdSaltInput(s);
3917+
if (parsed && s.length === 4) return "(ASCII)";
3918+
if (parsed) return "(Hex)";
3919+
return "";
3920+
}
3921+
38413922
function renderDwcTab(lic) {
38423923
const base = licBase(lic) + DWC_OFFSET;
3924+
const gameIdBytes = content.slice(base + 0x24, base + 0x28);
3925+
const gameIdText = formatGameIdSalt(gameIdBytes);
3926+
const gameIdFormat = gameIdSaltFormatLabel(gameIdText);
38433927
let html = `<div class="group-box">
38443928
<div class="group-title">DWC User Data</div>
38453929
<div class="grid-2col">`;
3930+
html += `
3931+
<label>Game ID salt</label>
3932+
<span class="dwc-input-wrap" id="dwcGameIdSaltWrap">
3933+
<input type="text" class="small-input dwc-salt-field" id="dwcGameIdSalt" value="${gameIdText}" maxlength="8">
3934+
<span id="dwcGameIdSaltFormat" style="font-size:11px; color: var(--text-muted);">${gameIdFormat}</span>
3935+
</span>
3936+
`;
38463937
for (const [label, off] of DWC_FIELDS) {
38473938
const val = view.getUint32(base + off, false) >>> 0;
38483939
html += `
@@ -3892,6 +3983,15 @@
38923983
markDwcWarnings(lic);
38933984
});
38943985
});
3986+
const gameIdInput = document.getElementById("dwcGameIdSalt");
3987+
if (gameIdInput) {
3988+
gameIdInput.addEventListener("input", () => {
3989+
const fmtEl = document.getElementById("dwcGameIdSaltFormat");
3990+
if (fmtEl) fmtEl.textContent = gameIdSaltFormatLabel(gameIdInput.value);
3991+
markDirtyNow();
3992+
markDwcWarnings(lic);
3993+
});
3994+
}
38953995

38963996
markDwcWarnings(lic);
38973997

@@ -4021,6 +4121,15 @@
40214121
document.getElementById("dwcApplyBtn").onclick = () => {
40224122
if (!ensureLoaded()) return;
40234123
const inputs = tabContent.querySelectorAll(".dwc-field");
4124+
const gameIdInput = document.getElementById("dwcGameIdSalt");
4125+
let gameIdBytes = null;
4126+
if (gameIdInput) {
4127+
gameIdBytes = parseGameIdSaltInput(gameIdInput.value);
4128+
if (!gameIdBytes) {
4129+
alert("Game ID salt must be 4 ASCII characters or 8 hex digits.");
4130+
return;
4131+
}
4132+
}
40244133

40254134
// First pass: validate all
40264135
for (const inp of inputs) {
@@ -4047,6 +4156,9 @@
40474156
const v = res.value >>> 0;
40484157
view.setUint32(base + off, v, false);
40494158
}
4159+
if (gameIdBytes) {
4160+
content.set(gameIdBytes, base + 0x24);
4161+
}
40504162

40514163
const crc = reversedWordsCRC32(base, DWC_DATA_LEN);
40524164
view.setUint32(base + DWC_DATA_LEN, crc >>> 0, false);
@@ -4213,6 +4325,34 @@
42134325

42144326
const currentEmpty = isDwcEmptyLicense(currentLic);
42154327

4328+
if (!currentEmpty) {
4329+
const gameIdWrap = document.getElementById("dwcGameIdSaltWrap");
4330+
let gameIdBytes = null;
4331+
const gameIdInput = document.getElementById("dwcGameIdSalt");
4332+
if (gameIdInput) {
4333+
const parsed = parseGameIdSaltInput(gameIdInput.value);
4334+
gameIdBytes = parsed ? parsed : new Uint8Array(0);
4335+
} else {
4336+
gameIdBytes = content.slice(
4337+
licBase(currentLic) + DWC_OFFSET + 0x24,
4338+
licBase(currentLic) + DWC_OFFSET + 0x28
4339+
);
4340+
}
4341+
const isRmcj =
4342+
gameIdBytes.length === 4 &&
4343+
gameIdBytes[0] === 0x52 &&
4344+
gameIdBytes[1] === 0x4D &&
4345+
gameIdBytes[2] === 0x43 &&
4346+
gameIdBytes[3] === 0x4A;
4347+
if (!isRmcj) {
4348+
addDwcWarning(
4349+
gameIdWrap,
4350+
"warn-red",
4351+
"The game salt for Mario Kart Wii's friend code calculation is RMCJ. Other values indicate that the save file has been tampered with and friend codes may not work or display properly."
4352+
);
4353+
}
4354+
}
4355+
42164356
// Authentic User ID #2 across all licenses (ignore empty licenses)
42174357
const auth2Values = [];
42184358
for (let lic = 0; lic < 4; lic++) {

0 commit comments

Comments
 (0)