add Linux diagnostics and texture preview tools
This commit is contained in:
parent
943d26e2d9
commit
0662d0a92d
6 changed files with 640 additions and 56 deletions
|
|
@ -36,6 +36,7 @@ vblank_precise_spin_us = 150
|
||||||
host_present_from_non_ui_thread = true
|
host_present_from_non_ui_thread = true
|
||||||
presenter_strict_guest_output_backpressure = false
|
presenter_strict_guest_output_backpressure = false
|
||||||
presenter_strict_guest_output_backpressure_timeout_ms = 100
|
presenter_strict_guest_output_backpressure_timeout_ms = 100
|
||||||
|
vulkan_async_placeholder_max_consecutive_skips = 120
|
||||||
|
|
||||||
present_letterbox = true
|
present_letterbox = true
|
||||||
present_safe_area_x = 100
|
present_safe_area_x = 100
|
||||||
|
|
|
||||||
212
scripts/decode_xenos_texture_dumps.py
Executable file
212
scripts/decode_xenos_texture_dumps.py
Executable file
|
|
@ -0,0 +1,212 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Decode debug-dumped Xbox 360 tiled RGBA8 resolve textures to PNGs."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import html
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PIL import Image
|
||||||
|
except ImportError as exc:
|
||||||
|
raise SystemExit("Pillow is required to write PNGs: python3 -m pip install --user pillow") from exc
|
||||||
|
|
||||||
|
|
||||||
|
DUMP_NAME_RE = re.compile(r"addr_([0-9a-fA-F]{8})_len_([0-9a-fA-F]+)_")
|
||||||
|
|
||||||
|
# These are the render-target resolve ranges observed around the Import Skater
|
||||||
|
# preset-preview path. Pitch is in pixels, including Xbox 360 tile padding.
|
||||||
|
KNOWN_SURFACES_BY_RANGE = {
|
||||||
|
(0x04614000, 0x02D000): (288, 160, 288),
|
||||||
|
(0x0460E000, 0x006000): (72, 40, 96),
|
||||||
|
(0x0460A000, 0x001000): (32, 16, 32),
|
||||||
|
(0x0460B000, 0x001000): (32, 16, 32),
|
||||||
|
(0x04911000, 0x2D0000): (1152, 640, 1152),
|
||||||
|
(0x04641000, 0x2D0000): (1152, 640, 1152),
|
||||||
|
(0x04BE1000, 0x2D0000): (1152, 640, 1152),
|
||||||
|
(0x04EB1000, 0x2D0000): (1152, 640, 1152),
|
||||||
|
(0x04BE1000, 0x168000): (1152, 320, 1152),
|
||||||
|
(0x04D49000, 0x168000): (1152, 320, 1152),
|
||||||
|
(0x04EB1000, 0x168000): (1152, 320, 1152),
|
||||||
|
(0x05019000, 0x168000): (1152, 320, 1152),
|
||||||
|
(0x05258000, 0x398000): (1280, 720, 1280),
|
||||||
|
(0x03554000, 0x300000): (1024, 768, 1024),
|
||||||
|
(0x03954000, 0x300000): (1024, 768, 1024),
|
||||||
|
(0x03C54000, 0x100000): (512, 512, 512),
|
||||||
|
}
|
||||||
|
|
||||||
|
KNOWN_SURFACES_BY_LENGTH = {
|
||||||
|
0x02D000: (288, 160, 288),
|
||||||
|
0x006000: (72, 40, 96),
|
||||||
|
0x001000: (32, 16, 32),
|
||||||
|
0x2D0000: (1152, 640, 1152),
|
||||||
|
0x168000: (1152, 320, 1152),
|
||||||
|
0x398000: (1280, 720, 1280),
|
||||||
|
0x384000: (1280, 720, 1280),
|
||||||
|
0x300000: (1024, 768, 1024),
|
||||||
|
0x100000: (512, 512, 512),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def tiled_offset_2d_row(y: int, pitch: int, log2_bpp: int) -> int:
|
||||||
|
macro = ((y // 32) * (pitch // 32)) << (log2_bpp + 7)
|
||||||
|
micro = ((y & 6) << 2) << log2_bpp
|
||||||
|
return (
|
||||||
|
macro
|
||||||
|
+ ((micro & ~0xF) << 1)
|
||||||
|
+ (micro & 0xF)
|
||||||
|
+ ((y & 8) << (3 + log2_bpp))
|
||||||
|
+ ((y & 1) << 4)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tiled_offset_2d_column(x: int, y: int, log2_bpp: int, base_offset: int) -> int:
|
||||||
|
macro = (x // 32) << (log2_bpp + 7)
|
||||||
|
micro = (x & 7) << log2_bpp
|
||||||
|
offset = base_offset + (macro + ((micro & ~0xF) << 1) + (micro & 0xF))
|
||||||
|
return (
|
||||||
|
((offset & ~0x1FF) << 3)
|
||||||
|
+ ((offset & 0x1C0) << 2)
|
||||||
|
+ (offset & 0x3F)
|
||||||
|
+ ((y & 16) << 7)
|
||||||
|
+ (((((y & 8) >> 2) + (x >> 3)) & 3) << 6)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_dump_name(path: Path) -> tuple[int, int] | None:
|
||||||
|
match = DUMP_NAME_RE.search(path.name)
|
||||||
|
if not match:
|
||||||
|
return None
|
||||||
|
return int(match.group(1), 16), int(match.group(2), 16)
|
||||||
|
|
||||||
|
|
||||||
|
def infer_surface(address: int, length: int) -> tuple[int, int, int] | None:
|
||||||
|
surface = KNOWN_SURFACES_BY_RANGE.get((address, length))
|
||||||
|
if surface:
|
||||||
|
return surface
|
||||||
|
surface = KNOWN_SURFACES_BY_LENGTH.get(length)
|
||||||
|
if surface:
|
||||||
|
return surface
|
||||||
|
|
||||||
|
pixels = length // 4
|
||||||
|
for pitch in (1280, 1152, 1024, 768, 720, 640, 512, 384, 320, 288, 256, 128, 96, 64, 32):
|
||||||
|
if pixels % pitch == 0:
|
||||||
|
height = pixels // pitch
|
||||||
|
if height > 0:
|
||||||
|
return pitch, height, pitch
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def untile_rgba8(raw: bytes, width: int, height: int, pitch: int) -> bytearray:
|
||||||
|
output = bytearray(width * height * 4)
|
||||||
|
for y in range(height):
|
||||||
|
row_offset = tiled_offset_2d_row(y, pitch, 2)
|
||||||
|
for x in range(width):
|
||||||
|
input_offset = tiled_offset_2d_column(x, y, 2, row_offset)
|
||||||
|
input_offset = (input_offset >> 2) * 4
|
||||||
|
if input_offset + 4 > len(raw):
|
||||||
|
continue
|
||||||
|
output_offset = (y * width + x) * 4
|
||||||
|
output[output_offset : output_offset + 4] = raw[input_offset : input_offset + 4]
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def save_variants(untiled: bytearray, width: int, height: int, output_stem: Path) -> list[Path]:
|
||||||
|
rgba = Image.frombytes("RGBA", (width, height), bytes(untiled))
|
||||||
|
|
||||||
|
bgra_bytes = bytearray(len(untiled))
|
||||||
|
for i in range(0, len(untiled), 4):
|
||||||
|
bgra_bytes[i + 0] = untiled[i + 2]
|
||||||
|
bgra_bytes[i + 1] = untiled[i + 1]
|
||||||
|
bgra_bytes[i + 2] = untiled[i + 0]
|
||||||
|
bgra_bytes[i + 3] = untiled[i + 3]
|
||||||
|
bgra = Image.frombytes("RGBA", (width, height), bytes(bgra_bytes))
|
||||||
|
|
||||||
|
alpha_rgb = bytearray(untiled)
|
||||||
|
for i in range(3, len(alpha_rgb), 4):
|
||||||
|
alpha_rgb[i] = 255
|
||||||
|
rgba_opaque = Image.frombytes("RGBA", (width, height), bytes(alpha_rgb))
|
||||||
|
|
||||||
|
paths = [
|
||||||
|
output_stem.with_name(output_stem.name + "_rgba.png"),
|
||||||
|
output_stem.with_name(output_stem.name + "_bgra.png"),
|
||||||
|
output_stem.with_name(output_stem.name + "_rgba_opaque.png"),
|
||||||
|
]
|
||||||
|
rgba.save(paths[0])
|
||||||
|
bgra.save(paths[1])
|
||||||
|
rgba_opaque.save(paths[2])
|
||||||
|
return paths
|
||||||
|
|
||||||
|
|
||||||
|
def write_index(rows: list[tuple[Path, int, int, int, int, list[Path]]], output_dir: Path) -> None:
|
||||||
|
index_path = output_dir / "index.html"
|
||||||
|
parts = [
|
||||||
|
"<!doctype html><meta charset='utf-8'><title>Texture dumps</title>",
|
||||||
|
"<style>body{font-family:sans-serif;margin:24px;background:#111;color:#eee}"
|
||||||
|
"section{margin:0 0 32px}img{max-width:360px;image-rendering:auto;background:#444}"
|
||||||
|
".row{display:flex;gap:16px;flex-wrap:wrap}.item{max-width:380px}"
|
||||||
|
"code{color:#b7f7ff}</style>",
|
||||||
|
"<h1>Texture dumps</h1>",
|
||||||
|
]
|
||||||
|
for raw_path, address, length, width, height, images in rows:
|
||||||
|
parts.append(
|
||||||
|
f"<section><h2><code>{html.escape(raw_path.name)}</code></h2>"
|
||||||
|
f"<p>address <code>0x{address:08X}</code>, length <code>0x{length:X}</code>, "
|
||||||
|
f"{width}x{height}</p><div class='row'>"
|
||||||
|
)
|
||||||
|
for image_path in images:
|
||||||
|
label = image_path.name.removeprefix(raw_path.stem + "_").removesuffix(".png")
|
||||||
|
parts.append(
|
||||||
|
f"<div class='item'><p>{html.escape(label)}</p>"
|
||||||
|
f"<img src='{html.escape(image_path.name)}'></div>"
|
||||||
|
)
|
||||||
|
parts.append("</div></section>")
|
||||||
|
index_path.write_text("\n".join(parts), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"input_dir",
|
||||||
|
nargs="?",
|
||||||
|
default="/home/alex/Desktop/skate_fresh/logs/texture_dumps/raw",
|
||||||
|
help="Directory containing resolve_*.bin dumps",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output-dir",
|
||||||
|
default="/home/alex/Desktop/skate_fresh/logs/texture_dumps/rendered",
|
||||||
|
help="Directory to write decoded PNGs and index.html",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
input_dir = Path(args.input_dir)
|
||||||
|
output_dir = Path(args.output_dir)
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
rows: list[tuple[Path, int, int, int, int, list[Path]]] = []
|
||||||
|
for raw_path in sorted(input_dir.glob("resolve_*.bin")):
|
||||||
|
parsed = parse_dump_name(raw_path)
|
||||||
|
if not parsed:
|
||||||
|
continue
|
||||||
|
address, length = parsed
|
||||||
|
surface = infer_surface(address, length)
|
||||||
|
if not surface:
|
||||||
|
print(f"Skipping {raw_path.name}: unknown dimensions for length 0x{length:X}")
|
||||||
|
continue
|
||||||
|
width, height, pitch = surface
|
||||||
|
raw = raw_path.read_bytes()
|
||||||
|
untiled = untile_rgba8(raw, width, height, pitch)
|
||||||
|
output_stem = output_dir / raw_path.stem
|
||||||
|
images = save_variants(untiled, width, height, output_stem)
|
||||||
|
rows.append((raw_path, address, length, width, height, images))
|
||||||
|
print(f"Decoded {raw_path.name}: {width}x{height}, pitch {pitch}")
|
||||||
|
|
||||||
|
write_index(rows, output_dir)
|
||||||
|
print(f"Wrote {output_dir / 'index.html'}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
317
scripts/render_static_rx2_textures.py
Executable file
317
scripts/render_static_rx2_textures.py
Executable file
|
|
@ -0,0 +1,317 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Extract Skate 3 EB archives and render top-mip RX2 textures to PNG."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import html
|
||||||
|
import math
|
||||||
|
import shutil
|
||||||
|
import struct
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
TEXTURE_FORMATS = {
|
||||||
|
0x52: ("DXT1", 8),
|
||||||
|
0x53: ("DXT3", 16),
|
||||||
|
0x54: ("DXT5", 16),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def safe_output_stem(path: Path, root: Path | None = None) -> str:
|
||||||
|
try:
|
||||||
|
label = path.relative_to(root) if root else path
|
||||||
|
except ValueError:
|
||||||
|
label = path
|
||||||
|
return "__".join(part.replace("/", "_").replace("\\", "_") for part in label.with_suffix("").parts)
|
||||||
|
|
||||||
|
|
||||||
|
def tiled_offset_2d_row(y: int, width: int, log2_bpp: int) -> int:
|
||||||
|
macro = ((y // 32) * (width // 32)) << (log2_bpp + 7)
|
||||||
|
micro = ((y & 6) << 2) << log2_bpp
|
||||||
|
return (
|
||||||
|
macro
|
||||||
|
+ ((micro & ~0xF) << 1)
|
||||||
|
+ (micro & 0xF)
|
||||||
|
+ ((y & 8) << (3 + log2_bpp))
|
||||||
|
+ ((y & 1) << 4)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def tiled_offset_2d_column(x: int, y: int, log2_bpp: int, base_offset: int) -> int:
|
||||||
|
macro = (x // 32) << (log2_bpp + 7)
|
||||||
|
micro = (x & 7) << log2_bpp
|
||||||
|
offset = base_offset + (macro + ((micro & ~0xF) << 1) + (micro & 0xF))
|
||||||
|
return (
|
||||||
|
((offset & ~0x1FF) << 3)
|
||||||
|
+ ((offset & 0x1C0) << 2)
|
||||||
|
+ (offset & 0x3F)
|
||||||
|
+ ((y & 16) << 7)
|
||||||
|
+ (((((y & 8) >> 2) + (x >> 3)) & 3) << 6)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def swap16(data: bytes) -> bytes:
|
||||||
|
output = bytearray(data)
|
||||||
|
for offset in range(0, len(output) - 1, 2):
|
||||||
|
output[offset], output[offset + 1] = output[offset + 1], output[offset]
|
||||||
|
return bytes(output)
|
||||||
|
|
||||||
|
|
||||||
|
def untile_blocks(data: bytes, blocks_w: int, blocks_h: int, bytes_per_block: int) -> bytes:
|
||||||
|
log2_bpp = 3 if bytes_per_block == 8 else 4
|
||||||
|
pitch_blocks = max(32, math.ceil(blocks_w / 32) * 32)
|
||||||
|
output = bytearray(blocks_w * blocks_h * bytes_per_block)
|
||||||
|
for y in range(blocks_h):
|
||||||
|
row_offset = tiled_offset_2d_row(y, pitch_blocks, log2_bpp)
|
||||||
|
for x in range(blocks_w):
|
||||||
|
input_offset = tiled_offset_2d_column(x, y, log2_bpp, row_offset)
|
||||||
|
input_offset = (input_offset >> log2_bpp) * bytes_per_block
|
||||||
|
output_offset = (y * blocks_w + x) * bytes_per_block
|
||||||
|
if input_offset + bytes_per_block <= len(data):
|
||||||
|
output[output_offset : output_offset + bytes_per_block] = data[
|
||||||
|
input_offset : input_offset + bytes_per_block
|
||||||
|
]
|
||||||
|
return swap16(output)
|
||||||
|
|
||||||
|
|
||||||
|
def make_dds(width: int, height: int, fourcc: str, pitch_or_size: int, payload: bytes) -> bytes:
|
||||||
|
header = bytearray(b"DDS ")
|
||||||
|
header += struct.pack("<I", 124)
|
||||||
|
header += struct.pack("<I", 0x0002100F)
|
||||||
|
header += struct.pack("<I", height)
|
||||||
|
header += struct.pack("<I", width)
|
||||||
|
header += struct.pack("<I", pitch_or_size)
|
||||||
|
header += struct.pack("<I", 0)
|
||||||
|
header += struct.pack("<I", 1)
|
||||||
|
header += bytes(44)
|
||||||
|
header += struct.pack("<I", 32)
|
||||||
|
header += struct.pack("<I", 0x4)
|
||||||
|
header += fourcc.encode("ascii")
|
||||||
|
header += struct.pack("<I", 0)
|
||||||
|
header += struct.pack("<I", 0)
|
||||||
|
header += struct.pack("<I", 0)
|
||||||
|
header += struct.pack("<I", 0)
|
||||||
|
header += struct.pack("<I", 0)
|
||||||
|
header += struct.pack("<I", 0x1000)
|
||||||
|
header += struct.pack("<I", 0)
|
||||||
|
header += struct.pack("<I", 0)
|
||||||
|
header += struct.pack("<I", 0)
|
||||||
|
header += struct.pack("<I", 0)
|
||||||
|
return bytes(header) + payload
|
||||||
|
|
||||||
|
|
||||||
|
def parse_rx2_header(data: bytes) -> tuple[int, int, int, int] | None:
|
||||||
|
if not data.startswith(b"\x89RW4xb2") or len(data) < 0x238:
|
||||||
|
return None
|
||||||
|
data_offset = int.from_bytes(data[0x44:0x48], "big")
|
||||||
|
if data_offset <= 0 or data_offset >= len(data):
|
||||||
|
data_offset = 0x238
|
||||||
|
fmt = data[0x167]
|
||||||
|
height = (int.from_bytes(data[0x168:0x16A], "big") + 1) * 8
|
||||||
|
width = (int.from_bytes(data[0x16A:0x16C], "big") + 1) & 0x1FFF
|
||||||
|
if width <= 0 or height <= 0 or width > 8192 or height > 8192:
|
||||||
|
return None
|
||||||
|
return fmt, width, height, data_offset
|
||||||
|
|
||||||
|
|
||||||
|
def render_rx2(
|
||||||
|
path: Path,
|
||||||
|
output_dir: Path,
|
||||||
|
root: Path | None = None,
|
||||||
|
layout: str = "tiled",
|
||||||
|
) -> Path | None:
|
||||||
|
output_path = output_dir / "images" / (safe_output_stem(path, root) + ".png")
|
||||||
|
if output_path.exists():
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
data = path.read_bytes()
|
||||||
|
parsed = parse_rx2_header(data)
|
||||||
|
if not parsed:
|
||||||
|
return None
|
||||||
|
fmt, width, height, data_offset = parsed
|
||||||
|
format_info = TEXTURE_FORMATS.get(fmt)
|
||||||
|
if not format_info:
|
||||||
|
return None
|
||||||
|
|
||||||
|
fourcc, bytes_per_block = format_info
|
||||||
|
blocks_w = max(1, math.ceil(width / 4))
|
||||||
|
blocks_h = max(1, math.ceil(height / 4))
|
||||||
|
top_mip_size = blocks_w * blocks_h * bytes_per_block
|
||||||
|
payload = data[data_offset : data_offset + top_mip_size]
|
||||||
|
if len(payload) < top_mip_size:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if layout == "linear":
|
||||||
|
decoded = swap16(payload)
|
||||||
|
elif layout == "tiled":
|
||||||
|
decoded = untile_blocks(payload, blocks_w, blocks_h, bytes_per_block)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown RX2 layout: {layout}")
|
||||||
|
|
||||||
|
dds = make_dds(width, height, fourcc, top_mip_size, decoded)
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
Image.open(__import__("io").BytesIO(dds)).convert("RGBA").save(output_path)
|
||||||
|
return output_path
|
||||||
|
|
||||||
|
|
||||||
|
def write_texture_page(
|
||||||
|
rendered: list[tuple[Path, Path]],
|
||||||
|
output_dir: Path,
|
||||||
|
page_number: int,
|
||||||
|
page_count: int,
|
||||||
|
) -> str:
|
||||||
|
page_name = f"page-{page_number:03d}.html"
|
||||||
|
rows = [
|
||||||
|
"<!doctype html><meta charset='utf-8'><title>Skate 3 RX2 textures</title>",
|
||||||
|
"<style>body{font-family:sans-serif;background:#111;color:#eee;margin:24px}"
|
||||||
|
".grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:14px}"
|
||||||
|
".item{background:#1b1b1b;padding:8px;border-radius:4px;overflow:hidden}"
|
||||||
|
"img{width:100%;height:auto;max-height:240px;object-fit:contain;background:#333;image-rendering:auto}"
|
||||||
|
"code{font-size:11px;word-break:break-all;color:#cceeff}"
|
||||||
|
"a{color:#9bdcff}.nav{display:flex;gap:12px;align-items:center;margin:12px 0 20px}</style>",
|
||||||
|
f"<h1>Skate 3 RX2 Textures: Page {page_number} / {page_count}</h1>",
|
||||||
|
"<div class='nav'>",
|
||||||
|
"<a href='index.html'>Index</a>",
|
||||||
|
]
|
||||||
|
if page_number > 1:
|
||||||
|
rows.append(f"<a href='page-{page_number - 1:03d}.html'>Previous</a>")
|
||||||
|
if page_number < page_count:
|
||||||
|
rows.append(f"<a href='page-{page_number + 1:03d}.html'>Next</a>")
|
||||||
|
rows.append("</div><div class='grid'>")
|
||||||
|
for source, image in rendered:
|
||||||
|
rel_image = image.relative_to(output_dir)
|
||||||
|
rows.append(
|
||||||
|
f"<div class='item'><img loading='lazy' src='{html.escape(str(rel_image))}'>"
|
||||||
|
f"<p><code>{html.escape(source.name)}</code></p></div>"
|
||||||
|
)
|
||||||
|
rows.append("</div>")
|
||||||
|
(output_dir / page_name).write_text("\n".join(rows), encoding="utf-8")
|
||||||
|
return page_name
|
||||||
|
|
||||||
|
|
||||||
|
def write_index(rendered: list[tuple[Path, Path]], output_dir: Path, page_size: int) -> None:
|
||||||
|
page_size = max(1, page_size)
|
||||||
|
pages = [rendered[offset : offset + page_size] for offset in range(0, len(rendered), page_size)]
|
||||||
|
page_names = [
|
||||||
|
write_texture_page(page, output_dir, page_number, len(pages))
|
||||||
|
for page_number, page in enumerate(pages, start=1)
|
||||||
|
]
|
||||||
|
|
||||||
|
rows = [
|
||||||
|
"<!doctype html><meta charset='utf-8'><title>Skate 3 RX2 textures</title>",
|
||||||
|
"<style>body{font-family:sans-serif;background:#111;color:#eee;margin:24px}"
|
||||||
|
"a{color:#9bdcff}.pages{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:10px}"
|
||||||
|
".page{background:#1b1b1b;padding:12px;border-radius:4px}</style>",
|
||||||
|
"<h1>Skate 3 RX2 Textures</h1>",
|
||||||
|
f"<p>{len(rendered)} textures rendered. Open one page at a time to avoid loading every PNG at once.</p>",
|
||||||
|
"<div class='pages'>",
|
||||||
|
]
|
||||||
|
for index, page_name in enumerate(page_names, start=1):
|
||||||
|
start = ((index - 1) * page_size) + 1
|
||||||
|
end = min(index * page_size, len(rendered))
|
||||||
|
rows.append(
|
||||||
|
f"<a class='page' href='{page_name}'>Page {index}<br>{start}-{end}</a>"
|
||||||
|
)
|
||||||
|
rows.append("</div>")
|
||||||
|
(output_dir / "index.html").write_text("\n".join(rows), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def maybe_extract_big(args: argparse.Namespace) -> Path:
|
||||||
|
extracted_dir = Path(args.extracted_dir)
|
||||||
|
texture_dir = extracted_dir / "data/content/createacharacter/texture"
|
||||||
|
if texture_dir.exists() and list(texture_dir.glob("*.rx2")):
|
||||||
|
return extracted_dir
|
||||||
|
|
||||||
|
if not args.big:
|
||||||
|
raise SystemExit("No extracted textures found and --big was not provided.")
|
||||||
|
quickbms = shutil.which(args.quickbms)
|
||||||
|
if not quickbms:
|
||||||
|
raise SystemExit(f"QuickBMS executable not found: {args.quickbms}")
|
||||||
|
script = Path(args.bms_script)
|
||||||
|
if not script.exists():
|
||||||
|
raise SystemExit(f"BMS script not found: {script}")
|
||||||
|
|
||||||
|
extracted_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
subprocess.run([quickbms, "-Y", str(script), str(args.big), str(extracted_dir)], check=True)
|
||||||
|
return extracted_dir
|
||||||
|
|
||||||
|
|
||||||
|
def read_paths_file(path: Path) -> list[Path]:
|
||||||
|
files: list[Path] = []
|
||||||
|
for line in path.read_text(encoding="utf-8").splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
files.append(Path(line))
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--big", type=Path, help="Optional createacharacter.big path to extract first")
|
||||||
|
parser.add_argument("--quickbms", default="/tmp/quickbms_skate/quickbms_linux/quickbms")
|
||||||
|
parser.add_argument("--bms-script", default="/tmp/quickbms_skate/fightnight.bms")
|
||||||
|
parser.add_argument("--extracted-dir", default="/tmp/skate_static_rx2")
|
||||||
|
parser.add_argument(
|
||||||
|
"--texture-dir",
|
||||||
|
type=Path,
|
||||||
|
help="Directory containing RX2 files to render. Defaults to createacharacter/texture.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--paths-file",
|
||||||
|
type=Path,
|
||||||
|
help="Text file containing explicit RX2 paths to render, one per line.",
|
||||||
|
)
|
||||||
|
parser.add_argument("--recursive", action="store_true", help="Recursively scan --texture-dir")
|
||||||
|
parser.add_argument(
|
||||||
|
"--layout",
|
||||||
|
choices=("tiled", "linear"),
|
||||||
|
default="tiled",
|
||||||
|
help="RX2 memory layout. createacharacter textures are tiled; FE/menu textures are usually linear.",
|
||||||
|
)
|
||||||
|
parser.add_argument("--output-dir", default="/tmp/skate_static_rx2_rendered")
|
||||||
|
parser.add_argument("--limit", type=int, default=500, help="Maximum textures to render")
|
||||||
|
parser.add_argument("--page-size", type=int, default=100, help="Textures per HTML page")
|
||||||
|
parser.add_argument("--verbose", action="store_true", help="Print every rendered texture")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
extracted_dir = Path(args.extracted_dir)
|
||||||
|
if args.paths_file:
|
||||||
|
sources = read_paths_file(args.paths_file)
|
||||||
|
source_root = None
|
||||||
|
else:
|
||||||
|
if args.texture_dir:
|
||||||
|
texture_dir = args.texture_dir
|
||||||
|
source_root = texture_dir
|
||||||
|
else:
|
||||||
|
extracted_dir = maybe_extract_big(args)
|
||||||
|
texture_dir = extracted_dir / "data/content/createacharacter/texture"
|
||||||
|
source_root = texture_dir
|
||||||
|
globber = texture_dir.rglob if args.recursive else texture_dir.glob
|
||||||
|
sources = sorted(globber("*.rx2"))
|
||||||
|
|
||||||
|
output_dir = Path(args.output_dir)
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
rendered: list[tuple[Path, Path]] = []
|
||||||
|
for source in sources:
|
||||||
|
image = render_rx2(source, output_dir, source_root, args.layout)
|
||||||
|
if image:
|
||||||
|
rendered.append((source, image))
|
||||||
|
if args.verbose:
|
||||||
|
print(f"Rendered {source.name}")
|
||||||
|
if args.limit and len(rendered) >= args.limit:
|
||||||
|
break
|
||||||
|
|
||||||
|
write_index(rendered, output_dir, args.page_size)
|
||||||
|
print(f"Wrote {len(rendered)} textures to {output_dir}")
|
||||||
|
print(f"Open {output_dir / 'index.html'}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|
@ -37,7 +37,6 @@
|
||||||
#include <rex/filesystem.h>
|
#include <rex/filesystem.h>
|
||||||
#include <rex/filesystem/devices/host_path_device.h>
|
#include <rex/filesystem/devices/host_path_device.h>
|
||||||
#include <rex/filesystem/vfs.h>
|
#include <rex/filesystem/vfs.h>
|
||||||
#include <rex/graphics/graphics_system.h>
|
|
||||||
#include <rex/input/input_system.h>
|
#include <rex/input/input_system.h>
|
||||||
#include <rex/kernel/xam/module.h>
|
#include <rex/kernel/xam/module.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
|
|
@ -185,19 +184,24 @@ bool ConfigContainsAnyKey(const toml::table& table, std::initializer_list<std::s
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DeveloperConfigHasResolutionScaleOverride(const std::filesystem::path& config_path) {
|
bool ConfigFileContainsAnyKey(const std::filesystem::path& config_path,
|
||||||
|
std::initializer_list<std::string_view> keys) {
|
||||||
if (config_path.empty() || !std::filesystem::exists(config_path)) {
|
if (config_path.empty() || !std::filesystem::exists(config_path)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
auto config = toml::parse_file(config_path.string());
|
auto config = toml::parse_file(config_path.string());
|
||||||
return ConfigContainsAnyKey(config, {"resolution_scale", "draw_resolution_scale_x",
|
return ConfigContainsAnyKey(config, keys);
|
||||||
"draw_resolution_scale_y"});
|
|
||||||
} catch (const toml::parse_error&) {
|
} catch (const toml::parse_error&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DeveloperConfigHasResolutionScaleOverride(const std::filesystem::path& config_path) {
|
||||||
|
return ConfigFileContainsAnyKey(config_path, {"resolution_scale", "draw_resolution_scale_x",
|
||||||
|
"draw_resolution_scale_y"});
|
||||||
|
}
|
||||||
|
|
||||||
constexpr int kDefaultResolutionScale = 2;
|
constexpr int kDefaultResolutionScale = 2;
|
||||||
|
|
||||||
void ApplyFirstRunVideoDefaults(const std::filesystem::path& settings_path,
|
void ApplyFirstRunVideoDefaults(const std::filesystem::path& settings_path,
|
||||||
|
|
@ -369,6 +373,14 @@ void Skate3BaseApp::OnCreateDialogs(rex::ui::ImGuiDrawer* drawer) {
|
||||||
"Save draw fingerprint log", [this] {
|
"Save draw fingerprint log", [this] {
|
||||||
SaveDrawFingerprintLog();
|
SaveDrawFingerprintLog();
|
||||||
});
|
});
|
||||||
|
rex::ui::RegisterBind("bind_skate3_log_debug_marker", "F9",
|
||||||
|
"Write debug marker to log", [this] {
|
||||||
|
LogDebugMarker();
|
||||||
|
});
|
||||||
|
rex::ui::RegisterBind("bind_skate3_log_user_marker", "F10",
|
||||||
|
"Write marker to log", [this] {
|
||||||
|
LogUserMarker();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Skate3BaseApp::OnPostSetup() {
|
void Skate3BaseApp::OnPostSetup() {
|
||||||
|
|
@ -410,48 +422,14 @@ void Skate3BaseApp::OnPostSetup() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Skate3BaseApp::OnPostLaunchModule(rex::system::XThread* thread) {
|
|
||||||
(void)thread;
|
|
||||||
|
|
||||||
if (std::getenv("SKATE3_INVALIDATE_GPU_MEMORY_EVERY_FRAME") == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* graphics_system =
|
|
||||||
static_cast<rex::graphics::GraphicsSystem*>(runtime()->graphics_system());
|
|
||||||
if (!graphics_system) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidate_gpu_memory_thread_running_.store(true, std::memory_order_release);
|
|
||||||
invalidate_gpu_memory_thread_ = std::thread([this, graphics_system]() {
|
|
||||||
while (invalidate_gpu_memory_thread_running_.load(
|
|
||||||
std::memory_order_acquire)) {
|
|
||||||
graphics_system->InvalidateGpuMemory();
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Skate3BaseApp::OnShutdown() {
|
void Skate3BaseApp::OnShutdown() {
|
||||||
rex::ui::UnregisterBind("bind_skate3_menu");
|
rex::ui::UnregisterBind("bind_skate3_menu");
|
||||||
rex::ui::UnregisterBind("bind_skate3_menu_alt");
|
rex::ui::UnregisterBind("bind_skate3_menu_alt");
|
||||||
rex::ui::UnregisterBind("bind_skate3_save_draw_fingerprints");
|
rex::ui::UnregisterBind("bind_skate3_save_draw_fingerprints");
|
||||||
|
rex::ui::UnregisterBind("bind_skate3_log_debug_marker");
|
||||||
|
rex::ui::UnregisterBind("bind_skate3_log_user_marker");
|
||||||
ApplyGameplayCursorMode();
|
ApplyGameplayCursorMode();
|
||||||
simple_settings_dialog_.reset();
|
simple_settings_dialog_.reset();
|
||||||
StopGpuMemoryInvalidationThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Skate3BaseApp::OnGuestThreadExit(rex::system::XThread* thread) {
|
|
||||||
(void)thread;
|
|
||||||
StopGpuMemoryInvalidationThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Skate3BaseApp::StopGpuMemoryInvalidationThread() {
|
|
||||||
invalidate_gpu_memory_thread_running_.store(false, std::memory_order_release);
|
|
||||||
if (invalidate_gpu_memory_thread_.joinable()) {
|
|
||||||
invalidate_gpu_memory_thread_.join();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Skate3BaseApp::ToggleSimpleSettings() {
|
void Skate3BaseApp::ToggleSimpleSettings() {
|
||||||
|
|
@ -501,10 +479,10 @@ void Skate3BaseApp::ToggleSimpleSettings() {
|
||||||
};
|
};
|
||||||
auto close_settings = [this]() { ApplyGameplayCursorMode(); };
|
auto close_settings = [this]() { ApplyGameplayCursorMode(); };
|
||||||
auto close_game = [this]() {
|
auto close_game = [this]() {
|
||||||
#if REX_PLATFORM_MAC
|
#if REX_PLATFORM_MAC || REX_PLATFORM_LINUX
|
||||||
std::thread([]() {
|
std::thread([]() {
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(10));
|
std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||||
REXLOG_WARN("macOS Close Game watchdog exiting process after shutdown timeout");
|
REXLOG_WARN("Close Game watchdog exiting process after shutdown timeout");
|
||||||
std::_Exit(EXIT_SUCCESS);
|
std::_Exit(EXIT_SUCCESS);
|
||||||
}).detach();
|
}).detach();
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -648,6 +626,28 @@ void Skate3BaseApp::SaveDrawFingerprintLog() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Skate3BaseApp::LogUserMarker() {
|
||||||
|
const uint32_t marker = debug_marker_count_.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||||
|
REXLOG_WARN("USER LOG MARKER #{}: F10 pressed", marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Skate3BaseApp::LogDebugMarker() {
|
||||||
|
const uint32_t marker = debug_marker_count_.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||||
|
rex::cvar::SetFlagByName("vulkan_debug_log_frame_summaries_remaining", "36000");
|
||||||
|
rex::cvar::SetFlagByName("vulkan_debug_frame_summary_interval_frames", "300");
|
||||||
|
rex::cvar::SetFlagByName("vulkan_debug_log_resolve_decisions_remaining", "10000");
|
||||||
|
rex::cvar::SetFlagByName("vulkan_debug_log_team_profile_background_candidates_remaining",
|
||||||
|
"1000");
|
||||||
|
rex::cvar::SetFlagByName("vulkan_debug_log_team_profile_background_bindings_remaining",
|
||||||
|
"256");
|
||||||
|
rex::cvar::SetFlagByName("filesystem_debug_log_fe_asset_ops_remaining", "10000");
|
||||||
|
rex::cvar::SetFlagByName("filesystem_debug_log_team_profile_background_remaining", "200");
|
||||||
|
REXLOG_WARN(
|
||||||
|
"USER DEBUG MARKER #{}: F9 pressed; enabled Vulkan frame summaries, resolve diagnostics, "
|
||||||
|
"FE asset diagnostics, and team_profile_background_0 tracing",
|
||||||
|
marker);
|
||||||
|
}
|
||||||
|
|
||||||
void Skate3BaseApp::ApplySelectedProfileToRuntime() {
|
void Skate3BaseApp::ApplySelectedProfileToRuntime() {
|
||||||
if (!runtime() || !runtime()->kernel_state() || !runtime()->kernel_state()->user_profile()) {
|
if (!runtime() || !runtime()->kernel_state() || !runtime()->kernel_state()->user_profile()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -703,51 +703,106 @@ std::set<std::string> Skate3BaseApp::DiscoverRecipeAliases(
|
||||||
return aliases;
|
return aliases;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Skate3BaseApp::CreateOverlayDirectory(
|
||||||
|
const std::filesystem::path& overlay_root, std::string_view guest_path) {
|
||||||
|
std::filesystem::path path = overlay_root;
|
||||||
|
size_t segment_start = 0;
|
||||||
|
while (segment_start < guest_path.size()) {
|
||||||
|
const size_t slash = guest_path.find('/', segment_start);
|
||||||
|
const size_t segment_end = slash == std::string_view::npos ? guest_path.size() : slash;
|
||||||
|
if (segment_end > segment_start) {
|
||||||
|
path /= std::string(guest_path.substr(segment_start, segment_end - segment_start));
|
||||||
|
}
|
||||||
|
if (slash == std::string_view::npos) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
segment_start = slash + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::create_directories(path, ec);
|
||||||
|
return !ec;
|
||||||
|
}
|
||||||
|
|
||||||
void Skate3BaseApp::InstallRecipeOverlay() {
|
void Skate3BaseApp::InstallRecipeOverlay() {
|
||||||
if (recipe_overlay_installed_ || !runtime() || !runtime()->file_system()) {
|
if (recipe_overlay_installed_ || !runtime() || !runtime()->file_system()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto content_root = game_data_root() / "data" / "content";
|
const auto content_root = runtime()->game_data_root() / "data" / "content";
|
||||||
if (!std::filesystem::exists(content_root)) {
|
if (!std::filesystem::exists(content_root)) {
|
||||||
|
REXLOG_WARN("Skipping Skate 3 recipe VFS overlay; content root not found: {}",
|
||||||
|
content_root.string());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto aliases = DiscoverRecipeAliases(content_root);
|
const auto aliases = DiscoverRecipeAliases(content_root);
|
||||||
if (aliases.empty()) {
|
if (aliases.empty()) {
|
||||||
|
REXLOG_WARN("Skipping Skate 3 recipe VFS overlay; no recipe aliases found in {}",
|
||||||
|
content_root.string());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto overlay_root = cache_root() / "vfs_recipe_aliases";
|
const auto overlay_root = cache_root() / "vfs_big_directory_aliases";
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
std::filesystem::create_directories(overlay_root, ec);
|
std::filesystem::create_directories(overlay_root, ec);
|
||||||
if (ec) {
|
if (ec) {
|
||||||
|
REXLOG_WARN("Skipping Skate 3 BIG-directory VFS overlay; failed to create {}: {}",
|
||||||
|
overlay_root.string(), ec.message());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t created = 0;
|
size_t created = 0;
|
||||||
for (const auto& alias : aliases) {
|
for (const auto& alias : aliases) {
|
||||||
std::filesystem::create_directories(overlay_root / alias, ec);
|
const std::string guest_path = std::string("data/content/recipe/") + alias;
|
||||||
if (!ec) {
|
if (CreateOverlayDirectory(overlay_root, guest_path)) {
|
||||||
++created;
|
++created;
|
||||||
}
|
}
|
||||||
ec.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr std::string_view kBigDirectoryAliases[] = {
|
||||||
|
"data/scene",
|
||||||
|
"data/scene/anim",
|
||||||
|
"data/scene/trickguide",
|
||||||
|
"data/livingworld/PluginDescriptor",
|
||||||
|
"data/state/livingworldentities/pedestrian/plugin",
|
||||||
|
};
|
||||||
|
for (std::string_view alias : kBigDirectoryAliases) {
|
||||||
|
if (CreateOverlayDirectory(overlay_root, alias)) {
|
||||||
|
++created;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!created) {
|
if (!created) {
|
||||||
|
REXLOG_WARN("Skipping Skate 3 BIG-directory VFS overlay; failed to create alias directories in {}",
|
||||||
|
overlay_root.string());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto device = std::make_unique<rex::filesystem::HostPathDevice>(
|
auto device = std::make_unique<rex::filesystem::HostPathDevice>(
|
||||||
"skate3recipe:", overlay_root, true);
|
"skate3bigdirs:", overlay_root, true);
|
||||||
if (!device->Initialize()) {
|
if (!device->Initialize()) {
|
||||||
|
REXLOG_WARN("Skipping Skate 3 BIG-directory VFS overlay; failed to initialize host device for {}",
|
||||||
|
overlay_root.string());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
runtime()->file_system()->RegisterDevice(std::move(device));
|
runtime()->file_system()->RegisterDevice(std::move(device));
|
||||||
runtime()->file_system()->RegisterSymbolicLink(
|
runtime()->file_system()->RegisterSymbolicLink(
|
||||||
"\\Device\\Harddisk0\\Partition1\\data\\content\\recipe",
|
"\\Device\\Harddisk0\\Partition1\\data\\content\\recipe",
|
||||||
"skate3recipe:");
|
"skate3bigdirs:\\data\\content\\recipe");
|
||||||
|
runtime()->file_system()->RegisterSymbolicLink(
|
||||||
|
"\\Device\\Harddisk0\\Partition1\\data\\scene",
|
||||||
|
"skate3bigdirs:\\data\\scene");
|
||||||
|
runtime()->file_system()->RegisterSymbolicLink(
|
||||||
|
"\\Device\\Harddisk0\\Partition1\\data\\livingworld\\PluginDescriptor",
|
||||||
|
"skate3bigdirs:\\data\\livingworld\\PluginDescriptor");
|
||||||
|
runtime()->file_system()->RegisterSymbolicLink(
|
||||||
|
"\\Device\\Harddisk0\\Partition1\\data\\state\\livingworldentities\\pedestrian\\plugin",
|
||||||
|
"skate3bigdirs:\\data\\state\\livingworldentities\\pedestrian\\plugin");
|
||||||
recipe_overlay_installed_ = true;
|
recipe_overlay_installed_ = true;
|
||||||
|
REXLOG_INFO("Installed Skate 3 BIG-directory VFS overlay with {} aliases from {}",
|
||||||
|
created, content_root.string());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Skate3BaseApp::InstallBigDeviceAliases() {
|
void Skate3BaseApp::InstallBigDeviceAliases() {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include <rex/rex_app.h>
|
#include <rex/rex_app.h>
|
||||||
#include <rex/ui/overlay/simple_settings_overlay.h>
|
#include <rex/ui/overlay/simple_settings_overlay.h>
|
||||||
|
|
@ -31,12 +30,9 @@ class Skate3BaseApp : public rex::ReXApp {
|
||||||
void OnConfigureFonts(ImFontAtlas* atlas) override;
|
void OnConfigureFonts(ImFontAtlas* atlas) override;
|
||||||
void OnCreateDialogs(rex::ui::ImGuiDrawer* drawer) override;
|
void OnCreateDialogs(rex::ui::ImGuiDrawer* drawer) override;
|
||||||
void OnPostSetup() override;
|
void OnPostSetup() override;
|
||||||
void OnPostLaunchModule(rex::system::XThread* thread) override;
|
|
||||||
void OnShutdown() override;
|
void OnShutdown() override;
|
||||||
void OnGuestThreadExit(rex::system::XThread* thread) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void StopGpuMemoryInvalidationThread();
|
|
||||||
void InstallRecipeOverlay();
|
void InstallRecipeOverlay();
|
||||||
void InstallBigDeviceAliases();
|
void InstallBigDeviceAliases();
|
||||||
void ToggleSimpleSettings();
|
void ToggleSimpleSettings();
|
||||||
|
|
@ -44,11 +40,15 @@ class Skate3BaseApp : public rex::ReXApp {
|
||||||
void ApplyGameplayCursorMode();
|
void ApplyGameplayCursorMode();
|
||||||
void RestartGame();
|
void RestartGame();
|
||||||
void SaveDrawFingerprintLog();
|
void SaveDrawFingerprintLog();
|
||||||
|
void LogUserMarker();
|
||||||
|
void LogDebugMarker();
|
||||||
void ApplySelectedProfileToRuntime();
|
void ApplySelectedProfileToRuntime();
|
||||||
|
|
||||||
static bool IsRecipeNameChar(char c);
|
static bool IsRecipeNameChar(char c);
|
||||||
static std::set<std::string> DiscoverRecipeAliases(
|
static std::set<std::string> DiscoverRecipeAliases(
|
||||||
const std::filesystem::path& content_root);
|
const std::filesystem::path& content_root);
|
||||||
|
static bool CreateOverlayDirectory(const std::filesystem::path& overlay_root,
|
||||||
|
std::string_view guest_path);
|
||||||
|
|
||||||
std::filesystem::path config_path_;
|
std::filesystem::path config_path_;
|
||||||
std::filesystem::path user_settings_path_;
|
std::filesystem::path user_settings_path_;
|
||||||
|
|
@ -56,6 +56,5 @@ class Skate3BaseApp : public rex::ReXApp {
|
||||||
std::unique_ptr<rex::ui::SimpleSettingsDialog> simple_settings_dialog_;
|
std::unique_ptr<rex::ui::SimpleSettingsDialog> simple_settings_dialog_;
|
||||||
bool recipe_overlay_installed_ = false;
|
bool recipe_overlay_installed_ = false;
|
||||||
bool big_device_aliases_installed_ = false;
|
bool big_device_aliases_installed_ = false;
|
||||||
std::atomic<bool> invalidate_gpu_memory_thread_running_{false};
|
std::atomic<uint32_t> debug_marker_count_{0};
|
||||||
std::thread invalidate_gpu_memory_thread_;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
2
third_party/rexglue-sdk
vendored
2
third_party/rexglue-sdk
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 3184592404a70a4c1fcd07c8dd6f2494c4bd8b4f
|
Subproject commit 5b6b87bcea1d70b6922815890bb81adc929ea106
|
||||||
Loading…
Add table
Reference in a new issue