cache write functions

This commit is contained in:
skillbert
2021-12-29 21:49:48 +01:00
parent 8a49afbd22
commit a279c6a75b
24 changed files with 852 additions and 420 deletions

View File

@@ -3,19 +3,19 @@
// run `npm run filetypes` to rebuild
export type achievements = {
name?: string | undefined
name?: string | null
description?: {
hasironman: number,
unk0: number,
descr: string,
unk1: number | undefined,
descr_ironman: string | undefined,
} | undefined
category?: number | undefined
spriteId?: number | undefined
runescore?: number | undefined
unknown_0x06?: number | undefined
rewardtext?: string | undefined
unk1: number | null,
descr_ironman: string | null,
} | null
category?: number | null
spriteId?: number | null
runescore?: number | null
unknown_0x06?: number | null
rewardtext?: string | null
subach_skills?: {
ironman: number,
level: number,
@@ -23,73 +23,73 @@ export type achievements = {
unk_0: number,
unk_1: number,
skill: number,
}[] | undefined
}[] | null
subach_varbits?: {
type: number,
value: number,
name: string,
stepsize: number,
varbit: number,
}[] | undefined
}[] | null
varbit_partial_state?: {
type: number,
value: number,
name: string,
stepsize: number,
varbit: number,
}[] | undefined
previous_achievements?: number[] | undefined
}[] | null
previous_achievements?: number[] | null
skill_reqs_2?: {
unk0: number,
level: number,
name: string,
unk1: number,
skill: number,
}[] | undefined
}[] | null
progress_states?: {
unk0: number,
value: number,
name: string,
varbits: number[],
}[] | undefined
}[] | null
subreqs?: {
unk0: number,
value: number,
name: string,
varbits: number[],
}[] | undefined
}[] | null
sub_achievements?: {
unk0: number,
achievement: number,
}[] | undefined
subcategory?: number | undefined
hidden?: number | undefined
f2p?: true | undefined
quest_req_for_miniquests?: number[] | undefined
quest_ids?: number[] | undefined
}[] | null
subcategory?: number | null
hidden?: number | null
f2p?: true | null
quest_req_for_miniquests?: number[] | null
quest_ids?: number[] | null
reqs23?: {
type: number,
varbit: number,
stepsize: number,
name: string | undefined,
requirement: number | undefined,
name: string | null,
requirement: number | null,
subbit: number,
}[] | undefined
}[] | null
reqs25?: {
type: number,
varbit: number,
value: number,
name: string | undefined,
requirement: number | undefined,
name: string | null,
requirement: number | null,
subbit: number,
}[] | undefined
unknown_0x13?: true | undefined
skill_req_count?: number[] | undefined
unknown_0x1D?: number | undefined
subreq_count?: number[] | undefined
unknown_0x1F?: number | undefined
unknown_0x20?: number | undefined
unknown_0x23?: true | undefined
unknown_0x25?: number | undefined
unknown_0x26?: true | undefined
}[] | null
unknown_0x13?: true | null
skill_req_count?: number[] | null
unknown_0x1D?: number | null
subreq_count?: number[] | null
unknown_0x1F?: number | null
unknown_0x20?: number | null
unknown_0x23?: true | null
unknown_0x25?: number | null
unknown_0x26?: true | null
};

View File

@@ -9,7 +9,7 @@ export type cacheindex = {
indices: ({
minor: number,
} & {
name: number | undefined,
name: number | null,
} & {
crc: number,
} & {

22
generated/enums.d.ts vendored
View File

@@ -3,33 +3,33 @@
// run `npm run filetypes` to rebuild
export type enums = {
key_type1?: number | undefined
value_type1?: number | undefined
key_type2?: number | undefined
value_type2?: number | undefined
stringValue?: string | undefined
intValue?: number | undefined
key_type1?: number | null
value_type1?: number | null
key_type2?: number | null
value_type2?: number | null
stringValue?: string | null
intValue?: number | null
stringArrayValue1?: [
number,
string,
][] | undefined
][] | null
intArrayValue1?: [
number,
number,
][] | undefined
][] | null
stringArrayValue2?: {
max: number,
values: [
number,
string,
][],
} | undefined
} | null
intArrayValue2?: {
max: number,
values: [
number,
number,
][],
} | undefined
unknown_83?: true | undefined
} | null
unknown_83?: true | null
};

200
generated/items.d.ts vendored
View File

@@ -3,168 +3,168 @@
// run `npm run filetypes` to rebuild
export type items = {
baseModel?: number | undefined
name?: string | undefined
buff_effect?: string | undefined
rotation_0?: number | undefined
rotation_1?: number | undefined
rotation_2?: number | undefined
modelTranslate_0?: number | undefined
modelTranslate_1?: number | undefined
unknown_0A?: true | undefined
stackable_1?: true | undefined
value?: number | undefined
equipSlotId?: number | undefined
equipId?: number | undefined
unknown_0F?: true | undefined
members?: true | undefined
multiStackSize?: number | undefined
maleModels_0?: number | undefined
maleModels_1?: number | undefined
femaleModels_0?: number | undefined
femaleModels_1?: number | undefined
unknown_1B?: number | undefined
ground_actions_0?: string | undefined
ground_actions_1?: string | undefined
ground_actions_2?: string | undefined
ground_actions_3?: string | undefined
ground_actions_4?: string | undefined
widget_actions_0?: string | undefined
widget_actions_1?: string | undefined
widget_actions_2?: string | undefined
widget_actions_3?: string | undefined
widget_actions_4?: string | undefined
baseModel?: number | null
name?: string | null
buff_effect?: string | null
rotation_0?: number | null
rotation_1?: number | null
rotation_2?: number | null
modelTranslate_0?: number | null
modelTranslate_1?: number | null
unknown_0A?: true | null
stackable_1?: true | null
value?: number | null
equipSlotId?: number | null
equipId?: number | null
unknown_0F?: true | null
members?: true | null
multiStackSize?: number | null
maleModels_0?: number | null
maleModels_1?: number | null
femaleModels_0?: number | null
femaleModels_1?: number | null
unknown_1B?: number | null
ground_actions_0?: string | null
ground_actions_1?: string | null
ground_actions_2?: string | null
ground_actions_3?: string | null
ground_actions_4?: string | null
widget_actions_0?: string | null
widget_actions_1?: string | null
widget_actions_2?: string | null
widget_actions_3?: string | null
widget_actions_4?: string | null
color_replacements?: [
number,
number,
][] | undefined
][] | null
material_replacements?: [
number,
number,
][] | undefined
][] | null
recolourPalette?: [
number,
number,
][] | undefined
nameColor?: number | undefined
recolorDstIndices?: number | undefined
retextureDstIndices?: number | undefined
tradeable?: true | undefined
buy_limit?: number | undefined
maleModels_2?: number | undefined
femaleModels_2?: number | undefined
maleHeads_0?: number | undefined
femaleHeads_0?: number | undefined
maleHeads_1?: number | undefined
femaleHeads_1?: number | undefined
category?: number | undefined
modelYaw?: number | undefined
dummyItem?: number | undefined
noteData?: number | undefined
noteTemplate?: number | undefined
][] | null
nameColor?: number | null
recolorDstIndices?: number | null
retextureDstIndices?: number | null
tradeable?: true | null
buy_limit?: number | null
maleModels_2?: number | null
femaleModels_2?: number | null
maleHeads_0?: number | null
femaleHeads_0?: number | null
maleHeads_1?: number | null
femaleHeads_1?: number | null
category?: number | null
modelYaw?: number | null
dummyItem?: number | null
noteData?: number | null
noteTemplate?: number | null
stack_info_0?: [
number,
number,
] | undefined
] | null
stack_info_1?: [
number,
number,
] | undefined
] | null
stack_info_2?: [
number,
number,
] | undefined
] | null
stack_info_3?: [
number,
number,
] | undefined
] | null
stack_info_4?: [
number,
number,
] | undefined
] | null
stack_info_5?: [
number,
number,
] | undefined
] | null
stack_info_6?: [
number,
number,
] | undefined
] | null
stack_info_7?: [
number,
number,
] | undefined
] | null
stack_info_8?: [
number,
number,
] | undefined
] | null
stack_info_9?: [
number,
number,
] | undefined
scale_0?: number | undefined
scale_1?: number | undefined
scale_2?: number | undefined
ambiance?: number | undefined
contrast?: number | undefined
team?: number | undefined
loanId?: number | undefined
loanTemplate?: number | undefined
] | null
scale_0?: number | null
scale_1?: number | null
scale_2?: number | null
ambiance?: number | null
contrast?: number | null
team?: number | null
loanId?: number | null
loanTemplate?: number | null
male_translate?: [
number,
number,
number,
] | undefined
] | null
female_translate?: [
number,
number,
number,
] | undefined
] | null
unknown_7F?: [
number,
number,
] | undefined
] | null
unknown_80?: [
number,
number,
] | undefined
] | null
unknown_81?: [
number,
number,
] | undefined
] | null
unknown_82?: [
number,
number,
] | undefined
] | null
quests?: [
number,
number,
][] | undefined
pickSizeShift?: number | undefined
bindLink?: number | undefined
bindTemplate?: number | undefined
ground_actions_cursor_0?: number | undefined
ground_actions_cursor_1?: number | undefined
ground_actions_cursor_2?: number | undefined
ground_actions_cursor_3?: number | undefined
ground_actions_cursor_4?: number | undefined
widget_actions_cursor_0?: number | undefined
widget_actions_cursor_1?: number | undefined
widget_actions_cursor_2?: number | undefined
widget_actions_cursor_3?: number | undefined
widget_actions_cursor_4?: number | undefined
dummy?: true | undefined
randomizeGroundPos?: true | undefined
combine_info?: number | undefined
combine_template?: number | undefined
combine_num_required?: number | undefined
combine_shard_name?: string | undefined
neverStackable?: true | undefined
unknown_A7?: true | undefined
unknown_A8?: true | undefined
][] | null
pickSizeShift?: number | null
bindLink?: number | null
bindTemplate?: number | null
ground_actions_cursor_0?: number | null
ground_actions_cursor_1?: number | null
ground_actions_cursor_2?: number | null
ground_actions_cursor_3?: number | null
ground_actions_cursor_4?: number | null
widget_actions_cursor_0?: number | null
widget_actions_cursor_1?: number | null
widget_actions_cursor_2?: number | null
widget_actions_cursor_3?: number | null
widget_actions_cursor_4?: number | null
dummy?: true | null
randomizeGroundPos?: true | null
combine_info?: number | null
combine_template?: number | null
combine_num_required?: number | null
combine_shard_name?: string | null
neverStackable?: true | null
unknown_A7?: true | null
unknown_A8?: true | null
extra?: {
prop: number,
intvalue: number | undefined,
stringvalue: string | undefined,
}[] | undefined
intvalue: number | null,
stringvalue: string | null,
}[] | null
};

View File

@@ -3,9 +3,9 @@
// run `npm run filetypes` to rebuild
export type mapscenes = {
sprite_id?: number | undefined
unknown_2?: number | undefined
unknown_3?: true | undefined
unknown_4?: true | undefined
unknown_5?: true | undefined
sprite_id?: number | null
unknown_2?: number | null
unknown_3?: true | null
unknown_4?: true | null
unknown_5?: true | null
};

View File

@@ -13,15 +13,15 @@ export type mapsquare_locations = {
type: number,
extra: {
flags: number,
rotation: number[] | undefined,
translateX: number | undefined,
translateY: number | undefined,
translateZ: number | undefined,
scale: number | undefined,
scaleX: number | undefined,
scaleY: number | undefined,
scaleZ: number | undefined,
} | undefined,
rotation: number[] | null,
translateX: number | null,
translateY: number | null,
translateZ: number | null,
scale: number | null,
scaleX: number | null,
scaleY: number | null,
scaleZ: number | null,
} | null,
}[],
}[],
};

View File

@@ -3,16 +3,16 @@
// run `npm run filetypes` to rebuild
export type mapsquare_overlays = {
primary_colour?: number[] | undefined
material?: number | undefined
unknown_0x05?: true | undefined
secondary_colour?: number[] | undefined
unknown_0x08?: true | undefined
unknown_0x09?: number | undefined
unknown_0x0A?: true | undefined
unknown_0x0B?: number | undefined
bleedToUnderlay?: true | undefined
tertiary_colour?: number[] | undefined
unknown_0x0E?: number | undefined
unknown_0x10?: number | undefined
primary_colour?: number[] | null
material?: number | null
unknown_0x05?: true | null
secondary_colour?: number[] | null
unknown_0x08?: true | null
unknown_0x09?: number | null
unknown_0x0A?: true | null
unknown_0x0B?: number | null
bleedToUnderlay?: true | null
tertiary_colour?: number[] | null
unknown_0x0E?: number | null
unknown_0x10?: number | null
};

View File

@@ -4,9 +4,9 @@
export type mapsquare_tiles = {
flags: number,
shape: number | undefined,
overlay: number | undefined,
settings: number | undefined,
underlay: number | undefined,
height: number | undefined,
shape: number | null,
overlay: number | null,
settings: number | null,
underlay: number | null,
height: number | null,
}[];

View File

@@ -3,9 +3,9 @@
// run `npm run filetypes` to rebuild
export type mapsquare_underlays = {
color?: number[] | undefined
material?: number | undefined
unknown_0x03?: number | undefined
unknown_0x04?: true | undefined
unknown_0x05?: true | undefined
color?: number[] | null
material?: number | null
unknown_0x03?: number | null
unknown_0x04?: true | null
unknown_0x05?: true | null
};

View File

@@ -4,9 +4,9 @@
export type mapsquare_watertiles = {
flags: number,
shape: number | undefined,
overlay: number | undefined,
settings: number | undefined,
underlay: number | undefined,
height: number | undefined,
shape: number | null,
overlay: number | null,
settings: number | null,
underlay: number | null,
height: number | null,
}[];

150
generated/npcs.d.ts vendored
View File

@@ -3,119 +3,119 @@
// run `npm run filetypes` to rebuild
export type npcs = {
models?: number[] | undefined
name?: string | undefined
boundSize?: number | undefined
actions_0?: string | undefined
actions_1?: string | undefined
actions_2?: string | undefined
actions_3?: string | undefined
actions_4?: string | undefined
models?: number[] | null
name?: string | null
boundSize?: number | null
actions_0?: string | null
actions_1?: string | null
actions_2?: string | null
actions_3?: string | null
actions_4?: string | null
color_replacements?: [
number,
number,
][] | undefined
][] | null
material_replacements?: [
number,
number,
][] | undefined
recolourPalette?: number[] | undefined
recolor_indices?: number | undefined
retexture_indices?: number | undefined
headModels?: number[] | undefined
drawMapDot?: false | undefined
combat?: number | undefined
scaleXZ?: number | undefined
scaleY?: number | undefined
unknown_63?: true | undefined
ambience?: number | undefined
modelContract?: number | undefined
head_icon_data?: number | undefined
unknown_67?: number | undefined
][] | null
recolourPalette?: number[] | null
recolor_indices?: number | null
retexture_indices?: number | null
headModels?: number[] | null
drawMapDot?: false | null
combat?: number | null
scaleXZ?: number | null
scaleY?: number | null
unknown_63?: true | null
ambience?: number | null
modelContract?: number | null
head_icon_data?: number | null
unknown_67?: number | null
morphs_1?: {
unk1: number,
unk2: number[],
unk3: number,
} | undefined
unknown_6B?: false | undefined
slowWalk?: false | undefined
animateIdle?: false | undefined
} | null
unknown_6B?: false | null
slowWalk?: false | null
animateIdle?: false | null
shadow?: {
SrcColor: number,
DstColor: number,
} | undefined
} | null
shadowAlphaIntensity?: {
Src: number,
Dst: number,
} | undefined
} | null
morphs_2?: {
unk1: number,
unk2: number,
unk3: number[],
unk4: number,
} | undefined
movementCapabilities?: number | undefined
} | null
movementCapabilities?: number | null
translations?: [
number,
number,
number,
number,
][] | undefined
iconHeight?: number | undefined
respawnDirection?: number | undefined
animation_group?: number | undefined
movementType?: number | undefined
][] | null
iconHeight?: number | null
respawnDirection?: number | null
animation_group?: number | null
movementType?: number | null
ambient_sound?: {
unk1: number,
unk2: number,
unk3: number,
unk4: number,
unk45: number,
} | undefined
} | null
oldCursor?: {
Op: number,
Cursor: number,
} | undefined
} | null
oldCursor2?: {
Op: number,
Cursor: number,
} | undefined
attackCursor?: number | undefined
armyIcon?: number | undefined
unknown_8C?: number | undefined
unknown_8D?: true | undefined
mapFunction?: number | undefined
unknown_8F?: true | undefined
members_actions_0?: string | undefined
members_actions_1?: string | undefined
members_actions_2?: string | undefined
members_actions_3?: string | undefined
members_actions_4?: string | undefined
} | null
attackCursor?: number | null
armyIcon?: number | null
unknown_8C?: number | null
unknown_8D?: true | null
mapFunction?: number | null
unknown_8F?: true | null
members_actions_0?: string | null
members_actions_1?: string | null
members_actions_2?: string | null
members_actions_3?: string | null
members_actions_4?: string | null
unknown_9B?: {
unknown_1: number,
unknown_2: number,
unknown_3: number,
unknown_4: number,
} | undefined
aByte3076_set_1?: true | undefined
aByte3076_set_0?: false | undefined
quests?: number[] | undefined
dummy_1?: true | undefined
unknown_A3?: number | undefined
} | null
aByte3076_set_1?: true | null
aByte3076_set_0?: false | null
quests?: number[] | null
dummy_1?: true | null
unknown_A3?: number | null
unknown_A4?: {
unknown_1: number,
unknown_2: number,
} | undefined
unknown_A5?: number | undefined
unknown_A8?: number | undefined
unknown_A9?: false | undefined
action_cursors_0?: number | undefined
action_cursors_1?: number | undefined
action_cursors_2?: number | undefined
action_cursors_3?: number | undefined
action_cursors_4?: number | undefined
action_cursors_5?: number | undefined
dummy_2?: true | undefined
} | null
unknown_A5?: number | null
unknown_A8?: number | null
unknown_A9?: false | null
action_cursors_0?: number | null
action_cursors_1?: number | null
action_cursors_2?: number | null
action_cursors_3?: number | null
action_cursors_4?: number | null
action_cursors_5?: number | null
dummy_2?: true | null
unknown_B3?: {
unknown_1: number,
unknown_2: number,
@@ -123,17 +123,17 @@ export type npcs = {
unknown_4: number,
unknown_5: number,
unknown_6: number,
} | undefined
unknown_B4?: number | undefined
} | null
unknown_B4?: number | null
unknown_B5?: {
unknown_1: number,
unknown_2: number,
} | undefined
unknown_B6?: true | undefined
unknown_B8?: number | undefined
} | null
unknown_B6?: true | null
unknown_B8?: number | null
extra?: {
prop: number,
intvalue: number | undefined,
stringvalue: string | undefined,
}[] | undefined
intvalue: number | null,
stringvalue: string | null,
}[] | null
};

196
generated/objects.d.ts vendored
View File

@@ -6,142 +6,142 @@ export type objects = {
models?: {
type: number,
values: number[],
}[] | undefined
name?: string | undefined
width?: number | undefined
length?: number | undefined
probably_nocollision?: true | undefined
maybe_allows_lineofsight?: true | undefined
deletable?: boolean | undefined
probably_morphFloor?: true | undefined
unknown_16?: true | undefined
occludes_1?: false | undefined
probably_animation?: number | undefined
maybe_blocks_movement?: true | undefined
wallkit_related_1C?: number | undefined
ambient?: number | undefined
actions_0?: string | undefined
actions_1?: string | undefined
actions_2?: string | undefined
actions_3?: string | undefined
actions_4?: string | undefined
contrast?: number | undefined
}[] | null
name?: string | null
width?: number | null
length?: number | null
probably_nocollision?: true | null
maybe_allows_lineofsight?: true | null
deletable?: boolean | null
probably_morphFloor?: true | null
unknown_16?: true | null
occludes_1?: false | null
probably_animation?: number | null
maybe_blocks_movement?: true | null
wallkit_related_1C?: number | null
ambient?: number | null
actions_0?: string | null
actions_1?: string | null
actions_2?: string | null
actions_3?: string | null
actions_4?: string | null
contrast?: number | null
color_replacements?: [
number,
number,
][] | undefined
][] | null
material_replacements?: [
number,
number,
][] | undefined
recolourPalette?: number[] | undefined
unknown_2C?: number | undefined
unknown_2D?: number | undefined
mirror?: true | undefined
unknown_40?: true | undefined
scaleX?: number | undefined
scaleY?: number | undefined
scaleZ?: number | undefined
dummy_45?: number | undefined
translateX?: number | undefined
translateY?: number | undefined
translateZ?: number | undefined
unknown_49?: true | undefined
unknown_4A?: true | undefined
unknown_4B?: number | undefined
][] | null
recolourPalette?: number[] | null
unknown_2C?: number | null
unknown_2D?: number | null
mirror?: true | null
unknown_40?: true | null
scaleX?: number | null
scaleY?: number | null
scaleZ?: number | null
dummy_45?: number | null
translateX?: number | null
translateY?: number | null
translateZ?: number | null
unknown_49?: true | null
unknown_4A?: true | null
unknown_4B?: number | null
morphs_1?: {
unk1: number,
unk2: number[],
unk3: number,
} | undefined
} | null
light_source_related_4E?: {
maybe_color: number,
maybe_radius: number,
} | undefined
} | null
unknown_4F?: {
unknown_1: number,
unknown_2: number,
unknown_3: number,
unknown_4: number[],
} | undefined
unknown_51?: number | undefined
unknown_52?: true | undefined
is_members?: true | undefined
unknown_59?: true | undefined
isMembers?: true | undefined
} | null
unknown_51?: number | null
unknown_52?: true | null
is_members?: true | null
unknown_59?: true | null
isMembers?: true | null
morphs_2?: {
unk1: number,
unk2: number,
unk3: number[],
unk4: number,
} | undefined
} | null
tilt_xz?: [
number,
number,
] | undefined
under_water?: true | undefined
probably_morphCeilingOffset?: number | undefined
ground_decoration_related_61?: true | undefined
has_animated_texture?: true | undefined
] | null
under_water?: true | null
probably_morphCeilingOffset?: number | null
ground_decoration_related_61?: true | null
has_animated_texture?: true | null
dummy_63?: {
unknown_2: number,
unknown_1: number,
} | undefined
} | null
dummy_64?: {
unknown_2: number,
unknown_1: number,
} | undefined
unused_65?: number | undefined
mapscene?: number | undefined
occludes_2?: false | undefined
interactable_related_68?: number | undefined
invertMapScene?: true | undefined
} | null
unused_65?: number | null
mapscene?: number | null
occludes_2?: false | null
interactable_related_68?: number | null
invertMapScene?: true | null
headModels?: {
model: number,
unknown_2: number,
}[] | undefined
mapFunction?: number | undefined
members_action_1?: string | undefined
members_action_2?: string | undefined
members_action_3?: string | undefined
members_action_4?: string | undefined
members_action_5?: string | undefined
unknown_A0?: number[] | undefined
singleuse_A2?: number | undefined
}[] | null
mapFunction?: number | null
members_action_1?: string | null
members_action_2?: string | null
members_action_3?: string | null
members_action_4?: string | null
members_action_5?: string | null
unknown_A0?: number[] | null
singleuse_A2?: number | null
unknown_A3?: {
unknown_1: number,
unknown_2: number,
unknown_3: number,
unknown_4: number,
} | undefined
singleuse_A4?: number | undefined
singleuse_A5?: number | undefined
singleuse_A6?: number | undefined
floor_thickness?: number | undefined
unused_a8?: true | undefined
unused_a9?: true | undefined
wallkit_related_AA?: number | undefined
possibly_wallkit_skew_AB?: number | undefined
} | null
singleuse_A4?: number | null
singleuse_A5?: number | null
singleuse_A6?: number | null
floor_thickness?: number | null
unused_a8?: true | null
unused_a9?: true | null
wallkit_related_AA?: number | null
possibly_wallkit_skew_AB?: number | null
lightsource_related_AD?: {
unknown_1: number,
unknown_2: number,
} | undefined
can_change_color?: true | undefined
unknown_B2?: number | undefined
unknown_BA?: number | undefined
dummy_bc?: true | undefined
treerockordoor_BD?: true | undefined
action_cursors_0?: number | undefined
action_cursors_1?: number | undefined
action_cursors_2?: number | undefined
action_cursors_3?: number | undefined
action_cursors_4?: number | undefined
action_cursors_5?: number | undefined
tileplacement_related_c4?: number | undefined
clan_citadel_C5?: number | undefined
invisible_c6?: true | undefined
flooroverlay_c7?: true | undefined
singleuse_C8?: true | undefined
} | null
can_change_color?: true | null
unknown_B2?: number | null
unknown_BA?: number | null
dummy_bc?: true | null
treerockordoor_BD?: true | null
action_cursors_0?: number | null
action_cursors_1?: number | null
action_cursors_2?: number | null
action_cursors_3?: number | null
action_cursors_4?: number | null
action_cursors_5?: number | null
tileplacement_related_c4?: number | null
clan_citadel_C5?: number | null
invisible_c6?: true | null
flooroverlay_c7?: true | null
singleuse_C8?: true | null
unknown_C9?: {
unknown_1: number,
unknown_2: number,
@@ -149,11 +149,11 @@ export type objects = {
unknown_4: number,
unknown_5: number,
unknown_6: number,
} | undefined
singleuse_CA?: number | undefined
} | null
singleuse_CA?: number | null
extra?: {
prop: number,
intvalue: number | undefined,
stringvalue: string | undefined,
}[] | undefined
intvalue: number | null,
stringvalue: string | null,
}[] | null
};

15
package-lock.json generated
View File

@@ -7,7 +7,7 @@
"": {
"name": "gloopvis",
"version": "0.1.0",
"license": "ISC",
"license": "GPL-4",
"dependencies": {
"autobind-decorator": "^2.4.0",
"bzip2": "^0.1.1",
@@ -28,6 +28,7 @@
},
"devDependencies": {
"@types/crc": "^3.4.0",
"@types/json-schema": "^7.0.9",
"@types/leaflet": "^1.7.6",
"@types/react": "^17.0.33",
"@types/react-dom": "^17.0.10",
@@ -204,6 +205,12 @@
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==",
"dev": true
},
"node_modules/@types/json-schema": {
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true
},
"node_modules/@types/keyv": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz",
@@ -7503,6 +7510,12 @@
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==",
"dev": true
},
"@types/json-schema": {
"version": "7.0.9",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz",
"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==",
"dev": true
},
"@types/keyv": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz",

View File

@@ -17,6 +17,7 @@
"license": "GPL-4",
"devDependencies": {
"@types/crc": "^3.4.0",
"@types/json-schema": "^7.0.9",
"@types/leaflet": "^1.7.6",
"@types/react": "^17.0.33",
"@types/react-dom": "^17.0.10",

View File

@@ -2,7 +2,7 @@ import { Stream, packedHSL2HSL, HSL2RGB, ModelModifications } from "./utils";
import { GLTFBuilder } from "./gltf";
import { CacheFileSource, CacheIndex, CacheIndexFile, SubFile } from "../cache";
import { GlTf, MeshPrimitive, Material } from "./gltftype";
import { cacheConfigPages, cacheMajors } from "../constants";
import { cacheConfigPages, cacheMajors, cacheMapFiles } from "../constants";
import { ParsedTexture } from "./textures";
import { AttributeSoure, buildAttributeBuffer, glTypeIds } from "./gltfutil";
import { parseMapscenes, parseMapsquareLocations, parseMapsquareOverlays, parseMapsquareTiles, parseMapsquareUnderlays, parseMapsquareWaterTiles, parseObject } from "../opdecoder";
@@ -485,7 +485,7 @@ function boxMesh(width: number, length: number, height: number) {
export function modifyMesh(mesh: ModelMeshData, mods: ModelModifications) {
let newmat = mods.replaceMaterials?.find(q => q[0] == mesh.materialId)?.[1];
let newmesh = { ...mesh };
if (typeof newmat != "undefined") {
if (newmat != undefined) {
newmesh.materialId = (newmat == (1 << 16) - 1 ? -1 : newmat);
}
@@ -817,26 +817,26 @@ export class TileGrid {
let height = 0;
for (let level = 0; level < squareLevels; level++) {
let tile = tiles[tileindex];
if (typeof tile.height != "undefined") {
if (tile.height != undefined) {
height += tile.height;
} else {
//TODO this is a guess that sort of fits
height += 30;
}
let visible = false;
let shape = (typeof tile.shape == "undefined" ? defaulttileshape : tileshapes[tile.shape]);
let shape = (tile.shape == undefined ? defaulttileshape : tileshapes[tile.shape]);
let bleedsOverlayMaterial = false;
let underlayprop: TileVertex | undefined = undefined;
let overlayprop: TileVertex | undefined = undefined;
//TODO bound checks
let underlay = (typeof tile.underlay != "undefined" ? this.mapconfig.underlays[tile.underlay - 1] : undefined);
let underlay = (tile.underlay != undefined ? this.mapconfig.underlays[tile.underlay - 1] : undefined);
if (underlay) {
if (underlay.color && (underlay.color[0] != 255 || underlay.color[1] != 0 || underlay.color[2] != 255)) {
visible = true;
}
underlayprop = { material: underlay.material ?? -1, color: underlay.color ?? [255, 0, 255], usesColor: !underlay.unknown_0x04 };
}
let overlay = (typeof tile.overlay != "undefined" ? this.mapconfig.overlays[tile.overlay - 1] : undefined);
let overlay = ( tile.overlay != undefined ? this.mapconfig.overlays[tile.overlay - 1] : undefined);
if (overlay) {
overlayprop = { material: overlay.material ?? -1, color: overlay.primary_colour ?? [255, 0, 255], usesColor: !overlay.unknown_0x0A };
bleedsOverlayMaterial = !!overlay.bleedToUnderlay;
@@ -929,8 +929,8 @@ export async function parseMapsquare(source: CacheFileSource, rect: MapRect, opt
continue;
}
let selfarchive = (await source.getFileArchive(selfindex));
let tileindex = selfindex.subindices.indexOf(3);
let tileindexwater = selfindex.subindices.indexOf(4);
let tileindex = selfindex.subindices.indexOf(cacheMapFiles.squares);
let tileindexwater = selfindex.subindices.indexOf(cacheMapFiles.squaresWater);
if (tileindex == -1) {
console.log(`skipping mapsquare ${rect.x + x} ${rect.z + z} as it has no tiles`);
@@ -1217,7 +1217,7 @@ async function mapsquareOverlays(source: CacheFileSource, grid: TileGrid, locs:
let group = floors[loc.effectiveLevel].mapscenes.get(sceneid);
if (!group) {
let mapscene = grid.mapconfig.mapscenes[sceneid];
if (typeof mapscene.sprite_id == "undefined") { return; }
if (mapscene.sprite_id == undefined) { return; }
let spritefile = await source.getFileById(cacheMajors.sprites, mapscene.sprite_id);
let sprite = parseSprite(spritefile);
let mat = new THREE.MeshBasicMaterial();
@@ -1271,7 +1271,7 @@ async function mapsquareOverlays(source: CacheFileSource, grid: TileGrid, locs:
addwall(wallmodels.diagonal, loc);
}
if (typeof loc.location.mapscene != "undefined") {
if (loc.location.mapscene != undefined) {
await addMapscene(loc, loc.location.mapscene);
}
}
@@ -1295,8 +1295,8 @@ function mapsquareObjectModels(locs: WorldLocation[]) {
let objectmeta = inst.location;
if (!model) {
let modelmods: ModelModifications = {
replaceColors: objectmeta.color_replacements,
replaceMaterials: objectmeta.material_replacements
replaceColors: objectmeta.color_replacements??undefined,
replaceMaterials: objectmeta.material_replacements??undefined
};
const translatefactor = 4;//no clue why but seems right
let translate = new Vector3().set(
@@ -1478,7 +1478,7 @@ export type WorldLocation = {
export async function mapsquareObjects(source: CacheFileSource, chunk: ChunkData, grid: TileGrid, collision = false) {
let locs: WorldLocation[] = [];
let locationindex = chunk.cacheIndex.subindices.indexOf(0);
let locationindex = chunk.cacheIndex.subindices.indexOf(cacheMapFiles.locations);
if (locationindex == -1) { return locs; }
let locations = parseMapsquareLocations.read(chunk.archive[locationindex].buffer).locations;

View File

@@ -1,3 +1,4 @@
import { crc32 } from "crc";
import { cacheMajors } from "./constants";
import { parseCacheIndex } from "./opdecoder";
@@ -79,6 +80,8 @@ export function packBufferArchive(buffers: Buffer[]) {
lastsize = buf.byteLength;
footerindex += 4;
}
result.writeUInt8(1, len - 1);
//TODO write last byte, whats in it?
return result;
}
@@ -86,6 +89,8 @@ export function unpackBufferArchive(buffer: Buffer, length: number) {
var subbufs: SubFile[] = [];
var scan = 0x0;
//whats in our missing byte?
let endbyte = buffer.readUInt8(buffer.length - 1);
if (endbyte != 1) { console.log("unexpected archive end byte", endbyte) }
var suboffsetScan = buffer.length - 0x1 - (0x4 * length);
var lastRecordSize = 0;
@@ -144,6 +149,7 @@ const mappedFileIds = {
[cacheMajors.npcs]: 256,//not sure
[cacheMajors.enums]: 256,
[cacheMajors.objects]: 256,
[cacheMajors.achievements]: 128,
[cacheMajors.materials]: Infinity
}

View File

@@ -1,29 +1,33 @@
import * as cache from "./cache";
import { decompress, decompressSqlite } from "./decompress";
import { compressSqlite, decompress, decompressSqlite } from "./decompress";
import * as path from "path";
//only type info, import the actual thing at runtime so it can be avoided if not used
import type * as sqlite3 from "sqlite3";
import { crc32 } from "crc";
type CacheTable = {
db: sqlite3.Database,
ready: Promise<void>,
indices: Promise<cache.CacheIndexFile>,
dbget: (q: string, params: any[]) => Promise<any>
dbget: (q: string, params: any[]) => Promise<any>,
dbrun: (q: string, params: any[]) => Promise<any>
}
export class GameCacheLoader extends cache.CacheFileSource {
cachedir: string;
writable: boolean;
opentables = new Map<number, CacheTable>();
constructor(cachedir: string) {
constructor(cachedir: string, writable?: boolean) {
super();
this.cachedir = cachedir;
this.writable = !!writable;
}
openTable(major: number) {
let sqlite = require("sqlite3") as typeof import("sqlite3");
if (!this.opentables.get(major)) {
let db = new sqlite.Database(path.resolve(this.cachedir, `js5-${major}.jcache`), sqlite.OPEN_READONLY);
let db = new sqlite.Database(path.resolve(this.cachedir, `js5-${major}.jcache`), this.writable ? sqlite.OPEN_READWRITE : sqlite.OPEN_READONLY);
let ready = new Promise<void>(done => db.once("open", done));
let dbget = async (query: string, args: any[]) => {
await ready;
@@ -34,11 +38,20 @@ export class GameCacheLoader extends cache.CacheFileSource {
})
})
}
let dbrun = async (query: string, args: any[]) => {
await ready;
return new Promise<any>((resolve, reject) => {
db.run(query, args, (err, res) => {
if (err) { reject(err); }
else { resolve(res); }
})
})
}
let indices = dbget(`SELECT DATA FROM cache_index`, []).then(row => {
return cache.indexBufferToObject(major, decompressSqlite(Buffer.from(row.DATA.buffer, row.DATA.byteOffset, row.DATA.byteLength)));
});
this.opentables.set(major, { db, ready, dbget, indices });
this.opentables.set(major, { db, ready, dbget, dbrun, indices });
}
return this.opentables.get(major)!;
}
@@ -48,13 +61,28 @@ export class GameCacheLoader extends cache.CacheFileSource {
let row = await dbget(`SELECT DATA,CRC FROM cache WHERE KEY=?`, [minor]);
if (typeof crc == "number" && row.CRC != crc) {
//TODO this is always off by either 1 or 2
//console.log(`crc from cache (${row.CRC}) did not match requested crc (${crc}) for ${major}.${minor}`);
// console.log(`crc from cache (${row.CRC}) did not match requested crc (${crc}) for ${major}.${minor}`);
}
return decompressSqlite(Buffer.from(row.DATA.buffer, row.DATA.byteOffset, row.DATA.byteLength));
let file = Buffer.from(row.DATA.buffer, row.DATA.byteOffset, row.DATA.byteLength);
let res = decompressSqlite(file);
return res;
}
async getFileArchive(index: cache.CacheIndex) {
return cache.unpackSqliteBufferArchive(await this.getFile(index.major, index.minor, index.crc), index.subindexcount);
let arch = await this.getFile(index.major, index.minor, index.crc);
let res = cache.unpackSqliteBufferArchive(arch, index.subindexcount);
return res;
}
writeFile(major: number, minor: number, file: Buffer) {
let { dbrun } = this.openTable(major);
let compressed = compressSqlite(file, "zlib");
return dbrun("UPDATE `cache` SET `DATA`=? WHERE `KEY`=?", [compressed, minor]);
}
writeFileArchive(index: cache.CacheIndex, files: Buffer[]) {
let arch = cache.packSqliteBufferArchive(files);
return this.writeFile(index.major, index.minor, arch);
}
async getIndexFile(major: number) {

View File

@@ -24,11 +24,11 @@ export function setLoadingIndicator(ind: typeof loadingIndicator) {
loadingIndicator = ind;
}
const ReadCacheSource: Type<string, () => Promise<CacheFileSource>> = {
const ReadCacheSource: Type<string, (opts?: { writable?: boolean }) => Promise<CacheFileSource>> = {
async from(str) {
let [mode, ...argparts] = str.split(":",);
let arg = argparts.join(":");
return async () => {
return async (opts) => {
switch (mode) {
case "live":
return new Downloader();
@@ -39,7 +39,7 @@ const ReadCacheSource: Type<string, () => Promise<CacheFileSource>> = {
await loadingIndicator.done();
return updater.fileSource;
case "cache":
return new GameCacheLoader(arg || path.resolve(process.env.ProgramData!, "jagex/runescape"));
return new GameCacheLoader(arg || path.resolve(process.env.ProgramData!, "jagex/runescape"), !!opts?.writable);
default:
throw new Error("unknown mode");
}

View File

@@ -20,6 +20,12 @@ export const cacheMajors = {
index: 255
}
export const cacheMapFiles={
locations:0,
squares:3,
squaresWater:4
}
export const cacheConfigPages = {
mapunderlays: 1,
mapoverlays: 4,

View File

@@ -92,7 +92,7 @@ var _lzma = function (input: Buffer) {
// var lzma = require("lzma");
// return Buffer.from(lzma.decompress(processed));
var lzma = require("lzma-native").LZMA();
return lzma.decompress(processed);
return lzma.decompress(processed) as Buffer;
}

View File

@@ -26,7 +26,7 @@ export async function svgfloor(source: CacheFileSource, grid: TileGrid, locs: Wo
let transparent = 0xff00ff;
let coltoint = (col: number[] | undefined) => {
let coltoint = (col: number[] | undefined | null) => {
if (!col) { return transparent; }
return col[0] << 16 | col[1] << 8 | col[2];
}
@@ -162,7 +162,7 @@ export async function svgfloor(source: CacheFileSource, grid: TileGrid, locs: Wo
if (!tile || tile?.effectiveLevel != loc.effectiveLevel) { break; }
if (tile.effectiveLevel == maplevel && (getOverlayColor(tile) != transparent || tile.visible)) { occluded = true; }
}
if (typeof loc.location.mapscene == "undefined") {
if (loc.location.mapscene == undefined) {
if (drawwalls && !occluded) {
if (loc.type == 0) {
addline(linegroup, loc.x - rect.x, loc.z - rect.z, 3, 0, loc.rotation);
@@ -181,7 +181,7 @@ export async function svgfloor(source: CacheFileSource, grid: TileGrid, locs: Wo
let src = "";
let width = 0;
let height = 0;
if (typeof mapscene.sprite_id != "undefined") {
if (mapscene.sprite_id != undefined) {
let spritefile = await source.getFileById(cacheMajors.sprites, mapscene.sprite_id);
let sprite = parseSprite(spritefile);
let pngfile = await sharp(sprite[0].data, { raw: { width: sprite[0].width, height: sprite[0].height, channels: 4 } }).png().toBuffer();

View File

@@ -1,4 +1,6 @@
import type * as jsonschema from "json-schema";
type PrimitiveInt = {
primitive: "int",
unsigned: boolean,
@@ -48,7 +50,9 @@ type ParserContext = Record<string, number>;
export type ChunkParser<T> = {
read(buf: ScanBuffer, ctx: ParserContext): T,
write(buf: ScanBuffer, v: unknown): void,
bubbleConditionValue?(statevalue: T, prop: string, currentvalue: number, isBubbling: boolean): number,
getTypescriptType(indent: string): string,
getJsonSChema(): jsonschema.JSONSchema6Definition,
condName?: string,
condValue?: number,
condMode?: CompareMode
@@ -211,6 +215,7 @@ function opcodesParser<T extends Record<string, any>>(opcodetype: ChunkParser<nu
write(buffer, value) {
if (typeof value != "object") { throw new Error("oject expected") }
for (let key in value) {
if (key.startsWith("$")) { continue; }
let parser = opts[key];
if (!parser) { throw new Error("unknown property " + key); }
opcodetype.write(buffer, parser.condValue);
@@ -226,13 +231,21 @@ function opcodesParser<T extends Record<string, any>>(opcodetype: ChunkParser<nu
}
r += indent + "}";
return r;
},
getJsonSChema() {
return {
type: "object",
properties: Object.fromEntries([...map.values()].map((prop) => {
return [prop.key, prop.parser.getJsonSChema()];
}))
}
}
}
}
function structParser<TUPPLE extends boolean, T extends Record<TUPPLE extends true ? number : string, any>>(props: { [key in keyof T]: ChunkParser<T[key]> }, isTuple: TUPPLE): ChunkParser<T> {
let keys: (keyof T)[] = Object.keys(props) as any;
return {
let keys = Object.keys(props);
let r: ChunkParser<T> = {
read(buffer, parentctx) {
let r = (isTuple ? [] : {}) as T;
let ctx: ParserContext = Object.create(parentctx);
@@ -249,16 +262,26 @@ function structParser<TUPPLE extends boolean, T extends Record<TUPPLE extends tr
},
write(buffer, value) {
if (typeof value != "object" || !value) { throw new Error("object expected"); }
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (key[0] == "$") { continue; }
if (!(key in value)) { throw new Error(`struct has no property ${key}`); }
let propvalue = value[key as string];
for (let key of keys) {
let propvalue: any;
if (key[0] == "$") {
propvalue = r.bubbleConditionValue!(value as any, key, 0, false);
} else {
// if (!(key in value)) { throw new Error(`struct has no property ${key}`); }
propvalue = value[key];
}
let prop = props[key];
//TODO calculate dependent values
prop.write(buffer, propvalue);
}
},
bubbleConditionValue(state, prop, val, isBubbling) {
//prop is shadowed
if (isBubbling && keys.indexOf(prop as any) != -1) { return val; }
for (let key of keys) {
val = props[key].bubbleConditionValue?.(state[key], prop, val, true) ?? val;
}
return val;
},
getTypescriptType(indent) {
let r = (isTuple ? "[" : "{") + "\n";
let newindent = indent + "\t";
@@ -268,25 +291,49 @@ function structParser<TUPPLE extends boolean, T extends Record<TUPPLE extends tr
}
r += indent + (isTuple ? "]" : "}");
return r;
},
getJsonSChema() {
return {
type: "object",
properties: Object.fromEntries([...Object.entries(props)].map(([key, prop]) => {
return [key, (prop as ChunkParser<any>).getJsonSChema()];
})),
required: keys
}
}
}
return r;
}
function optParser<T>(type: ChunkParser<T>, condvar: string, condvalue: number, compare: CompareMode): ChunkParser<T | undefined> {
let r: ChunkParser<T | undefined> = {
function optParser<T>(type: ChunkParser<T>, condvar: string, condvalue: number, compare: CompareMode): ChunkParser<T | null> {
let r: ChunkParser<T | null> = {
read(buffer, ctx) {
if (!checkCondition(r, ctx[condvar])) {
return undefined;
return null;
}
return type.read(buffer, ctx);
},
write(buffer, value) {
if (typeof value != "undefined") {
if ( value != null) {
return type.write(buffer, value);
}
},
bubbleConditionValue(state, prop, val) {
if (prop == condvar) {
val = forceCondition(this, val, state != null);
}
return val;
},
getTypescriptType(indent) {
return type.getTypescriptType(indent) + " | undefined";
return type.getTypescriptType(indent) + " | null";
},
getJsonSChema() {
return {
oneOf: [
type.getJsonSChema(),
{ type: "null" }
]
}
},
condName: condvar,
condValue: condvalue,
@@ -295,6 +342,19 @@ function optParser<T>(type: ChunkParser<T>, condvar: string, condvalue: number,
return r;
}
function forceCondition(parser: ChunkParser<any>, oldvalue: number, state: boolean) {
switch (parser.condMode!) {
case "eq":
return state ? parser.condValue! : oldvalue;
case "bitflag":
return (state ? oldvalue | (1 << parser.condValue!) : oldvalue & ~(1 << parser.condValue!));
case "bitflagnot":
return (state ? oldvalue & ~(1 << parser.condValue!) : oldvalue | (1 << parser.condValue!));
default:
throw new Error("unkown condition " + parser.condMode);
}
}
function checkCondition(parser: ChunkParser<any>, v: number) {
switch (parser.condMode!) {
case "eq":
@@ -315,7 +375,7 @@ function chunkedArrayParser<T>(lengthtype: ChunkParser<number>, chunktypes: Chun
let r: T[] = [];
let ctxs: any[] = [];
for (let chunkindex = 0; chunkindex < chunktypes.length; chunkindex++) {
let proptype = chunktypes[chunkindex]
let proptype = chunktypes[chunkindex];
for (let i = 0; i < len; i++) {
let ctx: any;
let obj: T;
@@ -338,10 +398,23 @@ function chunkedArrayParser<T>(lengthtype: ChunkParser<number>, chunktypes: Chun
write(buf, v) {
throw new Error("not implemented");
},
bubbleConditionValue(state, prop, val) {
if (state.length != 0) {
for (let chunk of chunktypes) {
val = chunk.bubbleConditionValue?.(state[0], prop, val, true) ?? val;
}
}
return val;
},
getTypescriptType(indent: string) {
let joined = chunktypes.map(c => c.getTypescriptType(indent)).join(" & ");
if (chunktypes.length == 1) { return `${joined}[]`; }
else { return `(${joined})[]`; }
},
getJsonSChema() {
return {
allOf: chunktypes.flatMap(chunk => chunk.getJsonSChema())
}
}
}
}
@@ -363,8 +436,20 @@ function arrayParser<T>(lengthtype: ChunkParser<number>, subtype: ChunkParser<T>
subtype.write(buffer, value[i]);
}
},
bubbleConditionValue(state, prop, val) {
if (state.length != 0) {
val = subtype.bubbleConditionValue?.(state[0], prop, val, true) ?? val;
}
return val;
},
getTypescriptType(indent) {
return `${subtype.getTypescriptType(indent)}[]`;
},
getJsonSChema() {
return {
type: "array",
items: subtype.getJsonSChema()
}
}
};
}
@@ -387,7 +472,6 @@ function arrayNullTerminatedParser<T>(lengthtype: ChunkParser<number>, proptype:
return r;
},
write(buffer, value) {
//throw new Error("not implemented");
if (!Array.isArray(value)) { throw new Error("array expected"); }
for (let prop of value) {
const lengthvalue = 1;//TODO get this from"prop"
@@ -396,8 +480,20 @@ function arrayNullTerminatedParser<T>(lengthtype: ChunkParser<number>, proptype:
}
lengthtype.write(buffer, 0);
},
bubbleConditionValue(state, prop, val) {
if (state.length != 0) {
val = proptype.bubbleConditionValue?.(state[0], prop, val, true) ?? val;
}
return val;
},
getTypescriptType(indent) {
return `${proptype.getTypescriptType(indent)}[]`;
},
getJsonSChema() {
return {
type: "array",
items: proptype.getJsonSChema()
};
}
};
}
@@ -459,8 +555,8 @@ function intParser(primitive: PrimitiveInt): ChunkParser<number> {
let mask = ~(~0 << (bytes * 8 - 1));
let int = (value & mask) | ((fitshalf ? 0 : 1) << (bytes * 8 - 1));
//always write as signed since bitwise operations in js cast to int32
buffer[`writeIntBE`](int, buffer.scan, bytes);
//write 32bit ints as unsigned since js bitwise operations cast to int32
buffer[`write${unsigned && bytes != 4 ? "U" : ""}IntBE`](int, buffer.scan, bytes);
buffer.scan += bytes;
} else if (readmode == "sumtail") {
throw new Error("not implemented");
@@ -471,6 +567,13 @@ function intParser(primitive: PrimitiveInt): ChunkParser<number> {
},
getTypescriptType() {
return "number";
},
getJsonSChema() {
return {
type: "integer",
maximum: 2 ** (primitive.bytes * 8 + (primitive.unsigned ? 0 : -1)) - 1,
minimum: (primitive.unsigned ? 0 : 2 ** (primitive.bytes * 8 - 1))
}
}
}
return parser;
@@ -492,7 +595,11 @@ function literalValueParser<T>(primitive: PrimitiveValue<T>): ChunkParser<T> {
} else {
return typeof primitive.value;
}
},
getJsonSChema() {
return {
type: typeof primitive.value as any
}
}
}
}
@@ -506,11 +613,21 @@ function referenceValueParser(propname: string, minbit: number, bitlength: numbe
return v;
},
write(buffer, value) {
//need to make the struct writer grab its value from here for invisible props
throw new Error("write for ref not implemented");
//nop, value is written elsewhere
},
bubbleConditionValue(state, prop, val) {
if (propname == prop) { val |= state << minbit; }
return val;
},
getTypescriptType() {
return "number";
},
getJsonSChema() {
return {
type: "integer",
minimum: 0,
maximum: 2 ** (bitlength * 8) - 1
}
}
}
}
@@ -546,6 +663,9 @@ function intAccumlatorParser(refname: string, value: ChunkParser<number | undefi
},
getTypescriptType() {
return "number";
},
getJsonSChema() {
return { type: "integer" };
}
}
}
@@ -576,7 +696,7 @@ function stringParser(primitive: PrimitiveString): ChunkParser<string> {
validateStringType(primitive);
let encoding = primitive.encoding;
let termination = primitive.termination;
let strbuf = Buffer.from(value, encoding);
let strbuf = Buffer.from([...primitive.prebytes, ...Buffer.from(value, encoding)]);
//either pad with 0's to fixed length and truncate and longer strings, or add a single 0 at the end
let strbinbuf = Buffer.alloc(termination == null ? strbuf.byteLength + 1 : termination, 0);
strbuf.copy(strbinbuf, 0, 0, Math.max(strbuf.byteLength, strbinbuf.byteLength));
@@ -585,6 +705,9 @@ function stringParser(primitive: PrimitiveString): ChunkParser<string> {
},
getTypescriptType() {
return "string";
},
getJsonSChema() {
return { type: "string" };
}
}
}
@@ -601,6 +724,9 @@ function booleanParser(): ChunkParser<boolean> {
},
getTypescriptType() {
return "boolean";
},
getJsonSChema() {
return { type: "boolean" };
}
}
}

View File

@@ -50,7 +50,7 @@
"extrasmap": [ "array",["struct",
["$type","unsigned byte"],
["prop","unsigned tribyte"],
["intvalue",["opt",["$type",0],"unsigned int"]],
["intvalue",["opt",["$type",0],"int"]],
["stringvalue",["opt",["$type",1],"string"]]
]]
}

View File

@@ -2,12 +2,15 @@ import { filesource, cliArguments } from "../cliparser";
import { run, command, number, option, string, boolean, Type, flag, oneOf } from "cmd-ts";
import * as fs from "fs";
import * as path from "path";
import { cacheConfigPages, cacheMajors } from "../constants";
import { parseAchievement, parseItem, parseObject, parseNpc, parseMapsquareTiles, FileParser, parseMapsquareUnderlays, parseMapsquareOverlays, parseMapZones, parseEnums, parseMapscenes } from "../opdecoder";
import { achiveToFileId, CacheFileSource } from "../cache";
import { cacheConfigPages, cacheMajors, cacheMapFiles } from "../constants";
import { parseAchievement, parseItem, parseObject, parseNpc, parseMapsquareTiles, FileParser, parseMapsquareUnderlays, parseMapsquareOverlays, parseMapZones, parseEnums, parseMapscenes, parseMapsquareLocations } from "../opdecoder";
import { achiveToFileId, CacheFileSource, CacheIndex, CacheIndexStub, fileIdToArchiveminor, SubFile } from "../cache";
import { parseSprite } from "../3d/sprite";
import sharp from "sharp";
import { FlatImageData } from "../3d/utils";
import { crc32 } from "crc";
import * as cache from "../cache";
import { GameCacheLoader } from "../cacheloader";
type KnownType = {
index: number,
@@ -18,12 +21,174 @@ type KnownType = {
img?: (b: Buffer, source: CacheFileSource) => Promise<FlatImageData[]>
}
type CacheFileId = {
index: CacheIndex,
subfile: number
}
type LogicalIndex = number[];
async function filerange(source: CacheFileSource, startindex: FileId, endindex: FileId) {
if (startindex.major != endindex.major) { throw new Error("range must span one major"); }
let indexfile = await source.getIndexFile(startindex.major);
let files: CacheFileId[] = [];
for (let index of indexfile) {
if (!index) { continue; }
if (index.minor >= startindex.minor && index.minor <= endindex.minor) {
for (let fileindex = 0; fileindex < index.subindices.length; fileindex++) {
let subfileid = index.subindices[fileindex];
if (index.minor == startindex.minor && subfileid < startindex.subindex) { continue; }
if (index.minor == endindex.minor && subfileid > endindex.subindex) { continue; }
files.push({ index, subfile: fileindex });
}
}
}
return files;
}
function worldmapIndex(subfile: number): DecodeLookup {
const major = cacheMajors.mapsquares;
const worldStride = 128;
return {
major,
async logicalRangeToFiles(source, start, end) {
let indexfile = await source.getIndexFile(major);
let files: CacheFileId[] = [];
for (let index of indexfile) {
if (!index) { continue; }
let x = index.minor % worldStride;
let z = Math.floor(index.minor / worldStride);
if (x >= start[0] && x <= end[0] && z >= start[1] && z <= end[1]) {
for (let fileindex = 0; fileindex < index.subindices.length; fileindex++) {
let subfileid = index.subindices[fileindex];
if (subfileid == subfile) {
files.push({ index, subfile: fileindex });
}
}
}
}
return files;
},
fileToLogical(major, minor, subfile) {
return [minor % worldStride, Math.floor(minor / worldStride)];
},
logicalToFile(id: LogicalIndex) {
return { major, minor: id[0] + id[1] * worldStride, subindex: subfile };
}
}
}
function singleMinorIndex(major: number, minor: number): DecodeLookup {
return {
major,
async logicalRangeToFiles(source, start, end) {
return filerange(source, { major, minor, subindex: start[0] }, { major, minor, subindex: end[0] });
},
fileToLogical(major, minor, subfile) {
return [subfile];
},
logicalToFile(id: LogicalIndex) {
return { major, minor, subindex: id[0] };
}
}
}
function chunkedIndex(major: number): DecodeLookup {
return {
major,
async logicalRangeToFiles(source, start, end) {
let startindex = fileIdToArchiveminor(major, start[0]);
let endindex = fileIdToArchiveminor(major, end[0]);
return filerange(source, startindex, endindex);
},
fileToLogical(major, minor, subfile) {
return [achiveToFileId(major, minor, subfile)];
},
logicalToFile(id: LogicalIndex) {
return fileIdToArchiveminor(major, id[0]);
}
};
}
function standardFile(parser: FileParser<any>, lookup: DecodeLookup): DecodeModeFactory {
let constr: DecodeModeFactory = (outdir) => {
let name = Object.entries(modes).find(q => q[1] == constr);
if (!name) { throw new Error(); }
let schema = parser.parser.getJsonSChema();
let relurl = `./.schema-${name[0]}.json`;
fs.writeFileSync(path.resolve(outdir, relurl), JSON.stringify(schema, undefined, "\t"));
return {
...lookup,
ext: "json",
read(b) {
let obj = parser.read(b);
obj.$schema = relurl;
return JSON.stringify(obj, undefined, "\t");
},
write(b) {
return parser.write(JSON.parse(b.toString("utf8")));
}
}
}
return constr;
}
type DecodeModeFactory = (outdir: string) => DecodeMode;
type FileId = { major: number, minor: number, subindex: number };
type DecodeLookup = {
major: number | undefined,
logicalRangeToFiles(source: CacheFileSource, start: LogicalIndex, end: LogicalIndex): Promise<CacheFileId[]>,
fileToLogical(major: number, minor: number, subfile: number): LogicalIndex,
logicalToFile(id: LogicalIndex): FileId,
}
type DecodeMode = {
ext: string,
read(buf: Buffer): (Buffer | string),
write(files: Buffer): Buffer
} & DecodeLookup;
const decodeBinary: DecodeModeFactory = () => {
return {
ext: "bin",
major: undefined,
fileToLogical(major, minor, subfile) { return [major, minor, subfile]; },
logicalToFile(id) { return { major: id[0], minor: id[1], subindex: id[2] }; },
async logicalRangeToFiles(source, start, end) {
if (start[0] != end[0]) { throw new Error("can only do one major at a time"); }
let major = start[0];
return filerange(source, { major, minor: start[1], subindex: start[2] }, { major, minor: start[1], subindex: start[2] });
},
read(b) { return b; },
write(b) { return b; }
}
}
const modes: Record<string, DecodeModeFactory> = {
bin: decodeBinary,
items: standardFile(parseItem, chunkedIndex(cacheMajors.items)),
npcs: standardFile(parseNpc, chunkedIndex(cacheMajors.npcs)),
objects: standardFile(parseObject, chunkedIndex(cacheMajors.objects)),
achievements: standardFile(parseAchievement, chunkedIndex(cacheMajors.achievements)),
overlays: standardFile(parseMapsquareOverlays, singleMinorIndex(cacheMajors.config, cacheConfigPages.mapoverlays)),
underlays: standardFile(parseMapsquareUnderlays, singleMinorIndex(cacheMajors.config, cacheConfigPages.mapunderlays)),
mapscenes: standardFile(parseMapscenes, singleMinorIndex(cacheMajors.config, cacheConfigPages.mapscenes)),
maptiles: standardFile(parseMapsquareTiles, worldmapIndex(cacheMapFiles.squares)),
maplocations: standardFile(parseMapsquareLocations, worldmapIndex(cacheMapFiles.locations)),
}
const decoders: Record<string, KnownType> = {
items: { index: cacheMajors.items, parser: parseItem },
npcs: { index: cacheMajors.npcs, parser: parseNpc },
objects: { index: cacheMajors.objects, parser: parseObject },
achievements: { index: cacheMajors.achievements, parser: parseAchievement },
sprites: { index: cacheMajors.sprites, img: (b) => Promise.resolve(parseSprite(b)) },
sprites: { index: cacheMajors.sprites, img: async (b) => parseSprite(b) },
overlays: { index: cacheMajors.config, minor: cacheConfigPages.mapoverlays, parser: parseMapsquareOverlays },
underlays: { index: cacheMajors.config, minor: cacheConfigPages.mapunderlays, parser: parseMapsquareOverlays },
@@ -32,10 +197,97 @@ const decoders: Record<string, KnownType> = {
mapzones: { index: cacheMajors.worldmap, minor: 0, parser: parseMapZones },
enums: { index: cacheMajors.enums, minor: 0, parser: parseEnums },
maptiles: { index: cacheMajors.mapsquares, subfile: 3, parser: parseMapsquareTiles },
}
let cmd2 = command({
name: "run",
args: {
...filesource,
save: option({ long: "save", short: "s", type: string, defaultValue: () => "extract" }),
mode: option({ long: "mode", short: "m", type: string }),
files: option({ long: "ids", short: "i", type: string }),
edit: flag({ long: "edit", short: "e" })
},
handler: async (args) => {
let modeconstr = modes[args.mode];
if (!modeconstr) { throw new Error("unknown mode"); }
let outdir = path.resolve(args.save);
let mode = modeconstr(outdir);
fs.mkdirSync(outdir, { recursive: true });
let parts = args.files.split(",");
let ranges = parts.map(q => {
let ends = q.split("-");
let start = ends[0].split(".");
let end = (ends[1] ?? ends[0]).split(".");
return {
start: [+start[0], +(start[1] ?? 0)] as [number, number],
end: [+end[0], +(end[1] ?? Infinity)] as [number, number]
}
});
let source = await args.source({ writable: args.edit });
let allfiles = (await Promise.all(ranges.map(q => mode.logicalRangeToFiles(source, q.start, q.end))))
.flat()
.sort((a, b) => a.index.major != b.index.major ? a.index.major - b.index.major : a.index.minor != b.index.minor ? a.index.minor - b.index.minor : a.subfile - b.subfile);
let lastarchive: null | { index: CacheIndex, subfiles: SubFile[] } = null;
for (let fileid of allfiles) {
let arch: SubFile[];
if (lastarchive && lastarchive.index == fileid.index) {
arch = lastarchive.subfiles;
} else {
arch = await source.getFileArchive(fileid.index);
lastarchive = { index: fileid.index, subfiles: arch };
// let modenet = cache.packBufferArchive(arch.map(q => q.buffer));
// console.log("modenet", modenet.byteLength, crc32(modenet));
// console.log("actual", fileid.index.uncompressed_size, fileid.index.uncompressed_crc);
}
let file = arch[fileid.subfile].buffer;
let res = mode.read(file);
let logicalid = mode.fileToLogical(fileid.index.major, fileid.index.minor, fileid.subfile);
let filename = path.resolve(outdir, `${args.mode}-${logicalid.join("_")}.${mode.ext}`);
fs.writeFileSync(filename, res);
}
if (args.edit) {
await new Promise<any>(d => process.stdin.once('data', d));
let archedited = () => {
if (!(source instanceof GameCacheLoader)) { throw new Error("can only do this on file source of type gamecacheloader"); }
if (lastarchive) {
console.log("writing archive", lastarchive.index.major, lastarchive.index.minor, "files", lastarchive.subfiles.length);
return source.writeFileArchive(lastarchive.index, lastarchive.subfiles.map(q => q.buffer));
}
}
for (let fileid of allfiles) {
let arch: SubFile[];
if (lastarchive && lastarchive.index == fileid.index) {
arch = lastarchive.subfiles;
} else {
await archedited();
arch = await source.getFileArchive(fileid.index);
lastarchive = { index: fileid.index, subfiles: arch };
}
let logicalid = mode.fileToLogical(fileid.index.major, fileid.index.minor, fileid.subfile);
let filename = path.resolve(outdir, `${args.mode}-${logicalid.join("_")}.${mode.ext}`);
let newfile = fs.readFileSync(filename);
arch[fileid.subfile].buffer = mode.write(newfile);
}
await archedited();
}
source.close();
console.log("done");
}
})
let cmd = command({
name: "download",
args: {
@@ -135,4 +387,4 @@ let cmd = command({
}
});
run(cmd, cliArguments());
run(cmd2, cliArguments());