I just put this example together. It might be alittle messy but this function can be used multiple ways plus i added some safeguards for error handling.
Defeats codeshifting and works for all modules in the collection.

Reference
Code:
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
Import
Code:
[DllImport("kernel32.dll")]
public static extern bool ReadProcessMemory(IntPtr hProcess, int lpBaseAddress, byte[] buffer, int size, int lpNumberOfBytesRead);
Function
Code:
        public static int ReadAddress(string Process_Name, string Address_Offsets)
        {
            Process[] P;
            if ((P = Process.GetProcessesByName(Process_Name)).Length == 0) return -1;
            int Addy = -1;
            while (Address_Offsets.Contains("  "))
                Address_Offsets = Address_Offsets.Replace("  ", " ");

            int Index = -1;
            while ((Index = Address_Offsets.IndexOf("0x", StringComparison.OrdinalIgnoreCase)) != -1)
                Address_Offsets = Address_Offsets.Replace(Address_Offsets.Substring(Index, 2), "");

            string[] tmp = Address_Offsets.Split(' ');
            if (tmp[0].Contains("+"))
            {
                string[] AD = tmp[0].Split('+');
                foreach (ProcessModule M in P[0].Modules)
                    if (M.ModuleName.ToLower() == AD[0].ToLower())
                        Addy = M.BaseAddress.ToInt32() + int.Parse(AD[1], NumberStyles.HexNumber);
            }
            else Addy = int.Parse(tmp[0], NumberStyles.HexNumber);

            if (tmp.Length == 1) return Addy;
            byte[] buff = new byte[4];
            ReadProcessMemory(P[0].Handle, Addy, buff, 4, 0);
            Addy = BitConverter.ToInt32(buff, 0);
            for (int i = 1; i < tmp.Length; i++)
            {
                int Off = int.Parse(tmp[i], NumberStyles.HexNumber);
                ReadProcessMemory(P[0].Handle, Addy + Off, buff, 4, 0);
                Addy = i != (tmp.Length - 1) ? BitConverter.ToInt32(buff, 0) : Addy += Off;
            }
            return Addy;
        }
Putting aside the amount of code used for simple pointer searching, i tried to make it all in one.

Usage- Offsets seperated with a space.
Code:
ReadAddress("solitaire", "solitaire.exe+97074 2c 10");//CodeShifting Mutli Pointer
ReadAddress("solitaire", "00BB7074 2c 10");//Normal Mutli Pointer
ReadAddress("solitaire", "solitaire.exe+97074");//CodeShifting Address
ReadAddress("solitaire", "00BB7074");//Normal Address

ReadAddress("solitaire", "SomeModule.dll+97074 2c 10");//CodeShifting Mutli Pointer from any module in the collection
[IMG]http://i203.photobucke*****m/albums/aa29/Baxter_esa/PointerScreen.png[/IMG]