Win 8 directory mayhem

BlitzMax Forums/BlitzMax Programming/Win 8 directory mayhem

Robby(Posted 2015) [#1]
Hi all! Having a big problem with my app! It is set to save and load files into
the directory it is installed to, and works fine - at least on win xp,vista,7.

But when I ran it in Windows 8.1, a fresh install after deleting everything from a previous install, I noticed the OLD files were loading into my app - from the previous install, which I had removed.

I did a search, and found that not only was my app saving files in the directory where the app was installed, but also just about everywhere else!!

Places like c:\users\robby\appdata\roaming\microsoft\windows
c:\users\robby\appdata\virtualstore and many other odd places!!

Some of these are labeled as "shortcuts". My files were indeed in my main folder, but nearly 20 other places as well!

What's going on? Is there any way I can insure the app saves and loads files only in the directory in which it is installed? Usually that's in the c:\programfiles i86 dir, but the user has a choice as to where to install.

My product was ready for release, now this major problem.

Desperate for a little advice! Thanks!! -Rob


Yasha(Posted 2015) [#2]
Basically, no. Installed programs aren't really supposed to do that. It "appeared to work" (i.e. that business with the virtual store) as a backward compatibility feature, because Windows has a lot of those (one that I think is usually disabled?).

You can do one of two things:

-- make a "portable" deployment, which doesn't get installed to a system-aware folder (because it doesn't get installed at all); if the program simply runs out of a Documents subdirectory, it's fine to save files where it is. If the files move, the program moves with them, and users often have their own copies. This is only really practical for small utilities, and it has the disadvantage that a user might still try to put it in a protected folder and then it stops working as expected.

-- design your application to save to a fixed location queried from the system, like a My Whatever or one of the local user data hidden folders. This is what everyone else usually does. It has the advantage that a user doesn't lose their files if the program needs to be uninstalled or if they move their work to another machine, and multiple users can share the program.

Writing to a Program Files folder during operation is a relic of the twentieth century that's only still possible because Microsoft hate killing old software (and enable buggy designs as a result). It's not actually supposed to be a design option for new software.


Brucey(Posted 2015) [#3]
Is there any way I can insure the app saves and loads files only in the directory in which it is installed? Usually that's in the c:\programfiles i86 dir

Program Files *should* be a protected folder. Your app shouldn't really be writing anything in there. That's what the user/app data folders are for.
On the new versions of Windows, I believe (someone can correct me if I'm wrong) the OS will not let your app write into Program Files at all, and uses some kind of virtual storage area that mimics that folder structure.


Robby(Posted 2015) [#4]
Thanks for the info. Probably be safest to write to other then the Program Files.

However, in tests, I think its not so much my Blitz app as much as the installer.
The excess of file locations only appeared when I ran the installer, which was made with
Inno Setup Compiler.

I'll fiddle with this and check back. Thanks again!


xlsior(Posted 2015) [#5]
When it comes to file locations, you need to follow Microsoft's guidelines, it's the only way that your program 'should' work on any computer.
MS says that no program is allowed to write to the program files folders after the initial program install, you have to use those other folders. you can dink around with folder permissions on your own PC and get it to let you write to program files normally, but that obviously won't be a working strategy when you intent to release your program to the world.
Not following the 'rules' could also keep your program from getting accepted by some publishers, and rightfully so -- any new windows version could stop your workarounds in their tracks.

However, keep in mind that you cannot just hardcode those paths either -- they will be called different on non-English windows versions, and if you hardcode c:\users in your program it will fail on those. there are windwos API calls that your program can query that will return the proper locations that the program should be writing to -- there's a few different modules for blitzmax that allow you to query those paths.

Another big reason to use the API calls is that if Microsoft changes their minds again with some future windows version, the old API calls should automatically be redirected to the new proper location for backwards compatibility support.


Robby(Posted 2015) [#6]
Thanks again for all the help. My usual method for saving out files is just making
a stream like this - saves to the current directory where the app is:

' (induntest is an integer made elsewhere)
savext = ".svn"
filename = "induntest" + savext
CreateFile(filename)
Local indungsvn:TStream = OpenStream(filename)
WriteInt(indungsvn, induntest)
CloseFile(indungsvn))

I found a little function somewhere here on the forum (see below)
It does seem to find the AppData folder, and gives me this for files_path$:

C:\Users\Robby\AppData\Roaming/

I'm not sure if that's a typo, the "/" instead of "\"?

More importantly, I'm not sure how to attach my save variables, like induntest.svn,
to the C:\Users\Robby\AppData\Roaming/ path found?

Don't I need a sub-dir first? Like:
C:\Users\Robby\AppData\Roaming/gamesaves

How would I make that? There's several dozen variables that get saved out,
and read in as well. I'm almost there, with the rest of the app done! Thanks!


Const CSIDL_APPDATA = $1a
Const CSIDL_COMMON_APPDATA = $23
Global appdata_path_ptr:Byte Ptr, files_path$

Extern "win32"
Function SHGetFolderPathA(hWnd:Int, folder:Int, token:Int, FLAGS:Int, str:Byte Ptr)
End Extern


Function GetAppDataFolder()
Local res, dir, cnt
appdata_path_ptr = New Byte[260]
res = SHGetFolderPathA(Null, CSIDL_APPDATA, Null, 1, appdata_path_ptr)
cnt = 0

While (appdata_path_ptr[cnt] <> 0)
cnt :+ 1
Wend

files_path$ = String.FromBytes(appdata_path_ptr, cnt)
files_path$ :+ "/" + AppName$

EndFunction


Brucey(Posted 2015) [#7]
You probably want to create a folder for your specific game - usually the name of your game, or perhaps one for your company, and inside that, another for your game.
Inside that folder, you may wish to create several different folders if you intend to separate out different kinds of data - saved/resources/etc.

One other thing to note is that SHGetFolderPathA() isn't always 100% effective - depending on your particular circumstances. Microsoft recommend you rather use SHGetKnownFolderPath if it is available.


xlsior(Posted 2015) [#8]
I'm not sure if that's a typo, the "/" instead of "\"?


They're mostly interchangeable on Windows.


Pingus(Posted 2015) [#9]
You may have a look there for a similar issue:

http://www.blitzbasic.com/Community/posts.php?topic=104301#1262754


Robby(Posted 2015) [#10]
Ok - I got this to work. It actually finds the appdata folder and I can save a file to it!
The trouble is that it keeps re-creating the directory even if it exists. I thought FileType() could determine if a directory exists as well as a file?

You can copy & paste this code as is to see. Let me know if I got this right. Thanks.

Const CSIDL_APPDATA = $1a
Const CSIDL_COMMON_APPDATA = $23
Global appdata_path_ptr:Byte Ptr
Global files_path:String, savext:String
Global files_path_test:String
Global induntest:Int, filename:String

induntest = 5

Extern "win32"
Function SHGetFolderPathA(hWnd:Int, folder:Int, token:Int, FLAGS:Int, str:Byte Ptr)
End Extern

' Get the correct appdata folder
GetAppDataFolder()

Print "Folder from sub: "
Print files_path


' put it in a test string to test
files_path_test = files_path

' add my games save directory
files_path_test = files_path_test + "\\madman_data\\"

Print "Folder to test:"
Print files_path_test


' If the folder does not exist, make it (is <> 2 the same?)
If FileType(files_path_test) = 0 Then
CreateDir(files_path_test)
Print "CREATED DIR"
End If

' But this still shows zero! Why not 2, now that it exists?
Print FileType(files_path_test)


' append a typical save file to test
filename = files_path_test + "\induntest.svn"

' save it out
CreateFile(filename)
Local indungsvn2:TStream = OpenStream(filename)
WriteInt(indungsvn2, induntest)
CloseFile(indungsvn2)

' exists? = 1 works!!!
Print FileType(filename)


End

Function GetAppDataFolder()
Local res, dir, cnt

appdata_path_ptr = New Byte[260]

res = SHGetFolderPathA(Null, CSIDL_APPDATA, Null, 1, appdata_path_ptr)
cnt = 0
While (appdata_path_ptr[cnt] <> 0)
cnt :+ 1
Wend
files_path = String.FromBytes(appdata_path_ptr, cnt)

'files_path :+ "/" + AppName$
'files_path :+ "\mad_dat\"
'Print files_path
'Print AppTitle
EndFunction


Robby(Posted 2015) [#11]
OK - I'm fine! I was checking for "\\madman_data\\" but when created,
its actually c:\users\robby\appdata\roaming\madman_data so I append another variable with just "\madman_data" appended and all is well!

I just hope it works on most Windows versions.