diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,9 @@
+filepath-bytestring (1.4.2.1.6) unstable; urgency=medium
+
+  * Added makeValid. All functions from filepath are now implemented.
+
+ -- Joey Hess <id@joeyh.name>  Thu, 02 Jan 2020 16:05:49 -0400
+
 filepath-bytestring (1.4.2.1.5) unstable; urgency=medium
 
   * Allow building with filepath-1.4.2 as well as 1.4.2.1;
diff --git a/System/FilePath/Internal.hs b/System/FilePath/Internal.hs
--- a/System/FilePath/Internal.hs
+++ b/System/FilePath/Internal.hs
@@ -129,7 +129,7 @@
     normalise, equalFilePath,
     makeRelative,
     isRelative, isAbsolute,
-    isValid, --makeValid
+    isValid, makeValid
     )
     where
 
@@ -774,7 +774,7 @@
 -- | Set the directory, keeping the filename the same.
 --
 -- > replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext"
--- > untested Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x
+-- > Valid x => replaceDirectory x (takeDirectory x) `equalFilePath` x
 replaceDirectory :: RawFilePath -> ByteString -> RawFilePath
 replaceDirectory x dir = combineAlways dir (takeFileName x)
 
@@ -1079,45 +1079,44 @@
 	(x1,x2) = splitDrive path
 	f x = map toUpper (decodeFilePath $ dropWhileEnd (== 32) $ dropExtensions x) `elem` badElements
 
-{-
-
 -- | Take a FilePath and make it valid; does not change already valid FilePaths.
 --
--- > untested isValid (makeValid x)
--- > untested isValid x ==> makeValid x == x
--- > untested makeValid "" == "_"
--- > untested makeValid "file\0name" == "file_name"
--- > untested Windows: makeValid "c:\\already\\/valid" == "c:\\already\\/valid"
--- > untested Windows: makeValid "c:\\test:of_test" == "c:\\test_of_test"
--- > untested Windows: makeValid "test*" == "test_"
--- > untested Windows: makeValid "c:\\test\\nul" == "c:\\test\\nul_"
--- > untested Windows: makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt"
--- > untested Windows: makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt"
--- > untested Windows: makeValid "c:\\nul\\file" == "c:\\nul_\\file"
--- > untested Windows: makeValid "\\\\\\foo" == "\\\\drive"
--- > untested Windows: makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file"
--- > untested Windows: makeValid "nul .txt" == "nul _.txt"
-makeValid :: FilePath -> FilePath
+-- > isValid (makeValid x)
+-- > isValid x ==> makeValid x == x
+-- > makeValid "" == "_"
+-- > makeValid "file\0name" == "file_name"
+-- > Windows: makeValid "c:\\already\\/valid" == "c:\\already\\/valid"
+-- > Windows: makeValid "c:\\test:of_test" == "c:\\test_of_test"
+-- > Windows: makeValid "test*" == "test_"
+-- > Windows: makeValid "c:\\test\\nul" == "c:\\test\\nul_"
+-- > Windows: makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt"
+-- > Windows: makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt"
+-- > Windows: makeValid "c:\\nul\\file" == "c:\\nul_\\file"
+-- > Windows: makeValid "\\\\\\foo" == "\\\\drive"
+-- > Windows: makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file"
+-- > Windows: makeValid "nul .txt" == "nul _.txt"
+makeValid :: RawFilePath -> RawFilePath
 makeValid "" = "_"
 makeValid path
-        | isPosix = map (\x -> if x == '\0' then '_' else x) path
-        | isJust (readDriveShare drv) && all isPathSeparator drv = take 2 drv ++ "drive"
+        | isPosix = B.map (\x -> if x == 0 then underscore else x) path
+        | isJust (readDriveShare drv) && B.all isPathSeparator drv = B.take 2 drv <> "drive"
         | isJust (readDriveUNC drv) && not (hasTrailingPathSeparator drv) =
-            makeValid (drv ++ [pathSeparator] ++ pth)
+            makeValid (drv <> B.singleton pathSeparator <> pth)
         | otherwise = joinDrive drv $ validElements $ validChars pth
     where
         (drv,pth) = splitDrive path
 
-        validChars = map f
-        f x = if isBadCharacter x then '_' else x
+	underscore :: Word8
+	underscore = fromIntegral (ord '_')
 
+        validChars = B.map f
+        f x = if isBadCharacter x then underscore else x
+
         validElements x = joinPath $ map g $ splitPath x
-        g x = h a ++ b
-            where (a,b) = break isPathSeparator x
-        h x = if map toUpper (dropWhileEnd (== ' ') a) `elem` badElements then a ++ "_" <.> b else x
+        g x = h a <> b
+            where (a,b) = B.break isPathSeparator x
+        h x = if map toUpper (decodeFilePath $ dropWhileEnd (== 32) a) `elem` badElements then a <> "_" <.> b else x
             where (a,b) = splitExtensions x
-
--}
 
 -- | Is a path relative, or is it fixed to the root?
 --
diff --git a/TODO b/TODO
--- a/TODO
+++ b/TODO
@@ -1,3 +0,0 @@
-* Some functions have not been ported from FilePath and are commented out:
-  makeValid
-  Implementations would be accepted if you need any of these.
diff --git a/filepath-bytestring.cabal b/filepath-bytestring.cabal
--- a/filepath-bytestring.cabal
+++ b/filepath-bytestring.cabal
@@ -1,6 +1,6 @@
 cabal-version:  >= 1.18
 name:           filepath-bytestring
-version:        1.4.2.1.5
+version:        1.4.2.1.6
 -- NOTE: Don't forget to update CHANGELOG and the filepath dependency below
 license:        BSD3
 license-file:   LICENSE
diff --git a/tests/Test.hs b/tests/Test.hs
--- a/tests/Test.hs
+++ b/tests/Test.hs
@@ -15,18 +15,18 @@
     let count = case args of i:_ -> read i; _ -> 10000
     putStrLn $ "Testing with " ++ show count ++ " repetitions"
     let alltests = equivtests ++ tests
-    let total = length alltests
+    let ntotal = length alltests
     let showOutput x = show x{output=""} ++ "\n" ++ output x
-    bad <- fmap catMaybes $ forM (zip [1..] alltests) $ \(i,(msg,prop)) -> do
-        putStrLn $ "Test " ++ show i ++ " of " ++ show total ++ ": " ++ msg
+    bad <- fmap catMaybes $ forM (zip [(1 :: Integer)..] alltests) $ \(i,(msg,prop)) -> do
+        putStrLn $ "Test " ++ show i ++ " of " ++ show ntotal ++ ": " ++ msg
         res <- quickCheckWithResult stdArgs{chatty=False, maxSuccess=count} prop
         case res of
             Success{} -> return Nothing
             bad -> do putStrLn $ showOutput bad; putStrLn "TEST FAILURE!"; return $ Just (msg,bad)
     if null bad then
-        putStrLn $ "Success, " ++ show total ++ " tests passed"
+        putStrLn $ "Success, " ++ show ntotal ++ " tests passed"
      else do
         putStrLn $ show (length bad) ++ " FAILURES\n"
-        forM_ (zip [1..] bad) $ \(i,(a,b)) ->
+        forM_ (zip [(1 :: Integer)..] bad) $ \(i,(a,b)) ->
             putStrLn $ "FAILURE " ++ show i ++ ": " ++ a ++ "\n" ++ showOutput b ++ "\n"
-        fail $ "FAILURE, failed " ++ show (length bad) ++ " of " ++ show total ++ " tests"
+        fail $ "FAILURE, failed " ++ show (length bad) ++ " of " ++ show ntotal ++ " tests"
diff --git a/tests/TestEquiv.hs b/tests/TestEquiv.hs
--- a/tests/TestEquiv.hs
+++ b/tests/TestEquiv.hs
@@ -47,6 +47,7 @@
     ,("equiv Posix.equalFilePath", equiv_2 OurPosix.equalFilePath TheirPosix.equalFilePath)
     ,("equiv Posix.makeRelative", equiv_2 OurPosix.makeRelative TheirPosix.makeRelative)
     ,("equiv Posix.splitSearchPath", equiv_1 OurPosix.splitSearchPath TheirPosix.splitSearchPath)
+    ,("equiv Posix.makeValid", equiv_1 OurPosix.makeValid TheirPosix.makeValid)
     ,("equiv Windows.isPathSeparator", equiv_0 OurWindows.isPathSeparator TheirWindows.isPathSeparator)
     ,("equiv Windows.isSearchPathSeparator", equiv_0 OurWindows.isSearchPathSeparator TheirWindows.isSearchPathSeparator)
     ,("equiv Windows.isExtSeparator", equiv_0 OurWindows.isExtSeparator TheirWindows.isExtSeparator)
@@ -87,4 +88,5 @@
     ,("equiv Windows.equalFilePath", equiv_2 OurWindows.equalFilePath TheirWindows.equalFilePath)
     ,("equiv Windows.makeRelative", equiv_2 OurWindows.makeRelative TheirWindows.makeRelative)
     ,("equiv Windows.splitSearchPath", equiv_1 OurWindows.splitSearchPath TheirWindows.splitSearchPath)
+    ,("equiv Windows.makeValid", equiv_1 OurWindows.makeValid TheirWindows.makeValid)
     ]
diff --git a/tests/TestGen.hs b/tests/TestGen.hs
--- a/tests/TestGen.hs
+++ b/tests/TestGen.hs
@@ -304,6 +304,8 @@
     ,("W.takeDirectory \"C:\\\\\" == \"C:\\\\\"", property $ W.takeDirectory "C:\\" == "C:\\")
     ,("P.replaceDirectory \"root/file.ext\" \"/directory/\" == \"/directory/file.ext\"", property $ P.replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext")
     ,("W.replaceDirectory \"root/file.ext\" \"/directory/\" == \"/directory/file.ext\"", property $ W.replaceDirectory "root/file.ext" "/directory/" == "/directory/file.ext")
+    ,("P.replaceDirectory x (P.takeDirectory x) `P.equalFilePath` x", property $ \(QFilePathValidP vx) -> let x = toRawFilePath vx in P.replaceDirectory x (P.takeDirectory x) `P.equalFilePath` x)
+    ,("W.replaceDirectory x (W.takeDirectory x) `W.equalFilePath` x", property $ \(QFilePathValidW vx) -> let x = toRawFilePath vx in W.replaceDirectory x (W.takeDirectory x) `W.equalFilePath` x)
     ,("\"/directory\" P.</> \"file.ext\" == \"/directory/file.ext\"", property $ "/directory" P.</> "file.ext" == "/directory/file.ext")
     ,("\"/directory\" W.</> \"file.ext\" == \"/directory\\\\file.ext\"", property $ "/directory" W.</> "file.ext" == "/directory\\file.ext")
     ,("\"directory\" P.</> \"/file.ext\" == \"/file.ext\"", property $ "directory" P.</> "/file.ext" == "/file.ext")
@@ -426,6 +428,24 @@
     ,("W.isValid \"foo\\tbar\" == False", property $ W.isValid "foo\tbar" == False)
     ,("W.isValid \"nul .txt\" == False", property $ W.isValid "nul .txt" == False)
     ,("W.isValid \" nul.txt\" == True", property $ W.isValid " nul.txt" == True)
+    ,("P.isValid (P.makeValid x)", property $ \(QFilePath vx) -> let x = toRawFilePath vx in P.isValid (P.makeValid x))
+    ,("W.isValid (W.makeValid x)", property $ \(QFilePath vx) -> let x = toRawFilePath vx in W.isValid (W.makeValid x))
+    ,("P.isValid x ==> P.makeValid x == x", property $ \(QFilePath vx) -> let x = toRawFilePath vx in P.isValid x ==> P.makeValid x == x)
+    ,("W.isValid x ==> W.makeValid x == x", property $ \(QFilePath vx) -> let x = toRawFilePath vx in W.isValid x ==> W.makeValid x == x)
+    ,("P.makeValid \"\" == \"_\"", property $ P.makeValid "" == "_")
+    ,("W.makeValid \"\" == \"_\"", property $ W.makeValid "" == "_")
+    ,("P.makeValid \"file\\0name\" == \"file_name\"", property $ P.makeValid "file\0name" == "file_name")
+    ,("W.makeValid \"file\\0name\" == \"file_name\"", property $ W.makeValid "file\0name" == "file_name")
+    ,("W.makeValid \"c:\\\\already\\\\/valid\" == \"c:\\\\already\\\\/valid\"", property $ W.makeValid "c:\\already\\/valid" == "c:\\already\\/valid")
+    ,("W.makeValid \"c:\\\\test:of_test\" == \"c:\\\\test_of_test\"", property $ W.makeValid "c:\\test:of_test" == "c:\\test_of_test")
+    ,("W.makeValid \"test*\" == \"test_\"", property $ W.makeValid "test*" == "test_")
+    ,("W.makeValid \"c:\\\\test\\\\nul\" == \"c:\\\\test\\\\nul_\"", property $ W.makeValid "c:\\test\\nul" == "c:\\test\\nul_")
+    ,("W.makeValid \"c:\\\\test\\\\prn.txt\" == \"c:\\\\test\\\\prn_.txt\"", property $ W.makeValid "c:\\test\\prn.txt" == "c:\\test\\prn_.txt")
+    ,("W.makeValid \"c:\\\\test/prn.txt\" == \"c:\\\\test/prn_.txt\"", property $ W.makeValid "c:\\test/prn.txt" == "c:\\test/prn_.txt")
+    ,("W.makeValid \"c:\\\\nul\\\\file\" == \"c:\\\\nul_\\\\file\"", property $ W.makeValid "c:\\nul\\file" == "c:\\nul_\\file")
+    ,("W.makeValid \"\\\\\\\\\\\\foo\" == \"\\\\\\\\drive\"", property $ W.makeValid "\\\\\\foo" == "\\\\drive")
+    ,("W.makeValid \"\\\\\\\\?\\\\D:file\" == \"\\\\\\\\?\\\\D:\\\\file\"", property $ W.makeValid "\\\\?\\D:file" == "\\\\?\\D:\\file")
+    ,("W.makeValid \"nul .txt\" == \"nul _.txt\"", property $ W.makeValid "nul .txt" == "nul _.txt")
     ,("W.isRelative \"path\\\\test\" == True", property $ W.isRelative "path\\test" == True)
     ,("W.isRelative \"c:\\\\test\" == False", property $ W.isRelative "c:\\test" == False)
     ,("W.isRelative \"c:test\" == True", property $ W.isRelative "c:test" == True)
