diff --git a/pyfpdb/CarbonToFpdb.py b/pyfpdb/CarbonToFpdb.py
index cc7afb81..0640d157 100644
--- a/pyfpdb/CarbonToFpdb.py
+++ b/pyfpdb/CarbonToFpdb.py
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
-#    Copyright 2008, Carl Gherardi
-
+# -*- coding: utf-8 -*-
+#
+#    Copyright 2010, Matthew Boss
 #    
 #    This program is free software; you can redistribute it and/or modify
 #    it under the terms of the GNU General Public License as published by
@@ -18,93 +19,286 @@
 
 ########################################################################
 
-#    Standard Library modules
-import Configuration
-import traceback
+# This code is based heavily on EverleafToFpdb.py, by Carl Gherardi
+#
+# OUTSTANDING MATTERS
+#
+# -- No siteID assigned
+# -- No support for games other than NL hold 'em cash. Hand histories for other
+#    games required
+# -- No support for limit hold 'em yet, though this would be easy to add
+# -- No support for tournaments (see also the last item below)
+# -- Assumes that the currency of ring games is USD
+# -- Only works for 'gametype="2"'. What is 'gametype'?
+# -- Only accepts 'realmoney="true"'
+# -- A hand's time-stamp does not record seconds past the minute (a
+#    limitation of the history format)
+# -- No support for a bring-in or for antes (is the latter in fact unnecessary
+#    for hold 'em on Carbon?)
+# -- hand.maxseats can only be guessed at
+# -- The last hand in a history file will often be incomplete and is therefore
+#    rejected
+# -- Is behaviour currently correct when someone shows an uncalled hand?
+# -- Information may be lost when the hand ID is converted from the native form
+#    xxxxxxxx-yyy(y*) to xxxxxxxxyyy(y*) (in principle this should be stored as
+#    a string, but the database does not support this). Is there a possibility
+#    of collision between hand IDs that ought to be distinct?
+# -- Cannot parse tables that run it twice (nor is this likely ever to be
+#    possible)
+# -- Cannot parse hands in which someone is all in in one of the blinds. Until
+#    this is corrected tournaments will be unparseable
+
 import sys
-import re
-import xml.dom.minidom
-from xml.dom.minidom import Node
-from HandHistoryConverter import HandHistoryConverter
+import logging
+from HandHistoryConverter import *
+from decimal import Decimal
 
-# Carbon format looks like:
+class Carbon(HandHistoryConverter):
 
-# 1) 
-# 2) 
-# 3)  
-#                
-#                ...
-# 4) 
-#                
-#                
-# 5) 
-#                
-# 6) 
-#           
-#           ....
-#	    
+    sitename = "Carbon"
+    filetype = "text"
+    codepage = "cp1252"
+    siteID   = 11
 
-# The full sequence for a NHLE cash game is:
-# BLINDS, PREFLOP, POSTFLOP, POSTTURN, POSTRIVER, SHOWDOWN, END_OF_GAME
-# This sequence can be terminated after BLINDS at any time by END_OF_FOLDED_GAME
+    # Static regexes
+    re_SplitHands = re.compile(r'\n+(?=)')
+    re_GameInfo = re.compile(r'', re.MULTILINE)
+    re_HandInfo = re.compile(r'[0-9]+)">')
+    re_PlayerInfo = re.compile(r'', re.MULTILINE)
+    re_Board = re.compile(r'', re.MULTILINE)
+    re_PostBB = re.compile(r'', re.MULTILINE)
+    re_PostBoth = re.compile(r'', re.MULTILINE)
+    #re_Antes = ???
+    #re_BringIn = ???
+    re_HeroCards = re.compile(r'', re.MULTILINE)
+    re_ShowdownAction = re.compile(r'', re.MULTILINE)
+    re_CollectPot = re.compile(r'', re.MULTILINE)
+    re_ShownCards = re.compile(r'', re.MULTILINE)
 
-class CarbonPoker(HandHistoryConverter): 
-	def __init__(self, config, filename):
-		print "Initialising Carbon Poker converter class"
-		HandHistoryConverter.__init__(self, config, filename, "Carbon") # Call super class init
-		self.setFileType("xml")
-        self.siteId   = 4 # Needs to match id entry in Sites database
+    def compilePlayerRegexs(self, hand):
+        pass
 
-	def readSupportedGames(self): 
-		pass
-	def determineGameType(self):
-		gametype = []
-		desc_node = self.doc.getElementsByTagName("description")
-		#TODO: no examples of non ring type yet
-		gametype = gametype + ["ring"]
-		type = desc_node[0].getAttribute("type")
-		if(type == "Holdem"):
-			gametype = gametype + ["hold"]
-		else:
-			print "Carbon: Unknown gametype: '%s'" % (type)
+    def playerNameFromSeatNo(self, seatNo, hand):
+        # This special function is required because Carbon Poker records
+        # actions by seat number, not by the player's name
+        for p in hand.players:
+            if p[0] == int(seatNo):
+                return p[1]
 
-		stakes = desc_node[0].getAttribute("stakes")
-		#TODO: no examples of anything except nlhe
-		m = re.match('(?PNo Limit)\s\(\$?(?P[.0-9]+)/\$?(?P[.0-9]+)\)', stakes)
+    def readSupportedGames(self):
+        return [["ring", "hold", "nl"],
+                ["tour", "hold", "nl"]]
 
-		if(m.group('LIMIT') == "No Limit"):
-			gametype = gametype + ["nl"]
+    def determineGameType(self, handText):
+        """return dict with keys/values:
+    'type'       in ('ring', 'tour')
+    'limitType'  in ('nl', 'cn', 'pl', 'cp', 'fl')
+    'base'       in ('hold', 'stud', 'draw')
+    'category'   in ('holdem', 'omahahi', omahahilo', 'razz', 'studhi', 'studhilo', 'fivedraw', '27_1draw', '27_3draw', 'badugi')
+    'hilo'       in ('h','l','s')
+    'smallBlind' int?
+    'bigBlind'   int?
+    'smallBet'
+    'bigBet'
+    'currency'  in ('USD', 'EUR', 'T$', )
+or None if we fail to get the info """
 
-		gametype = gametype + [self.float2int(m.group('SB'))]
-		gametype = gametype + [self.float2int(m.group('BB'))]
+        m = self.re_GameInfo.search(handText)
+        if not m:
+            # Information about the game type appears only at the beginning of
+            # a hand history file; hence it is not supplied with the second
+            # and subsequent hands. In these cases we use the value previously
+            # stored.
+            return self.info
+        self.info = {}
+        mg = m.groupdict()
 
-		return gametype
+        limits = { 'No Limit':'nl', 'Limit':'fl' }
+        games = {              # base, category
+                    'Holdem' : ('hold','holdem'),
+         'Holdem Tournament' : ('hold','holdem') }
 
-	def readPlayerStacks(self):
-		pass
-	def readBlinds(self):
-		pass
-	def readAction(self):
-		pass
+        if 'LIMIT' in mg:
+            self.info['limitType'] = limits[mg['LIMIT']]
+        if 'GAME' in mg:
+            (self.info['base'], self.info['category']) = games[mg['GAME']]
+        if 'SB' in mg:
+            self.info['sb'] = mg['SB']
+        if 'BB' in mg:
+            self.info['bb'] = mg['BB']
+        if mg['GAME'] == 'Holdem Tournament':
+            self.info['type'] = 'tour'
+            self.info['currency'] = 'T$'
+        else:
+            self.info['type'] = 'ring'
+            self.info['currency'] = 'USD'
 
-	# Override read function as xml.minidom barfs on the Carbon layout
-        # This is pretty dodgy
-	def readFile(self, filename):
-		print "Carbon: Reading file: '%s'" %(filename)
-		infile=open(filename, "rU")
-		self.obs = infile.read()
-		infile.close()
-		self.obs = "\n" + self.obs + ""
-		try:
-			doc = xml.dom.minidom.parseString(self.obs)
-			self.doc = doc
-		except:
-			traceback.print_exc(file=sys.stderr)
+        return self.info
+
+    def readHandInfo(self, hand):
+        m = self.re_HandInfo.search(hand.handText)
+        if m is None:
+            logging.info("Didn't match re_HandInfo")
+            logging.info(hand.handText)
+            return None
+        logging.debug("HID %s-%s, Table %s" % (m.group('HID1'),
+                      m.group('HID2'), m.group('TABLE')[:-1]))
+        hand.handid = m.group('HID1') + m.group('HID2')
+        hand.tablename = m.group('TABLE')[:-1]
+        hand.maxseats = 2 # This value may be increased as necessary
+        hand.starttime = datetime.datetime.strptime(m.group('DATETIME')[:12],
+                                                    '%Y%m%d%H%M')
+        # Check that the hand is complete up to the awarding of the pot; if
+        # not, the hand is unparseable
+        if self.re_EndOfHand.search(hand.handText) is None:
+            raise FpdbParseError(hid=m.group('HID1') + "-" + m.group('HID2'))
+
+    def readPlayerStacks(self, hand):
+        m = self.re_PlayerInfo.finditer(hand.handText)
+        for a in m:
+            seatno = int(a.group('SEAT'))
+            # It may be necessary to adjust 'hand.maxseats', which is an
+            # educated guess, starting with 2 (indicating a heads-up table) and
+            # adjusted upwards in steps to 6, then 9, then 10. An adjustment is
+            # made whenever a player is discovered whose seat number is
+            # currently above the maximum allowable for the table.
+            if seatno >= hand.maxseats:
+                if seatno > 8:
+                    hand.maxseats = 10
+                elif seatno > 5:
+                    hand.maxseats = 9
+                else:
+                    hand.maxseats = 6
+            if a.group('DEALTIN') == "true":
+                hand.addPlayer(seatno, a.group('PNAME'), a.group('CASH'))
+
+    def markStreets(self, hand):
+        #if hand.gametype['base'] == 'hold':
+        m = re.search(r'(?P.+(?=(?P.+(?=(?P.+(?=(?P.+))?', hand.handText, re.DOTALL)
+        hand.addStreets(m)
+
+    def readCommunityCards(self, hand, street):
+        m = self.re_Board.search(hand.streets[street])
+        if street == 'FLOP':
+            hand.setCommunityCards(street, m.group('CARDS').split(','))
+        elif street in ('TURN','RIVER'):
+            hand.setCommunityCards(street, [m.group('CARDS').split(',')[-1]])
+
+    def readAntes(self, hand):
+        pass # ???
+
+    def readBringIn(self, hand):
+        pass # ???
+
+    def readBlinds(self, hand):
+        try:
+            m = self.re_PostSB.search(hand.handText)
+            hand.addBlind(self.playerNameFromSeatNo(m.group('PSEAT'), hand),
+                          'small blind', m.group('SB'))
+        except: # no small blind
+            hand.addBlind(None, None, None)
+        for a in self.re_PostBB.finditer(hand.handText):
+            hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'), hand),
+                          'big blind', a.group('BB'))
+        for a in self.re_PostBoth.finditer(hand.handText):
+            bb = Decimal(self.info['bb'])
+            amount = Decimal(a.group('SBBB'))
+            if amount < bb:
+                hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'),
+                              hand), 'small blind', a.group('SBBB'))
+            elif amount == bb:
+                hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'),
+                              hand), 'big blind', a.group('SBBB'))
+            else:
+                hand.addBlind(self.playerNameFromSeatNo(a.group('PSEAT'),
+                              hand), 'both', a.group('SBBB'))
+
+    def readButton(self, hand):
+        hand.buttonpos = int(self.re_Button.search(hand.handText).group('BUTTON'))
+
+    def readHeroCards(self, hand):
+        m = self.re_HeroCards.search(hand.handText)
+        if m:
+            hand.hero = self.playerNameFromSeatNo(m.group('PSEAT'), hand)
+            cards = m.group('CARDS').split(',')
+            hand.addHoleCards('PREFLOP', hand.hero, closed=cards, shown=False,
+                              mucked=False, dealt=True)
+
+    def readAction(self, hand, street):
+        logging.debug("readAction (%s)" % street)
+        m = self.re_Action.finditer(hand.streets[street])
+        for action in m:
+            logging.debug("%s %s" % (action.group('ATYPE'),
+                                     action.groupdict()))
+            player = self.playerNameFromSeatNo(action.group('PSEAT'), hand)
+            if action.group('ATYPE') == 'RAISE':
+                hand.addCallandRaise(street, player, action.group('BET'))
+            elif action.group('ATYPE') == 'CALL':
+                hand.addCall(street, player, action.group('BET'))
+            elif action.group('ATYPE') == 'BET':
+                hand.addBet(street, player, action.group('BET'))
+            elif action.group('ATYPE') in ('FOLD', 'SIT_OUT'):
+                hand.addFold(street, player)
+            elif action.group('ATYPE') == 'CHECK':
+                hand.addCheck(street, player)
+            elif action.group('ATYPE') == 'ALL_IN':
+                hand.addAllIn(street, player, action.group('BET'))
+            else:
+                logging.debug("Unimplemented readAction: %s %s"
+                              % (action.group('PSEAT'),action.group('ATYPE'),))
+
+    def readShowdownActions(self, hand):
+        for shows in self.re_ShowdownAction.finditer(hand.handText):
+            cards = shows.group('CARDS').split(',')
+            hand.addShownCards(cards,
+                               self.playerNameFromSeatNo(shows.group('PSEAT'),
+                                                         hand))
+
+    def readCollectPot(self, hand):
+        pots = [Decimal(0) for n in range(hand.maxseats)]
+        for m in self.re_CollectPot.finditer(hand.handText):
+            pots[int(m.group('PSEAT'))] += Decimal(m.group('POT'))
+        # Regarding the processing logic for "committed", see Pot.end() in
+        # Hand.py
+        committed = sorted([(v,k) for (k,v) in hand.pot.committed.items()])
+        for p in range(hand.maxseats):
+            pname = self.playerNameFromSeatNo(p, hand)
+            if committed[-1][1] == pname:
+                pots[p] -= committed[-1][0] - committed[-2][0]
+            if pots[p] > 0:
+                hand.addCollectPot(player=pname, pot=pots[p])
+
+    def readShownCards(self, hand):
+        for m in self.re_ShownCards.finditer(hand.handText):
+            cards = m.group('CARDS').split(',')
+            hand.addShownCards(cards=cards, player=self.playerNameFromSeatNo(m.group('PSEAT'), hand))
 
 if __name__ == "__main__":
-	c = Configuration.Config()
-	e = CarbonPoker(c, "regression-test-files/carbon-poker/Niagara Falls (15245216).xml") 
-	e.processFile()
-	print str(e)
+    parser = OptionParser()
+    parser.add_option("-i", "--input", dest="ipath", help="parse input hand history", default="-")
+    parser.add_option("-o", "--output", dest="opath", help="output translation to", default="-")
+    parser.add_option("-f", "--follow", dest="follow", help="follow (tail -f) the input", action="store_true", default=False)
+    parser.add_option("-q", "--quiet", action="store_const", const=logging.CRITICAL, dest="verbosity", default=logging.INFO)
+    parser.add_option("-v", "--verbose", action="store_const", const=logging.INFO, dest="verbosity")
+    parser.add_option("--vv", action="store_const", const=logging.DEBUG, dest="verbosity")
+
+    (options, args) = parser.parse_args()
+
+    LOG_FILENAME = './logging.out'
+    logging.basicConfig(filename=LOG_FILENAME, level=options.verbosity)
+
+    e = Carbon(in_path = options.ipath,
+               out_path = options.opath,
+               follow = options.follow,
+               autostart = True)
 
diff --git a/pyfpdb/Database.py b/pyfpdb/Database.py
index 20807583..5cd25d9d 100644
--- a/pyfpdb/Database.py
+++ b/pyfpdb/Database.py
@@ -1311,6 +1311,7 @@ class Database:
         c.execute("INSERT INTO Sites (name,currency) VALUES ('Absolute', 'USD')")
         c.execute("INSERT INTO Sites (name,currency) VALUES ('PartyPoker', 'USD')")
         c.execute("INSERT INTO Sites (name,currency) VALUES ('Partouche', 'EUR')")
+        c.execute("INSERT INTO Sites (name,currency) VALUES ('Carbon', 'USD')")
         if self.backend == self.SQLITE:
             c.execute("INSERT INTO TourneyTypes (id, siteId, buyin, fee) VALUES (NULL, 1, 0, 0);")
         elif self.backend == self.PGSQL:
diff --git a/pyfpdb/FulltiltToFpdb.py b/pyfpdb/FulltiltToFpdb.py
index d5499636..2ee8bdd5 100755
--- a/pyfpdb/FulltiltToFpdb.py
+++ b/pyfpdb/FulltiltToFpdb.py
@@ -257,10 +257,13 @@ class Fulltilt(HandHistoryConverter):
                             ##int(m.group('HR')), int(m.group('MIN')), int(m.group('SEC')))
 
     def readPlayerStacks(self, hand):
+        # Split hand text for FTP, as the regex matches the player names incorrectly
+        # in the summary section
+        pre, post = hand.handText.split('SUMMARY')
         if hand.gametype['type'] == "ring" :
-            m = self.re_PlayerInfo.finditer(hand.handText)
+            m = self.re_PlayerInfo.finditer(pre)
         else:   #if hand.gametype['type'] == "tour"
-            m = self.re_TourneyPlayerInfo.finditer(hand.handText)
+            m = self.re_TourneyPlayerInfo.finditer(pre)
 
         for a in m:
             hand.addPlayer(int(a.group('SEAT')), a.group('PNAME'), a.group('CASH'))
diff --git a/pyfpdb/HUD_config.xml.example b/pyfpdb/HUD_config.xml.example
index 8b53e609..5a72c4c6 100644
--- a/pyfpdb/HUD_config.xml.example
+++ b/pyfpdb/HUD_config.xml.example
@@ -445,6 +445,43 @@ Left-Drag to Move"
                   
             
         
+
+
+        
+            
+                  
+                 
+                 
+                 
+                 
+                 
+                 
+                  
+            
+            
+                 
+                 
+                 
+                 
+                 
+                 
+            
+            
+                 
+                 
+            
+            
+                  
+                 
+                 
+                 
+                 
+                 
+                 
+                  
+                  
+            
+        
     
 
     
@@ -585,6 +622,7 @@ Left-Drag to Move"
         
         
         
+        
     
 
     
diff --git a/pyfpdb/Hand.py b/pyfpdb/Hand.py
index 7023d50c..93661d70 100644
--- a/pyfpdb/Hand.py
+++ b/pyfpdb/Hand.py
@@ -43,7 +43,7 @@ class Hand(object):
     LCS = {'H':'h', 'D':'d', 'C':'c', 'S':'s'}
     SYMBOL = {'USD': '$', 'EUR': u'$', 'T$': '', 'play': ''}
     MS = {'horse' : 'HORSE', '8game' : '8-Game', 'hose'  : 'HOSE', 'ha': 'HA'}
-    SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9 }
+    SITEIDS = {'Fulltilt':1, 'PokerStars':2, 'Everleaf':3, 'Win2day':4, 'OnGame':5, 'UltimateBet':6, 'Betfair':7, 'Absolute':8, 'PartyPoker':9, 'Partouche':10, 'Carbon':11 }
 
 
     def __init__(self, sitename, gametype, handText, builtFrom = "HHC"):
@@ -289,6 +289,24 @@ If a player has None chips he won't be added."""
             c = c.replace(k,v)
         return c
 
+    def addAllIn(self, street, player, amount):
+        """\
+For sites (currently only Carbon Poker) which record "all in" as a special action, which can mean either "calls and is all in" or "raises all in".
+"""
+        self.checkPlayerExists(player)
+        amount = re.sub(u',', u'', amount) #some sites have commas
+        Ai = Decimal(amount)
+        Bp = self.lastBet[street]
+        Bc = reduce(operator.add, self.bets[street][player], 0)
+        C = Bp - Bc
+        if Ai <= C:
+            self.addCall(street, player, amount)
+        elif Bp == 0:
+            self.addBet(street, player, amount)
+        else:
+            Rb = Ai - C
+            self._addRaise(street, player, C, Rb, Ai)
+
     def addAnte(self, player, ante):
         log.debug("%s %s antes %s" % ('BLINDSANTES', player, ante))
         if player is not None: