ZSNES 1.51 on Win32 patch to make Vista/7 friendly

Strictly for discussing ZSNES development and for submitting code. You can also join us on IRC at irc.libera.chat in #zsnes.
Please, no requests here.

Moderator: ZSNES Mods

gblues
Rookie
Posts: 22
Joined: Tue Jan 18, 2005 3:50 am

ZSNES 1.51 on Win32 patch to make Vista/7 friendly

Post by gblues »

I know ZSNES 2.0 is under construction, but I've got an improvement for 1.51 that I'd like to share.

When I was setting up ZSNES on my new Vista laptop, I was annoyed that it wouldn't save its configuration file if I put ZSNES in C:\Program Files (because Vista blocks writing to the application directory). I've done a little Win32 programming, so I found the code to modify in zpath.c to customize where ZSNES puts its configuration files.

Getting ZSNES compiled to test the changes was something else altogether; but I finally managed to wrangle the ZSNES 1.51 codebase into the Visual Studio IDE and compiled a working executable! (If anyone is interested in the particulars of how I accomplished this, let me know and I'll post a new thread about it)

I modified zpath.c so that it will attempt to create %APPDATA%\ZSNES\ and store the config files there. If it fails, it will revert to the old behavior. This change has the added benefit of enabling each user on a PC to have their own ZSNES configuration.

I have not done anything with the other ZSNES paths yet; I'm considering adding a check for Vista/7 to utilize the "%USERPROFILE%\Saved Games" folder and create a ZSNES folder containing subfolders for IPS files, save states, SRM files, and screenshots. I am also considering making the Game > Load dialog default to My Documents (although keeping the memory of the last folder you loaded from).

Here is a diff of zpath.c that applies my changes, if anyone else is interested:

Code: Select all

--- Documents\zsnes151src.tar\zsnes_1_51\src\zpath.c	Mon Jan 15 14:47:53 2007
+++ Documents\Visual Studio 2008\Projects\zsnes2008\zsnes2008\zpath.c	Thu Oct 15 12:56:59 2009
@@ -115,11 +115,38 @@
 }
 
 #else
-
+#ifdef __WIN32__
+/*
+	Microsoft guidelines prohibit writing data to the application directory; so we create
+	a zsnes folder in the user profile's application data (%APPDATA%) folder. This has the
+	side-effect of allowing each user to have their own zsnes configuration.
+ */
+void cfgpath_ensure(const char *launch_command)
+{
+	ZCfgPath = malloc(PATH_SIZE);
+	if( ZCfgPath )
+	{
+		char appdata[MAX_PATH];
+		ZCfgAlloc = true;
+
+		if( !GetEnvironmentVariable("APPDATA", appdata, MAX_PATH) )
+			strcpy_s(ZCfgPath, PATH_SIZE, ZStartPath);
+		else
+		{
+			strcpy_s(ZCfgPath, PATH_SIZE, appdata);
+			strcat_s(ZCfgPath, PATH_SIZE, "\\zsnes");
+			strcatslash(ZCfgPath);
+			CreateDirectory(ZCfgPath, NULL);
+		}
+	} else {
+		ZCfgPath = ZStartPath;
+	}
+}
+#else
 void cfgpath_ensure(const char *launch_command)
 {
   ZCfgPath = malloc(PATH_SIZE);
   if (ZCfgPath)
   {
     char *p = 0;
     ZCfgAlloc = true;
@@ -148,11 +175,12 @@
       ZCfgPath = ZStartPath;
     }
   }
   else
   {
     ZCfgPath = ZStartPath;
   }
 }
+#endif
 
 #endif
kode54
Zealot
Posts: 1140
Joined: Wed Jul 28, 2004 3:31 am
Contact:

Post by kode54 »

Actually, the correct way to retrieve that path is to use the SHGetFolderPath function. At least I think it is. Hmm...
byuu

Post by byuu »

kode54 wrote:Actually, the correct way to retrieve that path is to use the SHGetFolderPath function. At least I think it is. Hmm...
That is correct. gblues' code will fail if the username is written in anything but English / ANSI. Of course, so will everything else (Firefox, Winamp, etc), but I digress.

Code: Select all

wchar_t fp[_MAX_PATH] = L"";
SHGetFolderPathW(0, CSIDL_APPDATA | CSIDL_FLAG_CREATE, 0, 0, fp);
Technically, it's now SHGetKnownFolderPath; if you want to break XP support.
gblues
Rookie
Posts: 22
Joined: Tue Jan 18, 2005 3:50 am

Post by gblues »

So something like this?

Code: Select all

void cfgpath_ensure(const char *launch_command)
{
	ZCfgPath = malloc(PATH_SIZE);
	if( ZCfgPath )
	{
		ZCfgAlloc = true;

		if( !SUCCEEDED( SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, ZCfgPath) ))
			strcpy_s(ZCfgPath, PATH_SIZE, ZStartPath);
		else
		{
			PathAppend(ZCfgPath, TEXT("zsnes\\") );
			CreateDirectory(ZCfgPath, NULL);
		}
	} else {
		ZCfgPath = ZStartPath;
	}
}
(and no I don't want to break XP compatibility)
gblues
Rookie
Posts: 22
Joined: Tue Jan 18, 2005 3:50 am

Post by gblues »

I've updated my custom build to store SRAM, save state, and sound (SPC) data in the user profile instead of alongside the ROM or in the ZSNES directory.

I wrote a couple helper functions; one checks the version of shell32.dll so I know whether to use SHGetKnownFolderPath or SHGetFolderPath. The other assembles the base save path; either %USERPROFILE%\Saved Games\zsnes (Vista, 7, newer) or %USERPROFILE%\My Documents\My Games\zsnes (XP and older).

It still probably clobbers non-ANSI characters, but I think converting zsnes to use wchar * for path variables would require going deeper into the zsnes plumbing than I want to go.

Here is the relevant code:

Code: Select all

#ifdef __WIN32__
/*
 * KnownFolders only exists in Windows Vista and newer--specifically, shell32.dll version
 * 6.0.6000 or newer. This function checks the version of shell32.dll at runtime and
 * returns true if it is 6.0.6000 or newer.
 *
 * Results are cached to avoid expense of loading shell32.dll everytime we want to test.
 */

bool useKnownFolders(void)
{
	static bool result = false;
	static bool count = 0;
	HINSTANCE shell32;

	if( count > 0 ) return result;

	shell32 = LoadLibrary(TEXT("shell32.dll"));
	if( shell32 )
	{
		FARPROC DllGetVersion;
		DLLVERSIONINFO version;
		version.cbSize = sizeof(DLLVERSIONINFO);

		DllGetVersion = GetProcAddress(shell32, TEXT("DllGetVersion"));
		if( !DllGetVersion )
			result = false;
		else // GetProcAddress succeeded
		{
			result = true;
			DllGetVersion(&version);
			if( version.dwMajorVersion >= 6 )
			{
				if( version.dwMajorVersion == 6 && version.dwMinorVersion == 0 )
				{
					if( version.dwBuildNumber < 6000 )
					{
						result = false;
					}
				}
			} else { // version.dwMajorVersion < 6
				result = false;
			}
		}
		FreeLibrary(shell32);
	} else {
		result = false;
	}
	count++;
	return result;
}

/*
 * This function sets the string to either %USERPROFILE%\My Documents\My Games\ZSNES\
 * (Windows XP or older) or to %USERPROFILE\Saved Games\ZSNES\ (Vista and newer).
 */

void getSaveGameDir(char *dest, size_t size)
{
	if( useKnownFolders() ) // Vista+ has a "Saved Games" known folder
	{
		PWSTR wdest;
		size_t count;
		SHGetKnownFolderPath(&FOLDERID_SavedGames, 0, NULL, &wdest);
		wcstombs_s(&count, dest, size, wdest, _TRUNCATE);
		CoTaskMemFree(wdest);
	} else { // use My Documents\My Games\zsnes
		SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dest);
		PathAppend(dest, TEXT("My Games"));
		CreateDirectory(dest, NULL); // we don't care if this fails or not.
	}
	PathAppend(dest, TEXT("zsnes\\"));
	CreateDirectory(dest, NULL);
	return;
}
#endif
The modified portion of init_save_paths():

Code: Select all

  if (*SRAMPath)
  {
    ZSramPath = SRAMPath;
  }
  else
  {
    #ifdef __UNIXSDL__
    ZSramPath = ZCfgPath;
    #else
	#ifdef __WIN32__
	  if( !(ZSramPath = malloc(MAX_PATH)) )
	    ZSramPath = ZRomPath;
	  else
	  {
		  ZSramAlloc = 1;
		  getSaveGameDir(ZSramPath, MAX_PATH);
	  }
	#else
    ZSramPath = ZRomPath;
    #endif
	#endif
  }
kode54
Zealot
Posts: 1140
Joined: Wed Jul 28, 2004 3:31 am
Contact:

Post by kode54 »

For one thing, converting to wchar_t would require _wfopen() everywhere there's fopen(). Possibly better to do it like blargg's File_Extractor 1.0. That is, convert wchar_t stuff from environment or SHGet* to UTF-8, then have a fopen() replacement that converts it back to wchar_t and calls _wfopen.

Even more fun, converting certain file find operations to wchar_t and convert to UTF-8 for Windows, for Unicode compatible ROM selection and loading. Probably not worth exploring for now, since the new GUI will probably handle this already.
gblues
Rookie
Posts: 22
Joined: Tue Jan 18, 2005 3:50 am

Post by gblues »

I'm more concerned about having to rewrite any ASM code that deals with those paths.

Anyway, I'm interested in sharing my modifications, both in the new functionality and in the work I did getting it to build in the MS Visual Studio IDE. Obviously since the code is under the GPL there is nothing stopping me from doing so, but I'm not really trying to fork the project.

If anyone is interested in trying it out, I've uploaded it to my website:

http://www.strong-consultants.com/zsnes2008.zip

This is just the EXE intended as a drop-in replacement for zsnesw.exe in the official 1.51 distribution.

The VS project works but I'm not sure is optimal. I'm certainly willing to field any questions if anyone wants to do it themselves. I should probably do some polishing first before I release anything.
kode54
Zealot
Posts: 1140
Joined: Wed Jul 28, 2004 3:31 am
Contact:

Post by kode54 »

Well, if you retrieve wchar_t and convert to UTF-8, you can store it in the char buffer. Then you only need to change every C interface function that uses filenames and paths to convert back to wchar_t and use the relevant functions. At least, I think they're all C functions now.
byuu

Post by byuu »

Probably best to stick with SHGetFolderPathA for now.

Also, another thing to consider is supporting both multi-user and single-user modes. An explanation of how I do it and why is here.
Probably not worth exploring for now, since the new GUI will probably handle this already.
I'd be surprised, not even Firefox or Winamp give two shits about proper support for non-English filenames on Windows. I can sort of see why, too, it's quite a pain in the ass to get it right.
kode54
Zealot
Posts: 1140
Joined: Wed Jul 28, 2004 3:31 am
Contact:

Post by kode54 »

foobar2000 has gotten it right since day one.

I just tested, Firefox can open files from Unicode paths. Or at least, it can open from a Japanese folder name, and I don't have my ANSI locale set to Japanese. (lol weeaboo)

I think Winamp is working on the problem, heh.

It might be a good idea to do something about it eventually. You can check out the _wfopen and MultiByteToWideChar/WideCharToMultiByte code in File_Extractor, as bundled with bsnes' snesreader source code.

Or not. Don't bother updating now, it's too much work for one person, and nobody else is doing it either anyway.
gblues
Rookie
Posts: 22
Joined: Tue Jan 18, 2005 3:50 am

Post by gblues »

byuu wrote:Probably best to stick with SHGetFolderPathA for now.

Also, another thing to consider is supporting both multi-user and single-user modes. An explanation of how I do it and why is here.
Well, unfortunately your implementation of single-user mode violates Microsoft's application guidelines that completely forbid writing into the application directory. The correct (according to MS) way would be to create both a global config (under "All Users" profile) and use that if creation of the user's config file fails (or overridden with a commandline flag). You would probably need an installer to set up the appropriate shortcuts, create the config file, and set the security levels so the config file can be modified.
Fras
Hazed
Posts: 54
Joined: Tue Jan 16, 2007 5:32 pm

Post by Fras »

gblues wrote:Microsoft's application guidelines that completely forbid writing into the application directory
Ouch. And I have never liked when programs try to write outside the application directory. >.>
Rashidi
Trooper
Posts: 515
Joined: Fri Aug 18, 2006 2:45 pm

Post by Rashidi »

Fras wrote:
gblues wrote:Microsoft's application guidelines that completely forbid writing into the application directory
Ouch. And I have never liked when programs try to write outside the application directory. >.>
not just you, i never like it either.

i believe these cumbersomeness started back then to win 3.xx.
wheres some programs would simply write anything to \windows directory (or win.ini / system.ini files) just because.
byuu

Post by byuu »

gblues wrote:Well, unfortunately your implementation of single-user mode violates Microsoft's application guidelines that completely forbid writing into the application directory. The correct (according to MS) way would be to create both a global config (under "All Users" profile) and use that if creation of the user's config file fails (or overridden with a commandline flag). You would probably need an installer to set up the appropriate shortcuts, create the config file, and set the security levels so the config file can be modified.
Hah. Even Microsoft doesn't follow its own UI guidelines. See Office for the most egregious example :)

Microsoft's version of single-user makes it impossible for one user to have more than one configuration. With my approach, you can symlink shortcuts and have per-game configurations.
gblues
Rookie
Posts: 22
Joined: Tue Jan 18, 2005 3:50 am

Post by gblues »

Hah. Even Microsoft doesn't follow its own UI guidelines. See Office for the most egregious example :)
Well, that doesn't invalidate the guideline; Apple likes to break its own UI conventions too, although they tend to be more consistent against their guidelines than Microsoft in general. I happen to agree with the guideline, especially with an app like zsnes that can create so many files (SRM, ZST, SPC, screenshots, etc). Setting up a common folder that all users can write to is a security risk and just messy. Not to mention the pain of reconfiguring resolution/filters to your preference every time. And instead of reinventing the wheel (i.e. adding profiles within the zsnes configuraton file), just use the OS user profile features.

I also don't like apps that write to Program Files because I have gigs of apps and I don't want to back them up when I can simply reinstall; therefore I don't want to include C:\Program Files in any backups I make. And I shouldn't need to.
Microsoft's version of single-user makes it impossible for one user to have more than one configuration. With my approach, you can symlink shortcuts and have per-game configurations.
You could just as easily do this via cascading configurations: load the binary defaults (hard-coded), pull the general config (either a read-only config in the app directory or from the All Users profile or whatever), then pull the user's configuration, then pull the per-game configuration; each level is able to override the levels above it (i.e. per game > user > general > hardcoded). Then you don't have to worry about symlinking (especially on OSes where symlinking is not available or not easily done).

Waxed face edit: Quote tags need the username between double quotes.

So that the world might be mended.
Last edited by gblues on Mon Oct 19, 2009 7:58 pm, edited 1 time in total.
byuu

Post by byuu »

That sounds ridiculously complex >_<
Although if you're willing to add all that, it sounds really great.

You will get people upset if they can't have their config in the same folder (USB / Portable Executable users). I did when I only hid them under %APPDATA%. Many people seem to think the settings are put in the registry when you do this, too. What does Microsoft recommend that be used for now with these appdata folders, anyway?

I also hide some really advanced functions in the config files that should never be exposed to the GUI, eg S-CPU clock rate control. Nice to have that close by if you're a tester.

But anyway, yeah. Not trying to argue my approach as superior. It works for me so that's what I go with. If you want to follow the guidelines, please do by all means :)
grinvader
ZSNES Shake Shake Prinny
Posts: 5632
Joined: Wed Jul 28, 2004 4:15 pm
Location: PAL50, dood !

Post by grinvader »

gblues wrote:I'm more concerned about having to rewrite any ASM code that deals with those paths.
It's been a real, real long time, but I think there's no ASM dealing with path code itself (anymore). Correct me if I'm wrong or smth. At least in the recent tree (that you shouldn't use anyway).

The only thing related would be the GUI working with several char arrays built from directories and their contents, but the code generating those lists is not ASM, only the display. So anything that's not single-byte-per-char ASCII (also, a weird mix of the standard ASCII 0-127 and shift-jis extension 161-223) won't show up, but the lists will be generated just fine.
皆黙って俺について来い!!

Code: Select all

<jmr> bsnes has the most accurate wiki page but it takes forever to load (or something)
Pantheon: Gideon Zhi | CaitSith2 | Nach | kode54
gblues
Rookie
Posts: 22
Joined: Tue Jan 18, 2005 3:50 am

Post by gblues »

byuu wrote:That sounds ridiculously complex >_<
Although if you're willing to add all that, it sounds really great.

You will get people upset if they can't have their config in the same folder (USB / Portable Executable users). I did when I only hid them under %APPDATA%. Many people seem to think the settings are put in the registry when you do this, too. What does Microsoft recommend that be used for now with these appdata folders, anyway?
I suppose a command switch could be added to force zsnes to load everything from the path to the executable. That might be more elegant since it is a special-use case and not something everyone does. Maybe something like --single-user or --no-profile?
Squall_Leonhart
Trooper
Posts: 369
Joined: Tue Jun 10, 2008 6:19 am
Location: Australia
Contact:

Post by Squall_Leonhart »

heres an idea... don't use program files for emulators.

omg! the logic is astounding.
[img]http://img.photobucket.com/albums/v253/squall_leonhart69r/Final_Fantasy_8/squall_sig1.gif[/img]
[url=http://vba-m.com/]VBA-M Forum[/url], [url=http://www.ngohq.com]NGOHQ[/url]
adventure_of_link
Locksmith of Hyrule
Posts: 3634
Joined: Sun Aug 08, 2004 7:49 am
Location: 255.255.255.255
Contact:

Post by adventure_of_link »

Squall_Leonhart wrote:heres an idea... don't use program files for emulators.

omg! the logic is astounding.
well here's an idea: a software-based emulator is a PROGRAM, and therefore could be classified as being placed in that folder.

omg! the logic is astounding.

:P

</smartass>
<Nach> so why don't the two of you get your own room and leave us alone with this stupidity of yours?
NSRT here.
gblues
Rookie
Posts: 22
Joined: Tue Jan 18, 2005 3:50 am

Post by gblues »

Squall_Leonhart wrote:heres an idea... don't use program files for emulators.

omg! the logic is astounding.
Let's pretend you've got a windows system with a user account for you and your brother. You both want to use ZSNES. Where do you suggest putting it so that both you and your brother can play it?

Yeah. Program Files exists for a reason.

Further, what if you like to use the hq2x filter, but your brother prefers normal2x with scanlines? If you throw the 1.51 release into C:\Program Files (and rig it so C:\Program Files\ZSNES is writeable, which is a BAD idea), you and your brother are going to be clobbering each other's settings each time you load ZSNES.

My patch allows you to each have your own configuration so you can have your hq2x and your brother can have his scanlines and never the twain shall meet.

Since it also stores the SRM/ZST files in the user profile, you don't have to worry about your brother "accidently" saving over your games/save states.

If you want to go live in your own little planet where users run applications from their home directory and your PC is littered with 10 copies of ZSNES, well the official release is perfect for you. If you're like me and like to put applications where they belong and like them to work like they are supposed to, well the link to my custom build is a couple posts above.
funkyass
"God"
Posts: 1128
Joined: Tue Jul 27, 2004 11:24 pm

Post by funkyass »

gblues wrote:
I suppose a command switch could be added to force zsnes to load everything from the path to the executable. That might be more elegant since it is a special-use case and not something everyone does. Maybe something like --single-user or --no-profile?
could do it the way bsnes does it.
Does [Kevin] Smith masturbate with steel wool too?

- Yes, but don’t change the subject.
byuu

Post by byuu »

Oh, I tested it earlier today to verify. If a file or folder has non-ANSI characters, it simply doesn't show up in the ZSNES lists. Which is a heck of a lot better than showing up as garble and failing to load, like most programs do. So no reason to waste your time with the W variant.

Though I would have loved to see a full-Unicode 5x5 font. Especially this one ;)
Image
gblues wrote:If you want to go live in your own little planet where users run applications from their home directory and your PC is littered with 10 copies of ZSNES, well the official release is perfect for you. If you're like me and like to put applications where they belong and like them to work like they are supposed to, well the link to my custom build is a couple posts above.
And how many people are like you and have a sibling sharing their PC and both want to play a 16-year-old gaming console emulator so frequently that it's a hassle to have their settings overwritten by each other? :P

I'm teasing, of course. I fully support what you're doing, but if you got that into ZSNES official I guarantee people would complain about not being able to find the config files, or about not wanting to leave traces behind on computers. Just saying what I've been told about the idea, obviously I stick with it because I think multi-user and read-only program folders are important concepts.
could do it the way bsnes does it.
Indeed, for my approach the user has to manually create the bsnes.cfg file in the same directory as the executable, otherwise it'll go multi-user. That takes a deliberate action.
grinvader
ZSNES Shake Shake Prinny
Posts: 5632
Joined: Wed Jul 28, 2004 4:15 pm
Location: PAL50, dood !

Post by grinvader »

byuu wrote:Though I would have loved to see a full-Unicode 5x5 font. Especially this one ;)
Image

Code: Select all

   █
█████
█████
█████
  ███
Crystal clear.
if you got that into ZSNES official I guarantee people would complain about not being able to find the config files, ...
Well, there's also the fact that we kinda have a dedicated compile time option for a very related purpose, but who even reads the compiling notes anyway ?
皆黙って俺について来い!!

Code: Select all

<jmr> bsnes has the most accurate wiki page but it takes forever to load (or something)
Pantheon: Gideon Zhi | CaitSith2 | Nach | kode54
FitzRoy
Veteran
Posts: 861
Joined: Wed Aug 04, 2004 5:43 pm
Location: Sloop

Post by FitzRoy »

byuu wrote:And how many people are like you and have a sibling sharing their PC and both want to play a 16-year-old gaming console emulator so frequently that it's a hassle to have their settings overwritten by each other? :P

I'm teasing, of course.
Wow, you understand my argument after all, although he doesn't really have to "put up" with anything. It's more a question of which is worse: requiring 1% of users to occasionally make copies of programs or make 99% of users hunt down a hidden folder 50 miles away from the executable just to restore defaults or prepare it for portable usage, and that's assuming people read the documentation to know your program has the capacity. It's not exactly readily apparent that a file named "bsnes.cfg" can be placed in the directory to avoid having to setup preferences each time you put the stick in a new computer at the lab.
Post Reply