- Billiards: add native Russian Pyramid & American Eight-Ball pool with physics prediction, controls, and regression tests - Scratch Tickets: add dedicated counter, mat presentation, elliptical coin scraping, 4 ticket types, and payout effects - First-Person Dice: add native dice scene, hand anchor frames, throwing animation, and table perspective - City Wake & Weather: add arrival cutscene, alley geometry, weather/rain effects, and lighter/rocker animation - World Dressing: add hall paintings, furniture packs, hall lighting, and scene regression tests - Build & Tooling: update build.bat for modern MSVC toolchain, update launcher hash, and refresh shipping binaries
82 lines
4.1 KiB
Python
82 lines
4.1 KiB
Python
"""Verify the native/legacy file mappings and Windows MCI decoders without playing audio."""
|
|
import ctypes
|
|
from pathlib import Path
|
|
import re
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def asm_paths():
|
|
for file in [ROOT / "src_graphics/audio_data.inc", ROOT / "src_graphics/casino_actions_data.inc",
|
|
ROOT / "src_graphics/scratch_data.inc"]:
|
|
definitions = {}
|
|
current = None
|
|
for line in file.read_text(encoding="ascii").splitlines():
|
|
match = re.match(r"(\w+) db (.+)$", line)
|
|
continuation = re.match(r"\s+db (.+)$", line)
|
|
if match:
|
|
current = match[1]
|
|
definitions[current] = match[2]
|
|
elif continuation and current:
|
|
definitions[current] += "," + continuation[1]
|
|
else:
|
|
current = None
|
|
for symbol, payload in definitions.items():
|
|
chunks = re.findall(r'"[^"]*"|\d+', payload)
|
|
raw = b"".join(c[1:-1].encode("ascii") if c.startswith('"') else bytes([int(c)]) for c in chunks)
|
|
name = raw.rstrip(b"\0").decode("cp1251")
|
|
if name.endswith(".wav"):
|
|
yield symbol, name
|
|
|
|
|
|
def main():
|
|
native = dict(asm_paths())
|
|
assert native["soundCards"].endswith("playing_cards_deck_flick_through_001_20485.wav")
|
|
assert native["soundCoin"].endswith("Звук монеты брошенной в воздух и упавшей на поверхность.wav")
|
|
assert native["soundRoulette"].endswith("roulette-wheel-throw-2.wav")
|
|
assert native["rrDrySound3D"].endswith("revolverdryfirec.wav")
|
|
assert native["rrFireSound3D"].endswith("b05e1ac8be34248.wav")
|
|
assert native["soundClick"] != native["rrDrySound3D"]
|
|
assert native["soundDiceRoll"] != native["soundCards"]
|
|
assert len({native[name] for name in ("scrScrapeSound3D","scrPaperSound3D","scrPayoutSound3D")})==3
|
|
engine = (ROOT / "casino3d.inc").read_text(encoding="cp1251")
|
|
assert "invoke PlaySoundA, OFFSET sound" not in engine
|
|
if "LaunchClassicGame PROC" in engine:
|
|
assert "call InitSounds" in engine.split("run_game_body:")[1].split("LaunchClassicGame ENDP")[0]
|
|
else:
|
|
# Native-only builds intentionally removed the terminal launch path.
|
|
assert "run_game_body:" not in engine
|
|
assert "invoke PlayCasinoSound3D, OFFSET soundDiceRoll" in engine
|
|
audio = (ROOT / "src_graphics/audio_code.inc").read_text(encoding="ascii")
|
|
assert "invoke PlaySoundA, ebx, 0, eax" in audio
|
|
assert "20003h" in audio # SND_FILENAME | SND_NODEFAULT | SND_ASYNC
|
|
legacy = (ROOT / "poker.asm").read_text(encoding="cp1251")
|
|
recordings = set(native.values())
|
|
recordings.update(re.findall(r'open "(assets\\audio\\[^"\r\n]+\.wav)" type waveaudio', legacy))
|
|
checked = 0
|
|
for name in sorted(recordings):
|
|
if name.endswith("gman-wakeup.wav") and not (ROOT / name).exists():
|
|
continue # Optional future voice-over.
|
|
path = ROOT / name
|
|
assert path.is_file(), f"Missing audio: {name}"
|
|
command = f'open "{path}" type waveaudio alias audiotest'
|
|
error = ctypes.windll.winmm.mciSendStringW(command, None, 0, None)
|
|
assert error == 0, f"MCI cannot open {name}: {error}"
|
|
try:
|
|
error = ctypes.windll.winmm.mciSendStringW("set audiotest time format milliseconds", None, 0, None)
|
|
assert error == 0
|
|
length = ctypes.create_unicode_buffer(64)
|
|
error = ctypes.windll.winmm.mciSendStringW("status audiotest length", length, 64, None)
|
|
assert error == 0 and int(length.value) > 0
|
|
finally:
|
|
ctypes.windll.winmm.mciSendStringW("close audiotest", None, 0, None)
|
|
if ctypes.windll.kernel32.GetACP() == 1251:
|
|
error = ctypes.windll.winmm.mciSendStringA(command.encode("cp1251"), None, 0, None)
|
|
assert error == 0, f"MCI A/CP1251 cannot open {name}: {error}"
|
|
ctypes.windll.winmm.mciSendStringA(b"close audiotest", None, 0, None)
|
|
checked += 1
|
|
print(f"PASS: {checked} WAV files decoded; native and legacy mappings verified.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|