Fixing ChessBase 8 on Vista

As a chessplayer, I’m a long time owner or ChessBase 8. It’s a great program to organize my own games of chess. So far I found no need to do an update to ChessBase 9 or the new ChessBase light.

The situtation changed 2 weeks ago when I got my first PC with Windows Vista Home Premium installed. Vista by default enables ClearType and so far I like it. Unfortunately the older ChessBase programs like ChessBase 8, Fritz 8 or Fritz 9 or even my Shredder 10 do not correctly support ClearType.

Chessbase 8 Bad Font

You can see that the move notation in bold is perfectly readable, but the notation of variations is very thin and barely readable. Unfortunately that thin font is used in many places all over the program. The most important place is of course the display of variations.

The problem seems to be the font used. When switching over to “Arial” everything looks perfect. Unfortunately Arial is missing a few characters that are displayed, e. g. the nice chess pieces and a few special characters.

The only effective solution is to turn off ClearType for the whole operating system. That immediately fixes ChessBase 8 and makes it usable again. I don’t like this solution at all.

So I decided to dig into API hooking to ensure the fonts are created in a way that is good for Vista. My first google search on this scheme dug up a nice article on codeproject. I just modified the code to only hook up the windows API function CreateFontIndirect. The source you can download at codeproject is very simple and straight forward. No problems here.

#include "stdafx.h"

#include "ThreadSpy.h"

 

HFONT WINAPI MyCreateFontIndirectA( __in CONST LOGFONTA *lplf)

{

 

if (lplf)

{

LOGFONTA lfneu = *lplf;

if (lfneu.lfWeight <= FW_NORMAL)

{

lfneu.lfQuality = ANTIALIASED_QUALITY;

}

return ::CreateFontIndirectA(&lfneu);

}

else

return ::CreateFontIndirectA(lplf);

}

 

You can see that I’m explicitly hooking the A-Version of the function. I’m always compiling my own DLLs as unicode, so it’s important to specify the A-Version as the Chessbase programs I’m trying to fix here are not yet unicode compiled. The fix itself is simple and just ensures that all small-weight fonts are created using ANTIALIASED_QUALITY. This is not a ClearType quality and it’s not a default allowing Windows to choose. Specifying this flag makes sure the font gets rendered using the older antialiasing techniques.

Once the code is injected into the Chessbase program, the result is just perfect:

Chessbase 8 Good Font

The only missing part now is a small loader that injects my DLL into the Chessbase program. Fortunately the author of the first article on codeproject did a few more articles and provided the basis for a loader, too. The final loader code is found in his third part of the article series. My loader is a clear strip-down of his menu driven full fledged solution.

#include "stdafx.h"

 

PROCESS_INFORMATION pi;

 

BOOL EnableDebugPrivilege()

{

HANDLE           hToken;

LUID             sedebugnameValue;

TOKEN_PRIVILEGES tp;

 

 

if ( !::OpenProcessToken( GetCurrentProcess(),  TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken )) return FALSE;

//

// Given a privilege’s name SeDebugPrivilege, we should locate its local LUID mapping.

//

if ( !::LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &sedebugnameValue ) )

{

::CloseHandle( hToken );

return FALSE;

}

 

tp.PrivilegeCount = 1;

tp.Privileges[0].Luid = sedebugnameValue;

tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

 

if ( !::AdjustTokenPrivileges( hToken, FALSE, &tp, sizeof(tp), NULL, NULL ) )

{

::CloseHandle( hToken );

return FALSE;

}

 

::CloseHandle( hToken );

return TRUE;

}

 

BOOL InjectInto(LPCWSTR sName, LPCWSTR sParam)

{

// Check arguments

if (sName == NULL) return FALSE;

 

// Get the handle for the new specified process

STARTUPINFO startup;

GetStartupInfo(&startup);

HANDLE hThread;

 

WCHAR acParam[1024];

wcscpy_s(acParam, 1024, sName);

if (sParam != NULL && sParam[0])

{

wcscat_s(acParam, 1024, L" ");

wcscat_s(acParam, 1024, sParam);

if (!::CreateProcess(sName, acParam, NULL, NULL, TRUE, CREATE_SUSPENDED, 0, 0, &startup, &pi)) return FALSE;

}

else

{

if (!::CreateProcess(sName, NULL, NULL, NULL, TRUE, CREATE_SUSPENDED, 0, 0, &startup, &pi)) return FALSE;

}

 

// Get the library name

LPCWSTR LIBNAME = L"C8VistaFix.dll";

 

// Allocate space in the remote process for the pathname to our spying DLL

LPCWSTR LibName = (LPCWSTR)::VirtualAllocEx(pi.hProcess, NULL, (wcslen(LIBNAME) + 1) * sizeof(WCHAR), MEM_RESERVE, PAGE_EXECUTE_READWRITE);

LibName = (LPCWSTR)::VirtualAllocEx(pi.hProcess, (LPVOID)LibName, (wcslen(LIBNAME) + 1) * sizeof(WCHAR), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

if (LibName == NULL) return FALSE;

 

// Copy the DLL’s pathname to the remote process’s address space

if (!::WriteProcessMemory(pi.hProcess, (LPVOID)LibName, (LPVOID)LIBNAME, (wcslen(LIBNAME)+1)*sizeof(WCHAR), NULL)) return FALSE;

 

// Get the real address of LoadLibraryA in Kernel32.dll

PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)::GetProcAddress(::GetModuleHandleW(L"Kernel32"), "LoadLibraryW");

if (pfnThreadRtn == NULL) return FALSE;

 

// Create a remote thread that calls LoadLibraryA(DLLPathname)

hThread = ::CreateRemoteThread(pi.hProcess, NULL, 0, pfnThreadRtn, (LPVOID)LibName, 0, NULL);

if (hThread == NULL) return FALSE;

 

// Wait for the remote thread to terminate

::WaitForSingleObject(hThread, INFINITE);

 

// Get the thread exit code

DWORD exit;

GetExitCodeThread(hThread, &exit);

 

return TRUE;

 

}

 

BOOL LaunchThread()

{

// So resumre the thread

if (ResumeThread(pi.hThread) == -1)

return FALSE;

 

return TRUE;

}

 

 

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPCWSTR lpstrCmdLine, int nCmdShow)

{

if (!EnableDebugPrivilege())

{

MessageBoxW(0, L"Fehler bei EnableDebugPrivilege", L"Fehler", MB_OK);

return 1;

}

 

if (!InjectInto(L"C8Broken.exe", lpstrCmdLine))

{

MessageBoxW(0, L"Fehler bei Launch", L"Fehler", MB_OK);

return 1;

}

 

if (!LaunchThread())

{

MessageBoxW(0, L"Fehler bei Start", L"Fehler", MB_OK);

return 1;

}

 

 

return 0;

}

Basically it runs like this:

  • Load “C8Broken.exe” in a suspended, not yet running state
  • Inject the DLL that hooks the CreateFontIndirectA function calls
  • Start the “C8Broken.exe” thread.

You can imagine, that “C8Broken.exe” is just the original “C8.exe” renamed. My program takes the place of “C8.exe” so it gets called whenever Chessbase 8 is launched. The same goes if my code is used to replace “ChessProgram8.exe” or some newer version of it. Just rename the original file to “C8Broken.exe” and give the loaded the appropriate name.

You can download the compiled binaries as a ready-to-use solution to the Chessbase vista font problem. If you’re interested in my sourcecode, just drop me a note.

Thoughts on USB Stick Copy Protection

In my last blog entry, I wrote about a simple copy protection scheme for software stored on an USB stick. The last paragraph talks about enhancing that protection by using the usb vendor id and device id.

The idea is to enumerate the registry at HKLM\CurrentControlSet\Enum\USB and verify if the licensed USB stick is present. Fortunately I wrote that testing is required.

You must absolutely ensure that you only enumerate active devices. I know that Windows stores information on drivers and USB devices even after they’re unplugged. That eases driver installation and is very convenient. For a copy protection that behaviour might be less convenient as the protection must ensure to only enumerate present and active devices. Maybe that registry key only contains active devices, maybe not, I just don’t know.

If you implement a copy protection based on this information, make sure you search proper documentation and do some testing on this.

A simple Copy Protection for USB Sticks

Last week I had the need to implement a simple copy protection for a small software designed to run from an usb stick. The usb sticks are to be sold (or just given away for free) to a few total computer illiterates. The software on the sticks is only open source or free stuff. If the target audience would be capable of downloading and installing the software, the need for the usb stick would be zero.

The copy protection should protect agains the following attack scenario:
1) Take the original usb stick
2) Plug in another usb stick
3) Copy the files over with explorer
4) The second usb stick should not run the software

The easiest way to do a small copy protection is to take the drive serial number (generated during format) and write a small license for that serial number. Each new usb stick will have a different serial number and thus the software will detect an invalid license.

The first important function is GetVolumeInformation to get the serial number (lpVolumeSerialNumber). It’s just a DWORD that is not guaranteed to be unique, but should be pretty unique for my purpose here.

Writing a license for that serial number is very easy. Just some AES encryption is required and AES source is widly available with free licenses. You just need to generate 2 encryption keys. One key is used for encryption and the other key is used for authentication.

1) Encrypt the serial number using any scheme you like. Personally I prefer some variable length encryption scheme based on CBC. This is something I’ve properly implemented and can just reuse. Of course a simple AES encryption without any variable length and whatever is enough for a DWORD.

2) Do an OMAC (a simple CBC-MAC). An OMAC is a variable length encryption of the encrypted data from step 1). But not the whole thing, just the last 128 bits.

3) Write the encrypted data from step 1) and step 2) into a license file.

Once the software starts up, read the license file. The last 128 bit are the OMAC, so remove that and take the remaining data and compute the OMAC for that. If that computed OMAC matches the stored OMAC, you can be pretty sure that the license is generated from you (or someone has debugged your program and retrieved the 128 bit keys somehow).

Now you can just take the encrypted data, decrypt it and verify if the volume serials match. If not, just let the user know something is wrong.

Please note that the step for computing some authentication (the OMAC) is the most important step. This ensures the license is generated from you and not some random data generated with notepad. Encryption of the original volume serial number is just done to add some obscurity. If people see the number, they might actually see that it’s the volume serial number and finally find some tool on the web to manipulate that number. Encrypting it ensures nobody will get this idea without testing.

A better version of this simple copy protection should not only include the volume serial number but also the usb vendor and device id and maybe the revision number. This data is available in the registry at HKLM\CurrentControlSet\Enum\USB. It is not easy to match that data with the appropriate drive letter generated by windows, but that is not so important. If you encode this information into the license, the software can enumerate all usb devices and check if some device with these IDs is available. If it is, everything is ok. If it’s not, the original usb stick is not plugged into the machine.

This additional protection is extremely hard to break. The easiest way would be to buy identical usb sticks to the original one, hoping that the revision number is still the same. That task is not too easy to accomplish and in a few weeks maybe impossible. I did not implement this additional security, because the deadline was coming too fast and I had no time to test the retrieval of usb ids for windows 2000, xp and vista with different user access rights. There is no point in implementing some protection sheme if vista just blocks access to this information. So if you like to go this way, make sure you do proper testing!

Calling CoInitializeSecurity

When creating a new COM project with the option to run as service, Visual Studio creates a few sources for you. Usually this leaves a few TODOs open. The best TODO I found is for the call to InitializeSecurity.

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/CoInitializeSecurity1.cpp could not be found]

Documentation on this topic is very sparse and for sure not easy to understand.  All sample code and newsgroup postings for calling CoInitializeSecurity I found leave the security descriptor at NULL. Of course this is the easiest thing to do, but the TODO clearly states one should use a not null security descriptor. So let’s tackle the problem param by param.

CoInitializeSecurity is declared (documented) as this:

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/CoInitializeSecurity2.cpp could not be found]

I choose -1 for cAuthSvc and thus have to leave asAuthSvc at NULL. I believe that COM can correctly choose the authentication services to register.

dwAuthnLevel should be at PKT level at least according to the suggestion so I chose RPC_C_AUTHN_LEVEL_PKT_INTEGRITY. I’m not sure if this impacts performance, but calls to my COM object are pretty seldom and not really performance critical. Encryption is a bit too much for my project.

dwImpLevel should at least be RPC_C_IMP_LEVEL_IDENTIFY. In my project I go for RPC_C_IMP_LEVEL_IMPERSONATE as this seems to be the standard for DCOM. I must admit that I’m not yet sure about the impact of this parameter on my code.

pAuthList I let COM decide on this.

dwCapabilities is a combination of flags. I opt for the combination of EOAC_NO_CUSTOM_MARSHAL and EOAC_DYNAMIC_CLOAKING. This adheres a bit to the principle of least surprise and avoiding custom marshallers reduces one possibility of DLL injection.

Of course all reserved parameters are NULL as required.

This leaves the task of creating a security descriptor for the pVoid parameter. Unfortunately security descriptors are a pain to program and not easy to understand. I remember a great article at Codeproject that gave me an intro into Windows security and ACL stuff. But after reading that, I understood why all samples just leave this at NULL and decide to go for the defaults which unfortunately just don’t work without manual configuration inside DCOM.

ATL 8 includes a few classes for security in atlsecurity.h and suddenly the whole thing of creating a valid security descriptor is pretty easy. I need an owner, an owner group and an acl specifying a few groups. For the owner and the owning group, I take the values I can get from the process security descriptor. For my project the access control can be reduced to the default group INTERACTIVE. Access for LocalSystem is needed for COM to work correctly. The final code looks like this:

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/CoInitializeSecurity3.cpp could not be found]

This leaves you wondering what CMEWSecurityDesc is doing. The default CSecurityDesc class from ATL has no method to return a non-const SECURITY_DESCRIPTOR. But all API calls require this. So I created my own version of the class that includes a way to get a non-const pointer without a const_cast:

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/CoInitializeSecurity4.cpp could not be found]

ATL Service Installation Woes

While I’m developing software, the project usually runs pretty early in the process. Many features are still missing, but it runs. From that point on, the software should run on my computer just as it will run on any customer’s computer. Of course my code contains debug information, but in most cases it doesn’t contain any special debug code besides asserts and such stuff.

Having this in mind, I found the service installation procedure of a default CAtlServiceModuleT program to lack quite a few options. For end users, the service installation is usually done during setup using sophisticated tools allowing to control many options. As I don’t want to run a setup tool for every software build during the day, I need the ATL service installation to work a bit better.

Once a COM server based on CAtlServiceModuleT is compiled, there is the proper executable. To install the service I have to call this executable with the “-service” parameter.

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/AtlServiceInstall1.txt could not be found]

I immediately noted that the service gets installed with manual startup and quite a few other options set I might want to control. Especially the service description was missing and my new service immediately stood out in the list of services as “low quality” because it just looks different than all other services around.

As usual with all libraries there is a case where extensibility was not forseen. Service install and uninstall for sure is not meant to be modified. The method RegisterAppId calls Uninstall and Install immediately from the CAtlServiceModuleT template. In contrast to many other functions that are called using the pT-> prefix.

As I don’t want to rewrite the whole logic, I decided to leave the install and uninstall alone and just ensure the service installation is modified once installed with default parameters. To allow for this, I create my own copy of the RegisterAppId function. You can find my template declaration including the declaration of the inherited identifier in some other post.

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/AtlServiceInstall2.cpp could not be found]

Now every class can create a PostInstall method do do further service configuration besides the default stuff. The default PostInstall method reads the service configuration and just allows for customization. CSC_Handle is just a resource wrapper.

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/AtlServiceInstall3.cpp could not be found]

The default implementation of ChangeServiceConfig just passes all given parameters to the Win32 API ChangeServiceConfig. My final service class overrides this method and changes a few parameters like this:

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/AtlServiceInstall4.cpp could not be found]

Please note that this code is from my final service class and inherited points to my the default implementation. The most important change is to run with LocalService credentials instead of LocalSystem. I don’t need all the access rights associated with LocalSystem. Please note that this account requires Windows 2000 to work. On NT 4 this is not available. I’m not any more targetting NT 4, so I can safely assume at least Windows 2000. Whenever you change the account, make sure you provide the password. If you leave the password to NULL, the function call will fail.

Another noteable changes to the service configuration is to run in autostart mode and not wait for manual activation. My program wants to check for some registry values and maybe start running without any manual user interaction.

Once the configuration is changed, I add a description just to make it look nicer. This can serve as a sample if you like to do further customization.

Service installation requires a lot more testing. I’m not yet sure if this functionality is ever called at a customers site. If it is, I have to rewrite a lot of the logic to include more meaningful error messages instead of the simple message boxes in english language ATL offers.

Win32 Smart Handles

In my eyes the two most important concepts of C++ are templates and resource handler classes. Resource handlers allow to easily program the Win32 API without too much thinking of closing resources as the compiler properly takes care to call the destructor of all classes upon exit.

Fortunately ATL already includes a lot of resource wrapper classes like CHandle and CEvent. Unfortunately as complete as this list can ever get, one handle type is for sure missing.

I found no base implementation for the SC_HANDLE type that needs a call to CloseServiceHandle to close properly. I won’t go into the details of implementing that specific smart handle class. There are many good implementations to choose from. One suited for this is located at the great Codeproject site.

My implementation is more a copy & paste from CHandle and just called CSC_Handle.

ATL Service Never Ends

An ATL COM Server based on CAtlServiceModuleT doesn’t end itself once all references are down to zero. In contrast to that the base class CAtlExeModuleT provides sophisticated exit logic including a delay timer. This base functionality is deliberatly turned off for a service module.

My service shall not use resources if not needed, so I have to reintroduce the exit logic into my custom service class. To do this, I’ll repair the logic of CAtlExeModuleT to work inside a service and circumvent all other code that tries to avoid a shutdown. To ensure I can reuse all my work I created a library containing my code. My final program doesn’t derive from CAtlServiceModuleT but from my customized CMEWAtlServiceModuleT class.

My new service class template looks like this:

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/AtlServiceSpam1.cpp could not be found]

I’m using this declaration of “inherited” to call methods from the base class. This way I don’t have to write that long template name all over again. To reuse parts of the exit logic of CAtlExeServerT I have to ensure it’s member m_dwMainThreadID points to thread containing the message loop. And I have to rewrite the Unlock method to ensure the exit logic is invoked.

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/AtlServiceSpam2.cpp could not be found]

Unfortunately a service can be stopped by the service manager (e.g. per user request) and I don’t want to leave the monitoring thread running around in that case. I feel it’s a good habit to do a proper cleanup and let Windows only do the cleanup for my bugs. So I add another event to signal a service end and set that signal once the program exits. Unfortunately the whole monitoring logic needs a rewrite now, so I can further enhance the original waiting mechanism to actually wait for the thread and not just do a Sleep call and hope the thread is done.

ATL today includes resource keeper classes for events in atlsync.h just as for normal handles. Of course I use these classes to ease housekeeping.

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/AtlServiceSpam3.cpp could not be found]

It doesn’t hurt to set an event and wait for the thrad if the thread is already done with its work.  The handles will be closed by the wrapper classes and the wait should return immediately. The m_dwPause=0 ensures the original implementation of PostMessageLoop won’t delay the shutdown.

For all this to work, I have to rewrite the StartMonitor logic to create the new event, remember the thread and call my custom monitoring function. I cannot reuse the original implementation as I need the MonitorProc changed. Note that the new event is not an auto-reset event. Once set it’ll stay set and ensure exit.

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/AtlServiceSpam4.cpp could not be found]

In the future any derived class can decide to override the monitoring logic a little easier by providing a fresh MonitorShutdown function. Of course my new default implementation should suffice for me in the forseeable future:

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/AtlServiceSpam5.cpp could not be found]

The actual function doing the monitoring is running in a different thread context. I believe it’s still pretty safe to use the CEvent class for housekeeping. The rest of the logic is just copied for atlbase.h and enhanced a bit. The enhancement is to not only wait for the signal of zero references but instead to wait for the exit signal, too. Once the exit signal is given, the thread is terminated without further work besides housekeeping.

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/AtlServiceSpam6.cpp could not be found]

If I ever decide to write a COM Server running as plain EXE module, I should create a custom version of CAtlExeModuleT, too. The enhancement to properly close the monitoring thread is for sure worth the effort.

ATL Service Spams the Event Log

Visual Studio 2005 offers a great way to create a simple COM server. Once you start the nice wizard, you’re given the option to implement it as a service. I chose that option and created a small ATL application that is based on CAtlServiceModuleT and not directly CAtlExeModuleT.

The first big flaw with the default implementation of CAtlServiceModuleT is that it really spams the event log. After a short time of testing, I found my application event log full of messages “Service started” and “Service stopped”. Unfortunately these messages did not disappear when compiling for release mode. I could understand this detailed logging for debug builds but not for my release.

The first approach to solve this problem is to copy & paste the two offending methods (Run and ServiceMain) in atlbase.h to my server class and just remove the two offending calls to LogEvent. Unfortunately this solution will be a bit difficult to maintain in further releases of Visual Studio. Maybe Microsoft finally decides to rework a few key methods. This can break my code in a very subtle way that might go undetected until it’s too late.

Fortunately I found a different solution to get rid of the LogEvent problem. Looking at the implementation of LogEvent, I found that the API function ReportEvent is not called as ::ReportEvent but just directly. This way we can introduce our own version of ReportEvent into the ATL namespace and filter out the few uninteresting messages.

[The requested file http://www.meikel.com/blog/Mods/http://www.meikel.com/blog/Mods/AtlReportEvent.cpp could not be found]

 
This way the most annoying messages are gone and the maybe helpful error messages are still available. I’m not yet sure if the “Unknown Request” is worth logging or not. That’s up to you to decide for your own project.  The call to SetLastError is there to reduce the element of surprise if somewhen in the future I’d program code to just log the message “Service started” which magically fails. At least this function behaves like the API and returns extended information for GetLastError to fetch and it sets the custom field (bit 29) to ensure this is not messed up with some system error code.

ATL Article Series Introduction

Finally I’ve decided to start some serious C++ programming. The first thing I need for my project is a COM automation server that is running as a service. The main job of this service is to serve a singleton class to all connected desktops. In most cases this will be just one desktop for the logged on user, but there can be a lot more connections coming in from fast user switching to remote desktops.

I found that a COM automation service is the easiest way to connect all these running sessions and even allows to do some work without anybody logged on. My experience tells that programming a service is not too well supported by large libraries, but support has improved over the last years. My first service for NT was quite some more hassle than the one I’m programming now.

Personal interest and perfect COM support made this small task a perfect choice for a C++ with ATL project. Currently I’m using Visual Studio 2005 with ATL 8.

In the near future I’ll post articles about my experience with the chosen toolset. Problems and solutions. 

Dynamic PDF with PHP

Today I finished a small project to dynamically create a PDF-File for my partner-website www.fahrradreisen.de.

That website focuses on bike-tours. It has a large searchable database of tours and tour-descriptions. Now the description and all relevant information can be put into a PDF on-the-fly to allow any surfer a nice print experience.

Creating a PDF is pretty simple using FPDF from www.fpdf.org. But just like any other reporting project it is pretty large in size compared to other things. It costs quite a few codelines to nicely place logos and text, create page breaks, headers and footers.

To see this script in action, go to www.fahrradreisen.de and search for some tour. Click on “Druckversion” to see a dynamically generated PDF. Unfortunately that site is in german language only.