re(challenge): the unlock is a bit test, and the mission table is on disc
Static only. Bounding each GamePart's code block by its factory creator thunk (id -> creator recovered for 22 of 24 registrations at 0x8280C000-0x8280F800) puts GamePart_ChallengeMission at 0x82187E60-0x8218CF10. Resolving every string that block references gives the screen's config schema, and the record itself is on disc -- tables.pak schema 54a10697, one copy per language, English entry #64. Six missions: TimeAttack (record Time), ScoreAttack (record Points) and Extra01..Extra04, each with MISSION_ID / REQUIREMENT / REQUIREMENT_DESC / THUMBNAIL / STAGE_DESC / NEW_STAGE and a NORMAL_BUTTON / GRAY_BUTTON pair -- so the screen always lists all six and greys out what is not earned. THE GATE (0x82189970-0x821899D8), read off the code: REQUIREMENT absent -> available REQUIREMENT == "Always" -> available else n = atoi(REQUIREMENT) n == 0 -> locked n < 24 -> test bit n of the word at singleton+80 n >= 24 -> test bit (n-24) of the word at singleton+1956 The singleton is 0x821707C0 (lazy, global 0x828F48BC). So availability is one bit in a progress bitfield and REQUIREMENT is a bit INDEX -- not a stage number, score or difficulty. Values per mission are 🟡: the pool's numeric tokens are 16/25/26/27/29 and 24/28 already appear earlier as font metrics, so they would be deduped -- which fits 24..29 but IDXD dedup makes positional pairing unsound here, so it is recorded as a hypothesis, not a table. Negative: the requirement TEXT is not in GP_CHALLENGE.pak (TextIndex over it = 0 entries; its only prose is embedded font copyright). Its PATH is a per-language branch the loader does not currently reproduce. Next: three stores to +1956 sit in 0x822C7DD0 / 0x822C8748, the same region as the save serializer 0x822C00E8 -- if the bits are save-backed, a hand-written save unlocks all six challenge missions and the last 42 units become one run.
This commit is contained in:
78
crates/sylpheed-formats/examples/challenge_screen.rs
Normal file
78
crates/sylpheed-formats/examples/challenge_screen.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
//! RE probe: the GamePart_ChallengeMission screen config.
|
||||||
|
//!
|
||||||
|
//! The class's code range (`0x82187E60`..`0x8218CF10`, bounded by the factory
|
||||||
|
//! creator thunks either side) references these config keys:
|
||||||
|
//! MISSIONS, MISSION_ID, NEW_STAGE, REQUIREMENT, REQUIREMENT_DESC, "Always",
|
||||||
|
//! GRAY_BUTTON, NORMAL_BUTTON, THUMBNAIL, STAGE_DESC, TEXT_STAGE,
|
||||||
|
//! RECORD_TYPE, "Time", TEXT_RECORD, BASE_INFO
|
||||||
|
//! i.e. the challenge list is a *config record* with a per-mission REQUIREMENT.
|
||||||
|
//! It lives in `tables.pak` (one copy per language), not in GP_CHALLENGE.pak.
|
||||||
|
//!
|
||||||
|
//! Run: cargo run --release -p sylpheed-formats --example challenge_screen -- <disc-root>
|
||||||
|
|
||||||
|
use sylpheed_formats::{idxd::IdxdObject, pak::PakArchive};
|
||||||
|
|
||||||
|
const KEYS: &[&str] = &[
|
||||||
|
"MISSIONS",
|
||||||
|
"MISSION_ID",
|
||||||
|
"REQUIREMENT",
|
||||||
|
"REQUIREMENT_DESC",
|
||||||
|
"NEW_STAGE",
|
||||||
|
"GRAY_BUTTON",
|
||||||
|
"NORMAL_BUTTON",
|
||||||
|
"RECORD_TYPE",
|
||||||
|
"THUMBNAIL",
|
||||||
|
"STAGE_DESC",
|
||||||
|
];
|
||||||
|
|
||||||
|
fn find(h: &[u8], n: &[u8]) -> bool {
|
||||||
|
h.windows(n.len()).any(|w| w == n)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let disc = std::env::args().nth(1).unwrap_or_else(|| {
|
||||||
|
std::env::var("SYLPHEED_DISC").expect("pass disc root or set SYLPHEED_DISC")
|
||||||
|
});
|
||||||
|
|
||||||
|
let dat = format!("{disc}/dat");
|
||||||
|
let mut paks: Vec<_> = std::fs::read_dir(&dat)
|
||||||
|
.expect("dat dir")
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.map(|e| e.path())
|
||||||
|
.filter(|p| p.extension().is_some_and(|x| x == "pak"))
|
||||||
|
.collect();
|
||||||
|
paks.sort();
|
||||||
|
|
||||||
|
for p in &paks {
|
||||||
|
let Ok(arc) = PakArchive::open(p) else { continue };
|
||||||
|
for (i, e) in arc.entries().iter().enumerate() {
|
||||||
|
let Ok(b) = arc.read(e) else { continue };
|
||||||
|
if KEYS.iter().filter(|k| find(&b, k.as_bytes())).count() < KEYS.len() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let name = p.file_name().unwrap().to_string_lossy().to_string();
|
||||||
|
match IdxdObject::parse(&b) {
|
||||||
|
Ok(o) => {
|
||||||
|
let toks = o.tokens();
|
||||||
|
// language tag: the PATH value, e.g. "dat\GP_CHALLENGE.pak+eng\"
|
||||||
|
let lang = toks
|
||||||
|
.iter()
|
||||||
|
.find(|t| t.contains("GP_CHALLENGE.pak+"))
|
||||||
|
.map(|t| t.to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
println!(
|
||||||
|
"\n===== {name} entry #{i} schema {:08x} {} tokens [{lang}] =====",
|
||||||
|
o.schema_hash,
|
||||||
|
toks.len()
|
||||||
|
);
|
||||||
|
// Print the pool in order; the record is key/value interleaved and
|
||||||
|
// IDXD dedupes repeats, so pairing is read by eye, not asserted.
|
||||||
|
for (j, t) in toks.iter().enumerate() {
|
||||||
|
println!(" {j:3} {t}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => println!("\n===== {name} entry #{i}: IDXD parse failed: {e} ====="),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
750
docs/re/captures/challenge-screen-config.txt
Normal file
750
docs/re/captures/challenge-screen-config.txt
Normal file
@@ -0,0 +1,750 @@
|
|||||||
|
|
||||||
|
===== tables.pak entry #60 schema 54a10697 123 tokens [dat\GP_CHALLENGE.pak+fra\] =====
|
||||||
|
0 xBASE_INFO
|
||||||
|
1 0x061031
|
||||||
|
2 VERSION
|
||||||
|
3 dat\GP_CHALLENGE.pak+fra\
|
||||||
|
4 PATH
|
||||||
|
5 strings.tbl
|
||||||
|
6 STRINGS
|
||||||
|
7 py_menu_scr.prt
|
||||||
|
8 MENU
|
||||||
|
9 CHIPS
|
||||||
|
10 py_menu_n_btn1.prt
|
||||||
|
11 Button_TimeAttack
|
||||||
|
12 py_menu_n_btn2.prt
|
||||||
|
13 Button_ScoreAttack
|
||||||
|
14 py_menu_n_btn3.prt
|
||||||
|
15 Button_Extra01
|
||||||
|
16 py_menu_n_btn4.prt
|
||||||
|
17 Button_Extra02
|
||||||
|
18 py_menu_n_btn5.prt
|
||||||
|
19 Button_Extra03
|
||||||
|
20 py_menu_n_btn6.prt
|
||||||
|
21 Button_Extra04
|
||||||
|
22 py_menu_b_btn1.prt
|
||||||
|
23 Button_TimeAttack_Gray
|
||||||
|
24 py_menu_b_btn2.prt
|
||||||
|
25 Button_ScoreAttack_Gray
|
||||||
|
26 py_menu_b_btn3.prt
|
||||||
|
27 Button_Extra01_Gray
|
||||||
|
28 py_menu_b_btn4.prt
|
||||||
|
29 Button_Extra02_Gray
|
||||||
|
30 py_menu_b_btn5.prt
|
||||||
|
31 Button_Extra03_Gray
|
||||||
|
32 py_menu_b_btn6.prt
|
||||||
|
33 Button_Extra04_Gray
|
||||||
|
34 py_menu_challenge1.t32
|
||||||
|
35 Thumbnail_TimeAttack
|
||||||
|
36 py_menu_challenge2.t32
|
||||||
|
37 Thumbnail_ScoreAttack
|
||||||
|
38 py_menu_challenge3.t32
|
||||||
|
39 Thumbnail_Extra01
|
||||||
|
40 py_menu_challenge4.t32
|
||||||
|
41 Thumbnail_Extra02
|
||||||
|
42 py_menu_challenge5.t32
|
||||||
|
43 Thumbnail_Extra03
|
||||||
|
44 py_menu_challenge6.t32
|
||||||
|
45 Thumbnail_Extra04
|
||||||
|
46 py_menu_str_time.t32
|
||||||
|
47 Record_Time
|
||||||
|
48 py_menu_str_point.t32
|
||||||
|
49 Record_Point
|
||||||
|
50 py_menu_str_p.t32
|
||||||
|
51 Unit_Point
|
||||||
|
52 py_menu_new.prt
|
||||||
|
53 New_Stage
|
||||||
|
54 FONTS
|
||||||
|
55 Font_Desc
|
||||||
|
56 Font_Record
|
||||||
|
57 CONTM___.TTF
|
||||||
|
58 FONT
|
||||||
|
59 24
|
||||||
|
60 HEIGHT
|
||||||
|
61 28
|
||||||
|
62 Desc_Stage
|
||||||
|
63 580,400
|
||||||
|
64 LOCATE
|
||||||
|
65 8
|
||||||
|
66 LINE_SPACE
|
||||||
|
67 Desc_Time
|
||||||
|
68 1048,608,R
|
||||||
|
69 0
|
||||||
|
70 INDEX
|
||||||
|
71 Desc_Points
|
||||||
|
72 1032,608,R
|
||||||
|
73 UNIT
|
||||||
|
74 New_Stage01
|
||||||
|
75 New_Stage02
|
||||||
|
76 0,60
|
||||||
|
77 INDEX_ADJUST
|
||||||
|
78 New_Stage03
|
||||||
|
79 0,120
|
||||||
|
80 New_Stage04
|
||||||
|
81 0,180
|
||||||
|
82 New_Stage05
|
||||||
|
83 0,240
|
||||||
|
84 New_Stage06
|
||||||
|
85 0,300
|
||||||
|
86 MISSIONS
|
||||||
|
87 TimeAttack
|
||||||
|
88 ScoreAttack
|
||||||
|
89 Extra01
|
||||||
|
90 Extra02
|
||||||
|
91 Extra03
|
||||||
|
92 Extra04
|
||||||
|
93 MISSION_ID
|
||||||
|
94 Time
|
||||||
|
95 RECORD_TYPE
|
||||||
|
96 16
|
||||||
|
97 REQUIREMENT
|
||||||
|
98 NORMAL_BUTTON
|
||||||
|
99 GRAY_BUTTON
|
||||||
|
100 THUMBNAIL
|
||||||
|
101 TimeAttackRequirement
|
||||||
|
102 REQUIREMENT_DESC
|
||||||
|
103 TimeAttackStageDesc
|
||||||
|
104 STAGE_DESC
|
||||||
|
105 TEXT_STAGE
|
||||||
|
106 TEXT_RECORD
|
||||||
|
107 NEW_STAGE
|
||||||
|
108 25
|
||||||
|
109 Points
|
||||||
|
110 ScoreAttackRequirement
|
||||||
|
111 ScoreAttackStageDesc
|
||||||
|
112 26
|
||||||
|
113 Extra01Requirement
|
||||||
|
114 ExtraStage01Desc
|
||||||
|
115 27
|
||||||
|
116 Extra02Requirement
|
||||||
|
117 ExtraStage02Desc
|
||||||
|
118 Extra03Requirement
|
||||||
|
119 ExtraStage03Desc
|
||||||
|
120 29
|
||||||
|
121 Extra04Requirement
|
||||||
|
122 ExtraStage04Desc
|
||||||
|
|
||||||
|
===== tables.pak entry #64 schema 54a10697 123 tokens [dat\GP_CHALLENGE.pak+eng\] =====
|
||||||
|
0 xBASE_INFO
|
||||||
|
1 0x061031
|
||||||
|
2 VERSION
|
||||||
|
3 dat\GP_CHALLENGE.pak+eng\
|
||||||
|
4 PATH
|
||||||
|
5 strings.tbl
|
||||||
|
6 STRINGS
|
||||||
|
7 py_menu_scr.prt
|
||||||
|
8 MENU
|
||||||
|
9 CHIPS
|
||||||
|
10 py_menu_n_btn1.prt
|
||||||
|
11 Button_TimeAttack
|
||||||
|
12 py_menu_n_btn2.prt
|
||||||
|
13 Button_ScoreAttack
|
||||||
|
14 py_menu_n_btn3.prt
|
||||||
|
15 Button_Extra01
|
||||||
|
16 py_menu_n_btn4.prt
|
||||||
|
17 Button_Extra02
|
||||||
|
18 py_menu_n_btn5.prt
|
||||||
|
19 Button_Extra03
|
||||||
|
20 py_menu_n_btn6.prt
|
||||||
|
21 Button_Extra04
|
||||||
|
22 py_menu_b_btn1.prt
|
||||||
|
23 Button_TimeAttack_Gray
|
||||||
|
24 py_menu_b_btn2.prt
|
||||||
|
25 Button_ScoreAttack_Gray
|
||||||
|
26 py_menu_b_btn3.prt
|
||||||
|
27 Button_Extra01_Gray
|
||||||
|
28 py_menu_b_btn4.prt
|
||||||
|
29 Button_Extra02_Gray
|
||||||
|
30 py_menu_b_btn5.prt
|
||||||
|
31 Button_Extra03_Gray
|
||||||
|
32 py_menu_b_btn6.prt
|
||||||
|
33 Button_Extra04_Gray
|
||||||
|
34 py_menu_challenge1.t32
|
||||||
|
35 Thumbnail_TimeAttack
|
||||||
|
36 py_menu_challenge2.t32
|
||||||
|
37 Thumbnail_ScoreAttack
|
||||||
|
38 py_menu_challenge3.t32
|
||||||
|
39 Thumbnail_Extra01
|
||||||
|
40 py_menu_challenge4.t32
|
||||||
|
41 Thumbnail_Extra02
|
||||||
|
42 py_menu_challenge5.t32
|
||||||
|
43 Thumbnail_Extra03
|
||||||
|
44 py_menu_challenge6.t32
|
||||||
|
45 Thumbnail_Extra04
|
||||||
|
46 py_menu_str_time.t32
|
||||||
|
47 Record_Time
|
||||||
|
48 py_menu_str_point.t32
|
||||||
|
49 Record_Point
|
||||||
|
50 py_menu_str_p.t32
|
||||||
|
51 Unit_Point
|
||||||
|
52 py_menu_new.prt
|
||||||
|
53 New_Stage
|
||||||
|
54 FONTS
|
||||||
|
55 Font_Desc
|
||||||
|
56 Font_Record
|
||||||
|
57 CONTM___.TTF
|
||||||
|
58 FONT
|
||||||
|
59 24
|
||||||
|
60 HEIGHT
|
||||||
|
61 28
|
||||||
|
62 Desc_Stage
|
||||||
|
63 580,400
|
||||||
|
64 LOCATE
|
||||||
|
65 8
|
||||||
|
66 LINE_SPACE
|
||||||
|
67 Desc_Time
|
||||||
|
68 1048,608,R
|
||||||
|
69 0
|
||||||
|
70 INDEX
|
||||||
|
71 Desc_Points
|
||||||
|
72 1032,608,R
|
||||||
|
73 UNIT
|
||||||
|
74 New_Stage01
|
||||||
|
75 New_Stage02
|
||||||
|
76 0,60
|
||||||
|
77 INDEX_ADJUST
|
||||||
|
78 New_Stage03
|
||||||
|
79 0,120
|
||||||
|
80 New_Stage04
|
||||||
|
81 0,180
|
||||||
|
82 New_Stage05
|
||||||
|
83 0,240
|
||||||
|
84 New_Stage06
|
||||||
|
85 0,300
|
||||||
|
86 MISSIONS
|
||||||
|
87 TimeAttack
|
||||||
|
88 ScoreAttack
|
||||||
|
89 Extra01
|
||||||
|
90 Extra02
|
||||||
|
91 Extra03
|
||||||
|
92 Extra04
|
||||||
|
93 MISSION_ID
|
||||||
|
94 Time
|
||||||
|
95 RECORD_TYPE
|
||||||
|
96 16
|
||||||
|
97 REQUIREMENT
|
||||||
|
98 NORMAL_BUTTON
|
||||||
|
99 GRAY_BUTTON
|
||||||
|
100 THUMBNAIL
|
||||||
|
101 TimeAttackRequirement
|
||||||
|
102 REQUIREMENT_DESC
|
||||||
|
103 TimeAttackStageDesc
|
||||||
|
104 STAGE_DESC
|
||||||
|
105 TEXT_STAGE
|
||||||
|
106 TEXT_RECORD
|
||||||
|
107 NEW_STAGE
|
||||||
|
108 25
|
||||||
|
109 Points
|
||||||
|
110 ScoreAttackRequirement
|
||||||
|
111 ScoreAttackStageDesc
|
||||||
|
112 26
|
||||||
|
113 Extra01Requirement
|
||||||
|
114 ExtraStage01Desc
|
||||||
|
115 27
|
||||||
|
116 Extra02Requirement
|
||||||
|
117 ExtraStage02Desc
|
||||||
|
118 Extra03Requirement
|
||||||
|
119 ExtraStage03Desc
|
||||||
|
120 29
|
||||||
|
121 Extra04Requirement
|
||||||
|
122 ExtraStage04Desc
|
||||||
|
|
||||||
|
===== tables.pak entry #67 schema 54a10697 123 tokens [dat\GP_CHALLENGE.pak+deu\] =====
|
||||||
|
0 xBASE_INFO
|
||||||
|
1 0x061031
|
||||||
|
2 VERSION
|
||||||
|
3 dat\GP_CHALLENGE.pak+deu\
|
||||||
|
4 PATH
|
||||||
|
5 strings.tbl
|
||||||
|
6 STRINGS
|
||||||
|
7 py_menu_scr.prt
|
||||||
|
8 MENU
|
||||||
|
9 CHIPS
|
||||||
|
10 py_menu_n_btn1.prt
|
||||||
|
11 Button_TimeAttack
|
||||||
|
12 py_menu_n_btn2.prt
|
||||||
|
13 Button_ScoreAttack
|
||||||
|
14 py_menu_n_btn3.prt
|
||||||
|
15 Button_Extra01
|
||||||
|
16 py_menu_n_btn4.prt
|
||||||
|
17 Button_Extra02
|
||||||
|
18 py_menu_n_btn5.prt
|
||||||
|
19 Button_Extra03
|
||||||
|
20 py_menu_n_btn6.prt
|
||||||
|
21 Button_Extra04
|
||||||
|
22 py_menu_b_btn1.prt
|
||||||
|
23 Button_TimeAttack_Gray
|
||||||
|
24 py_menu_b_btn2.prt
|
||||||
|
25 Button_ScoreAttack_Gray
|
||||||
|
26 py_menu_b_btn3.prt
|
||||||
|
27 Button_Extra01_Gray
|
||||||
|
28 py_menu_b_btn4.prt
|
||||||
|
29 Button_Extra02_Gray
|
||||||
|
30 py_menu_b_btn5.prt
|
||||||
|
31 Button_Extra03_Gray
|
||||||
|
32 py_menu_b_btn6.prt
|
||||||
|
33 Button_Extra04_Gray
|
||||||
|
34 py_menu_challenge1.t32
|
||||||
|
35 Thumbnail_TimeAttack
|
||||||
|
36 py_menu_challenge2.t32
|
||||||
|
37 Thumbnail_ScoreAttack
|
||||||
|
38 py_menu_challenge3.t32
|
||||||
|
39 Thumbnail_Extra01
|
||||||
|
40 py_menu_challenge4.t32
|
||||||
|
41 Thumbnail_Extra02
|
||||||
|
42 py_menu_challenge5.t32
|
||||||
|
43 Thumbnail_Extra03
|
||||||
|
44 py_menu_challenge6.t32
|
||||||
|
45 Thumbnail_Extra04
|
||||||
|
46 py_menu_str_time.t32
|
||||||
|
47 Record_Time
|
||||||
|
48 py_menu_str_point.t32
|
||||||
|
49 Record_Point
|
||||||
|
50 py_menu_str_p.t32
|
||||||
|
51 Unit_Point
|
||||||
|
52 py_menu_new.prt
|
||||||
|
53 New_Stage
|
||||||
|
54 FONTS
|
||||||
|
55 Font_Desc
|
||||||
|
56 Font_Record
|
||||||
|
57 CONTM___.TTF
|
||||||
|
58 FONT
|
||||||
|
59 24
|
||||||
|
60 HEIGHT
|
||||||
|
61 28
|
||||||
|
62 Desc_Stage
|
||||||
|
63 580,400
|
||||||
|
64 LOCATE
|
||||||
|
65 8
|
||||||
|
66 LINE_SPACE
|
||||||
|
67 Desc_Time
|
||||||
|
68 1048,608,R
|
||||||
|
69 0
|
||||||
|
70 INDEX
|
||||||
|
71 Desc_Points
|
||||||
|
72 1032,608,R
|
||||||
|
73 UNIT
|
||||||
|
74 New_Stage01
|
||||||
|
75 New_Stage02
|
||||||
|
76 0,60
|
||||||
|
77 INDEX_ADJUST
|
||||||
|
78 New_Stage03
|
||||||
|
79 0,120
|
||||||
|
80 New_Stage04
|
||||||
|
81 0,180
|
||||||
|
82 New_Stage05
|
||||||
|
83 0,240
|
||||||
|
84 New_Stage06
|
||||||
|
85 0,300
|
||||||
|
86 MISSIONS
|
||||||
|
87 TimeAttack
|
||||||
|
88 ScoreAttack
|
||||||
|
89 Extra01
|
||||||
|
90 Extra02
|
||||||
|
91 Extra03
|
||||||
|
92 Extra04
|
||||||
|
93 MISSION_ID
|
||||||
|
94 Time
|
||||||
|
95 RECORD_TYPE
|
||||||
|
96 16
|
||||||
|
97 REQUIREMENT
|
||||||
|
98 NORMAL_BUTTON
|
||||||
|
99 GRAY_BUTTON
|
||||||
|
100 THUMBNAIL
|
||||||
|
101 TimeAttackRequirement
|
||||||
|
102 REQUIREMENT_DESC
|
||||||
|
103 TimeAttackStageDesc
|
||||||
|
104 STAGE_DESC
|
||||||
|
105 TEXT_STAGE
|
||||||
|
106 TEXT_RECORD
|
||||||
|
107 NEW_STAGE
|
||||||
|
108 25
|
||||||
|
109 Points
|
||||||
|
110 ScoreAttackRequirement
|
||||||
|
111 ScoreAttackStageDesc
|
||||||
|
112 26
|
||||||
|
113 Extra01Requirement
|
||||||
|
114 ExtraStage01Desc
|
||||||
|
115 27
|
||||||
|
116 Extra02Requirement
|
||||||
|
117 ExtraStage02Desc
|
||||||
|
118 Extra03Requirement
|
||||||
|
119 ExtraStage03Desc
|
||||||
|
120 29
|
||||||
|
121 Extra04Requirement
|
||||||
|
122 ExtraStage04Desc
|
||||||
|
|
||||||
|
===== tables.pak entry #68 schema 54a10697 123 tokens [dat\GP_CHALLENGE.pak+ita\] =====
|
||||||
|
0 xBASE_INFO
|
||||||
|
1 0x061031
|
||||||
|
2 VERSION
|
||||||
|
3 dat\GP_CHALLENGE.pak+ita\
|
||||||
|
4 PATH
|
||||||
|
5 strings.tbl
|
||||||
|
6 STRINGS
|
||||||
|
7 py_menu_scr.prt
|
||||||
|
8 MENU
|
||||||
|
9 CHIPS
|
||||||
|
10 py_menu_n_btn1.prt
|
||||||
|
11 Button_TimeAttack
|
||||||
|
12 py_menu_n_btn2.prt
|
||||||
|
13 Button_ScoreAttack
|
||||||
|
14 py_menu_n_btn3.prt
|
||||||
|
15 Button_Extra01
|
||||||
|
16 py_menu_n_btn4.prt
|
||||||
|
17 Button_Extra02
|
||||||
|
18 py_menu_n_btn5.prt
|
||||||
|
19 Button_Extra03
|
||||||
|
20 py_menu_n_btn6.prt
|
||||||
|
21 Button_Extra04
|
||||||
|
22 py_menu_b_btn1.prt
|
||||||
|
23 Button_TimeAttack_Gray
|
||||||
|
24 py_menu_b_btn2.prt
|
||||||
|
25 Button_ScoreAttack_Gray
|
||||||
|
26 py_menu_b_btn3.prt
|
||||||
|
27 Button_Extra01_Gray
|
||||||
|
28 py_menu_b_btn4.prt
|
||||||
|
29 Button_Extra02_Gray
|
||||||
|
30 py_menu_b_btn5.prt
|
||||||
|
31 Button_Extra03_Gray
|
||||||
|
32 py_menu_b_btn6.prt
|
||||||
|
33 Button_Extra04_Gray
|
||||||
|
34 py_menu_challenge1.t32
|
||||||
|
35 Thumbnail_TimeAttack
|
||||||
|
36 py_menu_challenge2.t32
|
||||||
|
37 Thumbnail_ScoreAttack
|
||||||
|
38 py_menu_challenge3.t32
|
||||||
|
39 Thumbnail_Extra01
|
||||||
|
40 py_menu_challenge4.t32
|
||||||
|
41 Thumbnail_Extra02
|
||||||
|
42 py_menu_challenge5.t32
|
||||||
|
43 Thumbnail_Extra03
|
||||||
|
44 py_menu_challenge6.t32
|
||||||
|
45 Thumbnail_Extra04
|
||||||
|
46 py_menu_str_time.t32
|
||||||
|
47 Record_Time
|
||||||
|
48 py_menu_str_point.t32
|
||||||
|
49 Record_Point
|
||||||
|
50 py_menu_str_p.t32
|
||||||
|
51 Unit_Point
|
||||||
|
52 py_menu_new.prt
|
||||||
|
53 New_Stage
|
||||||
|
54 FONTS
|
||||||
|
55 Font_Desc
|
||||||
|
56 Font_Record
|
||||||
|
57 CONTM___.TTF
|
||||||
|
58 FONT
|
||||||
|
59 24
|
||||||
|
60 HEIGHT
|
||||||
|
61 28
|
||||||
|
62 Desc_Stage
|
||||||
|
63 580,400
|
||||||
|
64 LOCATE
|
||||||
|
65 8
|
||||||
|
66 LINE_SPACE
|
||||||
|
67 Desc_Time
|
||||||
|
68 1048,608,R
|
||||||
|
69 0
|
||||||
|
70 INDEX
|
||||||
|
71 Desc_Points
|
||||||
|
72 1032,608,R
|
||||||
|
73 UNIT
|
||||||
|
74 New_Stage01
|
||||||
|
75 New_Stage02
|
||||||
|
76 0,60
|
||||||
|
77 INDEX_ADJUST
|
||||||
|
78 New_Stage03
|
||||||
|
79 0,120
|
||||||
|
80 New_Stage04
|
||||||
|
81 0,180
|
||||||
|
82 New_Stage05
|
||||||
|
83 0,240
|
||||||
|
84 New_Stage06
|
||||||
|
85 0,300
|
||||||
|
86 MISSIONS
|
||||||
|
87 TimeAttack
|
||||||
|
88 ScoreAttack
|
||||||
|
89 Extra01
|
||||||
|
90 Extra02
|
||||||
|
91 Extra03
|
||||||
|
92 Extra04
|
||||||
|
93 MISSION_ID
|
||||||
|
94 Time
|
||||||
|
95 RECORD_TYPE
|
||||||
|
96 16
|
||||||
|
97 REQUIREMENT
|
||||||
|
98 NORMAL_BUTTON
|
||||||
|
99 GRAY_BUTTON
|
||||||
|
100 THUMBNAIL
|
||||||
|
101 TimeAttackRequirement
|
||||||
|
102 REQUIREMENT_DESC
|
||||||
|
103 TimeAttackStageDesc
|
||||||
|
104 STAGE_DESC
|
||||||
|
105 TEXT_STAGE
|
||||||
|
106 TEXT_RECORD
|
||||||
|
107 NEW_STAGE
|
||||||
|
108 25
|
||||||
|
109 Points
|
||||||
|
110 ScoreAttackRequirement
|
||||||
|
111 ScoreAttackStageDesc
|
||||||
|
112 26
|
||||||
|
113 Extra01Requirement
|
||||||
|
114 ExtraStage01Desc
|
||||||
|
115 27
|
||||||
|
116 Extra02Requirement
|
||||||
|
117 ExtraStage02Desc
|
||||||
|
118 Extra03Requirement
|
||||||
|
119 ExtraStage03Desc
|
||||||
|
120 29
|
||||||
|
121 Extra04Requirement
|
||||||
|
122 ExtraStage04Desc
|
||||||
|
|
||||||
|
===== tables.pak entry #74 schema 54a10697 123 tokens [dat\GP_CHALLENGE.pak+jpn\] =====
|
||||||
|
0 BASE_INFO
|
||||||
|
1 0x061031
|
||||||
|
2 VERSION
|
||||||
|
3 dat\GP_CHALLENGE.pak+jpn\
|
||||||
|
4 PATH
|
||||||
|
5 strings.tbl
|
||||||
|
6 STRINGS
|
||||||
|
7 py_menu_scr.prt
|
||||||
|
8 MENU
|
||||||
|
9 CHIPS
|
||||||
|
10 py_menu_n_btn1.prt
|
||||||
|
11 Button_TimeAttack
|
||||||
|
12 py_menu_n_btn2.prt
|
||||||
|
13 Button_ScoreAttack
|
||||||
|
14 py_menu_n_btn3.prt
|
||||||
|
15 Button_Extra01
|
||||||
|
16 py_menu_n_btn4.prt
|
||||||
|
17 Button_Extra02
|
||||||
|
18 py_menu_n_btn5.prt
|
||||||
|
19 Button_Extra03
|
||||||
|
20 py_menu_n_btn6.prt
|
||||||
|
21 Button_Extra04
|
||||||
|
22 py_menu_b_btn1.prt
|
||||||
|
23 Button_TimeAttack_Gray
|
||||||
|
24 py_menu_b_btn2.prt
|
||||||
|
25 Button_ScoreAttack_Gray
|
||||||
|
26 py_menu_b_btn3.prt
|
||||||
|
27 Button_Extra01_Gray
|
||||||
|
28 py_menu_b_btn4.prt
|
||||||
|
29 Button_Extra02_Gray
|
||||||
|
30 py_menu_b_btn5.prt
|
||||||
|
31 Button_Extra03_Gray
|
||||||
|
32 py_menu_b_btn6.prt
|
||||||
|
33 Button_Extra04_Gray
|
||||||
|
34 py_menu_challenge1.t32
|
||||||
|
35 Thumbnail_TimeAttack
|
||||||
|
36 py_menu_challenge2.t32
|
||||||
|
37 Thumbnail_ScoreAttack
|
||||||
|
38 py_menu_challenge3.t32
|
||||||
|
39 Thumbnail_Extra01
|
||||||
|
40 py_menu_challenge4.t32
|
||||||
|
41 Thumbnail_Extra02
|
||||||
|
42 py_menu_challenge5.t32
|
||||||
|
43 Thumbnail_Extra03
|
||||||
|
44 py_menu_challenge6.t32
|
||||||
|
45 Thumbnail_Extra04
|
||||||
|
46 py_menu_str_time.t32
|
||||||
|
47 Record_Time
|
||||||
|
48 py_menu_str_point.t32
|
||||||
|
49 Record_Point
|
||||||
|
50 py_menu_str_p.t32
|
||||||
|
51 Unit_Point
|
||||||
|
52 py_menu_new.prt
|
||||||
|
53 New_Stage
|
||||||
|
54 FONTS
|
||||||
|
55 Font_Desc
|
||||||
|
56 Font_Record
|
||||||
|
57 DFSOGE5.TTC
|
||||||
|
58 FONT
|
||||||
|
59 24
|
||||||
|
60 HEIGHT
|
||||||
|
61 CONTM___.TTF
|
||||||
|
62 28
|
||||||
|
63 Desc_Stage
|
||||||
|
64 580,400
|
||||||
|
65 LOCATE
|
||||||
|
66 8
|
||||||
|
67 LINE_SPACE
|
||||||
|
68 Desc_Time
|
||||||
|
69 1048,608,R
|
||||||
|
70 INDEX
|
||||||
|
71 Desc_Points
|
||||||
|
72 1032,608,R
|
||||||
|
73 UNIT
|
||||||
|
74 New_Stage01
|
||||||
|
75 New_Stage02
|
||||||
|
76 0,60
|
||||||
|
77 INDEX_ADJUST
|
||||||
|
78 New_Stage03
|
||||||
|
79 0,120
|
||||||
|
80 New_Stage04
|
||||||
|
81 0,180
|
||||||
|
82 New_Stage05
|
||||||
|
83 0,240
|
||||||
|
84 New_Stage06
|
||||||
|
85 0,300
|
||||||
|
86 MISSIONS
|
||||||
|
87 TimeAttack
|
||||||
|
88 ScoreAttack
|
||||||
|
89 Extra01
|
||||||
|
90 Extra02
|
||||||
|
91 Extra03
|
||||||
|
92 Extra04
|
||||||
|
93 MISSION_ID
|
||||||
|
94 Time
|
||||||
|
95 RECORD_TYPE
|
||||||
|
96 16
|
||||||
|
97 REQUIREMENT
|
||||||
|
98 NORMAL_BUTTON
|
||||||
|
99 GRAY_BUTTON
|
||||||
|
100 THUMBNAIL
|
||||||
|
101 TimeAttackRequirement
|
||||||
|
102 REQUIREMENT_DESC
|
||||||
|
103 TimeAttackStageDesc
|
||||||
|
104 STAGE_DESC
|
||||||
|
105 TEXT_STAGE
|
||||||
|
106 TEXT_RECORD
|
||||||
|
107 NEW_STAGE
|
||||||
|
108 25
|
||||||
|
109 Points
|
||||||
|
110 ScoreAttackRequirement
|
||||||
|
111 ScoreAttackStageDesc
|
||||||
|
112 26
|
||||||
|
113 Extra01Requirement
|
||||||
|
114 ExtraStage01Desc
|
||||||
|
115 27
|
||||||
|
116 Extra02Requirement
|
||||||
|
117 ExtraStage02Desc
|
||||||
|
118 Extra03Requirement
|
||||||
|
119 ExtraStage03Desc
|
||||||
|
120 29
|
||||||
|
121 Extra04Requirement
|
||||||
|
122 ExtraStage04Desc
|
||||||
|
|
||||||
|
===== tables.pak entry #75 schema 54a10697 123 tokens [dat\GP_CHALLENGE.pak+esp\] =====
|
||||||
|
0 xBASE_INFO
|
||||||
|
1 0x061031
|
||||||
|
2 VERSION
|
||||||
|
3 dat\GP_CHALLENGE.pak+esp\
|
||||||
|
4 PATH
|
||||||
|
5 strings.tbl
|
||||||
|
6 STRINGS
|
||||||
|
7 py_menu_scr.prt
|
||||||
|
8 MENU
|
||||||
|
9 CHIPS
|
||||||
|
10 py_menu_n_btn1.prt
|
||||||
|
11 Button_TimeAttack
|
||||||
|
12 py_menu_n_btn2.prt
|
||||||
|
13 Button_ScoreAttack
|
||||||
|
14 py_menu_n_btn3.prt
|
||||||
|
15 Button_Extra01
|
||||||
|
16 py_menu_n_btn4.prt
|
||||||
|
17 Button_Extra02
|
||||||
|
18 py_menu_n_btn5.prt
|
||||||
|
19 Button_Extra03
|
||||||
|
20 py_menu_n_btn6.prt
|
||||||
|
21 Button_Extra04
|
||||||
|
22 py_menu_b_btn1.prt
|
||||||
|
23 Button_TimeAttack_Gray
|
||||||
|
24 py_menu_b_btn2.prt
|
||||||
|
25 Button_ScoreAttack_Gray
|
||||||
|
26 py_menu_b_btn3.prt
|
||||||
|
27 Button_Extra01_Gray
|
||||||
|
28 py_menu_b_btn4.prt
|
||||||
|
29 Button_Extra02_Gray
|
||||||
|
30 py_menu_b_btn5.prt
|
||||||
|
31 Button_Extra03_Gray
|
||||||
|
32 py_menu_b_btn6.prt
|
||||||
|
33 Button_Extra04_Gray
|
||||||
|
34 py_menu_challenge1.t32
|
||||||
|
35 Thumbnail_TimeAttack
|
||||||
|
36 py_menu_challenge2.t32
|
||||||
|
37 Thumbnail_ScoreAttack
|
||||||
|
38 py_menu_challenge3.t32
|
||||||
|
39 Thumbnail_Extra01
|
||||||
|
40 py_menu_challenge4.t32
|
||||||
|
41 Thumbnail_Extra02
|
||||||
|
42 py_menu_challenge5.t32
|
||||||
|
43 Thumbnail_Extra03
|
||||||
|
44 py_menu_challenge6.t32
|
||||||
|
45 Thumbnail_Extra04
|
||||||
|
46 py_menu_str_time.t32
|
||||||
|
47 Record_Time
|
||||||
|
48 py_menu_str_point.t32
|
||||||
|
49 Record_Point
|
||||||
|
50 py_menu_str_p.t32
|
||||||
|
51 Unit_Point
|
||||||
|
52 py_menu_new.prt
|
||||||
|
53 New_Stage
|
||||||
|
54 FONTS
|
||||||
|
55 Font_Desc
|
||||||
|
56 Font_Record
|
||||||
|
57 CONTM___.TTF
|
||||||
|
58 FONT
|
||||||
|
59 24
|
||||||
|
60 HEIGHT
|
||||||
|
61 28
|
||||||
|
62 Desc_Stage
|
||||||
|
63 580,400
|
||||||
|
64 LOCATE
|
||||||
|
65 8
|
||||||
|
66 LINE_SPACE
|
||||||
|
67 Desc_Time
|
||||||
|
68 1048,608,R
|
||||||
|
69 0
|
||||||
|
70 INDEX
|
||||||
|
71 Desc_Points
|
||||||
|
72 1032,608,R
|
||||||
|
73 UNIT
|
||||||
|
74 New_Stage01
|
||||||
|
75 New_Stage02
|
||||||
|
76 0,60
|
||||||
|
77 INDEX_ADJUST
|
||||||
|
78 New_Stage03
|
||||||
|
79 0,120
|
||||||
|
80 New_Stage04
|
||||||
|
81 0,180
|
||||||
|
82 New_Stage05
|
||||||
|
83 0,240
|
||||||
|
84 New_Stage06
|
||||||
|
85 0,300
|
||||||
|
86 MISSIONS
|
||||||
|
87 TimeAttack
|
||||||
|
88 ScoreAttack
|
||||||
|
89 Extra01
|
||||||
|
90 Extra02
|
||||||
|
91 Extra03
|
||||||
|
92 Extra04
|
||||||
|
93 MISSION_ID
|
||||||
|
94 Time
|
||||||
|
95 RECORD_TYPE
|
||||||
|
96 16
|
||||||
|
97 REQUIREMENT
|
||||||
|
98 NORMAL_BUTTON
|
||||||
|
99 GRAY_BUTTON
|
||||||
|
100 THUMBNAIL
|
||||||
|
101 TimeAttackRequirement
|
||||||
|
102 REQUIREMENT_DESC
|
||||||
|
103 TimeAttackStageDesc
|
||||||
|
104 STAGE_DESC
|
||||||
|
105 TEXT_STAGE
|
||||||
|
106 TEXT_RECORD
|
||||||
|
107 NEW_STAGE
|
||||||
|
108 25
|
||||||
|
109 Points
|
||||||
|
110 ScoreAttackRequirement
|
||||||
|
111 ScoreAttackStageDesc
|
||||||
|
112 26
|
||||||
|
113 Extra01Requirement
|
||||||
|
114 ExtraStage01Desc
|
||||||
|
115 27
|
||||||
|
116 Extra02Requirement
|
||||||
|
117 ExtraStage02Desc
|
||||||
|
118 Extra03Requirement
|
||||||
|
119 ExtraStage03Desc
|
||||||
|
120 29
|
||||||
|
121 Extra04Requirement
|
||||||
|
122 ExtraStage04Desc
|
||||||
@@ -109,7 +109,82 @@ trap) but it is **not proven** — proving it needs a run.
|
|||||||
range", and the kind field is the likely gate. It has never been tested — only
|
range", and the kind field is the likely gate. It has never been tested — only
|
||||||
1–16 and 24–29 were.
|
1–16 and 24–29 were.
|
||||||
|
|
||||||
## 5. The unlock condition ❔
|
## 5. The challenge screen, its six missions, and the unlock gate ✅
|
||||||
|
|
||||||
|
The factory registration sites (`0x8280C000`–`0x8280F800`) give **id → creator** for
|
||||||
|
22 of the 24 registered parts, and the creators bound each class's code block. So
|
||||||
|
`GamePart_ChallengeMission`'s methods are **`0x82187E60`–`0x8218CF10`** (between
|
||||||
|
`GP_BUNK`'s creator `0x82187E38` and its own `0x8218CF10`). Resolving every string
|
||||||
|
that range references gives the screen's whole config schema:
|
||||||
|
|
||||||
|
```
|
||||||
|
MISSIONS · MISSION_ID · RECORD_TYPE · REQUIREMENT · REQUIREMENT_DESC
|
||||||
|
NORMAL_BUTTON · GRAY_BUTTON · THUMBNAIL · STAGE_DESC · TEXT_STAGE
|
||||||
|
TEXT_RECORD · NEW_STAGE · "Always" · "Time" · BASE_INFO
|
||||||
|
```
|
||||||
|
|
||||||
|
**The record itself is on disc**, in `tables.pak` (schema `54a10697`, one copy per
|
||||||
|
language — English is entry #64), *not* in `GP_CHALLENGE.pak`. It names six missions:
|
||||||
|
|
||||||
|
| slot | `MISSION_ID` | buttons / thumbnail | record |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | `TimeAttack` | `Button_TimeAttack{,_Gray}`, `Thumbnail_TimeAttack` | `Time` |
|
||||||
|
| 2 | `ScoreAttack` | `Button_ScoreAttack{,_Gray}` | `Points` |
|
||||||
|
| 3–6 | `Extra01`…`Extra04` | `Button_Extra0N{,_Gray}` | — |
|
||||||
|
|
||||||
|
`GRAY_BUTTON` is the locked art, `NORMAL_BUTTON` the unlocked art — so the screen
|
||||||
|
always shows all six and greys out what you have not earned. Full dump:
|
||||||
|
[`captures/challenge-screen-config.txt`](captures/challenge-screen-config.txt).
|
||||||
|
|
||||||
|
### 5.1 The gate is a bit test against a progress bitfield ✅
|
||||||
|
|
||||||
|
`0x82189870` builds the list, and `0x82189970`–`0x821899D8` is the availability
|
||||||
|
decision for each mission:
|
||||||
|
|
||||||
|
```asm
|
||||||
|
lookup REQUIREMENT in the mission record ; bl 0x82448C50
|
||||||
|
absent -> AVAILABLE
|
||||||
|
== "Always" -> AVAILABLE ; strcmp, bl 0x825EDD20
|
||||||
|
else n = atoi(v) ; bl 0x825EDCD0
|
||||||
|
n == 0 -> LOCKED
|
||||||
|
n < 24 -> bit n of word A
|
||||||
|
n >= 24 -> bit (n-24) of word B
|
||||||
|
bit set ? AVAILABLE : LOCKED
|
||||||
|
```
|
||||||
|
|
||||||
|
Word A and word B are read from a **singleton** (`0x821707C0`, lazily constructed
|
||||||
|
behind the global at `0x828F48BC`) at offsets **`+80`** and **`+1956`**. So challenge
|
||||||
|
availability is one bit in a progress bitfield, and `REQUIREMENT` is that bit's index
|
||||||
|
— **not** a stage number, a score, or a difficulty.
|
||||||
|
|
||||||
|
### 5.2 What the six requirement values are 🟡
|
||||||
|
|
||||||
|
The record's numeric tokens are `16`, `25`, `26`, `27`, `29`, and `24`/`28` are
|
||||||
|
already in the pool earlier (they double as font metrics), so they would be **deduped
|
||||||
|
away** if used. That is consistent with the six values being `24`–`29` — the same
|
||||||
|
range as the challenge stage records — but **IDXD dedupes the string pool, so
|
||||||
|
positional key/value pairing is not sound here** (the same trap the movie/subtitle map
|
||||||
|
hit). Recorded as a hypothesis: reading the values properly needs the record's binary
|
||||||
|
index section, not the pool.
|
||||||
|
|
||||||
|
**Negative worth keeping:** the requirement *text* (`TimeAttackRequirement`,
|
||||||
|
`Extra01Requirement`, …) is **not** in `GP_CHALLENGE.pak` — building a `TextIndex`
|
||||||
|
over it yields **0 entries** and its only wordy payload is embedded font copyright.
|
||||||
|
The config's `PATH` is `dat\GP_CHALLENGE.pak+eng\`, a per-language branch, so those
|
||||||
|
keys resolve through a naming scheme the current loader does not reproduce. Reading
|
||||||
|
them would say in plain English what each mission asks for — worth one more attempt
|
||||||
|
via `hash::TOC_NAME_SCHEMES`.
|
||||||
|
|
||||||
|
### 5.3 Why this matters operationally
|
||||||
|
|
||||||
|
If word A / word B are restored from the save, a **hand-written save with those bits
|
||||||
|
set unlocks all six challenge missions** — and the savegame round-trip is already
|
||||||
|
solved. That would turn the last 42 units of the Route-B harvest into one run instead
|
||||||
|
of an unreachable menu. Three `stw`s to `+1956` sit in `0x822C7DD0` / `0x822C8748`,
|
||||||
|
i.e. the same code region as the save serializer (`0x822C00E8`) and deserializer
|
||||||
|
(`0x822C0380`) — suggestive, **not yet checked**. That is the next step.
|
||||||
|
|
||||||
|
## 6. The unlock condition, from the strings ❔
|
||||||
|
|
||||||
Three strings say challenge missions are *announced*, not menu-browsed:
|
Three strings say challenge missions are *announced*, not menu-browsed:
|
||||||
|
|
||||||
@@ -126,7 +201,7 @@ config lookup, exactly like the save screen's sprite keys, so there is nothing t
|
|||||||
grep back to. Naming the flag needs the challenge-availability check disassembled
|
grep back to. Naming the flag needs the challenge-availability check disassembled
|
||||||
from the screen side.
|
from the screen side.
|
||||||
|
|
||||||
## 6. What this makes actionable
|
## 7. What this makes actionable
|
||||||
|
|
||||||
`roster_target` against the harvested CSV, with the stage families now named:
|
`roster_target` against the harvested CSV, with the stage families now named:
|
||||||
|
|
||||||
@@ -151,7 +226,9 @@ rest are not in any stage roster table and need a different lever.
|
|||||||
## Reproduce
|
## Reproduce
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --release -q -p sylpheed-formats --example challenge_map -- <disc-root>
|
cargo run --release -q -p sylpheed-formats --example challenge_map -- <disc-root>
|
||||||
|
cargo run --release -q -p sylpheed-formats --example challenge_screen -- <disc-root>
|
||||||
python3 xenia-rs/zq.py dis 0x82184df0 0x82184e30 # the three-way section switch
|
python3 xenia-rs/zq.py dis 0x82184df0 0x82184e30 # the three-way section switch
|
||||||
python3 xenia-rs/zq.py dis 0x82185ed0 0x82185f10 # the same switch, second site
|
python3 xenia-rs/zq.py dis 0x82185ed0 0x82185f10 # the same switch, second site
|
||||||
|
python3 xenia-rs/zq.py dis 0x82189860 0x821899f0 # the challenge availability gate
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user