Esta sección te permite ver todos los posts escritos por este usuario. Ten en cuenta que sólo puedes ver los posts escritos en zonas a las que tienes acceso en este momento.
Mensajes - marianolloret
1
« en: Julio 22, 2026, 16:53:43 pm »
Estoy pensando en si sería posible desensamblar el juego mediante una herramienta de IA ... que generara un fichero en lenguaje C con el código contextualizado y bien documentado.
2
« en: Julio 22, 2026, 16:26:21 pm »
Como cada verano, he vuelto a engancharme a este juego. Esta vez, aprovechando el auge de la inteligencia artificial, me pregunté si podría usarla para modificarlo. Después de unas cuantas pruebas, puedo decir que algo he conseguido. Le pregunté a Gemini si existía algún sitio web en el que se explicara técnicamente el funcionamiento interno del juego, y me recomendó la siguiente página: https://moddingwiki.shikadi.net/wiki/Kings_of_the_Beach_PAK_FormatEn ella se documenta con detalle la estructura interna de los archivos .PAK, explicando el sistema de compresión RLE utilizado por el juego, el formato de las cabeceras y la organización de los datos una vez descomprimidos. Además, describe cómo se representan las imágenes en formato EGA de 4 bits por planos, el proceso necesario para reconstruirlas correctamente y las particularidades de algunos archivos parciales utilizados para superponer gráficos sobre otros. La documentación también incluye ejemplos de la estructura binaria y herramientas compatibles para visualizar, convertir y modificar estos archivos, convirtiéndose en una referencia imprescindible para cualquiera que quiera analizar o modificar los recursos gráficos de Kings of the Beach. Pues bien, con esto le pedí a Claude si podía crear un script en Python para convertir los ficheros de PAK a PNG y para mi sorpresa lo hizo en menos de un minuto: #!/usr/bin/env python3 """ kotb_pak_converter.py ======================
Convierte entre el formato de imagen .PAK del juego "Kings of the Beach" (EGA, 4 bits, planar, comprimido con RLE de tipo "code-based") y PNG.
Uso: # PAK -> PNG python3 kotb_pak_converter.py pak2png ENTRADA.PAK SALIDA.png
# PNG -> PAK python3 kotb_pak_converter.py png2pak ENTRADA.png SALIDA.PAK [--height N] [--no-partial]
# Autodetectar dirección según la extensión de los ficheros python3 kotb_pak_converter.py convert ENTRADA SALIDA
Notas: - La paleta usada es la paleta EGA estándar de 16 colores. - Al convertir PNG -> PAK, los colores de la imagen se ajustan (nearest color) a la paleta EGA de 16 colores más cercana. - El ancho de la imagen debe ser múltiplo de 8 (requisito del formato). - Si la imagen PNG tiene un bloque de líneas totalmente negras (índice de color 0) al final, se codifica como "archivo parcial" (igual que hacen los ficheros de canchas del juego), salvo que se use --no-partial. """
import argparse import struct import sys from pathlib import Path
try: from PIL import Image except ImportError: sys.exit("Este script necesita Pillow. Instálalo con: pip install Pillow")
# --------------------------------------------------------------------------- # Paleta EGA estándar (16 colores) # --------------------------------------------------------------------------- EGA_PALETTE = [ (0, 0, 0), (0, 0, 170), (0, 170, 0), (0, 170, 170), (170, 0, 0), (170, 0, 170), (170, 85, 0), (170, 170, 170), (85, 85, 85), (85, 85, 255), (85, 255, 85), (85, 255, 255), (255, 85, 85), (255, 85, 255), (255, 255, 85), (255, 255, 255), ]
# --------------------------------------------------------------------------- # RLE "code-based" (bit alto = copiar literal / bit alto apagado = repetir) # --------------------------------------------------------------------------- def rle_decompress(comp: bytes) -> bytes: out = bytearray() i = 0 n = len(comp) while i < n: code = comp i += 1 if code & 0x80: amt = code & 0x7F out.extend(comp[i:i + amt]) i += amt else: amt = code if amt == 0: # Código sin uso práctico (repetir 0 veces); lo saltamos. continue val = comp i += 1 out.extend([val] * amt) return bytes(out)
def rle_compress(data: bytes) -> bytes: out = bytearray() literal_buf = bytearray()
def flush_literal(): pos = 0 while pos < len(literal_buf): chunk = literal_buf[pos:pos + 127] out.append(0x80 | len(chunk)) out.extend(chunk) pos += len(chunk) literal_buf.clear()
i = 0 n = len(data) while i < n: run = 1 while i + run < n and data[i + run] == data and run < 127: run += 1 if run >= 2: flush_literal() out.append(run) # bit alto apagado => Repeat out.append(data) i += run else: literal_buf.append(data) i += 1 flush_literal() return bytes(out)
# --------------------------------------------------------------------------- # PAK -> PNG # --------------------------------------------------------------------------- def pak_to_png(pak_path: str, png_path: str) -> None: raw = Path(pak_path).read_bytes() if len(raw) < 4: raise ValueError("Fichero PAK demasiado pequeño / inválido")
declen = raw[-2] | (raw[-1] <<  comp = raw[:-2]
decompressed = rle_decompress(comp) if len(decompressed) != declen: print(f"Aviso: longitud descomprimida ({len(decompressed)}) " f"no coincide con la esperada ({declen})", file=sys.stderr)
bytewidth = decompressed[0] height = decompressed[1] imgdata = decompressed[2:]
width = bytewidth * 8 lines_available = len(imgdata) // (bytewidth * 4) if lines_available < height: print(f"Info: fichero parcial -> {lines_available} de {height} líneas " f"presentes (el resto se rellena en negro)", file=sys.stderr)
img = Image.new("RGB", (width, height), EGA_PALETTE[0]) pixels = img.load()
for line in range(lines_available): base = line * bytewidth * 4 planes = [ imgdata[base + p * bytewidth: base + (p + 1) * bytewidth] for p in range(4) ] for xb in range(bytewidth): b0, b1, b2, b3 = (planes[0][xb], planes[1][xb], planes[2][xb], planes[3][xb]) for bit in range( : shift = 7 - bit color_index = ( ((b0 >> shift) & 1) | (((b1 >> shift) & 1) << 1) | (((b2 >> shift) & 1) << 2) | (((b3 >> shift) & 1) << 3) ) x = xb * 8 + bit pixels[x, line] = EGA_PALETTE[color_index]
img.save(png_path) print(f"OK: {pak_path} -> {png_path} ({width}x{height}, " f"{lines_available} líneas de datos reales)")
# --------------------------------------------------------------------------- # PNG -> PAK # --------------------------------------------------------------------------- def _nearest_ega_index(rgb): r, g, b = rgb[:3] best_i, best_d = 0, None for i, (pr, pg, pb) in enumerate(EGA_PALETTE): d = (r - pr) ** 2 + (g - pg) ** 2 + (b - pb) ** 2 if best_d is None or d < best_d: best_d, best_i = d, i return best_i
def png_to_pak(png_path: str, pak_path: str, height: int = None, allow_partial: bool = True) -> None: img = Image.open(png_path).convert("RGB") width, img_height = img.size
if width % 8 != 0: raise ValueError(f"El ancho ({width}) debe ser múltiplo de 8") if width > 320 or img_height > 200: raise ValueError("El tamaño máximo soportado es 320x200")
bytewidth = width // 8 full_height = height if height is not None else img_height if img_height > full_height: raise ValueError("La imagen es más alta que la 'Height' indicada")
pixels = img.load()
# Índice de color EGA (0-15) por cada píxel indices = [ [_nearest_ega_index(pixels[x, y]) for x in range(width)] for y in range(img_height) ] # Rellenamos hasta full_height con líneas negras (índice 0) si hiciera falta for _ in range(full_height - img_height): indices.append([0] * width)
# Detectar líneas negras finales para codificar como fichero "parcial" lines_to_store = full_height if allow_partial: while lines_to_store > 0 and all(v == 0 for v in indices[lines_to_store - 1]): lines_to_store -= 1 if lines_to_store == 0: lines_to_store = full_height # imagen totalmente negra: guardar completa
imgdata = bytearray() for line in range(lines_to_store): row = indices[line] planes = [bytearray(bytewidth) for _ in range(4)] for xb in range(bytewidth): for bit in range( : x = xb * 8 + bit val = row shift = 7 - bit for p in range(4): if (val >> p) & 1: planes[p][xb] |= (1 << shift) for p in range(4): imgdata.extend(planes[p])
decompressed = bytes([bytewidth, full_height]) + bytes(imgdata) declen = len(decompressed) if declen > 0xFFFF: raise ValueError("Los datos descomprimidos exceden 65535 bytes")
compressed = rle_compress(decompressed)
out = compressed + struct.pack("<H", declen) Path(pak_path).write_bytes(out)
partial_note = (f" (parcial: {lines_to_store}/{full_height} líneas)" if lines_to_store < full_height else "") print(f"OK: {png_path} -> {pak_path} ({width}x{full_height}){partial_note}")
# --------------------------------------------------------------------------- # CLI # --------------------------------------------------------------------------- def main(): parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) sub = parser.add_subparsers(dest="mode", required=True)
p1 = sub.add_parser("pak2png", help="Convierte un fichero .PAK a .png") p1.add_argument("input") p1.add_argument("output")
p2 = sub.add_parser("png2pak", help="Convierte un fichero .png a .PAK") p2.add_argument("input") p2.add_argument("output") p2.add_argument("--height", type=int, default=None, help="Alto declarado en la cabecera (por defecto, el alto real del PNG)") p2.add_argument("--no-partial", action="store_true", help="Fuerza la codificación completa, sin recortar líneas negras finales")
p3 = sub.add_parser("convert", help="Autodetecta la dirección según la extensión") p3.add_argument("input") p3.add_argument("output") p3.add_argument("--height", type=int, default=None) p3.add_argument("--no-partial", action="store_true")
args = parser.parse_args()
if args.mode == "pak2png": pak_to_png(args.input, args.output) elif args.mode == "png2pak": png_to_pak(args.input, args.output, height=args.height, allow_partial=not args.no_partial) elif args.mode == "convert": in_ext = Path(args.input).suffix.lower() out_ext = Path(args.output).suffix.lower() if in_ext == ".pak" and out_ext == ".png": pak_to_png(args.input, args.output) elif in_ext == ".png" and out_ext == ".pak": png_to_pak(args.input, args.output, height=args.height, allow_partial=not args.no_partial) else: sys.exit("No se pudo determinar la dirección de conversión " "a partir de las extensiones de fichero.")
if __name__ == "__main__": main() |
ahora se ha abierto la posibilidad de modificar los gráficos del juego. PD: Para mi sorpresa, Claude me sugirió que si le indicaba los canvios que quería hacer en el fichero PAK intentaría realizarlos él sin la necesidad de convertir el fichero a PNG y luego de nuevo a PAK
3
« en: Septiembre 04, 2022, 20:01:07 pm »
¡Muchas gracias!
4
« en: Agosto 21, 2022, 12:08:12 pm »
Gracias!
5
« en: Agosto 21, 2022, 00:27:38 am »
6
« en: Agosto 18, 2022, 16:51:28 pm »
Hola!
El juego funciona bien en el DOSBox, cuando llegas a la pantalla de Obelix comiendo el jabali hay que esperar unos segundos y aparece una flecha que la movemos con el teclado y seleccionamos a alguno de los personajes que aparece en pantalla (por ejemplo Asterix).
Que estúpido soy ... no me había dado cuenta, muchas gracias!!!
7
« en: Agosto 17, 2022, 15:22:08 pm »
Buenas,
¿Alguien ha probado el juego? A mi con DOSBox se me acaba colgando en la tercera pantalla de presentación (con la animación de comer un jabalí).
¿Qué hago mal?
8
« en: Agosto 16, 2022, 10:59:56 am »
El enlace de onedrive sigue siendo válido y están los archivos
Tienes razón, estaba viendo el hilo con el navegador Firefox y no se construye bien, con Chrome sí y he podido encontrar el enlace. Gracias por vuestra ayuda!
9
« en: Agosto 11, 2022, 22:40:31 pm »
Creo que los enlaces estan rotos, ¿alguien lo pude volver a colgar? El CD y el manual si es posible.
Gracias por vuestra atención.
10
« en: Abril 06, 2022, 23:53:01 pm »
De los remakes que comentas: Hoy en día, el segundo es más interesante que el primero.
11
« en: Agosto 13, 2021, 10:32:24 am »
Recuerdo que hace mucho tiempo un conocido mío tuvo este juego, el original, y estava compuesto por dos dicos, el de programa y el de escenarios.
¿Estava versión de un disco es una revisión/redición?
12
« en: Julio 29, 2021, 10:12:19 am »
Encontre una guía del juego: https://gamefaqs.gamespot.com/pc/926297-kings-of-the-beach/faqs/35263contacté con el autor y me ha indicado que no existe una combinación de teclas para el dink, la mayoría de las veces se realiza al azar, pero siempre se realiza si ambos oponentes están cerca de ti debajo de la red. También he descubierto que el 'dink' en la versión de NES se denomina 'The Feint' y es muy facil de hacer.
14
« en: Julio 04, 2021, 19:58:51 pm »
Los juegos empaquetados en un zip suelen estar alterados: preconfigurados y con registros de tiempos o records.
Estoy de acuerdo contigo, lo mejor con diferencia son los ficheros con las imágenes de los discos. Es la manera de poder realizar una instalación con los ficheros originales.
Respecto el juego Lemmings, las imágenes de los discos no funcionan, pero hay un versión cd que incluye los dos primeros juegos que creo que permite instalarlo.
Rick Dangerous 1 y 2 creo que los podrías encontrar en "The Good Old Days IBM PC Floppy Collection" o "Floppy Disks of Software" de Internet Archive, de todos modos creo que los tengo.
15
« en: Julio 04, 2021, 17:02:21 pm »
Una duda sobre un aspecto del juego que no consigo resolver. Me he dado cuenta que cuando estás atacando, si optas por hacer un 'spike' (tecla L), en ocasiones en lugar de rematar (spike) le das un toque a la pelota (dink) que despista al rival. En el manual ( https://www.retrogames.cz/manualy/DOS/Kings_of_the_Beach_-_DOS_-_Manual.pdf), en la sección "Glossary", dice sobre 'Dink' "A technique where the hitter goes up as if he'll spike, but taps the ball over the net instead.", pero no especifica como realitzar el 'dink'. En la Wikipedia se puede leer: "The only 'advanced' moves in the game are the ability to dink or perform a one-handed Kong block (Stoklos's trademark).". ¿Alguien sabe cuando realiza un 'spike' y cuando un 'dink'?
|