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:
commit
b36a366240
826 files changed
+105304
No files matched your search
@@ -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())
|
||||
Executable
+101
@@ -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"
|
||||
@@ -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])
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
Executable
+97
@@ -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
|
||||
Executable
+55
@@ -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
|
||||
@@ -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')
|
||||
@@ -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__)
|
||||
Reference in new issue
Block a user