diff --git a/osufs/osufs/blkio.c b/osufs/osufs/blkio.c
index a7ba77be458df3221b15fccd2504d4199830c7e1..31170694094a2637703be26fa396fe449c5c25f7 100644
--- a/osufs/osufs/blkio.c
+++ b/osufs/osufs/blkio.c
@@ -16,6 +16,8 @@
 #include <errno.h>
 #include <string.h>
 
+#include <assert.h>
+
 #ifdef _WIN32
 
 #error "Win32 NOT supported"
@@ -90,7 +92,7 @@ mdev *blkio_open(const char *filename) {
 	if (base == MAP_FAILED) {
 		fprintf(stderr, "MMap failed.\n");
 		fprintf(stderr, "%s (%d)\n", strerror(errno), errno);
-		return NULL;
+		base = NULL;
 	}
 	if (close(fd) == -1) {
 		fprintf(stderr, "Failed to close file\n");
@@ -108,3 +110,28 @@ void blkio_close(mdev *dev) {
 	free(dev);
 }
 
+void blkio_read(mdev *dev, uint16_t addr, void *buf) {
+	assert((addr*512) < dev->len && "Requested address beyond device size");
+	ssize_t ret = pread(dev->fd, buf, 512, addr*512);
+	if (ret == -1) {
+		perror("Failed to read block device");
+		abort();
+	} else if (ret != 512) {
+		fprintf(stderr, "Could not fetch 512 bytes in one read. "
+				"Bytes read: %zd", ret);
+		abort();
+	}
+}
+
+void blkio_write(mdev *dev, uint16_t addr, void *buf) {
+	assert((addr*512) < dev->len && "Writing beyond device size");
+	ssize_t ret = pwrite(dev->fd, buf, 512, addr*512);
+	if (ret == -1) {
+		perror("Failed to read block device");
+		abort();
+	} else if (ret != 512) {
+		fprintf(stderr, "Could not write 512 bytes in one write. "
+				"Bytes written: %zd", ret);
+		abort();
+	}
+}
diff --git a/osufs/osufs/blkio.h b/osufs/osufs/blkio.h
index 9e87fb462c951b675a9dd3bf9c4e4ab3d86c76ba..bd330ddfdf39dd305bdb4c7644adc831a4be4525 100644
--- a/osufs/osufs/blkio.h
+++ b/osufs/osufs/blkio.h
@@ -15,14 +15,17 @@ extern "C" {
 	
 #include <inttypes.h>
 	
-typedef struct mdev_t {
-	void *base;
-	uint64_t len;
-	uint32_t fd;
-} mdev;
+	typedef struct mdev_t {
+		void *base;
+		uint64_t len;
+		uint32_t fd;
+	} mdev;
 
-mdev *blkio_open(const char *filename);
-void blkio_close(mdev *dev);
+	mdev *blkio_open(const char *filename);
+	void blkio_close(mdev *dev);
+
+	void blkio_read(mdev *dev, uint16_t addr, void *buf);
+	void blkio_write(mdev *dev, uint16_t addr, void *buf);
 
 #ifdef __cplusplus
 }
diff --git a/osufs/osufs/imgenc.c b/osufs/osufs/imgenc.c
new file mode 100644
index 0000000000000000000000000000000000000000..19d46fac6608d086bc8f9b3274a09ca7984a5273
--- /dev/null
+++ b/osufs/osufs/imgenc.c
@@ -0,0 +1,146 @@
+//
+//  imgenc.c
+//  osufs
+//
+//  Created by Fang Lu on 12/3/17.
+//  Copyright © 2017 Fang Lu. All rights reserved.
+//
+
+#include "imgenc.h"
+
+#include <stdio.h>
+#include <string.h>
+#include "libpng-wrapper.h"
+
+uint16_t img_write_dither128(const char *filename, mdev *dev, uint16_t addr) {
+	char cbuf[1024];
+	int ret;
+	
+	system("rm -f source.png plt.png dither.png");
+	
+	// Resize
+	sprintf(cbuf, "ffmpeg -i %s -vf \"scale=640:480\" source.png", filename);
+	ret = system(cbuf);
+	if (ret != 0) {
+		fprintf(stderr, "ffmpeg resize failed with exit code: %d\n", ret);
+		return addr;
+	}
+
+	// PaletteGen
+	ret = system("ffmpeg -i source.png -vf \"palettegen=max_colors=128\" plt.png");
+	if (ret != 0) {
+		fprintf(stderr, "ffmpeg palettegen failed with exit code: %d\n", ret);
+		return addr;
+	}
+	
+	// Dithering
+	ret = system("ffmpeg -i source.png -i plt.png -lavfi \"paletteuse=dither=floyd_steinberg\" -y dither.png");
+	if (ret != 0) {
+		fprintf(stderr, "ffmpeg dithering failed with exit code: %d\n", ret);
+	}
+	
+	FILE *fp = fopen("dither.png", "rb");
+	if (!fp) {
+		perror("Failed to open dithered image");
+		return addr;
+	}
+	
+	PNGImage img = pngimg_read("dither.png");
+	if (img.color_type != PNG_COLOR_TYPE_PALETTE) {
+		fprintf(stderr, "Internal Error: dithered image is not index!\n");
+		return addr;
+	}
+	
+	png_colorp plt;
+	int nplt;
+	if (!png_get_PLTE(img.png_ptr, img.info_ptr, &plt, &nplt)) {
+		fprintf(stderr, "Internal Error: failed to read palette!\n");
+		return addr;
+	}
+	printf("Read %d palette entries\n", nplt);
+	for (int i=0; i<nplt; i++) {
+		printf("%02x #%02x%02x%02x\n", i, plt[i].red, plt[i].green, plt[i].blue);
+	}
+
+	// Dump palette
+	uint8_t buf[512], *pltbuf = buf + 3;
+	buf[0] = 'P';
+	buf[1] = 'L';
+	buf[2] = 'T';
+	for (int i=0; i<128; i++) {
+		pltbuf[i*3+0] = plt[i].red;
+		pltbuf[i*3+1] = plt[i].green;
+		pltbuf[i*3+2] = plt[i].blue;
+	}
+	blkio_write(dev, addr++, buf);
+	
+	// Dump image, row major
+	size_t bufsize = png_get_rowbytes(img.png_ptr, img.info_ptr) * img.height;
+	size_t bufptr = 0;
+	while (bufsize > 0) {
+		blkio_write(dev, addr++, img.row_pointers[0]+bufptr);
+		bufptr += 512;
+		bufsize -= 512;
+	}
+	
+	pngimg_release(&img);
+	
+	fclose(fp);
+
+	return addr;
+}
+
+size_t img_write_rgb16(const char *filename, mdev *dev, uint16_t addr) {
+	char cbuf[1024];
+	int ret;
+	
+	system("rm -f source.bmp");
+	
+	// Resize
+	sprintf(cbuf, "ffmpeg -i %s -vf \"scale=640:480\" -pix_fmt rgb24 source.png", filename);
+	ret = system(cbuf);
+	if (ret != 0) {
+		fprintf(stderr, "ffmpeg resize failed with exit code: %d\n", ret);
+		return addr;
+	}
+	
+	FILE *fp = fopen("source.png", "rb");
+	if (!fp) {
+		perror("Failed to open resized image");
+		return addr;
+	}
+
+	PNGImage img = pngimg_read("dither.png");
+	if (img.color_type != PNG_COLOR_TYPE_RGB) {
+		fprintf(stderr, "Internal Error: resized image is not RGB24!\n");
+		return addr;
+	}
+	
+	// Dump image as RGB16 (R5 G6 B5), row major
+	// Each 512 byte block holds 256 pixels
+	uint16_t buf[256];
+	size_t npixels = img.width * img.height;
+	int bufptr, pixptr = 0;
+	uint8_t r, g, b;
+	png_bytep pixdata = img.row_pointers[0];
+	while (pixptr < npixels) {
+		bufptr = 0;
+		while (bufptr < 256) {
+			r = pixdata[pixptr*3+0];
+			g = pixdata[pixptr*3+1];
+			b = pixdata[pixptr*3+2];
+			buf[bufptr++] = 0;
+			pixptr++;
+			if (pixptr >= npixels)
+				break;
+		}
+		// Dump buffer
+		blkio_write(dev, addr++, buf);
+	}
+	
+	pngimg_release(&img);
+	
+	fclose(fp);
+
+	return 0;
+}
diff --git a/osufs/osufs/imgenc.h b/osufs/osufs/imgenc.h
new file mode 100644
index 0000000000000000000000000000000000000000..ca3062bef6892279f479743d91f4767b63287fda
--- /dev/null
+++ b/osufs/osufs/imgenc.h
@@ -0,0 +1,26 @@
+//
+//  imgenc.h
+//  osufs
+//
+//  Created by Fang Lu on 12/3/17.
+//  Copyright © 2017 Fang Lu. All rights reserved.
+//
+
+#ifndef imgenc_h
+#define imgenc_h
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include "blkio.h"
+	
+	uint16_t img_write_dither128(const char *filename, mdev *dev, uint16_t addr);
+	uint16_t img_write_r(const char *filename, mdev *dev, uint16_t addr);
+
+#ifdef __cplusplus
+}
+#endif
+	
+#endif /* imgenc_h */
diff --git a/osufs/osufs/libpng-wrapper.c b/osufs/osufs/libpng-wrapper.c
new file mode 100644
index 0000000000000000000000000000000000000000..5a744f61eeb3a5382ecd160fa4729254502b8f94
--- /dev/null
+++ b/osufs/osufs/libpng-wrapper.c
@@ -0,0 +1,147 @@
+//
+//  libpng-wrapper.c
+//  mp6
+//
+//  Created by Fang Lu on 11/16/17.
+//  Adapted from libpng example code at http://zarb.org/~gc/html/libpng.html
+//
+
+#include "libpng-wrapper.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+PNGImage pngimg_read(const char* file_name) {
+	PNGImage ret;
+	memset(&ret, 0, sizeof(ret));
+	uint8_t header[8];    // 8 is the maximum size that can be checked
+	
+	/* open file and test for it being a png */
+	FILE *fp = fopen(file_name, "rb");
+	if (!fp)
+		return ret;
+	fread(header, 1, 8, fp);
+	if (png_sig_cmp(header, 0, 8))
+		return ret;
+	
+	
+	/* initialize stuff */
+	ret.png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+	
+	if (!ret.png_ptr)
+		return ret;
+	
+	ret.info_ptr = png_create_info_struct(ret.png_ptr);
+	if (!ret.info_ptr)
+		return ret;
+	
+	if (setjmp(png_jmpbuf(ret.png_ptr)))
+		return ret;
+	
+	png_init_io(ret.png_ptr, fp);
+	png_set_sig_bytes(ret.png_ptr, 8);
+	
+	png_read_info(ret.png_ptr, ret.info_ptr);
+	
+	ret.width = png_get_image_width(ret.png_ptr, ret.info_ptr);
+	ret.height = png_get_image_height(ret.png_ptr, ret.info_ptr);
+	ret.color_type = png_get_color_type(ret.png_ptr, ret.info_ptr);
+	ret.bit_depth = png_get_bit_depth(ret.png_ptr, ret.info_ptr);
+	
+	ret.number_of_passes = png_set_interlace_handling(ret.png_ptr);
+	png_read_update_info(ret.png_ptr, ret.info_ptr);
+	
+	
+	/* read file */
+	if (setjmp(png_jmpbuf(ret.png_ptr)))
+		return ret;
+	
+	size_t rowbytes = png_get_rowbytes(ret.png_ptr,ret.info_ptr);
+	ret.row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * ret.height);
+	png_bytep pixdata = (png_bytep) malloc(ret.height * rowbytes);
+	if (!pixdata)
+		return ret;
+	for (int i=0; i<ret.height; i++)
+		ret.row_pointers[i] = pixdata + i * rowbytes;
+		
+	png_read_image(ret.png_ptr, ret.row_pointers);
+	
+	fclose(fp);
+	
+	return ret;
+}
+
+int pngimg_write(const char* file_name, png_bytep pixdata,
+				   size_t width, size_t height, int alpha) {
+	/* create file */
+	FILE *fp = fopen(file_name, "wb");
+	if (!fp)
+		return 0;
+	
+	PNGImage ps;
+	memset(&ps, 0, sizeof(ps));
+	
+	/* initialize stuff */
+	ps.png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+	
+	if (!ps.png_ptr)
+		return 0;
+	
+	ps.info_ptr = png_create_info_struct(ps.png_ptr);
+	if (!ps.info_ptr)
+		return 0;
+	
+	if (setjmp(png_jmpbuf(ps.png_ptr)))
+		return 0;
+	
+	png_init_io(ps.png_ptr, fp);
+	
+	
+	/* write header */
+	if (setjmp(png_jmpbuf(ps.png_ptr)))
+		return 0;
+	
+	png_set_IHDR(ps.png_ptr, ps.info_ptr, width, height, 8,
+				 (alpha ? PNG_COLOR_TYPE_RGBA :PNG_COLOR_TYPE_RGB),
+				 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
+				 PNG_FILTER_TYPE_BASE);
+
+	png_write_info(ps.png_ptr, ps.info_ptr);
+	
+	/* write bytes */
+	if (setjmp(png_jmpbuf(ps.png_ptr)))
+		return 0;
+	
+	size_t rowbytes = (alpha ? 4 : 3) * width;
+	ps.row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * height);
+	for (int i=0; i<height; i++)
+		ps.row_pointers[i] = pixdata + i * rowbytes;
+
+	png_write_image(ps.png_ptr, ps.row_pointers);
+	
+	free(ps.row_pointers);
+	
+	/* end write */
+	if (setjmp(png_jmpbuf(ps.png_ptr)))
+		return 0;
+	
+	png_write_end(ps.png_ptr, NULL);
+
+	png_destroy_write_struct(&ps.png_ptr, &ps.info_ptr);
+
+	fclose(fp);
+	return 1;
+}
+
+void pngimg_release(PNGImage *img) {
+	if (img->png_ptr && img->info_ptr) {
+		png_destroy_read_struct(&img->png_ptr, &img->info_ptr, NULL);
+	}
+	if (!img->row_pointers)
+		return;
+	if (img->row_pointers[0])
+		free(img->row_pointers[0]);
+	free(img->row_pointers);
+	memset(img, 0, sizeof(*img));
+}
diff --git a/osufs/osufs/libpng-wrapper.h b/osufs/osufs/libpng-wrapper.h
new file mode 100644
index 0000000000000000000000000000000000000000..171dc986468028a069dcd63347428298e83cf525
--- /dev/null
+++ b/osufs/osufs/libpng-wrapper.h
@@ -0,0 +1,41 @@
+//
+//  libpng-wrapper.h
+//  mp6
+//
+//  Created by Fang Lu on 11/16/17.
+//  Copyright © 2017 Fang Lu. All rights reserved.
+//
+
+#ifndef libpng_wrapper_h
+#define libpng_wrapper_h
+
+#include <inttypes.h>
+
+#include "png.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct PNGImage_t {
+	int width, height;
+	png_byte color_type;
+	png_byte bit_depth;
+	
+	png_structp png_ptr;
+	png_infop info_ptr;
+	int number_of_passes;
+	png_bytep * row_pointers;
+} PNGImage;
+
+PNGImage pngimg_read(const char* file_name);
+int pngimg_write(const char* file_name, png_bytep pixdata,
+					 size_t width, size_t height, int alpha);
+void pngimg_release(PNGImage *img);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+	
+#endif /* libpng_wrapper_h */
diff --git a/osufs/osufs/osufs.c b/osufs/osufs/osufs.c
index 66cc943e0676fbb1e2ca5a40251653d0d2bab7b3..e41444f8c2112864fef5b9ff7a2cc7d6ece6d540 100644
--- a/osufs/osufs/osufs.c
+++ b/osufs/osufs/osufs.c
@@ -7,3 +7,36 @@
 //
 
 #include "osufs.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+osu_meta osu_read_meta(mdev *dev) {
+	osu_meta ret;
+	blkio_read(dev, 0, &ret);
+	return ret;
+}
+
+osu_song osu_read_song(mdev *dev, int idx) {
+	osu_song ret;
+	blkio_read(dev, 1+idx, &ret);
+	return ret;
+}
+
+void osu_write_meta(mdev *dev, osu_meta *meta) {
+	blkio_write(dev, 0, meta);
+}
+
+void osu_write_song(mdev *dev, int idx, osu_song *song) {
+	blkio_write(dev, idx+1, song);
+}
+
+void osu_init_device(mdev *dev) {
+	osu_meta header;
+	strncpy(header.magic, "osu!", 4);
+	header.songs = 0;
+	header.available_block = 64;
+	osu_write_meta(dev, &header);
+}
+
diff --git a/osufs/osufs/osufs.h b/osufs/osufs/osufs.h
index a3e445d92dfe4730a0c1af744bccca7d92806b3b..e0b6af314253b3025acc01102e885a13ec2ea752 100644
--- a/osufs/osufs/osufs.h
+++ b/osufs/osufs/osufs.h
@@ -14,8 +14,48 @@ extern "C" {
 #endif
 
 #include <stdio.h>
+#include <inttypes.h>
+#include "blkio.h"
+	
+	typedef struct __attribute__((__packed__)) osu_meta_t {
+		char magic[4];							// 0   - 3
+		uint16_t songs;							// 4   - 5
+		uint16_t available_block;				// 6   - 7
+		
+		char _padding[504];						// 8   - 511
+	} osu_meta;
 
+	typedef struct __attribute__((__packed__)) osu_song_t {
+		char magic[4];							// 0   - 3
+		uint16_t cover_begin, cover_len;		// 4   - 7
+		uint16_t audio_begin, audio_len;		// 8   - 11
+		uint16_t beatmap_begin, beatmap_len;	// 12  - 15
+		int16_t difficulty;						// 16  - 17
+		int16_t hp_drain;						// 18  - 19
+		int16_t circle_size;					// 20  - 21
+		int16_t approach_rate;					// 22  - 23
+		int16_t slider_mult;					// 24  - 25
+		int16_t slider_tick;					// 26  - 27
+		uint8_t combo_colors[3][8];				// 28  - 51
+		uint32_t preview_offset;				// 52  - 56
+		uint32_t beatmap_id;					// 56  - 60
+		uint32_t beatmap_setid;					// 60  - 64
+		
+		char title[64];							// 64  - 127
+		char artist[64];						// 128 - 191
+		char creator[32];						// 192 - 223
+		char version[32];						// 224 - 255
+		
+		char _padding[256];						// 256-511
+	} osu_song;
 
+	osu_meta osu_read_meta(mdev *dev);
+	osu_song osu_read_song(mdev *dev, int idx);
+	void osu_write_meta(mdev *dev, osu_meta *meta);
+	void osu_write_song(mdev *dev, int idx, osu_song *song);
+
+	void osu_init_device(mdev *dev);
+	
 #ifdef __cplusplus
 }
 #endif
diff --git a/osufs/osufs/wavenc.c b/osufs/osufs/wavenc.c
new file mode 100644
index 0000000000000000000000000000000000000000..98641fb014d38b6c42909fbfa4d238b77abfe941
--- /dev/null
+++ b/osufs/osufs/wavenc.c
@@ -0,0 +1,80 @@
+//
+//  wavenc.c
+//  osufs
+//
+//  Created by Fang Lu on 12/3/17.
+//  Copyright © 2017 Fang Lu. All rights reserved.
+//
+
+#include "wavenc.h"
+
+#include <stdio.h>
+#include <string.h>
+
+uint16_t wav_write(const char *filename, mdev *dev, uint16_t addr) {
+	char cbuf[1024];
+	int ret;
+	
+	system("rm -f resample.wav");
+	
+	// Resample and mixdown channels
+	sprintf(cbuf, "ffmpeg -i %s -map_metadata -1 -vn -ac 1 -ar 44100 resample.wav", filename);
+	ret = system(cbuf);
+	if (ret != 0) {
+		fprintf(stderr, "ffmpeg resample failed with exit code: %d\n", ret);
+		return addr;
+	}
+	
+	FILE *fp = fopen("resample.wav", "rb");
+	if (!fp) {
+		perror("Failed to open dithered image");
+		return addr;
+	}
+
+	// Read header
+	fread(cbuf, 1, 12, fp);
+	if (strncmp(cbuf, "RIFF", 4) != 0 || strncmp(cbuf+8, "WAVE", 4) != 0) {
+		fprintf(stderr, "Internal error: ffmpeg output not readable\n");
+		fclose(fp);
+		return addr;
+	}
+	
+	// Find data chunk
+	uint32_t chunksize;
+	size_t nread;
+	while (1) {
+		nread = fread(cbuf, 1, 4, fp);
+		if (nread != 4) {
+			perror("Peek output wave failed");
+			fprintf(stderr, "Chould not find audio data chunk\n");
+			return addr;
+		}
+		nread = fread(&chunksize, 4, 1, fp);
+		if (nread != 1) {
+			perror("Peek output wave failed");
+			fprintf(stderr, "Could not find audio data chunk\n");
+			return addr;
+		}
+		if (strncmp(cbuf, "data", 4) == 0)
+			break;
+		else {
+			if (fseek(fp, chunksize, SEEK_CUR) != 0) {
+				perror("Seek in output wave failed");
+				return addr;
+			}
+		}
+	}
+	uint16_t buf[256];
+	while (chunksize > 0) {
+		nread = fread(buf, 2, 256, fp);
+		chunksize -= nread;
+		if (nread != 512 && chunksize != 0) {
+			perror("Read output wave failed");
+			return addr;
+		}
+		blkio_write(dev, addr++, buf);
+	}
+	
+	fclose(fp);
+	return addr;
+}
diff --git a/osufs/osufs/wavenc.h b/osufs/osufs/wavenc.h
new file mode 100644
index 0000000000000000000000000000000000000000..b0691767453a59059ef97da9d0689b0966f6e610
--- /dev/null
+++ b/osufs/osufs/wavenc.h
@@ -0,0 +1,25 @@
+//
+//  wavenc.h
+//  osufs
+//
+//  Created by Fang Lu on 12/3/17.
+//  Copyright © 2017 Fang Lu. All rights reserved.
+//
+
+#ifndef wavenc_h
+#define wavenc_h
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+	
+#include <stdlib.h>
+#include "blkio.h"
+	
+	uint16_t wav_write(const char *filename, mdev *dev, uint16_t addr);
+	
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* wavenc_h */