Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
H
hl2saveedit
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Alice
hl2saveedit
Commits
cad6ff5d
Commit
cad6ff5d
authored
May 11, 2018
by
Alice
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
the most beautiful thing in this is making a wild guess and having to commit it quickly (unk3)
parent
bd7bc8ae
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
105 additions
and
3 deletions
+105
-3
.gitignore
.gitignore
+2
-0
main.rs
src/main.rs
+103
-3
No files found.
.gitignore
View file @
cad6ff5d
/target/
**/*.rs.bk
test/
.vscode/
src/main.rs
View file @
cad6ff5d
/*
~~~ first of all i love half-life. thank u valv
Saves are .sav containers, with a "JSAV" magic header. (JsavFile)
They contain some metadata about the save, and multiple named files.
Those files are named <mapname>.hl1/hl2/hl3, look very different,
but .hl1/hl2 start with the same VALV magic header. The hl3 one is very small.
I'm guessing on load they are extracted in the same folder, and on save they
are written as files and then added to the main .sav.
It looks like there is a set of those for the current and previous map,
probably because more would make the save file too heavy and the game's
linearity doesn't require to be able to get far back. (so enemies should
respawn in the map before the last, i'm pretty sure that's right)
References:
- Valve wiki on save files. not much actual format info, but interesting anyway
https://developer.valvesoftware.com/wiki/Save_Game_Files
https://developer.valvesoftware.com/wiki/Data_Descriptions
- a fork of Xash3D, which is compatible with GoldSrc saves.
modern Source saves have a very similar structure.
https://github.com/FWGS/xash3d-fwgs/blob/master/engine/server/sv_save.c
*/
#[macro_use]
extern
crate
quick_error
;
#[macro_use]
extern
crate
log
;
extern
crate
clap
;
...
...
@@ -125,8 +152,8 @@ impl JsavFile {
let
savename
=
get_string
(
&
mut
cursor
,
80
)
?
;
trace!
(
"read savename: {:?}"
,
savename
);
let
unk3
=
get_bytes
(
&
mut
cursor
,
2168
)
?
;
trace!
(
"read unk3 (
{} bytes)"
,
unk3
.len
());
let
unk3
=
get_bytes
(
&
mut
cursor
,
size
-
124
)
?
;
trace!
(
"read unk3 (
offset=0x{:06x} size={} bytes)"
,
cursor
.position
()
as
usize
-
unk3
.len
()
,
unk3
.len
());
let
mut
entries
=
Vec
::
new
();
loop
{
...
...
@@ -136,8 +163,8 @@ impl JsavFile {
}
let
valv_pre
=
get_string
(
&
mut
cursor
,
260
)
?
;
let
valv_size
=
get_u32
(
&
mut
cursor
)
?
as
usize
;
trace!
(
"read block (offset=0x{:06x} size={} bytes): {}"
,
offset
,
valv_size
,
valv_pre
);
let
valv_content
=
get_bytes
(
&
mut
cursor
,
valv_size
)
?
;
trace!
(
"read VALV file (offset=0x{:06x} size={} bytes): {}"
,
offset
,
valv_size
,
valv_pre
);
entries
.push
(
JsavValvEntry
{
name
:
valv_pre
,
data
:
valv_content
,
...
...
@@ -154,6 +181,63 @@ impl JsavFile {
}
}
#[derive(Debug,
Clone)]
struct
ValvFile
{
version
:
u32
,
}
impl
ValvFile
{
pub
fn
load
(
data
:
&
[
u8
])
->
Result
<
Self
,
DecodeError
>
{
let
header
=
&
data
[
0
..
4
];
if
header
!=
b
"VALV"
{
return
Err
(
DecodeError
::
InvalidFileType
(
header
.to_vec
(),
b
"VALV"
.to_vec
()));
}
let
mut
cursor
=
Cursor
::
new
(
&
data
[
4
..
]);
let
version
=
get_u32
(
&
mut
cursor
)
?
;
trace!
(
"VALV: Version 0x{:04x} (known: 0x0073)"
,
version
);
let
token_size
=
get_u32
(
&
mut
cursor
)
?
as
usize
;
let
table_count
=
get_u32
(
&
mut
cursor
)
?
;
let
token_count
=
get_u32
(
&
mut
cursor
)
?
;
let
size
=
get_u32
(
&
mut
cursor
)
?
;
trace!
(
"VALV: size={}, table_count={}, token_count={}, token_size={}"
,
size
,
table_count
,
token_count
,
token_size
);
let
token_buffer
=
get_bytes
(
&
mut
cursor
,
token_size
)
?
;
let
tokens
:
Vec
<
String
>
=
token_buffer
.iter
()
.group_by
(|
&&
c
|
c
==
0
)
.into_iter
()
.filter
(|(
key
,
_
)|
*
key
==
false
)
.map
(|(
_
,
group
)|
{
let
bytes
:
Vec
<
u8
>
=
group
.cloned
()
.collect
();
String
::
from_utf8_lossy
(
&
bytes
)
.into_owned
()
})
.collect
();
trace!
(
"found {} tokens"
,
tokens
.len
());
for
t
in
tokens
.iter
()
{
trace!
(
"- {}"
,
t
);
}
trace!
(
"position: {}"
,
cursor
.position
());
loop
{
let
offset
=
cursor
.position
()
as
usize
;
if
offset
>=
cursor
.get_ref
()
.len
()
{
break
;
}
let
block_size
=
get_u32
(
&
mut
cursor
)
?
as
usize
;
trace!
(
"read block (offset=0x{:06x} size={} bytes)"
,
offset
,
block_size
);
let
_block_content
=
get_bytes
(
&
mut
cursor
,
block_size
)
?
;
/*entries.push(JsavValvEntry {
name: valv_pre,
data: valv_content,
})*/
}
Ok
(
ValvFile
{
version
:
version
,
})
}
}
fn
main
()
{
pretty_env_logger
::
init
();
...
...
@@ -167,6 +251,11 @@ fn main() {
.help
(
"Dump .valv files to this directory"
)
)
)
.subcommand
(
SubCommand
::
with_name
(
"valv"
)
.about
(
"Reads VALV (extracted .hl1/.hl2) files"
)
.arg
(
Arg
::
with_name
(
"path"
)
.value_name
(
"FILE"
)
.required
(
true
))
)
.get_matches
();
if
let
Some
(
matches
)
=
matches
.subcommand_matches
(
"jsav"
)
{
...
...
@@ -190,6 +279,17 @@ fn main() {
}
}
}
else
if
let
Some
(
matches
)
=
matches
.subcommand_matches
(
"valv"
)
{
let
path
=
matches
.value_of
(
"path"
)
.expect
(
"require path"
);
let
mut
f
=
File
::
open
(
path
)
.expect
(
"file not found"
);
let
mut
data
:
Vec
<
u8
>
=
Vec
::
new
();
f
.read_to_end
(
&
mut
data
)
.expect
(
"something went wrong reading the file"
);
let
save
=
ValvFile
::
load
(
&
data
)
.expect
(
"failed to read file"
);
println!
(
"{:#?}"
,
save
);
}
else
{
println!
(
"Unknown or missing subcommand. Try with 'help' or -h."
);
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment