-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d73a3ed
Showing
5 changed files
with
282 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
zig-out/ | ||
zig-cache/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Copyright 2023 Matthew Lugg | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# demofixup | ||
|
||
A tool for fixing up Portal 2 demos recorded prior to game build 8873 to play on modern builds. | ||
|
||
## Usage | ||
|
||
Command-line usage: | ||
|
||
./demofixup in.dem [out.dem] | ||
|
||
If `out.dem` is not specified, it defaults to `in_fixed.dem`. | ||
|
||
This usage means that on Windows, you can drag and drop a demo file onto the binary to convert it. | ||
|
||
## Details | ||
|
||
Portal 2 build 8873 removed the `point_survey` entity class. Unfortunately, this also broke all | ||
previously recorded demos. This is because demos contain a section at the start called "data tables" | ||
describing all entity classes and their associated networked properties. After the update, reading | ||
this section triggers an error because it includes an entity - `point_survey` - which the client is | ||
unaware of. | ||
|
||
This entity was never actually used (it was an old playtesting feature). Thus, we can fix this by | ||
simply removing the record of this entity from the datatables. There are two things that need | ||
removing: the serverclass and the sendtable. The sendtable is easy - just omit its entry from the | ||
list. The serverclass is harder, because every class is associated with an ID, and we want these to | ||
be unmodified for other entity types. Moreover, the IDs must be in the range `0` to `n-1` where `n` | ||
is the number of server classes. So, what we can do to solve this is replace the serverclass entry | ||
for this with a duplicate of any other. We'll never use this entry, but that's not important: what | ||
matters is that the game accepts it and plays the demo. Here, we replace `CPointSurvey` and | ||
`DT_PointSurvey` with `CPointCamera` and `DT_PointCamera`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
const std = @import("std"); | ||
|
||
pub fn build(b: *std.Build) void { | ||
const target = b.standardTargetOptions(.{}); | ||
const optimize = b.standardOptimizeOption(.{}); | ||
|
||
const exe = b.addExecutable(.{ | ||
.name = "demofixup", | ||
.root_source_file = .{ .path = "src/main.zig" }, | ||
.target = target, | ||
.optimize = optimize, | ||
}); | ||
exe.install(); | ||
|
||
const run_cmd = exe.run(); | ||
run_cmd.step.dependOn(b.getInstallStep()); | ||
if (b.args) |args| { | ||
run_cmd.addArgs(args); | ||
} | ||
|
||
const run_step = b.step("run", "Run the app"); | ||
run_step.dependOn(&run_cmd.step); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
const std = @import("std"); | ||
|
||
pub fn main() !u8 { | ||
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); | ||
defer arena.deinit(); | ||
const ally = arena.allocator(); | ||
|
||
const args = try std.process.argsAlloc(ally); | ||
if (args.len != 2 and args.len != 3) { | ||
std.io.getStdErr().writer().print( | ||
"usage: {s} <in name> [out name]\n", | ||
.{if (args.len > 0) args[0] else "demofixup"}, | ||
) catch {}; | ||
return 1; | ||
} | ||
|
||
const in_name = args[1]; | ||
const out_name = if (args.len == 3) args[2] else blk: { | ||
const base = if (std.mem.endsWith(u8, in_name, ".dem") or std.mem.endsWith(u8, in_name, ".DEM")) | ||
in_name[0 .. in_name.len - 4] | ||
else | ||
in_name; | ||
break :blk try std.fmt.allocPrint(ally, "{s}_fixed.dem", .{base}); | ||
}; | ||
|
||
var in_file = try std.fs.cwd().openFile(in_name, .{}); | ||
defer in_file.close(); | ||
var out_file = try std.fs.cwd().createFile(out_name, .{}); | ||
defer out_file.close(); | ||
|
||
var buf_rd = std.io.bufferedReader(in_file.reader()); | ||
var buf_wr = std.io.bufferedWriter(out_file.writer()); | ||
|
||
const in_rd = buf_rd.reader(); | ||
const out_wr = buf_wr.writer(); | ||
|
||
try clone(in_rd, out_wr, 1072); // demo header | ||
|
||
while (true) { | ||
const kind = try in_rd.readByte(); | ||
try out_wr.writeByte(kind); | ||
try clone(in_rd, out_wr, 5); | ||
switch (kind) { | ||
1, 2 => { // signon, packet | ||
try clone(in_rd, out_wr, 76 * 2 + 8); | ||
const size = try in_rd.readIntLittle(u32); | ||
try out_wr.writeIntLittle(u32, size); | ||
try clone(in_rd, out_wr, size); | ||
}, | ||
3 => {}, // synctick | ||
4 => { // consolecmd | ||
const size = try in_rd.readIntLittle(u32); | ||
try out_wr.writeIntLittle(u32, size); | ||
try clone(in_rd, out_wr, size); | ||
}, | ||
5 => { // usercmd | ||
try clone(in_rd, out_wr, 4); // cmd | ||
const size = try in_rd.readIntLittle(u32); | ||
try out_wr.writeIntLittle(u32, size); | ||
try clone(in_rd, out_wr, size); | ||
}, | ||
6 => { // datatables | ||
// oh boy 3am!!! | ||
const size = try in_rd.readIntLittle(u32); | ||
try out_wr.writeIntLittle(u32, size); | ||
var count_rd = std.io.countingReader(in_rd); | ||
var count_wr = std.io.countingWriter(out_wr); | ||
try doDataTableStuff(count_rd.reader(), count_wr.writer(), ally); | ||
try in_rd.skipBytes(size - count_rd.bytes_read, .{}); | ||
const to_pad = size - count_wr.bytes_written; | ||
for (0..to_pad) |_| { | ||
try out_wr.writeByte(0); | ||
} | ||
}, | ||
7 => { // stop | ||
break; | ||
}, | ||
8 => { // customdata | ||
try clone(in_rd, out_wr, 4); // type | ||
const size = try in_rd.readIntLittle(u32); | ||
try out_wr.writeIntLittle(u32, size); | ||
try clone(in_rd, out_wr, size); | ||
}, | ||
9 => { // stringtables | ||
const size = try in_rd.readIntLittle(u32); | ||
try out_wr.writeIntLittle(u32, size); | ||
try clone(in_rd, out_wr, size); | ||
}, | ||
else => @panic("silly demo"), | ||
} | ||
} | ||
|
||
// write all remaining data | ||
var fifo = std.fifo.LinearFifo(u8, .{ .Static = 4096 }).init(); | ||
try fifo.pump(in_rd, out_wr); | ||
|
||
try buf_wr.flush(); | ||
|
||
std.io.getStdOut().writer().print("Successfully wrote {s}!\n", .{out_name}) catch {}; | ||
return 0; | ||
} | ||
|
||
fn clone(in: anytype, out: anytype, n: usize) !void { | ||
var buf: [64]u8 = undefined; | ||
var rem = n; | ||
while (rem > 64) { | ||
try in.readNoEof(&buf); | ||
try out.writeAll(&buf); | ||
rem -= 64; | ||
} | ||
try in.readNoEof(buf[0..rem]); | ||
try out.writeAll(buf[0..rem]); | ||
} | ||
|
||
fn cloneBits(br: anytype, bw: anytype, n: usize) !void { | ||
var buf: [8]u8 = undefined; | ||
var rem = n; | ||
while (rem > 64) { | ||
try br.reader().readNoEof(&buf); | ||
try bw.writer().writeAll(&buf); | ||
rem -= 64; | ||
} | ||
const x = try br.readBitsNoEof(u64, rem); | ||
try bw.writeBits(x, rem); | ||
} | ||
|
||
fn doDataTableStuff(in_rd: anytype, out_wr: anytype, ally: std.mem.Allocator) !void { | ||
var br_l = std.io.bitReader(.Little, in_rd); | ||
var bw_l = std.io.bitWriter(.Little, out_wr); | ||
const br = &br_l; | ||
const bw = &bw_l; | ||
|
||
// write out sendtables but remove bad one | ||
while (try readBool(br)) { | ||
const needs_dec = try readBool(br); | ||
const table_name = try readStr(br, ally); | ||
const num_props = try br.readBitsNoEof(u10, 10); | ||
|
||
const skip = std.mem.eql(u8, table_name, "DT_PointSurvey\x00"); | ||
|
||
if (!skip) { | ||
try bw.writeBits(@as(u1, 1), 1); // presence bit | ||
try bw.writeBits(@boolToInt(needs_dec), 1); | ||
try bw.writer().writeAll(table_name); | ||
try bw.writeBits(num_props, 10); | ||
} | ||
|
||
for (0..num_props) |_| { | ||
const prop_ty = try br.readBitsNoEof(u5, 5); | ||
const prop_name = try readStr(br, ally); | ||
const prop_flags = try br.readBitsNoEof(u19, 19); | ||
const prop_priority = try br.readBitsNoEof(u8, 8); | ||
|
||
if (!skip) { | ||
try bw.writeBits(prop_ty, 5); | ||
try bw.writer().writeAll(prop_name); | ||
try bw.writeBits(prop_flags, 19); | ||
try bw.writeBits(prop_priority, 8); | ||
} | ||
|
||
if (prop_ty == 6 or prop_flags & (1 << 6) != 0) { | ||
const exclude_name = try readStr(br, ally); | ||
if (!skip) { | ||
try bw.writer().writeAll(exclude_name); | ||
} | ||
} else { | ||
const extra_len: usize = switch (prop_ty) { | ||
0...4 => 64 + 7, | ||
5 => 10, // array | ||
else => @panic("bad sendtable prop type"), | ||
}; | ||
|
||
if (!skip) { | ||
try cloneBits(br, bw, extra_len); | ||
} else { | ||
_ = try br.readBitsNoEof(u128, extra_len); | ||
} | ||
} | ||
} | ||
} | ||
|
||
try bw.writeBits(@as(u1, 0), 1); | ||
|
||
// write out the classes but replace the bad one with a dummy entry | ||
const num_classes = try br.readBitsNoEof(u16, 16); | ||
try bw.writeBits(num_classes, 16); | ||
for (0..num_classes) |_| { | ||
const class_id = try br.readBitsNoEof(u16, 16); | ||
const class_name = try readStr(br, ally); | ||
const dt_name = try readStr(br, ally); | ||
if (std.mem.eql(u8, dt_name, "DT_PointSurvey\x00")) { | ||
if (!std.mem.eql(u8, class_name, "CPointSurvey\x00")) { | ||
@panic("bad class name using DT_PointSurvey"); | ||
} | ||
try bw.writeBits(class_id, 16); | ||
try bw.writer().writeAll("CPointCamera\x00"); | ||
try bw.writer().writeAll("DT_PointCamera\x00"); | ||
} else { | ||
try bw.writeBits(class_id, 16); | ||
try bw.writer().writeAll(class_name); | ||
try bw.writer().writeAll(dt_name); | ||
} | ||
} | ||
} | ||
|
||
fn readBool(br: anytype) !bool { | ||
const x = try br.readBitsNoEof(u1, 1); | ||
return x == 1; | ||
} | ||
|
||
fn readStr(br: anytype, ally: std.mem.Allocator) ![]const u8 { | ||
var buf = std.ArrayList(u8).init(ally); | ||
while (true) { | ||
const c = try br.reader().readByte(); | ||
try buf.append(c); | ||
if (c == 0) break; | ||
} | ||
return buf.toOwnedSlice(); | ||
} |