Skip to content

Instantly share code, notes, and snippets.

@mochaaP
Created April 15, 2023 11:49
Show Gist options
  • Save mochaaP/dd1e0a2ff47aec36393d7ea54cb007e9 to your computer and use it in GitHub Desktop.
Save mochaaP/dd1e0a2ff47aec36393d7ea54cb007e9 to your computer and use it in GitHub Desktop.
IWKTK3 savefile verification analysis

KTK3 savefile analysis

It's a bit frustrating when I Wanna Kill The Kamilia 3 (IWKTK3) savefile have some sort of machine-binding mechanism in place and I can't find any information about it. So here you go.

Fields

All the following fields are random numbers generated on first launch.

registry_set_root(0);
if (registry_exists_ext("kmi", "ID")) {
    global.field90 = registry_read_real_ext("kmi", "ID");
} else {
    global.field90 = irandom(899999) + 100000;
    registry_write_real_ext("kmi", "ID", global.field90);
}
registry_set_root(0);
if (registry_exists_ext("kmj", "DT")) {
    global.field85 = registry_read_real_ext("kmj", "DT");
} else {
    global.field85 = irandom(899999) + 100000;
    registry_write_real_ext("kmj", "DT", global.field85);
}
registry_set_root(0);
if (registry_exists_ext("kmj", "CR")) {
    global.field687 = registry_read_real_ext("kmj", "CR");
} else {
    global.field687 = irandom(899999) + 100000;
    registry_write_real_ext("kmj", "CR", global.field687);
}
  • global.field90: \\HKCU\kmi\ID
  • global.field85: \\HKCU\kmj\DT
  • global.field687: \\HKCU\kmj\CR

Validation

  • DeathTime:

    #define script29
    var field82, field83, field84;
    if (file_exists(working_directory + "/Data/DeathTime") == 1) {
        field82 = file_bin_open(working_directory + "/Data/DeathTime", 0);
        field83 = file_bin_read_byte(field82) * 100000;
        field83 += file_bin_read_byte(field82) * 10000;
        field83 += file_bin_read_byte(field82) * 100;
        field83 += file_bin_read_byte(field82);
        if (field83 != global.field85) {
            show_message("Wrong Savefile!");
            game_end();
            exit;
        }
        global.field25 = file_bin_read_byte(field82) * 1000000;
        global.field25 += file_bin_read_byte(field82) * 10000;
        global.field25 += file_bin_read_byte(field82) * 100;
        global.field25 += file_bin_read_byte(field82);
        global.field86 = file_bin_read_byte(field82) * 1000000;
        global.field86 += file_bin_read_byte(field82) * 10000;
        global.field86 += file_bin_read_byte(field82) * 100;
        global.field86 += file_bin_read_byte(field82);
        if (file_bin_size(field82) == 60) {
            field84 = 48;
        } else {
            field84 = 60;
        }
        for (field29 = 1; field29 <= field84; field29 += 1) {
            global.field26[field29] = file_bin_read_byte(field82);
        }
    }
    file_bin_close(field82);
    
    #define script30
    var field82, field83, field87;
    field82 = file_bin_open(working_directory + "/Data/DeathTime", 1);
    field83 = global.field85;
    file_bin_write_byte(field82, floor(field83 / 100000));
    field83 -= floor(field83 / 100000) * 100000;
    file_bin_write_byte(field82, floor(field83 / 10000));
    field83 -= floor(field83 / 10000) * 10000;
    file_bin_write_byte(field82, floor(field83 / 100));
    field83 -= floor(field83 / 100) * 100;
    file_bin_write_byte(field82, field83);
    field87 = global.field25;
    file_bin_write_byte(field82, floor(field87 / 1000000));
    field87 -= floor(field87 / 1000000) * 1000000;
    file_bin_write_byte(field82, floor(field87 / 10000));
    field87 -= floor(field87 / 10000) * 10000;
    file_bin_write_byte(field82, floor(field87 / 100));
    field87 -= floor(field87 / 100) * 100;
    file_bin_write_byte(field82, field87);
    field87 = global.field86;
    file_bin_write_byte(field82, floor(field87 / 1000000));
    field87 -= floor(field87 / 1000000) * 1000000;
    file_bin_write_byte(field82, floor(field87 / 10000));
    field87 -= floor(field87 / 10000) * 10000;
    file_bin_write_byte(field82, floor(field87 / 100));
    field87 -= floor(field87 / 100) * 100;
    file_bin_write_byte(field82, field87);
    for (field29 = 1; field29 <= 60; field29 += 1) {
        file_bin_write_byte(field82, global.field26[field29]);
    }
    file_bin_close(field82);
  • saveData:

    #define script32
    var field82, field88, field89;
    field82 = file_bin_open(working_directory + "/Data/saveData", 1);
    field88 = global.field90;
    file_bin_write_byte(field82, floor(field88 / 100000));
    field88 -= floor(field88 / 100000) * 100000;
    file_bin_write_byte(field82, floor(field88 / 10000));
    field88 -= floor(field88 / 10000) * 10000;
    file_bin_write_byte(field82, floor(field88 / 100));
    field88 -= floor(field88 / 100) * 100;
    file_bin_write_byte(field82, field88);
    field89 = room;
    field89 *= 152;
    file_bin_write_byte(field82, floor(field89 / 10000));
    field89 -= floor(field89 / 10000) * 10000;
    file_bin_write_byte(field82, floor(field89 / 100));
    field89 -= floor(field89 / 100) * 100;
    file_bin_write_byte(field82, field89);
    field89 = round(object7.x);
    field89 *= 172;
    file_bin_write_byte(field82, floor(field89 / 10000));
    field89 -= floor(field89 / 10000) * 10000;
    file_bin_write_byte(field82, floor(field89 / 100));
    field89 -= floor(field89 / 100) * 100;
    file_bin_write_byte(field82, field89);
    field89 = round(object7.y);
    if (room == 619) {
        field89 -= 1;
    }
    field89 *= 182;
    file_bin_write_byte(field82, floor(field89 / 10000));
    field89 -= floor(field89 / 10000) * 10000;
    file_bin_write_byte(field82, floor(field89 / 100));
    field89 -= floor(field89 / 100) * 100;
    file_bin_write_byte(field82, field89);
    file_bin_write_byte(field82, global.field91);
    file_bin_write_byte(field82, global.field92);
    file_bin_write_byte(field82, global.field93);
    file_bin_write_byte(field82, global.field94);
    file_bin_write_byte(field82, global.field95);
    file_bin_write_byte(field82, global.field96);
    file_bin_write_byte(field82, global.field97);
    file_bin_write_byte(field82, global.field98);
    file_bin_write_byte(field82, global.field99);
    file_bin_write_byte(field82, global.field100);
    file_bin_write_byte(field82, global.field101);
    file_bin_write_byte(field82, global.field102);
    file_bin_write_byte(field82, global.field103);
    file_bin_write_byte(field82, global.field104);
    file_bin_write_byte(field82, global.field105);
    file_bin_write_byte(field82, global.field106);
    file_bin_write_byte(field82, global.field107);
    file_bin_write_byte(field82, global.field108);
    file_bin_write_byte(field82, global.field109);
    file_bin_write_byte(field82, global.field110);
    file_bin_write_byte(field82, global.field111);
    file_bin_write_byte(field82, global.field112);
    file_bin_write_byte(field82, global.field113);
    file_bin_write_byte(field82, global.field114);
    file_bin_write_byte(field82, global.field115);
    file_bin_write_byte(field82, global.field116);
    file_bin_write_byte(field82, global.field117);
    file_bin_write_byte(field82, global.field118);
    file_bin_write_byte(field82, global.field119);
    file_bin_write_byte(field82, global.field120);
    file_bin_write_byte(field82, global.field121);
    file_bin_write_byte(field82, global.field122);
    file_bin_write_byte(field82, global.field123);
    file_bin_write_byte(field82, global.field124);
    file_bin_write_byte(field82, global.field125);
    file_bin_write_byte(field82, global.field126);
    file_bin_write_byte(field82, global.field127);
    file_bin_write_byte(field82, global.field128);
    file_bin_write_byte(field82, global.field129);
    file_bin_write_byte(field82, global.field130);
    file_bin_write_byte(field82, global.field131);
    file_bin_write_byte(field82, global.field132);
    file_bin_write_byte(field82, global.field133);
    file_bin_write_byte(field82, global.field134);
    file_bin_write_byte(field82, global.field135);
    file_bin_write_byte(field82, global.field136);
    file_bin_write_byte(field82, global.field137);
    file_bin_write_byte(field82, global.field138);
    file_bin_write_byte(field82, global.field139);
    file_bin_close(field82);
    for (field29 = 1; field29 <= 50; field29 += 1) {
        if (global.field30[field29] == 1) {
            global.field30[field29] = 2;
        }
    }
    script247();
    script30();
    if (global.field140 == 0) {
        global.field140 = 1;
    }
    
    #define script33
    var field82, field88, field89, field141, field142;
    field82 = file_bin_open(working_directory + "/Data/saveData", 0);
    field88 = file_bin_read_byte(field82) * 100000;
    field88 += file_bin_read_byte(field82) * 10000;
    field88 += file_bin_read_byte(field82) * 100;
    field88 += file_bin_read_byte(field82);
    if (field88 != global.field90) {
        show_message("Wrong Savefile!");
        game_end();
        exit;
    }
    var field143;
    if (!instance_exists(7)) {
        instance_create(0, 0, 7);
    }
    field143 += file_bin_read_byte(field82) * 10000;
    field143 += file_bin_read_byte(field82) * 100;
    field143 += file_bin_read_byte(field82);
    field143 /= 152;
    if (frac(field143) != 0) {
        show_message("Wrong Savefile!");
        game_end();
    }
    field141 += file_bin_read_byte(field82) * 10000;
    field141 += file_bin_read_byte(field82) * 100;
    field141 += file_bin_read_byte(field82);
    field141 /= 172;
    if (frac(field141) != 0) {
        show_message("Wrong Savefile!");
        game_end();
    }
    field142 += file_bin_read_byte(field82) * 10000;
    field142 += file_bin_read_byte(field82) * 100;
    field142 += file_bin_read_byte(field82);
    field142 /= 182;
    if (frac(field142) != 0) {
        show_message("Wrong Savefile!");
        game_end();
    }
    global.field91 = file_bin_read_byte(field82);
    global.field92 = file_bin_read_byte(field82);
    global.field93 = file_bin_read_byte(field82);
    global.field94 = file_bin_read_byte(field82);
    global.field95 = file_bin_read_byte(field82);
    global.field96 = file_bin_read_byte(field82);
    global.field97 = file_bin_read_byte(field82);
    global.field98 = file_bin_read_byte(field82);
    global.field99 = file_bin_read_byte(field82);
    global.field100 = file_bin_read_byte(field82);
    global.field101 = file_bin_read_byte(field82);
    global.field102 = file_bin_read_byte(field82);
    global.field103 = file_bin_read_byte(field82);
    global.field104 = file_bin_read_byte(field82);
    global.field105 = file_bin_read_byte(field82);
    global.field106 = file_bin_read_byte(field82);
    global.field107 = file_bin_read_byte(field82);
    global.field108 = file_bin_read_byte(field82);
    global.field109 = file_bin_read_byte(field82);
    global.field110 = file_bin_read_byte(field82);
    global.field111 = file_bin_read_byte(field82);
    global.field112 = file_bin_read_byte(field82);
    global.field113 = file_bin_read_byte(field82);
    global.field114 = file_bin_read_byte(field82);
    global.field115 = file_bin_read_byte(field82);
    global.field116 = file_bin_read_byte(field82);
    global.field117 = file_bin_read_byte(field82);
    global.field118 = file_bin_read_byte(field82);
    global.field119 = file_bin_read_byte(field82);
    global.field120 = file_bin_read_byte(field82);
    global.field121 = file_bin_read_byte(field82);
    global.field122 = file_bin_read_byte(field82);
    global.field123 = file_bin_read_byte(field82);
    global.field124 = file_bin_read_byte(field82);
    global.field125 = file_bin_read_byte(field82);
    if (global.field125 == 0) {
        file_bin_close(field82);
        if (global.field26[3] == 1) {
            global.field126 = 1;
        }
        if (global.field26[7] == 1) {
            global.field127 = 1;
        }
        if (global.field26[11] == 1) {
            global.field128 = 1;
        }
        if (global.field26[15] == 1) {
            global.field129 = 1;
        }
    } else {
        global.field126 = file_bin_read_byte(field82);
        global.field127 = file_bin_read_byte(field82);
        global.field128 = file_bin_read_byte(field82);
        global.field129 = file_bin_read_byte(field82);
        global.field130 = file_bin_read_byte(field82);
        global.field131 = file_bin_read_byte(field82);
        global.field132 = file_bin_read_byte(field82);
        global.field133 = file_bin_read_byte(field82);
        global.field134 = file_bin_read_byte(field82);
        global.field135 = file_bin_read_byte(field82);
        global.field136 = file_bin_read_byte(field82);
        global.field137 = file_bin_read_byte(field82);
        global.field138 = file_bin_read_byte(field82);
        global.field139 = file_bin_read_byte(field82);
        file_bin_close(field82);
        script248();
    }
    if (room_get_name(field143) == "<undefined>") {
        if (global.field111 < 1) {
            show_message("You must clear 1st boss!");
            game_end();
        } else {
            room_goto(683);
            object7.x = 16;
            object7.y = 336;
        }
    } else {
        room_goto(field143);
        object7.x = field141;
        object7.y = field142;
        if (global.field125 == 0) {
            global.field125 = 1;
            script32();
        }
    }
  • saveData2

    #define script247
    var field82, field88, field29, field686;
    field82 = file_bin_open(working_directory + "/Data/saveData2", 1);
    field88 = global.field687;
    file_bin_write_byte(field82, floor(field88 / 100000));
    field88 -= floor(field88 / 100000) * 100000;
    file_bin_write_byte(field82, floor(field88 / 10000));
    field88 -= floor(field88 / 10000) * 10000;
    file_bin_write_byte(field82, floor(field88 / 100));
    field88 -= floor(field88 / 100) * 100;
    file_bin_write_byte(field82, field88);
    for (field29 = 1; field29 <= 50; field29 += 1) {
        file_bin_write_byte(field82, global.field30[field29]);
    }
    file_bin_close(field82);
    for (field29 = 1; field29 <= 50; field29 += 1) {
        if (global.field30[field29] == 2) {
            field686 += 1;
        }
    }
    if (field686 == 50) {
        if (global.field26[57] == 0) {
            global.field26[57] = 1;
            field27 = instance_create(x, y, 1444);
            field27.field28 = 57;
            script30();
        }
    }
    
    #define script248
    var field82, field29, field88, field688;
    if (file_exists(working_directory + "/Data/saveData2") == 1) {
        field82 = file_bin_open(working_directory + "/Data/saveData2", 0);
        field88 = file_bin_read_byte(field82) * 100000;
        field88 += file_bin_read_byte(field82) * 10000;
        field88 += file_bin_read_byte(field82) * 100;
        field88 += file_bin_read_byte(field82);
        if (field88 != global.field687) {
            show_message("Wrong Savefile!");
            game_end();
            exit;
        }
        if (file_bin_size(field82) == 44) {
            field688 = 40;
        } else {
            field688 = 50;
        }
        for (field29 = 1; field29 <= field688; field29 += 1) {
            global.field30[field29] = file_bin_read_byte(field82);
        }
        for (field29 = 1; field29 <= field688; field29 += 1) {
            if (global.field30[field29] == 2) {
                field686 += 1;
            }
        }
        file_bin_close(field82);
        if (field688 == 40) {
            global.field130 = 0;
            global.field131 = 0;
            global.field132 = 0;
            global.field133 = 0;
            global.field124 = 0;
            global.field135 = 0;
            global.field136 = 0;
            global.field26[60] = 0;
            global.field26[59] = 0;
            global.field26[58] = 0;
            global.field26[57] = 0;
            global.field26[56] = 0;
            global.field26[55] = 0;
            global.field26[54] = 0;
            global.field26[53] = 0;
            script30();
        }
    }

What the hell?

tl;dr: KTK3 does save file verification through checking the first $n$ bytes of savefile as some chosen decimal digits of the random number generated on first launch. The following are standard Yuutu-engine style savefile.

fundimentally broken lmao

PoC

( ͡° ͜ʖ ͡°)

bruh too lazy, do it urself

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment