Skip to content

Patches

Masked objects in the WAD comprise all actors and sprites. They can be found as weapons, objects, actors etc. They use the following headers and structures:

patch_t :: struct {
    origsize: i16le,         // the orig size of "grabbed" gfx
    width: i16le,            // bounding box size
    height: i16le,
    leftoffset: i16le,       // pixels to the left of origin
    topoffset: i16le,        // pixels above the origin
    collumnofs: [320]u16le,  // only [width] used, the [0] is &collumnofs[width]
}
Example of loading patches in Odin
Rott_Patch :: struct {
    original_size: u16,
    size: [2]u16,
    offset: [2]i16,
    columnofs: []u16,
    columns: []Rott_Column,
}

Rott_Column :: struct {
    posts: [dynamic]Rott_Post,
}

Rott_Post :: struct {
    top_delta: u8,
    length: u8,
    data: []byte,
}

load_rott_patch :: proc(data: []byte, allocator := context.allocator, loc := #caller_location) -> (patch: Rott_Patch) {
    patch.original_size = u16(slice.to_type(data[:2], u16le))
    patch.size.x = u16(slice.to_type(data[2:][:2], u16le))
    patch.size.y = u16(slice.to_type(data[4:][:2], u16le))
    patch.offset.x = i16(slice.to_type(data[6:][:2], i16le))
    patch.offset.y = i16(slice.to_type(data[8:][:2], i16le))
    patch.columnofs = make([]u16, patch.size.x, allocator, loc)
    patch.columns = make([]Rott_Column, patch.size.x, allocator, loc)

    // Binary data for column offsets
    column_data := data[10:][:patch.size.x * 2]

    // Read the column data
    read_columns(data, column_data, &patch.columnofs, &patch.columns, false, allocator, loc)

    return
}

read_columns :: proc(data: []byte, column_data: []byte, columnofs: ^[]u16, columns: ^[]Rott_Column, masked: bool = false, allocator := context.allocator, loc := #caller_location) {
    for &columnof, x in columnofs {
        columnof = u16(slice.to_type(column_data[x * 2:][:2], u16le))
    }

    for &column, x in columns {
        column.posts = make([dynamic]Rott_Post, allocator, loc)

        offset := 0

        for true {
            posts_data := data[int(columnofs[x]) + offset:]

            first_byte := posts_data[0]
            if first_byte == 255 {
                break
            }
            new_post: Rott_Post

            new_post.top_delta = first_byte
            new_post.length = posts_data[1]
            if masked && posts_data[2] == 254 {
                new_post.data = posts_data[2:][:1]
            }
            else {
                new_post.data = posts_data[2:][:new_post.length]
            }
            append(&column.posts, new_post, loc)

            offset_amount: int

            if masked && posts_data[2] == 254 {
                offset_amount = 3
            } else {
                offset_amount = 2 + int(new_post.length)
            }

            next_byte := posts_data[offset_amount]

            if next_byte != 255 {
                offset += offset_amount
            } else {
                break
            }
        }
    }
}

unload_rott_patch :: proc(patch: Rott_Patch, allocator := context.allocator, loc := #caller_location) {
    for column in patch.columns {
        delete(column.posts, loc)
    }

    delete(patch.columnofs, allocator, loc)
    delete(patch.columns, allocator, loc)
}

These are extremely similar to the patches used in another game (that rhymes with, erm, Tomb, except for the addition of the origsize parameter.

Certain objects in the game like masked walls and touch plates will use the second type of patch which acts like a translucent patch.

transpatch_t :: struct {
    origsize: i16le,         // the orig size of "grabbed" gfx
    width: i16le,            // bounding box size
    height: i16le,
    leftoffset: i16le,       // pixels to the left of origin
    topoffset: i16le,        // pixels above the origin
    translevel: i16le,
    collumnofs: [320]i16le,  // only [width] used, the [0] is &collumnofs[width]
}
Example of loading transparent patches in Odin
Rott_Trans_Patch :: struct {
    original_size: u16,
    size: [2]u16,
    offset: [2]i16,
    translevel: u16,
    columnofs: []u16,
    columns: []Rott_Column,
}

Rott_Column :: struct {
    posts: [dynamic]Rott_Post,
}

Rott_Post :: struct {
    top_delta: u8,
    length: u8,
    data: []byte,
}

load_rott_trans_patch :: proc(data: []byte, allocator := context.allocator, loc := #caller_location) -> (patch: Rott_Trans_Patch) {
    patch.original_size = u16(slice.to_type(data[:2], u16le))
    patch.size.x = u16(slice.to_type(data[2:][:2], u16le))
    patch.size.y = u16(slice.to_type(data[4:][:2], u16le))
    patch.offset.x = i16(slice.to_type(data[6:][:2], i16le))
    patch.offset.y = i16(slice.to_type(data[8:][:2], i16le))
    patch.translevel = u16(slice.to_type(data[10:][:2], u16le))
    patch.columnofs = make([]u16, patch.size.x, allocator, loc)
    patch.columns = make([]Rott_Column, patch.size.x, allocator, loc)

    column_data := data[12:][:patch.size.x * 2]

    read_columns(data, column_data, &patch.columnofs, &patch.columns, true, allocator, loc)

    return
}

read_columns :: proc(data: []byte, column_data: []byte, columnofs: ^[]u16, columns: ^[]Rott_Column, masked: bool = false, allocator := context.allocator, loc := #caller_location) {
    for &columnof, x in columnofs {
        columnof = u16(slice.to_type(column_data[x * 2:][:2], u16le))
    }

    for &column, x in columns {
        column.posts = make([dynamic]Rott_Post, allocator, loc)

        offset := 0

        for true {
            posts_data := data[int(columnofs[x]) + offset:]

            first_byte := posts_data[0]
            if first_byte == 255 {
                break
            }
            new_post: Rott_Post

            new_post.top_delta = first_byte
            new_post.length = posts_data[1]
            if masked && posts_data[2] == 254 {
                new_post.data = posts_data[2:][:1]
            }
            else {
                new_post.data = posts_data[2:][:new_post.length]
            }
            append(&column.posts, new_post, loc)

            offset_amount: int

            if masked && posts_data[2] == 254 {
                offset_amount = 3
            } else {
                offset_amount = 2 + int(new_post.length)
            }

            next_byte := posts_data[offset_amount]

            if next_byte != 255 {
                offset += offset_amount
            } else {
                break
            }
        }
    }
}

unload_rott_trans_patch :: proc(patch: Rott_Trans_Patch, allocator := context.allocator, loc := #caller_location) {
    for column in patch.columns {
        delete(column.posts, loc)
    }

    delete(patch.columnofs, allocator, loc)
    delete(patch.columns, allocator, loc)
}