diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bdaedba --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/vgmparse/__pycache__ diff --git a/.project b/.project new file mode 100644 index 0000000..9299c89 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + vgmparse + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/.pydevproject b/.pydevproject new file mode 100644 index 0000000..0e3729e --- /dev/null +++ b/.pydevproject @@ -0,0 +1,8 @@ + + +Default +python 2.7 + +/${PROJECT_DIR_NAME} + + diff --git a/README.md b/README.md index af7f861..397f9a3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A Python module for parsing [VGM (Video Game Music)](https://en.wikipedia.org/wiki/VGM_(file_format)) files. `.vgm` and `.vgz` (Gzip compressed) files are supported. -Currently, only version 1.50 of the VGM specification is supported. +Currently, only versions 1.01 and 1.50 of the VGM specification are supported. ## Installation The `vgmparse` module can be installed directly from GitHub using `pip`: @@ -79,3 +79,12 @@ then seek and read like a file: @>>> vgm_data.data_block.read(5) b'\x82\x88\x8a\x8a\x88' ``` + +### Saving a VGM file + +You can save the VGM back into a file by using the `.save()` method: + +``` +@>>> vgm_out = open('newfile.vgm', 'wb') +@>>> vgm_data.save(vgm_out) +``` diff --git a/test/Alex Kidd in Miracle World - 01 - Title Screen.vgm b/test/Alex Kidd in Miracle World - 01 - Title Screen.vgm new file mode 100644 index 0000000..f2ff322 Binary files /dev/null and b/test/Alex Kidd in Miracle World - 01 - Title Screen.vgm differ diff --git a/test/test.py b/test/test.py new file mode 100644 index 0000000..88a5bc2 --- /dev/null +++ b/test/test.py @@ -0,0 +1,29 @@ +import unittest +import vgmparse +import tempfile, time + +class TestParsing(unittest.TestCase): + + def test_vgm_1_01(self): + with open('Alex Kidd in Miracle World - 01 - Title Screen.vgm', 'rb') as f: + file_data = f.read() + parser = vgmparse.Parser(file_data) + + # The first command is a GG Stereo command + self.assertEqual(0x4F, ord(parser.command_list[0]['command'])) + self.assertEqual(0xFF, ord(parser.command_list[0]['data'])) + + # The second command is a SN76496 Latch/Data command + self.assertEqual(0x50, ord(parser.command_list[1]['command'])) + self.assertEqual(0x80, ord(parser.command_list[1]['data'])) + + def test_save(self): + with open('Alex Kidd in Miracle World - 01 - Title Screen.vgm', 'rb') as f: + file_data = f.read() + parser = vgmparse.Parser(file_data) + + with open('{0}/test-save-{1}.vgm'.format(tempfile.gettempdir(), time.time()), 'wb') as o: + parser.save(o) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/vgmparse/__init__.py b/vgmparse/__init__.py index 7f5af13..69d1b45 100644 --- a/vgmparse/__init__.py +++ b/vgmparse/__init__.py @@ -18,41 +18,39 @@ class Parser: # Supported VGM versions supported_ver_list = [ + 0x00000101, 0x00000150, ] # VGM metadata offsets metadata_offsets = { - # Version 1.50 - 0x00000150: { - 'vgm_ident': {'offset': 0x00, 'size': 4, 'type_format': None}, - 'eof_offset': {'offset': 0x04, 'size': 4, 'type_format': '> 8, version & 0xFF) + + def save(self, buffer): + # Pre-fill the header with zeroes + buffer.seek(0) + buffer.write(b'\0' * 0x40) + + # Iterate over the offsets and write the metadata + for value, offset_data in self.metadata_offsets.items(): + + # Seek to the data location and read the data + buffer.seek(offset_data['offset']) + + # Unpack the data if required + if offset_data['type_format'] is not None: + data = struct.pack( + offset_data['type_format'], + self.metadata[value], + ) + buffer.write(data) + else: + buffer.write(self.metadata[value]) + + # Write out the commands + for cmd in self.command_list: + buffer.write(cmd['command']) + if cmd['data']: + buffer.write(cmd['data']) + \ No newline at end of file