Important information: this site is currently scheduled to go offline indefinitely by end of the year.

.wz archive

The Original Forum. Game archives, full of resources. How to open them? Get help here.
Post Reply
singga
n00b
Posts: 19
Joined: Sat Aug 19, 2006 9:08 am

Post by singga »

themoviefund wrote:I used JavaScript.... I have the code, if you want. Now I'm trying to decipher how to use it. Lol.... You want to try my JavaScript?

EDIT: I took out the link. Here's my proof:
ohz???
the image in data.wz is store in BMP or you convert it?
i remenber Wizet store the image in ".img" :?:
terasonic
ultra-n00b
Posts: 8
Joined: Wed Aug 09, 2006 10:45 am

um...

Post by terasonic »

I don't connect Meds site. Link is Broken... :(
I need that tools.. :(

Tell me how I can get tools.
Meds
beginner
Posts: 36
Joined: Sun Feb 26, 2006 3:16 am
Contact:

Post by Meds »

I hate being called a scammer : ( So there you go : )

<3

oops forgot the link
http://heat.castleparadox.com/mslvl/geewiz.php
: 3

Buy me a beer? : (
terasonic
ultra-n00b
Posts: 8
Joined: Wed Aug 09, 2006 10:45 am

...

Post by terasonic »

I Love so Much!!!!

Meds. You a cool guy!!!
Meds
beginner
Posts: 36
Joined: Sun Feb 26, 2006 3:16 am
Contact:

Post by Meds »

...I need a new wand....*wink*
Mr.Mouse
Site Admin
Posts: 4073
Joined: Wed Jan 15, 2003 6:45 pm
Location: Dungeons of Doom
Has thanked: 450 times
Been thanked: 682 times
Contact:

Post by Mr.Mouse »

Meds wrote:I hate being called a scammer : ( So there you go : )

<3

oops forgot the link
http://heat.castleparadox.com/mslvl/geewiz.php
: 3

Buy me a beer? : (
AH FINALLY! You've certainly earned that beer. :) I'm sure you made lots of fans of the game happy here. :D

One thing though, the links to

For Maple Global v.19 : Download
For MapleSEA v.26: Download

404 on me.
Meds
beginner
Posts: 36
Joined: Sun Feb 26, 2006 3:16 am
Contact:

Post by Meds »

Oh right >.< I have to make new ones. Since the addresses change with each patch. I don't have the game though~ There are instructions in the readme on how to go about creating the mapdump.

Global MS mapdump is up.
Lambda
beginner
Posts: 20
Joined: Sat Aug 19, 2006 10:42 am

Post by Lambda »

Meds: If are you going to use the GPL as the license for your program you must offer source code to those who ask of it... Not that I think it adds anything to what I've figured out, but consider this a request.

---------------

Anyways, here's the algorithm used for those who want to work on further reverse engineering the file format on their own.

Wizet uses a 16 bit image format as opposed to the normal 32 bit image format. IE: instead of RR GG BB AA, they just have RG BA. Once you extract the zlib compressed data, you need to expand 16 bits to 32. (python code) That's why any image you see on Rick's site have pixels values like: 0x11ffcc. It just repeats the same rgb value twice.

Code: Select all

def image16to32(width, height, data):
  buf = ""
  for i in range(width*height):
    p = struct.unpack("BB", data[i*2:i*2+2])

    a = p[1] / 16 ; a = a*16 + a
    r = p[1] % 16 ; r = r*16 + r
    g = p[0] / 16 ; g = g*16 + g
    b = p[0] % 16 ; b = b*16 + b

    buf += struct.pack("BBBB", r, g, b, a)
  return buf
Here's a complete application to extract images from a known file location. I'll be posting all morning on how to figure out the rest of the file format, or at least a large(r) chunk of it.

Copy this code to a file named "extractat.py" and put it in the same directory as data.wz. It takes command line arguments of file locations. So to extract the mushrooms at the very beginning of the file, you'd type something similar to: "extractat.py 167995 169523 171333 172989 174630 176264 177900 179542 181372 182541" And out will pop 10 png files. The code is in the public domain and comes without warrenty.

Code: Select all

import zlib
import struct

def pack_chunk(tag,data):
  to_check = tag + data
  return struct.pack("!I",len(data)) + \
         to_check + \
         struct.pack("!I",zlib.crc32(to_check))

def dumpToPNG(width, height, data):
  raw_data = ""
  for y in range(height):
    raw_data += chr(0)
    raw_data += data[y*width*4:(y+1)*width*4]

  return struct.pack("8B", 137, 80, 78, 71, 13, 10, 26, 10) + \
         pack_chunk('IHDR',
           struct.pack("!IIBBBBB",width,height,8,6,0,0,0)) + \
         pack_chunk('IDAT', zlib.compress(raw_data,9)) + \
         pack_chunk('IEND', '')

def image16to32(width, height, data):
  buf = ""
  for i in range(width*height):
    p = struct.unpack("BB", data[i*2:i*2+2])

    a = p[1] / 16 ; a = a*16 + a
    r = p[1] % 16 ; r = r*16 + r
    g = p[0] / 16 ; g = g*16 + g
    b = p[0] % 16 ; b = b*16 + b

    buf += struct.pack("BBBB", r, g, b, a)
  return buf

from sys import argv

f = open("Data.wz", "rb")
i = 0
for arg in argv[1:]:
  offset = int(arg)

  offset = offset - 12
  f.seek(offset,0)
  (width, height) = struct.unpack("BB", f.read(2))
  f.read(6)
  (length, ) = struct.unpack("I", f.read(4))
  f.read(1)
  
  obj = zlib.decompressobj()
  buf = obj.decompress(f.read(length))

  g = open(arg + ".png", "wb")
  g.write(dumpToPNG(width, height, image16to32(width, height, buf)))
  g.close()

  i += 1
f.close()
Meds
beginner
Posts: 36
Joined: Sun Feb 26, 2006 3:16 am
Contact:

Post by Meds »

And they come out of the wood works : P

It was on the file with the source. But I couldn't figure out the png header, so everything was in bitmap and there's probably a few width offsets I'd missed by doing that.
Lambda
beginner
Posts: 20
Joined: Sat Aug 19, 2006 10:42 am

Post by Lambda »

The header of data.wz is the easiest section of the file to figure out, it contains a file identifer (the first 4 bytes of any file format generally identify it, somewhat like the ".123" file extensions), the size of the data minus the 4 bytes already read in but including the size value itself (64 bit value), the offset into the file where the filetable is at (32 bit value), and the Wizet copyright takes up the rest of the header:

Code: Select all

import struct

def rU32(f): return struct.unpack('I', f.read(4))[0]
def rU64(f): return struct.unpack('Q', f.read(8))[0]

def dumpFileLoc(f):  print "<tr><td>FileLocation</td><td>%d</td></tr>" % f.tell()

def dumpHeader(f):
  ident  = f.read(4)
  size   = rU64(f)
  offset = rU32(f)
  copy   = f.read(offset - f.tell())

  print "<tr><td>Header.FileIdentifier</td><td>%s</td></tr>" % ident
  print "<tr><td>Header.DataSize</td><td>%d</td></tr>" % size
  print "<tr><td>Header.DataStartOffset</td><td>0x%08x</td></tr>" % offset
  print "<tr><td>Header.Copyright</td><td>%s</td></tr>" % copy

def dump():
  f = open("data.wz", "rb")

  print "<html>"
  print " <head>"
  print "  <title>data.wz File Dump</title>"
  print " </head>"
  print " <body>"
  print "  <table>"
  dumpHeader(f)
  dumpFileLoc(f)
  print "  </table>"
  print " </body>"
  print "</html>"

dump()
I'm copying and pasting from my main source file just the sections that apply to each description, I do a quick check before submitting, but nothing extensive. So there may be errors.
Lambda
beginner
Posts: 20
Joined: Sat Aug 19, 2006 10:42 am

Post by Lambda »

The next 16 bits after the header are unknown at the moment. In the version of data.wz that I have, it's 0x0061.

After that, we begin the directory blocks. In my version there are 45 directories, and I had to fix my extractor to use that value. The program could be smarter and count the number of subdirectories and stop once it no longer finds them, or I could figure out the encoding used for file/directory offsets. If anyone else figures it out, I'd be glad to know.

The first value is the count of how many files are in the directory. Wizet tries to compactly store sizes. If the size is less then 128, it just gives you the size as a single byte. If it's >= then 128, it flags the value as a large value (hex code 0x80), and the next 4 bytes make up the actual count/size.

Code: Select all

def rPackNum(f):
  size = rU8(f)
  if(size == 0x80): size = rU32(f)
  return size
Once you know the number of files, start iterating over all of the entries. Each entry has a: Identifier byte, filename (or link), filesize, and then two unknown values.

The identifier byte can be one of the following three values: 0x04 for files, 0x03 for directories, and 0x02 for symbolic links.

After the identifer, comes the filename. The filenames and strings in the data.wz are tricky because wizet "encrypts" them. (Look! Scare quotes! I'm shaking.)

The decryption key begins with: 0xAA (0101 0101). If the value I'm encrypting is 0110 0111, and the key is 0101 0101, then the key will flip any bits where there is a 1 in the key; where there is a 0 in the key, the bit. So with our current key, we flip bits 1,3,5, and 7, for the final value of: 0010 0010. The decryption process works the same way.

After each character, the decryption key gets incremented by 1 (from 0xAA to 0xAB to 0xAC, etc...

The above complicated description boils down to the following 9 lines of code:

Code: Select all

def transStr(str):
  s = ''
  p = 0xAA
  for cc in str:
    a = ord(cc)
    val = ((a & ~p) ^ (~a & p)) & 0xff
    s += struct.pack("B", val )
    p += 1
  return s
Strings are guarenteed to be less then 256 characters.

Before each string is the negation/not of the string length. So if the string length is 3, the value read in will actually be -3. Since strings are less then 256 characters, the length is just a signed byte.

This leads to the following 3 lines of code:

Code: Select all

def rUStr(f):
  size = rU8(f)
  return transStr(f.read(256 - size))
Next is the filesize, and unknown1 (both PackNums), and then a 32 bit something for unknown2.

Symbolic links are the only oddities. Instead of a string, it gives a 32 bit link to the file that it is pointing to.

Put it all together, and you get:

Code: Select all

import struct
from sys import exit

def dumpFileLoc(f):  print "<tr><td>FileLocation</td><td>%d</td></tr>" % f.tell()
def dumpFileSize(f): print "<tr><td>FileSize</td><td>%d</td></tr>" % getFileSize(f)

def check(flag, val, f):
  if(flag != val):
    print "<tr><td>Unknown: %s</td><td>Expecting: %s</td></tr>" % (repr(flag), repr(val))
    dumpFileLoc(f)
    exit(-1)

def checkA(flag, vals, f):
  if(not flag in vals):
    print "<tr><td>Unknown:  %s</td><td>Expecting: %s</td></tr>" % (repr(flag), repr(vals))
    dumpFileLoc(f)
    exit(-1)

def rU8 (f): return struct.unpack('B', f.read(1))[0]
def rU16(f): return struct.unpack('H', f.read(2))[0]
def rU32(f): return struct.unpack('I', f.read(4))[0]
def rU64(f): return struct.unpack('Q', f.read(8))[0]
def rF32(f): return struct.unpack('f', f.read(4))[0]

def rPackNum(f):
  size = rU8(f)
  if(size == 0x80): size = rU32(f)
  return size

def transStr(str):
  s = ''
  p = 0xAA
  for cc in str:
    a = ord(cc)
    val = ((a & ~p) ^ (~a & p)) & 0xff
    s += struct.pack("B", val )
    p += 1
  return s

def rUStr(f):
  size = rU8(f)
  return transStr(f.read(256 - size))

def dumpHeader(f):
  ident  = f.read(4)
  size   = rU64(f)
  offset = rU32(f)
  copy   = f.read(offset - f.tell())

  print "<tr><td>Header.FileIdentifier</td><td>%s</td></tr>" % ident
  print "<tr><td>Header.DataSize</td><td>%d</td></tr>" % size
  print "<tr><td>Header.DataStartOffset</td><td>0x%08x</td></tr>" % offset
  print "<tr><td>Header.Copyright</td><td>%s</td></tr>" % copy

def dumpDirInfo(f, sname):
  filename = rUStr(f)
  filesize = rPackNum(f)
  unknown1 = rPackNum(f)
  unknown2 = rU32(f)

  print "<tr><td>%s.DirName</td><td>%s</td></tr>" % (sname, filename)
  print "<tr><td>%s.DirSize</td><td>%d</td></tr>" % (sname, filesize)
  print "<tr><td>%s.Unknown1</td><td>0x%08x</td></tr>" % (sname, unknown1)
  print "<tr><td>%s.Unknown2</td><td>0x%08x</td></tr>" % (sname, unknown2)

def dumpLinkInfo(f, sname):
  fileloc  = rU32(f)

  pos = f.tell()
  f.seek(fileloc + 0x3c + 1, 0)
  filename = rUStr(f)
  f.seek(pos)

  filesize = rPackNum(f)
  unknown1 = rPackNum(f)
  unknown2 = rU32(f)

  print "<tr><td>%s.LinkName (from %08x)</td><td>%s</td></tr>" % (sname, fileloc + 0x3c, filename)
  print "<tr><td>%s.LinkSize</td><td>%d</td></tr>" % (sname, filesize)
  print "<tr><td>%s.Unknown1</td><td>0x%08x</td></tr>" % (sname, unknown1)
  print "<tr><td>%s.Unknown2</td><td>0x%08x</td></tr>" % (sname, unknown2)

def dumpFileInfo(f, sname):
  filename = rUStr(f)
  filesize = rPackNum(f)
  unknown1 = rPackNum(f)
  unknown2 = rU32(f)

  print "<tr><td>%s.FileName</td><td>%s</td></tr>" % (sname, filename)
  print "<tr><td>%s.FileSize</td><td>%d</td></tr>" % (sname, filesize)
  print "<tr><td>%s.Unknown1</td><td>0x%08x</td></tr>" % (sname, unknown1)
  print "<tr><td>%s.Unknown2</td><td>0x%08x</td></tr>" % (sname, unknown2)

def dumpDirTree(f, sname):
  fileCount = rPackNum(f)

  for i in range(fileCount):
    fileType = rU8(f)
    if  (fileType == 0x04): dumpFileInfo(f, sname + "[" + str(i) + "]")
    elif(fileType == 0x03): dumpDirInfo(f, sname + "[" + str(i) + "]")
    elif(fileType == 0x02): dumpLinkInfo(f, sname + "[" + str(i) + "]")
    else: exit(-1)

def dumpDirTrees(f):
  unknown = rU16(f)
  print "<tr><td>Unknown16</td><td>0x%04x</td></tr>" % unknown

  for i in range(45):
    dumpDirTree(f, "Dir[" + str(i) + "]")

def dump():
  f = open("data.wz", "rb")

  print "<html>"
  print " <head>"
  print "  <title>data.wz File Dump</title>"
  print " </head>"
  print " <body>"
  print "  <table>"
  dumpHeader(f)
  dumpDirTrees(f)
  dumpFileLoc(f)
  print "  </table>"
  print " </body>"
  print "</html>"

dump() 
The basic encryption that they used took me a long time to figure out. But it was a fun puzzle.
Lambda
beginner
Posts: 20
Joined: Sat Aug 19, 2006 10:42 am

Post by Lambda »

Meds: I'm still not seeing source.

The only things in GeeWiz.zip are: bz2.pyd (python library), geeWiz.exe (the program), library.zip (a bunch of compiled python libraries), mapdump (all of the image offsets), mextract.exe (a program), MSVCR71.dll (a dll), python23.dll (a dll), python24.dll (a dll), README! (a text file), unicodedata.pyd (python library), w9xpopen.exe (a program), and zlib.pyd (a program).

I didn't see anything in the maplox!.zip either: bz2.pyd (python library), library.zip (a bunch of compiled python libraries), maplox.exe (the program), MSVCR71.dll (a dll), python24.dll (a dll), README! (a text file), unicodedata.pyd (python library), w9xpopen.exe (a program), and zlib.pyd (a program).

Am I blind and just not seeing it? I apologize if I am.

--

Some fun notes about the file table: If you add up the sizes of all of the files in the very first directory, it adds up to the size of the whole data.wz minus the size of the header and the file table itself. That's a good confirmation!

For those running the code, you'll need to redirect the output to an html file, ie: "dump.py > blah.html". It was easier for me to extract to an html file then it was to write a real GUI for it... especially when you are first figuring things out. Basic text wasn't enough, because eventually we'll see images show up in the HTML document. It starts to become a very large file very quickly.
terasonic
ultra-n00b
Posts: 8
Joined: Wed Aug 09, 2006 10:45 am

um...

Post by terasonic »

Korean Maple's Data.wz file is split for them.

I dont use the tools.. :(
how can I use that?
Image
singga
n00b
Posts: 19
Joined: Sat Aug 19, 2006 9:08 am

Post by singga »

Meds wrote:I hate being called a scammer : ( So there you go : )

<3

oops forgot the link
http://heat.castleparadox.com/mslvl/geewiz.php
: 3

Buy me a beer? : (
I will care your GeeWiz because I don't play GMS
=.=""""
thanks for sharing!~~~~
i want to buy you a beer 8D

and "care" i mean that your own copyright~!!!!!
Hong Kong guys love copryright
but not copy :P
singga
n00b
Posts: 19
Joined: Sat Aug 19, 2006 9:08 am

Post by singga »

Lambda wrote:The next 16 bits after the header are unknown at the moment. In the version of data.wz that I have, it's 0x0061.

After that, we begin the directory blocks. In my version there are 45 directories, and I had to fix my extractor to use that value. The program could be smarter and count the number of subdirectories and stop once it no longer finds them, or I could figure out the encoding used for file/directory offsets. If anyone else figures it out, I'd be glad to know.

The first value is the count of how many files are in the directory. Wizet tries to compactly store sizes. If the size is less then 128, it just gives you the size as a single byte. If it's >= then 128, it flags the value as a large value (hex code 0x80), and the next 4 bytes make up the actual count/size.

Code: Select all

def rPackNum(f):
  size = rU8(f)
  if(size == 0x80): size = rU32(f)
  return size
Once you know the number of files, start iterating over all of the entries. Each entry has a: Identifier byte, filename (or link), filesize, and then two unknown values.

The identifier byte can be one of the following three values: 0x04 for files, 0x03 for directories, and 0x02 for symbolic links.

After the identifer, comes the filename. The filenames and strings in the data.wz are tricky because wizet "encrypts" them. (Look! Scare quotes! I'm shaking.)

The decryption key begins with: 0xAA (0101 0101). If the value I'm encrypting is 0110 0111, and the key is 0101 0101, then the key will flip any bits where there is a 1 in the key; where there is a 0 in the key, the bit. So with our current key, we flip bits 1,3,5, and 7, for the final value of: 0010 0010. The decryption process works the same way.

After each character, the decryption key gets incremented by 1 (from 0xAA to 0xAB to 0xAC, etc...

The above complicated description boils down to the following 9 lines of code:

Code: Select all

def transStr(str):
  s = ''
  p = 0xAA
  for cc in str:
    a = ord(cc)
    val = ((a & ~p) ^ (~a & p)) & 0xff
    s += struct.pack("B", val )
    p += 1
  return s
Strings are guarenteed to be less then 256 characters.

Before each string is the negation/not of the string length. So if the string length is 3, the value read in will actually be -3. Since strings are less then 256 characters, the length is just a signed byte.

This leads to the following 3 lines of code:

Code: Select all

def rUStr(f):
  size = rU8(f)
  return transStr(f.read(256 - size))
Next is the filesize, and unknown1 (both PackNums), and then a 32 bit something for unknown2.

Symbolic links are the only oddities. Instead of a string, it gives a 32 bit link to the file that it is pointing to.

Put it all together, and you get:

Code: Select all

import struct
from sys import exit

def dumpFileLoc(f):  print "<tr><td>FileLocation</td><td>%d</td></tr>" % f.tell()
def dumpFileSize(f): print "<tr><td>FileSize</td><td>%d</td></tr>" % getFileSize(f)

def check(flag, val, f):
  if(flag != val):
    print "<tr><td>Unknown: %s</td><td>Expecting: %s</td></tr>" % (repr(flag), repr(val))
    dumpFileLoc(f)
    exit(-1)

def checkA(flag, vals, f):
  if(not flag in vals):
    print "<tr><td>Unknown:  %s</td><td>Expecting: %s</td></tr>" % (repr(flag), repr(vals))
    dumpFileLoc(f)
    exit(-1)

def rU8 (f): return struct.unpack('B', f.read(1))[0]
def rU16(f): return struct.unpack('H', f.read(2))[0]
def rU32(f): return struct.unpack('I', f.read(4))[0]
def rU64(f): return struct.unpack('Q', f.read(8))[0]
def rF32(f): return struct.unpack('f', f.read(4))[0]

def rPackNum(f):
  size = rU8(f)
  if(size == 0x80): size = rU32(f)
  return size

def transStr(str):
  s = ''
  p = 0xAA
  for cc in str:
    a = ord(cc)
    val = ((a & ~p) ^ (~a & p)) & 0xff
    s += struct.pack("B", val )
    p += 1
  return s

def rUStr(f):
  size = rU8(f)
  return transStr(f.read(256 - size))

def dumpHeader(f):
  ident  = f.read(4)
  size   = rU64(f)
  offset = rU32(f)
  copy   = f.read(offset - f.tell())

  print "<tr><td>Header.FileIdentifier</td><td>%s</td></tr>" % ident
  print "<tr><td>Header.DataSize</td><td>%d</td></tr>" % size
  print "<tr><td>Header.DataStartOffset</td><td>0x%08x</td></tr>" % offset
  print "<tr><td>Header.Copyright</td><td>%s</td></tr>" % copy

def dumpDirInfo(f, sname):
  filename = rUStr(f)
  filesize = rPackNum(f)
  unknown1 = rPackNum(f)
  unknown2 = rU32(f)

  print "<tr><td>%s.DirName</td><td>%s</td></tr>" % (sname, filename)
  print "<tr><td>%s.DirSize</td><td>%d</td></tr>" % (sname, filesize)
  print "<tr><td>%s.Unknown1</td><td>0x%08x</td></tr>" % (sname, unknown1)
  print "<tr><td>%s.Unknown2</td><td>0x%08x</td></tr>" % (sname, unknown2)

def dumpLinkInfo(f, sname):
  fileloc  = rU32(f)

  pos = f.tell()
  f.seek(fileloc + 0x3c + 1, 0)
  filename = rUStr(f)
  f.seek(pos)

  filesize = rPackNum(f)
  unknown1 = rPackNum(f)
  unknown2 = rU32(f)

  print "<tr><td>%s.LinkName (from %08x)</td><td>%s</td></tr>" % (sname, fileloc + 0x3c, filename)
  print "<tr><td>%s.LinkSize</td><td>%d</td></tr>" % (sname, filesize)
  print "<tr><td>%s.Unknown1</td><td>0x%08x</td></tr>" % (sname, unknown1)
  print "<tr><td>%s.Unknown2</td><td>0x%08x</td></tr>" % (sname, unknown2)

def dumpFileInfo(f, sname):
  filename = rUStr(f)
  filesize = rPackNum(f)
  unknown1 = rPackNum(f)
  unknown2 = rU32(f)

  print "<tr><td>%s.FileName</td><td>%s</td></tr>" % (sname, filename)
  print "<tr><td>%s.FileSize</td><td>%d</td></tr>" % (sname, filesize)
  print "<tr><td>%s.Unknown1</td><td>0x%08x</td></tr>" % (sname, unknown1)
  print "<tr><td>%s.Unknown2</td><td>0x%08x</td></tr>" % (sname, unknown2)

def dumpDirTree(f, sname):
  fileCount = rPackNum(f)

  for i in range(fileCount):
    fileType = rU8(f)
    if  (fileType == 0x04): dumpFileInfo(f, sname + "[" + str(i) + "]")
    elif(fileType == 0x03): dumpDirInfo(f, sname + "[" + str(i) + "]")
    elif(fileType == 0x02): dumpLinkInfo(f, sname + "[" + str(i) + "]")
    else: exit(-1)

def dumpDirTrees(f):
  unknown = rU16(f)
  print "<tr><td>Unknown16</td><td>0x%04x</td></tr>" % unknown

  for i in range(45):
    dumpDirTree(f, "Dir[" + str(i) + "]")

def dump():
  f = open("data.wz", "rb")

  print "<html>"
  print " <head>"
  print "  <title>data.wz File Dump</title>"
  print " </head>"
  print " <body>"
  print "  <table>"
  dumpHeader(f)
  dumpDirTrees(f)
  dumpFileLoc(f)
  print "  </table>"
  print " </body>"
  print "</html>"

dump() 
The basic encryption that they used took me a long time to figure out. But it was a fun puzzle.
it is your code or the code after 2000 evil click?
Post Reply