Difference between revisions of "User talk:Timeslip"

From the Oblivion ConstructionSet Wiki
Jump to navigation Jump to search
imported>Timeslip
m (→‎BSA hashes: Fixed rounding error)
imported>MentalElf
 
(18 intermediate revisions by 2 users not shown)
Line 52: Line 52:
</pre>
</pre>
This may not be 100% accurate, but I've run it over every file and folder in oblivion v1.1's meshes, textures and misc BSAs without any mismatches, so I'm fairly sure it's right.
This may not be 100% accurate, but I've run it over every file and folder in oblivion v1.1's meshes, textures and misc BSAs without any mismatches, so I'm fairly sure it's right.
=== Possible Issue ===
[[User:MentalElf|MentalElf]] What about the other file types... ".mp3", ".spt", ".egm"???
:[[User:Timeslip|Timeslip]] 15:42, 10 October 2006 (EDT): For any file types other than the ones I've listed in the switch, the lower dword is left untouched. The upper dword is calculated normally with GenHash2.
::[[User:MentalElf|MentalElf]] Ok, thanks!  I'm porting this to TCL/TK.
::[[User:MentalElf|MentalElf]] Hmmm.  I take it from the code that a "ulong" is 64 bits.  This presents a problem for me.  My long values are only 32 bits...
:::[[User:Timeslip|Timeslip]] 04:04, 11 October 2006 (EDT): Yes, in C# longs are 64 bit. For languages without 64 bit integer support, you can just split the hash into two 32 bit ints, since the upper and lower DWORDs are calculated seperately.
<pre>
============== TCL ===================================
#############################################################################
## Procedure:  GenHash
proc ::GenHash {HFile HExt} {
    set hash64 [expr wide(0)]
    set HFile [string tolower $HFile]
    set HExt [string tolower $HExt]
    set HFLen [string length $HFile]
    if { $HFLen > 0 } {
set hash64 [expr wide([scan [string index $HFile [expr $HFLen - 1]] "%c"])]
if { $HFLen > 2 } {
    set hash64 [expr {$hash64 + wide([scan [string index $HFile [expr $HFLen - 2]] "%c"] << 8)}]
}
set hash64 [expr {$hash64 + wide($HFLen << 16)}]
set hash64 [expr {$hash64 + (wide([scan [string index $HFile 0] "%c"] << 24) & 0xFFFFFFFF)}]
    }
    if { $HFLen > 3 } {
set hash64 [expr {$hash64 + ((wide([GenHash2 [string range $HFile 1 [expr $HFLen - 3]]]) & 0xFFFFFFFF) << 32)}]
    }
    if { [string length $HExt] > 0 } {
set hash64 [expr {$hash64 + ((wide([GenHash2 $HExt]) & 0xFFFFFFFF) << 32)}]
switch $HExt {
    ".nif" {
set i 1
    }
    ".kf" {
set i 2
    }
    ".dds" {
set i 3
    }
    ".wav" {
set i 4
    }
    default {
set i 0
    }
}
if { $i } {
    set a [expr {0xFF & ((($i & 0xFC) << 5) + (0xFF & (($hash64 & 0xFF000000) >> 24)))}]
    set b [expr {0xFF & ((($i & 0xFE) << 6) + (0xFF & $hash64))}]
    set c [expr {0xFF & (($i << 7) + (0xFF & (($hash64 & 0xFF00) >> 8)))}]
    set hash64 [expr {$hash64 - ($hash64 & 0xFF00FFFF)}]
    set hash64 [expr {$hash64 + ($a << 24) + $b + ($c << 8)}]
}
    }
    return $hash64
}
#############################################################################
## Procedure:  GenHash2
proc ::GenHash2 {s} {
    for { set hash32 0; set i 0 } { $i < [string length $s] } { incr i } {
set hash32 [expr $hash32 * 0x1003F]
set hash32 [expr $hash32 + [scan [string index $s $i] "%c"]]
    }
    return $hash32
}
</pre>

Latest revision as of 06:30, 11 October 2006

BSA hashes[edit source]

Oblivions BSA hashing algorithm is different from morrowinds, and doesn't appear to be documented anywhere. In case anyone else is working on any BSA tools and wants the algorithm, I'll post the C# code that obmm uses here.

//file is the file name without the extension
//Extension is the extension of a file, including the preceding '.'
//For hashing anything other than file names, ext must be an empty string
public static ulong GenHash(string file, string ext) {
  file=file.ToLower();
  ext=ext.ToLower();
  ulong hash=0;
  if(file.Length>0) {
    hash=(ulong)(
      (((byte)file[file.Length-1])*0x1)+
      ((file.Length>2?(byte)file[file.Length-2]:(byte)0)*0x100)+
      (file.Length*0x10000)+
      (((byte)file[0])*0x1000000)
    );
  }
  if(file.Length>3) {
    hash+=(ulong)(GenHash2(file.Substring(1, file.Length-3))*0x100000000);
  }
  if(ext.Length>0) {
    hash+=(ulong)(GenHash2(ext)*0x100000000);
    byte i=0;
    switch(ext) {
      case ".nif": i=1; break;
      case ".kf": i=2; break;
      case ".dds": i=3; break;
      case ".wav": i=4; break;
    }
    if(i!=0) {
      byte a=(byte)(((i&0xfc)<<5)+(byte)((hash&0xff000000)>>24));
      byte b=(byte)(((i&0xfe)<<6)+(byte)(hash&0xff));
      byte c=(byte)((i<<7)+(byte)((hash&0xff00)>>8));
      hash-=hash&0xFF00FFFF;
      hash+=(uint)((a<<24)+b+(c<<8));
    }
  }
  return hash;
}

private static uint GenHash2(string s) {
  uint hash=0;
  for(int i=0;i<s.Length;i++) {
    hash*=0x1003f;
    hash+=(byte)s[i];
  }
  return hash;
}

This may not be 100% accurate, but I've run it over every file and folder in oblivion v1.1's meshes, textures and misc BSAs without any mismatches, so I'm fairly sure it's right.

Possible Issue[edit source]

MentalElf What about the other file types... ".mp3", ".spt", ".egm"???

Timeslip 15:42, 10 October 2006 (EDT): For any file types other than the ones I've listed in the switch, the lower dword is left untouched. The upper dword is calculated normally with GenHash2.
MentalElf Ok, thanks! I'm porting this to TCL/TK.
MentalElf Hmmm. I take it from the code that a "ulong" is 64 bits. This presents a problem for me. My long values are only 32 bits...
Timeslip 04:04, 11 October 2006 (EDT): Yes, in C# longs are 64 bit. For languages without 64 bit integer support, you can just split the hash into two 32 bit ints, since the upper and lower DWORDs are calculated seperately.
============== TCL ===================================
#############################################################################
## Procedure:  GenHash

proc ::GenHash {HFile HExt} {
    set hash64 [expr wide(0)]
    set HFile [string tolower $HFile]
    set HExt [string tolower $HExt]
    set HFLen [string length $HFile]

    if { $HFLen > 0 } {
	set hash64 [expr wide([scan [string index $HFile [expr $HFLen - 1]] "%c"])]
	if { $HFLen > 2 } {
	    set hash64 [expr {$hash64 + wide([scan [string index $HFile [expr $HFLen - 2]] "%c"] << 8)}]
	}
	set hash64 [expr {$hash64 + wide($HFLen << 16)}]
	set hash64 [expr {$hash64 + (wide([scan [string index $HFile 0] "%c"] << 24) & 0xFFFFFFFF)}]
    }
    if { $HFLen > 3 } {
	set hash64 [expr {$hash64 + ((wide([GenHash2 [string range $HFile 1 [expr $HFLen - 3]]]) & 0xFFFFFFFF) << 32)}]
    }
    if { [string length $HExt] > 0 } {
	set hash64 [expr {$hash64 + ((wide([GenHash2 $HExt]) & 0xFFFFFFFF) << 32)}]
	switch $HExt {
	    ".nif" {
		set i 1
	    }
	    ".kf" {
		set i 2
	    }
	    ".dds" {
		set i 3
	    }
	    ".wav" {
		set i 4
	    }
	    default {
		set i 0
	    }
	}
	if { $i } {
	    set a [expr {0xFF & ((($i & 0xFC) << 5) + (0xFF & (($hash64 & 0xFF000000) >> 24)))}]
	    set b [expr {0xFF & ((($i & 0xFE) << 6) + (0xFF & $hash64))}]
	    set c [expr {0xFF & (($i << 7) + (0xFF & (($hash64 & 0xFF00) >> 8)))}]
	    set hash64 [expr {$hash64 - ($hash64 & 0xFF00FFFF)}]
	    set hash64 [expr {$hash64 + ($a << 24) + $b + ($c << 8)}]
	}
    }
    return $hash64
}
#############################################################################
## Procedure:  GenHash2

proc ::GenHash2 {s} {
    for { set hash32 0; set i 0 } { $i < [string length $s] } { incr i } {
	set hash32 [expr $hash32 * 0x1003F]
	set hash32 [expr $hash32 + [scan [string index $s $i] "%c"]]
    }
    return $hash32
}