Initial Commit

This commit is contained in:
kurethedead
2019-11-18 17:40:14 -08:00
commit 41538347bd
45 changed files with 16982 additions and 0 deletions
View File
+81
View File
@@ -0,0 +1,81 @@
# Fast64
This requires Blender 2.81.
![alt-text](https://bitbucket.org/kurethedead/fast64/raw/master/images/mario_running.gif)
This is a Blender plugin that allows one to export display lists, geolayouts, and animations to Super Mario 64, either directly into a ROM or to C code. It supports custom color combiners / geometry modes, and different geolayout node types.
Make sure to save often, as this plugin is prone to crashing when undoing quickly.
![alt-text](https://bitbucket.org/kurethedead/fast64/raw/master/images/mat_inspector.png)
![alt-text](https://bitbucket.org/kurethedead/fast64/raw/master/images/bone_inspector.png)
### Features (import means import into Blender)
- Geolayout import/export
- Display List import/export
- Animation import/export
- Collision export
- Skinned mesh support
- Custom normals
### Restrictions
- No YUV textures supported.
- When importing display lists / geolayouts from SM64 to Blender, only the mesh is imported (no material)
### Credits
Thanks to anonymous_moose, Cheezepin, Rovert, and especially InTheBeef for testing.
# Instructions
### Installation
Unzip and drag the entire folder into your blender addons folder (usually at C:\Program Files\Blender Foundation\Blender\2.80\scripts\addons). Note that you must drag the ENTIRE folder, not just the 'fast64_internal' folder. Then in Blender, go to Edit -> Preferences -> Add-Ons and find/check the 'Fast64' plugin. If it does not show up, go to Edit -> Preferences -> Save&Load and make sure 'Auto Run Python Scripts' is enabled.
### Tool Locations
The tools can be found in the properties sidebar under the 'Tools' tab (toggled by pressing N).
The F3D material inspector can be found in the properties editor under the material tab.
The geolayout inspector can be found in the properties editor under the bone tab in Pose Mode.
The RDP default settings can be found in the properties editor under the world tab.
### ROM File Settings
When importing from a ROM, the plugin will import from the ROM at filepath 'Import ROM', When exporting to a ROM, the plugin will make a copy of the file at 'Export ROM', modify it, and save the file to 'Output ROM'. The ROM must be expanded.
### Where Can I Export To?
By default, when exporting to ROM, the plugin will copy the contents of bank 4 to the range (0x11A35B8 - 0x11FFF00). This will leave you with free space to export assets to range (0x11D8930 - 0x11FFF00).
### Vertex Colors
To use vertex colors, select the "Vertex Colored Texture" preset and add two vertex color layers to your mesh named 'Col' and 'Alpha'. The alpha layer will use the greyscale value of the vertex color to determine alpha.
### F3D Materials
Any exported meshes must have an F3D Material, which can be added by the 'Create F3D Material' button in the material inspector window.
### RDP State Optimization
A material will not set every single geometry mode / other mode option. Instead, each property will be checked against the default settings located in the world tab in the properties sidebar. If the values are the same, then the code to change it is not added.
### Geolayouts
There are many different geolayout node types. In Blender, each node type is represented by a bone group. Imported geolayouts will already have these bone groups, but if you want to add them to your own armature use the 'Add Bone Groups' operator in the SM64 Armature Tools header. All regular bones (aka 0x13 commands) and 0x15 command bones are placed on armature layer 0. All other geolayout commands are placed on layer 1. Metarig generated bones are placed on layers 3 and 4 (see 'Animating Existing Geolayouts'). If you change a bone's geolayout command type and it dissapears, it might have been moved to a different layer. Make sure to go to the armature properties editor and set both layers 0 and 1 to be visible. Geolayout command types can be editted in the properties editor under the bone tab in pose mode.
### Exporting Geolayouts and Skinned Meshes
The N64 supports binary skinning, meaning each vertex will be influenced by one bone only. When skinning an exported geolayout, do NOT use automatic skinning, as this results in a smooth weight falloff. Instead, when weight painting set the weight to either 1 or 0, and set the brush Falloff to square. Skinning can only occur between an immediate parent and a child deform bone, not between siblings / across ancestors.
### Replacing Existing SM64 Geolayout Geometry
SM64 geolayouts are often in strange rest poses, which makes it hard to modify their geometry. It often helps to import an animation belonging to that geolayout to see what the idle pose of a geolayout should be. Once you know, you can rotate the bones of the armature in pose mode to a usable position and then use the 'Apply as Rest Pose' operator under the SM64 Armature Tools header. Skin your new mesh to that armature, then rotate the bones back to the original position and use 'Apply as Rest Pose' again. You can now export the geolayout to SM64 and it will be able to use existing animations.
### Importing An Animation To Blender
To import an animation to blender, select an armature for the animation to be exported to, and press 'Import animation'. Note that the armature's root 0x13 (i.e. regular) bone must be named 'root'. Animations can be found by looking at behaviour scripts where 27 and 28 commands are.
### Exporting Mario Animations
Mario animations use a DMA table, which contains 8 byte entries of (offset from table start, animation size). Documentation about this table is here:
https://dudaw.webs.com/sm64docs/sm64_marios_animation_table.txt
Basically, Mario's DMA table starts at 0x4EC000. There is an 8 byte header, and then the animation entries afterward. Thus the 'climb up ledge' DMA entry is at 0x4EC008. The first 8 bytes at that address indicate the offset from 0x4EC000 at which the actual animation exists. Using this table you can find animations you want to overwrite.
### Animating Existing Geolayouts
Often times it is hard to rig an existing SM64 geolayout, as there are many intermediate non-deform bones and bones don't point to their children. To make this easier you can use the 'Create Animatable Metarig' operator in the SM64 Armature Tools header. This will generate a metarig which can be used with IK. The metarig bones will be placed on armature layers 3 and 4.
### Exporting Geolayouts to C
When you export in C, there will be 3 paths. Geo Filepath is for geolayout data. DL Filepath is for f3d data. Definitions Filepath is for extern definitions of objects.
To replace a model in decomp, replace its geo.inc.c and model.inc.c contents with the geolayout file and the dl file respectively. Use the contents of the define file to replace existing defines in one of the group header files (ex. mario is in group0.h). Make sure that the name of your geolayout is the same the name of the geolayout you're replacing.
### Switch Statements
To add a switch mesh node, duplicate your switch bone and move it off to the side. Set the bone geolayout command to be Switch Option. Any nodes in this switch option must be children of this bone. Add any other bones to this bone. Add your switch geometry into your existing mesh in the correct position and skin it.
### Common Issues
Inivisible Mesh : Bone is not set to deform, or geometry is skinned to more than one bone.
Game crashes: Invalid function address for switch/function/held object bones.
+1899
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
from .f3d_parser import *
from .sm64_geolayout_writer import *
from .sm64_geolayout_parser import *
from .sm64_level_parser import *
from .sm64_constants import *
from .sm64_rom_tweaks import *
from .sm64_anim import *
from .f3d_material import *
from .sm64_geolayout_bone import *
from .sm64_collision import *
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+287
View File
@@ -0,0 +1,287 @@
combiner_enums = {
'Case A' : (
('COMBINED', 'Combined Color', 'Combined Color'),
('TEXEL0', 'Texture 0', 'Texture 0'),
('TEXEL1', 'Texture 1', 'Texture 1'),
('PRIMITIVE', 'Primitive Color', 'Primitive Color'),
('SHADE', 'Shade Color', 'Shade Color'),
('ENVIRONMENT', 'Environment Color', 'Environment Color'),
('1', '1', '1'),
('NOISE', 'Noise', 'Noise'),
('0', '0', '0'),
),
'Case B' : (
('COMBINED', 'Combined Color', 'Combined Color'),
('TEXEL0', 'Texture 0', 'Texture 0'),
('TEXEL1', 'Texture 1', 'Texture 1'),
('PRIMITIVE', 'Primitive Color', 'Primitive Color'),
('SHADE', 'Shade Color', 'Shade Color'),
('ENVIRONMENT', 'Environment Color', 'Environment Color'),
('CENTER', 'Chroma Key Center', 'Chroma Key Center'),
('K4', 'YUV Convert K4', 'YUV Convert K4'),
('0', '0', '0')
),
'Case C' : (
('COMBINED' , 'Combined Color', 'Combined Color'),
('TEXEL0' , 'Texture 0', 'Texture 0'),
('TEXEL1' , 'Texture 1', 'Texture 1'),
('PRIMITIVE' , 'Primitive Color', 'Primitive Color'),
('SHADE' , 'Shade Color', 'Shade Color'),
('ENVIRONMENT' , 'Environment Color', 'Environment Color'),
('SCALE' , 'Chroma Key Scale', 'Chroma Key Scale'),
('COMBINED_ALPHA' , 'Combined Color Alpha', 'Combined Color Alpha'),
('TEXEL0_ALPHA' , 'Texture 0 Alpha', 'Texture 0 Alpha'),
('TEXEL1_ALPHA' , 'Texture 1 Alpha', 'Texture 1 Alpha'),
('PRIMITIVE_ALPHA' , 'Primitive Color Alpha', 'Primitive Color Alpha'),
('SHADE_ALPHA' , 'Shade Color Alpha', 'Shade Color Alpha'),
('ENV_ALPHA' , 'Environment Color Alpha', 'Environment Color Alpha'),
('LOD_FRACTION' , 'LOD Fraction', 'LOD Fraction'),
('PRIM_LOD_FRAC' , 'Primitive LOD Fraction', 'Primitive LOD Fraction'),
('K5' , 'YUV Convert K5', 'YUV Convert K5'),
('0' , '0', '0'),
),
'Case D' : (
('COMBINED', 'Combined Color', 'Combined Color'),
('TEXEL0', 'Texture 0', 'Texture 0'),
('TEXEL1', 'Texture 1', 'Texture 1'),
('PRIMITIVE', 'Primitive Color', 'Primitive Color'),
('SHADE', 'Shade Color', 'Shade Color'),
('ENVIRONMENT', 'Environment Color', 'Environment Color'),
('1', '1', '1'),
('0', '0', '0'),
),
'Case A Alpha' : (
('COMBINED', 'Combined Color Alpha', 'Combined Color Alpha'),
('TEXEL0', 'Texture 0 Alpha', 'Texture 0 Alpha'),
('TEXEL1', 'Texture 1 Alpha', 'Texture 1 Alpha'),
('PRIMITIVE', 'Primitive Color Alpha', 'Primitive Color Alpha'),
('SHADE', 'Shade Color Alpha', 'Shade Color Alpha'),
('ENVIRONMENT', 'Environment Color Alpha', 'Environment Color Alpha'),
('1', '1', '1'),
('0', '0', '0'),
),
'Case B Alpha' : (
('COMBINED', 'Combined Color Alpha', 'Combined Color Alpha'),
('TEXEL0', 'Texture 0 Alpha', 'Texture 0 Alpha'),
('TEXEL1', 'Texture 1 Alpha', 'Texture 1 Alpha'),
('PRIMITIVE', 'Primitive Color Alpha', 'Primitive Color Alpha'),
('SHADE', 'Shade Color Alpha', 'Shade Color Alpha'),
('ENVIRONMENT', 'Environment Color Alpha', 'Environment Color Alpha'),
('1', '1', '1'),
('0', '0', '0'),
),
'Case C Alpha' : (
('LOD_FRACTION', 'LOD Fraction', 'LOD Fraction'),
('TEXEL0', 'Texture 0 Alpha', 'Texture 0 Alpha'),
('TEXEL1', 'Texture 1 Alpha', 'Texture 1 Alpha'),
('PRIMITIVE', 'Primitive Color Alpha', 'Primitive Color Alpha'),
('SHADE', 'Shade Color Alpha', 'Shade Color Alpha'),
('ENVIRONMENT', 'Environment Color Alpha', 'Environment Color Alpha'),
('PRIM_LOD_FRAC', 'Primitive LOD Fraction', 'Primitive LOD Fraction'),
('0', '0', '0'),
),
'Case D Alpha' : (
('COMBINED', 'Combined Color Alpha', 'Combined Color Alpha'),
('TEXEL0', 'Texture 0 Alpha', 'Texture 0 Alpha'),
('TEXEL1', 'Texture 1 Alpha', 'Texture 1 Alpha'),
('PRIMITIVE', 'Primitive Color Alpha', 'Primitive Color Alpha'),
('SHADE', 'Shade Color Alpha', 'Shade Color Alpha'),
('ENVIRONMENT', 'Environment Color Alpha', 'Environment Color Alpha'),
('1', '1', '1'),
('0', '0', '0'),
),
}
caseTemplateDict = {
'Case A' : 'Fast3D_A',
'Case B' : 'Fast3D_B',
'Case C' : 'Fast3D_C',
'Case D' : 'Fast3D_D',
'Case A Alpha' : 'Fast3D_A_alpha',
'Case B Alpha' : 'Fast3D_B_alpha',
'Case C Alpha' : 'Fast3D_C_alpha',
'Case D Alpha' : 'Fast3D_D_alpha',
}
# hardware v2
enumAlphaDither = [
('G_AD_PATTERN', 'Pattern', 'Pattern'),
('G_AD_NOTPATTERN', 'NOT Pattern', 'NOT Pattern'),
('G_AD_NOISE', 'Noise', 'Noise'),
('G_AD_DISABLE', 'Disable', 'Disable'),
]
# hardware v2
enumRGBDither = [
('G_CD_MAGICSQ', 'Magic Square', 'Magic Square'),
('G_CD_BAYER', 'Bayer', 'Bayer'),
('G_CD_NOISE', 'Noise', 'Noise'),
('G_CD_DISABLE', 'Disable', 'Disable'),
('G_CD_ENABLE', 'Enable', 'Enable')
]
enumCombKey = [
('G_CK_NONE', 'None', 'None'),
('G_CK_KEY', 'Key', 'Key'),
]
enumTextConv = [
('G_TC_CONV', 'Convert', 'Convert'),
('G_TC_FILTCONV', 'Filter And Convert', 'Filter And Convert'),
('G_TC_FILT', 'Filter', 'Filter')
]
enumTextFilt = [
('G_TF_POINT', 'Point', 'Point'),
('G_TF_AVERAGE', 'Average', 'Average'),
('G_TF_BILERP', 'Bilinear', 'Bilinear'),
]
enumTextLUT = [
('G_TT_NONE', 'None', 'None'),
('G_TT_RGBA16', 'RGBA16', 'RGBA16'),
('G_TT_IA16', 'IA16', 'IA16'),
]
enumTextLOD = [
('G_TL_TILE', 'Tile', 'Tile'),
('G_TL_LOD', 'LOD', 'LOD'),
]
enumTextDetail = [
('G_TD_CLAMP', 'Clamp', 'Clamp'),
('G_TD_SHARPEN', 'Sharpen', 'Sharpen'),
('G_TD_DETAIL', 'Detail', 'Detail'),
]
enumTextPersp = [
('G_TP_NONE', 'None', 'None'),
('G_TP_PERSP', 'Perspective', 'Perspective'),
]
enumCycleType = [
('G_CYC_1CYCLE', '1 Cycle', '1 Cycle'),
('G_CYC_2CYCLE', '2 Cycle', '2 Cycle'),
('G_CYC_COPY', 'Copy', 'Copy'),
('G_CYC_FILL', 'Fill', 'Fill'),
]
enumColorDither = [
('G_CD_DISABLE', 'Disable', 'Disable'),
('G_CD_ENABLE', 'Enable', 'Enable')
]
enumPipelineMode = [
('G_PM_1PRIMITIVE', '1 Primitive', '1 Primitive'),
('G_PM_NPRIMITIVE', 'N Primitive', 'N Primitive')
]
enumAlphaCompare = [
('G_AC_NONE', 'None', 'None'),
('G_AC_THRESHOLD', 'Threshold', 'Threshold'),
('G_AC_DITHER', 'Dither', 'Dither'),
]
enumDepthSource = [
('G_ZS_PIXEL', 'Pixel', 'Pixel'),
('G_ZS_PRIM', 'Primitive', 'Primitive'),
]
enumCoverage = [
('CVG_DST_CLAMP', 'Clamp', 'Clamp'),
('CVG_DST_WRAP', 'Wrap', 'Wrap'),
('CVG_DST_FULL', 'Full', 'Full'),
('CVG_DST_SAVE', 'Save', 'Save')
]
enumZMode = [
('ZMODE_OPA', 'Opaque', 'Opaque'),
('ZMODE_INTER', 'Interpenetrating', 'Interpenetrating'),
('ZMODE_XLU', 'Transparent (XLU)', 'Transparent (XLU)'),
('ZMODE_DEC', 'Decal', 'Decal')
]
enumBlendColor = [
('G_BL_CLR_IN', 'Input', 'Input'),
('G_BL_CLR_MEM', 'Memory', 'Memory'),
('G_BL_CLR_BL', 'Blend', 'Blend'),
('G_BL_CLR_FOG', 'Fog', 'Fog')
]
enumBlendAlpha = [
('G_BL_A_IN', 'Input', 'Input'),
('G_BL_A_FOG', 'Fog', 'Fog'),
('G_BL_A_SHADE', 'Shade', 'Shade'),
('G_BL_0', '0', '0')
]
enumBlendMix = [
('G_BL_1MA', '1 - Source Alpha', '1 - Source Alpha'),
('G_BL_A_MEM', 'Memory Alpha', 'Memory Alpha'),
('G_BL_1', '1', '1'),
('G_BL_0', '0', '0')
]
enumTexFormat = [
('I4','Intensity 4-bit', 'Intensity 4-bit'),
('I8','Intensity 8-bit', 'Intensity 8-bit'),
('IA4','Intensity Alpha 4-bit', 'Intensity Alpha 4-bit'),
('IA8','Intensity Alpha 8-bit', 'Intensity Alpha 8-bit'),
('IA16','Intensity Alpha 16-bit', 'Intensity Alpha 16-bit'),
('CI4','Color Index 4-bit', 'Color Index 4-bit'),
('CI8','Color Index 8-bit', 'Color Index 8-bit'),
('RGBA16','RGBA 16-bit', 'RGBA 16-bit'),
('RGBA32','RGBA 32-bit', 'RGBA 32-bit'),
#('YUV16','YUV 16-bit', 'YUV 16-bit'),
]
enumCIFormat = [
('RGBA16','RGBA 16-bit', 'RGBA 16-bit'),
('IA16','Intensity Alpha 16-bit', 'Intensity Alpha 16-bit'),
]
enumTexUV = [
('TEXEL0', 'Texture 0', 'Texture 0'),
('TEXEL1', 'Texture 1', 'Texture 1'),
]
texBitSize = {
'I4' : 4,
'IA4' : 4,
'CI4' : 4,
'I8' : 8,
'IA8' : 8,
'CI8' : 8,
'RGBA16' : 16,
'IA16' : 16,
'YUV16' : 16,
'RGBA32' : 32,
}
# 512 words * 64 bits / n bitSize
maxTexelCount = {
32 : 1024,
16 : 2048,
8 : 4096,
4 : 8192,
}
enumF3D = [
#('F3D_GBI', 'Fast3D', "Fast3D"),
#('F3DEX_GBI', 'F3DEX', "F3DEX"),
#('F3DEX_GBI_2', 'F3DEX2', "F3DEX2"),
('F3D', 'F3D', 'F3D'),
('F3DEX/LX', 'F3DEX/LX', 'F3DEX/LX'),
('F3DLX.Rej', 'F3DLX.Rej', 'F3DLX.Rej'),
('F3DLP.Rej', 'F3DLP.Rej', 'F3DLP.Rej'),
('F3DEX2/LX2', 'F3DEX2/LX2', 'F3DEX2/LX2'),
('F3DEX2.Rej/LX2.Rej', 'F3DEX2.Rej/LX2.Rej', 'F3DEX2.Rej/LX2.Rej'),
]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+333
View File
@@ -0,0 +1,333 @@
import bmesh
import bpy
import mathutils
import pprint
from .f3d_gbi import *
from .utility import *
from .sm64_constants import *
def getAxisVector(enumValue):
sign = -1 if enumValue[0] == '-' else 1
axis = enumValue[0] if sign == 1 else enumValue[1]
return (
sign if axis == 'X' else 0,
sign if axis == 'Y' else 0,
sign if axis == 'Z' else 0
)
def getExportRotation(forwardAxisEnum, convertTransformMatrix):
if 'Z' in forwardAxisEnum:
print("Z axis reserved for verticals.")
return None
elif forwardAxisEnum == 'X':
rightAxisEnum = '-Y'
elif forwardAxisEnum == '-Y':
rightAxisEnum = '-X'
elif forwardAxisEnum == '-X':
rightAxisEnum = 'Y'
else:
rightAxisEnum = 'X'
forwardAxis = getAxisVector(forwardAxisEnum)
rightAxis = getAxisVector(rightAxisEnum)
upAxis = (0, 0, 1)
# Z assumed to be up
columns = [rightAxis, forwardAxis, upAxis]
localToBlenderRotation = mathutils.Matrix([
[col[0] for col in columns],
[col[1] for col in columns],
[col[2] for col in columns]
]).to_quaternion()
return convertTransformMatrix.to_quaternion() @ localToBlenderRotation
def F3DtoBlenderObject(romfile, startAddress, scene,
newname, transformMatrix,
segmentData, shadeSmooth):
mesh = bpy.data.meshes.new(newname + '-mesh')
obj = bpy.data.objects.new(newname, mesh)
scene.collection.objects.link(obj)
createBlankMaterial(obj)
bMesh = bmesh.new()
bMesh.from_mesh(mesh)
parseF3D(romfile, startAddress, scene, bMesh, obj, \
transformMatrix, newname, segmentData, \
[None] * 16 * 16)
#bmesh.ops.rotate(bMesh, cent = [0,0,0],
# matrix = blenderToSM64Rotation,
# verts = bMesh.verts)
bMesh.to_mesh(mesh)
bMesh.free()
mesh.update()
if shadeSmooth:
bpy.ops.object.select_all(action = 'DESELECT')
obj.select_set(True)
bpy.ops.object.shade_smooth()
return obj
def getOrMakeVertexGroup(obj, groupName):
for group in obj.vertex_groups:
if group.name == groupName:
return group
return obj.vertex_groups.new(name = groupName)
def cmdToPositiveInt(cmd):
return cmd if cmd >= 0 else 256 + cmd
def parseF3D(romfile, startAddress, scene,
bMesh, obj, transformMatrix, groupName, segmentData, vertexBuffer):
f3d = F3D('F3D', False)
currentAddress = startAddress
romfile.seek(currentAddress)
command = romfile.read(8)
faceSeq = bMesh.faces
vertSeq = bMesh.verts
uv_layer = bMesh.loops.layers.uv.verify()
deform_layer = bMesh.verts.layers.deform.verify()
vertexGroup = getOrMakeVertexGroup(obj, groupName)
groupIndex = vertexGroup.index
textureSize = [32, 32]
currentTextureAddr = -1
jumps = [startAddress]
# Used for remove_double op at end
vertList = []
while len(jumps) > 0:
# FD, FC, B7 (tex, shader, geomode)
#print(format(command[0], '#04x') + ' at ' + hex(currentAddress))
if command[0] == cmdToPositiveInt(f3d.G_TRI1):
try:
newVerts = interpretDrawTriangle(command, vertexBuffer,
faceSeq, vertSeq, uv_layer, deform_layer, groupIndex)
vertList.extend(newVerts)
except TypeError:
print("Ignoring triangle from unloaded vertices.")
elif command[0] == cmdToPositiveInt(f3d.G_VTX):
interpretLoadVertices(romfile, vertexBuffer, transformMatrix,
command, segmentData)
elif command[0] == cmdToPositiveInt(f3d.G_SETTILESIZE):
textureSize = interpretSetTileSize(
int.from_bytes(command[4:8], 'big'))
elif command[0] == cmdToPositiveInt(f3d.G_DL):
if command[1] == 0:
jumps.append(currentAddress)
currentAddress = decodeSegmentedAddr(command[4:8],
segmentData = segmentData)
romfile.seek(currentAddress)
command = romfile.read(8)
continue
elif command[0] == cmdToPositiveInt(f3d.G_ENDDL):
currentAddress = jumps.pop()
elif command[0] == cmdToPositiveInt(f3d.G_SETGEOMETRYMODE):
pass
elif command[0] == cmdToPositiveInt(f3d.G_SETCOMBINE):
pass
elif command[0] == cmdToPositiveInt(f3d.G_SETTIMG):
currentTextureAddr =\
interpretSetTImage(command, segmentData)
elif command[0] == cmdToPositiveInt(f3d.G_LOADBLOCK):
# for now only 16bit RGBA is supported.
interpretLoadBlock(command, romfile, currentTextureAddr, textureSize,
'RGBA', 16)
elif command[0] == cmdToPositiveInt(f3d.G_SETTILE):
interpretSetTile(int.from_bytes(command[4:8], 'big'), None)
else:
pass
#print(format(command[0], '#04x') + ' at ' + hex(currentAddress))
currentAddress += 8
romfile.seek(currentAddress)
command = romfile.read(8)
bmesh.ops.remove_doubles(bMesh, verts = vertList, dist = 0.0001)
return vertexBuffer
def getPosition(vertexBuffer, index):
xStart = index * 16 + 0
yStart = index * 16 + 2
zStart = index * 16 + 4
xBytes = vertexBuffer[xStart : xStart + 2]
yBytes = vertexBuffer[yStart : yStart + 2]
zBytes = vertexBuffer[zStart : zStart + 2]
x = int.from_bytes(xBytes, 'big', signed=True) * sm64ToBlenderScale
y = int.from_bytes(yBytes, 'big', signed=True) * sm64ToBlenderScale
z = int.from_bytes(zBytes, 'big', signed=True) * sm64ToBlenderScale
return (x, y, z)
def getNormalorColor(vertexBuffer, index, isNormal = True):
xByte = bytes([vertexBuffer[index * 16 + 12]])
yByte = bytes([vertexBuffer[index * 16 + 13]])
zByte = bytes([vertexBuffer[index * 16 + 14]])
wByte = bytes([vertexBuffer[index * 16 + 15]])
if isNormal:
x = int.from_bytes(xByte, 'big', signed=True)
y = int.from_bytes(yByte, 'big', signed=True)
z = int.from_bytes(zByte, 'big', signed=True)
return (x,y,z)
else: # vertex color
r = int.from_bytes(xByte, 'big') / 255
g = int.from_bytes(yByte, 'big') / 255
b = int.from_bytes(zByte, 'big') / 255
a = int.from_bytes(wByte, 'big') / 255
return (r,g,b,a)
def getUV(vertexBuffer, index, textureDimensions = [32,32]):
uStart = index * 16 + 8
vStart = index * 16 + 10
uBytes = vertexBuffer[uStart : uStart + 2]
vBytes = vertexBuffer[vStart : vStart + 2]
u = int.from_bytes(uBytes, 'big', signed = True) / 32
v = int.from_bytes(vBytes, 'big', signed = True) / 32
# We don't know texture size, so assume 32x32.
u /= textureDimensions[0]
v /= textureDimensions[1]
v = 1 - v
return (u,v)
def interpretSetTile(data, texture):
clampMirrorFlags = bitMask(data, 18, 2)
def interpretSetTileSize(data):
hVal = bitMask(data, 0, 12)
wVal = bitMask(data, 12, 12)
height = hVal >> 2 + 1
width = wVal >> 2 + 1
return (width, height)
def interpretLoadVertices(romfile, vertexBuffer, transformMatrix, command,
segmentData = None):
command = int.from_bytes(command, 'big', signed=True)
numVerts = bitMask(command, 52, 4) + 1
startIndex = bitMask(command, 48, 4)
dataLength = bitMask(command, 32, 16)
segmentedAddr = bitMask(command, 0, 32)
dataStartAddr = decodeSegmentedAddr(segmentedAddr.to_bytes(4, 'big'),
segmentData = segmentData)
romfile.seek(dataStartAddr)
data = romfile.read(dataLength)
for i in range(numVerts):
vert = mathutils.Vector(readVectorFromShorts(data, i * 16))
vert = transformMatrix @ vert
transformedVert = bytearray(6)
writeVectorToShorts(transformedVert, 0, vert)
start = (startIndex + i) * 16
vertexBuffer[start: start + 6] = transformedVert
vertexBuffer[start + 6: start + 16] = data[i * 16 + 6: i * 16 + 16]
# Note the divided by 0x0A, which is due to the way BF command stores indices.
# Without this the triangles are drawn incorrectly.
def interpretDrawTriangle(command, vertexBuffer,
faceSeq, vertSeq, uv_layer, deform_layer, groupIndex):
verts = [None, None, None]
index0 = int(command[5] / 0x0A)
index1 = int(command[6] / 0x0A)
index2 = int(command[7] / 0x0A)
vert0 = mathutils.Vector(getPosition(vertexBuffer, index0))
vert1 = mathutils.Vector(getPosition(vertexBuffer, index1))
vert2 = mathutils.Vector(getPosition(vertexBuffer, index2))
verts[0] = vertSeq.new(vert0)
verts[1] = vertSeq.new(vert1)
verts[2] = vertSeq.new(vert2)
tri = faceSeq.new(verts)
# Assign vertex group
for vert in tri.verts:
vert[deform_layer][groupIndex] = 1
loopIndex = 0
for loop in tri.loops:
loop[uv_layer].uv = mathutils.Vector(
getUV(vertexBuffer, int(command[5 + loopIndex] / 0x0A)))
loopIndex += 1
return verts
def interpretSetTImage(command, levelData):
segmentedAddr = command[4:8]
return decodeSegmentedAddr(segmentedAddr, levelData)
def interpretLoadBlock(command, romfile, textureStart, textureSize, colorFormat, colorDepth):
numTexels = ((int.from_bytes(command[6:8], 'big')) >> 12) + 1
# This is currently broken.
#createNewTextureMaterial(romfile, textureStart, textureSize, numTexels, colorFormat, colorDepth, obj)
def printvbuf(vertexBuffer):
for i in range(0, int(len(vertexBuffer) / 16)):
print(getPosition(vertexBuffer, i))
print(getNormalorColor(vertexBuffer, i))
print(getUV(vertexBuffer, i))
def createBlankMaterial(obj):
newMat = bpy.data.materials.new('sm64_material')
obj.data.materials.append(newMat)
def createNewTextureMaterial(romfile, textureStart, textureSize, texelCount, colorFormat, colorDepth, obj):
newMat = bpy.data.materials.new('sm64_material')
newTex = bpy.data.textures.new('sm64_texture', 'IMAGE')
newImg = bpy.data.images.new('sm64_image', *textureSize, True, True)
newTex.image = newImg
newSlot = newMat.texture_slots.add()
newSlot.texture = newTex
obj.data.materials.append(newMat)
romfile.seek(textureStart)
texelSize = int(colorDepth / 8)
dataLength = texelCount * texelSize
textureData = romfile.read(dataLength)
if colorDepth != 16:
print("Warning: Only 16bit RGBA supported, input was " + \
str(colorDepth) + 'bit ' + colorFormat)
else:
print(str(texelSize) + " " + str(colorDepth))
for n in range(0, dataLength, texelSize):
oldPixel = textureData[n : n + texelSize]
newImg.pixels[n : n+4] = read16bitRGBA(
int.from_bytes(oldPixel, 'big'))
File diff suppressed because it is too large Load Diff
+501
View File
@@ -0,0 +1,501 @@
import bpy
import mathutils
import math
import re
from .utility import *
from .sm64_constants import *
from math import pi
sm64_anim_types = {'ROTATE', 'TRANSLATE'}
class SM64_Animation:
def __init__(self, name):
self.name = name
self.header = None
self.indices = SM64_ShortArray(name + '_indices', False)
self.values = SM64_ShortArray(name + '_values', False)
def to_binary(self, segmentData, isDMA, startAddress):
return self.header.to_binary(segmentData, isDMA, startAddress) + \
self.indices.to_binary() + \
self.values.to_binary()
def to_c(self):
return self.values.to_c() + '\n' +\
self.indices.to_c() + '\n' +\
self.header.to_c() + '\n'
class SM64_ShortArray:
def __init__(self, name, signed):
self.name = name
self.shortData = []
self.signed = signed
def to_binary(self):
data = bytearray(0)
for short in self.shortData:
#print(self.name + str(short))
data += short.to_bytes(2, 'big', signed = self.signed)
return data
def to_c(self):
data = 'static const ' + ('s' if self.signed else 'u') + \
'16 ' + self.name + '[] = {\n\t'
wrapCounter = 0
for short in self.shortData:
data += '0x' + format(short, '04X') + ', '
wrapCounter += 1
if wrapCounter > 8:
data += '\n\t'
wrapCounter = 0
data += '\n};\n'
return data
class SM64_AnimationHeader:
def __init__(self, name, repetitions, marioYOffset, frameInterval,
nodeCount, transformValuesStart, transformIndicesStart, animSize):
self.name = name
self.repetitions = repetitions
self.marioYOffset = marioYOffset
self.frameInterval = frameInterval
self.nodeCount = nodeCount
self.transformValuesStart = transformValuesStart
self.transformIndicesStart = transformIndicesStart
self.animSize = animSize # DMA animations only
self.transformIndices = []
# presence of segmentData indicates DMA.
def to_binary(self, segmentData, isDMA, startAddress):
if isDMA:
transformValuesStart = self.transformValuesStart
transformIndicesStart = self.transformIndicesStart
else:
transformValuesStart = self.transformValuesStart + startAddress
transformIndicesStart = self.transformIndicesStart + startAddress
data = bytearray(0)
data.extend(self.repetitions.to_bytes(2, byteorder='big'))
data.extend(self.marioYOffset.to_bytes(2, byteorder='big')) # y offset, only used for mario
data.extend([0x00, 0x00]) # unknown, common with secondary anims, variable length animations?
data.extend(int(round(self.frameInterval[0])).to_bytes(2, byteorder='big'))
data.extend(int(round(self.frameInterval[1] - 1)).to_bytes(2, byteorder='big'))
data.extend(self.nodeCount.to_bytes(2, byteorder='big'))
if not isDMA:
data.extend(encodeSegmentedAddr(transformValuesStart, segmentData))
data.extend(encodeSegmentedAddr(transformIndicesStart, segmentData))
data.extend(bytearray([0x00] * 6))
else:
data.extend(transformValuesStart.to_bytes(4, byteorder='big'))
data.extend(transformIndicesStart.to_bytes(4, byteorder='big'))
data.extend(self.animSize.to_bytes(4, byteorder='big'))
data.extend(bytearray([0x00] * 2))
return data
def to_c(self):
data = 'static const struct Animation ' + self.name + '[] = {\n' +\
'\t' + str(self.repetitions) + ',\n' + \
'\t' + str(self.marioYOffset) + ',\n' + \
'\t0,\n' + \
'\t' + str(int(round(self.frameInterval[0]))) + ',\n' + \
'\t' + str(int(round(self.frameInterval[1] - 1))) + ',\n' + \
'\tANIMINDEX_NUMPARTS(' + self.name + '_indices),\n' + \
'\t' + self.name + '_values,\n' + \
'\t' + self.name + '_indices,\n' + \
'\t0,\n' + \
'};\n'
return data
class SM64_AnimIndexNode:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
class SM64_AnimIndex:
def __init__(self, numFrames, startOffset):
self.startOffset = startOffset
self.numFrames = numFrames
def getLastKeyframeTime(keyframes):
last = keyframes[0].co[0]
for keyframe in keyframes:
if keyframe.co[0] > last:
last = keyframe.co[0]
return last
def exportAnimationC(armatureObj, loopAnim, filePath):
sm64_anim = exportAnimationCommon(armatureObj, loopAnim)
data = sm64_anim.to_c()
outFile = open(filePath, 'w')
outFile.write(data)
outFile.close()
def exportAnimationBinary(romfile, exportRange, armatureObj, DMAAddresses,
segmentData, isDMA, loopAnim):
startAddress = get64bitAlignedAddr(exportRange[0])
sm64_anim = exportAnimationCommon(armatureObj, loopAnim)
animData = sm64_anim.to_binary(segmentData, isDMA, startAddress)
if startAddress + len(animData) > exportRange[1]:
raise ValueError('Size too big: Data ends at ' + \
hex(startAddress + len(animData)) +\
', which is larger than the specified range.')
romfile.seek(startAddress)
romfile.write(animData)
addrRange = (startAddress, startAddress + len(animData))
if not isDMA:
animTablePointer = get64bitAlignedAddr(startAddress + len(animData))
romfile.seek(animTablePointer)
romfile.write(encodeSegmentedAddr(startAddress, segmentData))
return addrRange, animTablePointer
else:
if DMAAddresses is not None:
romfile.seek(DMAAddresses['entry'])
romfile.write((startAddress - DMAAddresses['start']).to_bytes(
4, byteorder = 'big'))
romfile.seek(DMAAddresses['entry'] + 4)
romfile.write(len(animData).to_bytes(4, byteorder='big'))
return addrRange, None
def exportAnimationCommon(armatureObj, loopAnim):
if armatureObj.animation_data is None or \
armatureObj.animation_data.action is None:
raise ValueError("No active animation selected.")
anim = armatureObj.animation_data.action
sm64_anim = SM64_Animation(toAlnum(anim.name))
nodeCount = len(armatureObj.data.bones)
translationData, armatureFrameData = convertAnimationData(anim, armatureObj)
repetitions = 0 if loopAnim else 1
marioYOffset = 0x00 # ??? Seems to be this value for most animations
frameInterval = [int(round(anim.frame_range[0])),
int(round(anim.frame_range[1])) + 1]
transformValuesOffset = 0
headerSize = 0x1A
transformIndicesStart = headerSize #0x18 if including animSize?
# all node rotations + root translation
# *3 for each property (xyz) and *4 for entry size
# each keyframe stored as 2 bytes
# transformValuesStart = transformIndicesStart + (nodeCount + 1) * 3 * 4
transformValuesStart = transformIndicesStart
for translationFrameProperty in translationData:
frameCount = len(translationFrameProperty)
sm64_anim.indices.shortData.append(frameCount)
sm64_anim.indices.shortData.append(transformValuesOffset)
if(transformValuesOffset) > 2**16 - 1:
raise ValueError('Animation is too large.')
transformValuesOffset += frameCount
transformValuesStart += 4
for value in translationFrameProperty:
sm64_anim.values.shortData.append(int.from_bytes(value.to_bytes(2,'big', signed = True), byteorder = 'big', signed = False))
for boneFrameData in armatureFrameData:
for boneFrameDataProperty in boneFrameData:
frameCount = len(boneFrameDataProperty)
sm64_anim.indices.shortData.append(frameCount)
sm64_anim.indices.shortData.append(transformValuesOffset)
if(transformValuesOffset) > 2**16 - 1:
raise ValueError('Animation is too large.')
transformValuesOffset += frameCount
transformValuesStart += 4
for value in boneFrameDataProperty:
sm64_anim.values.shortData.append(value)
animSize = headerSize + len(sm64_anim.indices.shortData) * 2 + \
len(sm64_anim.values.shortData) * 2
sm64_anim.header = SM64_AnimationHeader(sm64_anim.name, repetitions,
marioYOffset, frameInterval, nodeCount, transformValuesStart,
transformIndicesStart, animSize)
return sm64_anim
def saveQuaternionFrame(frameData, rotation):
for i in range(3):
field = rotation.to_euler()[i]
value = (math.degrees(field) % 360 ) / 360
frameData[i].append(min(int(round(value * (2**16 - 1))), 2**16 - 1))
def removeTrailingFrames(frameData):
for i in range(3):
if len(frameData[i]) < 2:
continue
lastUniqueFrame = len(frameData[i]) - 1
while lastUniqueFrame > 0:
if frameData[i][lastUniqueFrame] == \
frameData[i][lastUniqueFrame - 1]:
lastUniqueFrame -= 1
else:
break
frameData[i] = frameData[i][:lastUniqueFrame + 1]
def saveTranslationFrame(frameData, translation):
for i in range(3):
frameData[i].append(min(int(round(translation[i] / sm64ToBlenderScale)),
2**16 - 1))
def convertAnimationData(anim, armatureObj):
if 'root' not in armatureObj.data.bones:
raise ValueError('Cannot find bone named "root". The first animatable' +\
' (0x13) bone in the armature must be named "root."')
currentBone = armatureObj.data.bones["root"]
bonesToProcess = [currentBone.name]
animBones = []
# Get animation bones in order
while len(bonesToProcess) > 0:
boneName = bonesToProcess[0]
currentBone = armatureObj.data.bones[boneName]
currentPoseBone = armatureObj.pose.bones[boneName]
bonesToProcess = bonesToProcess[1:]
# Only handle 0x13 bones for animation
if currentPoseBone.bone_group is None:
animBones.append(boneName)
# Traverse children in alphabetical order.
childrenNames = sorted([bone.name for bone in currentBone.children])
bonesToProcess = childrenNames + bonesToProcess
# list of boneFrameData, which is [[x frames], [y frames], [z frames]]
translationData = [[],[],[]]
armatureFrameData = []
for i in range(len(animBones)):
armatureFrameData.append([[],[],[]])
for frame in range(int(round(anim.frame_range[1])) + 1):
bpy.context.scene.frame_set(frame)
translation = armatureObj.pose.bones["root"].location
saveTranslationFrame(translationData, translation)
for boneIndex in range(len(animBones)):
boneName = animBones[boneIndex]
currentBone = armatureObj.data.bones[boneName]
currentPoseBone = armatureObj.pose.bones[boneName]
rotationValue = currentPoseBone.matrix.to_quaternion()
if currentBone.parent is not None:
rotationValue = (currentPoseBone.parent.matrix.inverted() @ \
currentPoseBone.matrix).to_quaternion()
saveQuaternionFrame(armatureFrameData[boneIndex], rotationValue)
removeTrailingFrames(translationData)
for frameData in armatureFrameData:
removeTrailingFrames(frameData)
return translationData, armatureFrameData
def getNextBone(boneStack, armatureObj):
if len(boneStack) == 0:
raise ValueError("More bones in animation than on armature.")
bone = armatureObj.data.bones[boneStack[0]]
boneStack = boneStack[1:]
boneStack = sorted([child.name for child in bone.children]) + boneStack
# Only return 0x13 bone
while armatureObj.pose.bones[bone.name].bone_group is not None:
if len(boneStack) == 0:
raise ValueError("More bones in animation than on armature.")
bone = armatureObj.data.bones[boneStack[0]]
boneStack = boneStack[1:]
boneStack = sorted([child.name for child in bone.children]) + boneStack
return bone, boneStack
def importAnimationToBlender(romfile, startAddress, armatureObj, segmentData, isDMA):
if 'root' not in armatureObj.data.bones:
raise ValueError('Cannot find bone named "root". The first animatable' +\
'(0x13) bone in the armature must be named "root."')
animationHeader, armatureFrameData = \
readAnimation('sm64_anim', romfile, startAddress, segmentData, isDMA)
if len(armatureFrameData) > len(armatureObj.data.bones) + 1:
raise ValueError('More bones in animation than on armature.')
#bpy.context.scene.render.fps = 30
bpy.context.scene.frame_end = animationHeader.frameInterval[1]
anim = bpy.data.actions.new("sm64_anim")
boneStack = ['root']
isRootTranslation = True
# boneFrameData = [[x keyframes], [y keyframes], [z keyframes]]
# len(armatureFrameData) should be = number of bones
# property index = 0,1,2 (aka x,y,z)
for boneFrameData in armatureFrameData:
if isRootTranslation:
for propertyIndex in range(3):
fcurve = anim.fcurves.new(
data_path = 'pose.bones["root"].location',
index = propertyIndex,
action_group = 'root')
for frame in range(len(boneFrameData[propertyIndex])):
fcurve.keyframe_points.insert(frame, boneFrameData[propertyIndex][frame])
isRootTranslation = False
else:
bone, boneStack = getNextBone(boneStack, armatureObj)
print(bone.name)
for propertyIndex in range(3):
fcurve = anim.fcurves.new(
data_path = 'pose.bones["' + bone.name + '"].rotation_euler',
index = propertyIndex,
action_group = bone.name)
for frame in range(len(boneFrameData[propertyIndex])):
fcurve.keyframe_points.insert(frame, boneFrameData[propertyIndex][frame])
if armatureObj.animation_data is None:
armatureObj.animation_data_create()
armatureObj.animation_data.action = anim
def readAnimation(name, romfile, startAddress, segmentData, isDMA):
animationHeader = readAnimHeader(name, romfile, startAddress, segmentData, isDMA)
print("Frames: " + str(animationHeader.frameInterval[1]) + " / Nodes: " + str(animationHeader.nodeCount))
animationHeader.transformIndices = readAnimIndices(
romfile, animationHeader.transformIndicesStart, animationHeader.nodeCount)
armatureFrameData = [] #list of list of frames
# sm64 space -> blender space -> pose space
# BlenderToSM64: YZX (set rotation mode of bones)
# SM64toBlender: ZXY (set anim keyframes and model armature)
# new bones should extrude in +Y direction
# handle root translation
boneFrameData = [[],[],[]]
rootIndexNode = animationHeader.transformIndices[0]
boneFrameData[0] = [n for n in getKeyFramesTranslation(romfile, animationHeader.transformValuesStart, rootIndexNode.x)]
boneFrameData[1] = [n for n in getKeyFramesTranslation(romfile, animationHeader.transformValuesStart, rootIndexNode.y)]
boneFrameData[2] = [n for n in getKeyFramesTranslation(romfile, animationHeader.transformValuesStart, rootIndexNode.z)]
armatureFrameData.append(boneFrameData)
# handle rotations
for boneIndexNode in animationHeader.transformIndices[1:]:
boneFrameData = [[],[],[]]
# Transforming SM64 space to Blender space
boneFrameData[0] = [n for n in \
getKeyFramesRotation(romfile, animationHeader.transformValuesStart, boneIndexNode.x)]
boneFrameData[1] = [n for n in \
getKeyFramesRotation(romfile, animationHeader.transformValuesStart, boneIndexNode.y)]
boneFrameData[2] = [n for n in \
getKeyFramesRotation(romfile, animationHeader.transformValuesStart, boneIndexNode.z)]
armatureFrameData.append(boneFrameData)
return (animationHeader, armatureFrameData)
def getKeyFramesRotation(romfile, transformValuesStart, boneIndex):
ptrToValue = transformValuesStart + boneIndex.startOffset
romfile.seek(ptrToValue)
keyframes = []
for frame in range(boneIndex.numFrames):
romfile.seek(ptrToValue + frame * 2)
value = int.from_bytes(romfile.read(2), 'big') * 360 / (2**16)
keyframes.append(math.radians(value))
return keyframes
def getKeyFramesTranslation(romfile, transformValuesStart, boneIndex):
ptrToValue = transformValuesStart + boneIndex.startOffset
romfile.seek(ptrToValue)
keyframes = []
for frame in range(boneIndex.numFrames):
romfile.seek(ptrToValue + frame * 2)
keyframes.append(int.from_bytes(romfile.read(2), 'big', signed = True) *\
sm64ToBlenderScale)
return keyframes
def readAnimHeader(name, romfile, startAddress, segmentData, isDMA):
frameInterval = [0,0]
romfile.seek(startAddress + 0x00)
numRepeats = int.from_bytes(romfile.read(2), 'big')
romfile.seek(startAddress + 0x02)
marioYOffset = int.from_bytes(romfile.read(2), 'big')
romfile.seek(startAddress + 0x06)
frameInterval[0] = int.from_bytes(romfile.read(2), 'big')
romfile.seek(startAddress + 0x08)
frameInterval[1] = int.from_bytes(romfile.read(2), 'big')
romfile.seek(startAddress + 0x0A)
numNodes = int.from_bytes(romfile.read(2), 'big')
romfile.seek(startAddress + 0x0C)
transformValuesOffset = int.from_bytes(romfile.read(4), 'big')
if isDMA:
transformValuesStart = startAddress + transformValuesOffset
else:
transformValuesStart = decodeSegmentedAddr(
transformValuesOffset.to_bytes(4, byteorder='big'), segmentData)
romfile.seek(startAddress + 0x10)
transformIndicesOffset = int.from_bytes(romfile.read(4), 'big')
if isDMA:
transformIndicesStart = startAddress + transformIndicesOffset
else:
transformIndicesStart = decodeSegmentedAddr(
transformIndicesOffset.to_bytes(4, byteorder='big'), segmentData)
romfile.seek(startAddress + 0x14)
animSize = int.from_bytes(romfile.read(4), 'big')
return SM64_AnimationHeader(name, numRepeats, marioYOffset, frameInterval, numNodes,
transformValuesStart, transformIndicesStart, animSize)
def readAnimIndices(romfile, ptrAddress, nodeCount):
indices = []
# Handle root transform
rootPosIndex = readTransformIndex(romfile, ptrAddress)
indices.append(rootPosIndex)
# Handle rotations
for i in range(nodeCount):
rotationIndex = readTransformIndex(romfile, ptrAddress + (i+1) * 12)
indices.append(rotationIndex)
return indices
def readTransformIndex(romfile, startAddress):
x = readValueIndex(romfile, startAddress + 0)
y = readValueIndex(romfile, startAddress + 4)
z = readValueIndex(romfile, startAddress + 8)
return SM64_AnimIndexNode(x, y, z)
def readValueIndex(romfile, startAddress):
romfile.seek(startAddress)
numFrames = int.from_bytes(romfile.read(2), 'big')
romfile.seek(startAddress + 2)
# multiply 2 because value is the index in array of shorts (???)
startOffset = int.from_bytes(romfile.read(2), 'big') * 2
print(str(hex(startAddress)) + ": " + str(numFrames) + " " + str(startOffset))
return SM64_AnimIndex(numFrames, startOffset)
def writeAnimation(romfile, startAddress, segmentData):
pass
def writeAnimHeader(romfile, startAddress, segmentData):
pass
+936
View File
@@ -0,0 +1,936 @@
from .utility import *
from .sm64_constants import *
from bpy.utils import register_class, unregister_class
import bpy, bmesh
class CollisionVertex:
def __init__(self, position):
self.position = position
def to_binary(self):
data = bytearray(0)
if len(self.position) > 3:
raise ValueError("Vertex position should not be " + \
str(len(self.position) + ' fields long.'))
for field in self.position:
data.extend(int(round(field)).to_bytes(2, 'big', signed = True))
return data
def to_c(self):
return 'COL_VERTEX(' + \
str(int(round(self.position[0]))) + ', ' + \
str(int(round(self.position[1]))) + ', ' + \
str(int(round(self.position[2]))) + '),\n'
class CollisionTriangle:
def __init__(self, indices):
self.indices = indices
self.specialParam = None
def to_binary(self):
data = bytearray(0)
if len(self.indices) > 3:
raise ValueError("Triangle indices should not be " + \
str(len(self.indices) + ' fields long.'))
for index in self.indices:
data.extend(int(round(index)).to_bytes(2, 'big', signed = False))
if self.specialParam is not None:
data.extend(int(self.specialParam).to_bytes(2, 'big', signed=False))
return data
def to_c(self):
if self.specialParam is None:
return 'COL_TRI(' + \
str(int(round(self.indices[0]))) + ', ' + \
str(int(round(self.indices[1]))) + ', ' + \
str(int(round(self.indices[2]))) + '),\n'
else:
return 'COL_TRI_SPECIAL(' + \
str(int(round(self.indices[0]))) + ', ' + \
str(int(round(self.indices[1]))) + ', ' + \
str(int(round(self.indices[2]))) + ', ' + \
str(int(self.specialParam)) + '),\n'
class CollisionSpecial:
def __init__(self):
self.preset = None
self.position = [None] * 3
self.yaw = None
self.specialParam = None
def to_binary(self):
data = int(self.preset).to_bytes(2, 'big')
if len(self.position) > 3:
raise ValueError("Object position should not be " + \
str(len(self.position) + ' fields long.'))
for index in self.position:
data.extend(int(round(index)).to_bytes(2, 'big', signed = False))
if self.yaw is not None:
data.extend(int(self.yaw).to_bytes(2, 'big'))
if self.specialParam is not None:
data.extend(int(self.specialParam).to_bytes(2, 'big'))
return data
def to_c(self):
if self.yaw is None:
data = 'SPECIAL_OBJECT('
elif self.specialParam is None:
data = 'SPECIAL_OBJECT_WITH_YAW('
else:
data = 'SPECIAL_OBJECT_WITH_YAW_AND_PARAM('
data += str(self.preset) + ', ' +\
str(int(round(self.position[0]))) + ', ' + \
str(int(round(self.position[1]))) + ', ' + \
str(int(round(self.position[2])))
if self.yaw is None:
data += '),\n'
elif self.specialParam is None:
data += ', ' + str(self.yaw) + '),\n'
else:
data += ', ' + str(self.yaw) + ', ' + str(self.specialParam) + '),\n'
return data
class CollisionWaterBox:
def __init__(self):
self.waterBoxType = 'Water'
self.low = (0,0)
self.high = (0,0)
self.height = 0
def to_binary(self):
data = bytearray([0x00, 0x00 if self.waterBoxType == 'Water' else 0x32])
data.extend(int(round(self.low[0])).to_bytes(2, 'big', signed=True))
data.extend(int(round(self.low[1])).to_bytes(2, 'big', signed=True))
data.extend(int(round(self.high[0])).to_bytes(2, 'big', signed=True))
data.extend(int(round(self.high[1])).to_bytes(2, 'big', signed=True))
data.extend(int(round(self.height)).to_bytes(2, 'big', signed=True))
return data
def to_c(self):
data = 'COL_WATER_BOX(' + \
('0x00' if self.waterBoxType == 'Water' else '0x32') + ', ' + \
str(int(round(self.low[0]))) + ', ' + \
str(int(round(self.low[1]))) + ', ' + \
str(int(round(self.high[0]))) + ', ' + \
str(int(round(self.high[1]))) + ', ' + \
str(int(round(self.height))) + '),\n'
return data
class Collision:
def __init__(self, name):
self.name = name
self.startAddress = 0
self.vertices = []
# dict of collision type : triangle list
self.triangles = {}
self.specials = []
self.waterBoxes = []
def set_addr(self, startAddress):
startAddress = get64bitAlignedAddr(startAddress)
self.startAddress = startAddress
print('Collision ' + self.name + ': ' + str(startAddress) + \
', ' + str(self.size()))
return startAddress, startAddress + self.size()
def save_binary(self, romfile):
romfile.seek(self.startAddress)
romfile.write(self.to_binary())
def size(self):
return len(self.to_binary())
def to_c(self):
data = 'const Collision ' + self.name + '[] = {\n'
data += '\tCOL_INIT(),\n'
data += '\tCOL_VERTEX_INIT(' + str(len(self.vertices)) + '),\n'
for vertex in self.vertices:
data += '\t' + vertex.to_c()
for collisionType, triangles in self.triangles.items():
data += '\tCOL_TRI_INIT(' + collisionType + ', ' +\
str(len(triangles)) + '),\n'
for triangle in triangles:
data += '\t' + triangle.to_c()
data += '\tCOL_TRI_STOP(),\n'
if len(self.specials) > 0:
data += '\tCOL_SPECIAL_INIT(' + str(len(self.specials)) + '),\n'
for special in self.specials:
data += '\t' + special.to_c()
if len(self.waterBoxes) > 0:
data += '\tCOL_WATER_BOX_INIT(' + str(len(self.waterBoxes)) + '),\n'
for waterBox in self.waterBoxes:
data += '\t' + waterBox.to_c()
data += '\tCOL_END()\n' + '};\n'
return data
def to_binary(self):
colTypeDef = CollisionTypeDefinition()
data = bytearray([0x00, 0x40])
data += len(self.vertices).to_bytes(2, 'big')
for vertex in self.vertices:
data += vertex.to_binary()
for collisionType, triangles in self.triangles.items():
data += getattr(colTypeDef, collisionType).to_bytes(2, 'big')
data += len(triangles).to_bytes(2, 'big')
for triangle in triangles:
data += triangle.to_binary()
data += bytearray([0x00, 0x41])
if len(self.specials) > 0:
data += bytearray([0x00, 0x43])
data += len(self.specials).to_bytes(2, 'big')
for special in self.specials:
data += special.to_binary()
if len(self.waterBoxes) > 0:
data += bytearray([0x00, 0x44])
data += len(self.waterBoxes).to_bytes(2, 'big')
for waterBox in self.waterBoxes:
data += waterBox.to_binary()
data += bytearray([0x00, 0x42])
return data
class CollisionPanel(bpy.types.Panel):
bl_label = "Collision Inspector"
bl_idname = "SM64_Collision_Inspector"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "material"
bl_options = {'HIDE_HEADER'}
@classmethod
def poll(cls, context):
return (context.material is not None)
def draw(self, context):
box = self.layout.box()
#box.label(text = 'Collision Inspector')
material = context.material
if not material.collision_all_options:
prop_split(box, material, 'collision_type_simple',
'SM64 Collision Type')
else:
prop_split(box, material, 'collision_type',
'SM64 Collision Type All')
split = box.split(factor = 0.5)
split.label(text = '')
split.prop(material, 'collision_all_options')
#infoBox = box.box()
#infoBox.label(text = \
# 'For special params, make a vert color layer named "Collision."')
#infoBox.label(text = 'The red value 0-1 will be converted to 0-65535.')
class SM64ObjectPanel(bpy.types.Panel):
bl_label = "Object Inspector"
bl_idname = "SM64_Object_Inspector"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
bl_options = {'HIDE_HEADER'}
@classmethod
def poll(cls, context):
return (context.object is not None)
def draw(self, context):
box = self.layout.box()
box.label(text = 'SM64 Object Inspector')
obj = context.object
prop_split(box, obj, 'sm64_obj_type', 'Special Preset')
if obj.sm64_obj_type == 'Special':
prop_split(box, obj, 'sm64_special_preset', 'Special Preset')
elif obj.sm64_obj_type == 'Water Box':
prop_split(box, obj, 'sm64_water_box', 'Water Box Type')
def exportCollisionBinary(obj, transformMatrix, romfile, startAddress,
endAddress, addEntities):
collision = exportCollisionCommon(obj, transformMatrix, addEntities)
start, end = collision.set_addr(startAddress)
if end > endAddress:
raise ValueError('Size too big: Data ends at ' + hex(end) +\
', which is larger than the specified range.')
collision.save_binary(romfile)
return start, end
def exportCollisionC(obj, transformMatrix, filePath, addEntities):
fileObj = open(filePath, 'w')
collision = exportCollisionCommon(obj, transformMatrix, addEntities)
fileObj.write(collision.to_c())
fileObj.close()
def exportCollisionCommon(obj, transformMatrix, addEntities):
bpy.ops.object.select_all(action = 'DESELECT')
obj.select_set(True)
bMesh = bmesh.new()
bMesh.from_mesh(obj.data)
bmesh.ops.triangulate(bMesh, faces = bMesh.faces[:])
bMesh.verts.ensure_lookup_table()
bMesh.edges.ensure_lookup_table()
bMesh.verts.index_update()
bMesh.faces.index_update()
if len(obj.data.materials) == 0:
raise ValueError(obj.name + " must have a material associated with it.")
# dict of collisionType : faces
collisionDict = {}
for face in bMesh.faces:
material = obj.data.materials[face.material_index]
colType = material.collision_type if material.collision_all_options else \
material.collision_type_simple
if colType not in collisionDict:
collisionDict[colType] = []
collisionDict[colType].append(face)
collision = Collision(toAlnum(obj.name) + '_collision')
for collisionType, faces in collisionDict.items():
collision.triangles[collisionType] = []
for face in faces:
indices = []
for vert in face.verts:
roundedPosition = roundPosition(transformMatrix @ vert.co)
index = collisionVertIndex(roundedPosition, collision.vertices)
if index is None:
collision.vertices.append(CollisionVertex(roundedPosition))
indices.append(len(collision.vertices) - 1)
else:
indices.append(index)
collision.triangles[collisionType].append(CollisionTriangle(indices))
if addEntities:
childArray = obj.children
while len(childArray) > 0:
childObj = childArray[0]
childArray = childArray[1:]
if childObj.sm64_obj_type == 'Special':
special = CollisionSpecial()
special.preset = childObj.sm64_special_preset
special.position = roundPosition(transformMatrix @ \
childObj.location)
collision.specials.append(special)
elif childObj.sm64_obj_type == 'Water Box':
pass
childArray.extend(childObj.children)
return collision
def roundPosition(position):
return (int(round(position[0])),
int(round(position[1])),
int(round(position[2])))
def collisionVertIndex(vert, vertArray):
for i in range(len(vertArray)):
colVert = vertArray[i]
if colVert.position == vert:
return i
return None
classes = (
CollisionPanel,
#SM64ObjectPanel,
)
def register():
for cls in classes:
register_class(cls)
bpy.types.Material.collision_type = bpy.props.EnumProperty(
name = 'Collision Type', items = enumCollisionType,
default = 'SURFACE_DEFAULT')
bpy.types.Material.collision_type_simple = bpy.props.EnumProperty(
name = 'Collision Type', items = enumCollisionTypeSimple,
default = 'SURFACE_DEFAULT')
bpy.types.Material.collision_all_options = bpy.props.BoolProperty(
name = 'Show All Options')
bpy.types.Object.sm64_obj_type = bpy.props.EnumProperty(
name = 'SM64 Object Type', items = enumObjectType, default = 'None')
bpy.types.Object.sm64_water_box = bpy.props.EnumProperty(
name = 'SM64 Water Box', items = enumWaterBoxType, default = 'Water')
bpy.types.Object.sm64_special_preset = bpy.props.EnumProperty(
name = 'SM64 Special', items = enumSpecialType,
default = 'special_yellow_coin')
def unregister():
for cls in reversed(classes):
unregister_class(cls)
enumWaterBoxType = [
("Water", 'Water', "Water"),
('Toxic Haze', 'Toxic Haze', 'Toxic Haze')
]
class CollisionTypeDefinition:
def __init__(self):
self.SURFACE_DEFAULT = 0x0000
self.SURFACE_BURNING = 0x0001
self.SURFACE_0004 = 0x0004
self.SURFACE_HANGABLE = 0x0005
self.SURFACE_SLOW = 0x0009
self.SURFACE_DEATH_PLANE = 0x000A
self.SURFACE_CLOSE_CAMERA = 0x000B
self.SURFACE_WATER = 0x000D
self.SURFACE_FLOWING_WATER = 0x000E
self.SURFACE_INTANGIBLE = 0x0012
self.SURFACE_VERY_SLIPPERY = 0x0013
self.SURFACE_SLIPPERY = 0x0014
self.SURFACE_NOT_SLIPPERY = 0x0015
self.SURFACE_TTM_VINES = 0x0016
self.SURFACE_MGR_MUSIC = 0x001A
self.SURFACE_INSTANT_WARP_1B = 0x001B
self.SURFACE_INSTANT_WARP_1C = 0x001C
self.SURFACE_INSTANT_WARP_1D = 0x001D
self.SURFACE_INSTANT_WARP_1E = 0x001E
self.SURFACE_SHALLOW_QUICKSAND = 0x0021
self.SURFACE_DEEP_QUICKSAND = 0x0022
self.SURFACE_INSTANT_QUICKSAND = 0x0023
self.SURFACE_DEEP_MOVING_QUICKSAND = 0x0024
self.SURFACE_SHALLOW_MOVING_QUICKSAND = 0x0025
self.SURFACE_QUICKSAND = 0x0026
self.SURFACE_MOVING_QUICKSAND = 0x0027
self.SURFACE_WALL_MISC = 0x0028
self.SURFACE_NOISE_DEFAULT = 0x0029
self.SURFACE_NOISE_SLIPPERY = 0x002A
self.SURFACE_HORIZONTAL_WIND = 0x002C
self.SURFACE_INSTANT_MOVING_QUICKSAND = 0x002D
self.SURFACE_ICE = 0x002E
self.SURFACE_LOOK_UP_WARP = 0x002F
self.SURFACE_HARD = 0x0030
self.SURFACE_WARP = 0x0032
self.SURFACE_TIMER_START = 0x0033
self.SURFACE_TIMER_END = 0x0034
self.SURFACE_HARD_SLIPPERY = 0x0035
self.SURFACE_HARD_VERY_SLIPPERY = 0x0036
self.SURFACE_HARD_NOT_SLIPPERY = 0x0037
self.SURFACE_VERTICAL_WIND = 0x0038
self.SURFACE_BOSS_FIGHT_CAMERA = 0x0065
self.SURFACE_CAMERA_FREE_ROAM = 0x0066
self.SURFACE_THI3_WALLKICK = 0x0068
self.SURFACE_CAMERA_PLATFORM = 0x0069
self.SURFACE_CAMERA_MIDDLE = 0x006E
self.SURFACE_CAMERA_ROTATE_RIGHT = 0x006F
self.SURFACE_CAMERA_ROTATE_LEFT = 0x0070
self.SURFACE_CAMERA_BOUNDARY = 0x0072
self.SURFACE_NOISE_VERY_SLIPPERY_73 = 0x0073
self.SURFACE_NOISE_VERY_SLIPPERY_74 = 0x0074
self.SURFACE_NOISE_VERY_SLIPPERY = 0x0075
self.SURFACE_NO_CAM_COLLISION = 0x0076
self.SURFACE_NO_CAM_COLLISION_77 = 0x0077
self.SURFACE_NO_CAM_COL_VERY_SLIPPERY = 0x0078
self.SURFACE_NO_CAM_COL_SLIPPERY = 0x0079
self.SURFACE_SWITCH = 0x007A
self.SURFACE_VANISH_CAP_WALLS = 0x007B
self.SURFACE_PAINTING_WOBBLE_A6 = 0x00A6
self.SURFACE_PAINTING_WOBBLE_A7 = 0x00A7
self.SURFACE_PAINTING_WOBBLE_A8 = 0x00A8
self.SURFACE_PAINTING_WOBBLE_A9 = 0x00A9
self.SURFACE_PAINTING_WOBBLE_AA = 0x00AA
self.SURFACE_PAINTING_WOBBLE_AB = 0x00AB
self.SURFACE_PAINTING_WOBBLE_AC = 0x00AC
self.SURFACE_PAINTING_WOBBLE_AD = 0x00AD
self.SURFACE_PAINTING_WOBBLE_AE = 0x00AE
self.SURFACE_PAINTING_WOBBLE_AF = 0x00AF
self.SURFACE_PAINTING_WOBBLE_B0 = 0x00B0
self.SURFACE_PAINTING_WOBBLE_B1 = 0x00B1
self.SURFACE_PAINTING_WOBBLE_B2 = 0x00B2
self.SURFACE_PAINTING_WOBBLE_B3 = 0x00B3
self.SURFACE_PAINTING_WOBBLE_B4 = 0x00B4
self.SURFACE_PAINTING_WOBBLE_B5 = 0x00B5
self.SURFACE_PAINTING_WOBBLE_B6 = 0x00B6
self.SURFACE_PAINTING_WOBBLE_B7 = 0x00B7
self.SURFACE_PAINTING_WOBBLE_B8 = 0x00B8
self.SURFACE_PAINTING_WOBBLE_B9 = 0x00B9
self.SURFACE_PAINTING_WOBBLE_BA = 0x00BA
self.SURFACE_PAINTING_WOBBLE_BB = 0x00BB
self.SURFACE_PAINTING_WOBBLE_BC = 0x00BC
self.SURFACE_PAINTING_WOBBLE_BD = 0x00BD
self.SURFACE_PAINTING_WOBBLE_BE = 0x00BE
self.SURFACE_PAINTING_WOBBLE_BF = 0x00BF
self.SURFACE_PAINTING_WOBBLE_C0 = 0x00C0
self.SURFACE_PAINTING_WOBBLE_C1 = 0x00C1
self.SURFACE_PAINTING_WOBBLE_C2 = 0x00C2
self.SURFACE_PAINTING_WOBBLE_C3 = 0x00C3
self.SURFACE_PAINTING_WOBBLE_C4 = 0x00C4
self.SURFACE_PAINTING_WOBBLE_C5 = 0x00C5
self.SURFACE_PAINTING_WOBBLE_C6 = 0x00C6
self.SURFACE_PAINTING_WOBBLE_C7 = 0x00C7
self.SURFACE_PAINTING_WOBBLE_C8 = 0x00C8
self.SURFACE_PAINTING_WOBBLE_C9 = 0x00C9
self.SURFACE_PAINTING_WOBBLE_CA = 0x00CA
self.SURFACE_PAINTING_WOBBLE_CB = 0x00CB
self.SURFACE_PAINTING_WOBBLE_CC = 0x00CC
self.SURFACE_PAINTING_WOBBLE_CD = 0x00CD
self.SURFACE_PAINTING_WOBBLE_CE = 0x00CE
self.SURFACE_PAINTING_WOBBLE_CF = 0x00CF
self.SURFACE_PAINTING_WOBBLE_D0 = 0x00D0
self.SURFACE_PAINTING_WOBBLE_D1 = 0x00D1
self.SURFACE_PAINTING_WOBBLE_D2 = 0x00D2
self.SURFACE_PAINTING_WARP_D3 = 0x00D3
self.SURFACE_PAINTING_WARP_D4 = 0x00D4
self.SURFACE_PAINTING_WARP_D5 = 0x00D5
self.SURFACE_PAINTING_WARP_D6 = 0x00D6
self.SURFACE_PAINTING_WARP_D7 = 0x00D7
self.SURFACE_PAINTING_WARP_D8 = 0x00D8
self.SURFACE_PAINTING_WARP_D9 = 0x00D9
self.SURFACE_PAINTING_WARP_DA = 0x00DA
self.SURFACE_PAINTING_WARP_DB = 0x00DB
self.SURFACE_PAINTING_WARP_DC = 0x00DC
self.SURFACE_PAINTING_WARP_DD = 0x00DD
self.SURFACE_PAINTING_WARP_DE = 0x00DE
self.SURFACE_PAINTING_WARP_DF = 0x00DF
self.SURFACE_PAINTING_WARP_E0 = 0x00E0
self.SURFACE_PAINTING_WARP_E1 = 0x00E1
self.SURFACE_PAINTING_WARP_E2 = 0x00E2
self.SURFACE_PAINTING_WARP_E3 = 0x00E3
self.SURFACE_PAINTING_WARP_E4 = 0x00E4
self.SURFACE_PAINTING_WARP_E5 = 0x00E5
self.SURFACE_PAINTING_WARP_E6 = 0x00E6
self.SURFACE_PAINTING_WARP_E7 = 0x00E7
self.SURFACE_PAINTING_WARP_E8 = 0x00E8
self.SURFACE_PAINTING_WARP_E9 = 0x00E9
self.SURFACE_PAINTING_WARP_EA = 0x00EA
self.SURFACE_PAINTING_WARP_EB = 0x00EB
self.SURFACE_PAINTING_WARP_EC = 0x00EC
self.SURFACE_PAINTING_WARP_ED = 0x00ED
self.SURFACE_PAINTING_WARP_EE = 0x00EE
self.SURFACE_PAINTING_WARP_EF = 0x00EF
self.SURFACE_PAINTING_WARP_F0 = 0x00F0
self.SURFACE_PAINTING_WARP_F1 = 0x00F1
self.SURFACE_PAINTING_WARP_F2 = 0x00F2
self.SURFACE_PAINTING_WARP_F3 = 0x00F3
self.SURFACE_TTC_PAINTING_1 = 0x00F4
self.SURFACE_TTC_PAINTING_2 = 0x00F5
self.SURFACE_TTC_PAINTING_3 = 0x00F6
self.SURFACE_PAINTING_WARP_F7 = 0x00F7
self.SURFACE_PAINTING_WARP_F8 = 0x00F8
self.SURFACE_PAINTING_WARP_F9 = 0x00F9
self.SURFACE_PAINTING_WARP_FA = 0x00FA
self.SURFACE_PAINTING_WARP_FB = 0x00FB
self.SURFACE_PAINTING_WARP_FC = 0x00FC
self.SURFACE_WOBBLING_WARP = 0x00FD
self.SURFACE_TRAPDOOR = 0x00FF
self.SURFACE_CLASS_DEFAULT = 0x0000
self.SURFACE_CLASS_VERY_SLIPPERY = 0x0013
self.SURFACE_CLASS_SLIPPERY = 0x0014
self.SURFACE_CLASS_NOT_SLIPPERY = 0x0015
self.SURFACE_FLAG_DYNAMIC = (1 << 0)
self.SURFACE_FLAG_NO_CAM_COLLISION = (1 << 1)
self.SURFACE_FLAG_X_PROJECTION = (1 << 3)
self.TERRAIN_LOAD_VERTICES = 0x0040
self.TERRAIN_LOAD_CONTINUE = 0x0041
self.TERRAIN_LOAD_END = 0x0042
self.TERRAIN_LOAD_OBJECTS = 0x0043
self.TERRAIN_LOAD_ENVIRONMENT = 0x0044
self.TERRAIN_GRASS = 0x0000
self.TERRAIN_STONE = 0x0001
self.TERRAIN_SNOW = 0x0002
self.TERRAIN_SAND = 0x0003
self.TERRAIN_SPOOKY = 0x0004
self.TERRAIN_WATER = 0x0005
self.TERRAIN_SLIDE = 0x0006
self.TERRAIN_MASK = 0x0007
def SURFACE_IS_QUICKSAND(self, cmd):
return (cmd >= 0x21 and cmd <= 0x28)
def SURFACE_IS_NOT_HARD(self, cmd):
return (cmd != self.SURFACE_HARD and not (cmd >= 0x35 and cmd <= 0x37))
def SURFACE_IS_PAINTING_WARP(self, cmd):
return (cmd >= 0xD3 and cmd < 0xFD)
def TERRAIN_LOAD_IS_SURFACE_TYPE_LOW(self, cmd) :
return (cmd < 0x40)
def TERRAIN_LOAD_IS_SURFACE_TYPE_HIGH(self, cmd) :
return (cmd >= 0x65)
enumSpecialType = [
('special_null_start', 'Null Start', 'Null Start'),
('special_yellow_coin', 'Yellow Coin', 'Yellow Coin'),
('special_yellow_coin_2', 'Yellow Coin 2', 'Yellow Coin 2'),
('special_unknown_3', 'Unknown 3', 'Unknown 3'),
('special_boo', 'Boo', 'Boo'),
('special_unknown_5', 'Unknown 5', 'Unknown 5'),
('special_lll_moving_octagonal_mesh_platform',
'LLL Moving Octagonal Platform', 'LLL Moving Octagonal Platform'),
('special_snow_ball', 'Snow Ball', 'Snow Ball'),
('special_lll_drawbridge_spawner', 'LLL Drawbridge Spawner',
'LLL Drawbridge Spawner'),
('special_empty_9', 'Empty 9', 'Empty 9'),
('special_lll_rotating_block_with_fire_bars',
'LLL Rotating Block With Fire Bars', 'LLL Rotating Block With Fire Bars'),
('special_lll_floating_wood_bridge',
'LLL Floating Wood Bridge', 'LLL Floating Wood Bridge'),
('special_tumbling_platform', 'Tumbling Platform', 'Tumbling Platform'),
('special_lll_rotating_hexagonal_ring', 'LLL Rotating Hexagonal Ring',
'LLL Rotating Hexagonal Ring'),
('special_lll_sinking_rectangular_platform',
'LLL Sinking Rectangular Platform', 'LLL Sinking Rectangular Platform'),
('special_lll_sinking_square_platforms', 'LLL Sinking Square Platforms',
'LLL Sinking Square Platforms'),
('special_lll_tilting_square_platform', 'LLL Tilting Square Platform',
'LLL Tilting Square Platform'),
('special_lll_bowser_puzzle', 'LLL Bowser Puzzle', 'LLL Bowser Puzzle'),
('special_mr_i', 'Mr. I', 'Mr. I'),
('special_small_bully', 'Small Bully', 'Small Bully'),
('special_big_bully', 'Big Bully', 'Big Bully'),
('special_empty_21', 'Empty 21', 'Empty 21'),
('special_empty_22', 'Empty 22', 'Empty 22'),
('special_empty_23', 'Empty 23', 'Empty 23'),
('special_empty_24', 'Empty 24', 'Empty 24'),
('special_empty_25', 'Empty 25', 'Empty 25'),
('special_moving_blue_coin', 'Moving Blue Coin', 'Moving Blue Coin'),
('special_jrb_chest', 'JRB Chest', 'JRB Chest'),
('special_water_ring', 'Water Ring', 'Water Ring'),
('special_mine', 'Mine', 'Mine'),
('special_empty_30', 'Empty 30', 'Empty 30'),
('special_empty_31', 'Empty 31', 'Empty 31'),
('special_butterfly', 'Butterfly', 'Butterfly'),
('special_bowser', 'Bowser', 'Bowser'),
('special_wf_rotating_wooden_platform', 'WF Rotating Wooden Platform',
'WF Rotating Wooden Platform'),
('special_small_bomp', 'Small Bomp', 'Small Bomp'),
('special_wf_sliding_platform', 'WF Sliding Platform', 'WF Sliding Platform'),
('special_tower_platform_group', 'Tower Platform Group',
'Tower Platform Group'),
('special_rotating_counter_clockwise', 'Rotating Counter Clockwise',
'Rotating Counter Clockwise'),
('special_wf_tumbling_bridge', 'WF Tumbling Bridge', 'WF Tumbling Bridge'),
('special_large_bomp', 'Large Bomp', 'Large Bomp' ),
('special_level_geo_03', 'Level Geo 03', 'Level Geo 03'),
('special_level_geo_04', 'Level Geo 04', 'Level Geo 04'),
('special_level_geo_05', 'Level Geo 05', 'Level Geo 05'),
('special_level_geo_06', 'Level Geo 06', 'Level Geo 06'),
('special_level_geo_07', 'Level Geo 07', 'Level Geo 07'),
('special_level_geo_08', 'Level Geo 08', 'Level Geo 08'),
('special_level_geo_09', 'Level Geo 09', 'Level Geo 09'),
('special_level_geo_0A', 'Level Geo 0A', 'Level Geo 0A'),
('special_level_geo_0B', 'Level Geo 0B', 'Level Geo 0B'),
('special_level_geo_0C', 'Level Geo 0C', 'Level Geo 0C'),
('special_level_geo_0D', 'Level Geo 0D', 'Level Geo 0D'),
('special_level_geo_0E', 'Level Geo 0E', 'Level Geo 0E'),
('special_level_geo_0F', 'Level Geo 0F', 'Level Geo 0F'),
('special_level_geo_10', 'Level Geo 10', 'Level Geo 10'),
('special_level_geo_11', 'Level Geo 11', 'Level Geo 11'),
('special_level_geo_12', 'Level Geo 12', 'Level Geo 12'),
('special_level_geo_13', 'Level Geo 13', 'Level Geo 13'),
('special_level_geo_14', 'Level Geo 14', 'Level Geo 14'),
('special_level_geo_15', 'Level Geo 15', 'Level Geo 15'),
('special_level_geo_16', 'Level Geo 16', 'Level Geo 16'),
('special_bubble_tree', 'bubble_tree', 'bubble_tree'),
('special_spiky_tree', 'Spiky Tree', 'Spiky Tree'),
('special_snow_tree', 'Snow Tree', 'Snow Tree'),
('special_unknown_tree', 'Unknown Tree', 'Unknown Tree'),
('special_palm_tree', 'Palm Tree', 'Palm Tree'),
('special_wooden_door', 'Wooden Door', 'Wooden Door'),
('special_haunted_door', 'Haunted Door', 'Haunted Door'),
('special_unknown_door', 'Unknown Door', 'Unknown Door'),
('special_metal_door', 'Metal Door', 'Metal Door'),
('special_hmc_door', 'HMC Door', 'HMC Door'),
('special_unknown2_door', 'Unknown 2 Door', 'Unknown 2 Door'),
('special_wooden_door_warp', 'Wooden Door Warp', 'Wooden Door Warp'),
('special_unknown1_door_warp', 'Unknown 1 Door Warp', 'Unknown 1 Door Warp'),
('special_metal_door_warp', 'Metal Door Warp', 'Metal Door Warp'),
('special_unknown2_door_warp', 'Unknown 2 Door Warp', 'Unknown 2 Door Warp'),
('special_unknown3_door_warp', 'Unknown 3 Door Warp', 'Unknown 3 Door Warp'),
('special_castle_door_warp', 'Castle Door Warp', 'Castle Door Warp'),
('special_castle_door', 'Castle Door', 'Castle Door'),
('special_0stars_door', '0 Stars Door', '0 Stars Door'),
('special_1star_door', '1 Star Door', '1 Star Door'),
('special_3star_door', '3 Star Door', '3 Star Door'),
('special_key_door', 'Key Door', 'Key Door'),
('special_null_end', 'Null End', 'Null End'),
]
enumObjectType = [
('None', 'None', 'None'),
('Special', 'Special', 'Special'),
('Water Box', 'Water Box', 'Water Box'),
]
enumCollisionTypeSimple = [
('SURFACE_DEFAULT','Default','Default'),
('SURFACE_BURNING','Burning','Burning'),
('SURFACE_HANGABLE','Hangable','Hangable'),
('SURFACE_SLOW','Slow','Slow'),
('SURFACE_DEATH_PLANE','Death Plane','Death Plane'),
('SURFACE_WATER','Water','Water'),
('SURFACE_FLOWING_WATER','Flowing Water','Flowing Water'),
('SURFACE_INTANGIBLE','Intangible','Intangible'),
('SURFACE_VERY_SLIPPERY','Very Slippery','Very Slippery'),
('SURFACE_SLIPPERY','Slippery','Slippery'),
('SURFACE_NOT_SLIPPERY','Not Slippery','Not Slippery'),
('SURFACE_TTM_VINES','Vines','Vines'),
('SURFACE_SHALLOW_QUICKSAND','Shallow Quicksand','Shallow Quicksand'),
('SURFACE_DEEP_QUICKSAND','Deep Quicksand','Deep Quicksand'),
('SURFACE_INSTANT_QUICKSAND','Instant Quicksand','Instant Quicksand'),
('SURFACE_DEEP_MOVING_QUICKSAND','Deep Moving Quicksand','Deep Moving Quicksand'),
('SURFACE_SHALLOW_MOVING_QUICKSAND','Shallow Moving Quicksand','Shallow Moving Quicksand'),
('SURFACE_QUICKSAND','Quicksand','Quicksand'),
('SURFACE_MOVING_QUICKSAND','Moving Quicksand','Moving Quicksand'),
('SURFACE_WALL_MISC','Wall Misc','Wall Misc'),
('SURFACE_NOISE_DEFAULT','Noise Default','Noise Default'),
('SURFACE_NOISE_SLIPPERY','Noise Slippery','Noise Slippery'),
('SURFACE_HORIZONTAL_WIND','Horizontal Wind','Horizontal Wind'),
('SURFACE_INSTANT_MOVING_QUICKSAND','Instant Moving Quicksand','Instant Moving Quicksand'),
('SURFACE_ICE','Ice','Ice'),
('SURFACE_HARD','Hard','Hard'),
('SURFACE_HARD_SLIPPERY','Hard Slippery','Hard Slippery'),
('SURFACE_HARD_VERY_SLIPPERY','Hard Very Slippery','Hard Very Slippery'),
('SURFACE_HARD_NOT_SLIPPERY','Hard Not Slippery','Hard Not Slippery'),
('SURFACE_VERTICAL_WIND','Vertical Wind','Vertical Wind'),
('SURFACE_SWITCH','Switch','Switch'),
('SURFACE_VANISH_CAP_WALLS','Vanish Cap Walls','Vanish Cap Walls'),
]
enumCollisionType = [
('SURFACE_DEFAULT','Default','Default'),
('SURFACE_BURNING','Burning','Burning'),
('SURFACE_0004','0004','0004'),
('SURFACE_HANGABLE','Hangable','Hangable'),
('SURFACE_SLOW','Slow','Slow'),
('SURFACE_DEATH_PLANE','Death Plane','Death Plane'),
('SURFACE_CLOSE_CAMERA','Close Camera','Close Camera'),
('SURFACE_WATER','Water','Water'),
('SURFACE_FLOWING_WATER','Flowing Water','Flowing Water'),
('SURFACE_INTANGIBLE','Intangible','Intangible'),
('SURFACE_VERY_SLIPPERY','Very Slippery','Very Slippery'),
('SURFACE_SLIPPERY','Slippery','Slippery'),
('SURFACE_NOT_SLIPPERY','Not Slippery','Not Slippery'),
('SURFACE_TTM_VINES','Vines','Vines'),
('SURFACE_MGR_MUSIC','Music','Music'),
('SURFACE_INSTANT_WARP_1B','Instant Warp 1B','Instant Warp 1B'),
('SURFACE_INSTANT_WARP_1C','Instant Warp 1C','Instant Warp 1C'),
('SURFACE_INSTANT_WARP_1D','Instant Warp 1D','Instant Warp 1D'),
('SURFACE_INSTANT_WARP_1E','Instant Warp 1E','Instant Warp 1E'),
('SURFACE_SHALLOW_QUICKSAND','Shallow Quicksand','Shallow Quicksand'),
('SURFACE_DEEP_QUICKSAND','Deep Quicksand','Deep Quicksand'),
('SURFACE_INSTANT_QUICKSAND','Instant Quicksand','Instant Quicksand'),
('SURFACE_DEEP_MOVING_QUICKSAND','Deep Moving Quicksand','Deep Moving Quicksand'),
('SURFACE_SHALLOW_MOVING_QUICKSAND','Shallow Moving Quicksand','Shallow Moving Quicksand'),
('SURFACE_QUICKSAND','Quicksand','Quicksand'),
('SURFACE_MOVING_QUICKSAND','Moving Quicksand','Moving Quicksand'),
('SURFACE_WALL_MISC','Wall Misc','Wall Misc'),
('SURFACE_NOISE_DEFAULT','Noise Default','Noise Default'),
('SURFACE_NOISE_SLIPPERY','Noise Slippery','Noise Slippery'),
('SURFACE_HORIZONTAL_WIND','Horizontal Wind','Horizontal Wind'),
('SURFACE_INSTANT_MOVING_QUICKSAND','Instant Moving Quicksand','Instant Moving Quicksand'),
('SURFACE_ICE','Ice','Ice'),
('SURFACE_LOOK_UP_WARP','Look Up Warp','Look Up Warp'),
('SURFACE_HARD','Hard','Hard'),
('SURFACE_WARP','Warp','Warp'),
('SURFACE_TIMER_START','Timer Start','Timer Start'),
('SURFACE_TIMER_END','Timer End','Timer End'),
('SURFACE_HARD_SLIPPERY','Hard Slippery','Hard Slippery'),
('SURFACE_HARD_VERY_SLIPPERY','Hard Very Slippery','Hard Very Slippery'),
('SURFACE_HARD_NOT_SLIPPERY','Hard Not Slippery','Hard Not Slippery'),
('SURFACE_VERTICAL_WIND','Vertical Wind','Vertical Wind'),
('SURFACE_BOSS_FIGHT_CAMERA','Boss Fight Camera','Boss Fight Camera'),
('SURFACE_CAMERA_FREE_ROAM','Camera Free Roam','Camera Free Roam'),
('SURFACE_THI3_WALLKICK','Wallkick','Wallkick'),
('SURFACE_CAMERA_PLATFORM','Camera Platform','Camera Platform'),
('SURFACE_CAMERA_MIDDLE','Camera Middle','Camera Middle'),
('SURFACE_CAMERA_ROTATE_RIGHT','Camera Rotate Right','Camera Rotate Right'),
('SURFACE_CAMERA_ROTATE_LEFT','Camera Rotate Left','Camera Rotate Left'),
('SURFACE_CAMERA_BOUNDARY','Camera Boundary','Camera Boundary'),
('SURFACE_NOISE_VERY_SLIPPERY_73','Noise Very Slippery 73','Noise Very Slippery 73'),
('SURFACE_NOISE_VERY_SLIPPERY_74','Noise Very Slippery 74','Noise Very Slippery 74'),
('SURFACE_NOISE_VERY_SLIPPERY','Noise Very Slippery','Noise Very Slippery'),
('SURFACE_NO_CAM_COLLISION','No Cam Collision','No Cam Collision'),
('SURFACE_NO_CAM_COLLISION_77','No Cam Collision 77','No Cam Collision 77'),
('SURFACE_NO_CAM_COL_VERY_SLIPPERY','No Cam Collision Very Slippery','No Cam Collision Very Slippery'),
('SURFACE_NO_CAM_COL_SLIPPERY','No Cam Collision Slippery','No Cam Collision Slippery'),
('SURFACE_SWITCH','Switch','Switch'),
('SURFACE_VANISH_CAP_WALLS','Vanish Cap Walls','Vanish Cap Walls'),
('SURFACE_PAINTING_WOBBLE_A6','Painting Wobble A6','Painting Wobble A6'),
('SURFACE_PAINTING_WOBBLE_A7','Painting Wobble A7','Painting Wobble A7'),
('SURFACE_PAINTING_WOBBLE_A8','Painting Wobble A8','Painting Wobble A8'),
('SURFACE_PAINTING_WOBBLE_A9','Painting Wobble A9','Painting Wobble A9'),
('SURFACE_PAINTING_WOBBLE_AA','Painting Wobble AA','Painting Wobble AA'),
('SURFACE_PAINTING_WOBBLE_AB','Painting Wobble AB','Painting Wobble AB'),
('SURFACE_PAINTING_WOBBLE_AC','Painting Wobble AC','Painting Wobble AC'),
('SURFACE_PAINTING_WOBBLE_AD','Painting Wobble AD','Painting Wobble AD'),
('SURFACE_PAINTING_WOBBLE_AE','Painting Wobble AE','Painting Wobble AE'),
('SURFACE_PAINTING_WOBBLE_AF','Painting Wobble AF','Painting Wobble AF'),
('SURFACE_PAINTING_WOBBLE_B0','Painting Wobble B0','Painting Wobble B0'),
('SURFACE_PAINTING_WOBBLE_B1','Painting Wobble B1','Painting Wobble B1'),
('SURFACE_PAINTING_WOBBLE_B2','Painting Wobble B2','Painting Wobble B2'),
('SURFACE_PAINTING_WOBBLE_B3','Painting Wobble B3','Painting Wobble B3'),
('SURFACE_PAINTING_WOBBLE_B4','Painting Wobble B4','Painting Wobble B4'),
('SURFACE_PAINTING_WOBBLE_B5','Painting Wobble B5','Painting Wobble B5'),
('SURFACE_PAINTING_WOBBLE_B6','Painting Wobble B6','Painting Wobble B6'),
('SURFACE_PAINTING_WOBBLE_B7','Painting Wobble B7','Painting Wobble B7'),
('SURFACE_PAINTING_WOBBLE_B8','Painting Wobble B8','Painting Wobble B8'),
('SURFACE_PAINTING_WOBBLE_B9','Painting Wobble B9','Painting Wobble B9'),
('SURFACE_PAINTING_WOBBLE_BA','Painting Wobble BA','Painting Wobble BA'),
('SURFACE_PAINTING_WOBBLE_BB','Painting Wobble BB','Painting Wobble BB'),
('SURFACE_PAINTING_WOBBLE_BC','Painting Wobble BC','Painting Wobble BC'),
('SURFACE_PAINTING_WOBBLE_BD','Painting Wobble BD','Painting Wobble BD'),
('SURFACE_PAINTING_WOBBLE_BE','Painting Wobble BE','Painting Wobble BE'),
('SURFACE_PAINTING_WOBBLE_BF','Painting Wobble BF','Painting Wobble BF'),
('SURFACE_PAINTING_WOBBLE_C0','Painting Wobble C0','Painting Wobble C0'),
('SURFACE_PAINTING_WOBBLE_C1','Painting Wobble C1','Painting Wobble C1'),
('SURFACE_PAINTING_WOBBLE_C2','Painting Wobble C2','Painting Wobble C2'),
('SURFACE_PAINTING_WOBBLE_C3','Painting Wobble C3','Painting Wobble C3'),
('SURFACE_PAINTING_WOBBLE_C4','Painting Wobble C4','Painting Wobble C4'),
('SURFACE_PAINTING_WOBBLE_C5','Painting Wobble C5','Painting Wobble C5'),
('SURFACE_PAINTING_WOBBLE_C6','Painting Wobble C6','Painting Wobble C6'),
('SURFACE_PAINTING_WOBBLE_C7','Painting Wobble C7','Painting Wobble C7'),
('SURFACE_PAINTING_WOBBLE_C8','Painting Wobble C8','Painting Wobble C8'),
('SURFACE_PAINTING_WOBBLE_C9','Painting Wobble C9','Painting Wobble C9'),
('SURFACE_PAINTING_WOBBLE_CA','Painting Wobble CA','Painting Wobble CA'),
('SURFACE_PAINTING_WOBBLE_CB','Painting Wobble CB','Painting Wobble CB'),
('SURFACE_PAINTING_WOBBLE_CC','Painting Wobble CC','Painting Wobble CC'),
('SURFACE_PAINTING_WOBBLE_CD','Painting Wobble CD','Painting Wobble CD'),
('SURFACE_PAINTING_WOBBLE_CE','Painting Wobble CE','Painting Wobble CE'),
('SURFACE_PAINTING_WOBBLE_CF','Painting Wobble CF','Painting Wobble CF'),
('SURFACE_PAINTING_WOBBLE_D0','Painting Wobble D0','Painting Wobble D0'),
('SURFACE_PAINTING_WOBBLE_D1','Painting Wobble D1','Painting Wobble D1'),
('SURFACE_PAINTING_WOBBLE_D2','Painting Wobble D2','Painting Wobble D2'),
('SURFACE_PAINTING_WARP_D3','Painting Warp D3','Painting Warp D3'),
('SURFACE_PAINTING_WARP_D4','Painting Warp D4','Painting Warp D4'),
('SURFACE_PAINTING_WARP_D5','Painting Warp D5','Painting Warp D5'),
('SURFACE_PAINTING_WARP_D6','Painting Warp D6','Painting Warp D6'),
('SURFACE_PAINTING_WARP_D7','Painting Warp D7','Painting Warp D7'),
('SURFACE_PAINTING_WARP_D8','Painting Warp D8','Painting Warp D8'),
('SURFACE_PAINTING_WARP_D9','Painting Warp D9','Painting Warp D9'),
('SURFACE_PAINTING_WARP_DA','Painting Warp DA','Painting Warp DA'),
('SURFACE_PAINTING_WARP_DB','Painting Warp DB','Painting Warp DB'),
('SURFACE_PAINTING_WARP_DC','Painting Warp DC','Painting Warp DC'),
('SURFACE_PAINTING_WARP_DD','Painting Warp DD','Painting Warp DD'),
('SURFACE_PAINTING_WARP_DE','Painting Warp DE','Painting Warp DE'),
('SURFACE_PAINTING_WARP_DF','Painting Warp DF','Painting Warp DF'),
('SURFACE_PAINTING_WARP_E0','Painting Warp E0','Painting Warp E0'),
('SURFACE_PAINTING_WARP_E1','Painting Warp E1','Painting Warp E1'),
('SURFACE_PAINTING_WARP_E2','Painting Warp E2','Painting Warp E2'),
('SURFACE_PAINTING_WARP_E3','Painting Warp E3','Painting Warp E3'),
('SURFACE_PAINTING_WARP_E4','Painting Warp E4','Painting Warp E4'),
('SURFACE_PAINTING_WARP_E5','Painting Warp E5','Painting Warp E5'),
('SURFACE_PAINTING_WARP_E6','Painting Warp E6','Painting Warp E6'),
('SURFACE_PAINTING_WARP_E7','Painting Warp E7','Painting Warp E7'),
('SURFACE_PAINTING_WARP_E8','Painting Warp E8','Painting Warp E8'),
('SURFACE_PAINTING_WARP_E9','Painting Warp E9','Painting Warp E9'),
('SURFACE_PAINTING_WARP_EA','Painting Warp EA','Painting Warp EA'),
('SURFACE_PAINTING_WARP_EB','Painting Warp EB','Painting Warp EB'),
('SURFACE_PAINTING_WARP_EC','Painting Warp EC','Painting Warp EC'),
('SURFACE_PAINTING_WARP_ED','Painting Warp ED','Painting Warp ED'),
('SURFACE_PAINTING_WARP_EE','Painting Warp EE','Painting Warp EE'),
('SURFACE_PAINTING_WARP_EF','Painting Warp EF','Painting Warp EF'),
('SURFACE_PAINTING_WARP_F0','Painting Warp F0','Painting Warp F0'),
('SURFACE_PAINTING_WARP_F1','Painting Warp F1','Painting Warp F1'),
('SURFACE_PAINTING_WARP_F2','Painting Warp F2','Painting Warp F2'),
('SURFACE_PAINTING_WARP_F3','Painting Warp F3','Painting Warp F3'),
('SURFACE_TTC_PAINTING_1','TTC Painting 1','TTC Painting 1'),
('SURFACE_TTC_PAINTING_2','TTC Painting 2','TTC Painting 2'),
('SURFACE_TTC_PAINTING_3','TTC Painting 3','TTC Painting 3'),
('SURFACE_PAINTING_WARP_F7','Painting Warp F7','Painting Warp F7'),
('SURFACE_PAINTING_WARP_F8','Painting Warp F8','Painting Warp F8'),
('SURFACE_PAINTING_WARP_F9','Painting Warp F9','Painting Warp F9'),
('SURFACE_PAINTING_WARP_FA','Painting Warp FA','Painting Warp FA'),
('SURFACE_PAINTING_WARP_FB','Painting Warp FB','Painting Warp FB'),
('SURFACE_PAINTING_WARP_FC','Painting Warp FC','Painting Warp FC'),
('SURFACE_WOBBLING_WARP','Wobbling Warp','Wobbling Warp'),
('SURFACE_TRAPDOOR','Trapdoor','Trapdoor'),
]
enumPaintingCollisionType = [
('SURFACE_PAINTING_WOBBLE_A6','Painting Wobble A6','Painting Wobble A6'),
('SURFACE_PAINTING_WOBBLE_A7','Painting Wobble A7','Painting Wobble A7'),
('SURFACE_PAINTING_WOBBLE_A8','Painting Wobble A8','Painting Wobble A8'),
('SURFACE_PAINTING_WOBBLE_A9','Painting Wobble A9','Painting Wobble A9'),
('SURFACE_PAINTING_WOBBLE_AA','Painting Wobble AA','Painting Wobble AA'),
('SURFACE_PAINTING_WOBBLE_AB','Painting Wobble AB','Painting Wobble AB'),
('SURFACE_PAINTING_WOBBLE_AC','Painting Wobble AC','Painting Wobble AC'),
('SURFACE_PAINTING_WOBBLE_AD','Painting Wobble AD','Painting Wobble AD'),
('SURFACE_PAINTING_WOBBLE_AE','Painting Wobble AE','Painting Wobble AE'),
('SURFACE_PAINTING_WOBBLE_AF','Painting Wobble AF','Painting Wobble AF'),
('SURFACE_PAINTING_WOBBLE_B0','Painting Wobble B0','Painting Wobble B0'),
('SURFACE_PAINTING_WOBBLE_B1','Painting Wobble B1','Painting Wobble B1'),
('SURFACE_PAINTING_WOBBLE_B2','Painting Wobble B2','Painting Wobble B2'),
('SURFACE_PAINTING_WOBBLE_B3','Painting Wobble B3','Painting Wobble B3'),
('SURFACE_PAINTING_WOBBLE_B4','Painting Wobble B4','Painting Wobble B4'),
('SURFACE_PAINTING_WOBBLE_B5','Painting Wobble B5','Painting Wobble B5'),
('SURFACE_PAINTING_WOBBLE_B6','Painting Wobble B6','Painting Wobble B6'),
('SURFACE_PAINTING_WOBBLE_B7','Painting Wobble B7','Painting Wobble B7'),
('SURFACE_PAINTING_WOBBLE_B8','Painting Wobble B8','Painting Wobble B8'),
('SURFACE_PAINTING_WOBBLE_B9','Painting Wobble B9','Painting Wobble B9'),
('SURFACE_PAINTING_WOBBLE_BA','Painting Wobble BA','Painting Wobble BA'),
('SURFACE_PAINTING_WOBBLE_BB','Painting Wobble BB','Painting Wobble BB'),
('SURFACE_PAINTING_WOBBLE_BC','Painting Wobble BC','Painting Wobble BC'),
('SURFACE_PAINTING_WOBBLE_BD','Painting Wobble BD','Painting Wobble BD'),
('SURFACE_PAINTING_WOBBLE_BE','Painting Wobble BE','Painting Wobble BE'),
('SURFACE_PAINTING_WOBBLE_BF','Painting Wobble BF','Painting Wobble BF'),
('SURFACE_PAINTING_WOBBLE_C0','Painting Wobble C0','Painting Wobble C0'),
('SURFACE_PAINTING_WOBBLE_C1','Painting Wobble C1','Painting Wobble C1'),
('SURFACE_PAINTING_WOBBLE_C2','Painting Wobble C2','Painting Wobble C2'),
('SURFACE_PAINTING_WOBBLE_C3','Painting Wobble C3','Painting Wobble C3'),
('SURFACE_PAINTING_WOBBLE_C4','Painting Wobble C4','Painting Wobble C4'),
('SURFACE_PAINTING_WOBBLE_C5','Painting Wobble C5','Painting Wobble C5'),
('SURFACE_PAINTING_WOBBLE_C6','Painting Wobble C6','Painting Wobble C6'),
('SURFACE_PAINTING_WOBBLE_C7','Painting Wobble C7','Painting Wobble C7'),
('SURFACE_PAINTING_WOBBLE_C8','Painting Wobble C8','Painting Wobble C8'),
('SURFACE_PAINTING_WOBBLE_C9','Painting Wobble C9','Painting Wobble C9'),
('SURFACE_PAINTING_WOBBLE_CA','Painting Wobble CA','Painting Wobble CA'),
('SURFACE_PAINTING_WOBBLE_CB','Painting Wobble CB','Painting Wobble CB'),
('SURFACE_PAINTING_WOBBLE_CC','Painting Wobble CC','Painting Wobble CC'),
('SURFACE_PAINTING_WOBBLE_CD','Painting Wobble CD','Painting Wobble CD'),
('SURFACE_PAINTING_WOBBLE_CE','Painting Wobble CE','Painting Wobble CE'),
('SURFACE_PAINTING_WOBBLE_CF','Painting Wobble CF','Painting Wobble CF'),
('SURFACE_PAINTING_WOBBLE_D0','Painting Wobble D0','Painting Wobble D0'),
('SURFACE_PAINTING_WOBBLE_D1','Painting Wobble D1','Painting Wobble D1'),
('SURFACE_PAINTING_WOBBLE_D2','Painting Wobble D2','Painting Wobble D2'),
('SURFACE_PAINTING_WARP_D3','Painting Warp D3','Painting Warp D3'),
('SURFACE_PAINTING_WARP_D4','Painting Warp D4','Painting Warp D4'),
('SURFACE_PAINTING_WARP_D5','Painting Warp D5','Painting Warp D5'),
('SURFACE_PAINTING_WARP_D6','Painting Warp D6','Painting Warp D6'),
('SURFACE_PAINTING_WARP_D7','Painting Warp D7','Painting Warp D7'),
('SURFACE_PAINTING_WARP_D8','Painting Warp D8','Painting Warp D8'),
('SURFACE_PAINTING_WARP_D9','Painting Warp D9','Painting Warp D9'),
('SURFACE_PAINTING_WARP_DA','Painting Warp DA','Painting Warp DA'),
('SURFACE_PAINTING_WARP_DB','Painting Warp DB','Painting Warp DB'),
('SURFACE_PAINTING_WARP_DC','Painting Warp DC','Painting Warp DC'),
('SURFACE_PAINTING_WARP_DD','Painting Warp DD','Painting Warp DD'),
('SURFACE_PAINTING_WARP_DE','Painting Warp DE','Painting Warp DE'),
('SURFACE_PAINTING_WARP_DF','Painting Warp DF','Painting Warp DF'),
('SURFACE_PAINTING_WARP_E0','Painting Warp E0','Painting Warp E0'),
('SURFACE_PAINTING_WARP_E1','Painting Warp E1','Painting Warp E1'),
('SURFACE_PAINTING_WARP_E2','Painting Warp E2','Painting Warp E2'),
('SURFACE_PAINTING_WARP_E3','Painting Warp E3','Painting Warp E3'),
('SURFACE_PAINTING_WARP_E4','Painting Warp E4','Painting Warp E4'),
('SURFACE_PAINTING_WARP_E5','Painting Warp E5','Painting Warp E5'),
('SURFACE_PAINTING_WARP_E6','Painting Warp E6','Painting Warp E6'),
('SURFACE_PAINTING_WARP_E7','Painting Warp E7','Painting Warp E7'),
('SURFACE_PAINTING_WARP_E8','Painting Warp E8','Painting Warp E8'),
('SURFACE_PAINTING_WARP_E9','Painting Warp E9','Painting Warp E9'),
('SURFACE_PAINTING_WARP_EA','Painting Warp EA','Painting Warp EA'),
('SURFACE_PAINTING_WARP_EB','Painting Warp EB','Painting Warp EB'),
('SURFACE_PAINTING_WARP_EC','Painting Warp EC','Painting Warp EC'),
('SURFACE_PAINTING_WARP_ED','Painting Warp ED','Painting Warp ED'),
('SURFACE_PAINTING_WARP_EE','Painting Warp EE','Painting Warp EE'),
('SURFACE_PAINTING_WARP_EF','Painting Warp EF','Painting Warp EF'),
('SURFACE_PAINTING_WARP_F0','Painting Warp F0','Painting Warp F0'),
('SURFACE_PAINTING_WARP_F1','Painting Warp F1','Painting Warp F1'),
('SURFACE_PAINTING_WARP_F2','Painting Warp F2','Painting Warp F2'),
('SURFACE_PAINTING_WARP_F3','Painting Warp F3','Painting Warp F3'),
('SURFACE_TTC_PAINTING_1','TTC Painting 1','TTC Painting 1'),
('SURFACE_TTC_PAINTING_2','TTC Painting 2','TTC Painting 2'),
('SURFACE_TTC_PAINTING_3','TTC Painting 3','TTC Painting 3'),
('SURFACE_PAINTING_WARP_F7','Painting Warp F7','Painting Warp F7'),
('SURFACE_PAINTING_WARP_F8','Painting Warp F8','Painting Warp F8'),
('SURFACE_PAINTING_WARP_F9','Painting Warp F9','Painting Warp F9'),
('SURFACE_PAINTING_WARP_FA','Painting Warp FA','Painting Warp FA'),
('SURFACE_PAINTING_WARP_FB','Painting Warp FB','Painting Warp FB'),
('SURFACE_PAINTING_WARP_FC','Painting Warp FC','Painting Warp FC'),
]
enumUnusedCollisionType = [
('SURFACE_0004','0004','0004'),
]
+325
View File
@@ -0,0 +1,325 @@
import mathutils
# RAM address used in evaluating switch for hatless Mario
marioHatSwitch = 0x80277740
marioLowPolySwitch = 0x80277150
marioFaceExpressionCount = 4
loadMarioMIO0 = 0x2ABCA0
loadMarioGeo = 0x2ABCB8
# Full available rom interval for extended 0x04 bank (from SM64 Editor)
marioFullRomInterval = (0x11D8930, 0x11FFF00)
defaultExtendSegment4 = (0x11A35B8, 0x11FFF00)
mainLevelLoadScriptSegment = {
0x15 : (0x2ABCA0, 0x2AC6B0)
}
bank0Segment = {
0x00 : (0x000000, 0x7FFFFF)
}
# Segments for common geolayouts and Mario
loadSegmentAddresses = {
0x03 : 0x2ABCAC,
0x04 : 0x2ABCA0,
0x13 : 0x2ABCD0,
0x16 : 0x2ABCC4,
0x17 : 0x2ABCB8
}
geoNodeRotateOrder = 'ZXY'
sm64ToBlenderScale = 0.0047
marioScale = 0.25
sm64BoneUp = mathutils.Vector([1,0,0])
# Equivalent of euler(-90, -90, 0)
blenderToSM64Rotation = mathutils.Matrix(
[[0, 1, 0],
[0, 0, 1],
[1, 0, 0]]
)
blenderToSM64RotationLevel = mathutils.Matrix(
[[1, 0, 0],
[0, 0, 1],
[0,-1, 0]]
)
colorFormats = {
'RGBA' : 0,
'YUV' : 1,
'CI' : 2,
'IA' : 3,
'I' : 4
}
pixelBitSizes = {
'4bit' : 0,
'8bit' : 1,
'16bit' : 2,
'32bit' : 3
}
lightIndices = [0x86, 0x88, 0x8A, 0x8C, 0x8E, 0x90, 0x92, 0x94]
# Color combinations
# In SM64, environment alpha controls Mario's alpha.
# 2 cycle with specular lighting stored in primitive
S_PHONG_TEX = ['TEXEL0', '0', 'SHADE', '0',
'TEXEL0', '0', 'ENVIRONMENT', '0',
'PRIMITIVE', 'COMBINED', 'TEXEL0', 'COMBINED',
'0', '0', '0', 'COMBINED']
# MULTIPLY TWO TEXTURES
S_MULTIPLY = [
'TEXEL0', '0', 'TEXEL1', '0',
'0', '0', '0', 'ENVIRONMENT',
'TEXEL0', '0', 'TEXEL1', '0',
'0', '0', '0', 'ENVIRONMENT'
]
S_PRIM_TRANSPARENT_SHADE = ['TEXEL0', '0', 'SHADE', '0',
'TEXEL0', '0', 'PRIMITIVE', '0',
'TEXEL0', '0', 'SHADE', '0',
'TEXEL0', '0', 'PRIMITIVE', '0']
# REGULAR SHADED TEXTURE
S_SHADED_TEX = ['TEXEL0', '0', 'SHADE', '0',
'0', '0', '0', 'ENVIRONMENT',
'TEXEL0', '0', 'SHADE', '0',
'0', '0', '0', 'ENVIRONMENT']
# VERTEX COLOR
S_VERTEX_COLORED_TEX = ['TEXEL0', '0', 'SHADE', '0',
'ENVIRONMENT', '0', 'SHADE', '0',
'TEXEL0', '0', 'SHADE', '0',
'ENVIRONMENT', '0', 'SHADE', '0']
S_SHADED_TEX_CUTOUT = ['TEXEL0', '0', 'SHADE', '0',
'TEXEL0', '0', 'ENVIRONMENT', '0',
'TEXEL0', '0', 'SHADE', '0',
'TEXEL0', '0', 'ENVIRONMENT', '0']
# UNLIT TEXTURE, USED FOR METAL MARIO
S_UNLIT_TEX = ['0', '0', '0', 'TEXEL0',
'0', '0', '0', 'ENVIRONMENT',
'0', '0', '0', 'TEXEL0',
'0', '0', '0', 'ENVIRONMENT']
# SM64 CUSTOM IMPORTER CC
S_SHADED_TEX_NOALPHA = ['TEXEL0', '0', 'SHADE', '0',
'0', '0', '0', 'SHADE',
'TEXEL0', '0', 'SHADE', '0',
'0', '0', '0', 'SHADE']
# SM64 BODY CC
S_SHADED_SOLID = ['0', '0', '0', 'SHADE',
'0', '0', '0', 'ENVIRONMENT',
'0', '0', '0', 'SHADE',
'0', '0', '0', 'ENVIRONMENT']
# SM64 BODY TEXTURES (FACE, SIDEBURNS, ETC.)
S_UNLIT_DECAL_ON_SHADED_SOLID = ['TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE',
'0', '0', '0', 'ENVIRONMENT',
'TEXEL0', 'SHADE', 'TEXEL0_ALPHA', 'SHADE',
'0', '0', '0', 'ENVIRONMENT']
# SHADED RANDOM NOISE
S_SHADED_NOISE = ['NOISE', '0', 'SHADE', '0',
'0', '0', '0', 'ENVIRONMENT',
'NOISE', '0', 'SHADE', '0',
'0', '0', '0', 'ENVIRONMENT']
colorCombinationCommands = [
0x03, #load lighting data
0xB6, #clear geometry params
0xB7, #set geometry params
0xBB, #set texture scaling factor
0xF3, #set texture size
0xF5, #set texture properties
0xF7, #set fill color
0xF8, #set fog color
0xFB, #set env color
0xFC, #set color combination
0xFD #load texture
]
drawCommands = [
0x04, #load vertex data
0xBF #draw triangle
]
originalMarioHeadROMInterval = (
0x011B4F58,
0x011B5710
)
marioVanishOffsets = {
"regular" : 0xB0C,
"metal" : 0x9EC,
}
commonGeolayoutPointers = {
'Dorrie' : [2039136, 'HMC'],
'Bowser' : [1809204, 'BFB'],
'Bowser 2' : [1809328, 'BFB'],
'Lakitu' : [1985520, 'CC']
}
draw_layer_enums = [
('1', 'Solid', '0x01'),
('2', 'Solid Decal', '0x02'),
('4', 'Transparent (No Blending)', '0x04'),
('5', 'Transparent (Blending Front)', '0x05'),
('6', 'Transparent (Blending Back)', '0x06'),
]
level_enums = [
("HH" , "Haunted House" , "HH" ),
("CCM", "Cool Cool Mountain" , "CCM"),
("IC" , "Inside Castle" , "IC" ),
("HMC", "Hazy Maze Cave" , "HMC"),
("SSL", "Shifting Sand Land" , "SSL"),
("BOB", "Bob-Omb's Battlefield" , "BOB"),
("SML", "Snow Man's land" , "SML"),
("WDW", "Wet Dry World" , "WDW"),
("JRB", "Jolly Roger Bay" , "JRB"),
("THI", "Tiny Huge Island" , "THI"),
("TTC", "Tick Tock Clock" , "TTC"),
("RR" , "Rainbow Ride" , "RR" ),
("CG" , "Castle Grounds" , "CG" ),
("BFC", "Bowser First Course" , "BFC"),
("VC" , "Vanish Cap" , "VC" ),
("BFS", "Bowser's Fire Sea" , "BFS"),
("SA" , "Secret Aquarium" , "SA" ),
("BTC", "Bowser Third Course" , "BTC"),
("LLL", "Lethal Lava Land" , "LLL"),
("DDD", "Dire Dire Docks" , "DDD"),
("WF" , "Whomp's Fortress" , "WF" ),
("PIC", "Picture at the end" , "PIC"),
("CC" , "Castle Courtyard" , "CC" ),
("PSS", "Peach's Secret Slide" , "PSS"),
("MC" , "Metal Cap" , "MC" ),
("WC" , "Wing Cap" , "WC" ),
("BFB", "Bowser First Battle" , "BFB"),
("RC" , "Rainbow Clouds" , "RC" ),
("BSB", "Bowser Second Battle" , "BSB"),
("BTB", "Bowser Third Battle" , "BTB"),
("TTM", "Tall Tall Mountain" , "TTM"),
]
class SM64_CharacterImportData:
def __init__(self, geoAddr, level, switchDict):
self.geoAddr = geoAddr
self.level = level
self.switchDict = switchDict
character_enums = [
("Mario" , "Mario" , "Mario" ),
("Peach" , "Peach" , "Peach" ),
#("Bowser" , "Bowser" , "Bowser" ),
#("Koopa" , "Koopa" , "Koopa" ),
#("Toad" , "Toad" , "Toad" )
]
# switch index starts at 1.
# switch option index starts at 0.
sm64_character_data = {
"Mario" : SM64_CharacterImportData('12A784', 'CG', {
1 : { # Low poly mario
0 : 'Ignore'
},
2 : {
1 : "Material", # metal mario
2 : "Draw Layer", # vanish mario
3 : "Material", # metal vanish mario
},
3 : { # cap vs no cap
},
4 : { # face animation (cap)
0 : "Material",
1 : "Material",
2 : "Material",
3 : "Material",
4 : "Material",
5 : "Material",
6 : "Material",
7 : "Material",
},
5 : { # face animation (no cap)
0 : "Material",
1 : "Material",
2 : "Material",
3 : "Material",
4 : "Material",
5 : "Material",
6 : "Material",
7 : "Material",
},
6 : { # right fist type
# 0 = closed, 1 = open, 2 is something, 3-5 are same as 2
3 : 'Ignore',
4 : 'Ignore',
5 : 'Ignore',
},
7 : { # left fist type
# 0 = closed, 1 = open, 2 = peace, 3 = hold cap, 4 = hold cap wing
},
}),
'Peach' : SM64_CharacterImportData('180950', 'CG', {
1 : { # transparent peach
1 : 'Draw Layer',
},
2 : { # 0-3 = face animation kissing, 4-7 = face animation normal
0 : "Material",
1 : "Material",
2 : "Material",
3 : "Material",
4 : "Material",
5 : "Material",
6 : "Material",
7 : "Material",
}
}),
}
level_pointers = {
"HH" : 0x2AC094,
"CCM": 0x2AC0A8,
"IC" : 0x2AC0BC,
"HMC": 0x2AC0D0,
"SSL": 0x2AC0E4,
"BOB": 0x2AC0F8,
"SML": 0x2AC10C,
"WDW": 0x2AC120,
"JRB": 0x2AC134,
"THI": 0x2AC148,
"TTC": 0x2AC15C,
"RR" : 0x2AC170,
"CG" : 0x2AC184,
"BFC": 0x2AC198,
"VC" : 0x2AC1AC,
"BFS": 0x2AC1C0,
"SA" : 0x2AC1D4,
"BTC": 0x2AC1E8,
"LLL": 0x2AC1FC,
"DDD": 0x2AC210,
"WF" : 0x2AC224,
"PIC": 0x2AC238,
"CC" : 0x2AC24C,
"PSS": 0x2AC260,
"MC" : 0x2AC274,
"WC" : 0x2AC288,
"BFB": 0x2AC29C,
"RC" : 0x2AC2B0,
"BSB": 0x2AC2C4,
"BTB": 0x2AC2D8,
"TTM": 0x2AC2EC,
}
+406
View File
@@ -0,0 +1,406 @@
import bpy
from bpy.utils import register_class, unregister_class
from .sm64_geolayout_utility import createBoneGroups, addBoneToGroup
from .utility import prop_split
enumBoneType = [
("Switch", "Switch (0x0E)", "Switch"),
("Start", "Start (0x0B)", "Start"),
("TranslateRotate", "Translate Rotate (0x10)", "Translate Rotate"),
("Translate", "Translate (0x11)", "Translate"),
("Rotate", "Rotate (0x12)", "Rotate"),
("Billboard", "Billboard (0x14)", "Billboard"),
("DisplayList", "Display List (0x15)", "Display List"),
("Shadow", "Shadow (0x16)", "Shadow"),
("Function", "Function (0x18)", "Function"),
("HeldObject", "Held Object (0x1C)", "Held Object"),
("Scale", "Scale (0x1D)", "Scale"),
("StartRenderArea", "Start Render Area (0x20)", "Start Render Area"),
("Ignore", "Ignore", "Ignore bones when exporting."),
("SwitchOption", "Switch Option", "Switch Option"),
("DisplayListWithOffset", "Display List With Offset (0x13)",
"Display List With Offset"),
]
enumDrawLayers = [
('0', 'Background (0x00)', 'Background'),
('1', 'Opaque (0x01)', 'Opaque'),
('2', 'Opaque Decal (0x02)', 'Opaque Decal'),
('3', 'Opaque Intersecting (0x03)', 'Opaque Intersecting'),
('4', 'Cutout (0x04)', 'Cutout'),
('5', 'Transparent (0x05)', 'Transparent'),
('6', 'Transparent Decal (0x06)', 'Transparent Decal'),
('7', 'Transparent Intersecting (0x07)', 'Transparent Intersecting'),
]
enumFieldLayout = [
('0', 'Translate And Rotate', 'Translate And Rotate'),
('1', 'Translate', 'Translate'),
('2', 'Rotate', 'Rotate'),
#('3', 'Rotate Y', 'Rotate Y'),
# Rotate Y complicates exporting code, so we treat it as Rotate.
]
enumShadowType = [
('0', 'Circle Scalable (9 verts)', 'Circle Scalable (9 verts)'),
('1', 'Circle Scalable (4 verts)', 'Circle Scalable (4 verts)'),
('2', 'Circle Permanent (4 verts)', 'Circle Permanent (4 verts)'),
('10', 'Square Permanent', 'Square Permanent'),
('11', 'Square Scalable', 'Square Scalable'),
('12', 'Square Togglable', 'Square Togglable'),
]
enumSwitchOptions = [
('Mesh', 'Mesh Override', 'Switch to a different mesh hierarchy.'),
('Material', 'Material Override', 'Use the same mesh hierarchy, but override material on ALL meshes. Optionally override draw layer.'),
('Draw Layer', 'Draw Layer Override', 'Override draw layer only.'),
]
enumMatOverrideOptions = [
("All", 'All', 'Override every material with this one.'),
("Specific", 'Specific', 'Only override instances of give material.'),
]
def drawGeoInfo(panel, bone):
panel.layout.box().label(text = 'Geolayout Inspector')
if bone is None:
panel.layout.label(text = 'Edit geolayout properties in Pose mode.')
return
col = panel.layout.column()
prop_split(col, bone, 'geo_cmd', 'Geolayout Command')
if bone.geo_cmd in ['TranslateRotate', 'Translate', 'Rotate',
'Billboard', 'DisplayList', 'Scale', 'DisplayListWithOffset']:
prop_split(col, bone, 'draw_layer', 'Draw layer')
if bone.geo_cmd == 'Scale':
prop_split(col, bone, 'geo_scale', 'Scale')
if bone.geo_cmd == 'HeldObject':
prop_split(col, bone, 'geo_func', 'Function')
if bone.geo_cmd == 'Switch':
prop_split(col, bone, 'geo_func', 'Function')
prop_split(col, bone, 'func_param', 'Parameter')
col.label(text = 'Switch Option 0 is always this bone\'s children.')
col.operator(AddSwitchOption.bl_idname).option = len(bone.switch_options)
for i in range(len(bone.switch_options)):
drawSwitchOptionProperty(col, bone.switch_options[i], i)
if bone.geo_cmd == 'Function':
prop_split(col, bone, 'geo_func', 'Function')
prop_split(col, bone, 'func_param', 'Parameter')
infoBox2 = col.box()
infoBox2.label(text = 'This affects the next sibling bone in ' +\
'alphabetical order.')
'''
if bone.geo_cmd == 'Function' or \
bone.geo_cmd == 'Switch' or \
bone.geo_cmd == 'HeldObject':
infoBox = col.box()
infoBox.label(text = 'For binary, this is a memory address.')
infoBox.label(text = 'For C, this is a function name.')
'''
if bone.geo_cmd == 'TranslateRotate':
prop_split(col, bone, 'field_layout', 'Field Layout')
if bone.geo_cmd == 'Shadow':
prop_split(col, bone, 'shadow_type', 'Type')
prop_split(col, bone, 'shadow_solidity', 'Alpha')
prop_split(col, bone, 'shadow_scale', 'Scale')
if bone.geo_cmd == 'StartRenderArea':
prop_split(col, bone, 'culling_radius', 'Culling Radius')
#if bone.geo_cmd == 'SwitchOption':
# prop_split(col, bone, 'switch_bone', 'Switch Bone')
layerInfoBox = panel.layout.box()
layerInfoBox.label(text =
'Regular bones (0x13) are on armature layer 0.')
layerInfoBox.label(text =
'Other bones are on armature layer 1.')
layerInfoBox.label(text =
"'Ignore' bones are on any layer.")
class MaterialPointerProperty(bpy.types.PropertyGroup):
material : bpy.props.PointerProperty(type = bpy.types.Material)
class SwitchOptionProperty(bpy.types.PropertyGroup):
switchType : bpy.props.EnumProperty(name = 'Option Type',
items = enumSwitchOptions)
optionArmature : bpy.props.PointerProperty(name = 'Option Armature',
type = bpy.types.Object)
materialOverride : bpy.props.PointerProperty(type = bpy.types.Material,
name = 'Material Override')
materialOverrideType : bpy.props.EnumProperty(name = 'Material Override Type',
items = enumMatOverrideOptions)
specificOverrideArray : bpy.props.CollectionProperty(type = MaterialPointerProperty,
name = 'Specified Materials To Override')
specificIgnoreArray : bpy.props.CollectionProperty(type = MaterialPointerProperty,
name = 'Specified Materials To Ignore')
overrideDrawLayer : bpy.props.BoolProperty()
drawLayer : bpy.props.EnumProperty(items = enumDrawLayers, name = 'Draw Layer')
collapse : bpy.props.BoolProperty()
def drawSwitchOptionProperty(layout, switchOption, index):
box = layout.box()
#box.box().label(text = 'Switch Option ' + str(index + 1))
box.prop(switchOption, 'collapse', text = 'Switch Option ' + \
str(index + 1), icon = 'TRIA_DOWN' if not switchOption.collapse else \
'TRIA_RIGHT')
if not switchOption.collapse:
prop_split(box, switchOption, 'switchType', 'Type')
if switchOption.switchType == 'Material':
prop_split(box, switchOption, 'materialOverride', 'Material')
prop_split(box, switchOption, 'materialOverrideType',
"Material Override Type")
if switchOption.materialOverrideType == 'Specific':
matArrayBox = box.box()
matArrayBox.label(text = "Specified Materials To Override")
drawMatArray(matArrayBox, index, switchOption,
switchOption.specificOverrideArray, True)
else:
matArrayBox = box.box()
matArrayBox.label(text = "Specified Materials To Ignore")
drawMatArray(matArrayBox, index, switchOption,
switchOption.specificIgnoreArray, False)
prop_split(box, switchOption, 'overrideDrawLayer',
"Override Draw Layer")
if switchOption.overrideDrawLayer:
prop_split(box, switchOption, 'drawLayer', 'Draw Layer')
elif switchOption.switchType == 'Draw Layer':
prop_split(box, switchOption, 'drawLayer', "Draw Layer")
else:
prop_split(box, switchOption, 'optionArmature', 'Option Armature')
buttons = box.row(align = True)
buttons.operator(RemoveSwitchOption.bl_idname,
text = 'Remove Option').option = index
buttons.operator(AddSwitchOption.bl_idname,
text = 'Add Option').option = index + 1
moveButtons = box.row(align = True)
moveUp = moveButtons.operator(MoveSwitchOption.bl_idname,
text = 'Move Up')
moveUp.option = index
moveUp.offset = -1
moveDown = moveButtons.operator(MoveSwitchOption.bl_idname,
text = 'Move Down')
moveDown.option = index
moveDown.offset = 1
def drawMatArray(layout, option, switchOption, matArray, isSpecific):
addOp = layout.operator(AddSwitchOptionMat.bl_idname, text = 'Add Material')
addOp.option = option
addOp.isSpecific = isSpecific
for i in range(len(matArray)):
drawMatArrayProperty(layout, matArray[i], option, i, isSpecific)
def drawMatArrayProperty(layout, materialPointer, option, index, isSpecific):
row = layout.box().row()
row.prop(materialPointer, 'material', text = '')
removeOp = row.operator(RemoveSwitchOptionMat.bl_idname, text = 'Remove Material')
removeOp.option = option
removeOp.index = index
removeOp.isSpecific = isSpecific
class AddSwitchOptionMat(bpy.types.Operator):
bl_idname = 'bone.add_switch_option_mat'
bl_label = 'Add Switch Option Material'
option : bpy.props.IntProperty()
isSpecific : bpy.props.BoolProperty()
def execute(self, context):
bone = context.bone
if self.isSpecific:
bone.switch_options[self.option].specificOverrideArray.add()
else:
bone.switch_options[self.option].specificIgnoreArray.add()
self.report({'INFO'}, 'Success!')
return {'FINISHED'}
class RemoveSwitchOptionMat(bpy.types.Operator):
bl_idname = 'bone.remove_switch_option_mat'
bl_label = 'Remove Switch Option Material'
option : bpy.props.IntProperty()
index : bpy.props.IntProperty()
isSpecific : bpy.props.BoolProperty()
def execute(self, context):
if self.isSpecific:
context.bone.switch_options[self.option].specificOverrideArray.remove(self.index)
else:
context.bone.switch_options[self.option].specificIgnoreArray.remove(self.index)
self.report({'INFO'}, 'Success!')
return {'FINISHED'}
class GeolayoutBonePanel(bpy.types.Panel):
bl_label = "Geolayout Inspector"
bl_idname = "SM64_Geolayout_Inspector"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "bone"
bl_options = {'HIDE_HEADER'}
#@classmethod
#def poll(cls, context):
# return (context.bone is not None)
def draw(self, context):
drawGeoInfo(self, context.bone)
class AddSwitchOption(bpy.types.Operator):
bl_idname = 'bone.add_switch_option'
bl_label = 'Add Switch Option'
option : bpy.props.IntProperty()
def execute(self, context):
bone = context.bone
bone.switch_options.add()
bone.switch_options.move(len(bone.switch_options)-1, self.option)
self.report({'INFO'}, 'Success!')
return {'FINISHED'}
class RemoveSwitchOption(bpy.types.Operator):
bl_idname = 'bone.remove_switch_option'
bl_label = 'Remove Switch Option'
option : bpy.props.IntProperty()
def execute(self, context):
context.bone.switch_options.remove(self.option)
self.report({'INFO'}, 'Success!')
return {'FINISHED'}
class MoveSwitchOption(bpy.types.Operator):
bl_idname = 'bone.move_switch_option'
bl_label = 'Move Switch Option'
option : bpy.props.IntProperty()
offset : bpy.props.IntProperty()
def execute(self, context):
bone = context.bone
bone.switch_options.move(self.option, self.option + self.offset)
self.report({'INFO'}, 'Success!')
return {'FINISHED'}
'''
class GeolayoutBoneSidePanel(bpy.types.Panel):
bl_idname = "SM64_Geolayout_Inspector_Side"
bl_label = "SM64 Geolayout Inspector"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Item'
@classmethod
def poll(cls, context):
return context.selected_bones is not None and \
len(context.selected_bones) > 0
def draw(self, context):
drawGeoInfo(self, context.selected_bones[0])
'''
def getSwitchOptionBone(switchArmature):
optionBones = []
for poseBone in switchArmature.pose.bones:
if poseBone.bone_group is not None and \
poseBone.bone_group.name == 'SwitchOption':
optionBones.append(poseBone.name)
if len(optionBones) > 1:
raise ValueError("There should only be one switch option bone in " +\
switchArmature.name + '.')
elif len(optionBones) < 1:
raise ValueError("Could not find a switch option bone in " +\
switchArmature.name + ', which should be the root bone in the hierarchy.')
return optionBones[0]
def updateBone(self, context):
if not hasattr(context, 'bone'):
print("No bone in context.")
return
armatureObj = context.object
createBoneGroups(armatureObj)
if context.bone.geo_cmd != 'DisplayListWithOffset':
addBoneToGroup(armatureObj, context.bone.name, context.bone.geo_cmd)
bpy.ops.object.mode_set(mode="POSE")
else:
addBoneToGroup(armatureObj, context.bone.name, None)
bpy.ops.object.mode_set(mode="POSE")
classes = (
GeolayoutBonePanel,
#GeolayoutBoneSidePanel
AddSwitchOption,
RemoveSwitchOption,
AddSwitchOptionMat,
RemoveSwitchOptionMat,
MoveSwitchOption,
MaterialPointerProperty,
SwitchOptionProperty,
)
def register():
for cls in classes:
register_class(cls)
bpy.types.Bone.geo_cmd = bpy.props.EnumProperty(
name = 'Geolayout Command', items = enumBoneType,
default = 'DisplayListWithOffset', update = updateBone)
bpy.types.Bone.draw_layer = bpy.props.EnumProperty(
name = 'Draw Layer', items = enumDrawLayers, default = '1')
# Scale
bpy.types.Bone.geo_scale = bpy.props.FloatProperty(
name = 'Scale', min = 2**(-16), max = 2**(16), default = 1)
# Function, HeldObject, Switch
# 8027795C for HeldObject
bpy.types.Bone.geo_func = bpy.props.StringProperty(
name = 'Function', default = '',
description = 'Name of function for C, hex address for binary.')
# Function
bpy.types.Bone.func_param = bpy.props.IntProperty(
name = 'Function Parameter', min = -2**(15), max = 2**(15) - 1, default = 0)
# TranslateRotate
bpy.types.Bone.field_layout = bpy.props.EnumProperty(
name = 'Field Layout', items = enumFieldLayout, default = '0')
# Shadow
bpy.types.Bone.shadow_type = bpy.props.EnumProperty(
name = 'Shadow Type', items = enumShadowType, default = '1')
bpy.types.Bone.shadow_solidity = bpy.props.FloatProperty(
name = 'Shadow Alpha', min = 0, max = 1, default = 1)
bpy.types.Bone.shadow_scale = bpy.props.IntProperty(
name = 'Shadow Scale', min = -2**(15), max = 2**(15) - 1, default = 100)
# StartRenderArea
bpy.types.Bone.culling_radius = bpy.props.IntProperty(
name = 'Culling Radius', min=-2**(15), max=2**(15)-1, default=2000)
#bpy.types.Bone.switch_bone = bpy.props.StringProperty(
# name = 'Switch Bone')
bpy.types.Bone.switch_options = bpy.props.CollectionProperty(
type = SwitchOptionProperty)
def unregister():
for cls in reversed(classes):
unregister_class(cls)
#del bpy.types.Bone.geo_cmd
#del bpy.types.Bone.draw_layer
#del bpy.types.Bone.geo_scale
#del bpy.types.Bone.geo_func
#del bpy.types.Bone.func_param
#del bpy.types.Bone.field_layout
#del bpy.types.Bone.shadow_type
#del bpy.types.Bone.shadow_solidity
#del bpy.types.Bone.shadow_scale
#del bpy.types.Bone.culling_radius
+104
View File
@@ -0,0 +1,104 @@
drawLayers = {
'Unused' : [0, 3],
'Solid' : 1,
'Decal' : 2,
'AlphaTest' : 4,
'Blend' : 5,
'BlendBehind' : 6
}
GEO_BRANCH_STORE = 0x00
GEO_END = 0x01
GEO_BRANCH = 0x02
GEO_RETURN = 0x03
GEO_NODE_OPEN = 0x04
GEO_NODE_CLOSE = 0x05
GEO_SET_RENDER_AREA = 0x08
GEO_SET_CAMERA_FRUSTRUM = 0x0A
GEO_START = 0x0B
GEO_SET_Z_BUF = 0x0C
GEO_SET_RENDER_RANGE = 0x0D
GEO_SWITCH = 0x0E
GEO_TRANSLATE_ROTATE = 0x10
GEO_TRANSLATE = 0x11
GEO_ROTATE = 0x12
GEO_LOAD_DL_W_OFFSET = 0x13
GEO_BILLBOARD = 0x14
GEO_LOAD_DL = 0x15
GEO_START_W_SHADOW = 0x16
GEO_SETUP_OBJ_RENDER = 0x17
GEO_CALL_ASM = 0x18
GEO_SET_BG = 0x19
GEO_HELD_OBJECT = 0x1C
GEO_NOP = [0x1A, 0x1E, 0x1F]
GEO_SCALE = 0x1D
GEO_START_W_RENDERAREA = 0x20
startCommands = [
GEO_START,
GEO_START_W_SHADOW,
GEO_START_W_RENDERAREA
]
nodeGroupCmds = [
GEO_START,
GEO_SWITCH,
GEO_TRANSLATE_ROTATE,
GEO_TRANSLATE,
GEO_ROTATE,
GEO_LOAD_DL_W_OFFSET,
GEO_BILLBOARD,
GEO_START_W_SHADOW,
GEO_SCALE,
GEO_START_W_RENDERAREA
]
nodeDeformCmds = [
GEO_TRANSLATE_ROTATE,
GEO_TRANSLATE,
GEO_ROTATE,
GEO_LOAD_DL_W_OFFSET,
GEO_BILLBOARD,
GEO_LOAD_DL,
GEO_SCALE,
]
nodeDeformCmdsBoneGroups = [
'TranslateRotate',
'Translate',
'Rotate',
'Billboard',
'DisplayList',
'Scale'
]
nodeCmds = [
GEO_NODE_OPEN,
#GEO_START,
#GEO_START_W_SHADOW,
#GEO_START_W_RENDERAREA,
GEO_LOAD_DL,
GEO_LOAD_DL_W_OFFSET,
GEO_BRANCH,
#GEO_SWITCH,
#GEO_SCALE,
#GEO_TRANSLATE_ROTATE
]
geoCmdStatic = {
0x04: [0x00, 0x01, 0x03, 0x04, 0x05, 0x09, 0x0B, 0x0C, 0x17, 0x20],
0x08: [0x02, 0x0D, 0x0E, 0x12, 0x14, 0x15, 0x16, 0x18, 0x19],
0x0C: [0x08, 0x13, 0x1C],
0x14: [0x0F],
}
def getGeoLayoutCmdLength(byte0, byte1):
for length, cmdList in geoCmdStatic.items():
if byte0 in cmdList:
return length
# handle variable length
if byte0 in [0x0A]:
return 0x08 if byte1 == 0 else 0x0C
else:
raise ValueError("Unhandled geolayout command: " + hex(byte0))
File diff suppressed because it is too large Load Diff
+89
View File
@@ -0,0 +1,89 @@
import bpy
def getBoneGroupByName(armatureObj, name):
for boneGroup in armatureObj.pose.bone_groups:
if boneGroup.name == name:
return boneGroup
return None
def getBoneGroupIndex(armatureObj, name):
index = 0
for boneGroup in armatureObj.pose.bone_groups:
if boneGroup.name == name:
return index
else:
index += 1
return -1
class BoneNodeProperties:
def __init__(self, deform, theme):
self.deform = deform
self.theme = theme
# Only 0x13 commands are keyframe animated.
# We want to ignore/prevent animations on these other nodes.
boneNodeProperties = {
"Switch" : BoneNodeProperties(False, 'THEME01'), #0xE
"Start" : BoneNodeProperties(True, 'THEME12'), #0x0B
"TranslateRotate" : BoneNodeProperties(True, 'THEME02'), #0x10
"Translate" : BoneNodeProperties(True, 'THEME03'), #0x11
"Rotate" : BoneNodeProperties(True, 'THEME04'), #0x12
"Billboard" : BoneNodeProperties(True, 'THEME14'), #0x14
"DisplayList" : BoneNodeProperties(True, 'THEME06'), #0x15
"Shadow" : BoneNodeProperties(False, 'THEME07'), #0x16
"Function" : BoneNodeProperties(False, 'THEME05'), #0x18
"HeldObject" : BoneNodeProperties(False, 'THEME09'), #0x1C
"Scale" : BoneNodeProperties(True, 'THEME10'), #0x1D
"StartRenderArea" : BoneNodeProperties(True, 'THEME13'), #0x20
"Ignore" : BoneNodeProperties(False, 'THEME08'), # Used for rigging
"SwitchOption" : BoneNodeProperties(False, 'THEME11')
}
boneLayers = {
'anim' : 0,
'other' : 1,
'meta' : 2,
'visual' : 3
}
def createBoneLayerMask(values):
mask = [False] * 32
for value in values:
mask[value] = True
return mask
def createBoneGroups(armatureObj):
for (groupName, properties) in boneNodeProperties.items():
if getBoneGroupByName(armatureObj, groupName) is None:
boneGroup = armatureObj.pose.bone_groups.new(name = groupName)
boneGroup.color_set = properties.theme
def addBoneToGroup(armatureObj, boneName, groupName):
if groupName is None:
bpy.ops.object.mode_set(mode="OBJECT")
posebone = armatureObj.pose.bones[boneName]
bone = armatureObj.data.bones[boneName]
posebone.bone_group = None
bone.use_deform = True
bone.layers = createBoneLayerMask([boneLayers['anim']])
posebone.lock_location = (False, False, False)
posebone.lock_rotation = (False, False, False)
posebone.lock_scale = (False, False, False)
return
elif groupName not in boneNodeProperties:
raise ValueError("Bone group " + groupName + " doesn't exist.")
bpy.ops.object.mode_set(mode="OBJECT")
posebone = armatureObj.pose.bones[boneName]
bone = armatureObj.data.bones[boneName]
posebone.bone_group_index = getBoneGroupIndex(armatureObj, groupName)
if groupName != 'Ignore':
bone.use_deform = boneNodeProperties[groupName].deform
if groupName != "DisplayList":
bone.layers = createBoneLayerMask([boneLayers['other']])
if groupName != "SwitchOption":
posebone.lock_location = (True, True, True)
posebone.lock_rotation = (True, True, True)
posebone.lock_scale = (True, True, True)
File diff suppressed because it is too large Load Diff
+81
View File
@@ -0,0 +1,81 @@
terrain_types = {
'Normal A' : 0x00,
'Normal B' : 0x01,
'Snow' : 0x02,
'Sand' : 0x03,
'Haunted House' : 0x04,
'Water Level' : 0x05,
'Slippery Slide': 0x06,
}
default_dialog_ID = {
'BOB' : 0x00,
'WF' : 0x1E,
'BFC' : 0x5A,
'VC' : 0x81,
'MC' : 0x82,
'WC' : 0x83,
}
L_LOAD_RAW_JUMP0 = 0x00
L_LOAD_RAW_JUMP1 = 0x01
L_END = 0x02
L_DELAY1 = 0x03
L_DELAY2 = 0x04
L_JUMP = 0x05
L_PUSH = 0x06
L_POP = 0x07
L_PUSH_16 = 0x08
L_POP_16 = 0x09
L_PUSH_00 = 0x0A
L_COND_POP = 0x0B
L_COND_JUMP = 0x0C
L_COND_PUSH = 0x0D
L_COND_SKIP = 0x0E
L_SKIP = 0x0F
L_NOOP = 0x10
L_SET_ACC_ASM = 0x11
L_ACTIVE_SET_ACC = 0x12
L_SET_ACC = 0x13
L_POOL_PUSH = 0x14
L_POOL_POP = 0x15
L_LOAD_ROM_RAM = 0x16
L_LOAD_ROM_SEG = 0x17
L_LOAD_MIO0_SEG = 0x18
L_MARIO_DEMO = 0x19
L_LOAD_MIO0_TEX = 0x1A
L_LOAD_START = 0x1B
L_MEM_CLEANUP = 0x1C
L_LOAD_END = 0x1D
L_POOL_ALLOC = 0x1E
L_AREA_START = 0x1F
L_AREA_END = 0x20
L_LOAD_POLY_WO_GEO = 0x21
L_LOAD_POLY_W_GEO = 0x22
L_23 = 0x23
L_PLACE_OBJECT = 0x24
L_LOAD_MARIO = 0x25
L_WARP_CONNECT = 0x26
L_WARP_PAINTING = 0x27
L_WARP_AREA = 0x28
L_29 = 0x29
L_2A = 0x2A
L_SET_DEFAULT_MARIO_POS = 0x2B
L_2C = 0x2C
L_2D = 0x2D
L_LOAD_COLLISION = 0x2E
L_RENDER_AREA = 0x2F
L_SHOW_DIALOG = 0x30
L_SET_DEFAULT_TERRAIN = 0x31
L_NOOP2 = 0x32
L_FADE_OVERLAY = 0x33
L_BLACKOUT_SCREEN = 0x34
L_35 = 0x35
L_SET_MUSIC_SCREEN = 0x36
L_SET_MUSIC_LEVEL = 0x37
L_38 = 0x38
L_PLACE_MACRO_OBJECT = 0x39
L_3A = 0x3A
L_JET_STREAM = 0x3B
L_3C = 0x3C
+435
View File
@@ -0,0 +1,435 @@
import bpy
import mathutils
import copy
from math import pi
from .sm64_level_constants import *
from .sm64_geolayout_constants import *
from .f3d_parser import *
from .utility import *
def parseLevelAtPointer(romfile, pointerAddress):
segmentData = parseCommonSegmentLoad(romfile)
romfile.seek(pointerAddress)
command = romfile.read(16)
segment = command[3]
segmentStart = int.from_bytes(command[4:8], 'big')
segmentEnd = int.from_bytes(command[8:12], 'big')
segmentData[segment] = (segmentStart, segmentEnd)
startAddress = decodeSegmentedAddr(
command[12:16], segmentData)
parsedLevel = parseLevel(romfile, startAddress, segmentData)
for segment, interval in parsedLevel.segmentData.items():
print("Segment " + format(segment, '#04x') + ': ' + hex(interval[0])\
+ " - " + hex(interval[1]))
return parsedLevel
def parseCommonSegmentLoad(romfile):
segmentData = copy.deepcopy(mainLevelLoadScriptSegment)
for segment, pointer in loadSegmentAddresses.items():
romfile.seek(pointer)
command = romfile.read(12)
segment = command[3]
segmentStart = int.from_bytes(command[4:8], 'big')
segmentEnd = int.from_bytes(command[8:12], 'big')
segmentData[segment] = (segmentStart, segmentEnd)
return segmentData
# second byte = command length
def parseLevel(romfile, startAddress, segmentData):
currentAddress = startAddress
romfile.seek(currentAddress)
currentCmd = romfile.read(2)
romfile.seek(currentAddress) # second seek is because reading moves read pointer forward
currentCmd = romfile.read(currentCmd[1])
#currentAddress += currentCmd[1]
scriptStack = [currentAddress]
currentLevel = SM64_Level()
currentLevel.segmentData = segmentData
currentArea = currentLevel.nonArea
# Note that this is only applicable for specific levels,
# accessed through a jump table in the main level script.
while len(scriptStack) > 0 and currentCmd[0] is not L_END:
#print(bytesToHex(currentCmd) + " at " + hex(currentAddress))
if currentCmd[0] == L_JUMP:
currentAddress = decodeSegmentedAddr(
currentCmd[4:8], segmentData)
elif currentCmd[0] == L_PUSH:
scriptStack.append(currentAddress)
#print([hex(value) for value in scriptStack])
currentAddress = decodeSegmentedAddr(
currentCmd[4:8], segmentData)
elif currentCmd[0] == L_POP:
currentAddress = scriptStack.pop()
romfile.seek(currentAddress)
currentCmd = romfile.read(2)
romfile.seek(currentAddress)
currentCmd = romfile.read(currentCmd[1])
currentAddress += currentCmd[1]
#print([hex(value) for value in scriptStack])
elif currentCmd[0] == L_NOOP or \
currentCmd[0] == L_NOOP2:
pass
elif currentCmd[0] == L_LOAD_ROM_SEG or\
currentCmd[0] == L_LOAD_MIO0_SEG or\
currentCmd[0] == L_LOAD_MIO0_TEX:
segmentData[currentCmd[3]] = [
int.from_bytes(currentCmd[4:8], 'big'),
int.from_bytes(currentCmd[8:12], 'big')
]
elif currentCmd[0] == L_AREA_START:
if currentArea is not currentLevel.nonArea:
raise ValueError("Nested areas not supported.")
else:
currentArea = SM64_Area(currentAddress, currentCmd)
elif currentCmd[0] == L_AREA_END:
currentLevel.areas.append(currentArea)
currentArea = currentLevel.nonArea
elif currentCmd[0] == L_LOAD_POLY_W_GEO or\
currentCmd[0] == L_LOAD_POLY_WO_GEO:
polygonData = SM64_Geometry(currentCmd, currentAddress)
currentLevel.geometry.append(polygonData)
elif currentCmd[0] == L_PLACE_OBJECT:
newObj = SM64_Object(currentCmd, currentAddress)
currentArea.objects.append(newObj)
elif currentCmd[0] == L_WARP_CONNECT or\
currentCmd[0] == L_WARP_PAINTING:
warp = SM64_Warp(currentCmd, currentAddress)
currentArea.warps.append(warp)
elif currentCmd[0] == L_WARP_AREA:
areaWarp = SM64_Area_Warp(currentCmd, currentAddress)
currentArea.areaWarps.append(areaWarp)
elif currentCmd[0] == L_SET_DEFAULT_MARIO_POS:
marioPos = SM64_MarioStart(currentCmd, currentAddress)
currentLevel.marioStartPosition = marioPos
elif currentCmd[0] == L_LOAD_COLLISION:
col = SM64_Collider(currentCmd, currentAddress)
currentArea.collider = col
elif currentCmd[0] == L_SHOW_DIALOG:
dialog = SM64_Dialog_ID(currentCmd, currentAddress)
currentArea.startDialogID = dialog
elif currentCmd[0] == L_SET_DEFAULT_TERRAIN:
terrain = SM64_Default_Terrain(currentCmd, currentAddress)
currentArea.defaultTerrainType = terrain
elif currentCmd[0] == L_SET_MUSIC_LEVEL:
music = SM64_Music_Level(currentCmd, currentAddress)
currentArea.music = music
elif currentCmd[0] == L_PLACE_MACRO_OBJECT:
macroObj = SM64_Macro_Object(currentCmd, currentAddress)
currentArea.macroObjects.append(macroObj)
elif currentCmd[0] == L_JET_STREAM:
jetStream = SM64_Jet_Stream(currentCmd, currentAddress)
currentArea.jetStreams.append(jetStream)
else:
print("Unhandled command: " + hex(currentCmd[0]))
if currentCmd[0] != L_PUSH and currentCmd[0] != L_JUMP and currentCmd[0] != L_POP:
currentAddress += currentCmd[1]
romfile.seek(currentAddress)
currentCmd = romfile.read(2)
romfile.seek(currentAddress)
currentCmd = romfile.read(currentCmd[1])
#currentAddress += currentCmd[1]
return currentLevel
class SM64_Level:
def __init__(self):
self.segmentData = {}
self.geometry = []
self.areas = []
self.marioStartPosition = None
self.nonArea = SM64_Area(0, [0x00, 0x00, 0x00, 0x00])
class SM64_Area:
def __init__(self, startAddress, command):
self.objects = []
self.warps = []
self.areaWarps = []
self.collider = None
self.macroObjects = []
self.startDialogID = None
self.music = None
self.defaultTerrainType = None
self.marioStart = None
self.startAddress = startAddress
self.areaNum = command[2]
self.geoAddress = command[4:8]
self.jetStreams = []
class SM64_Music_Screen:
def __init__(self, command = None, address = None):
if command is None:
self.seqNum = None
self.params = None
else:
self.seqNum = command[5]
self.params = command[2:5]
self.address = address
def to_microcode(self):
command = bytearray(8)
command[0] = L_SET_MUSIC_SCREEN
command[1] = 8
command[2:5] = self.params
command[5] = seqNum
return command
class SM64_Music_Level:
def __init__(self, command = None, address = None):
if command is None:
self.seqNum = None
else:
self.seqNum = command[3]
self.address = address
def to_microcode(self):
command = bytearray(8)
command[0] = L_SET_MUSIC_LEVEL
command[1] = 4
command[3] = seqNum
return command
class SM64_Geometry:
def __init__(self, command = None, address = None):
if command is None:
self.geoCmd = None
self.drawLayer = None
self.modelID = None
self.geoAddress = None
else:
self.geoCmd = command[0]
self.drawLayer = command[2] >> 4
self.modelID = command[3]
self.geoAddress = command[4:8]
self.address = address
#print('Level geo: ' + hex(int.from_bytes(self.geoAddress, 'big')))
def to_microcode(self):
command = bytearray(8)
command[0] = self.geoCmd
command[1] = 8
command[2] = self.drawLayer << 4
command[3] = self.modelID
command[4:8] = self.collisionAddress
return command
class SM64_Collider:
def __init__(self, command = None, address = None):
if command is None:
self.collisionAddress = None
self.address = None
else:
self.collisionAddress = command[4:8]
self.address = address
def to_microcode(self):
command = bytearray(8)
command[0] = L_LOAD_COLLISION
command[1] = 8
command[4:8] = self.collisionAddress
return command
class SM64_Dialog_ID:
def __init__(self, command = None, address = None):
if command is None:
self.ID = None
self.address = None
else:
self.ID = command[3]
self.address = address
def to_microcode(self):
command = bytearray(8)
command[0] = L_SHOW_DIALOG
command[1] = 4
command[3] = self.ID
return command
class SM64_Default_Terrain:
def __init__(self, command = None, address = None):
if command is None:
self.terrainType = None
self.address = None
else:
self.terrainType = command[3]
self.address = address
def to_microcode(self):
command = bytearray(8)
command[0] = L_SET_DEFAULT_TERRAIN
command[1] = 4
command[3] = self.terrainType
return command
class SM64_Object:
def __init__(self, command = None, address = None):
if command is None:
self.mask = None
self.modelID = None
self.position = [0,0,0]
self.rotation = [0,0,0]
self.behaviourParams = None
self.behaviourAddress = None
else:
self.mask = command[2]
self.modelID = command[3]
self.position = readVectorFromShorts(command, 4)
self.rotation = readEulerVectorFromShorts(command, 10)
self.behaviourParams = command[16:20]
self.behaviourAddress = command[20:24]
self.address = address
def to_microcode(self):
command = bytearray(24)
command[0] = L_PLACE_OBJECT
command[1] = 0x18
command[2] = self.mask
command[3] = self.modelID
writeVectorToShorts(command, 4, self.position)
writeEulerVectorToShorts(command, 10, self.rotation)
command[16:20] = self.behaviourParams
command[20:24] = self.behaviourAddress
return command
class SM64_Macro_Object:
def __init__(self, command = None, address = None):
if command is None:
self.objPlacementListPointer = None
#self.objectID = None
#self.position = [0,0,0]
else:
self.objPlacementListPointer = command[4:8]
self.address = address
def to_microcode(self):
command = bytearray(8)
command[0] = L_PLACE_MACRO_OBJECT
command[1] = 8
command[4:8] = self.objPlacementListPointer
class SM64_Jet_Stream:
def __init__(self, command = None, address = None):
if command is None:
self.position = None
self.intensity = 0
else:
self.position = readVectorFromShorts(command, 4)
self.intensity = readFloatFromShort(command, 10)
self.address = address
def to_microcode(self, command = None, address = None):
command = bytearray(12)
command[0] = L_JET_STREAM
command[1] = 12
writeVectorToShorts(command, 4, self.position)
writeFloatToShort(command, 10, self.intensity)
self.address = address
return command
class SM64_Warp:
def __init__(self, command = None, address = None):
if command is None:
self.warpCmd = None
self.curWarpID = None
self.destCourseID = None
self.destCourseArea = None
self.destWarpID = None
else:
self.warpCmd = command[0]
self.curWarpID = command[2]
self.destCourseID = command[3]
self.destCourseArea = command[4]
self.destWarpID = command[5]
self.address = address
def to_microcode(self):
command = bytearray(8)
command[0] = self.warpCmd
command[1] = 8
command[2] = self.curWarpID
command[3] = self.destCourseID
command[4] = self.destCourseArea
command[5] = self.destWarpID
return command
class SM64_Area_Warp:
def __init__(self, command = None, address = None):
if command is None:
self.collisionType = None
self.courseAreaID = None
self.teleportPosition = [0,0,0]
else:
self.collisionType = command[2]
self.courseAreaID = command[3]
self.teleportPosition = readVectorFromShorts(command, 4)
self.address = address
def to_microcode(self):
command = bytearray(12)
command[0] = L_WARP_AREA
command[1] = 12
command[2] = self.collisionType
command[3] = self.courseAreaID
writeVectorToShorts(command, 4, self.teleportPosition)
return command
class SM64_MarioStart:
def __init__(self, command = None, address = None):
if command is None:
self.areaID = None
self.position = (0,0,0)
self.yRotation = 0
else:
self.areaID = command[2]
self.yRotation = readEulerFloatFromShort(command, 4)
self.position = readVectorFromShorts(command, 6)
self.address = address
def to_microcode(self):
command = bytearray(12)
command[0] = L_SET_DEFAULT_MARIO_POS
command[1] = 12
writeEulerFloatToShort(command, 4, self.yRotation)
writeVectorToShorts(command, 6, self.position)
return command
+39
View File
@@ -0,0 +1,39 @@
from .sm64_constants import loadSegmentAddresses
def ExtendBank0x04(romfile, segmentData, segment4):
# Extend bank 0x04
romfile.seek(loadSegmentAddresses[0x04] + 4)
oldStart = int.from_bytes(romfile.read(4), 'big')
romfile.seek(loadSegmentAddresses[0x04] + 4)
romfile.write(int.to_bytes(segment4[0], 4, 'big'))
romfile.seek(loadSegmentAddresses[0x04] + 8)
oldEnd = int.from_bytes(romfile.read(4), 'big')
romfile.seek(loadSegmentAddresses[0x04] + 8)
romfile.write(int.to_bytes(segment4[1], 4, 'big'))
romfile.seek(oldStart)
oldData = romfile.read(oldEnd - oldStart)
if oldEnd - oldStart > segment4[1] - segment4[0]:
print("Not enough space to copy old data.")
raise ValueError("Not enough space to copy old data.")
romfile.seek(segment4[0])
romfile.write(oldData)
segmentData[0x04] = (segment4[0], segment4[1])
def DisableLowPolyMario(romfile, geoStartAddress):
# disable low poly mario
romfile.seek(geoStartAddress + 0x42)
romfile.write(bytearray.fromhex('2E 18'))
def readSegment4(romfile, segmentData):
romfile.seek(loadSegmentAddresses[0x04] + 0x4)
seg4start = int.from_bytes(romfile.read(4), 'big')
romfile.seek(loadSegmentAddresses[0x04] + 0x8)
seg4end = int.from_bytes(romfile.read(4), 'big')
segmentData[0x04] = (seg4start, seg4end)
+262
View File
@@ -0,0 +1,262 @@
import bpy
from math import pi, ceil, degrees, radians
from mathutils import *
from .sm64_constants import *
from .sm64_geolayout_constants import *
import random
import string
def getAddressFromRAMAddress(RAMAddress):
addr = RAMAddress - 0x80000000
if addr < 0:
raise ValueError("Invalid RAM address.")
return addr
def getObjectQuaternion(obj):
if obj.rotation_mode == 'QUATERNION':
rotation = mathutils.Quaternion(obj.rotation_quaternion)
elif obj.rotation_mode == 'AXIS_ANGLE':
rotation = mathutils.Quaternion(obj.rotation_axis_angle)
else:
rotation = mathutils.Euler(
obj.rotation_euler, obj.rotation_mode).to_quaternion()
return rotation
def tempName(name):
letters = string.digits
return name + '_temp' + "".join(random.choice(letters) for i in range(10))
def prop_split(layout, data, field, name):
split = layout.split(factor = 0.5)
split.label(text = name)
split.prop(data, field, text = '')
def toAlnum(name):
if name == '':
return None
for i in range(len(name)):
if not name[i].isalnum():
name = name[:i] + '_' + name[i+1:]
if name[0].isdigit():
name = '_' + name
return name
def get64bitAlignedAddr(address):
endNibble = hex(address)[-1]
if endNibble != '0' and endNibble != '8':
address = ceil(address / 8) * 8
return address
def getNameFromPath(path):
index = len(path) - 1
n = path[index]
while n != '/' and n != '\\' and index > 0:
index -= 1
n = path[index]
if index == 0 and n != '/' and n != '\\':
return toAlnum(path)
else:
return toAlnum(path[index + 1:])
def gammaCorrect(color):
return [
gammaCorrectValue(color[0]),
gammaCorrectValue(color[1]),
gammaCorrectValue(color[2])]
def gammaCorrectValue(u):
if u < 0.0031308:
y = u * 12.92
else:
y = 1.055 * pow(u, (1/2.4)) - 0.055
return min(max(y, 0), 1)
def gammaInverse(color):
return [
gammaInverseValue(color[0]),
gammaInverseValue(color[1]),
gammaInverseValue(color[2])]
def gammaInverseValue(u):
if u < 0.04045:
y = u / 12.92
else:
y = ((u + 0.055) / 1.055) ** 2.4
return min(max(y, 0), 1)
def printBlenderMessage(msgSet, message, blenderOp):
if blenderOp is not None:
blenderOp.report(msgSet, message)
else:
print(message)
def bytesToInt(value):
return int.from_bytes(value, 'big')
def bytesToHex(value, byteSize = 4):
return format(bytesToInt(value), '#0' + str(byteSize * 2 + 2) + 'x')
def bytesToHexClean(value, byteSize = 4):
return format(bytesToInt(value), '0' + str(byteSize * 2) + 'x')
def intToHex(value, byteSize = 4):
return format(value, '#0' + str(byteSize * 2 + 2) + 'x')
def intToBytes(value, byteSize):
return bytes.fromhex(intToHex(value, byteSize)[2:])
# byte input
# returns an integer, usually used for file seeking positions
def decodeSegmentedAddr(address, segmentData):
#print(bytesAsHex(address))
segmentStart = segmentData[address[0]][0]
return segmentStart + bytesToInt(address[1:4])
#int input
# returns bytes, usually used for writing new segmented addresses
def encodeSegmentedAddr(address, segmentData):
segment = getSegment(address, segmentData)
segmentStart = segmentData[segment][0]
segmentedAddr = address - segmentStart
return intToBytes(segment, 1) + intToBytes(segmentedAddr, 3)
def getSegment(address, segmentData):
for segment, interval in segmentData.items():
if address in range(*interval):
return segment
raise ValueError("Address " + hex(address) + \
" is not found in any of the provided segments.")
# Position
def readVectorFromShorts(command, offset):
return [readFloatFromShort(command, valueOffset) for valueOffset
in range(offset, offset + 6, 2)]
def readFloatFromShort(command, offset):
return int.from_bytes(command[offset: offset + 2],
'big', signed = True) * sm64ToBlenderScale
def writeVectorToShorts(command, offset, values):
for i in range(3):
valueOffset = offset + i * 2
writeFloatToShort(command, valueOffset, values[i])
def writeFloatToShort(command, offset, value):
command[offset : offset + 2] = \
int(round(value / sm64ToBlenderScale)).to_bytes(
2, 'big', signed = True)
def convertFloatToShort(value):
return int(round((value / sm64ToBlenderScale)))
def convertEulerFloatToShort(value):
return int(round(degrees(value)))
# Rotation
# Rotation is stored as a short.
# Zero rotation starts at Z+ on an XZ plane and goes counterclockwise.
# 2**16 - 1 is the last value before looping around again.
def readEulerVectorFromShorts(command, offset):
return [readEulerFloatFromShort(command, valueOffset) for valueOffset
in range(offset, offset + 6, 2)]
def readEulerFloatFromShort(command, offset):
return radians(int.from_bytes(command[offset: offset + 2],
'big', signed = True))
def writeEulerVectorToShorts(command, offset, values):
for i in range(3):
valueOffset = offset + i * 2
writeEulerFloatToShort(command, valueOffset, values[i])
def writeEulerFloatToShort(command, offset, value):
command[offset : offset + 2] = int(round(degrees(value))).to_bytes(
2, 'big', signed = True)
# convert 32 bit (8888) to 16 bit (5551) color
def convert32to16bitRGBA(oldPixel):
if oldPixel[3] > 127:
alpha = 1
else:
alpha = 0
newPixel = (oldPixel[0] >> 3) << 11 |\
(oldPixel[1] >> 3) << 6 |\
(oldPixel[2] >> 3) << 1 |\
alpha
return newPixel.to_bytes(2, 'big')
# convert normalized RGB values to bytes (0-255)
def convertRGB(normalizedRGB):
return bytearray([
int(normalizedRGB[0] * 255),
int(normalizedRGB[1] * 255),
int(normalizedRGB[2] * 255)
])
# convert normalized RGB values to bytes (0-255)
def convertRGBA(normalizedRGBA):
return bytearray([
int(normalizedRGBA[0] * 255),
int(normalizedRGBA[1] * 255),
int(normalizedRGBA[2] * 255),
int(normalizedRGBA[3] * 255)
])
def vector3ComponentMultiply(a, b):
return mathutils.Vector(
(a.x * b.x, a.y * b.y, a.z * b.z)
)
# Position values are signed shorts.
def convertPosition(position):
positionShorts = [int(floatValue) for floatValue in position]
F3DPosition = bytearray(0)
for shortData in [shortValue.to_bytes(2, 'big', signed=True) for shortValue in positionShorts]:
F3DPosition.extend(shortData)
return F3DPosition
# UVs in F3D are a fixed point short: s10.5 (hence the 2**5)
# fixed point is NOT exponent+mantissa, it is integer+fraction
def convertUV(normalizedUVs, textureWidth, textureHeight):
#print(str(normalizedUVs[0]) + " - " + str(normalizedUVs[1]))
F3DUVs = convertFloatToFixed16Bytes(normalizedUVs[0] * textureWidth) +\
convertFloatToFixed16Bytes(normalizedUVs[1] * textureHeight)
return F3DUVs
def convertFloatToFixed16Bytes(value):
value *= 2**5
value = min(max(value, -2**15), 2**15 - 1)
return int(round(value)).to_bytes(2, 'big', signed = True)
def convertFloatToFixed16(value):
value *= 2**5
value = min(max(value, -2**15), 2**15 - 1)
return int.from_bytes(
int(round(value)).to_bytes(2, 'big', signed = True), 'big')
# Normal values are signed bytes (-128 to 127)
# Normalized magnitude = 127
def convertNormal(normal):
F3DNormal = bytearray(0)
for axis in normal:
F3DNormal.extend(int(axis * 127).to_bytes(1, 'big', signed=True))
return F3DNormal
def byteMask(data, offset, amount):
return bitMask(data, offset * 8, amount * 8)
def bitMask(data, offset, amount):
return (~(-1 << amount) << offset & data) >> offset
def read16bitRGBA(data):
r = bitMask(data, 11, 5) / ((2**5) - 1)
g = bitMask(data, 6, 5) / ((2**5) - 1)
b = bitMask(data, 1, 5) / ((2**5) - 1)
a = bitMask(data, 0, 1) / ((2**1) - 1)
return [r,g,b,a]
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
View File
Binary file not shown.