initial commit: llamamod 0.2.0 – reverse-engineered level formats (wad, map, trak, text, stpc), python tools and notes, java mod manager (iso install, mod pipeline with exe patching, save editor, wine/windows launcher) with mods (test world, debug wireframe, mirror, randomizer, fog, cheats, intro skip, practice, free camera, boot to level, level projects) and the 3d level editor (geometry, objects, cross-world import, environment, textures, play-test, manual)

This commit is contained in:
MineTec committed 2026-11-07 08:15:40 +01:00
commit b36a366240
826 files changed
+105304

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""Dekodiert Argonaut-ASF-Musik (v2.1, 4-Bit-ADPCM) wie asfcodec.adl aus dem Spiel.
Nachgebaut aus der Disassembly von asfcodec.adl (Routinen 0x26e01969 / 0x26e01ca1):
Filter 0: s = s1 + (n << shift)
Filter 1: s = 2*s1 - s2 + (n << shift) (+ Übertrag aus 'adc', s. u.)
- kein Clipping; Ausgabe wird als 16 Bit gespeichert (Überlauf = Wrap)
- s1 läuft innerhalb eines Blocks ungekürzt im Register weiter,
am Blockanfang wird es aus dem 16-Bit-Puffer gelesen
- s2 wird immer aus dem 16-Bit-Puffer gelesen
Aufruf: asf2wav.py eingabe.asf ausgabe.wav
"""
import array
import struct
import sys
import wave
M32 = 0xFFFFFFFF
def s32(x):
x &= M32
return x - (1 << 32) if x & 0x80000000 else x
def wrap16(x):
return ((x + 0x8000) & 0xFFFF) - 0x8000
def decode(data):
off = struct.unpack_from('<I', data, 12)[0]
nblocks, _, _, rate, _, flags = struct.unpack_from('<IIIHHI', data, off)
ch = 2 if flags & 2 else 1
p = off + 20
out = array.array('h', bytes(nblocks * 32 * ch * 2))
mem = [[0, 0] for _ in range(ch)] # gespeicherte 16-Bit-Werte: [vorletztes, letztes]
o = 0
for _ in range(nblocks):
for c in range(ch):
ctl = data[p]
blk = data[p + 1:p + 17]
p += 17
cl = (ctl >> 4) + 2
prev2, prev1 = mem[c]
eax = prev1 # Blockanfang: s1 aus dem Speicher
k = o + c
for byte in blk:
for nib in (byte >> 4, byte & 15):
n = nib - 16 if nib & 8 else nib
edx = s32((n * 16) << cl)
if ctl & 4:
ebx = s32(eax << 7)
a = s32(-prev2 << 6)
carry = ((a & M32) + (edx & M32)) >> 32
eax = s32(a + edx + ebx + carry) >> 6
else:
eax = s32((eax << 6) + edx) >> 6
w = wrap16(eax)
out[k] = w
k += ch
prev2, prev1 = prev1, w
mem[c] = [prev2, prev1]
o += 32 * ch
return rate, ch, out
if __name__ == '__main__':
rate, ch, pcm = decode(open(sys.argv[1], 'rb').read())
with wave.open(sys.argv[2], 'wb') as w:
w.setnchannels(ch)
w.setsampwidth(2)
w.setframerate(rate)
w.writeframes(pcm.tobytes())
+101
View File
@@ -0,0 +1,101 @@
#!/bin/bash
# Baut ein .deb für "Ein Königreich für ein Lama" aus einer intakten CD-ISO.
# ./build.sh /pfad/zur/GROOVE.iso
# Benötigt: 7z, ffmpeg (mit argo_asf/libmp3lame), wrestool, icotool, convert,
# fakeroot, dpkg-deb
set -euo pipefail
umask 022
ISO=$(realpath "${1:?Aufruf: $0 /pfad/zur/cd.iso}")
HERE=$(cd "$(dirname "$0")" && pwd)
PKG=ein-koenigreich-fuer-ein-lama
VER=1.0-1
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
ROOT="$WORK/root"
OPT="$ROOT/opt/$PKG"
echo ">> ISO entpacken"
mkdir -p "$WORK/iso"
7z x -y -o"$WORK/iso" "$ISO" >/dev/null
SRC="$WORK/iso/Setup/Eng"
[ -f "$SRC/Hd/groove.exe" ] || { echo "groove.exe nicht gefunden – falsche ISO?" >&2; exit 1; }
echo ">> Integrität prüfen"
python3 -I - "$SRC" <<'EOF'
import sys, struct, os
src = sys.argv[1]
bad = []
fe = open(os.path.join(src, 'Cd/wads/FESOUND.WAD'), 'rb').read()
if struct.unpack('<I', fe[:4])[0] != len(fe) - 4: bad.append('FESOUND.WAD')
for f in os.listdir(os.path.join(src, 'Cd/wads')):
if f.lower().endswith('.wad') and f.upper() != 'FESOUND.WAD':
d = open(os.path.join(src, 'Cd/wads', f), 'rb').read(8)
if d[4:8] != b'OFNI': bad.append(f)
cvs = open(os.path.join(src, 'Hd/music/english.cvs'), 'rb').read()
if cvs[-4 * 1024 * 1024:].count(0) == 4 * 1024 * 1024: bad.append('english.cvs (Nullbereich am Ende)')
if bad:
sys.exit('Beschädigte Dateien: ' + ', '.join(bad))
print(' ok')
EOF
echo ">> Spieldaten"
mkdir -p "$OPT"
cp -r "$SRC/Cd" "$OPT/cd"
cp -r "$SRC/Hd" "$OPT/hd"
cp "$WORK/iso/GROOVE.HLP" "$OPT/" 2>/dev/null || true
find "$OPT" -type d -exec chmod 755 {} +
find "$OPT" -type f -exec chmod 644 {} +
echo ">> Startskript, Menüeintrag, Icon"
install -Dm755 "$HERE/files/$PKG" "$ROOT/usr/games/$PKG"
ln -s "$PKG" "$ROOT/usr/games/lama"
install -Dm644 "$HERE/files/$PKG.desktop" "$ROOT/usr/share/applications/$PKG.desktop"
wrestool -x -t 14 "$SRC/Hd/groove.exe" -o "$WORK/icon.ico"
icotool -x -o "$WORK/icon.png" "$WORK/icon.ico" 2>/dev/null
for s in 32 64 128; do
mkdir -p "$ROOT/usr/share/icons/hicolor/${s}x${s}/apps"
convert "$WORK/icon.png" -filter point -resize "${s}x${s}" \
"$ROOT/usr/share/icons/hicolor/${s}x${s}/apps/$PKG.png"
done
echo ">> Soundtrack nach MP3"
OST="$ROOT/usr/share/$PKG/soundtrack"
mkdir -p "$OST"
# Nicht FFmpegs adpcm_argo verwenden: der klemmt den Prädiktor auf 16 Bit,
# das Original lässt ihn überlaufen -> sonst massiver DC-Offset / Drift.
# asf2wav.py dekodiert wie asfcodec.adl; Hochpass 10 Hz entfernt den Rest-DC
# (steckt auch im Original), Pegel so, dass das MP3 nicht übersteuert.
mkdir -p "$WORK/wav"
ls "$SRC/Cd/music/"*.asf | xargs -P "$(nproc)" -I{} \
sh -c 'python3 -I "$0" "$1" "$2/$(basename "$1" .asf).wav"' "$HERE/asf2wav.py" {} "$WORK/wav"
peak() {
ffmpeg -nostdin -v error -i "$1" -c:a pcm_f32le -f f32le - | python3 -I -c \
'import sys,array; a=array.array("f",sys.stdin.buffer.read()); print(max(max(a),-min(a)))'
}
n=0
while IFS= read -r f; do
n=$((n + 1)); b=$(basename "$f" .wav); out="$OST/$(printf %02d "$n") - $b.mp3"
for gain in -2 -4 -6; do
ffmpeg -nostdin -hide_banner -loglevel error -y -i "$f" \
-af "highpass=f=10:poles=2,volume=${gain}dB" -c:a libmp3lame -q:a 2 \
-metadata title="$b" -metadata album="Ein Königreich für ein Lama (PC-Spiel)" \
-metadata artist="Disney Interactive / Argonaut Games" -metadata date=2000 \
-metadata track="$n" "$out"
python3 -I -c 'import sys; sys.exit(float(sys.argv[1]) > 0.99)' "$(peak "$out")" && break
done
done < <(ls "$WORK/wav/"*.wav | sort -f)
chmod 644 "$OST"/*.mp3
echo ">> Doku"
DOC="$ROOT/usr/share/doc/$PKG"
install -Dm644 "$HERE/files/README.Debian" "$DOC/README.Debian"
install -Dm644 "$HERE/files/copyright" "$DOC/copyright"
echo ">> Paket bauen"
find "$ROOT" -type d -exec chmod 755 {} +
mkdir -p "$ROOT/DEBIAN"
SIZE=$(du -sk --exclude=DEBIAN "$ROOT" | cut -f1)
sed -e "s/@VERSION@/$VER/" -e "s/@SIZE@/$SIZE/" "$HERE/files/control" > "$ROOT/DEBIAN/control"
fakeroot dpkg-deb -Zxz -z3 --build "$ROOT" "$HERE/${PKG}_${VER}_all.deb"
echo ">> Fertig: $HERE/${PKG}_${VER}_all.deb"
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""Entpackt Argonaut-.cor-Bilder (Ein Königreich für ein Lama) nach PNG.
Aufbau: 8 Byte Signatur 17 98 AF 89 FE 42 D1 12, u32 Version (1),
u32 entpackte Größe, u32 gepackte Größe, dann LZ-Daten:
n < 0x80 -> n Bytes Rohdaten folgen
1LLL oooo oooooooo -> kopiere L+3 Bytes ab o Bytes weiter vorne
Das Ergebnis sind 640x480 Pixel à 3 Byte (RGB) oder à 1 Byte (Graustufen, z. B. 151.cor).
Aufruf: cor2png.py eingabe.cor ausgabe.png (benötigt Pillow)
"""
import struct
import sys
MAGIC = bytes.fromhex('1798af89fe42d112')
def unpack(data):
if data[:8] != MAGIC:
raise ValueError('keine COR-Datei')
_, size, packed = struct.unpack_from('<III', data, 8)
src = data[20:20 + packed]
out = bytearray()
i = 0
while i < len(src) and len(out) < size:
b = src[i]
if b < 0x80:
out += src[i + 1:i + 1 + b]
i += 1 + b
else:
length = ((b >> 4) & 7) + 3
off = ((b & 0x0F) << 8) | src[i + 1]
i += 2
for _ in range(length):
out.append(out[-off])
return bytes(out), size
if __name__ == '__main__':
raw, size = unpack(open(sys.argv[1], 'rb').read())
print(f'{sys.argv[1]}: {len(raw)} von {size} Bytes')
if len(sys.argv) > 2:
from PIL import Image
mode = {640 * 480 * 3: 'RGB', 640 * 480: 'L'}[size]
Image.frombytes(mode, (640, 480), raw).save(sys.argv[2])
+35
View File
@@ -0,0 +1,35 @@
Ein Königreich für ein Lama unter Wine
======================================
Start: Menüeintrag "Ein Königreich für ein Lama" oder im Terminal `lama`.
Installierte Dateien
/opt/ein-koenigreich-fuer-ein-lama/cd Inhalt der CD (Setup/Eng/Cd), wird zu Laufwerk L:
/opt/ein-koenigreich-fuer-ein-lama/hd Programmdateien (groove.exe, Audio-DLLs, Sprache)
/usr/share/ein-koenigreich-fuer-ein-lama/soundtrack Soundtrack als MP3
Pro Benutzer (beim ersten Start angelegt)
~/.wine-lama Wine-Prefix (32 Bit)
~/.wine-lama/drive_c/Program Files/Disney Interactive/Ein Koenigreich fuer ein Lama
Programmordner, enthält die Spielstände (*.ksv)
Was der Starter bei jedem Start einrichtet
- Laufwerk L: als CD-ROM -> /opt/.../cd (das Spiel prüft dort auf 111.cor)
- HKLM\Software\Disney Interactive\Emperors New Groove\1.0: CDPath, ScreenW, ScreenH
- Wine-virtueller-Desktop "Lama" in Spielauflösung, damit das Spiel die
echte Bildschirmauflösung nicht umstellt (es kennt keinen Fenstermodus)
- Arbeitsverzeichnis = Programmordner (das Spiel speichert dort; aus einem
schreibgeschützten Verzeichnis gestartet stürzt es ab)
Fenstergröße
Standard: Breite des Hauptmonitors, Höhe = Arbeitsbereich minus Titelleiste.
Anpassen in ~/.config/ein-koenigreich-fuer-ein-lama.conf, z. B.:
WIDTH=1920
HEIGHT=1080
TITLEBAR=28
Bekannte Eigenheiten
- "ege bitte die CD ..." – das fehlende "L" ist ein Fehler im Originalspiel.
- DEFANIM.WAD wird gesucht, fehlt aber auch auf der Original-CD; harmlos.
- Zwischensequenzen sind MPEG-1 (.dat); dafür braucht Wine die 32-Bit-
GStreamer-Plugins (gstreamer1.0-plugins-ugly:i386, gstreamer1.0-libav:i386).
+19
View File
@@ -0,0 +1,19 @@
Package: ein-koenigreich-fuer-ein-lama
Version: @VERSION@
Section: games
Priority: optional
Architecture: all
Depends: wine (>= 6.0), wine32:i386, x11-xserver-utils, x11-utils
Recommends: gstreamer1.0-plugins-ugly:i386, gstreamer1.0-libav:i386
Installed-Size: @SIZE@
Maintainer: Elias Müller <elias@elias-mueller.com>
Description: Ein Königreich für ein Lama (Disney/Argonaut, PC 2000) für Wine
Deutsche PC-Version von Disneys "Ein Königreich für ein Lama"
(The Emperor's New Groove), entwickelt von Argonaut Games.
.
Enthält die vollständigen Spieldaten der CD, einen Starter, der ein
eigenes 32-Bit-Wine-Prefix einrichtet (CD als Laufwerk L:, Registry,
virtueller Desktop passend zur Bildschirmgröße), einen Menüeintrag
sowie den Soundtrack als MP3.
.
Nur für den privaten Gebrauch mit eigener Original-CD.
+8
View File
@@ -0,0 +1,8 @@
Ein Königreich für ein Lama / The Emperor's New Groove (PC)
Copyright (C) 2000 Disney Interactive; entwickelt von Argonaut Games PLC.
Proprietäre Software. Dieses Paket enthält die Daten der Original-CD und ist
ausschließlich für den privaten Gebrauch durch Besitzer der Original-CD
bestimmt. Weitergabe nicht gestattet.
Startskript und Paketierung: Elias Müller, 2026.
+99
View File
@@ -0,0 +1,99 @@
#!/bin/sh
# Startet "Ein Königreich für ein Lama" (Disney/Argonaut, 2000) unter Wine.
#
# Beim ersten Start wird ein eigenes 32-Bit-Wine-Prefix angelegt. Bei jedem
# Start werden Laufwerk L: (Spiel-CD), Registry und Fenstergröße gesetzt.
#
# Einstellungen (optional) in ~/.config/ein-koenigreich-fuer-ein-lama.conf:
# WIDTH=1920 # Fensterbreite (Standard: Breite des Hauptmonitors)
# HEIGHT=1080 # Fensterhöhe (Standard: Arbeitsbereich - Titelleiste)
# TITLEBAR=28 # Höhe der Fenster-Titelleiste in Pixeln
# WINEPREFIX=... # anderes Prefix (Standard: ~/.wine-lama)
set -e
DATA=/opt/ein-koenigreich-fuer-ein-lama
CONF="${XDG_CONFIG_HOME:-$HOME/.config}/ein-koenigreich-fuer-ein-lama.conf"
TITLEBAR=28
WIDTH=
HEIGHT=
[ -f "$CONF" ] && . "$CONF"
export WINEPREFIX="${WINEPREFIX:-$HOME/.wine-lama}"
export WINEARCH=win32
export WINEDEBUG="${WINEDEBUG:--all}"
GAMEDIR="$WINEPREFIX/drive_c/Program Files/Disney Interactive/Ein Koenigreich fuer ein Lama"
WINGAME='C:\Program Files\Disney Interactive\Ein Koenigreich fuer ein Lama'
# --- Prefix anlegen --------------------------------------------------------
if [ ! -f "$WINEPREFIX/system.reg" ]; then
echo "Lege Wine-Prefix in $WINEPREFIX an ..."
wineboot -i >/dev/null 2>&1
wineserver -w
fi
# --- Programmdateien (Festplatten-Teil der Installation) -------------------
# Programmdateien werden verlinkt; Spielstände (*.ksv) werden kopiert, da das
# Spiel sie im Programmverzeichnis überschreibt.
mkdir -p "$GAMEDIR/music"
for f in "$DATA"/hd/* "$DATA"/hd/music/*; do
[ -f "$f" ] || continue
rel="${f#"$DATA"/hd/}"
case "$f" in
*.ksv) [ -e "$GAMEDIR/$rel" ] || { cp "$f" "$GAMEDIR/$rel"; chmod u+w "$GAMEDIR/$rel"; } ;;
*) ln -sfn "$f" "$GAMEDIR/$rel" ;;
esac
done
# --- Spiel-CD als Laufwerk L: ----------------------------------------------
# Mit aktiven Mods (lama-mod an) zeigt L: auf das Overlay.
if [ -d "$WINEPREFIX/cd-overlay" ]; then
ln -sfn "$WINEPREFIX/cd-overlay" "$WINEPREFIX/dosdevices/l:"
else
ln -sfn "$DATA/cd" "$WINEPREFIX/dosdevices/l:"
fi
# --- Fenstergröße bestimmen ------------------------------------------------
if [ -z "$WIDTH" ] || [ -z "$HEIGHT" ]; then
geo=$(xrandr --current 2>/dev/null | awk '
/ connected/ { for (i = 1; i <= NF; i++) if ($i ~ /^[0-9]+x[0-9]+\+/) {
if ($0 ~ / primary /) { print $i; found = 1; exit }
if (!first) first = $i } }
END { if (!found && first) print first }')
mw=${geo%%x*}
mh=${geo#*x}; mh=${mh%%+*}
wa=$(xprop -root _NET_WORKAREA 2>/dev/null | sed -n 's/.*= *//p' | cut -d, -f4 | tr -d ' ')
case "$mw$mh" in ''|*[!0-9]*) mw=1024; mh=768 ;; esac
h=$mh
case "$wa" in ''|*[!0-9]*) ;; *) [ "$wa" -lt "$h" ] && h=$wa ;; esac
h=$((h - TITLEBAR))
: "${WIDTH:=$mw}"
: "${HEIGHT:=$h}"
fi
# --- Registry --------------------------------------------------------------
REG=$(mktemp --suffix=.reg)
trap 'rm -f "$REG"' EXIT
cat > "$REG" <<EOF
REGEDIT4
[HKEY_LOCAL_MACHINE\\Software\\Wine\\Drives]
"l:"="cdrom"
[HKEY_LOCAL_MACHINE\\Software\\Disney Interactive\\Emperors New Groove\\1.0]
"CDPath"="L:\\\\"
"ScreenW"=dword:$(printf %08x "$WIDTH")
"ScreenH"=dword:$(printf %08x "$HEIGHT")
[HKEY_CURRENT_USER\\Software\\Wine\\Explorer]
"Desktop"="Lama"
[HKEY_CURRENT_USER\\Software\\Wine\\Explorer\\Desktops]
"Lama"="${WIDTH}x${HEIGHT}"
EOF
wine regedit "$REG" >/dev/null 2>&1
# --- Start -----------------------------------------------------------------
# Das Spiel legt Spielstände im aktuellen Verzeichnis an.
cd "$GAMEDIR"
wine "$WINGAME\\groove.exe" "$@"
@@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Name=Ein Königreich für ein Lama
GenericName=Jump'n'Run
Comment=Disneys Ein Königreich für ein Lama (2000) unter Wine
Exec=/usr/games/ein-koenigreich-fuer-ein-lama
Icon=ein-koenigreich-fuer-ein-lama
Terminal=false
Categories=Game;ActionGame;
StartupWMClass=explorer.exe
+97
View File
@@ -0,0 +1,97 @@
#!/bin/sh
# Mods für "Ein Königreich für ein Lama" ein- und ausschalten.
#
# lama-mod an Mods aus ~/.wine-lama/mods/*/ aktivieren
# lama-mod aus Originalzustand (L: = unveränderte CD-Daten)
# lama-mod status
# lama-mod debug an Debug-Ansicht (Kollision/unsichtbare Objekte) zuschalten
# lama-mod debug aus Debug-Ansicht abschalten (andere Mods bleiben, wie sie sind)
#
# Eine Mod ist ein Ordner, dessen Dateien so liegen wie auf der CD unter
# Setup/Eng/Cd (z. B. wads/T1L9M001.wad, T191.COR, movies/191INTRO.DAT).
# Aktiviert wird über ein Overlay aus Symlinks: Originaldateien bleiben
# unangetastet, Mod-Dateien kommen dazu bzw. ersetzen gleichnamige.
# Mods mit einer Datei ".aus" im Ordner werden übersprungen (so schaltet
# "lama-mod debug an|aus" die Mod "debug" unabhängig von den anderen).
# Reihenfolge: alphabetisch, spätere Mods gewinnen (testlevel > debug).
set -e
export WINEPREFIX="${WINEPREFIX:-$HOME/.wine-lama}"
MODS="$WINEPREFIX/mods"
OVL="$WINEPREFIX/cd-overlay"
DBG="$MODS/debug"
if [ -d /opt/ein-koenigreich-fuer-ein-lama/cd ]; then
BASE=/opt/ein-koenigreich-fuer-ein-lama/cd
else
BASE="$WINEPREFIX/iso-dk/Setup/Eng/Cd"
fi
overlay_an() {
rm -rf "$OVL"
mkdir -p "$OVL"
(cd "$BASE" && find . -type d) | while IFS= read -r d; do mkdir -p "$OVL/$d"; done
(cd "$BASE" && find . -type f) | while IFS= read -r f; do ln -s "$BASE/$f" "$OVL/$f"; done
for m in "$MODS"/*/; do
[ -d "$m" ] || continue
if [ -e "$m/.aus" ]; then
echo "Mod: $(basename "$m") (abgeschaltet)"
continue
fi
echo "Mod: $(basename "$m")"
(cd "$m" && find . -path ./src -prune -o -name .aus -prune -o \( -type f -o -type l \) -print) | while IFS= read -r f; do
# vorhandene Datei gleichen Namens (Groß-/Kleinschreibung egal) ersetzen
dir=$(dirname "$f"); name=$(basename "$f")
mkdir -p "$OVL/$dir"
find "$OVL/$dir" -maxdepth 1 -iname "$name" -exec rm -f {} +
ln -s "$m/$f" "$OVL/$f"
[ -n "$LEISE" ] || echo " + $f"
done
done
ln -sfn "$OVL" "$WINEPREFIX/dosdevices/l:"
echo "Mods aktiv (L: -> $OVL)"
}
case "${1:-status}" in
an|on)
overlay_an
;;
aus|off)
ln -sfn "$BASE" "$WINEPREFIX/dosdevices/l:"
rm -rf "$OVL"
echo "Originalzustand (L: -> $BASE)"
;;
debug)
if [ ! -d "$DBG/wads" ]; then
echo "Debug-Mod fehlt ($DBG/wads)." >&2; exit 1
fi
case "${2:-status}" in
an|on)
rm -f "$DBG/.aus"
LEISE=1 overlay_an
echo "Debug-Ansicht AN ($(ls "$DBG/wads" | wc -l) Level). Wirkt beim nächsten Laden eines Levels."
;;
aus|off)
touch "$DBG/.aus"
if [ "$(readlink "$WINEPREFIX/dosdevices/l:")" = "$OVL" ]; then
LEISE=1 overlay_an
fi
echo "Debug-Ansicht AUS."
;;
status)
if [ -e "$DBG/.aus" ]; then echo "Debug-Ansicht: aus"; else echo "Debug-Ansicht: an (wenn Mods aktiv)"; fi
;;
*)
sed -n '2,9p' "$0"; exit 1 ;;
esac
;;
status)
echo "L: -> $(readlink "$WINEPREFIX/dosdevices/l:")"
for m in "$MODS"/*/; do
[ -d "$m" ] || continue
if [ -e "$m/.aus" ]; then s=" (abgeschaltet)"; else s=""; fi
echo "installierte Mod: $(basename "$m")$s"
done
;;
*)
sed -n '2,9p' "$0"; exit 1 ;;
esac
+55
View File
@@ -0,0 +1,55 @@
#!/bin/sh
# Mods für "Ein Königreich für ein Lama" ein- und ausschalten.
#
# lama-mod an Mods aus ~/.wine-lama/mods/*/ aktivieren
# lama-mod aus Originalzustand (L: = unveränderte CD-Daten)
# lama-mod status
#
# Eine Mod ist ein Ordner, dessen Dateien so liegen wie auf der CD unter
# Setup/Eng/Cd (z. B. wads/T1L9M001.wad, T191.COR, movies/191INTRO.DAT).
# Aktiviert wird über ein Overlay aus Symlinks: Originaldateien bleiben
# unangetastet, Mod-Dateien kommen dazu bzw. ersetzen gleichnamige.
set -e
export WINEPREFIX="${WINEPREFIX:-$HOME/.wine-lama}"
MODS="$WINEPREFIX/mods"
OVL="$WINEPREFIX/cd-overlay"
if [ -d /opt/ein-koenigreich-fuer-ein-lama/cd ]; then
BASE=/opt/ein-koenigreich-fuer-ein-lama/cd
else
BASE="$WINEPREFIX/iso-dk/Setup/Eng/Cd"
fi
case "${1:-status}" in
an|on)
rm -rf "$OVL"
mkdir -p "$OVL"
(cd "$BASE" && find . -type d) | while IFS= read -r d; do mkdir -p "$OVL/$d"; done
(cd "$BASE" && find . -type f) | while IFS= read -r f; do ln -s "$BASE/$f" "$OVL/$f"; done
for m in "$MODS"/*/; do
[ -d "$m" ] || continue
echo "Mod: $(basename "$m")"
(cd "$m" && find . -path ./src -prune -o \( -type f -o -type l \) -print) | while IFS= read -r f; do
# vorhandene Datei gleichen Namens (Groß-/Kleinschreibung egal) ersetzen
dir=$(dirname "$f"); name=$(basename "$f")
mkdir -p "$OVL/$dir"
find "$OVL/$dir" -maxdepth 1 -iname "$name" -exec rm -f {} +
ln -s "$m/$f" "$OVL/$f"
echo " + $f"
done
done
ln -sfn "$OVL" "$WINEPREFIX/dosdevices/l:"
echo "Mods aktiv (L: -> $OVL)"
;;
aus|off)
ln -sfn "$BASE" "$WINEPREFIX/dosdevices/l:"
rm -rf "$OVL"
echo "Originalzustand (L: -> $BASE)"
;;
status)
echo "L: -> $(readlink "$WINEPREFIX/dosdevices/l:")"
ls -1 "$MODS" 2>/dev/null | sed 's/^/installierte Mod: /'
;;
*)
sed -n '2,12p' "$0"; exit 1 ;;
esac
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""Packt ein Bild als Argonaut-.cor (Gegenstück zu cor2png.py).
Das Bild wird auf 640x480 RGB gebracht und mit dem LZ-Schema des Spiels
komprimiert:
n < 0x80 -> n Bytes Rohdaten folgen (n = 1..127)
1LLL oooo oooooooo -> kopiere L+3 Bytes (3..10) ab o Bytes (1..4095) zurück
Aufruf: png2cor.py eingabe.png ausgabe.cor (benötigt Pillow)
"""
import struct
import sys
MAGIC = bytes.fromhex('1798af89fe42d112')
MAXOFF, MINLEN, MAXLEN = 0x0FFF, 3, 10
def pack(raw):
out = bytearray()
lit = bytearray()
last = {} # 3-Byte-Präfix -> Liste letzter Positionen
def flush():
for k in range(0, len(lit), 127):
part = lit[k:k + 127]
out.append(len(part))
out.extend(part)
lit.clear()
i, n = 0, len(raw)
while i < n:
best_len, best_off = 0, 0
if i + MINLEN <= n:
key = raw[i:i + 3]
for p in reversed(last.get(key, ())):
off = i - p
if off > MAXOFF:
break
ln = 3
while ln < MAXLEN and i + ln < n and raw[p + ln] == raw[i + ln]:
ln += 1
if ln > best_len:
best_len, best_off = ln, off
if ln == MAXLEN:
break
step = best_len if best_len >= MINLEN else 1
for j in range(i, min(i + step, n - 2)):
lst = last.setdefault(raw[j:j + 3], [])
lst.append(j)
if len(lst) > 16:
del lst[0]
if best_len >= MINLEN:
flush()
out.append(0x80 | ((best_len - 3) << 4) | (best_off >> 8))
out.append(best_off & 0xFF)
i += best_len
else:
lit.append(raw[i])
i += 1
flush()
return MAGIC + struct.pack('<III', 1, len(raw), len(out)) + bytes(out)
if __name__ == '__main__':
from PIL import Image
img = Image.open(sys.argv[1]).convert('RGB').resize((640, 480), Image.LANCZOS)
data = pack(img.tobytes())
open(sys.argv[2], 'wb').write(data)
print(f'{sys.argv[2]}: {len(data)} Bytes')
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""Liest und schreibt Level-WADs von "Ein Königreich für ein Lama" (Argonaut).
Aufbau: u32 Gesamtlänge (ohne dieses Feld), dann Chunks:
4 Byte Kennung (rückwärts gespeichert, z. B. 'OFNI' = INFO), u32 Länge, Daten
bis zum Chunk 'END '.
wadtool.py list level.wad
wadtool.py name level.wad neu.wad "Neuer Levelname"
wadtool.py extract level.wad CHUNK datei.bin
wadtool.py replace level.wad neu.wad CHUNK datei.bin
"""
import struct
import sys
def read(path):
d = open(path, 'rb').read()
total = struct.unpack_from('<I', d, 0)[0]
if total != len(d) - 4:
raise ValueError(f'{path}: Längenfeld {total} passt nicht zu {len(d) - 4}')
chunks, o = [], 4
while o + 8 <= len(d):
tag = d[o:o + 4][::-1].decode('latin1')
ln = struct.unpack_from('<I', d, o + 4)[0]
chunks.append([tag, d[o + 8:o + 8 + ln]])
o += 8 + ln
if tag == 'END ':
break
return chunks
def write(path, chunks):
body = b''.join(t.encode('latin1')[::-1] + struct.pack('<I', len(c)) + c for t, c in chunks)
open(path, 'wb').write(struct.pack('<I', len(body)) + body)
def get(chunks, tag):
for c in chunks:
if c[0] == tag:
return c
raise KeyError(tag)
if __name__ == '__main__':
cmd, src = sys.argv[1], sys.argv[2]
ch = read(src)
if cmd == 'list':
for t, c in ch:
extra = ''
if t == 'NAME':
extra = repr(c[4:].split(b'\0')[0].decode('latin1'))
print(f'{t!r:8} {len(c):10d} {extra}')
elif cmd == 'name':
c = get(ch, 'NAME')
c[1] = c[1][:4] + sys.argv[4].encode('latin1') + b'\0'
write(sys.argv[3], ch)
elif cmd == 'extract':
open(sys.argv[4], 'wb').write(get(ch, sys.argv[3].ljust(4))[1])
elif cmd == 'replace':
get(ch, sys.argv[4].ljust(4))[1] = open(sys.argv[5], 'rb').read()
write(sys.argv[3], ch)
else:
sys.exit(__doc__)