Possible support for "The Neverhood"?
Moderator: ScummVM Team
-
- Posts: 1
- Joined: Tue Dec 23, 2008 4:10 pm
Possible support for "The Neverhood"?
Just wondering. Here are some pro's and con's:
Pros:
It's an adventure game
Sprite based (I think)
Cons:
Has Video
Has also been for Windows
Not really the same as a SCUMM game
There's probably more, but that's all I could think up.
Pros:
It's an adventure game
Sprite based (I think)
Cons:
Has Video
Has also been for Windows
Not really the same as a SCUMM game
There's probably more, but that's all I could think up.
Re: Possible support for "The Neverhood"?
Wow! Finally there is someone who wants to develop an engine for Neverhood! Cool! I voted "yes"!!SonicTheBro wrote:Just wondering. Here are some pro's and con's:
Pros:
It's an adventure game
Sprite based (I think)
Eugene
Re: Possible support for "The Neverhood"?
Not sarcastic at all:Psev wrote:Wow! Finally there is someone who wants to develop an engine for Neverhood! Cool! I voted "yes"!!SonicTheBro wrote:Just wondering. Here are some pro's and con's:
Pros:
It's an adventure game
Sprite based (I think)
Eugene
I recently played The Neverhood actually, and incidently, it ran just fine in win xp. But portability sure would be nice:)
Re: Possible support for "The Neverhood"?
Don't be like that. Isn't it easier to say "Sorry, but ..."sev wrote:Wow! Finally there is someone who wants to develop an engine for Neverhood! Cool! I voted "yes"!!SonicTheBro wrote:Just wondering. Here are some pro's and con's:
Pros:
It's an adventure game
Sprite based (I think)
Eugene
You know you don't have the FAQ very clear on the frontpage, it's one tiny little link hidden in the menu, so no surprise no one reads it.
Re: Possible support for "The Neverhood"?
Every forum subject has a sticky called "Forum rules". One of these rules is reading the faq before posting.iPwnzorz wrote:You know you don't have the FAQ very clear on the frontpage, it's one tiny little link hidden in the menu, so no surprise no one reads it.
I also voted yes. Neverhood support would be great of course.
-
- Posts: 3
- Joined: Fri Aug 28, 2009 10:01 pm
(Sorry for the thread necromancy.)
Cons:
- Doesn't seem to be script based, so would require the original source code or a re-implementation.
Having said that:
This is a bit of python code to extract the BLB archive. It gives you music, sound, images and animations. I only tested the free demo that you can download here:
http://download.cnet.com/The-Neverhood- ... 03341.html
I found the demo decompresses fine with The Unarchiver on MacOS. I assume on Linux you can use Wine, and on Windows you can install it directly.
(I don't have the full game myself. And I don't need a PM of where to find it.)
Most credit go to Valery V. Anisimovsky for finding the archive format, compression method (PKWARE) and ADPCM audio compression.
I found that you can decompress PKWARE stream files with blast, which is in the contrib/blast directory of zlib, and the image and animation format. You need to compile blast into a dynamic library for your platform, so it can be loaded from the python script.
Unfortunately I don't have the time to develop an engine myself. If you're lucky I get the smacker files decoded sometime.
(Update: ffmpeg supports the smacker files in Neverhood.)
(Update: added a few "del" statements to buffers.)
(Update: fixed extension for smacker files.)
Cons:
- Doesn't seem to be script based, so would require the original source code or a re-implementation.
Having said that:
Code: Select all
import struct, array, wave, sys, os
from ctypes import *
# Enable headless pygame
# set SDL to use the dummy NULL video driver,
# so it doesn't need a windowing system.
os.environ["SDL_VIDEODRIVER"] = "dummy"
if True:
# Some platforms might need to init the display for some parts of pygame.
import pygame.display
pygame.display.init()
screen = pygame.display.set_mode((1,1))
import pygame
from pygame.locals import *
# Some magic to make the blast decompression function available from inside python
class BUFFER(Structure):
_fields_ = [("data", c_void_p), ("len", c_uint)]
# typedef unsigned (*blast_in)(void *how, unsigned char **buf);
BLASTINFUNC = CFUNCTYPE(c_uint, c_void_p, POINTER(c_void_p))
def py_blast_in_func(a, b):
buf = cast(a, POINTER(BUFFER))
b[0] = buf.contents.data
return buf.contents.len
blast_in_func = BLASTINFUNC(py_blast_in_func)
# typedef int (*blast_out)(void *how, unsigned char *buf, unsigned len);
BLASTOUTFUNC = CFUNCTYPE(c_int, c_void_p, c_void_p, c_uint)
def py_blast_out_func(a, b, c):
buf = cast(a, POINTER(BUFFER))
memmove(buf.contents.data + buf.contents.len, b, c)
buf.contents.len += c
return 0
blast_out_func = BLASTOUTFUNC(py_blast_out_func)
if __name__ == "__main__":
# make an output directory
if not os.access("data", os.F_OK):
os.mkdir("data")
# You need to make a dynamic library of zlib/contrib/blast.c, on MacOS that is
# gcc -dynamiclib -o blast.dylib -dylib blast.c
lib_blast = CDLL("blast.dylib")
blast = lib_blast.blast
blast.restype = c_int
blast.argtypes = [BLASTINFUNC, c_void_p, BLASTOUTFUNC, c_void_p]
f = open("nevdemo_full/NEVDEMO.BLB", "rb")
# Header
(magic, id, unknown, dataSize, fileSize, file_number) = struct.unpack('<4sBBHLL', f.read(16))
if magic != "\x40\x49\x00\x02":
print "wrong id, not a blb file"
exit()
# file ID
id_table = []
for i in xrange(file_number):
(id,) = struct.unpack('<L', f.read(4))
id_table.append(id)
# dir entries
file_table = []
for i in xrange(file_number):
file_entry = struct.unpack('<BBHLLLL', f.read(20))
file_table.append(file_entry)
# offset shift table (for music and sfx files)
shift_table = f.read(dataSize)
# initial pal
pal = [(x,x,x) for x in xrange(256)]
# export files into raw
file_type = { 7: "sound", 8: "music", 10: "video", 2:"image", 4:"animation" }
file_ext = { 7: "wav", 8: "wav", 10: "smk", 2:"tga", 4:"tga" }
for i in xrange(file_number):
(type, mode, index, id, start, in_len, out_len) = file_table[i]
if mode != 101:
f.seek(start)
data = f.read(in_len)
if mode == 3:
buffer = create_string_buffer(out_len)
in_buf = BUFFER(cast(data, c_void_p), in_len)
out_buf = BUFFER(cast(buffer, c_void_p), 0)
blast(blast_in_func, byref(in_buf), blast_out_func, byref(out_buf))
data = string_at(buffer, out_len)
if type in [7,8]:
# sound/music
if index > len(shift_table):
shift = 255
else:
shift = ord(shift_table[index])
w = wave.open("data/%s-%i.%s" % (file_type[type], i, file_ext[type]), "wb")
w.setnchannels( 1 )
w.setsampwidth( 2 )
w.setframerate( 22050 )
if shift < 16:
buffer = array.array( "h" )
curValue = 0
for val in data:
val = (ord(val) ^ 128) - 128
curValue += val
try:
buffer.append(curValue << shift)
except OverflowError:
try:
# This obviously means that something is going wrong with the decompression or with the scaling
buffer.append(curValue)
except OverflowError:
buffer.append(0)
# I'm not sure if this is required, it doesn't seem to be on MacOS. Maybe WAV does the endian-swap internally, or stores a flag in the wav header?
if sys.byteorder == "little":
buffer.byteswap()
w.writeframes( buffer.tostring() )
del buffer
else:
if (len(data) & 1) != 0:
data = data + "\0"
w.writeframes( data )
w.close()
elif type == 2:
# image
(format, width, height) = struct.unpack('<HHH', data[0:6])
# Format flags
# 0x1 - compressed
# 0x4 - offset
# 0x8 - palette
pos = 6
if format & 0x8:
pal = [(ord(x[0]), ord(x[1]), ord(x[2])) for x in [data[pos + idx * 4: pos + 3 + idx * 4] for idx in xrange(256)]]
pos += 1024
# I assume the palette is decided by the room an item appears in. But reusing the last palette makes things at least visible
if format & 0x4:
# some kind of position?
offset = struct.unpack('<HH', data[pos:pos+4])
# print offset
pos += 4
if format & 0x1:
# compressed
framebuffer = array.array("B")
framebuffer.extend([0 for x in xrange(width * height)])
ypos = 0
while True:
header = struct.unpack('<HH', data[pos:pos+4])
pos += 4
if header == (0,0):
break
(row_count, items_per_row_count) = header
for row_index in xrange(row_count):
for item_index in xrange(items_per_row_count):
(xpos, ) = struct.unpack('<H', data[pos:pos+2])
pos += 2
(fragment_len,) = struct.unpack('<H', data[pos:pos+2])
pos += 2
for b in xrange(fragment_len):
framebuffer[ypos + xpos + b] = ord(data[pos + b])
pos += fragment_len
ypos += width
surface = pygame.image.fromstring(framebuffer.tostring(), (width, height), "P")
surface.set_palette(pal)
pygame.image.save(surface, "data/%s-%i.%s" % (file_type[type], i, file_ext[type]))
del framebuffer
else:
# uncompressed
surface = pygame.image.fromstring(data[pos:pos + width * height], (width, height), "P")
surface.set_palette(pal)
pygame.image.save(surface, "data/%s-%i.%s" % (file_type[type], i, file_ext[type]))
elif type == 4:
header = struct.unpack('<HHIIII', data[0:20])
# print "%x" % i, header
palette_offset = header[3]
pal = [(ord(x[0]), ord(x[1]), ord(x[2])) for x in [data[palette_offset + idx * 4: palette_offset + 3 + idx * 4] for idx in xrange(256)]]
pos = 20
if header[0] == 2:
unknown_header = struct.unpack('<HHHH', data[pos:pos+8])
# print unknown_header
pos += 8
frame_count = header[5]
frame_list = []
for frame_index in xrange(frame_count):
frame = struct.unpack('<hhhhhhhhhhhhhhI', data[pos + frame_index * 32:pos + 32 + frame_index * 32])
# print frame
frame_list.append(frame)
data_offset = header[2]
for frame_index in xrange(frame_count):
frame = frame_list[frame_index]
width = frame[5]
height = frame[6]
pos = data_offset + frame[14]
framebuffer = array.array("B")
framebuffer.extend([0 for x in xrange(width * height)])
ypos = 0
while True:
header = struct.unpack('<HH', data[pos:pos+4])
pos += 4
if header == (0,0):
break
(row_count, items_per_row_count) = header
for row_index in xrange(row_count):
for item_index in xrange(items_per_row_count):
(xpos, ) = struct.unpack('<H', data[pos:pos+2])
pos += 2
(fragment_len,) = struct.unpack('<H', data[pos:pos+2])
pos += 2
for b in xrange(fragment_len):
framebuffer[ypos + xpos + b] = ord(data[pos + b])
pos += fragment_len
ypos += width
surface = pygame.image.fromstring(framebuffer.tostring(), (width, height), "P")
surface.set_palette(pal)
pygame.image.save(surface, "data/%s-%i-%i.%s" % (file_type[type], i, frame_index, file_ext[type]))
del framebuffer
else:
of = open("data/%s-%i.%s" % (file_type[type], i, file_ext[type]), "wb")
of.write(data)
of.close()
del data
f.close()
http://download.cnet.com/The-Neverhood- ... 03341.html
I found the demo decompresses fine with The Unarchiver on MacOS. I assume on Linux you can use Wine, and on Windows you can install it directly.
(I don't have the full game myself. And I don't need a PM of where to find it.)
Most credit go to Valery V. Anisimovsky for finding the archive format, compression method (PKWARE) and ADPCM audio compression.
I found that you can decompress PKWARE stream files with blast, which is in the contrib/blast directory of zlib, and the image and animation format. You need to compile blast into a dynamic library for your platform, so it can be loaded from the python script.
Unfortunately I don't have the time to develop an engine myself. If you're lucky I get the smacker files decoded sometime.
(Update: ffmpeg supports the smacker files in Neverhood.)
(Update: added a few "del" statements to buffers.)
(Update: fixed extension for smacker files.)
Last edited by Onkel Hotte on Sat Aug 29, 2009 7:33 am, edited 2 times in total.
- Laserschwert
- Posts: 280
- Joined: Mon Mar 06, 2006 11:48 pm
-
- Posts: 3
- Joined: Fri Aug 28, 2009 10:01 pm
- Laserschwert
- Posts: 280
- Joined: Mon Mar 06, 2006 11:48 pm
- ezekiel000
- Posts: 443
- Joined: Mon Aug 25, 2008 5:17 pm
- Location: Surrey, England
There is a reimplementation outside of scummvm here:
http://github.com/Blaizer/Neverhood/commits/master
http://github.com/Blaizer/Neverhood/commits/master