Skip to content

Commit

Permalink
2.1
Browse files Browse the repository at this point in the history
Automatically append file name extensions to names given to enforce using .thing_text and .thing_bin extension
  • Loading branch information
erumi321 committed Jul 12, 2024
1 parent 8d19a36 commit 9a275f8
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 79 deletions.
156 changes: 87 additions & 69 deletions HadesMapper/HadesMapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,54 +116,13 @@ def WriteTriBoolean(value):
#write a float using struct
def WriteSingle(inp):
if inp != 0:
# #see https://en.wikipedia.org/wiki/Single-precision_floating-point_format
# #get rid of sign so that math isnt affected, its added back alter
# value = abs(inp)
# decimalBinary = ""
# decimal = value - math.floor(value)
# currentDecimal = decimal
# #convert fraction into binary fraction, limiting at precision limit (23 repetitions)
# for i in range(0, 23):
# currentDecimal = currentDecimal * 2
# decimalBinary += str(int(currentDecimal >= 1))
# if currentDecimal >= 1:
# currentDecimal -= 1
# if currentDecimal == 0:
# break

# #get whole value
# wholeBinary = str(bin(math.floor(value - decimal)))[2:]
# #get normalized exponent (offset from 127)
# normalExponent = 127 + (len(wholeBinary) - 1)
# #convert exponent into binary
# exponentBinary = str(bin(normalExponent))[2:]
# #shift so that only 1 digit remains before decimal point
# fractionBinary = wholeBinary[1:] + decimalBinary

# #concat all parts together into an unrounded (and thus unlimited in size) set of bits
# unroundedBinary = str(int(inp < 0)) + exponentBinary + fractionBinary

# binaryRep = ""
# #if less then len 32 append 0s to end to get to 32 bits
# if len(unroundedBinary) < 32:
# binaryRep = unroundedBinary + "0" * (32 - len(binaryRep))
# #if greater then 32 round to 32 bits 1s round up the next digit, very simply so if we get any 1 then the rest chain causing the final bit to be 1
# #it should not round like this, this is not how it works but its a quick implementation
# elif len(unroundedBinary) > 32:
# for i in range(len(unroundedBinary) - 1, 32, -1):
# if unroundedBinary[i] == "1":
# binaryRep = unroundedBinary[0:31] + "1"
# break
# else:
# binaryRep = unroundedBinary
binaryRep =''.join('{:0>8b}'.format(c) for c in struct.pack('!f', inp))
#store binary as 2 chunks of 16
sections = ["",""]
for i in range(8, 33, 8):
byte = binaryRep[i-8:i]
sections[(i - 1) // 16] += byte

#f.write(chr(int(bytes[1][0:8], 2))) #1,2
#write in order of D C B A
for section in reversed(sections):
sectionBytes = [int(section[8:16], 2), int(section[0:8], 2)]
Expand Down Expand Up @@ -215,7 +174,7 @@ def WriteDataType(type):
#read a binary file and write it to JSON
def DecodeBinaries(inputFilePath, outputFilePath, issequel):
global f
f = open(inputFilePath, "rb")
f = open(inputFilePath + ".thing_bin", "rb")
f.read(4) #read SGB1, whatever it is
f.read(4) #always going be 12, put need to read it to get it out of the way
obstacleCount = ReadUInt32()
Expand Down Expand Up @@ -296,17 +255,17 @@ def DecodeBinaries(inputFilePath, outputFilePath, issequel):
f.close()

jsonString = json.dumps(obstacleTable, sort_keys=True, indent=4)
with open(outputFilePath, "w+") as oid:
with open(outputFilePath + ".thing_text", "w+") as oid:
oid.write(jsonString)

#read a json file and write it to binaries
def EncodeBinaries(inputFilePath, outputFilePath, issequel):
global f

f = open(outputFilePath, "wb+")
f = open(outputFilePath + ".thing_bin", "wb+")

inputFileContent = ""
with open(inputFilePath) as fid:
with open(inputFilePath + ".thing_text") as fid:
lines = fid.readlines()
inputFileContent = "".join(lines)

Expand All @@ -326,26 +285,55 @@ def EncodeBinaries(inputFilePath, outputFilePath, issequel):

WriteBoolean(item["Active"])

WriteBoolean(item["AllowMovementReaction"])
WriteSingle(item["Ambient"])
if "AllowMovementReaction" in item:
WriteBoolean(item["AllowMovementReaction"])
else:
WriteBoolean(True)
if "Material" in item and item["Material"] != None:
WriteSingle(item["Material"]["Ambient"])
else:
WriteSingle(0)
WriteSingle(item["Angle"])

WriteInt32(len(item["AttachedIDs"]))
for attachedID in item["AttachedIDs"]:
WriteInt32(attachedID)
WriteInt32(item["AttachToID"])

WriteBoolean(item["CausesOcculsion"])
WriteBoolean(item["Clutter"])

if "CausesOcculsion" in item:
WriteBoolean(item["CausesOcculsion"])
else:
WriteBoolean(True)

if "Clutter" in item:
WriteBoolean(item["Clutter"])
else:
WriteBoolean(False)
WriteBoolean(item["Collision"])

WriteColor(item["Color"])

WriteStringAllowNull(item["Comments"])
if "Comments" in item:
WriteStringAllowNull(item["Comments"])
else:
WriteStringAllowNull("")

if "CreatesShadows" in item:
WriteTriBoolean(item["CreatesShadows"])
else:
WriteTriBoolean(True)

if item["DataType"] != "MapArea":
WriteDataType(item["DataType"])
else:
WriteDataType("Obstacle")

if "DrawVfxOnTop" in item:
WriteTriBoolean(item["DrawVfxOnTop"])
else:
WriteTriBoolean(True)

WriteTriBoolean(item["CreatesShadows"])
WriteDataType(item["DataType"])
WriteTriBoolean(item["DrawVfxOnTop"])

WriteBoolean(item["FlipHorizontal"])
WriteBoolean(item["FlipVertical"])
Expand All @@ -355,11 +343,19 @@ def EncodeBinaries(inputFilePath, outputFilePath, issequel):
if issequel == False: f.write(bytes([0,0,0,0]))
WriteStringAllowNull(group)

WriteStringAllowNull(item["HelpTextID"])
if "HelpTextID" in item:
WriteStringAllowNull(item["HelpTextID"])
else:
WriteStringAllowNull("")

WriteSingle(item["Hue"])
WriteSingle(item["Saturation"])
WriteSingle(item["Value"])
if "Hue" in item:
WriteSingle(item["Hue"])
WriteSingle(item["Saturation"])
WriteSingle(item["Value"])
else:
WriteSingle(0)
WriteSingle(0)
WriteSingle(0)

WriteInt32(item["Id"])

Expand All @@ -369,25 +365,47 @@ def EncodeBinaries(inputFilePath, outputFilePath, issequel):
WriteSingle(item["Location"]["X"])
WriteSingle(item["Location"]["Y"])

WriteStringAllowNull(item["Name"])
if "Name" in item:
WriteStringAllowNull(item["Name"])
else:
WriteStringAllowNull("")

WriteSingle(item["OffsetZ"])
WriteSingle(item["ParallaxAmount"])

WriteInt32(len(item["Points"]))
for point in item["Points"]:
WriteSingle(point["X"])
WriteSingle(point["Y"])
if "Points" in item:
WriteInt32(len(item["Points"]))
for point in item["Points"]:
WriteSingle(point["X"])
WriteSingle(point["Y"])
else:
WriteInt32(0)

WriteSingle(item["Scale"])
WriteSingle(item["SkewAngle"])
WriteSingle(item["SkewScale"])

WriteInt32(item["SortIndex"])
if "SkewAngle" in item:
WriteSingle(item["SkewAngle"])
else:
WriteSingle(0)

WriteTriBoolean(item["StopsLight"])
if "SkewScale" in item:
WriteSingle(item["SkewScale"])
else:
WriteSingle(0)

WriteSingle(item["Tallness"])
WriteInt32(item["SortIndex"])

WriteTriBoolean(item["UseBoundsForSortArea"])
if "StopsLight" in item:
WriteTriBoolean(item["StopsLight"])
else:
WriteTriBoolean(True)

if "Tallness" in item:
WriteSingle(item["Tallness"])
else:
WriteSingle(0)

if "UseBoundsForSortArea" in item:
WriteTriBoolean(item["UseBoundsForSortArea"])
else:
WriteTriBoolean(False)
f.close()
8 changes: 4 additions & 4 deletions HadesMapper/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ def main():

# encode parser
encode_parser = subparsers.add_parser('encode', help='Encode JSON into a binary file', aliases=['ec'])
encode_parser.add_argument('-i', '-input', metavar='input', default='input.thing_text', type=str, help='The JSON file to encode, default is input.thing_text')
encode_parser.add_argument('-o', '-output', metavar='output', default='output.thing_bin', type=str, help='The binary file to output, default is output.thing_bin')
encode_parser.add_argument('-i', '-input', metavar='input', default='input', type=str, help='The .thing_text (JSON) file to encode, default is input')
encode_parser.add_argument('-o', '-output', metavar='output', default='output', type=str, help='The .thing_bin (binary) file to output, default is output')
encode_parser.add_argument('-s', '-sequel', action='store_true', help='Flag for whether the game is the sequel (Hades II), default is Hades 1')
encode_parser.set_defaults(func=cli_encode)

#decode parser
decode_parser = subparsers.add_parser('decode', help='Encode JSON into a binary file', aliases=['dc'])
decode_parser.add_argument('-i', '-input', metavar='input', default='input.thing_bin', type=str, help='The binary file to decode, default is input.thing_bin')
decode_parser.add_argument('-o', '-output', metavar='output', default='output.thing_text', type=str, help='The JSON file to output to, default is output.thing_text')
decode_parser.add_argument('-i', '-input', metavar='input', default='input.thing_bin', type=str, help='The .thing_bin (binary) file to decode, default is input')
decode_parser.add_argument('-o', '-output', metavar='output', default='output.thing_text', type=str, help='The ,thing_text (JSON) file to output to, default is output')
decode_parser.add_argument('-s', '-sequel', action='store_true', help='Flag for whether the game is the sequel (Hades II), default is Hades 1')
decode_parser.set_defaults(func=cli_decode)

Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This is a tool to read the game's binary map files and decompile them to a JSON
Download the latest [Release](https://github.com/SGG-Modding/HadesMapper/releases). Open command prompt and cd into the directory of the wheel and pip install it, remember to have .whl at the end of the name.

# How to use this?
The script has 2 modes, encode and decode, as well as 2 submodes for Hades I and Hades II.
The script has 2 modes, encode and decode, as well as 2 submodes for Hades I and Hades II. **When inputting file names do not put their extension, the script automatically handles that (files must have the extension `.thing_text` or `.thing_bin`)**.

## Hades 2
To encode or decode any map binaries to or from Hades II, add `-s` or `-sequel` as a flag. Defaults to using Hades I formating.
Expand All @@ -17,18 +17,18 @@ To encode or decode any map binaries to or from Hades II, add `-s` or `-sequel`
```
HadesMapper ec
```
Defaults to an input of `input.thing_text` and output of `output.thing_bin`.
Defaults to an input of `input` and output of `output`.

## Decode Mode (Binaries to JSON)
```
HadesMapper dc
```
Defaults to an input of `input.thing_bin` and output of `output.thing_text`.
Defaults to an input of `input` and output of `output`.

## Arguments
Both commands share the same arguments which are
* `-i` or `-input`: changes the input file the script reads from
* `-o` or `-output`: changes the output file the script writes to
* `-i` or `-input`: changes the input file the script reads from, automatically adds `.thing_text` or `.thing_bin` to the name given.
* `-o` or `-output`: changes the output file the script writes to, automatically adds `.thing_text` or `.thing_bin` to the name given.

# Putting it in Game
To put a new binary in the game, name your new binary to whatever your map name is, for example `MyNewMap.thing_bin`. Put it in your mod folder and put 2 lines in your modfile.txt which read:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
entry_points = {
"console_scripts": ['HadesMapper = HadesMapper.cli:main']
},
version = "2.0",
version = "2.1",
description = "Unpack Hades and Hades II's map binaries into JSON and pack JSON into game usable map binaries",
#long_description = long_descr,
author = "erumi321",
Expand Down

0 comments on commit 9a275f8

Please sign in to comment.